Skip to content

wang1zhen/solarion-emacs

Repository files navigation

README

Solarion Emacs

https://img.shields.io/badge/link-996.icu-red.svg

Here is my personal configuration file for Emacs. The whole README file is a symlink to solarion-emacs.org, and with org babel, this file could be tangled into separate emacs-lisp files under $HOME/.emacs.d/lisp/.

前置依赖与说明:

  • 运行环境:Emacs 29+(使用了 pixel-scroll-precision-mode、treesit 等特性)
  • LaTeX 预览:Ghostscript(用于 org/LaTeX 片段预览)
  • vterm 构建:cmake、libtool、make(动态模块构建依赖)
  • WSL 剪贴板:wl-copy、wl-paste(Wayland 环境下的复制/粘贴支持)
  • 字体:Nerd Font(等宽)、Noto Serif CJK(中日韩)、Noto Color Emoji(Emoji)

软链与自动导出(tangle):本仓库的 README.org 指向 solarion-emacs.org。保存本文件时会自动 tangle 生成 lisp/ 下的模块文件;具体钩子与逻辑详见后文配置(auto tangle 的实现说明)。

Table of Contents

Installation

Clone the repository

git clone [email protected]:wang1zhen/solarion-emacs ~/.emacs.d

或使用 HTTPS 与浅克隆(首次拉取更快):

git clone --depth=1 https://github.com/wang1zhen/solarion-emacs ~/.emacs.d

系统依赖(按需安装)

  • Emacs 29+(建议 GUI 版本以使用 posframe/字体配置)
  • Ghostscript(LaTeX 预览与导出辅助)
  • cmake、libtool、make(用于构建 vterm 动态模块)
  • wl-copy、wl-paste(WSL/Wayland 剪贴板集成)
  • 字体:Nerd Font、Noto Serif CJK、Noto Color Emoji

Tangle the org file

emacs --batch --eval "(progn (require 'org) (let ((org-confirm-babel-evaluate nil)) (org-babel-tangle-file \"~/.emacs.d/solarion-emacs.org\")))"

Launch Emacs and have fun!

early-init.el

;;; early-init.el --- early-init.el is run before package and UI initialization happens -*- lexical-binding: t -*-

;;; Code:

;; Defer garbage collection further back in the startup process
(setq gc-cons-threshold most-positive-fixnum
      gc-cons-percentage 0.5)

;; Package initialize occurs automatically, before `user-init-file' is
;; loaded, but after `early-init-file'. We handle package
;; initialization, so we must prevent Emacs from doing it early!
(setq package-enable-at-startup nil)

;; Inhibit resizing frame
(setq frame-inhibit-implied-resize t)

;; Faster to disable these here (before they've been initialized)
(push '(menu-bar-lines . 0) default-frame-alist)
(push '(tool-bar-lines . 0) default-frame-alist)
(push '(vertical-scroll-bars) default-frame-alist)
(setq inhibit-startup-screen t)
(tooltip-mode -1)

;; 启动优化:临时禁用 file-name-handler 以减少 I/O 开销,启动后恢复
(defvar solarion--file-name-handler-alist-backup file-name-handler-alist)
(setq file-name-handler-alist nil)
(add-hook 'emacs-startup-hook
          (lambda ()
            (setq file-name-handler-alist solarion--file-name-handler-alist-backup)
            (makunbound 'solarion--file-name-handler-alist-backup)))

init-custom-vars.el

提示:字体可通过命令定制:`M-x customize-group RET solarion-fonts RET`。 若系统缺少指定字体,本配置中的字体设置由 init-fonts.el 处理;可根据需要自行调整字体族与字号。

;;; init-custom-vars.el --- Customizable variables for Solarion -*- lexical-binding: t -*-

;;; Code:

(defgroup solarion-fonts nil
  "Font configuration for Solarion Emacs."
  :group 'faces)

(defcustom solarion-font-default "CaskaydiaCove Nerd Font Mono"
  "Default monospace font used in Emacs (e.g., for code, modeline)."
  :type 'string
  :group 'solarion-fonts)

(defcustom solarion-font-variable "CaskaydiaCove Nerd Font"
  "Variable-pitch font for UI elements (if used)."
  :type 'string
  :group 'solarion-fonts)

(defcustom solarion-font-cjk "Noto Serif CJK SC"
  "CJK font for displaying Chinese/Japanese/Korean."
  :type 'string
  :group 'solarion-fonts)

(defcustom solarion-font-emoji "Noto Color Emoji"
  "Font for emoji symbols."
  :type 'string
  :group 'solarion-fonts)

(defcustom solarion-font-size 140
  "Font size (in 1/10 pt) for Emacs default font."
  :type 'integer
  :group 'solarion-fonts)

(provide 'init-custom-vars)

init.el

;;; init.el --- Load the full configuration -*- lexical-binding: t -*-

;;; Code:

(add-to-list 'load-path (expand-file-name "lisp" user-emacs-directory))

;; custom
(require 'init-custom-vars)

;; Ensure custom.el exists, or copy from example
(let ((custom-file-path (expand-file-name "custom.el" user-emacs-directory))
      (example-path (expand-file-name "custom-example.el" user-emacs-directory)))
  (unless (file-exists-p custom-file-path)
    (when (file-exists-p example-path)
      (copy-file example-path custom-file-path)))
  (setq custom-file custom-file-path)
  (when (file-exists-p custom-file)
    (load custom-file)))

;; Adjust garbage collection thresholds during startup, and thereafter
(let ((normal-gc-cons-threshold (* 20 1024 1024))
      (init-gc-cons-threshold (* 128 1024 1024)))
  (setq gc-cons-threshold init-gc-cons-threshold)
  (add-hook 'emacs-startup-hook
            (lambda () (setq gc-cons-threshold normal-gc-cons-threshold))))

;; Always load newest byte code
(setq load-prefer-newer t)

;; Packages & modules(加载时抑制重绘/消息,启动后恢复,减少闪烁与噪音)
(let ((inhibit-redisplay t)
      (inhibit-message t))
  (require 'init-straight)

  ;; Useful functions defined
  (require 'init-func)

  ;; Preferences
  (require 'init-basic)
  (require 'init-general)

  (require 'init-ui)
  (require 'init-scroll)
  (require 'init-fonts)
  (require 'init-edit)
  (require 'init-treesit)

  ;; Keybindings
  (require 'init-hydra)
  (require 'init-map)

  (require 'init-vertico)
  (require 'init-corfu)
  (require 'init-tempel)

  (require 'init-magit)
  (require 'init-ibuffer)
  (require 'init-midnight)

  (require 'init-tramp)

  (require 'init-org)
  (require 'init-denote)
  (require 'init-latex)

  (require 'init-dired)

  (require 'init-dashboard)
  (require 'init-vterm)
  (require 'init-persp)

  (require 'init-im)

  ;; WSL specific setting
  (when (and (eq system-type 'gnu/linux) (getenv "WSLENV"))
    (require 'init-wsl)))

(add-hook 'emacs-startup-hook
          (lambda ()
            (setq inhibit-redisplay nil
                  inhibit-message nil)
            (redisplay)))

init-straight.el

;;; init-straight.el --- Initialize package configurations -*- lexical-binding: t -*-

;;; Code:

;; Set timeout to avoid blocking on bad connections
(setq url-queue-timeout 30)

;; 使用文件监视器检测包修改(straight.el 的 watch-files 模式)。
;; 需在 straight 引导之前设置;相比启动时全量 find 更高效,
;; 但会消耗一定的文件监视资源。
;; NOTE: 需要 `watchexec` 可执行文件;若未安装,会在启动时出现
;; “Cannot start filesystem watcher without 'watchexec' installed” 警告。
(when (executable-find "watchexec")
  (setq straight-check-for-modifications '(watch-files)))

(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
      (bootstrap-version 5))
  (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)))
  (setq straight-use-package-by-default t)
  (setq straight-vc-git-default-protocol 'https)
  (setq straight-vc-git-default-clone-depth 1)
  (load bootstrap-file nil 'nomessage))

;; Should set before loading `use-package'
(setq use-package-expand-minimally t)
(setq use-package-enable-imenu-support t)

;; Install use-package with straight
(straight-use-package 'use-package)

(require 'use-package)

;; Optional:profile use-package loading time
;; M-x use-package-report
(setq use-package-compute-statistics t)

;; Native compile
;; Log warnings but not pop up the *Warnings* buffer
(setq native-comp-async-report-warnings-errors 'silent)

;; Required by `use-package'
(use-package diminish)
(use-package bind-key)

(provide 'init-straight)

init-func.el

;;; init-func.el --- Useful functions are defined here -*- lexical-binding: t -*-

;;; Code:

;; Font
(defun font-installed-p (font-name)
  "Check if font with FONT-NAME is available."
  (find-font (font-spec :name font-name)))

;; Auto tangle babel file
(defun org-babel-auto-tangle ()
  (when (and (eq major-mode 'org-mode)
             (string-equal (buffer-name) "solarion-emacs.org"))
    (org-babel-tangle)))

;; Define split-window-below-and-focus and split-window-right-and-focus
(defun split-window-below-and-focus ()
  "Split the window vertically and focus the new window."
  (interactive)
  (split-window-below)
  (windmove-down))

(defun split-window-right-and-focus ()
  "Split the window horizontally and focus the new window."
  (interactive)
  (split-window-right)
  (windmove-right))

(defun solarion/org-mode-setup ()
  (auto-fill-mode 0)
  (visual-line-mode 1)
  (adaptive-wrap-prefix-mode 1)
  ;; (electric-pair-local-mode -1)
  )

;; Ask for the filename before pasting an image
;; filename should end with ".png/.jpg/.svg"
(defun solarion/org-download-paste-clipboard-wsl ()
  "Save clipboard image to a subdirectory named after current Org file, and insert link."
  (interactive)
  (unless buffer-file-name
    (user-error "Buffer is not visiting a file"))
  (let* ((powershell (executable-find "powershell.exe"))
         (file-base-name (format-time-string "image-%Y%m%d_%H%M%S.png"))
         (file-name (read-string (format "Filename [%s]: " file-base-name) nil nil file-base-name))
         (org-file-dir (file-name-directory buffer-file-name))
         (org-base-name (file-name-base buffer-file-name))
         (image-dir (expand-file-name (concat org-base-name "/") org-file-dir))
         (file-path-wsl (expand-file-name file-name image-dir)))
    (unless powershell
      (user-error "未找到 powershell.exe:该功能仅在 WSL/Windows 环境可用"))
    ;; Step 1: save to Windows temp
    (shell-command
     (format "%s -command \"(Get-Clipboard -Format Image).Save(\\\"C:/Users/Public/%s\\\")\""
             powershell file-name))
    ;; Step 2: move into local folder
    (make-directory image-dir t)
    (rename-file (concat "/mnt/c/Users/Public/" file-name) file-path-wsl t)
    ;; Step 3: insert link
    (let ((relative-path (file-relative-name file-path-wsl org-file-dir)))
      (insert (format "#+ATTR_LATEX: :width \\linewidth\n#+ATTR_ORG: :width 400\n[[file:%s]]\n" relative-path))
      (org-display-inline-images))))

;; dashboard
(defun solarion-edit-config (&rest _)
  (interactive)
  (find-file (concat user-emacs-directory "solarion-emacs.org")))

;; buffer
(defun solarion-new-buffer nil
  (interactive)
  (let ((buffer (generate-new-buffer "*new*")))
    (set-window-buffer nil buffer)
    (with-current-buffer buffer
      (funcall (default-value 'major-mode)))))

;; Delete file and buffer
(defun delete-file-and-buffer ()
  "Kill the current buffer and deletes the file it is visiting."
  (interactive)
  (let ((filename (buffer-file-name)))
    (if filename
        (if (y-or-n-p (concat "Do you really want to delete file " filename " ?"))
            (progn
              (delete-file filename)
              (message "Deleted file %s." filename)
              (kill-buffer)))
      (message "Not a file visiting buffer!"))))

(defun flash-mode-line ()
  (invert-face 'mode-line)
  (run-with-timer 0.1 nil #'invert-face 'mode-line))

(defun copy-line (arg)
  "Copy lines (as many as prefix argument) in the kill ring.
          Ease of use features:
          - Move to start of next line.
          - Appends the copy on sequential calls.
          - Use newline as last char even on the last line of the buffer.
          - If region is active, copy its lines."
  (interactive "p")
  (let ((beg (line-beginning-position))
        (end (line-end-position arg)))
    (when mark-active
      (if (> (point) (mark))
          (setq beg (save-excursion (goto-char (mark)) (line-beginning-position)))
        (setq end (save-excursion (goto-char (mark)) (line-end-position)))))
    (if (eq last-command 'copy-line)
        (kill-append (buffer-substring beg end) (< end beg))
      (kill-ring-save beg end)))
  (kill-append "\n" nil)
  (beginning-of-line (or (and arg (1+ arg)) 2))
  (if (and arg (not (= 1 arg))) (message "%d lines copied" arg)))

(defun solarion/git-add-commit-push ()
  "Simple commit current git project and push to its upstream."
  (interactive)
  (when (and buffer-file-name
             (buffer-modified-p))
    (save-buffer))                   ;; save it first if modified.
  (magit-diff-unstaged)
  (when (yes-or-no-p "Do you really want to commit everything?")
    (magit-stage-modified t)         ;; stage modified and untracked
    (magit-diff-staged)
    (let ((msg (read-string "Commit Message: ")))
      (when (= 0 (length msg))
        (setq msg (format-time-string "commit by magit in emacs@%Y-%m-%d %H:%M:%S"
                                      (current-time))))
      (magit-call-git "commit" "-m" msg)
      (magit-push-current-to-upstream nil)
      (message "now do async push to %s" (magit-get "remote" "origin" "url"))))
  (magit-mode-bury-buffer))

(provide 'init-func)

init-basic.el

;;; init-basic.el --- Default configurations -*- lexical-binding: t -*-

;;; Code:

;; Increase how much is read from processes in a single chunk (default is 4kb)
(setq read-process-output-max #x10000)  ; 64kb

;; Garbage Collector Magic Hack
(use-package gcmh
  :diminish
  :init
  (setq gcmh-idle-delay 5
        gcmh-high-cons-threshold #x1000000) ; 16MB
  :hook (after-init . gcmh-mode))

;; Encoding
;; UTF-8 as the default coding system
(when (fboundp 'set-charset-priority)
  (set-charset-priority 'unicode))

;; Explicitly set the prefered coding systems to avoid annoying prompt
;; from emacs (especially on Microsoft Windows)
(prefer-coding-system 'utf-8)
(setq locale-coding-system 'utf-8)

(set-language-environment 'utf-8)
(set-default-coding-systems 'utf-8)
(set-buffer-file-coding-system 'utf-8)
(set-clipboard-coding-system 'utf-8)
(set-file-name-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-selection-coding-system 'utf-8)
(modify-coding-system-alist 'process "*" 'utf-8)

;; Ensure environment variables inside Emacs look the same as in the user's shell
(use-package exec-path-from-shell
  :init
  (setq exec-path-from-shell-variables '("PATH" "MANPATH")
        exec-path-from-shell-arguments '("-l"))
  :config
  (exec-path-from-shell-initialize))

;; Start server
(use-package server
  :straight nil
  :hook (after-init . server-mode))

;; Go to the last place when previously visited the file
(use-package saveplace
  :straight nil
  :hook (after-init . save-place-mode))

(use-package recentf
  :straight nil
  :hook (after-init . recentf-mode)
  :init
  (setq recentf-max-saved-items 500
        recentf-max-menu-items 15
        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:" "/persp-confs/"
          (lambda (file) (file-in-directory-p file package-user-dir))))
  :config
  (push (expand-file-name recentf-save-file) recentf-exclude)
  (add-to-list 'recentf-filename-handlers #'abbreviate-file-name))

(use-package savehist
  :straight nil
  :hook (after-init . savehist-mode)
  :init
  (setq enable-recursive-minibuffers t ; Allow commands in minibuffers
        history-length 1000
        savehist-additional-variables '(mark-ring
                                        global-mark-ring
                                        search-ring
                                        regexp-search-ring
                                        extended-command-history)
        savehist-autosave-interval 300))

(use-package simple
  :straight nil
  :hook ((after-init . size-indication-mode)
         (text-mode . visual-line-mode)
         (helpful-mode . visual-line-mode)
         ((prog-mode org-mode markdown-mode conf-mode latex-mode) . (lambda () (setq show-trailing-whitespace t))))
  :init
  (setq column-number-mode t
        line-number-mode t
        ;; kill-whole-line t               ; Kill line including '\n'
        line-move-visual t
        ;; track-eol t                     ; Keep cursor at end of lines. Require line-move-visual is nil.
        set-mark-command-repeat-pop t)  ; Repeating C-SPC after popping mark pops it again
  )

(use-package so-long
  :straight nil
  :hook (after-init . global-so-long-mode)
  :config (setq so-long-threshold 400))

(use-package adaptive-wrap
  :commands adaptive-wrap-prefix-mode)

(use-package keyfreq
  :init
  (setq keyfreq-file "~/.emacs.d/.keyfreq")
  (setq keyfreq-file-lock "~/.emacs.d/.keyfreq.lock")
  (keyfreq-mode 1)
  (keyfreq-autosave-mode 1)
  :config
  (setq keyfreq-excluded-commands
        '(self-insert-command
          org-self-insert-command
          forward-char
          backward-char
          previous-line
          next-line))
  (setq keyfreq-excluded-regexp
        '("\\`vertico-.*\\'"
          "\\`iscroll-.*\\'"
          "\\`vterm-.*\\'")))

;; Misc
(fset 'yes-or-no-p 'y-or-n-p)
(setq-default major-mode 'emacs-lisp-mode
              tab-width 8
              indent-tabs-mode nil)     ; Permanently indent with spaces, never with TABs

;; flash the modeline for visual bell
(setq visible-bell nil
      ring-bell-function 'flash-mode-line)
(setq inhibit-compacting-font-caches t  ; Don’t compact font caches during GC.
      delete-by-moving-to-trash t       ; Deleting files go to OS's trash folder
      make-backup-files nil             ; Forbide to make backup files
      create-lockfiles nil              ; Forbide to make lockfiles
      auto-save-default nil             ; Disable auto save

      uniquify-buffer-name-style 'post-forward-angle-brackets ; Show path if names are same
      adaptive-fill-regexp "[ t]+|[ t]*([0-9]+.|*+)[ t]*"
      adaptive-fill-first-line-regexp "^* *$"
      sentence-end-double-space nil)

;; Use the system clipboard
(setq select-enable-clipboard t)

;; Always focus the help window
(setq help-window-select t)

;; Enable mouse in terminal mode
(xterm-mouse-mode)

;; Auto tangle this file after save (without prompt)
(add-hook 'after-save-hook #'org-babel-auto-tangle)

;; 初始 *scratch* buffer 使用 fundamental-mode
(setq initial-major-mode 'fundamental-mode)

;; Disable scratch buffer text
(setq initial-scratch-message nil)

(provide 'init-basic)

init-general.el

Only prepare the packages here, specific keybindings goes to init-map.el.

;;; init-general.el --- Initialize general -*- lexical-binding: t -*-

;;; Code:

(use-package key-chord
  :diminish
  :init
  (key-chord-mode))

(use-package general)

(provide 'init-general)

init-ui.el

;;; init-ui.el --- Better lookings and appearances. -*- lexical-binding: t -*-

;;; Code:

;; Title
(setq frame-title-format '((:eval (if (buffer-file-name)
                                      (abbreviate-file-name (buffer-file-name))
                                    "%b"))
                           "    "
                           user-login-name
                           "@"
                           system-name)
      icon-title-format frame-title-format)

;; Optimization
(setq idle-update-delay 1.0)

(setq-default cursor-in-non-selected-windows nil)
(setq highlight-nonselected-windows nil)

(tooltip-mode -1) ;; Disable tooltips
(set-fringe-mode 10) ;; 左右边框 仅对GUI生效
(global-hl-line-mode t)

;; 分屏偏好可自定义
(defgroup solarion-window nil
  "Solarion 的窗口分屏偏好。"
  :group 'windows)

(defcustom solarion-split-height-threshold nil
  "赋值给 `split-height-threshold` 的值。nil 表示不进行水平分割(上下分屏)。"
  :type '(choice (const :tag "禁止水平分割" nil)
                 integer)
  :group 'solarion-window)

(defcustom solarion-split-width-threshold 80
  "赋值给 `split-width-threshold` 的值。80 表示在宽度大于等于 80 时进行垂直分割(左右分屏)。"
  :type '(choice (const :tag "禁止垂直分割" nil)
                 integer)
  :group 'solarion-window)

(setq split-height-threshold solarion-split-height-threshold
      split-width-threshold  solarion-split-width-threshold)

;; Mode-line
(defun solarion-apply-modeline-fonts ()
  "应用 modeline 字体(避免主题覆盖)。"
  (when (display-graphic-p)
    (set-face-attribute 'mode-line nil :font solarion-font-default :height solarion-font-size)
    (set-face-attribute 'mode-line-inactive nil :font solarion-font-default :height solarion-font-size)))

;; 确保切换主题后也保持自定义的 modeline 字体
(advice-add 'enable-theme :after (lambda (&rest _) (solarion-apply-modeline-fonts)))

(use-package doom-modeline
  :diminish doom-modeline-mode
  :init
  (setq doom-modeline-modal-icon nil)
  ;; Must use mono font here
  (solarion-apply-modeline-fonts)
  (doom-modeline-mode t))

(use-package nerd-icons
  :init
  (setq nerd-icons-font-family solarion-font-default))

(use-package display-line-numbers
  :straight nil
  :init
  (setq display-line-numbers-width-start t)
  (setq display-line-numbers-current-absolute t)
  :config
  (dolist (mode '(c-mode-common-hook
                  c-mode-hook
                  emacs-lisp-mode-hook
                  lisp-interaction-mode-hook
                  lisp-mode-hook
                  sh-mode-hook
                  python-mode-hook
                  html-mode-hook
                  rust-mode-hook
                  conf-mode-hook))
    (add-hook mode (lambda () (setq display-line-numbers t)))))

;; Suppress GUI features
(setq use-file-dialog nil
      use-dialog-box nil
      inhibit-startup-screen t
      inhibit-startup-echo-area-message t)

;; Display dividers between windows
(setq window-divider-default-places t
      window-divider-default-bottom-width 1
      window-divider-default-right-width 1)
(add-hook 'window-setup-hook #'window-divider-mode)

(add-to-list 'default-frame-alist '(fullscreen . maximized))

(use-package rainbow-delimiters
  :hook
  (prog-mode . rainbow-delimiters-mode)
  (LaTeX-mode . rainbow-delimiters-mode)
  :commands rainbow-delimiters-mode)

(use-package which-key
  :diminish which-key-mode
  :hook (after-init . which-key-mode)
  :init
  (setq which-key-idle-delay 0.2)
  (setq which-key-sort-order 'which-key-key-order-alpha)
  (setq which-key-prefix-prefix "")
  :config
  (set-face-attribute 'which-key-group-description-face nil :weight 'bold))

(use-package which-key-posframe
  :after which-key
  :init
  (which-key-posframe-mode)
  :config
  (setq which-key-posframe-parameters
        '((left-fringe . 8)
          (right-fringe . 8))))

(use-package command-log-mode
  :commands command-log-mode)

(use-package helpful
  :bind
  ([remap describe-function] . helpful-callable)
  ([remap describe-variable] . helpful-variable)
  ([remap describe-key] . helpful-key)
  :commands (helpful-callable helpful-variable helpful-key))

(use-package winum
  :hook (after-init . winum-mode))

(use-package posframe)

(use-package shackle
  :hook (after-init . shackle-mode)
  :init
  (setq shackle-default-alignment 'right) ; default below
  (setq shackle-select-reused-windows t)
  (setq shackle-rules
        '(("*vterm*" :size 0.3 :align below :popup t)
          ;; (compilation-mode :ignore t)
          ;; ("\\*Async Shell.*\\*" :regexp t :ignore t)
          ;; ("\\*corfu.*\\*" :regexp t :ignore t)
          ;; ("*eshell*" :select t :size 0.4 :align t :popup t)
          (helpful-mode :size 0.4 :align t :popup t)
          (help-mode :size 0.4 :align t :popup t)
          ;; ("*Messages*" :select t :size 0.4 :align t :popup t)
          (magit-status-mode :inhibit-window-quit t :other t)
          (magit-log-mode :inhibit-window-quit t :other t)
          ("\\*Org Src.*\\*" :regexp t :inhibit-window-quit t :other t))))

(use-package ef-themes
  :straight '(ef-themes :type git :host github :repo "protesilaos/ef-themes")
  :init
  (setq ef-themes-headings nil)
  (setq ef-themes-mixed-fonts nil)
  (setq ef-themes-variable-pitch-ui nil)
  (ef-themes-select 'ef-cyprus))

(use-package valign
  :hook (org-mode . valign-mode))

(provide 'init-ui)

init-scroll.el

Use iscroll for image scrolling and pixel-scroll-precision-mode for smooth scrolling (available since emacs 29)

;;; init-scroll.el --- Better scrolling effects. -*- lexical-binding: t -*-

;;; Code:

(setq scroll-preserve-screen-position 'always
      next-screen-context-lines 5)

(use-package iscroll
  :init
  :hook (org-mode . iscroll-mode))


(when (fboundp 'pixel-scroll-precision-mode)
  (pixel-scroll-precision-mode))

(provide 'init-scroll)

init-fonts.el

The font settings are mainly for GUI Emacs, this would not affect TUI Emacs. font check Chinese: 遍角次亮采之门 Symbols: ♪ Kana: 夜に駆ける

;;; init-fonts.el --- Fonts configurations (for GUI) -*- lexical-bindings: t -*-

;;; Code:

(defun solarion-config-fonts ()
  "Apply font configuration based on user custom variables."
  (when (display-graphic-p)
    ;; Default font
    (set-face-attribute 'default nil :font solarion-font-default :height solarion-font-size)

    ;; Fixed-pitch (等宽字体)
    (set-face-attribute 'fixed-pitch nil :font solarion-font-default :height solarion-font-size)

    ;; Variable-pitch(比例字体)
    (set-face-attribute 'variable-pitch nil :font solarion-font-variable :height solarion-font-size)

    ;; CJK 字体
    (set-fontset-font t 'han (font-spec :family solarion-font-cjk :weight 'semi-bold :slant 'normal))
    (set-fontset-font t 'cjk-misc (font-spec :family solarion-font-cjk :weight 'semi-bold :slant 'normal))
    (set-fontset-font t 'kana (font-spec :family "Noto Serif CJK JP" :weight 'semi-bold :slant 'normal))

    ;; Emoji
    (set-fontset-font t 'symbol (font-spec :family solarion-font-emoji) nil 'prepend)))

(solarion-config-fonts)
(add-hook 'window-setup-hook #'solarion-config-fonts)
(add-hook 'server-after-make-frame-hook #'solarion-config-fonts)

;; https://github.com/mickeynp/ligature.el
(use-package ligature
  :straight '(ligature :type git :host github :repo "mickeynp/ligature.el")
  :config
  ;; Enable the "www" ligature in every possible major mode
  (ligature-set-ligatures 't '("www"))
  ;; Enable traditional ligature support in eww-mode, if the
  ;; `variable-pitch' face supports it
  (ligature-set-ligatures 'eww-mode '("ff" "fi" "ffi"))
  ;; Enable all Cascadia Code ligatures in programming modes
  (ligature-set-ligatures 'prog-mode '("|||>" "<|||" "<==>" "<!--" "####" "~~>" "***" "||=" "||>"
                                       ":::" "::=" "=:=" "===" "==>" "=!=" "=>>" "=<<" "=/=" "!=="
                                       "!!." ">=>" ">>=" ">>>" ">>-" ">->" "->>" "-->" "---" "-<<"
                                       "<~~" "<~>" "<*>" "<||" "<|>" "<$>" "<==" "<=>" "<=<" "<->"
                                       "<--" "<-<" "<<=" "<<-" "<<<" "<+>" "</>" "###" "#_(" "..<"
                                       "..." "+++" "/==" "///" "_|_" "www" "&&" "^=" "~~" "~@" "~="
                                       "~>" "~-" "**" "*>" "*/" "||" "|}" "|]" "|=" "|>" "|-" "{|"
                                       "[|" "]#" "::" ":=" ":>" ":<" "$>" "==" "=>" "!=" "!!" ">:"
                                       ">=" ">>" ">-" "-~" "-|" "->" "--" "-<" "<~" "<*" "<|" "<:"
                                       "<$" "<=" "<>" "<-" "<<" "<+" "</" "#{" "#[" "#:" "#=" "#!"
                                       "##" "#(" "#?" "#_" "%%" ".=" ".-" ".." ".?" "+>" "++" "?:"
                                       "?=" "?." "??" ";;" "/*" "/=" "/>" "//" "__" "~~" "(*" "*)"
                                       "\\\\" "://"))
  ;; Enables ligature checks globally in all buffers. You can also do it
  ;; per mode with `ligature-mode'.
  (global-ligature-mode t))

(provide 'init-fonts)

init-edit.el

;;; init-edit.el --- Initialize editing configurations -*- lexical-binding: t -*-

;;; Code:

;; Automatically reload files was modified by external program
(use-package autorevert
  :straight nil
  :diminish
  :init
  (setq global-auto-revert-non-file-buffers t
        auto-revert-interval 1
        auto-revert-remote-files t)
  (global-auto-revert-mode))

(use-package auto-save
  :straight '(auto-save :type git :host github :repo "manateelazycat/auto-save")
  :config
  (auto-save-enable)
  (setq auto-save-enable-predicate
        (lambda () (not (string-match-p "\\*.*\\*" (buffer-name)))))
  (setq auto-save-silent t)   ; quietly save
  (setq auto-save-delete-trailing-whitespace t)  ; automatically delete spaces at the end of the line when saving
  )

(use-package format-all
  :commands (format-all-buffer format-all--language-id-buffer))

;; Jump to things in Emacs tree-style
(use-package avy
  :config (setq avy-all-windows t
                avy-background t
                avy-style 'at-full
                avy-timeout-seconds 0.5))

(use-package beginend
  :diminish beginend-global-mode
  :hook (after-init . beginend-global-mode))

;; A comprehensive visual interface to diff & patch
(use-package ediff
  :straight nil
  :hook (;; show org ediffs unfolded
         (ediff-prepare-buffer . outline-show-all)
         ;; restore window layout when done
         ;; (ediff-quit . winner-undo)
         )
  :config
  (setq ediff-window-setup-function 'ediff-setup-windows-plain
        ediff-split-window-function 'split-window-vertically
        ediff-merge-split-window-function 'split-window-vertically))

;; Increase selected region by semantic units
(use-package expand-region
  :commands er/expand-region)

;; Hungry deletion
(use-package hungry-delete
  :diminish
  :hook (after-init . global-hungry-delete-mode)
  :init (setq hungry-delete-except-modes '(help-mode minibuffer-mode minibuffer-inactive-mode calc-mode)
              hungry-delete-chars-to-skip "\f"))

;; Move to the beginning/end of line or code
(use-package mwim
  :config
  (general-define-key
   :keymaps 'override
   "C-a" #'mwim-beginning-of-code-or-line
   "C-e" #'mwim-end-of-code-or-line))

(general-def "C-/" #'undo-only)
(general-def "C-r" #'undo-redo)

;; vundo
(use-package vundo
  :config
  (general-def "C-x u" #'vundo)
  (general-def vundo-mode-map "C-n" #'vundo-next)
  (general-def vundo-mode-map "C-p" #'vundo-previous)
  (general-def vundo-mode-map "C-f" #'vundo-forward)
  (general-def vundo-mode-map "C-b" #'vundo-backward)
  (setq vundo-glyph-alist vundo-unicode-symbols))

;; Handling capitalized subwords in a nomenclature
(use-package subword
  :straight nil
  :diminish
  :hook ((prog-mode . subword-mode)
         (minibuffer-setup . subword-mode)))

(use-package sudo-edit
  :commands (sudo-edit-find-file sudo-edit-current-file))

;; On-the-fly spell checker
(use-package flyspell
  :straight nil
  :diminish
  :if (executable-find "aspell")
  :hook
  (((text-mode outline-mode) . flyspell-mode)
   (prog-mode . flyspell-prog-mode)
   (LaTeX-mode . flyspell-mode)
   (flyspell-mode . (lambda ()
                      (dolist (key '("C-;" "C-," "C-."))
                        (unbind-key key flyspell-mode-map)))))
  :init
  (setq flyspell-issue-message-flag nil
        ispell-program-name "aspell"
        ispell-extra-args '("--sug-mode=ultra" "--lang=en_US" "--run-together")))

;; Framework for mode-specific buffer indexes
(use-package imenu
  :straight nil
  :init
  (setq imenu-auto-rescan t))

;; 中英文间自动加入空格
(use-package pangu-spacing
  :diminish pangu-spacing-mode
  :hook ((text-mode . pangu-spacing-mode)
         (org-mode . pangu-spacing-mode))
  :init
  (setq pangu-spacing-real-insert-separator t))

;; occur
(add-hook 'occur-hook (lambda () (switch-to-buffer-other-window "*Occur*")))

;; smartparens
(use-package smartparens
  :diminish
  :config
  (require 'smartparens-config)
  (add-hook 'org-mode-hook #'smartparens-mode)
  (add-hook 'LaTeX-mode-hook #'smartparens-mode)
  (add-hook 'emacs-lisp-mode-hook #'smartparens-mode)
  ;; custom pairs
  (with-eval-after-load 'org
    (sp-local-pair 'org-mode "\\[" "\\]")
    (sp-local-pair 'org-mode "=" nil :actions :rem)
    (sp-local-pair 'org-mode "/" nil :actions :rem)))

(use-package lorem-ipsum)

;; conf-mode
(defcustom solarion/conf-indent-offset 2
  "Indent width for brace-based conf indentation."
  :type 'integer :group 'editing)

(defun solarion/conf--brace-level (pos)
  "Count brace nesting before POS, skipping comments/strings."
  (save-excursion
    (goto-char (point-min))
    (let ((lvl 0))
      (while (re-search-forward "[{}]" pos t)
        (unless (nth 8 (syntax-ppss))      ; 在注释或字符串中则跳过
          (if (equal (char-before) ?{)
              (cl-incf lvl)
            (cl-decf lvl))))
      (max 0 lvl))))

(defun solarion/conf-indent-line ()
  "Brace-based indent: closing '}' outdents by one level."
  (interactive)
  (let* ((bol (save-excursion (back-to-indentation) (point)))
         (closing (save-excursion (goto-char bol) (looking-at-p "}")))
         (lvl (solarion/conf--brace-level bol))
         (target (* solarion/conf-indent-offset (max 0 (if closing (1- lvl) lvl)))))
    (indent-line-to target)))

(define-minor-mode solarion/brace-indent-mode
  "Generic brace-based indentation for conf-like files."
  :lighter " { }"
  (if solarion/brace-indent-mode
      (progn
        (setq-local indent-line-function #'solarion/conf-indent-line)
        (setq-local indent-tabs-mode nil))
    (kill-local-variable 'indent-line-function)))

(defun solarion/conf--line-str ()
  (string-trim (buffer-substring-no-properties
                (line-beginning-position) (line-end-position))))

(defun solarion/conf--skip-comment-or-blank-p (s)
  (or (string-empty-p s) (string-prefix-p "#" s)))

(defun solarion/conf--yaml-p ()
  "Heuristic: YAML keys with ':', list items '-', or '---' header."
  (save-excursion
    (goto-char (point-min))
    (let ((checked 0) (hits 0))
      (while (and (< checked 60) (not (eobp)))
        (let ((s (solarion/conf--line-str)))
          (unless (solarion/conf--skip-comment-or-blank-p s)
            (cl-incf checked)
            (when (or
                   (string-match-p "^[[:space:]]*---[[:space:]]*$" s)
                   (string-match-p "^[[:space:]]*-[[:space:]]" s)
                   (string-match-p "^[[:space:]]*\\(?:[A-Za-z0-9_.-]+\\|\"[^\"]+\"\\|'[^']+'\\):[[:space:]]" s))
              (cl-incf hits))))
        (forward-line 1))
      (>= hits 2))))

(defun solarion/conf--toml-p ()
  "Heuristic: [section] or repeated key = value lines."
  (save-excursion
    (goto-char (point-min))
    (let ((checked 0) (assigns 0) (has-section nil))
      (while (and (< checked 80) (not (eobp)))
        (let ((s (solarion/conf--line-str)))
          (unless (solarion/conf--skip-comment-or-blank-p s)
            (cl-incf checked)
            (when (or (string-match-p "^\\[[^]]+\\]$" s)
                      (string-match-p "^\\[\\[[^]]+\\]\\]$" s))
              (setq has-section t))
            (when (string-match-p "^[A-Za-z0-9_.-]+\\s*=\\s*.+$" s)
              (cl-incf assigns))))
        (forward-line 1))
      (or has-section (>= assigns 3)))))

(defun solarion/conf--looks-like-brace-conf-p ()
  (save-excursion
    (goto-char (point-min))
    (re-search-forward "^[[:space:]]*[^#\n]*{[[:space:]]*$" nil t)))

(defun solarion/conf--maybe-switch-mode ()
  "Prefer YAML/TOML. Else enable brace indent."
  (cond
   ((solarion/conf--yaml-p) (yaml-mode))
   ((solarion/conf--toml-p) (toml-mode))
   ((solarion/conf--looks-like-brace-conf-p)
    (solarion/brace-indent-mode 1))))

(add-hook 'conf-unix-mode-hook
          (lambda ()
            (solarion/conf--maybe-switch-mode)))

(use-package prettier-js
  :hook
  ('css-mode . 'prettier-js-mode)
  ('json-mode . 'prettier-js-mode)
  ('markdown-mode . 'prettier-js-mode)
  ('html-mode . 'prettier-js-mode)
  ('js-mode . 'prettier-js-mode)
  ('web-mode . 'prettier-js-mode)
  )

(provide 'init-edit)

init-treesit.el

;;; init-treesit.el --- Treesit settings -*- lexical-binding: t -*-

;;; Code:

(setq treesit-language-source-alist
      '((bash        "https://github.com/tree-sitter/tree-sitter-bash"       nil      "src")
        (c           "https://github.com/tree-sitter/tree-sitter-c"          nil      "src")
        (cpp         "https://github.com/tree-sitter/tree-sitter-cpp"        nil      "src")
        (css         "https://github.com/tree-sitter/tree-sitter-css"        nil      "src")
        (go          "https://github.com/tree-sitter/tree-sitter-go"         nil      "src")
        (html        "https://github.com/tree-sitter/tree-sitter-html"       nil      "src")
        (java        "https://github.com/tree-sitter/tree-sitter-java"       nil      "src")
        (javascript  "https://github.com/tree-sitter/tree-sitter-javascript" "master" "src")
        (typescript  "https://github.com/tree-sitter/tree-sitter-typescript" "master" "typescript/src")
        (tsx         "https://github.com/tree-sitter/tree-sitter-typescript" "master" "tsx/src")
        (json        "https://github.com/tree-sitter/tree-sitter-json"       nil      "src")
        (python      "https://github.com/tree-sitter/tree-sitter-python"     nil      "src")
        (rust        "https://github.com/tree-sitter/tree-sitter-rust"       nil      "src")
        (toml        "https://github.com/tree-sitter/tree-sitter-toml"       nil      "src")
        (yaml        "https://github.com/ikatyang/tree-sitter-yaml"          nil      "src")
        ))

(defun solarion/treesit-ensure (&rest langs)
  "安装缺失的语法解析器。"
  (dolist (lang langs)
    (unless (treesit-language-available-p lang)
      (treesit-install-language-grammar lang))))

;; 按需安装(第一次配置后执行一次即可)
;; (solarion/treesit-ensure 'bash 'c 'cpp 'css 'go 'html 'java
;;                          'javascript 'typescript 'tsx 'json
;;                          'python 'rust 'toml 'yaml)

;; 将传统主模式自动映射到 *-ts-mode(可按需删减)
(setq major-mode-remap-alist
      '((c-mode          . c-ts-mode)
        (c++-mode        . c++-ts-mode)
        (c-or-c++-mode   . c-or-c++-ts-mode)
        (conf-toml-mode  . toml-ts-mode)
        (css-mode        . css-ts-mode)
        (js-mode         . js-ts-mode)
        (javascript-mode . js-ts-mode)
        (js-json-mode    . json-ts-mode)
        (json-mode       . json-ts-mode)
        (python-mode     . python-ts-mode)
        (sh-mode         . bash-ts-mode)
        (typescript-mode . typescript-ts-mode)
        (yaml-mode       . yaml-ts-mode)
        ))

(use-package nix-ts-mode
  :mode "\\.nix\\'")

;; 源码内容相对 #+begin_src 向右缩 2 列
(setq org-src-preserve-indentation nil)   ;; 关闭"保留原缩进"
(setq org-edit-src-content-indentation 2) ;; 内容右移 2 列
(setq org-src-tab-acts-natively t)        ;; 在子缓冲里用主模式缩进

;; 缓冲缩进(优先走当前主模式的缩进器;若是 *-ts-mode 就是 treesit)
(defun indent-buffer (&optional beg end)
  "缩进当前缓冲或给定区域。默认整个缓冲区。"
  (interactive)
  (save-excursion
    (save-restriction
      (widen)
      (let* ((rb (or beg (point-min)))
             (re (or end (point-max)))
             ;; Makefile 等保持 TAB
             (indent-tabs-mode
              (and indent-tabs-mode (derived-mode-p 'makefile-mode))))
        (indent-region rb re nil)))))

(defun solarion/indent-buffer-and-format nil
  "先 treesit/主模式缩进,再调用外部 formatter,最后清理行尾空白。"
  (interactive)

  (indent-buffer)
  (when (derived-mode-p 'prog-mode 'conf-mode 'text-mode)
    (ignore-errors (delete-trailing-whitespace))))

(provide 'init-treesit)

init-hydra.el

    ;;; init-hydra.el --- Hydra configurations -*- lexical-binding: t -*-

    ;;; Code:

(use-package hydra
  :config
  (defhydra solarion/hydra-window-resize (:timeout 4)
    "Resize window"
    ("j" enlarge-window "Increase height")
    ("k" shrink-window "Decrease height")
    ("h" shrink-window-horizontally "Decrease width")
    ("l" enlarge-window-horizontally "Increase width")
    ("SPC" balance-windows "Balance windows")
    ("q" nil "quit" :exit t)))

(provide 'init-hydra)

init-map.el

Define the majority of keybindings here.

;;; init-map.el --- Keybindings -*- lexical-binding: t -*-

;;; Code:
;; misc
(general-def [f10] #'solarion/indent-buffer-and-format) ;; f12 reserved for yakuake
(general-def [f5] #'revert-buffer)
(general-def ";" (general-key-dispatch 'self-insert-command
                   :timeout 0.25
                   "'" #'comment-line))
(general-def "j" (general-key-dispatch 'self-insert-command
                   :timeout 0.25
                   "k" (general-key "C-g")))
(general-def "k" (general-key-dispatch 'self-insert-command
                   :timeout 0.25
                   "j" #'avy-goto-char-timer))
(general-def :keymaps 'override "C-c k" #'copy-line)

(general-unbind "M-`")	;; reserved for tmux

(general-create-definer global-leader-def
  :keymaps 'override
  :prefix "C-c")

(general-create-definer local-leader-def
  :keymaps 'override
  :prefix "C-c m")

;; Global leader
(global-leader-def

  ;; maps
  "h" #'(help-command :which-key "Help")

  ;; keys
  "C-." #'consult-imenu  ;; "C-c ." for org-time-stamp
  "=" #'er/expand-region
  "C-s" #'consult-ripgrep
  "C-SPC" #'consult-mark

  ;; window
  "w" '(:ignore t :which-key "Window")
  "ws" #'split-window-below-and-focus
  "wv" #'split-window-right-and-focus
  "wd" #'(delete-window :which-key "Delete window")
  "wq" #'(kill-buffer-and-window :which-key "Kill buffer and window")
  "wr" #'(solarion/hydra-window-resize/body :which-key "Window Resize")
  "w=" #'(balance-windows :which-key "Balance Windows")
  "1" #'(winum-select-window-1 :which-key "Switch to window 1")
  "2" #'(winum-select-window-2 :which-key "Switch to window 2")
  "3" #'(winum-select-window-3 :which-key "Switch to window 3")
  "4" #'(winum-select-window-4 :which-key "Switch to window 4")
  "5" #'(winum-select-window-5 :which-key "Switch to window 5")

  ;; buffer & bookmark
  "b" '(:ignore t :which-key "Buffer/Bookmark")
  "bp" #'(previous-buffer :which-key "Previous Buffer")
  "bn" #'(next-buffer :which-key "Next Buffer")
  "bb" #'(consult-buffer :which-key "Switch Buffer")
  "bc" #'(clone-indirect-buffer :which-key "Clone Buffer")
  "bd" #'(kill-current-buffer :which-key "Kill Buffer")
  "bi" #'ibuffer
  "bm" #'(bookmark-set :which-key "Set Bookmark")
  "bM" #'(bookmark-delete :which-key "Delete Bookmark")
  "bj" #'(consult-bookmark :which-key "Jump to Bookmark")
  "bl" #'(list-bookmarks :which-key "Bookmarks List")
  "bN" #'(solarion-new-buffer :which-key "New Empty Buffer")
  "br" #'(revert-buffer :which-key "Revert Buffer")

  ;; file
  "f" '(:ignore t :which-key "File")
  "fd" #'(dired-jump :which-key "Dired Jump")
  "fD" #'(delete-file-and-buffer :which-key "Delete File")
  "ff" #'(find-file :which-key "Find File")
  "fs" #'(save-buffer :which-key "Save File")
  "fS" #'(write-file :which-key "Save File As")
  "fr" #'(consult-recent-file :which-key "Recent Files")
  "fp" #'(solarion-edit-config :which-key "Edit Config")

  ;; quit
  "q" '(:ignore t :which-key "Quit")
  "qf" #'(delete-frame :which-key "Delete Frame")
  "qq" #'(save-buffers-kill-terminal :which-key "Quit Emacs")

  ;; git
  "g" '(:ignore t :which-key "Git")
  "gR" #'vc-revert
  "g/" #'magit-dispatch
  "g." #'magit-file-dispatch
  ;; "g'" #'forge-dispatch
  "gb" #'magit-branch-checkout
  "gg" #'magit-status
  "gG" #'solarion/git-add-commit-push
  "gD" #'magit-file-delete
  "gB" #'magit-blame
  "gC" #'magit-clone
  "gF" #'magit-fetch
  "gL" #'magit-log-buffer-file
  "gS" #'magit-stage-file
  "gU" #'magit-unstage-file
  "gf" '(:ignore t :which-key "find")
  "gff" #'magit-find-file
  "gfg" #'magit-find-git-config-file
  "gfc" #'magit-show-commit
  ;; "gfi" #'forge-visit-issue
  ;; "gfp" #'forge-visit-pullreq
  "gl" '(:ignore t :which-key "list")
  "glr" #'magit-list-repositories
  "gls" #'magit-list-submodules
  ;; "gli" #'forge-list-issues
  ;; "glp" #'forge-list-pullreqs
  ;; "gln" #'forge-list-notifications
  "gc" '(:ignore t :which-key "create")
  "gcr" #'magit-init
  "gcR" #'magit-clone
  "gcc" #'magit-commit-create
  "gcf" #'magit-commit-fixup
  "gcb" #'magit-branch-and-checkout
  ;; "gci" #'forge-create-issue
  ;; "gcp" #'forge-create-pullreq

  ;; custom
  "o" '(:ignore t :which-key "Custom Entry")
  "oT" #'(consult-theme :which-key "Choose Theme")
  "ot" #'(ef-themes-load-random :which-key "Random Theme")
  "oo" #'occur
  "oy" #'yadm
  )

;; Local leader
;; org-mode
(local-leader-def org-mode-map
  "," #'org-switchb
  "." #'consult-org-heading
  "b" #'org-mark-ring-goto
  ;; 插入结构化模板(如 src/example 等)
  "s" #'org-insert-structure-template
  "a" '(:ignore t :which-key "Attach")
  "aa" #'org-attach
  "ap" #'solarion/org-download-paste-clipboard-wsl
  "e" #'(org-export-dispatch :which-key "Export")
  "d" '(:ignore t :which-key "Date")
  "dd" #'org-deadline
  "ds" #'org-schedule
  "dt" #'org-time-stamp
  "dT" #'org-time-stamp-inactive
  "f" #'org-footnote-action
  "h" #'org-toggle-heading
  "i" #'org-toggle-item
  "p" '(:ignore t :which-key "Priority")
  "pd" #'org-priority-down
  "pp" #'org-priority
  "pu" #'org-priority-up
  "R" #'org-refile
  "t" #'org-todo
  "x" #'org-toggle-checkbox)

(general-def help-map
  ;; new keybinds
  "'"    #'describe-char

  ;; Unbind `help-for-help'. Conflicts with which-key's help command for the
  ;; <leader> h prefix. It's already on ? and F1 anyway.
  "C-h"  nil

  ;; replacement keybinds
  ;; replaces `info-emacs-manual' b/c it's on C-m now
  "r"    nil

  "b"   #'describe-bindings
  "B"   #'general-describe-keybindings

  ;; replaces `apropos-command'
  "a"    #'apropos
  "A"    #'apropos-documentation
  ;; replaces `describe-copying' b/c not useful
  "C-c"  #'describe-coding-system
  ;; replaces `Info-got-emacs-command-node' b/c redundant w/ `Info-goto-node'
  "F"    #'describe-face
  ;; replaces `view-hello-file' b/c annoying
  "h"    nil
  ;; replaces `help-with-tutorial', b/c it's less useful than `load-theme'
  "t"    #'consult-theme
  ;; replaces `finder-by-keyword' b/c not useful
  "p"    nil)

(provide 'init-map)

init-vertico.el

The bundle of vertico, consult, orderless, marginalia and embark

;;; init-vertico.el --- Initialize the vertico bundle -*- lexical-binding: t -*-

;;; Code:

(use-package vertico
  :straight (vertico :files (:defaults "extensions/*"))
  :init
  (vertico-mode)

  (setq vertico-scroll-margin 2)

  ;; Show 10 candidates
  (setq vertico-count 10)

  ;; Optionally enable cycling for `vertico-next' and `vertico-previous'.
  (setq vertico-cycle t))

(use-package vertico-posframe
  :after vertico
  :init
  (vertico-posframe-mode 1)
  :config
  (setq vertico-posframe-parameters
        '((left-fringe . 8)
          (right-fringe . 8))))

(use-package vertico-directory
  :after vertico
  :straight nil
  ;; More convenient directory navigation commands
  :bind (:map vertico-map
              ("RET" . vertico-directory-enter)
              ("DEL" . vertico-directory-delete-char)
              ("C-DEL" . vertico-directory-delete-word))
  ;; Tidy shadowed file names
  :hook (rfn-eshadow-update-overlay . vertico-directory-tidy))

(use-package nerd-icons-completion
  :after marginalia
  :hook (marginalia-mode . nerd-icons-completion-marginalia-setup)
  :config
  (nerd-icons-completion-mode 1))

(use-package pinyinlib
  :after orderless
  :config
  (defun completion--regex-pinyin (str)
    (orderless-regexp (pinyinlib-build-regexp-string str)))
  (add-to-list 'orderless-matching-styles 'completion--regex-pinyin))

(use-package orderless
  :init
  ;; Configure a custom style dispatcher (see the Consult wiki)
  ;; (setq orderless-style-dispatchers '(+orderless-dispatch)
  ;;       orderless-component-separator #'orderless-escapable-split-on-space)
  (setq completion-styles '(basic orderless)
        completion-category-defaults nil
        completion-category-overrides '((file (styles basic partial-completion)))))

;; Enable richer annotations using the Marginalia package
(use-package marginalia
  ;; Either bind `marginalia-cycle` globally or only in the minibuffer
  ;; The :init configuration is always executed (Not lazy!)
  :init
  ;; Must be in the :init section of use-package such that the mode gets
  ;; enabled right away. Note that this forces loading the package.
  (marginalia-mode))

(use-package consult
  :bind
  ("C-s" . consult-line)
  ([remap switch-to-buffer] . consult-buffer)
  ([remap yank-pop] . consult-yank-pop)
  :config
  (setq consult-fontify-preserve nil))

(use-package embark
  :bind
  ("C-." . embark-act)
  ("M-." . embark-dwim)
  ;; Optionally replace the key help with a completing-read interface
  :init
  (setq prefix-help-command #'embark-prefix-help-command))

(use-package embark-consult
  :after (embark consult)
  :demand t
  ;; only necessary if you have the hook below
  ;; if you want to have consult previews as you move around an
  ;; auto-updating embark collect buffer
  :hook
  (embark-collect-mode . consult-preview-at-point-mode))

(use-package wgrep)

(provide 'init-vertico)

init-corfu.el

Corfu enhances completion at point with a small completion popup. The current candidates are shown in a popup below or above the point. Corfu is the minimalistic completion-in-region counterpart of the Vertico minibuffer UI.

;;; init-corfu.el --- Completion Overlay Region FUnction -*- lexical-binding: t -*-

;;; Code:

(use-package corfu
  :init
  ;; corfu settings
  (setq corfu-cycle t)
  (setq corfu-auto t)
  (setq corfu-auto-prefix 2)
  (setq corfu-auto-delay 0)
  (setq corfu-count 7)
  (setq corfu-preselect-first nil) ;; tab for complete common

  ;; emacs settings
  ;; TAB cycle if there are only few candidates
  (setq completion-cycle-threshold 5)

  ;; Enable indentation+completion using the TAB key.
  ;; `completion-at-point' is often bound to M-TAB.
  (setq tab-always-indent 'complete)

  ;; Emacs 30 及更新:可替换 ispell 的补全为 cape-dict(向后兼容 Emacs 29)
  (when (boundp 'text-mode-ispell-word-completion)
    (setq text-mode-ispell-word-completion 'cape-dict))

  ;; Hide commands in M-x which do not apply to the current mode.  Corfu
  ;; commands are hidden, since they are not used via M-x. This setting is
  ;; useful beyond Corfu.
  (setq read-extended-command-predicate #'command-completion-default-include-p)
  :config
  (global-corfu-mode)
  )

(use-package cape
  :init
  ;; Add `completion-at-point-functions', used by `completion-at-point'.
  (setq cape-dabbrev-check-other-buffers nil) ;; only check current buffer for completion
  ;; 将 dabbrev 放在 CAPF 链最后,避免影响语言专用补全
  (add-to-list 'completion-at-point-functions #'cape-file)
  (add-to-list 'completion-at-point-functions #'cape-dabbrev t))

(use-package nerd-icons-corfu
  :after corfu
  :config
  (add-to-list 'corfu-margin-formatters #'nerd-icons-corfu-formatter)
  (setq nerd-icons-corfu-mapping
        '((array :style "cod" :icon "symbol_array" :face font-lock-type-face)
          (boolean :style "cod" :icon "symbol_boolean" :face font-lock-builtin-face)
          ;; You can alternatively specify a function to perform the mapping,
          ;; use this when knowing the exact completion candidate is important.
          (file :fn nerd-icons-icon-for-file :face font-lock-string-face)
          ;; ...
          (t :style "cod" :icon "code" :face font-lock-warning-face)))
  ;; Remember to add an entry for `t', the library uses that as default.
  )

(provide 'init-corfu)

init-tempel.el

Tempel is a tiny template package for Emacs, which uses the syntax of the Emacs Tempo library. A substitute for yasnippet.

;;; init-tempel.el --- Simple templates for Emacs -*- lexical-binding: t -*-

;;; Code:

;; Configure Tempel
(use-package tempel
  :bind
  (:map tempel-map
        ([tab] . tempel-next))
  :init
  (setq tempel-path
        (list (expand-file-name "tempel-templates/*.eld" user-emacs-directory)))
  (setq tempel-trigger-prefix "<")
  ;; Setup completion at point
  (defun tempel-setup-capf ()
    ;; Add the Tempel Capf to `completion-at-point-functions'.
    ;; `tempel-expand' only triggers on exact matches. Alternatively use
    ;; `tempel-complete' if you want to see all matches, but then you
    ;; should also configure `tempel-trigger-prefix', such that Tempel
    ;; does not trigger too often when you don't expect it. NOTE: We add
    ;; `tempel-expand' *before* the main programming mode Capf, such
    ;; that it will be tried first.
    (setq-local completion-at-point-functions
                (cons #'tempel-complete
                      completion-at-point-functions)))

  (add-hook 'prog-mode-hook 'tempel-setup-capf)
  (add-hook 'text-mode-hook 'tempel-setup-capf))

(provide 'init-tempel)

init-magit.el

;;; init-magit.el --- Configuration related to git -*- lexical-binding: t -*-

;;; Code:

(use-package magit
  :init
  (setq magit-display-buffer-function #'magit-display-buffer-traditional)
  :config
  (define-key magit-mode-map (kbd "p") #'magit-push)
  (define-key magit-mode-map (kbd "P") #'magit-push-current-to-pushremote)
  ;; 在 git-commit 缓冲中,将 C-c . 绑定到 org-time-stamp
  (with-eval-after-load 'git-commit
    (require 'org)
    (define-key git-commit-mode-map (kbd "C-c .") #'org-time-stamp)))

(use-package diff-hl
  :diminish
  :init
  (global-diff-hl-mode)
  (add-hook 'magit-pre-refresh-hook 'diff-hl-magit-pre-refresh)
  (add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh))

(provide 'init-magit)

init-ibuffer.el

;;; init-ibuffer.el --- Initialize ibuffer configurations -*- lexical-binding: t -*-

;;; Code:

(defconst solarion/ibuffer-custom-groups
  '(("Dired" (mode . dired-mode))
    ("Org" (mode . org-mode))
    ("Emacs" (or (name . "^\\*scratch\\*$")
                 (name . "^\\*Backtrace\\*$")
                 (name . "^\\*Messages\\*$")))
    ("Help" (or (name . "Help")
                (name . "^helpful")))
    ("Magit" (name . "^magit")))
  "Static ibuffer groups for frequently visited buffers.")

(defun solarion/ibuffer-project-groups ()
  "Return ibuffer groups built from project roots when available."
  (when (require 'ibuffer-project nil t)
    (ibuffer-project-generate-filter-groups)))

(defun solarion/ibuffer-group-by-project-and-kind ()
  "Enable project groups followed by custom fallback groups."
  (ibuffer-auto-mode 1)
  (setq ibuffer-filter-groups
        (append (solarion/ibuffer-project-groups)
                solarion/ibuffer-custom-groups))
  (unless (eq ibuffer-sorting-mode 'alphabetic)
    (ibuffer-do-sort-by-alphabetic)))

(use-package ibuffer
  :straight nil
  :hook (ibuffer-mode . solarion/ibuffer-group-by-project-and-kind)
  :custom
  (ibuffer-show-empty-filter-groups nil)
  (ibuffer-saved-filter-groups
   `(("custom" ,@solarion/ibuffer-custom-groups))))

(use-package ibuffer-project
  :straight nil
  :after ibuffer
  :commands (ibuffer-project-generate-filter-groups))

(provide 'init-ibuffer)

init-midnight.el

Clean inactive buffers.

;;; init-midnight.el --- Configurations for midnight -*- lexical-binding: t -*-

;;; Code:

;; use `clean-buffer-list' from `midnight.el'
(use-package midnight
  :config
  ;; kill buffers if they were last disabled more than this seconds ago
  (setq clean-buffer-list-delay-special (* 3 60 60))

  ;; run clean-buffer-list every 30 minites
  (setq clean-buffer-list-timer (run-at-time t 1800 'clean-buffer-list))

  ;; kill everything, clean-buffer-list is very intelligent at not killing
  ;; unsaved buffer.
  ;; 满足条件且超过`clean-buffer-list-delay-special'的buffer才会被清除
  (setq clean-buffer-list-kill-regexps '("^.*$"))

  (defvar solarion-clean-buffer-list-kill-never-buffer-names
    (delq nil
          (list "*httpd*" "*Messages*" "*Backtrace*" "*scratch*" "*Ibuffer*"
                (when (boundp 'dashboard-buffer-name) dashboard-buffer-name)
                (when (boundp 'vterm-buffer-name) vterm-buffer-name)
                (when (boundp 'minimap-buffer-name) minimap-buffer-name)))
    "Buffer names never to kill.")

  (setq clean-buffer-list-kill-never-buffer-names
        (append solarion-clean-buffer-list-kill-never-buffer-names clean-buffer-list-kill-never-buffer-names))

  (defvar solarion-clean-buffer-list-kill-never-regexps
    nil
    "regexps not to kill")
  (setq clean-buffer-list-kill-never-regexps
        (append solarion-clean-buffer-list-kill-never-regexps clean-buffer-list-kill-never-regexps)))

(provide 'init-midnight)

init-tramp.el

;;; init-tramp.el --- Tramp settings -*- lexical-binding: t -*-

;;; Code:

(use-package tramp
  :straight (:type built-in)
  :config
  (add-to-list 'tramp-methods
               '("yadm"
                 (tramp-login-program "yadm")
                 (tramp-login-args (("enter")))
                 (tramp-login-env (("SHELL" . "/bin/sh")))
                 (tramp-remote-shell "/bin/sh")
                 (tramp-remote-shell-args ("-c"))))
  (defun yadm ()
    (interactive)
    (magit-status "/yadm::")))

;; NOTE: Ensure `yadm` is installed and available in PATH for the
;; custom TRAMP method above.

(provide 'init-tramp)

init-org.el

;;; init-org.el --- Org-mode -*- lexical-binding: t -*-

;;; Code:

(use-package org
  :straight (:type built-in)
  :hook
  (org-mode . solarion/org-mode-setup)
  (org-mode . org-num-mode)
  :config
  (org-babel-do-load-languages
   'org-babel-load-languages
   '((emacs-lisp . t)
     (shell . t)
     (latex . t)
     (python . t)
     (matlab . t)
     (gnuplot . t)))
  (setq org-startup-with-inline-images nil)
  (setq org-startup-with-latex-preview nil)
  (setq org-adapt-indentation t);; add indentation for newlines
  (setq org-highlight-latex-and-related '(native script entities))
  (setq org-directory "~/org")
  (setq org-ellipsis "[+]")
  (setq org-tags-column -80)
  (setq org-log-done 'time)
  (setq org-hide-emphasis-markers nil) ;; Show bold and italic verbosely
  (setq org-link-descriptive t) ;; Show links verbosely
  (setq org-hide-leading-stars t)
  (setq org-return-follows-link t)
  ;; export settings
  (setq org-export-with-tags nil)
  (setq org-export-with-sub-superscripts '{})
  ;; latex settings
  ;; .latexmkrc for reference
  ;; # --- Engine & output ---
  ;; $pdf_mode = 5;  # 5 = XeLaTeX -> PDF
  ;; # $out_dir  = './LaTeX.out';
  ;; $aux_dir  = './LaTeX.aux';
  ;; $synctex  = 1;

  ;; # Keep going through errors (use -halt-on-error instead if you prefer strict)
  ;; $xelatex  = 'xelatex -file-line-error -interaction=nonstopmode %O %S';

  ;; # Use biber with biblatex
  ;; $bibtex   = 'biber %O %B';

  ;; # --- Nomenclature (nlo -> nls) ---
  ;; add_cus_dep('nlo', 'nls', 0, 'makenomenclature');
  ;; sub makenomenclature {
  ;;     system("makeindex -s nomencl.ist -o \"$_[0].nls\" \"$_[0].nlo\"");
  ;; }

  ;; # --- Cleaning: include nomencl, biber, and common aux files ---
  ;; push @generated_exts, qw(
  ;;   nls nlo ilg ind idx
  ;;   bcf run.xml bbl blg
  ;;   synctex.gz toc out lot lof aux fls fdb_latexmk nav snm
  ;; );
  (setq org-latex-pdf-process
        '("latexmk -xelatex -shell-escape -f -interaction=nonstopmode %f && latexmk -c"))
  ;; 清理更多中间文件
  (require 'ox-latex)
  (setq org-latex-hyperref-template "\\hypersetup{\n pdfauthor={%a},\n pdftitle={%t},\n pdfkeywords={%k},\n pdfsubject={%d},\n pdfcreator={%c}, \n pdflang={%L}, hidelinks, colorlinks=false}\n")
  (setq org-latex-logfiles-extensions
        (append org-latex-logfiles-extensions
                '("bcf" "run.xml" "bbl" "blg" "synctex.gz" "toc" "out" "lot" "lof"
                  "aux" "fls" "fdb_latexmk" "nav" "snm" "nls" "nlo" "ilg" "ind" "idx")))
  (setq org-latex-toc-command "\\pagestyle{empty}\n\\tableofcontents\n\\clearpage\n\\setcounter{page}{1}\n\\pagestyle{plain}\n\n")
  ;; maketitle command
  (setq org-latex-title-command "\\maketitle\n\\thispagestyle{empty}")
  (setq org-latex-src-block-backend 'listings)
  (setq org-export-with-smart-quotes t)
  ;; size of the preview latex fragments
  (plist-put org-format-latex-options :scale 2)
  (general-def org-src-mode-map "C-c C-c" #'org-edit-src-exit)
  (general-def org-mode-map "C-RET" #'org-meta-return)
  (general-def org-mode-map "C-<return>" #'org-meta-return)
  ;; org latex packages
  (setq org-latex-packages-alist
        '(("" "siunitx")
          ("" "amsmath")
          ("" "mathrsfs")
          ("scheme=plain" "ctex")
          ;; mlmodern is thicker
          ;; ("" "mlmodern")
          ("" "lmodern")
          ("T1" "fontenc")
          ("" "listings")))
  (setq org-latex-listings-options
        '(("breaklines" "true")
          ("breakatwhitespace" "false")
          ("basicstyle" "\\ttfamily\\small")
          ("columns" "flexible")
          ("keepspaces" "true")))
  (setq org-image-actual-width t)
  (setq org-preview-latex-image-directory (concat user-emacs-directory ".local/ltximg/"))

  ;; treesit
  (defun solarion/org-set-lang-mode (lang mode-sym)
    "把 org-src-lang-modes 里 LANG 的映射设为 MODE-SYM,先去重再插入到表头。"
    (setq org-src-lang-modes
          (cons (cons lang mode-sym)
                (cl-remove-if (lambda (e) (string= (car e) lang))
                              org-src-lang-modes))))

  ;; —— 按需写入 treesit 映射(值是不带 -mode 的符号)————
  (solarion/org-set-lang-mode "json"       'json-ts)
  (solarion/org-set-lang-mode "toml"       'toml-ts)
  (solarion/org-set-lang-mode "ini"        'toml-ts)
  (solarion/org-set-lang-mode "javascript" 'js-ts)
  (solarion/org-set-lang-mode "js"         'js-ts)
  (solarion/org-set-lang-mode "typescript" 'typescript-ts)
  (solarion/org-set-lang-mode "tsx"        'tsx)
  (solarion/org-set-lang-mode "python"     'python-ts)
  (solarion/org-set-lang-mode "yaml"       'yaml-ts)
  (solarion/org-set-lang-mode "c"          'c-ts)
  (solarion/org-set-lang-mode "cpp"        'c++-ts)
  (solarion/org-set-lang-mode "bash"       'bash-ts)
  (solarion/org-set-lang-mode "sh"         'bash-ts)
  (solarion/org-set-lang-mode "nix"        'nix-ts)
  )

(use-package ox-gfm
  :config (add-to-list 'org-export-backends 'gfm))

(use-package org-superstar
  :diminish org-superstar-mode
  :hook (org-mode . (lambda () (org-superstar-mode)))
  :init
  (setq
   org-superstar-headline-bullets-list '("" "" "" "")
   org-superstar-cycle-headline-bullets nil
   org-superstar-prettify-item-bullets nil))

(use-package org-download
  :after org
  :config
  (org-download-enable)
  (setq org-download-method 'directory)
  (defun solarion/org-download-image-dir ()
    "Return a directory name based on current Org filename (e.g. asdf.org → asdf/)."
    (when buffer-file-name
      (let ((basename (file-name-base buffer-file-name)))
        (expand-file-name (concat basename "/") (file-name-directory buffer-file-name)))))
  (setq org-download-image-dir #'solarion/org-download-image-dir)
  (setq
   org-download-heading-lvl nil
   org-download-timestamp "%Y%m%d-%H%M%S_")

  ;; to change image width seperately (also hide the annotate #+DOWNLOADED)
  (setq org-download-annotate-function
        (lambda (_link)
          "#+ATTR_LATEX: :width \\linewidth\n#+ATTR_ORG: :width 400\n")))

;; Automatically insert table of contents after the heading with :TOC: tag
(use-package toc-org
  :hook (org-mode . toc-org-mode))

;; Auto-toggle Org LaTeX fragments
(use-package org-fragtog)

;; matlab mode, currently only for org mode, could be separated
;; the package name is matlab, yet it provides `matlab'
(use-package matlab
  :straight matlab-mode
  :diminish
  :config
  (add-to-list 'auto-mode-alist '("\\.m\\'" . matlab-mode))
  (setq matlab-indent-function t)
  (setq matlab-shell-command "matlab"))

;; gnuplot mode, currently only for org mode, could be separated
(use-package gnuplot
  :diminish
  :config
  (add-to-list 'auto-mode-alist '("\\.gp$" . gnuplot-mode)))

(use-package ox-hugo
  :after ox)

(provide 'init-org)

init-denote.el

;;; init-denote.el --- Denote -*- lexical-binding: t -*-

;;; Code:

(use-package denote
  :hook
  (;; Apply colours to Denote names in Dired.  This applies to all
   ;; directories.  Check `denote-dired-directories' for the specific
   ;; directories you may prefer instead.  Then, instead of
   ;; `denote-dired-mode', use `denote-dired-mode-in-directories'.
   (dired-mode . denote-dired-mode))
  :bind
  :config
  (setq denote-directory (expand-file-name "~/notes"))
  (setq denote-save-buffers nil)
  (setq denote-known-keywords '("emacs" "linux" "openwrt"))
  (setq denote-infer-keywords t)
  (setq denote-sort-keywords t)
  (setq denote-prompts '(title keywords))
  (setq denote-excluded-directories-regexp nil)
  (setq denote-excluded-keywords-regexp nil)
  (setq denote-rename-confirmations '(rewrite-front-matter modify-file-name))

  ;; Pick dates, where relevant, with Org's advanced interface:
  (setq denote-date-prompt-use-org-read-date t)

  ;; By default, we do not show the context of links.  We just display
  ;; file names.  This provides a more informative view.
  (setq denote-backlinks-show-context t)

  ;; Automatically rename Denote buffers using the `denote-rename-buffer-format'.
  (denote-rename-buffer-mode t))

(use-package consult-notes
  :commands (consult-notes
             consult-notes-search-in-all-notes
             )
  :config
  (when (locate-library "denote")
    (consult-notes-denote-mode))
  ;; search only for text files in denote dir
  (setq consult-notes-denote-files-function (lambda () (denote-directory-files nil t t))))

(global-leader-def
  ;; denote
  "n" '(:ignore t :which-key "Denote")
  "nn" #'consult-notes
  "nd" #'denote-sort-dired
  "nl" #'denote-link
  "nL" #'denote-add-links
  "nb" #'denote-backlinks
  "nr" #'denote-rename-file
  "nR" #'denote-rename-file-using-front-matter
  )

(provide 'init-denote)

init-latex.el

;;; init-latex.el --- Initialize LaTeX settings -*- lexical-binding: t -*-

;; GhostScript is needed for previewing latex fragments

;;; Code:

;; Note that it *must* be 'use-package latex', if 'auctex' is used instead,
;; 'auctex.el' is never called later, and the :config section is not set.
;; Many (most?) people use 'use-package tex', which is fine and probably
;; more "correct", but then care would have to be taken with variables which
;; are not defined in 'tex.el' (starting with "TeX-"), but in 'latex.el'
;; (starting with "LaTeX-"). As 'latex.el' requires 'tex.el', simply setting
;; 'use-package latex' catches all in one go.
(use-package latex
  :straight auctex
  :config
  (add-hook 'LaTeX-mode-hook #'TeX-source-correlate-mode)
  (setq
   LaTeX-electric-left-right-brace t
   TeX-parse-self t ;; parse onload
   TeX-auto-save t ;; parse on save
   ;; use hidden dirs for auctex files
   TeX-auto-local ".auctex-auto"
   TeX-style-local ".auctex-style"

   TeX-source-correlate-method 'synctex
   ;; don't start the emacs server when correlating sources
   TeX-source-correlate-start-server nil
   ;; just save, dont ask me before each compilation
   TeX-save-query nil)

  (setq-default TeX-engine 'xetex)

  (setq preview-default-option-list '("displaymath" "floats" "graphics" "textmath" "footnotes"))

  (setq preview-scale-function 2.0)

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

(use-package auctex-latexmk
  :hook (LaTeX-mode . (lambda ()
                        (setq TeX-command-default "LatexMk")))
  :config
  (setq auctex-latexmk-inherit-TeX-PDF-mode t)
  (auctex-latexmk-setup))

(provide 'init-latex)

init-dired.el

;;; init-dired.el --- Emacs built in file manager -*- lexical-binding: t -*-

;;; Code:

(use-package dired
  :straight nil
  :config
  (setq dired-listing-switches "-alh --group-directories-first"
        dired-dwim-target t
        dired-create-destination-dirs 'ask
        dired-recursive-deletes 'always
        dired-recursive-copies 'always))

(use-package nerd-icons-dired
  :hook (dired-mode . nerd-icons-dired-mode))

(use-package dired-rsync
  :after dired
  :config
  (general-def dired-mode-map "C-c C-r" #'dired-rsync))

;; Colourful dired
(use-package diredfl
  :after dired
  :hook (dired-mode . diredfl-mode))

(use-package dired-single
  :after dired
  :bind
  (:map dired-mode-map
        ([remap dired-find-file] . dired-single-buffer)
        ([remap dired-mouse-find-file-other-window] . dired-single-buffer-mouse)
        ([remap dired-up-directory] . dired-single-up-directory)))

(use-package dired-hide-dotfiles
  :after dired
  :hook (dired-mode . dired-hide-dotfiles-mode)
  :config
  (general-def dired-mode-map "H" 'dired-hide-dotfiles-mode))

(defun solarion/dired-here ()
  "Open current file's directory in Dired."
  (interactive)
  (dired default-directory))
(general-def "C-c d" #'solarion/dired-here)

(provide 'init-dired)

init-dashboard.el

;;; init-dashboard.el --- Setup for the splash screen (dashboard) -*- lexical-binding: t -*-

;;; Code:

(use-package dashboard
  :diminish
  :init
  (defun solarion-init-time ()
    "Showing Emacs initializing time, packages loaded and GC"
    (format "Loaded %d packages in %.2f ms."
            (- (length load-path) (length (get 'load-path 'initial-value)))
            (* 1e3 (float-time (time-subtract after-init-time before-init-time)))))
  (defun solarion/dashboard-update-packages ()
    (interactive)
    (straight-pull-all)
    (message "Packages updated."))
  (setq dashboard-banner-logo-title (concat "Emacs " emacs-version)
        dashboard-startup-banner "~/.emacs.d/logo.png"
        dashboard-image-banner-max-height 400
        dashboard-page-separator "\n\n"
        dashboard-center-content t
        dashboard-show-shortcuts t
        dashboard-items '((recents . 5)
                          (bookmarks . 5)
                          (projects . 3))
        dashboard-startupify-list '(dashboard-insert-banner
                                    dashboard-insert-newline
                                    dashboard-insert-banner-title
                                    dashboard-insert-newline
                                    dashboard-insert-navigator
                                    dashboard-insert-newline
                                    dashboard-insert-init-info
                                    dashboard-insert-items)
        dashboard-navigator-buttons `(((,(nerd-icons-octicon "nf-oct-history") "Restore (R)" "Restore previous session" (lambda (&rest _) (restore-previous-session)))
                                       (,(nerd-icons-codicon "nf-cod-tools") "Config (C)" "Open custom file" solarion-edit-config)
                                       (,(nerd-icons-mdicon "nf-md-update") "Update (U)" "Update Packages" solarion/dashboard-update-packages))))
  (when (< (length command-line-args) 2)
    (setq initial-buffer-choice 'dashboard-open))
  :config
  (dashboard-setup-startup-hook)
  (setq dashboard-init-info (solarion-init-time))
  (general-def dashboard-mode-map
    "R" #'restore-previous-session
    "C" #'solarion-edit-config
    "U" #'straight-pull-all))

(provide 'init-dashboard)

init-vterm.el

;;; init-vterm.el --- Emacs libvterm integration -*- lexical-binding: t -*-

;;; Code:

(if (and module-file-suffix           ;; dynamic module
         (executable-find "cmake")
         (executable-find "libtool")  ;; install libtool-bin
         (executable-find "make"))
    (progn
      (use-package vterm
        :init
        (setq vterm-always-compile-module t)
        :bind (:map vterm-mode-map
                    ("C-\\" . toggle-input-method)
                    ("C-q" . vterm-send-next-key)))

      (use-package vterm-toggle
        :bind
        ([f2] . vterm-toggle)
        ([C-f2] . vterm-toggle-cd)
        (:map vterm-mode-map
              ("C-<return>" . vterm-toggle-insert-cd)
              ([f2] . vterm-toggle))))
  (unless (get 'solarion/vterm-warning 'shown)
    (display-warning 'vterm
                     "vterm disabled: missing module support or build tools (cmake/libtool/make)."
                     :warning)
    (put 'solarion/vterm-warning 'shown t)))

(provide 'init-vterm)

init-persp.el

Restore previous session.

;;; init-persp.el --- Configurations for persp-mode -*- lexical-binding: t -*-

;;; Code:

(use-package persp-mode
  :diminish
  :hook
  ((after-init . persp-mode)
   (persp-mode . persp-load-frame)
   (kill-emacs . persp-save-frame))
  :init
  (setq persp-keymap-prefix nil
        persp-nil-name "default"
        persp-set-last-persp-for-new-frames nil
        persp-kill-foreign-buffer-behaviour 'kill
        persp-auto-resume-time 0)
  :config
  ;; Save and load frame parameters (size & position)
  (defvar persp-frame-file (expand-file-name "persp-frame" persp-save-dir)
    "File of saving frame parameters.")

  (defun persp-save-frame ()
    "Save the current frame parameters to file."
    (interactive)
    (when (and (display-graphic-p) persp-mode)
      (condition-case error
          (with-temp-buffer
            (erase-buffer)
            (insert
             ";;; -*- mode: emacs-lisp; coding: utf-8-unix -*-\n"
             ";;; This is the previous frame parameters.\n"
             ";;; Last generated " (current-time-string) ".\n"
             "(setq initial-frame-alist\n"
             (format "      '((top . %d)\n" (frame-parameter nil 'top))
             (format "        (left . %d)\n" (frame-parameter nil 'left))
             (format "        (width . %d)\n" (frame-parameter nil 'width))
             (format "        (height . %d)\n" (frame-parameter nil 'height))
             (format "        (fullscreen . %s)))\n" (frame-parameter nil 'fullscreen)))
            (write-file persp-frame-file))
        (error
         (warn "persp frame: %s" (error-message-string error))))))

  (defun persp-load-frame ()
    "Load frame with the previous frame's geometry."
    (interactive)
    (when (and (display-graphic-p) persp-mode)
      (condition-case error
          (progn
            (load persp-frame-file)

            ;; Handle multiple monitors gracefully
            (when (or (>= (eval (frame-parameter nil 'left)) (display-pixel-width))
                      (>= (eval (frame-parameter nil 'top)) (display-pixel-height)))
              (set-frame-parameter nil 'left 0)
              (set-frame-parameter nil 'top 0)))
        (error
         (warn "persp frame: %s" (error-message-string error))))))

  (defun restore-previous-session ()
    "Restore the previous session."
    (interactive)
    (when (bound-and-true-p persp-mode)
      (restore-session persp-auto-save-fname))
    (message "Restoring previous session from: %s" persp-auto-save-fname))

  (defun restore-session (fname)
    "Restore the specified session."
    (interactive (list (read-file-name "Load perspectives from a file: "
                                       persp-save-dir)))
    (when (bound-and-true-p persp-mode)
      (message "Restoring session...")
      (quit-window t)
      (condition-case-unless-debug err
          (persp-load-state-from-file fname)
        (error "Error: Unable to restore session -- %s" err))
      (message "Restoring session...done")))
  ;; Don't save dead or temporary buffers
  (add-hook 'persp-filter-save-buffers-functions
            (lambda (b)
              "Ignore dead and unneeded buffers."
              (or (not (buffer-live-p b))
                  (string-prefix-p " *" (buffer-name b)))))
  (add-hook 'persp-filter-save-buffers-functions
            (lambda (b)
              "Ignore temporary buffers."
              (let ((bname (file-name-nondirectory (buffer-name b))))
                (or (string-prefix-p ".newsrc" bname)
                    (string-prefix-p "magit" bname)
                    (string-prefix-p "*vterm" bname)
                    (string-prefix-p "COMMIT_EDITMSG" bname)
                    (string-prefix-p "Pfuture-Callback" bname)
                    (string-prefix-p "treemacs-persist" bname)
                    (string-match-p "\\.elc\\|\\.tar\\|\\.gz\\|\\.zip\\'" bname)
                    (string-match-p "\\.bin\\|\\.so\\|\\.dll\\|\\.exe\\'" bname)))))

  ;; Don't save persp configs in `recentf'
  (with-eval-after-load 'recentf
    (push persp-save-dir recentf-exclude)))

(provide 'init-persp)

init-im.el

;;; init-im.el --- 输入法相关 -*- lexical-binding: t -*-

;;; Code:

;; requires librime-dev on Debian
(use-package rime
  :init
  (setq default-input-method "rime"
        rime-show-candidate 'posframe
        rime-popup-style 'vertical
        rime-posframe-style 'vertical
        rime-user-data-dir (expand-file-name "rime/" user-emacs-directory)
        rime-posframe-properties '(:internal-border-width 2))
  :config
  (general-def rime-mode-map "C-`" #'rime-send-keybinding)
  (general-def rime-active-mode-map "S-<delete>" #'rime-send-keybinding))

;; requires emacs-mozc on Debian
(use-package mozc
  :init
  (setq mozc-candidate-style 'echo-area))

(defun solarion/toggle-input-method ()
  "Toggle between nil, rime and japanese-mozc input methods."
  (interactive)
  (cond
   ((equal current-input-method nil)
    (set-input-method 'rime))
   ((equal current-input-method "rime")
    (set-input-method 'japanese-mozc))
   ((equal current-input-method "japanese-mozc")
    (set-input-method 'rime))))

(general-def :keymaps 'override "C-c SPC" #'solarion/toggle-input-method)

(provide 'init-im)

init-wsl.el

;;; init-wsl.el --- wsl-specific setup -*- lexical-binding: t -*-

;;; Code:

;; teach Emacs how to open links with your default browser
(let ((cmd-exe "/mnt/c/Windows/System32/cmd.exe")
      (cmd-args '("/c" "start")))
  (when (file-exists-p cmd-exe)
    (setq browse-url-generic-program  cmd-exe
          browse-url-generic-args     cmd-args
          browse-url-browser-function 'browse-url-generic
          search-web-default-browser 'browse-url-generic)))

;; Fix wayland Copy from wsl to Windows
(setq select-active-regions nil)

(defun wl-copy (text)
  (let ((process-connection-type nil))
    (let ((proc (start-process "wl-copy" "*Messages*" "wl-copy" "-f" "-n")))
      (process-send-string proc text)
      (process-send-eof proc))))

(defun wl-paste ()
  (shell-command-to-string "wl-paste -n"))

(if (and (executable-find "wl-copy")
         (executable-find "wl-paste"))
    (progn
      (setq interprogram-cut-function 'wl-copy)
      (setq interprogram-paste-function 'wl-paste))
  (message "wl-clipboard not found; install it (e.g. sudo apt install wl-clipboard) to enable WSL clipboard sync."))

;; WSL tweak to keep Org LaTeX previews scaled correctly under Wayland
(setq display-mm-dimensions-alist '(("wayland-0" . (797 . 344))))

(provide 'init-wsl)

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published