Skip to content

xhcoding/emacs.d

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

72 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GNU Emacs

A simple Emacs config on Windows

安装方法

  1. 下载配置文件
    git clone https://github.com/xhcoding/emacs.d.git ~/.emacs.d
        
  2. 运行 org-tangle.bat 生成 .el 文件
  3. 启动 emacs

目录

early-init.el

Emacs 启动过程的早期加载。

;;; early-init.el --- early init  -*- lexical-binding: t no-byte-compile: t; -*-

设置配置启动模式

(defconst my-config-mode (intern (downcase (or (getenv "EMACS_CONFIG_MODE") "full"))))

(defconst full? (eq my-config-mode 'full))

(defconst minimal? (eq my-config-mode 'minimal))

(defconst debug? (eq my-config-mode 'debug))

(defconst not-full? (not full?))

(defconst not-minimal? (not minimal?))

(defconst not-debug? (not debug?))

开启 debug-on-error

(when debug?
  (setq toggle-debug-on-error t))

将 gc 的阈值设置到最大,避免启动的时候多次 gc 拖慢启动速度

(setq gc-cons-threshold most-positive-fixnum)

file handler 临时设置为 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)))))))

不要初始化 package

(setq package-enable-at-startup nil)

禁止 frame 缩放

(setq frame-inhibit-implied-resize t)

关闭菜单栏

(push '(menu-bar-lines . 0) default-frame-alist)

关闭工具栏

(push '(tool-bar-lines . 0) default-frame-alist)

关闭滚动条

(push '(vertical-scroll-bars) default-frame-alist)

启动后全屏

(when full?
  (push '(fullscreen . fullscreen) default-frame-alist))

straight 包管理

(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name
        "straight/repos/straight.el/bootstrap.el"
        (or (bound-and-true-p straight-base-dir)
            user-emacs-directory)))
      (bootstrap-version 7))
  (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))

(setq straight-vc-git-default-clone-depth 1)

空闲时增量加载包

from: https://github.com/doomemacs/doomemacs/blob/master/lisp/doom-start.el

(defvar doom-incremental-packages '(t)
  "A list of packages to load incrementally after startup. Any large packages
here may cause noticeable pauses, so it's recommended you break them up into
sub-packages. For example, `org' is comprised of many packages, and can be
broken up into:

  (doom-load-packages-incrementally
   '(calendar find-func format-spec org-macs org-compat
     org-faces org-entities org-list org-pcomplete org-src
     org-footnote org-macro ob org org-clock org-agenda
     org-capture))

This is already done by the lang/org module, however.

If you want to disable incremental loading altogether, either remove
`doom-load-packages-incrementally-h' from `emacs-startup-hook' or set
`doom-incremental-first-idle-timer' to nil. Incremental loading does not occur
in daemon sessions (they are loaded immediately at startup).")

(defvar doom-incremental-first-idle-timer (if (daemonp) 0 2.0)
  "How long (in idle seconds) until incremental loading starts.

Set this to nil to disable incremental loading.
Set this to 0 to load all incrementally deferred packages immediately at
`emacs-startup-hook'.")

(defvar doom-incremental-idle-timer 0.75
  "How long (in idle seconds) in between incrementally loading packages.")

(defun doom-load-packages-incrementally (packages &optional now)
  "Registers PACKAGES to be loaded incrementally.

If NOW is non-nil, load PACKAGES incrementally, in `doom-incremental-idle-timer'
intervals."
  (let ((gc-cons-threshold most-positive-fixnum))
    (if (not now)
        (cl-callf append doom-incremental-packages packages)
      (while packages
        (let ((req (pop packages))
              idle-time)
          (if (featurep req)
              (message "start:iloader: Already loaded %s (%d left)" req (length packages))
            (condition-case-unless-debug e
                (and
                 (or (null (setq idle-time (current-idle-time)))
                     (< (float-time idle-time) doom-incremental-first-idle-timer)
                     (not
                      (while-no-input
                        (message "start:iloader: Loading %s (%d left)" req (length packages))
                        ;; If `default-directory' doesn't exist or is
                        ;; unreadable, Emacs throws file errors.
                        (let ((default-directory user-emacs-directory)
                              (inhibit-message t)
                              (file-name-handler-alist
                               (list (rassq 'jka-compr-handler file-name-handler-alist))))
                          (require req nil t)
                          t))))
                 (push req packages))
              (error
               (message "Error: failed to incrementally load %S because: %s" req e)
               (setq packages nil)))
            (if (null packages)
                (message "start:iloader: Finished!")
              (run-at-time (if idle-time
                               doom-incremental-idle-timer
                             doom-incremental-first-idle-timer)
                           nil #'doom-load-packages-incrementally
                           packages t)
              (setq packages nil))))))))

(defun doom-load-packages-incrementally-h ()
  "Begin incrementally loading packages in `doom-incremental-packages'.

If this is a daemon session, load them all immediately instead."
  (when (numberp doom-incremental-first-idle-timer)
    (if (zerop doom-incremental-first-idle-timer)
        (mapc #'require (cdr doom-incremental-packages))
      (run-with-idle-timer doom-incremental-first-idle-timer
                           nil #'doom-load-packages-incrementally
                           (cdr doom-incremental-packages) t))))

(add-hook 'emacs-startup-hook #'doom-load-packages-incrementally-h 100)

use-package 扩展

添加 :ban 关键字,不要执行 use-package

(require 'use-package)
(push :ban use-package-keywords)

(defalias 'use-package-normalize/:ban 'use-package-normalize-test)

(defalias 'use-package-handler/:ban 'use-package-handler/:unless)

启动速度测试

(use-package benchmark-init
  :ban not-debug?
  :straight t
  :demand t
  :hook (after-init . benchmark-init/deactivate)
  :bind ("<f7>" . benchmark-init/show-durations-tree)
  )

emacs 配置

;;; init.el --- init  -*- lexical-binding: t no-byte-compile: t; -*-

载入 custom 文件

(setq custom-file (expand-file-name "custom.el" user-emacs-directory))
(load custom-file t)

定义一些固定的常量

(defconst sys-is-windows (memq system-type '(cygwin windows-nt ms-dos)))

(defconst sys-no-windows (not sys-is-windows))

;; 动态库目录
(defconst my-lib-dir (expand-file-name "lib" user-emacs-directory))
(add-to-list 'load-path my-lib-dir)

;; 可执行文件目录
(defconst my-bin-dir (expand-file-name "bin" user-emacs-directory))
;; 将 my-bin-dir 加入到 PATH 中
(setenv "PATH" (concat my-bin-dir (if sys-is-windows ";" ":") (getenv "PATH")))


;; 外部配置文件目录
(defconst my-etc-dir (expand-file-name "etc" user-emacs-directory))

;; 存放 org 文件的目录
(defconst my-org-dir (expand-file-name "~/Org/"))

;; 私有文件目录
(defconst my-private-dir (expand-file-name "private" my-org-dir))

;; 私有代码片段目录
(defconst my-private-snippets-dir (expand-file-name "snippets" my-private-dir))

;; 存放代码的目录
(if sys-is-windows
    (defconst my-code-dir (expand-file-name "D:/Code"))
  (defconst my-code-dir (expand-file-name "~/Code")))

;; 存放一些存档文件的目录
(if sys-is-windows
    (defconst my-archives-dir (expand-file-name "D:/Archives"))
  (defconst my-archives-dir (expand-file-name "~/Archives")))

常用的工具函数

重命名当前文件

(defun my/rename-this-file-and-buffer (new-name)
  "Rename both current buffer and file it's visiting to NEW_NAME."
  (interactive "sNew name: ")
  (let ((name (buffer-name))
        (filename (buffer-file-name)))
    (unless filename
      (error "Buffer '%s' is not visiting a file" name))
    (progn
      (when (file-exists-p filename)
        (rename-file filename new-name 1))
      (set-visited-file-name new-name)
      (rename-buffer new-name))))

删除当前文件

(defun my/delete-file-and-buffer ()
  "Kill the current buffer and deletes the file it is visiting."
  (interactive)
  (let ((filename (buffer-file-name)))
    (when (and filename (y-or-n-p (concat "Do you really want to delete file " filename "?")))
      (delete-file filename t)
      (message "Deleted file %s." filename)
      (kill-buffer)
      )))

清理 Message buffer

(defun my/clear-messages-buffer ()
  "Clear *Messages* buffer."
  (interactive)
  (let ((inhibit-read-only t))
    (with-current-buffer "*Messages*"
      (erase-buffer))))

开关网络代理

(defun my/toggle-url-proxy ()
  "Toggle proxy for the url.el library."
  (interactive)
  (require 'url)
  (cond
   (url-proxy-services
    (message "Turn off URL proxy")
    (setq url-proxy-services nil))
   (t
    (message "Turn on URL proxy")
    (setq url-proxy-services
          '(("http" . "127.0.0.1:7890")
            ("https" . "127.0.0.1:7890")
            ("no_proxy" . "0.0.0.0"))))))

清理掉没用的 buffer

(defun my/kill-unused-buffers ()
  "Kill unused buffers."
  (interactive)
  (ignore-errors
    (save-excursion
      (dolist (buf (buffer-list))
        (set-buffer buf)
        (when (and (string-prefix-p "*" (buffer-name)) (string-suffix-p "*" (buffer-name)))
          (kill-buffer buf))
        ))))

基本设置

用户信息

 (setq user-full-name "xhcoding"
	user-mail-address "xhcoding@foxmail.com")

增加 IO 性能

(setq process-adaptive-read-buffering nil)
(setq read-process-output-max (* 1024 1024))

设置编码

默认使用 utf-8 ,在 windows 文件名编码使用 gbk ,不然打不开中文文件

(prefer-coding-system 'utf-8)
(when sys-is-windows
  (setq file-name-coding-system 'gbk))

设置 frame 标题

(setq frame-title-format "Emacs")

关闭启动动画

(setq inhibit-startup-message t)

scratch buffer 设置

scratch 为 fundaemental-mode

(setq initial-major-mode 'fundamental-mode)

scratch buffer 内容为空

(setq initial-scratch-message nil)

yes-or-no-p 改为 y-or-n-p

(setq use-short-answers t)

关掉提示

(setq ring-bell-function 'ignore)

禁止光标闪烁

(blink-cursor-mode -1)

启动时不要显示

For information about GNU Emacs…

(advice-add #'display-startup-echo-area-message :override #'ignore)

tab 设置

(setq-default tab-width 4)

(setq-default indent-tabs-mode nil)

一行不超过 120 字符

(setq-default fill-column 120)
(column-number-mode +1)

删除文件时放到回收站

(setq-default delete-by-moving-to-trash t)

高亮当前行

(global-hl-line-mode +1)

gcmh 优化垃圾回收

(use-package gcmh
  :straight t
  :hook (emacs-startup . gcmh-mode)
  :init
  (setq gcmh-idle-delay 'auto
	  gcmh-auto-idle-delay-factor 10
	  gcmh-high-cons-threshold 33554432)) ; 32MB

auto-save 自动保存

ref: https://github.com/manateelazycat/auto-save

(use-package auto-save
  :straight (auto-save :type git :host github :repo "manateelazycat/auto-save")
  :defer 3
  :init
  ;; 关闭 emacs 默认的自动备份
  (setq make-backup-files nil)
  ;; 关闭 emacs 默认的 自动保存
  (setq auto-save-default nil)
  :config
  (setq auto-save-silent t)
  (auto-save-enable)
  )

rime 智能的中文输入法

(use-package rime
  :straight t
  :defer t
  :bind ("C-j" . rime-force-enable)
  :init
  (setq rime--module-path
        (expand-file-name (concat "librime-emacs" module-file-suffix) my-lib-dir))
  (when sys-is-windows
    (setq rime-share-data-dir (expand-file-name "rime-data" my-etc-dir)))

  (defun my-*require-rime(&rest _)
    "Require rime when toggle-input-method."
    (unless (featurep 'rime)
      (require 'rime)))

  (advice-add 'toggle-input-method :before #'my-*require-rime)

  :custom
  (default-input-method "rime")
  (rime-user-data-dir (expand-file-name "rime-user" my-etc-dir))

  :config
  (setq
   rime-disable-predicates '(rime-predicate-after-alphabet-char-p
                             rime-predicate-prog-in-code-p))
  (if (display-graphic-p)
      (setq rime-show-candidate 'posframe)
    (setq rime-show-candidate 'minibuffer)))

字体和主题

(use-package faces
  :ban minimal?
  :custom-face
  (variable-pitch
   ((t (:font ,(font-xlfd-name (font-spec :family "Alegreya"))))))
  (default
   ((t (:font ,(font-xlfd-name (font-spec :family "CaskaydiaCove NFP"))
              :height 120))))
  (fixed-pitch
   ((t (:inherit default))))
  :config
  (set-fontset-font t 'emoji (font-spec :family "Segoe UI Symbol"))

  (set-fontset-font t 'chinese-gbk (font-spec :family "微软雅黑"))

  )


(load-theme 'modus-operandi-tinted :no-confirm)

ligature 连字体

(use-package ligature
  :ban minimal?
  :straight t
  :defer t
  :hook prog-mode
  :config
  ;; Enable all Cascadia Code ligatures in programming modes
  (ligature-set-ligatures 'prog-mode '("|||>" "<|||" "<==>" "<!--" "####" "~~>" "***" "||=" "||>"
                                       ":::" "::=" "=:=" "===" "==>" "=!=" "=>>" "=<<" "=/=" "!=="
                                       "!!." ">=>" ">>=" ">>>" ">>-" ">->" "->>" "-->" "---" "-<<"
                                       "<~~" "<~>" "<*>" "<||" "<|>" "<$>" "<==" "<=>" "<=<" "<->"
                                       "<--" "<-<" "<<=" "<<-" "<<<" "<+>" "</>" "###" "#_(" "..<"
                                       "..." "+++" "/==" "///" "_|_" "www" "&&" "^=" "~~" "~@" "~="
                                       "~>" "~-" "**" "*>" "*/" "||" "|}" "|]" "|=" "|>" "|-" "{|"
                                       "[|" "]#" "::" ":=" ":>" ":<" "$>" "==" "=>" "!=" "!!" ">:"
                                       ">=" ">>" ">-" "-~" "-|" "->" "--" "-<" "<~" "<*" "<|" "<:"
                                       "<$" "<=" "<>" "<-" "<<" "<+" "</" "#{" "#[" "#:" "#=" "#!"
                                       "##" "#(" "#?" "#_" "%%" ".=" ".-" ".." ".?" "+>" "++" "?:"
                                       "?=" "?." "??" ";;" "/*" "/=" "/>" "//" "__" "~~" "(*" "*)"
                                       "\\\\" "://")))

doom-modeline

(use-package doom-modeline
  :straight t
  :hook (after-init . doom-modeline-mode))

hydra 方便的快捷键设置

(use-package hydra
  :straight t)

(use-package pretty-hydra
  :straight t
  :after hydra
  :init
  (cl-defun pretty-hydra-title (title &optional icon-type icon-name
                                      &key face height v-adjust)
    "Add an icon in the hydra title."
    (let ((face (or face `(:foreground ,(face-background 'highlight))))
          (height (or height 1.0))
          (v-adjust (or v-adjust 0.0)))
      (concat
       (when (and icon-type icon-name)
         (let ((f (intern (format "all-the-icons-%s" icon-type))))
           (when (fboundp f)
             (concat
              (apply f (list icon-name :face face :height height :v-adjust v-adjust))
              " "))))
       (propertize title 'face face)))))

recentf 读取最近文件

(use-package recentf
  :commands (recentf-open-files)
  :hook (after-init . recentf-mode)
  :bind ("C-x C-r" . recentf-open-files)
  :init (setq recentf-max-saved-items 500
              recentf-exclude
              '("\\.?cache" ".cask" "url" "COMMIT_EDITMSG\\'" "bookmarks"
                "\\.\\(?:gz\\|gif\\|svg\\|png\\|jpe?g\\|bmp\\|xpm\\)$"
                "\\.?ido\\.last$" "\\.revive$" "/G?TAGS$" "/.elfeed/"
                "^/tmp/" "^/var/folders/.+$" ; "^/ssh:"
                (lambda (file) (file-in-directory-p file package-user-dir))))
  :config
  (push (expand-file-name recentf-save-file) recentf-exclude))

popper 更方便的弹出窗口

(use-package popper
  :straight t
  :hook (emacs-startup . popper-mode)
  :init
  (setq popper-reference-buffers
        '(
          help-mode
          rustic-cargo-run-mode
          lsp-bridge-ref-mode
          "^\\*eshell.*\\*$" eshell-mode
          ;; emacs-aichat
          "^\\*?[aA]ichat.*\\*$"

          "^\\*xref*\\*$"
          "^\\*compilation*\\*$"
          ))
  :config

  (with-no-warnings
    (defun my-popper-fit-window-height (win)
      "Determine the height of popup window WIN by fitting it to the buffer's content."
      (fit-window-to-buffer
       win
       (floor (frame-height) 3)
       (floor (frame-height) 3)))
    (setq popper-window-height #'my-popper-fit-window-height)

    (defun my-popper-window-popper-p (buffer)
      "Whether `buffer' is popper window."
      (when-let* ((window (caar popper-open-popup-alist))
                  (buffer (cdar popper-open-popup-alist))
                  (window-p (string= (buffer-name) (buffer-name buffer))))
        window))

    (defun my-popper-close-window (&rest _)
      "Close popper window via `C-g'."
      ;; `C-g' can deactivate region
      (when (and (called-interactively-p 'interactive)
                 (not (region-active-p))
                 popper-open-popup-alist)
        (let ((window (caar popper-open-popup-alist)))
          (when (window-live-p window)
            (delete-window window)))))

    (advice-add #'keyboard-quit :before #'my-popper-close-window))
  )

autorevert 自动同步外部文件改变

(use-package autorevert
  :config
  (global-auto-revert-mode +1))

minibuffer 补全及增强

(use-package pinyinlib
  :straight t
  :defer t)

(use-package orderless
  :straight t
  :custom (completion-styles '(orderless))
  :config
  ;; 拼音搜索支持
  (defun completion--regex-pinyin (str)
    (require 'pinyinlib)
    (orderless-regexp (pinyinlib-build-regexp-string str)))
  (add-to-list 'orderless-matching-styles 'completion--regex-pinyin)
  )

(use-package vertico
  :straight t
  :config
  (vertico-mode +1))

(use-package marginalia
  :after (vertico)
  :straight t
  :config
  (marginalia-mode +1))


(use-package consult
  :straight t
  :defer t
  :bind (("C-s" . consult-line)
         ("C-x b" . consult-buffer)
         ("C-x C-b" . consult-bookmark)
         ("C-x C-i" . consult-imenu))
  :custom
  (consult-preview-key nil)
  (consult-buffer-sources '(consult--source-buffer consult--source-recent-file))
  :config

  (when sys-is-windows
    (add-to-list 'process-coding-system-alist '("es.exe" gbk . gbk))
    (add-to-list 'process-coding-system-alist '("explorer" gbk . gbk))
    (add-to-list 'process-coding-system-alist '("rg" utf-8 . gbk))
    (setq consult-locate-args (encode-coding-string "es.exe -i -p -r" 'gbk))))

vundo undo 增强

(use-package vundo
  :straight t
  :bind ("C-x u" . vundo))

avy 快速移动光标到屏幕上任意字符

(use-package avy
  :straight t
  :bind (("M-'" . my/avy-goto-char-timer))
  :init
  (defun my/avy-goto-char-timer (&optional arg)
    "Make avy-goto-char-timer support pinyin"
    (interactive "P")
    (require 'pinyinlib)
    (require 'avy)
    (let ((avy-all-windows (if arg
                               (not avy-all-windows)
                             avy-all-windows)))
      (avy-with avy-goto-char-timer
        (setq avy--old-cands (avy--read-candidates
                              'pinyinlib-build-regexp-string))
        (avy-process avy--old-cands))))


  :config
  (setq avy-all-windows nil
        avy-all-windows-alt t
        avy-background t
        avy-style 'pre))

ace-window 快速切换到其它 window

(use-package ace-window
  :straight t
  :pretty-hydra
  ((:title (pretty-hydra-title "Window Management" 'faicon "th" :height 1.1 :v-adjust -0.1)
           :foreign-keys warn :quit-key ("q" "C-g"))
   ("Split"
    (("r" split-window-right "horizontally" :exit t)
     ("R" split-window-right "horizontally continue")
     ("v" split-window-below "vertically" :exit t)
     ("V" split-window-below "vertically continue"))

    "Resize"
    (("h" shrink-window-horizontally "")
     ("j" enlarge-window "")
     ("k" shrink-window "")
     ("l" enlarge-window-horizontally "")
     ("n" balance-windows "balance" :exit t))

    "Zoom"
    (("+" text-scale-increase "in")
     ("=" text-scale-increase "in")
     ("-" text-scale-decrease "out")
     ("0" (text-scale-increase 0) "reset"))))
    :bind (("M-o" . ace-window)
           ("C-c w" . ace-window-hydra/body))

  )

toggle-one-window 快速切换到单窗口

ref:https://github.com/manateelazycat/toggle-one-window

(defvar my-window--configuration nil
  "The window configuration use for `toggle-one-window'.")

(defun my-window--one-window-p ()
  (equal 1 (length (cl-remove-if #'(lambda (w)
                                 (and
                                  (window-dedicated-p w)
                                  (not (window-parameter w 'quit-restore))))
                             (window-list)))))

(defun my/toggle-one-window ()
  "Toggle between window layout and one window."
  (interactive)
  (cond
   ;; 如果当前 buffer 所在 Window 是 popper
   ((my-popper-window-popper-p (current-buffer))
    (if (my-window--one-window-p)
        (when my-window--configuration
          (set-window-configuration my-window--configuration)
          (setq my-window--configuration nil))

      (setq my-window--configuration (current-window-configuration))
      (let ((buffer (current-buffer)))
        (other-window 1)
        (delete-other-windows)
        (switch-to-buffer buffer))))
   (t
    (if (my-window--one-window-p)
        (when my-window--configuration
          (set-window-configuration my-window--configuration)
          (setq my-window--configuration nil))
      (setq my-window--configuration (current-window-configuration))
      (delete-other-windows)))))

(global-set-key (kbd "M-;") #'my/toggle-one-window)

yasnippet 快速插入代码片段

(use-package yasnippet
  :straight t
  :defer t
  :config
  (add-to-list 'yas-snippet-dirs my-private-snippets-dir)
  (yas-reload-all))

(use-package yasnippet-snippets
  :straight t
  :after yasnippet)

separedit 快速编辑

(use-package separedit
  :straight t
  :bind ("C-c '" . separedit))

compile 增强

(use-package fancy-compilation
  :straight t
  :after compile
  :config
  (setq fancy-compilation-override-colors nil)
  (fancy-compilation-mode +1))

eshell 增强

eshell 基本配置

(use-package eshell
  :defer t
  :custom
  (eshell-kill-processes-on-exit t)
  :config

  (my-cc--import-vcvars)
  (setq exec-path (parse-colon-path (getenv "Path")))

  ;; 默认为插入模式
  (add-to-list 'meow-mode-state-list '(eshell-mode . insert))


  ;; 配合 popper 实现 toggle 效果
  (defun my/eshell ()
    (interactive)
    (if-let* ((window (caar popper-open-popup-alist))
             (buffer (cdar popper-open-popup-alist))
             (eshell-opened (string= eshell-buffer-name (buffer-name buffer))))
        (when (window-live-p window)
          (delete-window window))
      (eshell)))

  ;; cat 高亮
  (defun my-eshell-cat-with-syntax-highlight (file)
    "Like cat but with syntax highlight."
    (with-temp-buffer
      (insert-file-contents file)
      (let ((buffer-file-name file))
        (delay-mode-hooks
          (set-auto-mode)
          (font-lock-ensure)))
      (buffer-string)))

  (advice-add 'eshell/cat :override #'my-eshell-cat-with-syntax-highlight)

  )

eshell 历史记录设置

(use-package em-hist
  :defer t
  :custom
  (eshell-history-size 10240)
  (eshell-hist-ignoredups t)
  (eshell-save-history-on-exit t))

esh-mode 设置

(use-package esh-mode
  :bind (:map eshell-mode-map
              ("C-r" . consult-history)))

eshell-git-prompt 提示符美化

(use-package eshell-git-prompt
  :straight t
  :after esh-mode
  :config
  (eshell-git-prompt-use-theme 'powerline))

eshell-z 智能目录跳转

(use-package eshell
  :defer t
  :config
  (require 'em-dirs)
  (defvar my-eshell-z--table nil)

  (defvar my-eshell-z-file-name (expand-file-name "z" eshell-directory-name))

  (defun my-eshell-z--load ()
    (setq my-eshell-z--table (make-hash-table :test 'equal))
    (when (file-exists-p my-eshell-z-file-name)
      (dolist (element (with-temp-buffer
                         (insert-file-contents my-eshell-z-file-name)
                         (goto-char (point-min))
                         (read (current-buffer))))
        (when (file-directory-p (car element))
          (puthash (car element) (cadr element) my-eshell-z--table)))))

  (defun my-eshell-z--save ()
    (let ((dir (file-name-directory my-eshell-z-file-name)))
      (unless (file-exists-p dir)
        (make-directory dir t))
      (with-temp-file my-eshell-z-file-name
        (let ((result (list)))
          (maphash #'(lambda (key value)
                       (when (> value 0)
                         (add-to-list 'result (list key (- value 0.1))))
                       )
                   my-eshell-z--table)
          (prin1 result (current-buffer))))))

  (defun my-eshell-z--update ()
    (let ((cur-dir default-directory))
      (if-let ((score (gethash cur-dir my-eshell-z--table)))
          (puthash cur-dir (+ score 1) my-eshell-z--table)
        (puthash cur-dir 1 my-eshell-z--table))))

  (defun eshell/z (&rest args)
    (let* ((first (car args))
           (result first))
      (if (not first)
          (setq result "~")
        (cond
         ((string-match-p "^[\\.]+$" first)
          (let ((target ""))
            (cl-loop repeat (length first) do
                     (setq target (concat target "../")))
            (setq result target)))
         ((string= "-" first)
          (setq result first))
         (t (let ((regex "")
                  target-score
                  target-dir)
              (dolist (arg args)
                (setq regex (concat regex arg ".*")))
              (maphash #'(lambda (key value)
                           (when (string-match-p regex key)
                             (when (and target-score (> value target-score))
                               (setq target-dir key
                                     target-score value))
                             (unless target-score
                               (setq target-dir key
                                     target-score value))))
                       my-eshell-z--table)
              (if target-dir
                  (setq result target-dir)
                (setq result args))
              ))))
      ;; (message "result: %s" result)
      (eshell/cd result)))
  (add-hook 'eshell-mode-hook #'my-eshell-z--load)
  (add-hook 'eshell-directory-change-hook #'my-eshell-z--update)
  (add-hook 'kill-emacs-hook #'my-eshell-z--save)

  (defun eshell/zp (&rest args)
    "Jump directory in current project."
    (let* ((project-root (nth 2 (project-current))))
      (unless project-root
        (setq project-root default-directory))
      (when-let* ((result (eshell-command-result
                          (concat "fd --type directory --absolute-path " (car args) " " project-root)))
                  (paths (split-string result "\n" t)))
        (if (= (length paths) 1)
            (eshell/cd (car paths))
          (eshell/cd (completing-read "Choose: " paths nil t))))))
  )

fingertip 智能括号插入

(use-package fingertip
  :straight (fingertip :type git :host github :repo "manateelazycat/fingertip")
  :defer t
  :hook ((prog-mode toml-ts-mode) . my-enable-pair-parents)
  :bind (:map fingertip-mode-map
              ("(" . fingertip-open-round)
              ("[" . fingertip-open-bracket)
              ("{" . fingertip-open-curly)
              (")" . fingertip-close-round)
              ("]" . fingertip-close-bracket)
              ("}" . fingertip-close-curly)
              ("=" . fingertip-equal)
              ("\"" . fingertip-double-quote)
              ("SPC" . fingertip-space)
              ("RET". fingertip-newline)
              ("C-k" . fingertip-kill)
              ("M-\"" . fingertip-wrap-double-quote)
              ("M-[" . fingertip-wrap-bracket)
              ("M-{" . fingertip-wrap-curly)
              ("M-(" . fingertip-wrap-round)
              ("M-]" . fingertip-unwrap)
              ("M-n" . fingertip-jump-right)
              ("M-p" . fingertip-jump-left)
              ("M-RET" . fingertip-jump-out-pair-and-newline))
  :init
  (defun my-enable-pair-parents ()
    (if (treesit-parser-list)
        (fingertip-mode)
      (electric-pair-mode))))

color-rg 快速搜索重构

(use-package color-rg
  :straight (color-rg :type git :host github :repo "manateelazycat/color-rg")
  :defer t
  :commands (color-rg-search-symbol-in-project color-rg-search-input-in-project)
  :custom
  (color-rg-search-no-ignore-file nil)
  :config
  (add-to-list 'meow-mode-state-list '(color-rg-mode . motion)))

markdown

(use-package markdown-mode
  :straight t
  :defer t)

lsp-bridge 代码补全

(use-package lsp-bridge
  :disabled t
  :straight (lsp-bridge :type git :host github :repo "manateelazycat/lsp-bridge"
                        :files ("*")
                        :build nil)
  :defer t
  :bind (:map lsp-bridge-mode-map
              ([remap xref-find-definitions] . lsp-bridge-find-def)
              ([remap xref-find-references] . lsp-bridge-find-references)
              ([remap xref-go-back] . lsp-bridge-find-def-return))
  :init

  ;; 手动添加到 load-path
  (add-to-list 'load-path (straight--repos-dir "lsp-bridge"))

  (setq lsp-bridge-org-babel-lang-list nil)

  ;; https://tecosaur.github.io/emacs-config/config.html#lsp-support-src
  (cl-defmacro my-lsp-org-babel-enable (lang)
    "Support LANG in org source code block."
    (cl-check-type lang string)
    (let* ((edit-pre (intern (format "org-babel-edit-prep:%s" lang)))
           (intern-pre (intern (format "lsp--%s" (symbol-name edit-pre)))))
      `(progn
         (defun ,intern-pre (info)
           (let ((file-name (->> info caddr (alist-get :file))))
             (unless file-name
               (setq file-name (expand-file-name "OrgBabel/org-src-babel" my-code-dir))
               (write-region (point-min) (point-max) file-name))
             (setq buffer-file-name file-name)
             (my-enable-code-service)))
         (put ',intern-pre 'function-documentation
              (format "Enable lsp-bridge-mode in the buffer of org source block (%s)."
                      (upcase ,lang)))
         (if (fboundp ',edit-pre)
             (advice-add ',edit-pre :after ',intern-pre)
           (progn
             (defun ,edit-pre (info)
               (,intern-pre info))
             (put ',edit-pre 'function-documentation
                  (format "Prepare local buffer environment for org source block (%s)."
                          (upcase ,lang))))))))

  (with-eval-after-load 'org
    (dolist (lang '("c" "cpp" "python" "rust"))
      (eval `(my-lsp-org-babel-enable ,lang))))


  :config
  (push '(scss-mode . "vscode-css-language-server") lsp-bridge-single-lang-server-mode-list)
  (push '(json-ts-mode . "vscode-json-language-server") lsp-bridge-single-lang-server-mode-list)

  (setq lsp-bridge-user-langserver-dir my-etc-dir
        lsp-bridge-user-multiserver-dir my-etc-dir)


  (setq acm-enable-tabnine nil
        acm-enable-quick-access t
        lsp-bridge-enable-hover-diagnostic t)
  )

acm-terminal

(use-package popon
  :disabled t
  :straight t
  :defer t)
(use-package acm-terminal
  :disabled t
  :defer t
  :straight (acm-terminal :type git :host github :repo "twlz0ne/acm-terminal")
  :init
  (unless (display-graphic-p)
    (with-eval-after-load 'acm
      (require 'acm-terminal))))

company 补全

(use-package company
  :straight t
  :defer t
  :bind (:map company-active-map
              ("TAB" . company-complete-selection)))

eglot-booster 加速 eglot

(use-package eglot-booster
  :straight (eglot-booster :type git :host github :repo "jdtsmith/eglot-booster")
  :after eglot
  :config (eglot-booster-mode))

启动代码服务

(defun my-enable-code-service ()
  (require 'yasnippet)
  (yas-minor-mode)
  (company-mode)
  (eglot-ensure))

highlight-parentheses 高亮外层括号

(use-package highlight-parentheses
  :straight t
  :hook (prog-mode . highlight-parentheses-mode))

evil-nerd-commenter 智能注释代码

(use-package evil-nerd-commenter
  :straight t
  :bind ("C-/" . evilnc-comment-or-uncomment-lines))

apheleia 智能格式化代码

(use-package apheleia
  :straight t
  :defer t)

git 配置

magit

(use-package magit
  :straight (magit :type git :host github :repo "magit/magit"
                   :files ("lisp/magit"
                           "lisp/magit*.el"
                           "lisp/git-rebase.el"
                           "docs/magit.texi"
                           "docs/AUTHORS.md"
                           "LICENSE"
                           "Documentation/magit.texi" ; temporarily for stable
                           "Documentation/AUTHORS.md" ; temporarily for stable
                           ))
  :bind ("C-x g" . magit-status)
  :config
  (when sys-is-windows
    (setenv "GIT_ASKPASS" "git-gui--askpass")))

(use-package ssh-agency
  :straight t
  :after magit)

显示当前行的最后提交信息

(use-package git-messenger
  :straight t
  :bind (:map vc-prefix-map
         ("p" . git-messenger:popup-message)
         :map git-messenger-map
         ("m" . git-messenger:copy-message))
  :init
  (setq git-messenger:show-detail t
        git-messenger:use-magit-popup t)

  (defface posframe-border
    `((t (:inherit region)))
    "Face used by the `posframe' border."
    :group 'posframe)
  :config
  (with-no-warnings
    (with-eval-after-load 'hydra
      (defhydra git-messenger-hydra (:color blue)
        ("s" git-messenger:popup-show "show")
        ("c" git-messenger:copy-commit-id "copy hash")
        ("m" git-messenger:copy-message "copy message")
        ("," (catch 'git-messenger-loop (git-messenger:show-parent)) "go parent")
        ("q" git-messenger:popup-close "quit")))

    (defun my-git-messenger:format-detail (vcs commit-id author message)
      (if (eq vcs 'git)
          (let ((date (git-messenger:commit-date commit-id))
                (colon (propertize ":" 'face 'font-lock-comment-face)))
            (concat
             (format "%s%s %s \n%s%s %s\n%s  %s %s \n"
                     (propertize "Commit" 'face 'font-lock-keyword-face) colon
                     (propertize (substring commit-id 0 8) 'face 'font-lock-comment-face)
                     (propertize "Author" 'face 'font-lock-keyword-face) colon
                     (propertize author 'face 'font-lock-string-face)
                     (propertize "Date" 'face 'font-lock-keyword-face) colon
                     (propertize date 'face 'font-lock-string-face))
             (propertize (make-string 38 ?─) 'face 'font-lock-comment-face)
             message
             (propertize "\nPress q to quit" 'face '(:inherit (font-lock-comment-face italic)))))
        (git-messenger:format-detail vcs commit-id author message)))

    (defun my-git-messenger:popup-message ()
      "Popup message with `posframe', `pos-tip', `lv' or `message', and dispatch actions with `hydra'."
      (interactive)
      (let* ((hydra-hint-display-type 'message)
             (vcs (git-messenger:find-vcs))
             (file (buffer-file-name (buffer-base-buffer)))
             (line (line-number-at-pos))
             (commit-info (git-messenger:commit-info-at-line vcs file line))
             (commit-id (car commit-info))
             (author (cdr commit-info))
             (msg (git-messenger:commit-message vcs commit-id))
             (popuped-message (if (git-messenger:show-detail-p commit-id)
                                  (my-git-messenger:format-detail vcs commit-id author msg)
                                (cl-case vcs
                                  (git msg)
                                  (svn (if (string= commit-id "-")
                                           msg
                                         (git-messenger:svn-message msg)))
                                  (hg msg)))))
        (setq git-messenger:vcs vcs
              git-messenger:last-message msg
              git-messenger:last-commit-id commit-id)
        (run-hook-with-args 'git-messenger:before-popup-hook popuped-message)
        (git-messenger-hydra/body)
        (cond ((and (fboundp 'posframe-workable-p) (posframe-workable-p))
               (let ((buffer-name "*git-messenger*"))
                 (posframe-show buffer-name
                                :string (concat (propertize "\n" 'face '(:height 0.3))
                                                popuped-message
                                                "\n"
                                                (propertize "\n" 'face '(:height 0.3)))
                                :left-fringe 8
                                :right-fringe 8
                                :max-width (round (* (frame-width) 0.62))
                                :max-height (round (* (frame-height) 0.62))
                                :internal-border-width 1
                                :internal-border-color (face-background 'posframe-border nil t)
                                :background-color (face-background 'tooltip nil t))
                 (unwind-protect
                     (push (read-event) unread-command-events)
                   (posframe-hide buffer-name))))
              ((and (fboundp 'pos-tip-show) (display-graphic-p))
               (pos-tip-show popuped-message))
              ((fboundp 'lv-message)
               (lv-message popuped-message)
               (unwind-protect
                   (push (read-event) unread-command-events)
                 (lv-delete-window)))
              (t (message "%s" popuped-message)))
        (run-hook-with-args 'git-messenger:after-popup-hook popuped-message)))
    (advice-add #'git-messenger:popup-close :override #'ignore)
    (advice-add #'git-messenger:popup-message :override #'my-git-messenger:popup-message)))

smerge-mode 解决冲突

(use-package smerge-mode
  :ensure nil
  :pretty-hydra
  ((:title (pretty-hydra-title "Smerge" 'octicon "diff")
           :color pink :quit-key "q")
   ("Move"
    (("n" smerge-next "next")
     ("p" smerge-prev "previous"))
    "Keep"
    (("b" smerge-keep-base "base")
     ("u" smerge-keep-upper "upper")
     ("l" smerge-keep-lower "lower")
     ("a" smerge-keep-all "all")
     ("RET" smerge-keep-current "current")
     ("C-m" smerge-keep-current "current"))
    "Diff"
    (("<" smerge-diff-base-upper "upper/base")
     ("=" smerge-diff-upper-lower "upper/lower")
     (">" smerge-diff-base-lower "upper/lower")
     ("R" smerge-refine "refine")
     ("E" smerge-ediff "ediff"))
    "Other"
    (("C" smerge-combine-with-next "combine")
     ("r" smerge-resolve "resolve")
     ("k" smerge-kill-current "kill")
     )))
  :bind (:map smerge-mode-map
              ("C-c m" . smerge-mode-hydra/body)))

visual-regexp 可视化的正则替换

(use-package visual-regexp
  :straight t
  :defer t)

expand-region 递增选区

(use-package expand-region
  :straight t
  :bind (("C-=" . er/expand-region))
  :config
  (defun treesit-mark-bigger-node ()
    (let* ((root (treesit-buffer-root-node))
           (node (treesit-node-descendant-for-range root (region-beginning) (region-end)))
           (node-start (treesit-node-start node))
           (node-end (treesit-node-end node)))
      ;; Node fits the region exactly. Try its parent node instead.
      (when (and (= (region-beginning) node-start) (= (region-end) node-end))
        (when-let ((node (treesit-node-parent node)))
          (setq node-start (treesit-node-start node)
                node-end (treesit-node-end node))))
      (set-mark node-end)
      (goto-char node-start)))

  (add-to-list 'er/try-expand-list 'treesit-mark-bigger-node)
  )

启用 treesit

(setq major-mode-remap-alist
      '((c-mode          . c-ts-mode)
        (c++-mode        . c++-ts-mode)
        (c-or-c++-mode   . c-or-c++-ts-mode)
        (cmake-mode      . cmake-ts-mode)
        (conf-toml-mode  . toml-ts-mode)
        (csharp-mode     . csharp-ts-mode)
        (css-mode        . css-ts-mode)
        (dockerfile-mode . dockerfile-ts-mode)
        (go-mode         . go-ts-mode)
        (java-mode       . java-ts-mode)
        (json-mode       . json-ts-mode)
        (js-json-mode    . json-ts-mode)
        (javascript-mode . js-ts-mode)
        (python-mode     . python-ts-mode)
        (sh-mode         . bash-ts-mode)))

(setq treesit-language-source-alist
      '((bash . ("https://github.com/tree-sitter/tree-sitter-bash"))
        (c . ("https://github.com/tree-sitter/tree-sitter-c"))
        (cpp . ("https://github.com/tree-sitter/tree-sitter-cpp"))
        (css . ("https://github.com/tree-sitter/tree-sitter-css"))
        (cmake . ("https://github.com/uyha/tree-sitter-cmake"))
        (csharp     . ("https://github.com/tree-sitter/tree-sitter-c-sharp.git"))
        (dockerfile . ("https://github.com/camdencheek/tree-sitter-dockerfile"))
        (elisp . ("https://github.com/Wilfred/tree-sitter-elisp"))
        (go . ("https://github.com/tree-sitter/tree-sitter-go"))
        (gomod      . ("https://github.com/camdencheek/tree-sitter-go-mod.git"))
        (html . ("https://github.com/tree-sitter/tree-sitter-html"))
        (java       . ("https://github.com/tree-sitter/tree-sitter-java.git"))
        (javascript . ("https://github.com/tree-sitter/tree-sitter-javascript"))
        (json . ("https://github.com/tree-sitter/tree-sitter-json"))
        (lua . ("https://github.com/Azganoth/tree-sitter-lua"))
        (make . ("https://github.com/alemuller/tree-sitter-make"))
        (markdown . ("https://github.com/MDeiml/tree-sitter-markdown" nil "tree-sitter-markdown/src"))
        (ocaml . ("https://github.com/tree-sitter/tree-sitter-ocaml" nil "ocaml/src"))
        (org . ("https://github.com/milisims/tree-sitter-org"))
        (python . ("https://github.com/tree-sitter/tree-sitter-python"))
        (php . ("https://github.com/tree-sitter/tree-sitter-php"))
        (typescript . ("https://github.com/tree-sitter/tree-sitter-typescript" nil "typescript/src"))
        (tsx . ("https://github.com/tree-sitter/tree-sitter-typescript" nil "tsx/src"))
        (ruby . ("https://github.com/tree-sitter/tree-sitter-ruby"))
        (rust . ("https://github.com/tree-sitter/tree-sitter-rust"))
        (sql . ("https://github.com/m-novikov/tree-sitter-sql"))
        (vue . ("https://github.com/merico-dev/tree-sitter-vue"))
        (yaml . ("https://github.com/ikatyang/tree-sitter-yaml"))
        (toml . ("https://github.com/tree-sitter/tree-sitter-toml"))
        (zig . ("https://github.com/GrayJack/tree-sitter-zig"))))

elisp 配置

(use-package elisp-mode
  :hook (emacs-lisp-mode . my-enable-elisp-dev)
  :config
  (defun my-enable-elisp-dev ()
    (my-enable-code-service)
    (treesit-parser-create 'elisp)))

C++ 开发配置

cmake-ts-mode 设置

(use-package cmake-ts-mode
  :hook (cmake-ts-mode . my-enable-code-service))

c-ts-mode 设置

(use-package c-ts-mode
  :hook ((c-ts-mode c++-ts-mode) . my-enable-code-service)
  :custom
  (c-ts-mode-indent-offset  4)
  (c-basic-offset 4))

导入 VS 环境变量

(defconst my-cc--msvc-env-vars
  '(
    "DevEnvDir"
    "Framework40Version"
    "FrameworkDir"
    "FrameworkDIR32"
    "FrameworkDIR64"
    "FrameworkVersion"
    "FrameworkVersion32"
    "FrameworkVersion64"
    "INCLUDE"
    "LIB"
    "LIBPATH"
    "NETFXSDKDir"
    "PATH"
    "UCRTVersion"
    "UniversalCRTSdkDir"
    "user_inputversion"
    "VCIDEInstallDir"
    "VCINSTALLDIR"
    "VCToolsInstallDir"
    "VCToolsRedistDir"
    "VCToolsVersion"
    "VS170COMNTOOLS"
    "VisualStudioVersion"
    "VSINSTALLDIR"
    "WindowsLibPath"
    "WindowsSdkBinPath"
    "WindowsSdkDir"
    "WindowsSDKLibVersion"
    "WindowsSDKVersion"
    "WindowsSDK_ExecutablePath_x64"
    "WindowsSDK_ExecutablePath_x86"
    ;;/* These are special also need to be cached */
    "CL"
    "_CL_"
    "LINK"
    "_LINK_"
    "TMP"
    "UCRTCONTEXTROOT"
    "VCTARGETSPATH"
)
  "List of environment variables required for Visual C++ to run as expected for a VS installation.")

;; 导入 vs2022 community 64位构建环境变量
(defun my-cc--import-vcvars ()
  "Import the environment variables corresponding to a VS dev batch file."
  (let* ((common-dir "C:/Program Files/Microsoft Visual Studio/2022/Community/Common7/Tools")
         (devbat "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Auxiliary/Build/vcvarsall.bat")
         (args "amd64")
         (major-version "17")
         (bat-path (expand-file-name "cmake-tools-vcvars.bat" (temporary-file-directory)))
         (env-file-path (concat bat-path ".env"))
         (vars (make-hash-table :test 'equal)))
    (setq bat
          (format "@echo off
cd /d \"%%~dp0\"
set \"VS%s0COMNTOOLS=%s\"
set \"INCLUDE=\"
call \"%s\" %s
setlocal enableextensions enabledelayedexpansion
cd /d \"%%~dp0\"
%s" major-version common-dir devbat args
(mapconcat (lambda (env-var) (format "if DEFINED %s echo %s := %%%s%% >> %s" env-var env-var env-var env-file-path))
           my-cc--msvc-env-vars "\n")))
    (with-temp-file bat-path
      (insert bat))
    (shell-command-to-string (concat "cmd.exe /c " bat-path))

    (if (not (file-exists-p env-file-path))
        nil
      (dolist (line (with-temp-buffer
                      (insert-file-contents env-file-path)
                      (split-string (buffer-string) "\n" t)))
        (let ((var (split-string line " := " t)))
          (puthash (string-trim (car var)) (string-trim (cadr var)) vars)))
      (if (not (gethash "INCLUDE" vars))
          nil
        (maphash #'(lambda (key value)
                     (setenv key value))
                 vars)))))

Rust 开发配置

(use-package rust-ts-mode
  :hook (rust-ts-mode . my-enable-code-service))

python 开发配置

(use-package python
  :hook (python-ts-mode . my-enable-code-service))

json-ts-mode

(use-package json-ts-mode
  :hook (json-ts-mode . my-enable-code-service)
  :custom
  (json-ts-mode-indent-offset 2)
  :config
  (defun my-json-generate-language-server-json ()
    (with-temp-file (expand-file-name "vscode-json-language-server.json" my-etc-dir)
      (url-insert-file-contents "https://www.schemastore.org/api/json/catalog.json")
      (let* ((catalog (json-parse-string (buffer-substring-no-properties (point-min) (point-max)) :object-type 'plist))
             (schemas (plist-get catalog :schemas))
             (exclude-pattern ".*\.\\(cff\\|cjs\\|js\\|mjs\\|toml\\|yaml\\|yml\\)$")
             (json-validation ))
        (mapc #'(lambda (schema)
                  (when-let* ((url (plist-get schema :url))
                              (file-match (plist-get schema :fileMatch))
                              (filtered-file-match (seq-filter #'(lambda (match)
                                                                   (and (not (string-prefix-p "!" match))
                                                                        (not (string-match-p exclude-pattern match))))
                                                               file-match)))
                    (add-to-list 'json-validation `((:url . ,url) (:fileMatch ., filtered-file-match)))))
              schemas)

        (let* ((ls '(:name "vscode-json-language-server" :languageId "json" :command ("vscode-json-language-server" "--stdio")))
               (json `(:schemas ,json-validation))
               (settings `(:json ,json))
               (json-encoding-pretty-print t))
          (plist-put ls :settings settings)
          (erase-buffer)
          (goto-char (point-min))
          (insert (json-encode ls))))))
  )

qml 开发

(use-package qml-ts-mode
  :ban minimal?
  :straight (qml-ts-mode :type git :host github :repo "xhcoding/qml-ts-mode")
  :hook (qml-ts-mode . my-enable-code-service)
  :custom
  (qml-ts-mode-indent-offset 4))

plantuml 支持

(use-package plantuml-mode
  :ban minimal?
  :straight t
  :defer t
  :custom
  (plantuml-jar-path (expand-file-name "plantuml.jar" my-lib-dir))
  (plantuml-default-exec-mode 'jar))

dape 调试

(use-package dape
  :straight t)

dash-docs 查询 dash 文档

(use-package dash-docs
  :ban minimal?
  :straight t
  :commands (my/dash-docs-search)
  :bind (([remap apropos-documentation] . my/dash-docs-search)
         ("<f1> D" . my/dash-docs-search-docset))
  :defer t
  :init
  ;; 初始化文档
  (dolist (mode-docsets
           '((c++-ts-mode-hook . ("C++" "Boost" "Qt_6"))
             (qml-ts-mode-hook . ("Qt_6"))
             (cmake-ts-mode-hook . ("CMake"))
             (rust-ts-mode-hook . ("Rust"))
             (python-ts-mode-hook . ("Python_3"))
             ))
    (let ((hook (car mode-docsets))
          (docsets (cdr mode-docsets)))
      (add-hook hook `(lambda ()
                        (setq-local dash-docs-docsets ',docsets)
                        ))))

  :custom
  (dash-docs-enable-debugging nil)
  (dash-docs-browser-func 'eaf-open-browser)
  :config
  (when sys-is-windows
    (setq dash-docs-docsets-path (expand-file-name "persist/zeal/docsets" (getenv "SCOOP"))))

  ;; fix can not open..
  ;; ref: https://github.com/dash-docs-el/dash-docs/pull/11
  (defun dash-docs-sql (db-path sql)
    "Run in the db located at DB-PATH the SQL command and parse the results.
If there are errors, print them in `dash-docs-debugging-buffer'"
    (dash-docs-parse-sql-results
     (with-output-to-string
       (let ((error-file (when dash-docs-enable-debugging
                           (make-temp-file "dash-docs-errors-file"))))
         (call-process "sqlite3" nil (list standard-output error-file) nil
                       ;; args for sqlite3:
                       "-list" "-init" null-device db-path sql)

         ;; display errors, stolen from emacs' `shell-command` function
         (when (and error-file (file-exists-p error-file))
           (if (< 0 (nth 7 (file-attributes error-file)))
               (with-current-buffer (dash-docs-debugging-buffer)
                 (let ((pos-from-end (- (point-max) (point))))
                   (or (bobp)
                       (insert "\f\n"))
                   ;; Do no formatting while reading error file,
                   ;; because that can run a shell command, and we
                   ;; don't want that to cause an infinite recursion.
                   (format-insert-file error-file nil)
                   ;; Put point after the inserted errors.
                   (goto-char (- (point-max) pos-from-end)))
                 (display-buffer (current-buffer))))
           (delete-file error-file))))))

  ;; 搜索文档
  (defun my/dash-docs-search (&optional pattern)
    "Search doc."
    (interactive)
    (when-let ((search-pattern
                (or pattern
                    (let* ((current-symbol
                            (if (use-region-p)
                                (buffer-substring-no-properties (region-beginning) (region-end))
                              (thing-at-point 'symbol)))
                           (input-string
                            (string-trim
                             (read-string
                              (format "Pattern (%s): " current-symbol) nil))))
                      (when (string-blank-p input-string)
                        (setq input-string current-symbol))
                      input-string))))
      (dash-docs-create-buffer-connections)
      (dash-docs-create-common-connections)
      (when-let ((results (dash-docs-search search-pattern))
                 (select t)
                 (select-index -1)
                 (select-result t))
        (setq select (completing-read "Select: "
                                      (let ((index 0))
                                        (mapcar (lambda (result)
                                                  (setq index (+ index 1))
                                                  (format "%s. %s" index (car result)))
                                                results))
                                      nil t))
        (setq select-index (- (string-to-number (car (split-string select "\\. "))) 1))
        (setq select-result (nth select-index results))
        (dash-docs-browse-url (cdr select-result)))))

  (defun my/dash-docs-search-docset (&optional docset)
    "Search doc in `docset'"
    (interactive (list (dash-docs-read-docset
                        "Docset"
                        (dash-docs-installed-docsets))))
    (unless (boundp 'dash-docs-docsets)
      (setq-local dash-docs-docsets `(,docset)))

    (let ((old-dash-docs-docsets dash-docs-docsets))
      (unwind-protect
          (progn
          (setq-local dash-docs-docsets `(,docset))
          (call-interactively #'my/dash-docs-search)
          (setq-local dash-docs-docsets old-dash-docs-docsets))
        (setq-local dash-docs-docsets old-dash-docs-docsets))))

  )

shrface 让 eww 的阅读体验更好

(use-package shrface
  :ban minimal?
  :straight t
  :defer t
  :config
  (shrface-basic)
  (shrface-trial)
  (shrface-default-keybindings) ; setup default keybindings
  (setq shrface-href-versatile t))

eww 配置

(use-package eww
  :ban minimal?
  :defer t
  :init
  (add-hook 'eww-after-render-hook #'shrface-mode)
  :config
  (require 'shrface))

olivetti 居中显示内容

(use-package olivetti
  :ban minimal?
  :straight t
  :defer t
  :hook (prog-mode text-mode outline-mode special-mode elfeed-show-mode)
  )

org 配置

org-mode 美化设置

(use-package org
  :ban minimal?
  :defer t
  :straight t
  :hook (org-mode . my--org-prettify-symbols)
  :custom-face
  ;; 设置Org mode标题以及每级标题行的大小
  (org-document-title ((t (:height 1.75 :weight bold))))
  (org-level-1 ((t (:height 1.2 :weight bold))))
  (org-level-2 ((t (:height 1.15 :weight bold))))
  (org-level-3 ((t (:height 1.1 :weight bold))))
  (org-level-4 ((t (:height 1.05 :weight bold))))
  (org-level-5 ((t (:height 1.0 :weight bold))))
  (org-level-6 ((t (:height 1.0 :weight bold))))
  (org-level-7 ((t (:height 1.0 :weight bold))))
  (org-level-8 ((t (:height 1.0 :weight bold))))
  (org-level-9 ((t (:height 1.0 :weight bold))))
  ;; 设置代码块用上下边线包裹
  (org-block-begin-line ((t (:underline t :background unspecified))))
  (org-block-end-line ((t (:overline t :underline nil :background unspecified))))
  :custom
  ;; 标题行美化
  (org-fontify-whole-heading-line t)
  ;; 设置标题行折叠符号
  (org-ellipsis "")
  ;; TODO标签美化
  (org-fontify-todo-headline t)
  ;; DONE标签美化
  (org-fontify-done-headline t)
  ;; 引用块美化
  (org-fontify-quote-and-verse-blocks t)
  ;; 隐藏宏标记
  (org-hide-macro-markers t)
  ;; 隐藏强调标签
  (org-hide-emphasis-markers t)
  ;; 高亮latex语法
  (org-highlight-latex-and-related '(native script entities))
  ;; 以UTF-8显示
  (org-pretty-entities t)
  ;; 当启用缩进模式时自动隐藏前置星号
  (org-indent-mode-turns-on-hiding-stars t)
  ;; 自动启用缩进
  (org-startup-indented nil)
  ;; 根据标题栏自动缩进文本
  (org-adapt-indentation nil)
  ;; 自动显示图片
  (org-startup-with-inline-images t)
  ;; 默认以Overview的模式展示标题行
  (org-startup-folded 'overview)
  ;; 允许字母列表
  (org-list-allow-alphabetical t)
  ;; 编辑时检查是否在折叠的不可见区域
  (org-fold-catch-invisible-edits 'smart)
  ;; 上标^下标_是否需要特殊字符包裹,这里设置需要用大括号包裹
  (org-use-sub-superscripts '{})
  :config

  (create-fontset-from-fontset-spec
   (font-xlfd-name
    (font-spec :family "等距更纱黑体 SC"
               :weight 'regular
               :slant 'normal
               :registry "fontset-orgtable")))

  (set-fontset-font "fontset-orgtable" '(#x0 . #xffff)
                    (font-spec :family "等距更纱黑体 SC"
                               :weight 'regular
                               :slant 'normal))

  (set-face-attribute 'org-table nil :fontset "fontset-orgtable" :font "fontset-orgtable")

  (defun my--org-prettify-symbols ()
    (setq prettify-symbols-alist
          (mapcan (lambda (x) (list x (cons (upcase (car x)) (cdr x))))
                  '(
                    ("#+begin_src"                        . ?✎)
                    ("#+end_src"                          . ?□)
                    ("#+results:"                         . ?💻)
                    ("#+date:"                            . ?📅)
                    ("#+author:"                          . ?👤)
                    ("#+title:"                           . ?📓)
                    ("#+identifier:"                 . ?🆔)
                    ("#+hugo_tags:"                       . ?📍)
                    ("#+hugo_categories:"                 . ?📁)
                    ("#+hugo_locale:"                     . ?🌐)
                    ("#+hugo_draft:"                      . ?🚮)
                    ("#+hugo_custom_front_matter:"        . ?📝)
                    ("#+begin_quote"                      . )
                    ("#+end_quote"                        . )
                    )))
    (setq prettify-symbols-unprettify-at-point t)
    (prettify-symbols-mode 1))

  ;; 设置标题行之间总是有空格;列表之间根据情况自动加空格
  (setq org-blank-before-new-entry '((heading . t)
                                     (plain-list-item . auto)
                                     )))

(use-package org-modern
  :ban minimal?
  :straight t
  :hook (org-mode . org-modern-mode)
  :config
  ;; 额外的行间距,0.1表示10%,1表示1px
  (setq-default line-spacing 0.1)
  ;; 复选框美化
  (setq org-modern-checkbox
        '((?X . #("▢✓" 0 2 (composition ((2)))))
          (?- . #("▢–" 0 2 (composition ((2)))))
          (?\s . #("" 0 1 (composition ((1)))))))
  ;; 列表符号美化
  (setq org-modern-list
        '((?- . "")
          (?+ . "")
          (?* . "")))
  ;; 代码块类型美化,我们使用了 `prettify-symbols-mode'
  (setq org-modern-block-name nil)
  ;; #+关键字美化,我们使用了 `prettify-symbols-mode'
  (setq org-modern-keyword nil)
  ;; 关闭表格美化
  (setq org-modern-table nil)
  )

(use-package org-appear
  :ban minimal?
  :straight t
  :hook (org-mode . org-appear-mode)
  :config
  (setq org-appear-autolinks t)
  (setq org-appear-autosubmarkers t)
  (setq org-appear-autoentities t)
  (setq org-appear-autokeywords t)
  (setq org-appear-inside-latex t))

org 基本配置

(use-package org
  :ban minimal?
  :defer t
  :straight t
  :custom
  (org-directory my-org-dir)
  (org-modules '(ol-wl))
  (org-structure-template-alist
   '(("q" . "quote\n")
     ("s" . "src")
     ("e" . "src elisp\n")
     ("c" . "src cpp\n")
     ("h" . "export html"))
   ))

org babel 设置

(use-package org
  :ban minimal?
  :defer t
  :straight t
  :custom
  (org-confirm-babel-evaluate nil)
  (org-export-use-babel nil)
  (org-src-lang-modes '(("C" . c-ts)
                       ("C++" . c++-ts)
                       ("asymptote" . asy)
                       ("bash" . sh)
                       ("beamer" . latex)
                       ("calc" . fundamental)
                       ("cpp" . c++-ts)
                       ("ditaa" . artist)
                       ("desktop" . conf-desktop)
                       ("dot" . fundamental)
                       ("elisp" . emacs-lisp)
                       ("ocaml" . tuareg)
                       ("screen" . shell-script)
                       ("shell" . sh)
                       ("sqlite" . sql)
                       ("toml" . conf-toml)))
  :config
  (org-babel-do-load-languages 'org-babel-load-languages
                               '((emacs-lisp . t)
                                 (perl . t)
                                 (python . t)
                                 (ruby . t)
                                 (js . t)
                                 (css . t)
                                 (sass . t)
                                 (C . t)
                                 (java . t)
                                 (plantuml . t)))
  ;; C 执行支持 :stdin 数据
  (defun my*org-babel-execute-add-stdin(args)
    (let* ((body (nth 0 args))
           (params (nth 1 args))
           (stdin (cdr (assq :stdin params)))
           (cmdline (cdr (assq :cmdline params)))
           (stdin-file (expand-file-name "input_data.txt" (temporary-file-directory)))
           (data))
      (when stdin
        (setq data
              (save-excursion
                (org-babel-goto-named-src-block stdin)
                (org-element-property :value (org-element-at-point))))
        (with-temp-file stdin-file
          (insert data))
        (setq cmdline (concat (or cmdline "") " < " stdin-file))
        (setf (alist-get :cmdline params) cmdline))
      `(,body ,params)
      ))
  (advice-add #'org-babel-C-execute :filter-args 'my*org-babel-execute-add-stdin)

  )

gtd 配置

(use-package org-gtd
  :ban minimal?
  :straight t
  :after org
  :bind (("C-c d c" . org-gtd-capture)
         ("C-c d e" . org-gtd-engage)
         ("C-c d p" . org-gtd-process-inbox)
         ("C-c d n" . org-gtd-show-all-next)
         ("C-c d s" . org-gtd-review-stuck-projects)
         :map org-gtd-clarify-map
         ("C-c c" . org-gtd-organize))
  :init
  (setq org-gtd-update-ack "3.0.0")
  :custom
  (org-gtd-directory (expand-file-name "gtd" my-org-dir))
  :config
  (org-gtd-mode +1)
  (setq org-edna-use-inheritance t)
  (org-edna-mode +1))

export 设置

(use-package org
  :ban minimal?
  :straight t
  :defer t
  :hook (org-mode . my--set-org-html-head-extra)
  :custom
  (org-export-with-broken-links t)
  :config
  (defun my--set-org-html-head-extra ()
    "Set org html head extra"
    (let ((path (expand-file-name "custom-head.html" my-etc-dir)))
      (when (file-exists-p path)
        (setq org-html-head-extra (with-temp-buffer
                                    (insert-file-contents path)
                                    (buffer-string))))))
  )

(use-package htmlize
  :ban minimal?
  :straight t
  :after org)

生成 TOC

(use-package toc-org
  :ban minimal?
  :straight t
  :commands (toc-org-insert-toc)
  :custom
  (toc-org-max-depth 3))

org-contrib wanderlust 支持

(use-package org-contrib
  :ban minimal?
  :straight t
  :after org)

增量载入 org 包

(when full?
  (doom-load-packages-incrementally
   '(calendar find-func format-spec org-macs org-compat org-faces org-entities
              org-list org-pcomplete org-src org-footnote org-macro ob org org-agenda
              org-capture org-gtd)))

知识管理及博客配置

denote

(use-package denote
  :ban minimal?
  :straight t
  :custom
  (denote-directory (expand-file-name "denote" "D:/Code/Test"))
  (denote-known-keywords '("emacs" "life" "cs"))
  (denote-org-front-matter
   "#+title: %1$s
#+date: %2$s
#+author: xhcoding
#+identifier: %4$s
#+hugo_locale: zh
#+hugo_tags: %3$s
#+hugo_categories: %3$s
#+hugo_draft: true

"
)
  :bind (("C-c n n" . denote))
  )

org-roam

(use-package emacsql-sqlite-builtin
  :disabled t
  :straight t
  :defer t)

(use-package org-roam
  :disabled t
  :straight t
  :custom
  (org-roam-directory (expand-file-name "org-roam" my-org-dir))
  :bind (("C-c n l" . org-roam-buffer-toggle)
         ("C-c n f" . org-roam-node-find)
         ("C-c n g" . org-roam-graph)
         ("C-c n i" . org-roam-node-insert)
         ("C-c n c" . org-roam-capture)
         ;; Dailies
         ("C-c n j" . org-roam-dailies-capture-today))
  :init
  (setq org-roam-v2-ack t)
  (setq org-roam-database-connector 'sqlite-builtin)
  :config
  (org-roam-setup)

  (setq org-roam-capture-templates
        '(("d" "default" plain "%?"
           :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
                              "
#+title: ${title}
#+date: %<%Y-%m-%d>
#+author: xhcoding
#+hugo_locale: zh
#+hugo_tags:
#+hugo_categories:
#+hugo_draft: false
")
           :unnarrowed t)))
  )

hugo 配置

;; 博客相关配置
(defconst my-blog-root-dir
  "~/Blog/"
  "Blog root directory.")

(defconst my-blog-img-dir
  (expand-file-name "images/" my-archives-dir)
  "Blog's image directory.")

(defconst my-blog-res-url
  "https://images.xhcoding.cn/blog/"
  "Blog's source address.")

(defconst my-blog-post-url
  "https://xhcoding.cn/post/"
  "Blog's post address.")

(use-package ox-hugo
  :ban minimal?
  :straight t
  :after org
  :custom
  (org-hugo-base-dir my-blog-root-dir)
  (org-hugo-section "post")
  (org-hugo-default-section-directory "post")
  (org-hugo-auto-set-lastmod t)
  :config
  (defun my-blog--blog-image-file-path-p (raw-path)
    (let ((file-path (expand-file-name raw-path)))
      (equal (string-match-p (regexp-quote (expand-file-name my-blog-img-dir)) file-path) 0)))

  ;; ~/Documents/Blog/images/1.png ==> https://images.xhcoding.cn/1.png

  (defun my-blog--convert-image-url (raw-path)
    (let ((file-path (expand-file-name raw-path)))
      (concat my-blog-res-url (string-trim-left file-path my-blog-img-dir))))

  ;; id:xxxxxx ==> https://xhcoding.cn/post/aaaaa
  (defun my-blog--convert-publish-url (id)
    (let ((file-name (file-name-base (org-roam-node-file (org-roam-node-from-id id)))))
      (concat my-blog-post-url file-name)))

  ;; ~/Documents/Blog/images/1.png ==> https://images.xhcoding.cn/1.png
  ;; id:xxxxxx ==> https://xhcoding.cn/post/aaaaa
  (defun my-blog*export-blog-url (args)
    (let* ((link (nth 0 args))
           (desc (nth 1 args))
           (info (nth 2 args))
           (type (org-element-property :type link))
           (raw-path (org-element-property :path link)))
      (cond
       ((and (string-equal type "file") (my-blog--blog-image-file-path-p raw-path))
        (progn
          (let* ((image-url (my-blog--convert-image-url raw-path))
                 (new-link (org-element-put-property link :path image-url )))
            `(,new-link ,desc ,info))))
       ((string-equal type "id")
        (progn
          (let* ((post-url (my-blog--convert-publish-url raw-path))
                 (new-link))
            (setq new-link (org-element-put-property link :path post-url))
            (setq new-link (org-element-put-property new-link :type "file"))
            `(,new-link ,desc, info))))
       (t `(,link ,desc ,info))
       )))

  (advice-add #'org-hugo-link :filter-args #'my-blog*export-blog-url)

  ;; front matter author => authors
  (defun my-blog*convert-author-to-authors(front-matter)
    "Convert author to authors."
    (let ((ret (replace-regexp-in-string "author" "authors" front-matter)))
      ret))

  (advice-add #'org-hugo--get-front-matter :filter-return #'my-blog*convert-author-to-authors)
  )

(use-package easy-hugo
  :ban minimal?
  :straight t
  :defer t
  :config
  (require 'org-roam)
  (setq easy-hugo-basedir (expand-file-name my-blog-root-dir)
        easy-hugo-postdir (expand-file-name org-roam-directory)
        easy-hugo-org-header t
        easy-hugo-github-deploy-script "deploy.bat"))

上传博客图片到腾讯云对象存储

(defun my-blog/upload-images-to-tencent-cos ()
  "Upload blog images dir to tencent cos."
  (interactive)
  (call-process-shell-command
   (concat
    "coscmd upload -rs "
    my-blog-img-dir
    " blog/"
    )))

自动在中英文插入空格

(use-package pangu-spacing
  :ban minimal?
  :straight t
  :after org
  :hook (org-mode . pangu-spacing-mode)
  :custom
  (pangu-spacing-real-insert-separtor t))

org-download 下载图片

(use-package org-download
  :ban minimal?
  :straight t
  :after org
  :config
  (when sys-is-windows
    (setq org-download-screenshot-method "irfanview /capture=4 /convert=\"%s\""
          org-download-display-inline-images 'posframe
          org-download-abbreviate-filename-function 'expand-file-name))

  (setq-default org-download-image-dir my-blog-img-dir
                org-download-heading-lvl nil)


  ;; 截图的名称不要总是 screenshot
  (defun my/org-download-screenshot ()
    "Capture screenshot and insert the resulting file.
The screenshot tool is determined by `org-download-screenshot-method'."
    (interactive)
    (let* ((screenshot-dir (file-name-directory org-download-screenshot-file))
           (org-file-path (buffer-file-name))
           (org-file-name (file-name-sans-extension (file-name-nondirectory org-file-path)))
           (new-screenshot-name (concat org-file-name ".png"))
           (new-screenshot-path (expand-file-name  new-screenshot-name screenshot-dir)))
      (when (and (featurep 'org-roam) (string-prefix-p org-roam-directory org-file-path))
        (setq new-screenshot-name
              (substring new-screenshot-name (+ 1 (string-match-p "-" new-screenshot-name)))
              new-screenshot-path
              (expand-file-name new-screenshot-name screenshot-dir)))
      (make-directory screenshot-dir t)
      (if (functionp org-download-screenshot-method)
          (funcall org-download-screenshot-method
                   org-download-screenshot-file)
        (shell-command-to-string
         (format org-download-screenshot-method
                 org-download-screenshot-file)))
      (when (file-exists-p org-download-screenshot-file)
        (rename-file org-download-screenshot-file new-screenshot-path)
        (org-download-image new-screenshot-path)
        (delete-file new-screenshot-path))))

  (defun my/org-download-clipboard()
    "Download from clipboard"
    (interactive)
    (let ((org-download-screenshot-method "irfanview /clippaste /convert=\"%s\""))
      (my/org-download-screenshot)))

  ;; 将图片保存到当前 buffer 名称目录下
  ;; ref: https://github.com/abo-abo/org-download/issues/195
  (defun my-org-download-method (link)
    (let* ((filename
           (file-name-nondirectory
            (car (url-path-and-query
                  (url-generic-parse-url link)))))
          (org-file-path (buffer-file-name))
          (org-file-name (file-name-sans-extension (file-name-nondirectory org-file-path)))
          (dirname (expand-file-name org-file-name my-blog-img-dir)))
      (when (and (featurep 'org-roam) (string-prefix-p org-roam-directory org-file-path))
        (setq dirname (expand-file-name
                       (substring org-file-name (+ 1 (string-match-p "-" org-file-name)))
                       my-blog-img-dir)))
      (make-directory dirname t)
      (expand-file-name (funcall org-download-file-format-function filename) dirname)))
  (setq org-download-method 'my-org-download-method))

eaf 配置

(use-package eaf
  :ban minimal?
  :straight (emacs-application-framework :type git :host github :repo "emacs-eaf/emacs-application-framework"
                        :files ("*")
                        :build nil)
  :defer t
  :commands (eaf-open eaf-open-browser eaf-open-this-buffer eaf-open-pdf-from-history eaf-open-cloud-music)
  :init
  ;; 手动添加到 load-path
  (add-to-list 'load-path (straight--repos-dir "emacs-application-framework"))
  :config
  (setq eaf-proxy-type "http")
  (setq eaf-proxy-host "127.0.0.1")
  (setq eaf-proxy-port "7890")

  (setq eaf-webengine-default-zoom 2.5)
  (when sys-is-windows
    (setq eaf-chrome-bookmark-file (expand-file-name "~/AppData/Local/Microsoft/Edge/User Data/Default/Bookmarks"))

    (defun my-eaf--enable-python-utf8-mode (environment)
      (append (list "PYTHONUTF8=1") environment))
        (advice-add 'eaf--build-process-environment :filter-return #'my-eaf--enable-python-utf8-mode))
  (require 'eaf-browser)
  (require 'eaf-pdf-viewer)
  (require 'eaf-org-previewer)
  (require 'eaf-markdown-previewer)
  (require 'eaf-music-player))

popweb 配置

(use-package popweb
  :ban minimal?
  :straight (popweb :type git :host github :repo "manateelazycat/popweb" :build nil)
  :commands (popweb-dict-eudic-input popweb-import-browser-cookies)
  :bind ("<f1> t" . popweb-dict-eudic-input)
  :init
  (let ((repo (straight--repos-dir "popweb")))
    (add-to-list 'load-path repo)
    (add-to-list 'load-path (expand-file-name "extension/dict" repo))
    )
  :config
  (require 'popweb-dict)

  ;; 欧陆词典,可以登录后加入生词,方便手机同步
  (popweb-dict-create "eudic" "https://dict.eudic.net/dicts/en/%s"
                      (concat
                       "document.getElementsByTagName('header')[0].style.display = 'none';"
                       "document.getElementById('head-bar').style.display = 'none';"
                       "document.getElementById('head-bk').style.display = 'none';"
                       "document.getElementById('scrollToTop').style.display = 'none';"
                       "document.getElementById('bodycontent').children[4].style.display = 'none';"
                       ))
  )

dictionary-overlay 方便阅读英文文章

(use-package websocket
  :ban minimal?
  :straight t
  :defer t)

(use-package websocket-bridge
  :ban minimal?
  :straight (websocket-bridge :type git :host github :repo "ginqi7/websocket-bridge")
  :defer t)


(use-package dictionary-overlay
  :ban minimal?
  :straight (dictionary-overlay :type git :host github :repo "ginqi7/dictionary-overlay"
                                :build nil)
  :defer t
  :init
  (add-to-list 'load-path (straight--repos-dir "dictionary-overlay"))
  :custom
  (dictionary-overlay-python "python"))

邮件配置

Wanderlust 邮件前端

Wanderlust + offlineimap3 + mu

(use-package wl
  :ban minimal?
  :straight (wanderlust)
  :defer t
  :hook ((wl . meow-motion-mode))
  :bind (:map wl-folder-mode-map
              (("q" . wl-folder-suspend)))
  :init
  (setq wl-init-file (expand-file-name "wl.el" my-private-dir)
        wl-folders-file (expand-file-name "folders.wl" my-private-dir)
        wl-address-file (expand-file-name "address.wl" my-private-dir))
  :config
  (if (boundp 'mail-user-agent)
      (setq mail-user-agent 'wl-user-agent))
  (if (fboundp 'define-mail-user-agent)
      (define-mail-user-agent
        'wl-user-agent
        'wl-user-agent-compose
        'wl-draft-send
        'wl-draft-kill
        'mail-send-hook))
  (setq wl-quicksearch-folder "[]")

  (setq wl-message-ignored-field-list
      '(".")
      wl-message-visible-field-list
      '("^\\(To\\|Cc\\):"
        "^Subject:"
        "^\\(From\\|Reply-To\\):"
        "^\\(Posted\\|Date\\):"
        "^Organization:"
        "^X-\\(Face\\(-[0-9]+\\)?\\|Weather\\|Fortune\\|Now-Playing\\):")
      wl-message-sort-field-list
      (append wl-message-sort-field-list
              '("^Reply-To" "^Posted" "^Date" "^Organization")))

  ;; windows 上 mu find 返回的路径以 /cygdrive/ 开头,我们需要自己处理一下
  (defun my--elmo-search-parse-filename-list ()
    (let (bol locations)
      (goto-char (point-min))
      (while (not (eobp))
        (beginning-of-line)
        (when (and elmo-search-use-drive-letter
                   (looking-at "^\\(/cygdrive/\\)?\\([A-Za-z]\\)\\([:|]\\)?/"))
          (replace-match "/\\2:/")
          (beginning-of-line))
        (unless (looking-at "^file://")
          (insert "file://")
          (beginning-of-line))
        (setq bol (point))
        (end-of-line)
        (setq locations (cons (buffer-substring bol (point)) locations))
        (forward-line))
      (nreverse locations)))


  (elmo-search-register-engine
   'mu-msys 'local-file
   :prog "mu"
   :args '("find" elmo-search-split-pattern-list "--fields" "l")
   :charset 'utf-8
   :parser 'my--elmo-search-parse-filename-list)

  (setq elmo-search-default-engine 'mu-msys)

  ;; mu 的输入要用 gbk 编码,不然无法输入中文
  (add-to-list 'process-coding-system-alist '("mu" utf-8 . gbk))

  ;; mime 附件保存目录
  (setq mime-save-directory (expand-file-name "mails" my-archives-dir))
  )

调用 compose-mail 前先 require wanderlust

(when full?
  
  (defun my-*require-wanderlust (&rest _)
    (require 'wl))

  (advice-add 'compose-mail :before #'my-*require-wanderlust))

alert-toast 邮件通知

(use-package alert-toast
  :ban minimal?
  :straight t
  :after wl
  :config
  (defun my--notify-new-mail-arrived (number)
    (alert-toast-notify `(:title "Wanderlust" :message ,(format "你有 %s 封未读邮件" number))))

  (add-hook 'wl-biff-new-mail-hook #'my--notify-new-mail-arrived)

  )

elfeed RSS 订阅

(use-package elfeed
  :ban minimal?
  :straight t
  :defer t
  :config
  (defun my-eaf-elfeed-open-url ()
    "Display the currently selected item in an eaf buffer."
    (interactive)
    (let ((entry (elfeed-search-selected :ignore-region)))
          (require 'elfeed-show)
          (when (elfeed-entry-p entry)
            ;; Move to next feed item.
            (elfeed-untag entry 'unread)
            (elfeed-search-update-entry entry)
            (unless elfeed-search-remain-on-entry (forward-line))
            (my/toggle-one-window)
            (eaf-open-browser (elfeed-entry-link entry))
            )))
  )

(use-package elfeed-org
  :ban minimal?
  :straight t
  :after elfeed
  :config
  (elfeed-org)
  (setq rmh-elfeed-org-files `(,(expand-file-name "elfeed.org" my-org-dir))))

leetcode.el 刷题

(use-package leetcode
  :ban minimal?
  :straight (leetcode :type git :host github :repo "xhcoding/leetcode.el")
  :defer t
  :custom
  (leetcode-prefer-language "cpp")
  (leetcode-save-solutions t)
  (leetcode-directory (expand-file-name "Project/LeetCode" my-code-dir))
  :config
  ;; 在 leetcode 里关闭自动补全
  (defun my-*after-leetcode--start-coding (problem problem-info)
    (let-alist problem
      (let* ((title (leetcode-problem-title problem-info))
             (code-buf-name (leetcode--get-code-buffer-name title)))
        (with-current-buffer (leetcode--get-code-buffer code-buf-name)
          (when lsp-bridge-mode
            (lsp-bridge-mode -1))))))

  (advice-add 'leetcode--start-coding :after 'my-*after-leetcode--start-coding)

  )

LaTex

(use-package tex
  :ban minimal?
  :straight (auctex)
  :defer t
  :config
  (add-to-list 'TeX-command-list '("XeLaTeX" "%`xelatex --synctex=1%(mode)%' %t" TeX-run-TeX nil t))
  (add-to-list 'TeX-view-program-list '("eaf" eaf-pdf-synctex-forward-view))
  (add-to-list 'TeX-view-program-selection '(output-pdf "eaf")))

emacs-aichat AI 对话

(use-package async-await
  :ban minimal?
  :straight t
  :defer t)

(use-package aichat
  :ban minimal?
  :defer t
  :load-path (lambda () (expand-file-name "Project/emacs-aichat" my-code-dir))
  :config
  (setq aichat-bingai-proxy "localhost:7890"
        aichat-openai-proxy "localhost:7890")
  (aichat-toggle-debug)
  (aichat-bingai-prompt-create "translator"
                               :input-prompt "请翻译: "
                               :text-format "我想让你充当翻译员,我会用任何语言与你交谈,你会检测我说的的语言,如果我说的是中文,你就翻译成英文;如果我说的不是中文,你就翻译成英文。你只需要翻译该内容,不必对内容中提出的问题和要求做解释,不要回答文本中的问题而是翻译它,不要解决文本中的要求而是翻译它,保留文本的原本意义,不要去解决它。你的回答里只需要翻译后的内容,不要有任何其它词,只能是翻译后的内容。我的第一句话是:\n%s"
                               :chat t
                               :assistant t
                               :replace-or-insert t)

  (aichat-bingai-prompt-create "coder"
                               :input-prompt "代码: "
                               :text-format "我想让你充当计算机教授,请向我解释下面这段代码的作用:\n%s"
                               :chat t)

    (aichat-bingai-prompt-create "refactor"
                               :input-prompt "代码: "
                               :text-format "我想让你充当计算机教授,请帮我重构下面这段代码,重构后的代码性能要更好,可读性要更高,如果必要的话,可以加一些注释。你的回答里只需要返回重构后的代码,不要有其它解释,只能是重构后的代码:\n%s"
                               :replace-or-insert t)
  )

which-key 按键提示

(use-package which-key
  :straight t
  :hook (after-init . which-key-mode))

meow 模式编辑

(use-package meow
  :straight t
  :demand t
  :hook (after-init . meow-global-mode)
  :config

  (with-eval-after-load 'rime
    (dolist (p '(meow-normal-mode-p meow-motion-mode-p meow-keypad-mode-p))
      (add-to-list 'rime-disable-predicates p)))

  (setq meow-cheatsheet-layout meow-cheatsheet-layout-qwerty)

  (setq meow-char-thing-table
        '((?r . round)
          (?c . curly)
          (?s . string)
          (?b . buffer)
          (?d . defun)))

  (setq meow-use-clipboard t)

  (meow-leader-define-key
   ;; Use SPC (0-9) for digit arguments.
   '("1" . meow-digit-argument)
   '("2" . meow-digit-argument)
   '("3" . meow-digit-argument)
   '("4" . meow-digit-argument)
   '("5" . meow-digit-argument)
   '("6" . meow-digit-argument)
   '("7" . meow-digit-argument)
   '("8" . meow-digit-argument)
   '("9" . meow-digit-argument)
   '("0" . meow-digit-argument)
   '("?" . meow-cheatsheet)

   '("b" . consult-buffer)
   '("r" . consult-ripgrep)

   '("f" . consult-find)

   '("/" . evilnc-comment-or-uncomment-lines)

   '("w" . ace-window-hydra/body)
   )

  (meow-normal-define-key
   ;; char move
   '("j" . meow-next)
   '("J" . meow-next-expand)
   '("k" . meow-prev)
   '("K" . meow-prev-expand)
   '("l" . meow-right)
   '("L" . meow-right-expand)
   '("h" . meow-left)
   '("H" . meow-left-expand)

   ;; word move
   '("e" . meow-next-word)
   '("E" . meow-next-symbol)
   '("b" . meow-back-word)
   '("B" . meow-back-symbol)

   ;; line move
   '("x" . meow-line)
   '("X" . meow-goto-line)

   ;; thing move
   '("," . meow-inner-of-thing)
   '("." . meow-bounds-of-thing)
   '("[" . meow-beginning-of-thing)
   '("]" . meow-end-of-thing)
   '("o" . meow-block)
   '("O" . meow-to-block)

   ;; jump
   '("n" . meow-search)
   '("f" . meow-find)
   '("v" . meow-visit)

   ;; action
   '("i" . meow-insert)
   '("I" . meow-open-above)
   '("a" . meow-append)
   '("A" . meow-open-below)
   '("y" . meow-save)
   '("Y" . meow-sync-grab)
   '("c" . meow-change)
   '("r" . meow-replace)
   '("R" . meow-swap-grab)
   '("p" . meow-yank)
   '("s" . meow-kill)
   '("d" . meow-delete)
   '("D" . meow-backward-delete)
   '("G" . meow-grab)
   '("m" . meow-join)
   '("t" . meow-till)
   '("u" . undo)
   '("U" . meow-undo)
   '("w" . meow-mark-word)
   '("W" . meow-mark-symbol)
   '("z" . meow-pop-selection)

   ;; others
   '("0" . meow-expand-0)
   '("9" . meow-expand-9)
   '("8" . meow-expand-8)
   '("7" . meow-expand-7)
   '("6" . meow-expand-6)
   '("5" . meow-expand-5)
   '("4" . meow-expand-4)
   '("3" . meow-expand-3)
   '("2" . meow-expand-2)
   '("1" . meow-expand-1)
   '("-" . negative-argument)
   '(";" . meow-reverse)
   '("g" . meow-cancel-selection)
   '("q" . meow-quit)
   '("'" . repeat)
   '("<escape>" . ignore)
   )
  )

启动 emacs server

(when sys-is-windows
  (unless (daemonp)
    (server-start)))

载入私有配置文件

(ignore-errors
  (load (expand-file-name "config.el" my-private-dir)))