/Emacs

Primary LanguageEmacs Lisp

Personal emacs configuration

Early Init

 ;;; early-init.el --- Early Init File -*- lexical-binding: t; no-byte-compile: t -*-
 ;; Defer garbage collection further back in the startup process
 (setq gc-cons-threshold most-positive-fixnum
	gc-cons-percentage 0.6)

 ;; (setq package-enable-at-startup nil)
 ;; (setq package-quickstart nil)
 ;; Prevent the glimpse of un-styled Emacs by disabling these UI elements early.
 (setq inhibit-startup-message t)
 (setq inhibit-splash-screen t)
 (setq inhibit-compacting-font-caches t)

 (push '(menu-bar-lines . 0) default-frame-alist)
 (push '(tool-bar-lines . 0) default-frame-alist)
 (push '(vertical-scroll-bars) default-frame-alist)
 (push '(fullscreen . maximized) default-frame-alist)
 ;; Resizing the Emacs frame can be a terribly expensive part of changing the
 ;; font. By inhibiting this, we easily halve startup times with fonts that are
 ;; larger than the system default.
 ;; (setq frame-inhibit-implied-resize t)
 ;; (setq use-file-dialog nil)
 ;; (setq use-dialog-box nil)
 ;; Make the initial buffer load faster by setting its mode to fundamental-mode
 ;; (setq initial-major-mode 'fundamental-mode)
 ;; Prevent unwanted runtime builds in gccemacs (native-comp); packages are
 ;; compiled ahead-of-time when they are installed and site files are compiled
 ;; when gccemacs is installed.
 ;; (setq comp-deferred-compilation nil)
 ;; Disable mode-line, It's uglily after theme changed
 ;; (setq-default mode-line-format nil)
 ;;; early-init.el ends here

Rudimentary Configuration

Lexical Binding

关于 lexical-binding 的作用见 Make Emacs run (slightly) faster with lexical binding . 或者 Lisp 已死,Lisp **!

;; init.el --- Personal Emacs Configuration -*- lexical-binding: t; -*-
;;; Commentary:
;;; Code:
(defvar my/init-start-time (current-time) "Time when init.el was started.")
(defvar my/section-start-time (current-time) "Time when section was started.")

Speedup

可以省个0.15s 启动时间。

;; https://github.com/seagle0128/.emacs.d/blob/master/init.el
(setq auto-mode-case-fold nil)

(unless (or (daemonp) noninteractive init-file-debug)
  (let ((old-file-name-handler-alist file-name-handler-alist))
    (setq file-name-handler-alist nil)
    (add-hook 'emacs-startup-hook
              (lambda ()
                "Recover file name handlers."
                (setq file-name-handler-alist
                      (delete-dups (append file-name-handler-alist
                                           old-file-name-handler-alist)))))))

;; Defer garbage collection further back in the startup process
(setq gc-cons-threshold most-positive-fixnum)
(add-hook 'emacs-startup-hook
          (lambda ()
            "Recover GC values after startup."
            (setq gc-cons-threshold 800000)))

;; Suppress flashing at startup
(setq-default inhibit-redisplay t
              inhibit-message t)
(add-hook 'window-setup-hook
          (lambda ()
            (setq-default inhibit-redisplay nil
                          inhibit-message nil)
            (redisplay)))

Straight

 ;; (require 'package)
 ;; (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
 ;; (package-initialize)
 (defvar bootstrap-version)
 (let ((bootstrap-file
	 (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
	(bootstrap-version 6))
   (unless (file-exists-p bootstrap-file)
     (with-current-buffer
	  (url-retrieve-synchronously
	   "https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
	   'silent 'inhibit-cookies)
	(goto-char (point-max))
	(eval-print-last-sexp)))
   (load bootstrap-file nil 'nomessage))

Benchmark

(straight-use-package 'benchmark-init)
(require 'benchmark-init)
(add-hook 'after-init-hook 'benchmark-init/deactivate)
(straight-use-package 'esup)

Variables

自定义一些变量,方便配置文件位置或针对特定系统进行相关设定。

(defvar my-cloud "~/Nextcloud"
  "This folder is My cloud.")
(defvar my-galaxy (expand-file-name "L.Personal.Galaxy" my-cloud)
  "This folder stores all the plain text files of my life.")
(defvar website-directory "~/Nextcloud/L.Personal.Galaxy/website"
  "The source folder of my blog")
(defvar my/publish-directory "~/shuyi.github.io")

很多包默认会在 Emacs 用户文件夹中生成很多文件夹放置临时文件,拉屎行为。Keep emacs directory clean。

(straight-use-package 'no-littering)

(require 'no-littering)
(straight-use-package 'epkg)
(straight-use-package 'compat)
(straight-use-package 'closql)
(straight-use-package 'emacsql-sqlite)
(straight-use-package 'epkg-marginalia)

(with-eval-after-load 'marginalia
  (cl-pushnew 'epkg-marginalia-annotate-package
        (alist-get 'package marginalia-annotator-registry)))

PATH

Mac 上会提示找不到程序所在位置,一个解决方式是使用exec-path-from-shell,但是这个会导致 Emacs 启动慢 0.5s 左右。

;; https://www.emacswiki.org/emacs/ExecPath
(defun set-exec-path-from-shell-PATH ()
  "Set up Emacs' `exec-path' and PATH environment variable to match
that used by the user's shell.

This is particularly useful under Mac OS X and macOS, where GUI
apps are not started from a shell."
  (interactive)
  (let ((path-from-shell (replace-regexp-in-string
              "[ \t\n]*$" "" (shell-command-to-string
                      "$SHELL --login -c 'echo $PATH'"
                            ))))
    (setenv "PATH" path-from-shell)
    (setq exec-path (split-string path-from-shell path-separator))))

(set-exec-path-from-shell-PATH)

Evil, general and evil-collection

(straight-use-package 'evil)

(setq evil-want-keybinding nil)

(setq evil-undo-system 'undo-fu)

(evil-mode 1)
(straight-use-package 'general)
(straight-use-package 'evil-collection)
(add-hook 'after-init-hook 'evil-collection-init)

Which-key

(straight-use-package 'which-key)

(which-key-mode 1)
(with-eval-after-load 'which-key
  (setq which-key-idle-delay 0.3))

Server

(server-mode)

Restart emacs

(straight-use-package 'restart-emacs)

(general-define-key
 :keymaps '(normal visual emacs)
 :prefix "SPC"
 :non-normal-prefix "M-SPC"
 "q" '(:ignore t :wk "Quit/Restart")
 "qR" '(restart-emacs :wk "Restart emacs"))

Magit

(straight-use-package 'magit)

Auto tangle

(straight-use-package 'org-auto-tangle)

(add-hook 'org-mode-hook 'org-auto-tangle-mode)

Recentf

(add-hook 'after-init-hook 'recentf-mode)
(setq recentf-max-saved-items 1000)
(setq recentf-exclude nil)

(general-define-key
 :states '(normal visual emacs)
 :prefix "SPC"
 :non-normal-prefix "M-SPC"
 "ff" '(find-file :wk "Find file")
 "fr" '(recentf-open-files :wk "Recent files"))

Auto-save

(straight-use-package '(auto-save :type git :host github :repo "manateelazycat/auto-save"))
(require 'auto-save)
(setq auto-save-silent t)
(setq auto-save-delete-trailing-whitespace t)
(add-hook 'after-init-hook 'auto-save-enable)

undo-fu, undo-fu-session and vundo

(straight-use-package 'undo-fu)

(straight-use-package 'undo-fu-session)
(add-hook 'after-init-hook 'global-undo-fu-session-mode)

(straight-use-package 'vundo)
(with-eval-after-load 'vundo
  (setq vundo-glyph-alist vundo-unicode-symbols))
(global-set-key (kbd "C-x u") 'vundo)
(toggle-frame-fullscreen)
 (set-frame-font "Iosevka Fixed 16" nil t)
 (if (display-graphic-p)
     (dolist (charset '(kana han cjk-misc bopomofo))
	(set-fontset-font (frame-parameter nil 'font)
			  charset (font-spec :family "Source Han Serif SC" :height 160))))
(straight-use-package 'doom-themes)
(defun my/apply-theme (appearance)
  "Load theme, taking current system APPEARANCE into consideration."
  (mapc #'disable-theme custom-enabled-themes)
  (pcase appearance
    ('light (load-theme 'doom-nord-light t))
    ('dark (load-theme 'doom-nord t))))
(add-hook 'ns-system-appearance-change-functions #'my/apply-theme)

Misc

(defun my/emacs-config ()
  "My literate Emacs configuration."
  (interactive)
  (find-file (expand-file-name "README.org" user-emacs-directory)))

(general-define-key
 :keymaps '(normal visual emacs)
 :prefix "SPC"
 :non-normal-prefix "M-SPC"
 "f" '(:ignore t :wk "Files")
 "fi" '(my/emacs-config :wk "Emacs configuration"))
(defun switch-to-message ()
  "Quick switch to `*Message*' buffer."
  (interactive)
  (switch-to-buffer "*Messages*"))

(defun switch-to-scratch ()
  "Quick switch to `*Scratch*' buffer."
  (interactive)
  (switch-to-buffer "*scratch*"))

(general-define-key
 :keymaps '(normal visual emacs)
 :prefix "SPC"
 :non-normal-prefix "M-SPC"
 "b" '(:ignore t :wk "Buffer/Bibtex")
 "bb" '(switch-to-buffer :wk "Switch buffer")
 "be" '(eval-buffer :wk "Eval buffer")
 "bs" '(switch-to-scratch :wk "Swtich to scratch")
 "bm" '(switch-to-message :wk "Swtich to message"))

(general-define-key
 :states '(normal visual emacs)
 :prefix "SPC"
 :non-normal-prefix "M-SPC"
 ";e" '(epkg-describe-package :wk "Epkg"))
 (defun toggle-proxy ()
   "Toggle proxy for the url.el library."
   (interactive)
   (if url-proxy-services
	(proxy-disable)
     (proxy-enable)))

 (defun proxy-enable ()
   "Enable proxy."
   (interactive)
   (setq url-proxy-services
	    '(("http" . "127.0.0.1:8889")
	      ("https" . "127.0.0.1:8889")
	      ("no_proxy" . "0.0.0.0")))
   (message "Proxy enabled! %s" (car url-proxy-services)))

 (defun proxy-disable ()
   "Disable proxy."
   (interactive)
   (if url-proxy-services
	(setq url-proxy-services nil))
   (message "Proxy disabled!"))

 (proxy-enable)

 (general-define-key
  :keymaps '(normal visual emacs)
  :prefix "SPC"
  :non-normal-prefix "M-SPC"
  "t" '(:ignore t :wk "Toggles")
  "tp" '(proxy-enable :wk "Enable proxy")
  "tP" '(proxy-disable :wk "Disable proxy"))

Personal Info

(setq user-full-name "Duan Ning")
(setq user-mail-address "duan_n@outlook.com")

Emacs User Interface, Delicious

(message "Rudimentary Configuration: %.2fs"
         (float-time (time-subtract (current-time) my/section-start-time)))

(setq my/section-start-time (current-time))

Icons

(straight-use-package 'all-the-icons)

(when (display-graphic-p)
  (require 'all-the-icons))

(with-eval-after-load 'all-the-icons
  (set-fontset-font "fontset-default" 'unicode (font-spec :family "all-the-icons"))  ;;这里不能用 append,否则不工作。
  (set-fontset-font "fontset-default" 'unicode (font-spec :family "file-icons") nil 'append)
  (set-fontset-font "fontset-default" 'unicode (font-spec :family "Material Icons") nil 'append))

All-the-icons-completion

(straight-use-package 'all-the-icons-completion)

(all-the-icons-completion-mode)
(add-hook 'marginalia-mode-hook #'all-the-icons-completion-marginalia-setup)

Fringe bitmap

Word wrap at window edge, hide the right and left curly arrow. So ugly.

(define-fringe-bitmap 'right-curly-arrow  [])
(define-fringe-bitmap 'left-curly-arrow  [])

Highlight line

(global-hl-line-mode)

Menu-bar

(add-hook 'org-mode-hook 'menu-bar--wrap-long-lines-window-edge)
(add-hook 'text-mode-hook 'menu-bar--display-line-numbers-mode-relative)
(add-hook 'prog-mode-hook 'menu-bar--display-line-numbers-mode-relative)

Time

(with-eval-after-load 'time
  (setq display-time-24hr-format t)
  (setq display-time-format "%m/%d %H:%M %a")
  (setq display-time-load-average-threshold nil))

(add-hook 'after-init-hook 'display-time-mode 20)

Modeline

(straight-use-package 'doom-modeline)

(add-hook 'after-init-hook 'doom-modeline-mode)

(with-eval-after-load 'doom-modeline
  (setq doom-modeline-icon t)
  (setq doom-modeline-height 20))

Cursor color

(straight-use-package '(im-cursor-chg :type git :host github :repo "Jousimies/im-cursor-chg"))

(cursor-chg-mode)

(with-eval-after-load 'im-cursor-chg
  (setq im-cursor-color "red"))

Battery

(setq battery-load-critical 15)
(setq battery-mode-line-format " %b%p% ")
(add-hook 'after-init-hook 'display-battery-mode 10)

Beacon

(straight-use-package 'beacon)

(add-hook 'after-init-hook 'beacon-mode)

Paren

(setq show-paren-style 'mixed)
(setq show-paren-context-when-offscreen 'overlay)

(add-hook 'text-mode-hook 'show-paren-mode)

Rainbow

(straight-use-package 'rainbow-mode)

(add-hook 'prog-mode-hook 'rainbow-mode)
(straight-use-package 'rainbow-delimiters)

(add-hook 'prog-mode-hook 'rainbow-delimiters-mode)

Electric

(add-hook 'text-mode-hook 'electric-pair-mode)

Prettify symbols

(setq prettify-symbols-alist '(("lambda" . )
                               ("function" . ?𝑓)))
(add-hook 'prog-mode-hook 'prettify-symbols-mode)

Dashboard

(straight-use-package 'dashboard)

(setq dashboard-startup-banner (expand-file-name "banner.txt" user-emacs-directory))
(setq dashboard-center-content t)
(setq dashboard-set-init-info t)
;; (setq dashboard-set-file-icons t)
;; (setq dashboard-items '((recents  . 5)
;;                         (bookmarks . 5)
;;                         (registers . 5)))
(setq dashboard-items nil)
;; (setq dashboard-set-navigator t)
(add-hook 'after-init-hook 'dashboard-setup-startup-hook)

Powerful Emacs Equipped with Builtin Packages

Better default

(setq ring-bell-function 'ignore)
(setq use-short-answers t)
(setq read-process-output-max #x10000)
(setq message-kill-buffer-on-exit t)
(setq message-kill-buffer-query nil)
(setq-default indent-tabs-mode nil)
(setq-default tab-width 4)

system coding

(when (fboundp 'set-charset-priority)
  (set-charset-priority 'unicode))

(prefer-coding-system 'utf-8)
(set-default-coding-systems 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)

delete selection

插入文本会将所选文本删除,这在其他的很多软件中都有,Emacs 中默认没有,需要手动开启。

(delete-selection-mode 1)

autorevert

(setq auto-revert-verbose t)
(global-auto-revert-mode 1)

save hist

Toggle saving of minibuffer history.

(setq history-length 1000)
(setq savehist-save-minibuffer-history 1)
(setq savehist-additional-variables '(kill-ring
				  search-ring
				  regexp-search-ring))
(setq history-delete-duplicates t)
(add-hook 'after-init-hook 'savehist-mode)

save place

(add-hook 'after-init-hook 'save-place-mode)

midnight

(add-hook 'after-init-hook 'midnight-mode)

minibuffer

(setq minibuffer-prompt-properties
      '(read-only t cursor-intangible t face minibuffer-prompt))

so-long

Emacs 的长行检测。在 Emacs 中当编辑长行时,会很卡,开启此模式可以提高性能。

(add-hook 'text-mode-hook 'global-so-long-mode)

large file

(setq large-file-warning-threshold nil)

hippie

妙啊,Hippie-expand 的功能是这么的好用,我原来输入路径需要使用 cape-file,现在使用 hippie-expand 就好了。它还有其他的功能,介绍性的说明可以看这

设置 hippie-expand-try-functions-list ,把 try-expand-listtry-expand-line 去掉,他们会在末尾增加括号,有点多余。我己经使用 elec-pair 自动成对插入括号。

(setq hippie-expand-try-functions-list '(try-complete-file-name-partially
					   try-complete-file-name
					   try-expand-all-abbrevs
					   try-expand-dabbrev
					   try-expand-dabbrev-all-buffers
					   try-expand-dabbrev-from-kill
					   try-complete-lisp-symbol-partially
					   try-complete-lisp-symbol))

(global-set-key [remap dabbrev-expand] 'hippie-expand)

Winner

(add-hook 'after-init-hook 'winner-mode)

Awesome Emacs Equipped with Third-Party Packages

Corfu, corfu-doc and kind-icon

(straight-use-package 'corfu)

(global-corfu-mode)

(with-eval-after-load 'corfu
  (setq corfu-auto t)
  (setq corfu-cycle t)
  (setq corfu-quit-at-boundary t)
  (setq corfu-auto-prefix 2)
  (setq corfu-preselect-first t)
  (setq corfu-quit-no-match t)
  (setq completion-cycle-threshold 3))

(defun corfu-enable-always-in-minibuffer ()
  "Enable Corfu in the minibuffer if Vertico/Mct are not active."
  (unless (or (bound-and-true-p mct--active)
      (bound-and-true-p vertico--input))
    (corfu-mode 1)))
(add-hook 'minibuffer-setup-hook #'corfu-enable-always-in-minibuffer 1)

(straight-use-package 'corfu-doc)

(add-hook 'corfu-mode-hook 'corfu-doc-mode)

(straight-use-package 'kind-icon)

(setq kind-icon-default-face 'corfu-default)

(add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter)

Vertico

(straight-use-package '(vertico
                        :files (:defaults "extensions/*")
                        :includes (vertico-directory)))

(vertico-mode)

(with-eval-after-load 'vertico
  (define-key vertico-map (kbd "C-j") 'vertico-directory-up)
  (setq vertico-cycle t)
  (setq completion-in-region-function
    (lambda (&rest args)
      (apply (if vertico-mode
             #'consult-completion-in-region
           #'completion--in-region)
         args))))


(setq read-file-name-completion-ignore-case t
  read-buffer-completion-ignore-case t
  completion-ignore-case t)

orderless

(straight-use-package 'orderless)
(setq completion-styles '(orderless partial-completion)
      completion-category-defaults nil
      completion-category-overrides '((file (styles . (partial-completion)))))

marginalia

(straight-use-package 'marginalia)
(add-hook 'minibuffer-setup-hook 'marginalia-mode)

;; (with-eval-after-load 'marginalia
;;   (cl-pushnew 'epkg-marginalia-annotate-package
;; 	(alist-get 'package marginalia-annotator-registry)))

consult

(straight-use-package 'consult)

(add-hook 'completion-list-mode-hook 'consult-preview-at-point-mode)

(global-set-key [remap apropos] 'consult-apropos)
(global-set-key [remap bookmark-jump] 'consult-bookmark)

(global-set-key [remap goto-line] 'consult-goto-line)
(global-set-key [remap imenu] 'consult-imenu)
(global-set-key [remap locate] 'consult-locate)
(global-set-key [remap load-theme] 'consult-theme)
(global-set-key [remap man] 'consult-man)
(global-set-key [remap recentf-open-files] 'consult-recent-file)
(global-set-key [remap switch-to-buffer] 'consult-buffer)
(global-set-key [remap switch-to-buffer-other-window] 'consult-buffer-other-window)
(global-set-key [remap switch-to-buffer-other-frame] 'consult-buffer-other-frame)
(global-set-key [remap yank-pop] 'consult-yank-pop)

(with-eval-after-load 'evil
  (evil-declare-key 'normal org-mode-map
    "gh" 'consult-outline))

consult-dir

(straight-use-package 'consult-dir)

(with-eval-after-load 'consult-dir
  (global-set-key (kbd "C-x C-d") 'consult-dir)
  (with-eval-after-load 'vertico
    (define-key vertico-map (kbd "C-x C-d") 'consult-dir)
    (define-key vertico-map (kbd "C-x C-j") 'consult-dir-jump-file)))

embark

(straight-use-package 'embark)
(with-eval-after-load 'embark
  (setq prefix-help-command #'embark-prefix-help-command))

rime

我使用 emacs-rime 和三码郑码,不需要进行词库的维护。~rime-show-candidate~ 使用 posframe 对于性能还是有消耗的,所以日常使用 minibuffer 就可以了。

(straight-use-package 'rime)

(setq rime-user-data-dir "~/Library/Rime/")
(setq rime-emacs-module-header-root "/opt/homebrew/Cellar/emacs-plus@28/28.2/include")
(setq rime-librime-root (expand-file-name "librime/dist" user-emacs-directory))
(setq default-input-method "rime")
;; (setq rime-title `(,(propertize (all-the-icons-faicon "pencil-square-o" :v-adjust -0.1)
;;                                'face `(:family ,(all-the-icons-faicon-family)))))
(setq rime-show-candidate 'minibuffer)
(setq rime-posframe-properties '(:internal-border-width 0))
(setq rime-disable-predicates '(rime-predicate-prog-in-code-p
				  rime-predicate-org-in-src-block-p
				  rime-predicate-org-latex-mode-p
				  rime-predicate-current-uppercase-letter-p))

(setq rime-inline-predicates '(rime-predicate-space-after-cc-p
				 rime-predicate-after-alphabet-char-p))

(with-eval-after-load 'rime
  (define-key rime-mode-map (kbd "M-j") 'rime-force-enable))

(with-eval-after-load 'evil
  (add-hook 'evil-insert-state-entry-hook (lambda ()
					      (if (eq major-mode 'org-mode)
						  (activate-input-method "rime"))))
  (add-hook 'evil-insert-state-exit-hook #'evil-deactivate-input-method))

rime-regexp

使用拼音进行中文的检索。原来使用的是拼音首字母进行检索,但是检出来的结果太多,所以还是使用全拼音进行检索。

(straight-use-package '(rime-regexp :type git :host github :repo "colawithsauce/rime-regexp.el"))

(rime-regexp-mode 1)

hungry delete

(straight-use-package 'hungry-delete)
(global-hungry-delete-mode)

Search engine

(straight-use-package 'engine-mode)
(with-eval-after-load 'engine-mode
  (defengine google "https://google.com/search?q=%s"
             :keybinding "g"
             :docstring "Search Google.")
  (defengine wikipedia "https://en.wikipedia.org/wiki/Special:Search?search=%s"
             :keybinding "w"
             :docstring "Search Wikipedia.")
  (defengine github "https://github.com/search?ref=simplesearch&q=%s"
             :keybinding "h"
             :docstring "Search GitHub.")
  (defengine baidu "https://www.baidu.com/s?ie=utf-8&wd="
             :keybinding "b"
             :docstring "Search Baidu.")
  (defengine youtube "http://www.youtube.com/results?aq=f&oq=&search_query=%s"
             :keybinding "y"
             :docstring "Search YouTube.")
  (defengine moviedouban "https://search.douban.com/movie/subject_search?search_text=%s"
             :keybinding "m"
             :docstring "Search Moive DouBan.")
  (defengine zhihu "https://www.zhihu.com/search?type=content&q=%s"
             :keybinding "z"
             :docstring "Search Zhihu."))
(add-hook 'after-init-hook 'engine-mode)


(general-define-key
 :states '(normal visual emacs)
 :prefix "SPC"
 :non-normal-prefix "M-SPC"
 "s" '(:ignore t :wk "Search")
 "sb" '(engine/search-baidu :wk "Baidu")
 "ss" '(engine/search-google :wk "Google")
 "sG" '(engine/search-github :wk "Github")
 "sy" '(engine/search-youtube :wk "Youtube")
 "sw" '(engine/search-wikipedia :wk "Wikipedia")
 "sm" '(engine/search-moviedouban :wk "Movie DouBan")
 "sz" '(engine/search-zhihu :wk "Zhihu"))

Emacs

Tempel

(straight-use-package 'tempel)
(setq tempel-path "~/.emacs.d/template/tempel")

(global-set-key (kbd "M-+") 'tempel-complete)
(global-set-key (kbd "M-*") 'tempel-insert)

Dirvish

(setq insert-directory-program "/opt/homebrew/bin/gls")

(straight-use-package 'dirvish)

(dirvish-override-dired-mode)

(with-eval-after-load 'dirvish
  (setq dirvish-use-header-line 'global)
  (setq dirvish-header-line-format
        '(:left (path) :right (free-space))
        dirvish-mode-line-format
        '(:left (sort file-time " " file-size symlink) :right (omit yank index)))

  (customize-set-variable 'dirvish-quick-access-entries
                          '(("h" "~/"                          "Home")
                            ("d" "~/Downloads/"                "Downloads")
                            ("n" "~/Nextcloud/"                "Nextcloud"))))

(with-eval-after-load 'evil-collection
  (evil-collection-define-key 'normal 'dirvish-mode-map (kbd "q") 'dirvish-quit))

(global-set-key [remap dired] 'dirvish)

(general-define-key
 :states '(normal visual emacs)
 :prefix "SPC"
 :non-normal-prefix "M-SPC"
 ";a" '(dirvish-quick-access :wk "Quick access"))

Grab link

(straight-use-package 'grab-mac-link)

(defun my/link-safari ()
  (interactive)
  (grab-mac-link-dwim 'safari))

(general-define-key
 :keymaps '(normal visual emacs)
 :prefix "SPC"
 :non-normal-prefix "M-SPC"
 "l" '(:ignore t :wk "Link/Language")
 "ls" '(my/link-safari :wk "Grab Safari Link"))

Browse

(straight-use-package 'browse-at-remote)

(general-define-key
 :states '(normal visual emacs)
 :prefix "SPC"
 :non-normal-prefix "M-SPC"
 "fR" '(browse-at-remote :wk "Browse remote"))

helpful

(straight-use-package 'helpful)
(setq help-window-select t)
(global-set-key [remap describe-function] 'helpful-callable)
(global-set-key [remap describe-variable] 'helpful-variable)
(global-set-key [remap describe-key] 'helpful-key)

gcmh

Emacs 中拉圾回收的策略是当Emacs自上一次垃圾收集后分配的内存超过 gc-cons-threshold 阀值时就会触发新一轮的垃圾收集行为。~gcmh~ 包对 Emacs 的拉圾回收进行了设置,当正常使用时,拉圾回收的阈值设置的较高,当 Emacs 空闲时阈值设的较低。

优化Emacs的垃圾搜集行为 一文中提出了通过记录每次垃圾收集的时间来进行判断和调整 gc-cons-threshold 的值。

(straight-use-package 'gcmh)

(add-hook 'after-init-hook 'gcmh-mode)
(with-eval-after-load 'gcmh
  (setq gcmh-idle-delay 'auto)
  (setq gcmh-auto-idle-delay-factor 10)
  (setq gcmh-high-cons-threshold #x1000000))

zen

(straight-use-package 'writeroom-mode)

(general-define-key
 :states '(normal visual emacs)
 :prefix "SPC"
 :non-normal-prefix "M-SPC"
 "tz" '(writeroom-mode :wk "Zen mode"))

Emacs Works with Multiple Language, Piece of Cake

Language Spelling

ispell

(with-eval-after-load 'ispell
  (setq ispell-program-name "/opt/homebrew/bin/aspell")
  (setq ispell-extra-args '("--sug-mode=ultra" "--lang=en_US" "--run-together"))
  (setq ispell-aspell-dict-dir
        (ispell-get-aspell-config-value "dict-dir"))

  (setq ispell-aspell-data-dir
        (ispell-get-aspell-config-value "data-dir"))

  (setq ispell-personal-dictionary (expand-file-name "config/ispell/.aspell.en.pws" my-galaxy)))

wucuo

Fastest solution to spell check camel case code or plain text.

(straight-use-package 'wucuo)

(add-hook 'prog-mode-hook #'wucuo-start)
(add-hook 'text-mode-hook #'wucuo-start)

flyspell

(with-eval-after-load 'flyspell
  (define-key flyspell-mode-map (kbd "C-;") nil)
  (define-key flyspell-mode-map (kbd "C-,") nil)
  (define-key flyspell-mode-map (kbd "C-.") nil))

flyspell-correct

grammar

(straight-use-package 'langtool)

(setq langtool-http-server-host "localhost")
(setq langtool-http-server-port 8081)

Language Translate

dictionary

(setq dictionary-server "dict.org")

go-translate

使用 google 进行翻译需要使用 Proxy

(straight-use-package 'go-translate)

(with-eval-after-load 'go-translate
  (setq gts-translate-list '(("en" "zh")))
  (setq gts-default-translator (gts-translator
                                :picker (gts-noprompt-picker)
                                :engines (list
                                          (gts-google-engine :parser (gts-google-summary-parser)))
                                :render (gts-buffer-render))))


(general-define-key
 :keymaps '(normal visual)
 :prefix "SPC"
 :non-normal-prefix "M-SPC"
 "ll" '(gts-do-translate :wk "Translate"))

lingva

(straight-use-package 'lingva)
(with-eval-after-load 'lingva
  (setq lingva-target "zh"))

(general-define-key
 :keymaps '(normal visual)
 :prefix "SPC"
 :non-normal-prefix "M-SPC"
 "lL" '(lingva-translate :wk "Lingva"))

Flymake

(add-hook 'prog-mode-hook 'flymake-mode)
;; (add-hook 'flymake-mode-hook 'flymake-popon-mode)

Organize Life with Plain Text, High Effective System

Better default

(with-eval-after-load 'org
  (setq org-modules '())
  (setq org-imenu-depth 4)
  (setq org-return-follows-link t)
  (setq org-image-actual-width nil)
  (setq org-display-remote-inline-images 'download)
  (setq org-log-into-drawer t)
  (setq org-fast-tag-selection-single-key 'expert)
  (setq org-adapt-indentation nil)
  (setq org-fontify-quote-and-verse-blocks t)
  (setq org-support-shift-select t)
  (setq org-treat-S-cursor-todo-selection-as-state-change nil)
  (setq org-hide-leading-stars nil)
  (setq org-startup-with-inline-images t)
  (setq org-image-actual-width '(500))
  (setq org-use-speed-commands t))

Tasks states

(with-eval-after-load 'org
  (setq org-todo-repeat-to-state t)
  (setq org-todo-keywords
        '((sequence "TODO(t)" "NEXT(n)" "INPROGRESS(i)" "|" "WAIT(w@)" "SOMEDAY(s@)" "CNCL(c@/!)" "DONE(d)")))
  (setq org-todo-state-tags-triggers
        (quote (("CNCL" ("CNCL" . t))
                ("WAIT" ("WAIT" . t))
                ("SOMEDAY" ("WAIT") ("SOMEDAY" . t))
                (done ("WAIT") ("SOMEDAY"))
                ("TODO" ("WAIT") ("CNCL") ("SOMEDAY"))
                ("NEXT" ("WAIT") ("CNCL") ("SOMEDAY"))
                ("DONE" ("WAIT") ("CNCL") ("SOMEDAY"))))))

org-babel

根据需要加载 org-babel-load-languages, 加快 Emacs 的启动速度,相关讨论见 Emacs-china 论坛

(with-eval-after-load 'org
  (setq org-babel-python-command "python3")
  ;; (org-babel-do-load-languages
  ;;  'org-babel-load-languages
  ;;  '((emacs-lisp . t)))
  (defun my/org-babel-execute-src-block (&optional _arg info _params)
    "Load language if needed"
    (let* ((lang (nth 0 info))
           (sym (if (member (downcase lang) '("c" "cpp" "c++")) 'C (intern lang)))
           (backup-languages org-babel-load-languages))
      ;; - (LANG . nil) 明确禁止的语言,不加载。
      ;; - (LANG . t) 已加载过的语言,不重复载。
      (unless (assoc sym backup-languages)
        (condition-case err
            (progn
              (org-babel-do-load-languages 'org-babel-load-languages (list (cons sym t)))
              (setq-default org-babel-load-languages (append (list (cons sym t)) backup-languages)))
          (file-missing
           (setq-default org-babel-load-languages backup-languages)
           err)))))
  (advice-add 'org-babel-execute-src-block :before 'my/org-babel-execute-src-block)
  (setq org-confirm-babel-evaluate nil))

org-capture

(setq org-capture-templates
      `(
        ("c" "Contents to Current Clocked Task"
         plain (clock)
         "%i%?"
         :empty-lines 1)
        ("a" "Anki Deck")
        ("ae" "Deck: English" entry (file (lambda () (concat my-galaxy "/anki/anki_english.org")))
         "* %?\n" :jump-to-captured t)
        ("ac" "Deck: Civil Engineering" entry (file (lambda () (concat my-galaxy "/anki/anki_engineering.org")))
         "* %?\n" :jump-to-captured t)
        ("i" ,(format "%s\tInbox: Daily note and tasks" (all-the-icons-material "inbox" :face 'all-the-icons-green)) plain (file+olp+datetree (lambda () (concat my-galaxy "/inbox/inbox.org")))
         "**** %?\n%U\n" :time-prompt t :tree-type week)
        ("l" "Lists")
        ("lm" "Movie" entry (file+headline (lambda () (concat my-galaxy "/roam/main/movie.org")) "Movie list")
         "* %?
:PROPERTIES:
:GENRE: %^{Film genre|Action|Adventure|Comedy|Drama|Fantasy|Horror|Musicals|Mystery|Romance|Science fiction|Sports|Thriller}
:COUNTRY:
:SCORE:
:PLOT: %^{PLOT}
:END:")
        ("s" ,(format "%s\tCode Snippet" (all-the-icons-material "code" :face 'all-the-icons-cyan)) entry (file (lambda () (concat my-galaxy "/scripts/snippets.org")))
         "* %?\t%^g\n#+BEGIN_SRC %^{language}\n\n#+END_SRC")
        ("c" ,(format "%s\tContacts" (all-the-icons-material "contacts" :face 'all-the-icons-maroon)) entry (file (lambda () (concat my-galaxy "/people/contacts.org")))
         "* %(org-contacts-template-name)
:PROPERTIES:
:EMAIL: %(org-contacts-template-email)
:PHONE:
:ALIAS:
:NICKNAME:
:IGNORE:
:ICON:
:NOTE:
:ADDRESS:
:BIRTHDAY:
:END:")))
(with-eval-after-load 'org
  (setq org-capture-templates '(("a" "Anki Deck")
                                ("ae" "Deck: English" entry (file (lambda () (concat my-galaxy "/anki/anki_english.org")))
                                 "* %?\n" :jump-to-captured t)
                                ("ac" "Deck: Civil Engineering" entry (file (lambda () (concat my-galaxy "/anki/anki_engineering.org")))
                                 "* %?\n" :jump-to-captured t))))

(global-set-key (kbd "<f10>") 'org-capture)

Attachment

(with-eval-after-load 'org
  (setq org-attach-id-to-path-function-list
        '(org-attach-id-ts-folder-format
          org-attach-id-uuid-folder-format))
  (setq org-attach-dir-relative t))

Refile

(with-eval-after-load 'org
  (setq org-refile-targets '((nil :maxlevel . 9)
                             (org-agenda-files :maxlevel . 9)))
  (setq org-refile-use-outline-path t)
  (setq org-outline-path-complete-in-steps nil)
  (setq org-refile-allow-creating-parent-nodes 'confirm)
  (setq org-refile-use-outline-path 'file)
  (setq org-refile-active-region-within-subtree t))

ol

(with-eval-after-load 'ol
  (setq org-link-frame-setup '((vm . vm-visit-folder-other-frame)
                               (vm-imap . vm-visit-imap-folder-other-frame)
                               (gnus . org-gnus-no-new-news)
                               (file . find-file)
                               (wl . wl-other-frame))))

Archive

(with-eval-after-load 'org
  (setq org-archive-location (expand-file-name "todos/gtd_archive.org::datetree/" my-galaxy)))
(defun my/gtd-archive ()
  (interactive)
  (find-file (expand-file-name "todos/gtd_archive.org" my-galaxy)))

Habit

(with-eval-after-load 'org
  (add-to-list 'org-modules 'org-habit t))

SRC

默认是在右侧打开编辑 buffer,屏幕小,所以我选择当前窗口打开编辑 buffer 。

(with-eval-after-load 'org
  (setq org-src-window-setup 'current-window)
  (setq org-src-ask-before-returning-to-edit-buffer nil))

ID

(with-eval-after-load 'org
  (setq org-id-method 'ts)
  (setq org-id-link-to-org-use-id 'create-if-interactive))

Clock

参照了Org Mode - Organize Your Life In Plain Text! 中的设置,punch-in 和 punch-out 是很好的一个概念,具体的使用方法看此链接中的说明,写的很清楚。

(with-eval-after-load 'org
  ;;(org-clock-persistence-insinuate)
  (setq org-clock-history-length 23)
  (setq org-clock-in-resume t)
  (setq org-clock-into-drawer "LOGCLOCK")
  (setq org-clock-out-remove-zero-time-clocks t)
  (setq org-clock-out-when-done t)
  (setq org-clock-persist t)
  (setq org-clock-clocktable-default-properties '(:maxlevel 5 :link t :tags t))
  (setq org-clock-persist-query-resume nil)
  (setq org-clock-report-include-clocking-task t)
  ;; (setq org-clock-out-switch-to-state "DONE")
  (setq org-clock-in-switch-to-state 'bh/clock-in-to-next)
  (setq bh/keep-clock-running nil))

(general-define-key
 :states '(normal visual emacs)
 :prefix "SPC"
 :non-normal-prefix "M-SPC"
 "oc" '(:ignore t :wk "Clock")
 "ocj" '(org-clock-goto :wk "Clock goto")
 "oci" '(org-clock-in :wk "Clock In")
 "oco" '(org-clock-out :wk "Clock Out"))

Punch in and punch out

Some useful function borrowed from Org Mode - Organize Your Life In Plain Text!

(defun bh/is-task-p ()
  "Any task with a todo keyword and no subtask"
  (save-restriction
    (widen)
    (let ((has-subtask)
          (subtree-end (save-excursion (org-end-of-subtree t)))
          (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
      (save-excursion
        (forward-line 1)
        (while (and (not has-subtask)
                    (< (point) subtree-end)
                    (re-search-forward "^\*+ " subtree-end t))
          (when (member (org-get-todo-state) org-todo-keywords-1)
            (setq has-subtask t))))
      (and is-a-task (not has-subtask)))))
(defun bh/is-project-p ()
  "Any task with a todo keyword subtask"
  (save-restriction
    (widen)
    (let ((has-subtask)
          (subtree-end (save-excursion (org-end-of-subtree t)))
          (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
      (save-excursion
        (forward-line 1)
        (while (and (not has-subtask)
                    (< (point) subtree-end)
                    (re-search-forward "^\*+ " subtree-end t))
          (when (member (org-get-todo-state) org-todo-keywords-1)
            (setq has-subtask t))))
      (and is-a-task has-subtask))))
(defun bh/find-project-task ()
  "Move point to the parent (project) task if any"
  (save-restriction
    (widen)
    (let ((parent-task (save-excursion (org-back-to-heading 'invisible-ok) (point))))
      (while (org-up-heading-safe)
        (when (member (nth 2 (org-heading-components)) org-todo-keywords-1)
          (setq parent-task (point))))
      (goto-char parent-task)
      parent-task)))
(defun bh/is-project-subtree-p ()
  "Any task with a todo keyword that is in a project subtree.
  Callers of this function already widen the buffer view."
  (let ((task (save-excursion (org-back-to-heading 'invisible-ok)
                              (point))))
    (save-excursion
      (bh/find-project-task)
      (if (equal (point) task)
          nil
        t))))
(defun bh/clock-in-to-next (kw)
  "Switch a task from TODO to NEXT when clocking in.
    Skips capture tasks, projects, and subprojects.
    Switch projects and subprojects from NEXT back to TODO"
  (when (not (and (boundp 'org-capture-mode) org-capture-mode))
    (cond
     ((and (member (org-get-todo-state) (list "TODO"))
           (bh/is-task-p))
      "NEXT")
     ((and (member (org-get-todo-state) (list "NEXT"))
           (bh/is-project-p))
      "TODO"))))
(defun bh/punch-in (arg)
  "Start continuous clocking and set the default task to the
  selected task.  If no task is selected set the Organization task
  as the default task."
  (interactive "p")
  (setq bh/keep-clock-running t)
  (if (equal major-mode 'org-agenda-mode)
      ;;
      ;; We're in the agenda
      ;;
      (let* ((marker (org-get-at-bol 'org-hd-marker))
             (tags (org-with-point-at marker (org-get-tags))))
        (if (and (eq arg 4) tags)
            (org-agenda-clock-in '(16))
          (bh/clock-in-default-task-as-default)))
    ;;
    ;; We are not in the agenda
    ;;
    (save-restriction
      (widen)
                                        ; Find the tags on the current task
      (if (and (equal major-mode 'org-mode) (not (org-before-first-heading-p)) (eq arg 4))
          (org-clock-in '(16))
        (bh/clock-in-default-task-as-default)))))
(defun bh/punch-out ()
  (interactive)
  (setq bh/keep-clock-running nil)
  (when (org-clock-is-active)
    (org-clock-out))
  (org-agenda-remove-restriction-lock))
(defun bh/clock-in-default-task ()
  (save-excursion
    (org-with-point-at org-clock-default-task
      (org-clock-in))))
(defun bh/clock-in-parent-task ()
  "Move point to the parent (project) task if any and clock in"
  (let ((parent-task))
    (save-excursion
      (save-restriction
        (widen)
        (while (and (not parent-task) (org-up-heading-safe))
          (when (member (nth 2 (org-heading-components)) org-todo-keywords-1)
            (setq parent-task (point))))
        (if parent-task
            (org-with-point-at parent-task
              (org-clock-in))
          (when bh/keep-clock-running
            (bh/clock-in-default-task)))))))
(defvar bh/default-task-id "20220524T114723.420565")
(defun bh/clock-in-default-task-as-default ()
  (interactive)
  (org-with-point-at (org-id-find bh/default-task-id 'marker)
    (org-clock-in '(16))))
(defun bh/clock-out-maybe ()
  (when (and bh/keep-clock-running
             (not org-clock-clocking-in)
             (marker-buffer org-clock-default-task)
             (not org-clock-resolving-clocks-due-to-idleness))
    (bh/clock-in-parent-task)))
(add-hook 'org-clock-out-hook 'bh/clock-out-maybe 'append)
(defun bh/clock-in-last-task (arg)
  "Clock in the interrupted task if there is one
  Skip the default task and get the next one.
  A prefix arg forces clock in of the default task."
  (interactive "p")
  (let ((clock-in-to-task
         (cond
          ((eq arg 4) org-clock-default-task)
          ((and (org-clock-is-active)
                (equal org-clock-default-task (cadr org-clock-history)))
           (caddr org-clock-history))
          ((org-clock-is-active) (cadr org-clock-history))
          ((equal org-clock-default-task (car org-clock-history)) (cadr org-clock-history))
          (t (car org-clock-history)))))
    (widen)
    (org-with-point-at clock-in-to-task
      (org-clock-in nil))))

(general-define-key
 :keymaps '(normal visual emacs)
 :prefix "SPC"
 :non-normal-prefix "M-SPC"
 "ti" '(bh/punch-in :wk "Punch In")
 "to" '(bh/punch-out :wk "Punch Out"))

org-publish

(with-eval-after-load 'ox-html
  (setq org-html-preamble t)
  (setq org-html-preamble-format '(("en" "<a href=\"/index.html\" class=\"button\">Home</a>
<a href=\"/notes/index.html\" class=\"button\">Notes</a>
<a href=\"/engineering/index.html\" class=\"button\">Engineering</a>
<a href=\"/movies/index.html\" class=\"button\">Movies</a>
<a href=\"/books/index.html\" class=\"button\">Books</a>
<a href=\"/about.html\" class=\"button\">About</a>
<hr>")))

  (setq org-html-postamble t)

  (setq org-html-postamble-format
        '(("en" "<hr><div class=\"generated\">Created with %c on MacOS</div>")))

  (setq org-html-head-include-default-style nil)

  (setq org-html-head "<link rel=\"stylesheet\" type=\"text/css\" href=\"../css/style.css\" />"))
(with-eval-after-load 'ox-publish
  (setq org-publish-project-alist
        `(("site"
           :base-directory ,website-directory
           :base-extension "org"
           :recursive nil
           :publishing-directory ,my/publish-directory
           :publishing-function org-html-publish-to-html
           )
          ("notes"
           :base-directory ,(expand-file-name "notes" website-directory)
           :base-extension "org"
           :publishing-directory ,(expand-file-name "notes" my/publish-directory)
           :publishing-function org-html-publish-to-html
           :auto-sitemap t
           :sitemap-filename "index.org"
           :sitemap-title "Notes"
           :sitemap-sort-files anti-chronologically)
          ("books"
           :base-directory ,(expand-file-name "books" website-directory)
           :base-extension "org"
           :publishing-directory ,(expand-file-name "books" my/publish-directory)
           :publishing-function org-html-publish-to-html
           :auto-sitemap t
           :sitemap-filename "index.org"
           :sitemap-title "Books"
           :sitemap-sort-files anti-chronologically)
          ("movies"
           :base-directory ,(expand-file-name "movies" website-directory)
           :base-extension "org"
           :publishing-directory ,(expand-file-name "movies" my/publish-directory)
           :publishing-function org-html-publish-to-html
           :auto-sitemap t
           :sitemap-filename "index.org"
           :sitemap-title "Movies"
           :sitemap-sort-files anti-chronologically)
          ("engineering"
           :base-directory ,(expand-file-name "engineering" website-directory)
           :base-extension "org"
           :publishing-directory ,(expand-file-name "engineering" my/publish-directory)
           :publishing-function org-html-publish-to-html
           :auto-sitemap t
           :sitemap-filename "index.org"
           :sitemap-title "Engineering"
           :sitemap-sort-files anti-chronologically)
          ("static"
           :base-directory ,website-directory
           :base-extension "css\\|txt\\|jpg\\|gif\\|png"
           :recursive t
           :publishing-directory  ,my/publish-directory
           :publishing-function org-publish-attachment)

          ("personal-website" :components ("site" "notes" "books" "movies" "engineering" "static")))))

Copy id to clipboard

(defun my/copy-idlink ()
  "Copy idlink to clipboard."
  (interactive)
  (when (eq major-mode 'org-agenda-mode) ;switch to orgmode
    (org-agenda-show)
    (org-agenda-goto))
  (when (eq major-mode 'org-mode) ; do this only in org-mode buffers
    (let* ((mytmphead (nth 4 (org-heading-components)))
           (mytmpid (funcall 'org-id-get-create))
           (mytmplink (format "- [ ] [[id:%s][%s]]" mytmpid mytmphead)))
      (kill-new mytmplink)
      (message "Copied %s to killring (clipboard)" mytmplink))))

(general-define-key
 :keymaps '(normal visual emacs)
 :prefix "SPC"
 :non-normal-prefix "M-SPC"
 "li" '(my/copy-idlink :wk "Copy IDLink"))

TOC

(straight-use-package 'toc-org)

(add-hook 'org-mode-hook 'toc-org-mode)

Superstar

(straight-use-package 'org-superstar)

(add-hook 'org-mode-hook 'org-superstar-mode)

org-download

(straight-use-package 'org-download)

(add-hook 'org-mode-hook 'org-download-enable)
(with-eval-after-load 'org-download
  (setq org-download-image-dir (expand-file-name "pictures" my-galaxy))
  (setq org-download-screenshot-method 'screencapture)
  (setq org-download-abbreviate-filename-function 'expand-file-name)
  (setq org-download-timestamp "%Y%m%d%H%M%S")
  (setq org-download-display-inline-images nil)
  (setq org-download-heading-lvl nil)
  (setq org-download-annotate-function (lambda (_link) ""))
  (setq org-download-image-attr-list '("#+NAME: fig: " "#+CAPTION: " "#+ATTR_ORG: :width 500px" "#+ATTR_LATEX: :width 10cm :placement [!htpb]" "#+ATTR_HTML: :width 600px"))
  (setq org-download-screenshot-basename ".png"))

(general-define-key
 :states '(normal visual emacs)
 :prefix "SPC"
 :non-normal-prefix "M-SPC"
 "od" '(:ignore t :wk "Download")
 "odc" '(org-download-clipboard :wk "Download Clipboard")
 "ody" '(org-download-yank :wk "Download Yank")
 "odr" '(org-download-rename-last-file :wk "Rename last file")
 "odR" '(org-download-rename-at-point :wk "Rename point"))

Plantuml

(straight-use-package '(plantuml :type git :host github :repo "ginqi7/plantuml-emacs"))

(add-hook 'org-mode-hook (lambda ()
                             (require 'plantuml)))
(with-eval-after-load 'plantuml
  (setq plantuml-jar-path
        (concat
         (string-trim
          (shell-command-to-string "readlink -f $(brew --prefix plantuml)"))
         "/libexec/plantuml.jar")))

(general-define-key
 :states '(normal visual emacs)
 :prefix "SPC"
 :non-normal-prefix "M-SPC"
 "op" '(:ignore t :wk "Plantuml")
 "opm" '(plantuml-org-to-mindmap-open :wk "Mindmap")
 "opw" '(plantuml-org-to-wbs-open :wk "WBS"))

Drill

这个 视频 展示的 org-capute 自动从字典获取中英文的意义挺不错的。

(straight-use-package 'org-drill)

Appear

(straight-use-package 'org-appear)

(setq org-appear-trigger 'manual)
(setq org-appear-autolinks t)

(add-hook 'org-mode-hook 'org-appear-mode)

Math preview

The full list of macros and environments with their packages is available here. 把相应的环境加入到 math-preveiw-tex-default-packages 即可。

(straight-use-package 'math-preview)
(with-eval-after-load 'math-preview
  (setq math-preview-scale 1.1)
  (setq math-preview-raise 0.3)
  (setq math-preview-margin '(1 . 0)))

(general-define-key
 :states '(normal visual emacs)
 :prefix "SPC"
 :non-normal-prefix "M-SPC"
 "tm" '(math-preview-all :wk "Math preveiw"))

Org-roam

(straight-use-package 'org-roam)
(with-eval-after-load 'org-roam
  (setq org-roam-db-gc-threshold most-positive-fixnum)
  (setq org-roam-directory (file-truename (expand-file-name "roam" my-galaxy)))
  (add-hook 'org-roam-mode-hook 'turn-on-visual-line-mode)
  (add-hook 'org-mode-hook (lambda ()
                             (setq-local time-stamp-active t
                                         time-stamp-start "#\\+MODIFIED:[ \t]*"
                                         time-stamp-end "$"
                                         time-stamp-format "\[%Y-%m-%d %3a %H:%M\]")
                             (add-hook 'before-save-hook 'time-stamp nil 'local)))
  (add-hook 'after-init-hook 'org-roam-db-autosync-enable)

  (add-to-list 'display-buffer-alist
               '("\\*org-roam\\*"
                 (display-buffer-in-side-window)
                 (side . right)
                 (window-width . 0.25)))
  ;; org-roam-capture
  (setq org-roam-capture-templates '(("a" "articles" plain "%?"
                                      :target (file+head "articles/${slug}.org"
                                                         "#+TITLE: ${title}\n#+CREATED: %U\n#+MODIFIED: \n")
                                      :unnarrowed t)
                                     ("b" "Books" plain (file "~/.emacs.d/template/readinglog")
                                      :target (file+head "books/${slug}.org"
                                                         "#+TITLE: ${title}\n#+CREATED: %U\n#+MODIFIED: \n")
                                      :unnarrowed t)
                                     ("d" "Diary" plain "%?"
                                      :target (file+datetree "daily/<%Y-%m>.org" day))
                                     ("m" "main" plain "%?"
                                      :target (file+head "main/${slug}.org"
                                                         "#+TITLE: ${title}\n#+CREATED: %U\n#+MODIFIED: \n")
                                      :unnarrowed t)
                                     ("p" "people" plain (file "~/.emacs.d/template/crm")
                                      :target (file+head "crm/${slug}.org"
                                                         "#+TITLE: ${title}\n#+CREATED: %U\n#+MODIFIED: \n")
                                      :unnarrowed t)
                                     ("r" "reference" plain (file "~/.emacs.d/template/reference")
                                      :target (file+head "ref/${citekey}.org"
                                                         "#+TITLE: ${title}\n#+CREATED: %U\n#+MODIFIED: \n")
                                      :unnarrowed t)
                                     ("w" "work" plain "%?"
                                      :target (file+head "work/${slug}.org"
                                                         "#+TITLE: ${title}\n#+CREATED: %U\n#+MODIFIED: \n")
                                      :unnarrowed t))))
(general-define-key
 :states '(normal visual emacs)
 :prefix "SPC"
 :non-normal-prefix "M-SPC"
 "n" '(:ignore t :wk "Notes")
 "nb" '(org-roam-buffer-toggle :wk "Roam buffer")
 "nr" '(org-roam-node-random :wk "Random node")
 "nf" '(org-roam-node-find :wk "Find node")
 "ni" '(org-roam-node-insert :wk "Insert node")
 "ns" '(org-roam-db-sync :wk "Sync DB")

 "na" '(org-roam-alias-add :wk "Add alias")
 "nA" '(org-roam-alias-remove :wk "Remove alias")
 "nt" '(org-roam-tag-add :wk "Add tag")
 "nT" '(org-roam-tag-remove :wk "Remove tag")

 "nc" '(org-roam-dailies-capture-today :wk "Capture today")
 "nd" '(org-roam-dailies-goto-today :wk "Goto today")
 "nD" '(org-roam-dailies-goto-date :wk "Goto date"))

Roam Buffer

(add-to-list 'display-buffer-alist
             '("\\*org-roam\\*"
               (display-buffer-in-side-window)
               (side . right)
               (window-width . 0.25)))

(defun my/org-roam-buffer-show (_)
  (when (and (not (minibufferp))
             (not org-roam-capture--node)
             (not (derived-mode-p 'calendar-mode))
             (not org-capture-mode)
             (xor (org-roam-buffer-p) (eq 'visible (org-roam-buffer--visibility))))
    (org-roam-buffer-toggle)))

(add-hook 'window-buffer-change-functions #'my/org-roam-buffer-show)

Node find interface

(with-eval-after-load 'org-roam
  (cl-defmethod org-roam-node-type ((node org-roam-node))
    "Return the TYPE of NODE."
    (condition-case nil
        (file-name-nondirectory
         (directory-file-name
          (file-name-directory
           (file-relative-name (org-roam-node-file node) org-roam-directory))))
      (error "")))

  (cl-defmethod org-roam-node-directories ((node org-roam-node))
    (if-let ((dirs (file-name-directory (file-relative-name (org-roam-node-file node) org-roam-directory))))
        (format "(%s)" (car (split-string dirs "/")))
      ""))

  (cl-defmethod org-roam-node-backlinkscount ((node org-roam-node))
    (let* ((count (caar (org-roam-db-query
                         [:select (funcall count source)
                                  :from links
                                  :where (= dest $s1)
                                  :and (= type "id")]
                         (org-roam-node-id node)))))
      (format "[%d]" count)))

  (cl-defmethod org-roam-node-doom-filetitle ((node org-roam-node))
     "Return the value of \"#+title:\" (if any) from file that NODE resides in.
 If there's no file-level title in the file, return empty string."
     (or (if (= (org-roam-node-level node) 0)
          (org-roam-node-title node)
        (org-roam-get-keyword "TITLE" (org-roam-node-file node)))
         ""))

  (cl-defmethod org-roam-node-doom-hierarchy ((node org-roam-node))
    "Return hierarchy for NODE, constructed of its file title, OLP and direct title.
   If some elements are missing, they will be stripped out."
    (let ((title     (org-roam-node-title node))
          (olp       (org-roam-node-olp   node))
          (level     (org-roam-node-level node))
          (filetitle (org-roam-node-doom-filetitle node))
          (separator (propertize " > " 'face 'shadow)))
      (cl-case level
        ;; node is a top-level file
        (0 filetitle)
        ;; node is a level 1 heading
        (1 (concat (propertize filetitle 'face '(shadow italic))
                   separator title))
        ;; node is a heading with an arbitrary outline path
        (t (concat (propertize filetitle 'face '(shadow italic))
                   separator (propertize (string-join olp " > ") 'face '(shadow italic))
                   separator title)))))

  (setq org-roam-node-display-template (concat "${type:8} ${backlinkscount:3} ${doom-hierarchy:*}" (propertize "${tags:20}" 'face 'org-tag) " ")))

Org roam UI

(straight-use-package 'org-roam-ui)
(with-eval-after-load 'org-roam-ui
  (setq org-roam-ui-sync-theme t)
  (setq org-roam-ui-follow t)
  (setq org-roam-ui-update-on-save t)
  (setq org-roam-ui-open-on-start t)

  (require 'websocket))

(general-define-key
 :states '(normal visual emacs)
 :prefix "SPC"
 :non-normal-prefix "M-SPC"
 "nu" '(org-roam-ui-open :wk "Random node"))

Consult-org-roam

(straight-use-package 'consult-org-roam)

(general-define-key
 :states '(normal visual emacs)
 :prefix "SPC"
 :non-normal-prefix "M-SPC"
 "ns" '(consult-org-roam-search :wk "Search")
 "nb" '(consult-org-roam-backlinks :wk "Open Backlinks")
 "nl" '(consult-org-roam-forward-links :wk "Open Links"))

Transclusion

默认的 fringe 颜色是默白的,和我默认的主题颜色一样,这样就导致会看不清 transclusion 的状态。使用 face-sepc-set 更改下颜色。

(straight-use-package 'org-transclusion)

(face-spec-set 'org-transclusion-fringe
       '((((background light))
          :foreground "black")
         (t
          :foreground "white"))
       'face-override-spec)
(face-spec-set 'org-transclusion-source-fringe
       '((((background light))
          :foreground "black")
         (t
          :foreground "white"))
       'face-override-spec)

(general-define-key :states '(normal visual emacs)
                    :keymaps 'org-mode-map
                    :prefix "SPC m"
                    "t" '(:ignore t :wk "Transclusion")
                    "ta" '(org-transclusion-add :wk "Add")
                    "tA" '(org-transclusion-add-all :wk "Add all")
                    "tr" '(org-transclusion-remove :wk "Remove")
                    "tR" '(org-transclusion-remove-all :wk "Remove all")
                    "tg" '(org-transclusion-refresh :wk "Refresh")
                    "tm" '(org-transclusion-make-from-link :wk "Make link")
                    "to" '(org-transclusion-open-source :wk "Open source")
                    "te" '(org-transclusion-live-sync-start :wk "Edit live"))

Bibtex

使用 org-cite 和 citar 足够了,其余的配置己不需要。

(setq org-cite-global-bibliography `(,(concat my-galaxy "/bibtexs/References.bib")
                                     ,(expand-file-name "L.Calibre/calibre.bib" my-cloud)))
  (straight-use-package 'citar)
  (with-eval-after-load 'citar
    (setq citar-bibliography org-cite-global-bibliography)

    (setq citar-notes-paths `(,(expand-file-name "roam/ref" my-galaxy)))

    (setq citar-at-point-function 'embark-act)

    (setq citar-templates '((main . "${author editor:30} ${date year issued:4} ${title:48}")
                            (suffix . "${=key= id:15} ${=type=:12} ${tags keywords:*}")
                            (preview . "${author editor} (${year issued date}) ${title}, ${journal journaltitle publisher container-title collection-title}.\n")
                            (note . "${title}")))
    (setq citar-symbols
          `((file ,(all-the-icons-faicon "file-o" :face 'all-the-icons-green :v-adjust -0.1) . " ")
            (note ,(all-the-icons-material "speaker_notes" :face 'all-the-icons-blue :v-adjust -0.3) . " ")
            (link ,(all-the-icons-octicon "link" :face 'all-the-icons-orange :v-adjust 0.01) . " ")))
    (setq citar-symbol-separator "  ")

    (setq citar-open-note-function 'orb-citar-edit-note)

    (setq citar-library-file-extensions (list "pdf" "jpg"))
    (setq citar-file-additional-files-separator "-"))

    (with-eval-after-load 'citar-org
      (define-key citar-org-citation-map (kbd "<return>") 'org-open-at-point)
      (define-key org-mode-map (kbd "C-c C-x @") 'citar-insert-citation))

    (with-eval-after-load 'citar
      (with-eval-after-load 'embark
        (citar-embark-mode 1)))
;; https://blog.tecosaur.com/tmio/2021-07-31-citations.html
(with-eval-after-load 'citar
  (setq org-cite-insert-processor 'citar)
  (setq org-cite-follow-processor 'citar)
  (setq org-cite-activate-processor 'citar))

(general-define-key
 :states '(normal visual emacs)
 :prefix "SPC"
 :non-normal-prefix "M-SPC"
 "bo" '(citar-open-files :wk "Open bibtex")
 "bO" '(citar-open-entry :wk "Show entry")
 "bn" '(citar-open-note :wk "Open note")
 "bl" '(citar-open-links :wk "Open links"))
(straight-use-package 'citar-org-roam)

(setq citar-org-roam-subdir "ref")
(with-eval-after-load 'org-roam
  (with-eval-after-load 'citar
    (citar-org-roam-mode 1)))

org-roam-bibtex

(straight-use-package 'org-roam-bibtex)
(with-eval-after-load 'org-roam-bibtex
  (setq orb-note-actions-interface 'default
        orb-roam-ref-format 'org-cite))
(add-hook 'org-mode-hook 'org-roam-bibtex-mode)

RSS management

elfeed

(straight-use-package 'elfeed)

(with-eval-after-load 'elfeed
  (setq elfeed-show-entry-switch #'elfeed-display-buffer))

(with-eval-after-load 'evil
  (evil-set-initial-state 'elfeed-search-mode 'emacs)
  (evil-set-initial-state 'elfeed-show-mode 'emacs))

elfeed buffer display. 代码借鉴自 karthink 的配置。这样可以去掉 elfeed-goodies 这个包了。

(defun elfeed-display-buffer (buf &optional act)
  (pop-to-buffer buf '((display-buffer-reuse-window display-buffer-in-side-window)
                       (side . bottom)
                       (window-height . 0.8)
                       (reusable-frames . visible)
                       (window-parameters
                        (select . t)
                        (quit . t)
                        (popup . t)))))

elfeed-org

(straight-use-package 'elfeed-org)

(add-hook 'after-init-hook 'elfeed-org)

(with-eval-after-load 'elfeed-org
  (setq rmh-elfeed-org-files `(,(concat my-galaxy "/rss/elfeed.org"))))

(defun my/rss-source ()
  "Open elfeed config file."
  (interactive)
  (find-file (car rmh-elfeed-org-files)))

elfeed-summary

(straight-use-package 'elfeed-summary)

(setq elfeed-summary-other-window t)
(setq elfeed-summary-settings
      '((group (:title . "科技")
               (:elements (query . (and tec (not emacs) (not blogs)))
                          (group (:title . "Emacs")
                                 (:elements (query . emacs))
                                 (:face . org-level-1))
                          (group (:title . "Blogs")
                                 (:elements (query . blogs)))))
        (group (:title . "News")
               (:elements (query . news)))
        (group (:title . "Books")
               (:elements (query . book)))
        (group (:title . "Finance")
               (:elements (query . finance)))))
(advice-add 'elfeed-summary :after 'elfeed-summary-update)

(general-define-key
 :states '(normal visual emacs)
 :prefix "SPC"
 :non-normal-prefix "M-SPC"
 "E" '(elfeed-summary :wk "Elfeed"))

Finance

(straight-use-package 'ledger-mode)

(with-eval-after-load 'ledger
  (setq ledger-schedule-file (expand-file-name "finance/schedule.ledger" my-galaxy)))

(add-hook 'ledger-mode-hook 'corfu-mode)
(with-eval-after-load 'ledger
  (setq ledger-reports
        '(("bal"            "%(binary) -f %(ledger-file) bal")
          ("bal this month" "%(binary) -f %(ledger-file) bal -p %(month) -S amount")
          ("bal this year"  "%(binary) -f %(ledger-file) bal -p 'this year'")
          ("net worth"      "%(binary) -f %(ledger-file) bal Assets Liabilities")
          ("account"        "%(binary) -f %(ledger-file) reg %(account)")
          ("reg" "%(binary) -f %(ledger-file) reg")
          ("payee" "%(binary) -f %(ledger-file) reg @%(payee)"))))


(defun my/finance-file ()
  "Open finance file."
  (interactive)
  (find-file (expand-file-name "finance/finance.ledger" my-galaxy)))

(general-define-key
 :keymaps '(normal visual emacs)
 :prefix "SPC"
 :non-normal-prefix "M-SPC"
 "fo" '(:ignore t :wk "Open file")
 "fof" '(my/finance-file :wk "Finance file"))

Remove link

;; https://github.com/jeremyf/dotemacs/blob/main/emacs.d/jf-org-mode.el
(defun jf/org-link-remove-link ()
  "Remove the link part of an org-mode link at point and keep
only the description"
  (interactive)
  (let ((elem (org-element-context)))
    (when (eq (car elem) 'link)
      (let* ((content-begin (org-element-property :contents-begin elem))
             (content-end  (org-element-property :contents-end elem))
             (link-begin (org-element-property :begin elem))
             (link-end (org-element-property :end elem)))
        (when (and content-begin content-end)
          (let ((content (buffer-substring-no-properties content-begin content-end)))
            (delete-region link-begin link-end)
            (insert content)))))))

(general-define-key
 :keymaps '(normal visual emacs)
 :prefix "SPC"
 :non-normal-prefix "M-SPC"
 "lr" '(jf/org-link-remove-link :wk "Link Remove"))

Youtube link time

构造 URL 可以跳转到指定的时间点。

;; http://mbork.pl/2022-10-10_Adding_timestamps_to_youtube_links
(defun yt-set-time (time)
  "Set TIME in the YouTube link at point.
TIME is number of seconds if called from Lisp, and a string if
called interactively. Supported formats:
- seconds
- minutes:seconds
- number of seconds with the \"s\" suffix."
  (interactive (list
                (if current-prefix-arg
                    (prefix-numeric-value current-prefix-arg)
                  (read-string "Time: "))))
  (let ((url (thing-at-point-url-at-point)))
    (if (and url
             (string-match
              (format "^%s"
                      (regexp-opt
                       '("https://www.youtube.com/"
                         "https://youtu.be/")
                       "\\(?:"))
              url))
        (let* ((bounds (thing-at-point-bounds-of-url-at-point))
               (time-present-p (string-match "t=[0-9]+" url))
               (question-mark-present-p (string-search "?" url))
               (seconds (cond
                         ((numberp time)
                          time)
                         ((string-match
                           "^\\([0-9]+\\):\\([0-9]\\{2\\}\\)$" time)
                          (+ (* 60 (string-to-number
                                    (match-string 1 time)))
                             (string-to-number (match-string 2 time))))
                         ((string-match "^\\([0-9]+\\)s?$" time)
                          (string-to-number (match-string 1 time)))
                         (t (error "Wrong argument format"))))
               (new-url (if time-present-p
                            (replace-regexp-in-string
                             "t=[0-9]+"
                             (format "t=%i" seconds)
                             url)
                          (concat url
                                  (if question-mark-present-p "&" "?")
                                  (format "t=%i" seconds)))))
          (delete-region (car bounds) (cdr bounds))
          (insert new-url))
      (error "Not on a Youtube link"))))

(general-define-key
 :keymaps '(normal visual emacs)
 :prefix "SPC"
 :non-normal-prefix "M-SPC"
 "lt" '(yt-set-time :wk "Set Youtube link time"))

Tasks

GTD 就是Getting Things Done的缩写,意思是“把需要做的事情处理好”,是一个管理时间的方法。Org-mode 用于实践 GTD 是一个非常很好用的任务管理系统。目前我将笔记系统和任务管理系统结合在一起使用。

;; @https://medium.com/@jakeb0x/straightforward-emacs-show-all-unchecked-org-mode-checkboxes-199f22e8524a
(defun jakebox/org-occur-unchecked-boxes ()
    "Show unchecked Org Mode checkboxes."
    (interactive)
    (occur "\\[ \\]"))
(with-eval-after-load 'org
  (define-key org-mode-map (kbd "C-c o [") 'jakebox/org-occur-unchecked-boxes))

Agenda

https://orgmode.org/worg/agenda-optimization.html

(with-eval-after-load 'org
  (setq org-agenda-files (directory-files-recursively (expand-file-name "todos" my-galaxy) "org$\\|archive$"))
  (setq org-agenda-dim-blocked-tasks t)
  (setq org-agenda-compact-blocks t))

(defun my/gtd-file ()
    (interactive)
    (find-file (expand-file-name "todos/gtd.org" my-galaxy)))

(add-hook 'org-agenda-finalize-hook #'org-agenda-find-same-or-today-or-agenda 90)

(with-eval-after-load 'org
  (define-key org-mode-map (kbd "C-,") nil)
  (define-key org-mode-map (kbd "C-'") nil))

(general-define-key
 :states '(normal visual emacs)
 :prefix "SPC"
 :non-normal-prefix "M-SPC"
 "o" '(:ignore t :wk "Org")
 "oa" '(my/org-agenda :wk "Agenda")
 "ot" '(org-todo-list :wk "Todo list")
 "ov" '(org-search-view :wk "View search"))
;; https://github.com/daviwil/dotfiles/blob/master/Emacs.org
(defun vulpea-buffer-tags-get ()
  "Return filetags value in current buffer."
  (vulpea-buffer-prop-get-list "filetags" "[ :]"))
(defun vulpea-buffer-tags-set (&rest tags)
  "Set TAGS in current buffer.
If filetags value is already set, replace it."
  (if tags
  (vulpea-buffer-prop-set
   "filetags" (concat ":" (string-join tags ":") ":"))
    (vulpea-buffer-prop-remove "filetags")))
(defun vulpea-buffer-tags-add (tag)
  "Add a TAG to filetags in current buffer."
  (let* ((tags (vulpea-buffer-tags-get))
     (tags (append tags (list tag))))
    (apply #'vulpea-buffer-tags-set tags)))
(defun vulpea-buffer-tags-remove (tag)
  "Remove a TAG from filetags in current buffer."
  (let* ((tags (vulpea-buffer-tags-get))
     (tags (delete tag tags)))
    (apply #'vulpea-buffer-tags-set tags)))
(defun vulpea-buffer-prop-set (name value)
  "Set a file property called NAME to VALUE in buffer file.
If the property is already set, replace its value."
  (setq name (downcase name))
  (org-with-point-at 1
    (let ((case-fold-search t))
  (if (re-search-forward (concat "^#\\+" name ":\\(.*\\)")
                 (point-max) t)
      (replace-match (concat "#+" name ": " value) 'fixedcase)
    (while (and (not (eobp))
            (looking-at "^[#:]"))
      (if (save-excursion (end-of-line) (eobp))
      (progn
        (end-of-line)
        (insert "\n"))
        (forward-line)
        (beginning-of-line)))
    (insert "#+" name ": " value "\n")))))
(defun vulpea-buffer-prop-set-list (name values &optional separators)
  "Set a file property called NAME to VALUES in current buffer.
VALUES are quoted and combined into single string using
`combine-and-quote-strings'.
If SEPARATORS is non-nil, it should be a regular expression
matching text that separates, but is not part of, the substrings.
If nil it defaults to `split-string-default-separators', normally
\"[ \f\t\n\r\v]+\", and OMIT-NULLS is forced to t.
If the property is already set, replace its value."
  (vulpea-buffer-prop-set
   name (combine-and-quote-strings values separators)))
(defun vulpea-buffer-prop-get (name)
  "Get a buffer property called NAME as a string."
  (org-with-point-at 1
    (when (re-search-forward (concat "^#\\+" name ": \\(.*\\)")
                 (point-max) t)
  (buffer-substring-no-properties
   (match-beginning 1)
   (match-end 1)))))
(defun vulpea-buffer-prop-get-list (name &optional separators)
  "Get a buffer property NAME as a list using SEPARATORS.
If SEPARATORS is non-nil, it should be a regular expression
matching text that separates, but is not part of, the substrings.
If nil it defaults to `split-string-default-separators', normally
\"[ \f\t\n\r\v]+\", and OMIT-NULLS is forced to t."
  (let ((value (vulpea-buffer-prop-get name)))
    (when (and value (not (string-empty-p value)))
  (split-string-and-unquote value separators))))
(defun vulpea-buffer-prop-remove (name)
  "Remove a buffer property called NAME."
  (org-with-point-at 1
    (when (re-search-forward (concat "\\(^#\\+" name ":.*\n?\\)")
                 (point-max) t)
  (replace-match ""))))

Dynamic agenda。如果需要给合使用 org-roam 和固定的 org 文件可以使用下面的配置,改写 vulpea-agenda-files-update

(defun vulpea-agenda-files-update (&rest _)
  "Update the value of `org-agenda-files'."
  (setq org-agenda-files (seq-uniq
                          (append
                           (vulpea-project-files)
                           '("/path/to/file1"
                             "/path/to/file2"
                             "...")))))
(with-eval-after-load 'org
  (with-eval-after-load 'org-roam
    (defun vulpea-project-p ()
      "Return non-nil if current buffer has any todo entry.
TODO entries marked as done are ignored, meaning the this
function returns nil if current buffer contains only completed
tasks."
      (seq-find                                 ; (3)
       (lambda (type)
         (or (eq type 'todo)
             (eq type 'done)))
       (org-element-map                         ; (2)
           (org-element-parse-buffer 'headline) ; (1)
           'headline
         (lambda (h)
           (org-element-property :todo-type h)))))
    (defun vulpea-project-update-tag ()
      "Update PROJECT tag in the current buffer."
      (when (and (not (active-minibuffer-window))
                 (vulpea-buffer-p))
        (save-excursion
          (goto-char (point-min))
          (let* ((tags (vulpea-buffer-tags-get))
                 (original-tags tags))
            (if (vulpea-project-p)
                (setq tags (cons "project" tags))
              (setq tags (remove "project" tags)))
            ;; cleanup duplicates
            (setq tags (seq-uniq tags))
            ;; update tags if changed
            (when (or (seq-difference tags original-tags)
                      (seq-difference original-tags tags))
              (apply #'vulpea-buffer-tags-set tags))))))
    (defun vulpea-buffer-p ()
      "Return non-nil if the currently visited buffer is a note."
      (and buffer-file-name
           (string-prefix-p
            (expand-file-name (file-name-as-directory org-roam-directory))
            (file-name-directory buffer-file-name))))
    (defun vulpea-project-files ()
      "Return a list of note files containing 'project' tag." ;
      (seq-uniq
       (seq-map
        #'car
        (org-roam-db-query
         [:select [nodes:file]
                  :from tags
                  :left-join nodes
                  :on (= tags:node-id nodes:id)
                  :where (like tag (quote "%\"project\"%"))]))))

    (defun vulpea-agenda-files-update (&rest _)
      "Update the value of `org-agenda-files'."
      (setq org-agenda-files (seq-uniq
                              (append
                               (vulpea-project-files)
                               `(,(expand-file-name "todos/gtd.org" my-galaxy))))))

    (add-hook 'find-file-hook #'vulpea-project-update-tag)
    (add-hook 'before-save-hook #'vulpea-project-update-tag)
    (add-hook 'find-file-hook #'vulpea-agenda-files-update)

    (advice-add 'org-agenda :before #'vulpea-agenda-files-update)
    (advice-add 'org-todo-list :before #'vulpea-agenda-files-update)))
(with-eval-after-load 'org
  (setq org-agenda-custom-commands
        '(("A" "Archive"
           ((todo "DONE|CNCL"
                  ((org-agenda-prefix-format " %i")
                   (org-agenda-hide-tags-regexp "project")
                   (org-agenda-overriding-header "Archive")))))
          (" " "GTD Lists: Daily agenda and tasks"
           ((agenda "" ((org-agenda-span 2)
                        (org-deadline-warning-days 3)
                        (org-agenda-block-separator nil)
                        (org-scheduled-past-days 365)
                        (org-agenda-hide-tags-regexp "project")
                        (org-agenda-day-face-function (lambda (date) 'org-agenda-date))
                        (org-agenda-format-date "%A %-e %B %Y")
                        (org-agenda-prefix-format " %i %?-12t% s")
                        (org-agenda-overriding-header "Today's agenda")))
            ;; (agenda "" ((org-agenda-time-grid nil)
            ;;             (org-agenda-start-on-weekday nil)
            ;;             (org-agenda-span 14)
            ;;             (org-agenda-show-all-dates nil)
            ;;             (org-deadline-warning-days 0)
            ;;             (org-agenda-prefix-format " %i")
            ;;             (org-agenda-block-separator nil)
            ;;             (org-agenda-hide-tags-regexp "project")
            ;;             (org-agenda-entry-types '(:deadline))
            ;;             (org-agenda-skip-function '(org-agenda-skip-entry-if 'todo 'done))
            ;;             (org-agenda-overriding-header "Upcoming deadlines (+14d)")))
            (tags-todo "*"
                       ((org-agenda-skip-function `(org-agenda-skip-entry-if 'deadline
                                                                             'schedule
                                                                             'timestamp
                                                                             'notregexp ,(format "\\[#%s\\]" (char-to-string org-priority-highest))))
                        (org-agenda-hide-tags-regexp "project")
                        (org-agenda-prefix-format " %i")
                        (org-agenda-overriding-header "Important tasks without a date")))
            (todo "NEXT"
                  ((org-agenda-skip-function '(org-agenda-skip-if nil '(timestamp)))
                   (org-agenda-prefix-format " %i")
                   (org-agenda-block-separator nil)
                   (org-agenda-hide-tags-regexp "project")
                   (org-agenda-overriding-header "Next tasks list")))
            (todo "INPROGRESS"
                  ((org-agenda-block-separator nil)
                   (org-agenda-prefix-format " %i")
                   (org-agenda-hide-tags-regexp "project")
                   (org-agenda-overriding-header "Inprogress tasks list")))
            (tags-todo "-Computer-Emacs/TODO"
                       ((org-agenda-skip-function `(org-agenda-skip-entry-if 'deadline
                                                                             'schedule
                                                                             'timestamp
                                                                             'regexp ,(format "\\[#%s\\]" (char-to-string org-priority-highest))))
                        (org-agenda-prefix-format " %i")
                        (org-agenda-hide-tags-regexp "project")
                        (org-agenda-block-separator nil)
                        (org-agenda-overriding-header "Todo tasks list")))
            (tags-todo "Emacs|Computer"
                       ((org-agenda-block-separator nil)
                        (org-agenda-skip-function '(org-agenda-skip-if nil '(timestamp)))
                        (org-agenda-prefix-format " %i")
                        (org-agenda-hide-tags-regexp "project")
                        (org-agenda-overriding-header "Computer science")))
            (tags-todo "Family"
                       ((org-agenda-skip-function '(org-agenda-skip-if nil '(timestamp)))
                        (org-agenda-prefix-format " %i")
                        (org-agenda-hide-tags-regexp "project")
                        (org-agenda-block-separator nil)
                        (org-agenda-overriding-header "Family")))
            (todo "WAIT|SOMEDAY"
                  ((org-agenda-block-separator nil)
                   (org-agenda-prefix-format " %i")
                   (org-agenda-hide-tags-regexp "project")
                   (org-agenda-overriding-header "Tasks on hold"))))))))

(defun my/org-agenda ()
  "Open my org-agenda."
  (interactive)
  (org-agenda "" " "))

(global-set-key (kbd "<f12>") 'my/org-agenda)

Blog

(straight-use-package 'ox-hugo)

(with-eval-after-load 'ox
  (require 'ox-hugo))

Latex

(straight-use-package 'auctex)

(add-to-list 'auto-mode-alist '("\\.tex\\'" . LaTeX-mode))

(setq TeX-auto-save t)
(setq TeX-parse-self t)

(setq TeX-save-query nil)
(setq TeX-electric-sub-and-superscript t)
(setq TeX-auto-local ".auctex-auto")
(setq TeX-style-local ".auctex-style")
(setq TeX-source-correlate-mode t)
(setq TeX-source-correlate-method 'synctex)
(setq TeX-source-correlate-start-server nil)
(setq-default TeX-master nil)

(setq LaTeX-section-hook '(LaTeX-section-heading
                           LaTeX-section-title
                           LaTeX-section-toc
                           LaTeX-section-section
                           LaTeX-section-label))
(setq LaTeX-fill-break-at-separators nil)
(setq LaTeX-item-indent 0)

为了在 org-mode 中快速的输入数学符号,开启 org-cdlatex-mode 。该包是 org-mode 自带的的。进一步的是什用 cdlatex 这个包,会自动成对的输入符号。进一步的是考虑使用 tempel 等这类包,自动展开 snippet

(setq org-highlight-latex-and-related '(latex script))
(with-eval-after-load 'ox-latex
  (setq org-latex-classes nil
        org-latex-listings 'minted
        org-export-latex-listings 'minted
        org-latex-minted-options '(("breaklines" "true")
                                   ("breakanywhere" "true")))
  (add-to-list 'org-latex-classes
               '("book"
                 "\\documentclass[UTF8,twoside,a4paper,12pt,openright]{ctexrep}
        [NO-DEFAULT-PACKAGES]
        [NO-PACKAGES]
        [EXTRA]"
                 ("\\chapter{%s}" . "\\chapter*{%s}")
                 ("\\section{%s}" . "\\section*{%s}")
                 ("\\subsection{%s}" . "\\subsection*{%s}")
                 ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
                 ("\\paragraph{%s}" . "\\paragraph*{%s}")
                 ("\\subparagraph{%s}" . "\\subparagraph*{%s}")))
  (add-to-list 'org-latex-classes '("article-cn" "\\documentclass{ctexart}
        [NO-DEFAULT-PACKAGES]
        [NO-PACKAGES]
        [EXTRA]"
                                    ("\\section{%s}" . "\\section*{%s}")
                                    ("\\subsection{%s}" . "\\subsection*{%s}")
                                    ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
                                    ("\\paragraph{%s}" . "\\paragraph*{%s}")
                                    ("\\subparagraph{%s}" . "\\subparagraph*{%s}")))
  (add-to-list 'org-latex-classes '("article" "\\documentclass[11pt]{article}
                    [NO-DEFAULT-PACKAGES]
                    [NO-PACKAGES]
                    [EXTRA]"
                                    ("\\section{%s}" . "\\section*{%s}")
                                    ("\\subsection{%s}" . "\\subsection*{%s}")
                                    ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
                                    ("\\paragraph{%s}" . "\\paragraph*{%s}")
                                    ("\\subparagraph{%s}" . "\\subparagraph*{%s}")))
  (add-to-list 'org-latex-classes '("beamer" "\\documentclass[presentation]{beamer}
                                    [DEFAULT-PACKAGES]
                                    [PACKAGES]
                                    [EXTRA]"
                                    ("\\section{%s}" . "\\section*{%s}")
                                    ("\\subsection{%s}" . "\\subsection*{%s}")
                                    ("\\subsubsection{%s}" . "\\subsubsection*{%s}"))))

(setq org-latex-pdf-process '("xelatex -8bit --shell-escape  -interaction=nonstopmode -output-directory %o %f"
                              "bibtex -shell-escape %b"
                              "xelatex -8bit --shell-escape  -interaction=nonstopmode -output-directory %o %f"
                              "xelatex -8bit --shell-escape  -interaction=nonstopmode -output-directory %o %f"
                              "rm -fr %b.out %b.log %b.tex %b.brf %b.bbl")
      org-latex-logfiles-extensions '("lof" "lot" "tex~" "aux" "idx" "log" "out" "toc" "nav" "snm" "vrb" "dvi" "fdb_latexmk" "blg" "brf" "fls" "entoc" "ps" "spl" "bbl")
      org-latex-prefer-user-labels t)

ox-beamer

(add-hook 'org-mode-hook #'(lambda ()
                             (require 'ox-beamer)))

reftex

(straight-use-package 'reftex)

(add-hook 'LaTeX-mode-hook 'turn-on-reftex)
(add-hook 'latex-mode-hook 'turn-on-reftex)

(setq reftex-toc-split-windows-horizontally t)
(setq reftex-toc-split-windows-fraction 0.25)
(add-hook 'reftex-toc-mode-hook 'menu-bar--visual-line-mode-enable)
(add-hook 'reftex-toc-mode-hook #'(lambda () (setq-local mode-line-format nil)))

Emacs Can Do More, Applications

PDF Reader

pdf tools

如果不想高亮文件后打开标注,可以将 pdf-annot-activate-created-annotations 设置为 nil

(straight-use-package 'pdf-tools)

(add-hook 'after-init-hook 'pdf-tools-install)
(setq pdf-view-use-unicode-ligther nil)
;; (run-with-idle-timer 10 nil #'(lambda () (pdf-tools-install)))

;; (add-hook 'doc-view-mode-hook 'pdf-tools-install)
(with-eval-after-load 'pdf-tools
  (add-hook 'pdf-tools-enabled-hook #'(lambda ()
                                        (if (string-equal "dark" (frame-parameter nil 'background-mode))
                                            (pdf-view-themed-minor-mode 1)))))

pdf-view

(with-eval-after-load 'pdf-tools
  (setq pdf-view-use-unicode-ligther nil)
  (setq pdf-view-use-scaling t)
  (setq pdf-view-use-imagemagick nil)
  (setq pdf-annot-activate-created-annotations nil))

(defun my/get-file-name ()
  (interactive)
  (kill-new (file-name-base (buffer-file-name)))
  (message "Copied %s" (file-name-base (buffer-file-name))))

(with-eval-after-load 'pdf-view
    (define-key pdf-view-mode-map (kbd "w") 'my/get-file-name)
    (define-key pdf-view-mode-map (kbd "h") 'pdf-annot-add-highlight-markup-annotation)
    (define-key pdf-view-mode-map (kbd "t") 'pdf-annot-add-text-annotation)
    (define-key pdf-view-mode-map (kbd "d") 'pdf-annot-delete)
    (define-key pdf-view-mode-map (kbd "q") 'kill-this-buffer)
    (define-key pdf-view-mode-map (kbd "y") 'pdf-view-kill-ring-save)
    (define-key pdf-view-mode-map (kbd "G") 'pdf-view-goto-page)
    (define-key pdf-view-mode-map [remap pdf-misc-print-document] 'mrb/pdf-misc-print-pages))

pdf-outline

(with-eval-after-load 'pdf-outline
  (define-key pdf-outline-buffer-mode-map (kbd "<RET>") 'pdf-outline-follow-link-and-quit))

pdf-annot

(with-eval-after-load 'pdf-annot
  (define-key pdf-annot-edit-contents-minor-mode-map (kbd "<return>") 'pdf-annot-edit-contents-commit)
  (define-key pdf-annot-edit-contents-minor-mode-map (kbd "<S-return>") 'newline))

Create pdf annotations file. 基于 consult-bibtex 这个包实现的。

(defun my/edit-notes ()
  "Edit reference note base pdf name."
  (interactive)
  (if (equal (file-name-extension (buffer-name)) "pdf")
      (consult-bibtex-edit-notes (file-name-sans-extension (buffer-name)))
    (consult-bibtex-edit-notes (consult-bibtex--read-entry))))

Extract pdf annotations with pdfannots. 这边的实现还有些问题,回头重新优化下。

 (defun my/org-delete-heading-content (heading)
   "Delete content of specific HEADING"
   (org-map-entries
    (lambda ()
      (let ((name (nth 4 (org-heading-components))))
	 (if (string= name heading)
	     (save-restriction
	       (org-mark-subtree)
	       (forward-line)
	       (delete-region (region-beginning) (region-end))))))))
 (defun my/extract-pdf-annots-to-ref-note ()
   (interactive)
   (let (annots)
     (setf annots (shell-command-to-string (format "pdfannots.py %s" (find-file (buffer-name)))))
     (consult-bibtex-edit-notes (file-name-sans-extension (buffer-name)))
     (my/org-delete-heading-content "Research Contribution")
     (goto-char (org-find-exact-headline-in-buffer "Research Contribution"))
     (forward-line)
     (dolist (item (split-string annots "\n"))
	(if (string-prefix-p "   >" item)
	    (princ (concat (replace-regexp-in-string "   >" "+" item) "\n")
		   (current-buffer))))))

pdf-cache

(with-eval-after-load 'pdf-cache
  (define-pdf-cache-function pagelabels))

pdf print

可以通过命令行使用打印机,不再需要用外部软件打开再去打印文件,而且可以选择打印的页数。来自 Marcel van der Boom 的配置文件。

(with-eval-after-load 'pdf-misc
  (setq pdf-misc-print-program-executable "/usr/bin/lp"))

(defun mrb/pdf-misc-print-pages(filename pages &optional interactive-p)
    "Wrapper for `pdf-misc-print-document` to add page selection support."
    (interactive (list (pdf-view-buffer-file-name)
           (read-string "Page range (empty for all pages): "
                    (number-to-string (pdf-view-current-page)))
           t) pdf-view-mode)
    (let ((pdf-misc-print-program-args
       (if (not (string-blank-p pages))
       (cons (concat "-P " pages) pdf-misc-print-program-args)
         pdf-misc-print-program-args)))
  (pdf-misc-print-document filename)))

pdf password

需要安装 qpdf ,如果使用 Homebrew,可以使用 brew install qpdfEmacs: Password-protect current pdf

 (defun pdf-password-protect ()
   "Password protect current pdf in buffer or `dired' file."
   (interactive)
   (unless (executable-find "qpdf")
     (user-error "QPDF not installed"))
   (unless (equal "pdf"
		   (or (when (buffer-file-name)
			 (downcase (file-name-extension (buffer-file-name))))
		       (when (dired-get-filename nil t)
			 (downcase (file-name-extension (dired-get-filename nil t))))))
     (user-error "No pdf to act on"))
   (let* ((user-password (read-passwd "user-password: "))
	   (owner-password (read-passwd "owner-password: "))
	   (input (or (buffer-file-name)
		      (dired-get-filename nil t)))
	   (output (concat (file-name-sans-extension input)
			   "_enc.pdf")))
     (message
      (string-trim
	(shell-command-to-string
	 (format "qpdf --verbose --encrypt %s %s 256 -- %s %s"
		 user-password owner-password input output))))))

EPUB Reader

(straight-use-package 'nov)
(with-eval-after-load 'nov
  (setq nov-unzip-program (executable-find "bsdtar")
        nov-unzip-args '("-xC" directory "-f" filename)))
(add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode))

Calibredb

(straight-use-package 'calibredb)
(with-eval-after-load 'calibredb
  (setq calibredb-root-dir "~/Nextcloud/L.Calibre/")
  (setq calibredb-db-dir (expand-file-name "metadata.db" calibredb-root-dir))
  (setq calibredb-add-delete-original-file t)
  (setq calibredb-size-show t)
  (setq calibredb-format-character-icons t)

  (setq calibredb-ref-default-bibliography (expand-file-name "calibre.bib" calibredb-root-dir)))

(global-set-key (kbd "<f1>") 'calibredb)
(with-eval-after-load 'evil
  (evil-set-initial-state 'calibredb-search-mode 'emacs))

Vterm

(straight-use-package 'vterm)
(with-eval-after-load 'vterm
  (setq vterm-kill-buffer-on-exit t)
  (setq vterm-max-scrollback 5000))

(general-define-key
 :states '(normal visual emacs)
 :prefix "SPC"
 :non-normal-prefix "M-SPC"
 "tv" '(vterm :wk "vterm"))

Mail

(setq display-time-mail-icon `(,(propertize (all-the-icons-material "mail")
                                            'face `(:family ,(all-the-icons-material-family)))))
(setq message-sendmail-envelope-from 'header)
(setq message-kill-buffer-query nil)
(setq message-sendmail-extra-arguments '("-a" "outlook"))
(setq message-send-mail-function 'sendmail-send-it)
(with-eval-after-load 'mu4e
  (setq send-mail-function 'sendmail-send-it)
  (setq sendmail-program (executable-find "msmtp"))
  (setq mail-specify-envelope-from t)
  (setq mail-envelope-from 'header))
(unless (fboundp 'mu4e)
  (autoload #'mu4e "mu4e" nil t))

(with-eval-after-load 'mu4e
  (setq mu4e-mu-binary "/opt/homebrew/bin/mu")
  (setq mail-user-agent 'mu4e-user-agent)
  (setq mu4e-mu-binary (executable-find "mu"))
  (setq mu4e-update-interval (* 15 60))
  (setq mu4e-attachment-dir "~/Downloads/")
  (setq mu4e-get-mail-command (concat (executable-find "mbsync") " -a"))
  (setq mu4e-index-update-in-background t)
  (setq mu4e-index-update-error-warning t)
  (setq mu4e-index-update-error-warning nil)
  (setq mu4e-index-cleanup t)
  (setq mu4e-view-show-images t)
  (setq mu4e-view-image-max-width 800)
  (setq mu4e-view-show-addresses t)
  (setq mu4e-confirm-quit nil)
  (setq mu4e-context-policy 'pick-first)
  (with-eval-after-load 'mu4e
    (setq mu4e-sent-folder   "/outlook/Sent"
          mu4e-drafts-folder "/outlook/Drafts"
          mu4e-trash-folder  "/outlook/Deleted"
          mu4e-refile-folder  "/outlook/Archive"))
  (setq mu4e-view-prefer-html nil)
  (setq mu4e-html2text-command 'mu4e-shr2text)
  (setq mu4e-main-hide-personal-addresses t)
  (setq mu4e-headers-precise-alignment t)
  (setq mu4e-headers-include-related t)
  (setq mu4e-headers-auto-update t)
  (setq mu4e-headers-date-format "%d/%m/%y")
  (setq mu4e-headers-time-format "%H:%M")
  (setq mu4e-headers-fields '((:flags . 12)
                              (:human-date . 9)
                              (:subject . 90)
                              (:from-or-to . 40)
                              (:tags . 20)))
  (setq mu4e-use-fancy-chars nil)
  (setq mu4e-bookmarks '(("flag:unread AND NOT flag:trashed" "Unread messages" ?u)
                         ("date:today..now" "Today's messages" ?t)
                         ("date:7d..now" "Last 7 days" ?w)
                         ("date:1d..now AND NOT list:emacs-orgmode.gnu.org" "Last 1 days" ?o)
                         ("date:1d..now AND list:emacs-orgmode.gnu.org" "Last 1 days (org mode)" ?m)
                         ("maildir:/drafts" "drafts" ?d)
                         ("flag:flagged AND NOT flag:trashed" "flagged" ?f)
                         ("mime:image/*" "Messages with images" ?p)))
  (setq mu4e-compose-reply-ignore-address '("no-?reply" "duan_n@outlook.com"))
  (setq mu4e-compose-format-flowed nil)
  (setq mu4e-compose-signature-auto-include nil)
  (setq mu4e-compose-dont-reply-to-self t))

(run-with-idle-timer 10 nil #'(lambda () (mu4e 'background)))

(general-define-key
 :states '(normal visual emacs)
 :prefix "SPC"
 :non-normal-prefix "M-SPC"
 "M" '(mu4e :wk "MAIL"))

Mu4e-alert

(straight-use-package 'mu4e-alert)

(add-hook 'mu4e-index-updated-hook 'mu4e-alert-enable-notifications)
(with-eval-after-load 'mu4e
  (mu4e-alert-enable-mode-line-display))

Mu4e-column-faces

(straight-use-package 'mu4e-column-faces)

(with-eval-after-load 'mu4e
  (mu4e-column-faces-mode))

Mu4e headers icon

(with-eval-after-load 'mu4e-headers
  (setq mu4e-use-fancy-chars t)
  (setq mu4e-headers-list-mark `("s" . ,(propertize
                                         (all-the-icons-material "list")
                                         'face `(:family ,(all-the-icons-material-family)))))
  (setq mu4e-headers-seen-mark `("S" . ,(propertize
                                         (all-the-icons-material "mail_outline")
                                         'face `(:family ,(all-the-icons-material-family)))))

  (setq mu4e-headers-new-mark `("N" . ,(propertize
                                        (all-the-icons-material "markunread")
                                        'face `(:family ,(all-the-icons-material-family)))))

  (setq mu4e-headers-unread-mark `("u" . ,(propertize
                                           (all-the-icons-material "notifications_none")
                                           'face `(:family ,(all-the-icons-material-family)))))
  (setq mu4e-headers-signed-mark `("s" . ,(propertize
                                           (all-the-icons-material "check")
                                           'face `(:family ,(all-the-icons-material-family)))))

  (setq mu4e-headers-encrypted-mark `("x" . ,(propertize
                                              (all-the-icons-material "enhanced_encryption")
                                              'face `(:family ,(all-the-icons-material-family)))))

  (setq mu4e-headers-draft-mark `("D" . ,(propertize
                                          (all-the-icons-material "drafts")
                                          'face `(:family ,(all-the-icons-material-family)))))
  (setq mu4e-headers-attach-mark `("a" . ,(propertize
                                           (all-the-icons-material "attachment")
                                           'face `(:family ,(all-the-icons-material-family)))))
  (setq mu4e-headers-passed-mark `("P" . ,(propertize ; ❯ (I'm participated in thread)
                                           (all-the-icons-material "center_focus_weak")
                                           'face `(:family ,(all-the-icons-material-family)))))
  (setq mu4e-headers-flagged-mark `("F" . ,(propertize
                                            (all-the-icons-material "flag")
                                            'face `(:family ,(all-the-icons-material-family)))))
  (setq mu4e-headers-replied-mark `("R" . ,(propertize
                                            (all-the-icons-material "reply_all")
                                            'face `(:family ,(all-the-icons-material-family)))))
  (setq mu4e-headers-trashed-mark `("T" . ,(propertize
                                            (all-the-icons-material "cancel")
                                            'face `(:family ,(all-the-icons-material-family)))))

  (setq mu4e-headers-personal-mark `("p" . ,(propertize
                                             (all-the-icons-material "person")
                                             'face `(:family ,(all-the-icons-material-family)
                                                             :foreground 'mu4e-special-header-value-face)))))

Mu4e-conversation

(straight-use-package 'mu4e-conversation)

(with-eval-after-load 'mu4e
  (global-mu4e-conversation-mode))

Telega

(straight-use-package 'telega)

(setq telega-proxies (list '(:server "127.0.0.1" :port 8889 :enable t
                                     :type (:@type "proxyTypeHttp"))))
(setq telega-server-libs-prefix "/opt/homebrew/Cellar/tdlib/1.8.0/include/")

(general-define-key
 :states '(normal visual emacs)
 :prefix "SPC"
 :non-normal-prefix "M-SPC"
 "T" '(telega :wk "Telega"))