Skip to content

Latest commit

 

History

History
4425 lines (3676 loc) · 159 KB

init.org

File metadata and controls

4425 lines (3676 loc) · 159 KB

Initial configuration

Header

;;; init.el --- Init file for my own -*- lexical-binding: t; -*-

;; Author: derui <[email protected]>
;; Maintainer: derui <[email protected]>

;;; Commentary:

;;: Customization:

;;; Code:

;; DO NOT EDIT THIS FILE DIRECTLY

起動時間を計測する

https://zenn.dev/takeokunn/articles/56010618502ccc

(defconst my:before-load-init-time (current-time))

;;;###autoload
(defun my:load-init-time ()
  "Loading time of user init files including time for `after-init-hook'."
  (let ((time1 (float-time
                (time-subtract after-init-time my:before-load-init-time)))
        (time2 (float-time
                (time-subtract (current-time) my:before-load-init-time))))
    (message (concat "Loading init files: %.0f [msec], "
                     "of which %.f [msec] for `after-init-hook'.")
             (* 1000 time1) (* 1000 (- time2 time1)))))
(add-hook 'after-init-hook #'my:load-init-time t)

(defun my:emacs-init-time ()
  "Emacs booting time in msec."
  (interactive)
  (message "Emacs booting time: %.0f [msec] = `emacs-init-time'."
           (* 1000
              (float-time (time-subtract
                           after-init-time
                           before-init-time)))))

(add-hook 'after-init-hook #'my:emacs-init-time)

elごとの読み込み時間を取る

(defvar my:setup-tracker--level 0)
(defvar my:setup-tracker--parents nil)
(defvar my:setup-tracker--times nil)
(defvar my:setup-tracker-enabled nil)

(when my:setup-tracker-enabled

  (when load-file-name
    (push load-file-name my:setup-tracker--parents)
    (push (current-time) my:setup-tracker--times)
    (setq my:setup-tracker--level (1+ my:setup-tracker--level)))

  (add-variable-watcher
   'load-file-name
   (lambda (_ v &rest __)
     (cond ((equal v (car my:setup-tracker--parents))
            nil)
           ((equal v (cadr my:setup-tracker--parents))
            (setq my:setup-tracker--level (1- my:setup-tracker--level))
            (let* ((now (current-time))
                   (start (pop my:setup-tracker--times))
                   (elapsed (+ (* (- (nth 1 now) (nth 1 start)) 1000)
                               (/ (- (nth 2 now) (nth 2 start)) 1000))))
              (with-current-buffer (get-buffer-create "*my:setup-tracker*")
                (save-excursion
                  (goto-char (point-min))
                  (dotimes (_ my:setup-tracker--level) (insert "> "))
                  (insert
                   (file-name-nondirectory (pop my:setup-tracker--parents))
                   " (" (number-to-string elapsed) " msec)\n")))))
           (t
            (push v my:setup-tracker--parents)
            (push (current-time) my:setup-tracker--times)
            (setq my:setup-tracker--level (1+ my:setup-tracker--level)))))))

Initialize elpaca

(eval-when-compile
  (defvar elpaca-installer-version 0.7)
  (defvar elpaca-directory (expand-file-name "elpaca/" user-emacs-directory))
  (defvar elpaca-builds-directory (expand-file-name "builds/" elpaca-directory))
  (defvar elpaca-repos-directory (expand-file-name "repos/" elpaca-directory))
  (defvar elpaca-order '(elpaca :repo "https://github.com/progfolio/elpaca.git"
                                :ref nil :depth 1
                                :files (:defaults "elpaca-test.el" (:exclude "extensions"))
                                :build (:not elpaca--activate-package)))
  (let* ((repo  (expand-file-name "elpaca/" elpaca-repos-directory))
         (build (expand-file-name "elpaca/" elpaca-builds-directory))
         (order (cdr elpaca-order))
         (default-directory repo))
    (add-to-list 'load-path (if (file-exists-p build) build repo))
    (unless (file-exists-p repo)
      (make-directory repo t)
      (when (< emacs-major-version 28) (require 'subr-x))
      (condition-case-unless-debug err
          (if-let ((buffer (pop-to-buffer-same-window "*elpaca-bootstrap*"))
                   ((zerop (apply #'call-process `("git" nil ,buffer t "clone"
                                                   ,@(when-let ((depth (plist-get order :depth)))
                                                       (list (format "--depth=%d" depth) "--no-single-branch"))
                                                   ,(plist-get order :repo) ,repo))))
                   ((zerop (call-process "git" nil buffer t "checkout"
                                         (or (plist-get order :ref) "--"))))
                   (emacs (concat invocation-directory invocation-name))
                   ((zerop (call-process emacs nil buffer nil "-Q" "-L" "." "--batch"
                                         "--eval" "(byte-recompile-directory \".\" 0 'force)")))
                   ((require 'elpaca))
                   ((elpaca-generate-autoloads "elpaca" repo)))
              (progn (message "%s" (buffer-string)) (kill-buffer buffer))
            (error "%s" (with-current-buffer buffer (buffer-string))))
        ((error) (warn "%s" err) (delete-directory repo 'recursive))))
    (unless (require 'elpaca-autoloads nil t)
      (require 'elpaca)
      (elpaca-generate-autoloads "elpaca" repo)
      (load "./elpaca-autoloads")))
  (add-hook 'after-init-hook #'elpaca-process-queues)
  (elpaca `(,@elpaca-order))

  ;; 全体を更新するために先頭に追加する
  (add-hook 'emacs-startup-hook #'elpaca-process-queues))

実行パスの設定

exec-pathに必要なパスを追加する。

(add-to-list 'exec-path (expand-file-name "~/.npm/bin"))
(add-to-list 'exec-path (expand-file-name "~/.asdf/shims"))
(add-to-list 'exec-path "/usr/local/bin")
(add-to-list 'exec-path "/usr/bin")
(add-to-list 'exec-path "/usr/sbin")
(add-to-list 'exec-path my:user-local-exec-path)
(add-to-list 'exec-path (expand-file-name "bin" my:roswell-path))
(add-to-list 'exec-path (expand-file-name "bin" my:cargo-path))

外部設定の読込み

git管理外になっているファイル。これは、パス関連など、環境毎に違うので登録するとめんどくさいものに対して利用する。

(let ((user-env (locate-user-emacs-file "conf/user-env.el")))
  (load user-env t))

configuration macros

設定で使う簡単なmacro を定義する。

(defmacro darwin-cli! (&rest body)
  "macOSかつCLIでの起動時にだけ有効になる設定を書くMacro"
  (if (and (eq system-type 'darwin) (not window-system))
      `(progn ,@body)
    nil))

(defmacro darwin-gui! (&rest body)
  "macOSかつGUIでの起動時にだけ有効になる設定をするmacro"
  (if (and (eq system-type 'darwin) window-system)
      `(progn ,@body)
    nil))

(defmacro darwin! (&rest body)
  "macOSでの起動時にだけ有効になる設定をするmacro"
  (if (eq system-type 'darwin)
      `(progn ,@body)
    nil))

(defmacro linux! (&rest body)
  "Linux環境での起動次にだけ有効になる設定をするmacro"
  (if (and (eq system-type 'gnu/linux))
      `(progn ,@body)
    nil))

(defmacro each! (lst &rest body)
  "`lst'をloop-unrollingしたものに転換する。

リストの要素は `it' でアクセスできる"
  (declare (indent 1))
  `(progn
     ,@(mapcar (lambda (v)
                 `(let ((it (quote ,v)))
                    ,@body)
                 )
               lst)))

load-path configuration macro

(defmacro load-package (symbol)
  "`symbol' に対応するload-pathを追加する"
  (declare (indent 1))
  (let* ((dir (expand-file-name user-emacs-directory))
         (package-name (cond ((symbolp symbol)
                              (symbol-name symbol))
                             (t symbol)))
         (autoload-name (seq-concatenate 'string package-name "-autoloads"))
         (autoload-file-name (file-name-concat dir "elpaca" "builds" package-name
                                               (seq-concatenate 'string autoload-name ".el") ))
         (locate-autoload-p (file-exists-p autoload-file-name)))
    `(progn
       (message "Loading %s/%s..." ,package-name ,autoload-name)
       (add-to-list 'load-path ,(file-name-concat dir "elpaca" "builds" package-name))
       ,(when locate-autoload-p
          `(load ,autoload-file-name nil t t t)))
    ))

Configuration queues

遅延して実行するための簡単なQueue機構。

(defvar my:high-priority-startup-queue nil
  "高いPriorityで実行されるQueue")
(defvar my:high-priority-startup-timer nil)

(defvar my:low-priority-startup-queue nil
  "低いPriorityで実行されるQueue")
(defvar my:low-priority-startup-timer nil)

(defmacro with-high-priority-startup (&rest body)
  "high priorityな遅延処理を登録する"
  (declare (indent 0))
  `(setq my:high-priority-startup-queue
         (append my:high-priority-startup-queue ',body)))

(defmacro with-low-priority-startup (&rest body)
  "low priorityな遅延処理を登録する"
  (declare (indent 0))
  `(setq my:low-priority-startup-queue
         (append my:low-priority-startup-queue ',body)))

(add-hook 'emacs-startup-hook
          ;; Timerを開始して、 `my:high-priority-startup-queue' にある処理を実行していく
          (lambda ()
            (setq my:high-priority-startup-timer
                  (run-with-timer 0.01 0.001
                                  (lambda ()
                                    (if my:high-priority-startup-queue
                                        (let ((inhibit-message t))
                                          (eval (pop my:high-priority-startup-queue)))
                                      (cancel-timer my:high-priority-startup-timer)))))
            ;; Timerを開始して、 `my:low-priority-startup-queue' にある処理を実行していく
            (setq my:low-priority-startup-timer
                  (run-with-timer 0.03 0.001
                                  (lambda ()
                                    (if my:low-priority-startup-queue
                                        (let ((inhibit-message t))
                                          (eval (pop my:low-priority-startup-queue)))
                                      (cancel-timer my:low-priority-startup-timer)))))))

Emacs base configuration

基本設定

macOS限定の設定

(darwin!
 ;; altとMetaを入れ替える
 (setq mac-option-modifier 'alt)
 (setq mac-command-modifier 'meta))

(darwin-gui!
 ;; macOSで描画がかなり遅いのを解消できるかもしれない設定
 (add-to-list 'default-frame-alist '(inhibit-double-buffering . t)))

font-lock

(setq font-lock-support-mode 'jit-lock-mode)

全角空白やタブに色をつける

(defface my-face-b-2 '((t (:background "gray26"))) "face for tab" :group 'my)
(defface my-face-u-1 '((t (:foreground "SteelBlue" :underline t))) "" :group 'my)
(defvar my-face-b-2 'my-face-b-2)
(defvar my-face-u-1 'my-face-u-1)

(defun my:font-lock-mode (&rest _)
  (font-lock-add-keywords
   major-mode
   '(("\t" 0 my-face-b-2 append)
     ("[ \t]+$" 0 my-face-u-1 append))))
(advice-add 'font-lock-mode :before 'my:font-lock-mode)

bookmarkのfaceを無効化する

Emacs 28.1からデフォルト値が変更されたので、元々のfaceに合うように戻す。

(with-eval-after-load 'bookmark
  (set-face-attribute 'bookmark-face nil :foreground 'unspecified :background 'unspecified :inherit 'unspecified))

mode lineでvariable pitchを使わないようにする

Emacs 29でなんでかmode lineのフォントとしてvariable pitchが利用されるような設定が追加されたので、同じものを利用するようにする。

mode lineをvariable pitchではなく等幅フォントを利用する。

(set-face-attribute 'mode-line-active nil :inherit 'mode-line)

グローバルに有効にするmode

(with-high-priority-startup
  (global-font-lock-mode +1)

  (show-paren-mode +1)

  (transient-mark-mode +1)

  ;; pixelベースのスクロール処理
  (pixel-scroll-precision-mode +1)
  )

major-modeのhookをdirectory localの後に起動できるようにする

https://blog.tomoya.dev/posts/how-to-automatically-switch-lsp-servers-in-lsp-mode/

denoとts-lsを切り替えながらやりたい場合などに利用する。directory localを適用してからhookを実行したい場合は、 <major-mode>-local-vars-hook というhookを実行すること。

(defun my:run-local-vars-mode-hook ()
  "Run `major-mode' hook after the local variables have been processed."
  (run-hooks (intern (concat (symbol-name major-mode) "-local-vars-hook"))))
(add-hook 'hack-local-variables-hook 'my:run-local-vars-mode-hook)

focus outしたら強制保存

(defun my:save-all-buffers ()
  "focusの状態が変ったら、全bufferを保存する"
  (when (not (frame-focus-state))
    (save-some-buffers "!")))

(add-function :after after-focus-change-function #'my:save-all-buffers)

複数のスペースは段落とみなさないように

(setopt sentence-end-double-space nil)

標準パッケージ

browse-url

(with-eval-after-load 'browse-url
  (cond
   ((executable-find "firefox")
    (progn
      (setq browse-url-browser-function #'browse-url-firefox)
      (setq browse-url-generic-program "firefox")
      (setq browse-url-firefox-program "firefox")))
   ((executable-find "chromium")
    (progn
      (setq browse-url-browser-function #'browse-url-chromium)
      (setq browse-url-generic-program "chromium")))
   ((executable-find "vivaldi")
    (progn
      (setq browse-url-browser-function #'browse-url-chromium)
      (setq browse-url-generic-program "vivaldi")))))

server

(with-low-priority-startup
  (defun my:copy-input-and-exit ()
    "Copy the current input to the kill ring and exit."
    (interactive)
    (let ((buffer (current-buffer))
          (select-enable-clipboard t))

      (my:copy-with-system-clipboard (buffer-substring-no-properties (point-min) (point-max)))

      (or (delete-frame)
          (server-edit))

      (with-current-buffer buffer
        (let (kill-buffer-hook kill-buffer-query-functions)
          (set-buffer-modified-p 'nil)
          (kill-buffer)))))

  (define-minor-mode temporary-edit-mode
    "Temporary editing mode with server"
    :keymap (let ((map (make-sparse-keymap)))
              (keymap-set map "C-c C-y" 'my:copy-input-and-exit)
              map))

  (add-hook 'server-switch-hook #'temporary-edit-mode))

(with-low-priority-startup
  (server-start))

(with-eval-after-load 'server
  ;; serverで開いたバッファをkillする
  (setopt server-kill-new-buffers t)
  ;; COMMIT_EDITMSGも一時ファイルとして扱う
  (setopt server-temp-file-regexp "\\`/tmp/Re\\|/draft\\|COMMIT_EDITMSG\\'")
  )

dired

(declare-function 'my:dired-do-native-comp nil)
(declare-function 'my:dired-next-buffer-on-window nil)
(declare-function 'my:dired-balance nil)
(declare-function 'my:window-transient nil)
(declare-function 'dired-up-directory nil)
(declare-function 'dired-find-file nil)
(declare-function 'dired-next-line nil)
(declare-function 'dired-previous-line nil)

(with-eval-after-load 'dired
  (keymap-set dired-mode-map "N" #'my:dired-do-native-comp)
  ;; dired内でもhjklで移動できるようにしておく
  (keymap-set dired-mode-map "h" #'dired-up-directory)
  (keymap-set dired-mode-map "l" #'dired-find-file)
  (keymap-set dired-mode-map "j" #'dired-next-line)
  (keymap-set dired-mode-map "k" #'dired-previous-line)
  (keymap-set dired-mode-map "<left>" #'dired-up-directory)
  (keymap-set dired-mode-map "<right>" #'dired-find-file)
  (keymap-set dired-mode-map "<down>" #'dired-next-line)
  (keymap-set dired-mode-map "<up>" #'dired-previous-line)
  ;; 2画面ファイラっぽく、次に開いているdiredバッファに移動できるようにする
  (keymap-set dired-mode-map "<tab>" #'my:dired-next-buffer-on-window)
  (keymap-set dired-mode-map "." #'my:dired-balance)
  (keymap-set dired-mode-map "C-w" #'my:window-transient)
  ;; / でisearchできるようにする
  (keymap-set dired-mode-map "/" #'isearch-forward)
  ;; r でwdired modeに変更する
  (keymap-set dired-mode-map "r" #'wdired-change-to-wdired-mode)
  
  ;; configurations
  ;; diredでファイルをコピーする際に、コピー先をもう一つのdiredに切り替える
  (setopt dired-dwim-target t)
  (setopt dired-recursive-copies 'always)
  (setopt dired-recursive-deletes 'always)
  (setopt dired-listing-switches "-al --group-directories-first")
  ;; 標準で用意された、新規にdiredを開かないようにするための処理
  (setopt dired-kill-when-opening-new-dired-buffer t)

  (darwin!
   ;; macOSの場合、lsがcoreutilsとは別物なので、coreutils版の方を利用するように切り替える
   (setopt insert-directory-program "gls"))
  )

(with-low-priority-startup
  ;; wdired-modeに入った時点でmultistate modeにする
  (declare-function multistate-mode 'multistate)
  (add-hook 'wdired-mode-hook #'multistate-mode)
  
  (defun my:dired-do-native-comp ()
    "選択されているファイルをnative-compする"
    (interactive)
    (when-let* ((file (dired-get-filename))
                (enabled (fboundp 'native-compile-async)))
      (condition-case err
          (native-compile-async file)
        (error (dired-log "native-compile error for %s:\n%s\n" file err)))))

  (defun my:dired-next-buffer-on-window ()
    "現在のdiredバッファ以外で、かつ他のwindowに存在しているdired bufferに移動する。
対象になるバッファが無い場合は何もしない"
    (interactive)
    (when-let ((next-dired-buffer (seq-find
                                   (lambda (buf)
                                     (and (eq 'dired-mode (buffer-local-value 'major-mode buf))
                                          (not (eq (current-buffer) buf))
                                          (get-buffer-window buf)))
                                   (buffer-list))))
      (select-window (get-buffer-window next-dired-buffer))))

  (defun my:dired-balance ()
    "diredを使うにあたってよく利用する状態になるように調整する.

- 今のdired bufferが side-window用の場合は何もしない
- windowが一つしかない場合、vertical splitをする
- windowが3つ以上ある場合、2つにする
- windowが2つあるが、片方がdired bufferではない場合、current bufferを表示する
"
    (interactive)
    (unless (window-parameter (selected-window) 'window-side)
      (when (< 2 (count-windows))
        (delete-other-windows))
      (when (= 1 (count-windows))
        (split-window-horizontally))
      (let* ((current-w (get-buffer-window (current-buffer)))
             (b (seq-find (lambda (buf)
                            (let ((w2 (get-buffer-window buf)))
                              ;; side windowは対象にしない
                              (and (not (equal current-w w2))
                                   (not (window-parameter w2 'window-side)))
                              ))
                          (buffer-list)))
             (w (get-buffer-window b))
             (other-buffer-mode (buffer-local-value 'major-mode b)))
        (when (and (not (eq 'dired-mode other-buffer-mode))
                   w)
          (save-current-buffer
            (select-window w)
            (switch-to-buffer (current-buffer))
            )
          ))))
  )

uniquify

バッファ名を単一化するためのpackage。

(with-eval-after-load 'uniquify
  
  (setopt uniquify-buffer-name-style 'forward)
  (setopt uniquify-separator "/")
  (setopt uniquify-after-kill-buffer-p t)    ; rename after killing uniquified
  (setopt uniquify-ignore-buffers-re "^\\*") ; don't muck with special buffers
  )

(with-low-priority-startup
  (require 'uniquify))

shell

(with-eval-after-load 'shell
  (setopt explicit-shell-file-name "/bin/bash")
  (setopt shell-file-name "/bin/bash")
  (setq shell-command-switch "-c"))

;; Emacsからの起動であることを明示する
(setenv "EMACS" "t")

recentf

(with-eval-after-load 'recentf
  ;; 最大1000まで保存するようにする
  (setopt recentf-max-saved-items 1000)
  ;; /tmpのものはそもそも残らないようにする
  (add-to-list 'recentf-exclude "/tmp/*"))

(with-low-priority-startup
  (require 'recentf)

  (recentf-mode +1))

dabbrev

(with-low-priority-startup
  (keymap-global-set "M-/" #'dabbrev-completion)
  (keymap-global-set "C-M-/" #'dabbrevv-expand)
  )

project.el

プロジェクト管理用の各種基本的な処理を提供してくれる。projectileより機能としては少ないが、必要十分な機能はある。

(with-eval-after-load 'project
  (defun my:project-try-nodejs (dir)
    "Find a super-directory of DIR containing a package.json file."
    (let ((dir (locate-dominating-file dir "package.json")))
      (and dir (cons 'explicit dir))))

  (cl-defmethod project-root ((project (head explicit)))
    (cdr project))

  (add-hook 'project-find-functions #'my:project-try-nodejs)
  )

files

(with-eval-after-load 'files
  (setopt auto-save-default t)
  
  ;; visited-modeでは5秒ごとに変更する
  (setopt auto-save-visited-interval 5)
  ;; 5秒操作がなかったら自動保存
  (setopt auto-save-interval 5))

(with-low-priority-startup
  (require 'files)
  
  (auto-save-mode +1)
  ;; 保存するfileはbufferと同じ名前にする。globalなminor mode
  (auto-save-visited-mode +1)
  )

electric-pair

標準であるpairの挿入package。

(with-low-priority-startup
  (add-hook 'prog-mode-hook #'electric-pair-local-mode))

isearch

consult/isearchを使い分けたいので、設定する。

(with-eval-after-load 'isearch
  ;; isearchでwrapするときにdingを鳴らさない
  (setopt isearch-wrap-pause t)
  ;; 検索する方向を変えるときに、再度検索し直す
  (setopt isearch-repeat-on-direction-change t)

  ;; isearchを実行しているときにlazinessに件数をカウントする
  (setopt isearch-lazy-count nil)
  (setopt lazy-count-prefix-format "(%s/%s) ")
  (setopt lazy-count-suffix-format nil)

  ;; highlightをlazyにする
  (setopt isearch-lazy-highlight t)
  (setopt lazy-highlight-no-delay-length 4)

  (each! (;; abortだと戻ってしまうため、cancel にしている
          ("C-g" isearch-cancel)
          ;; C-hで文字の削除
          ("C-h" isearch-delete-char)
          ;; C-oでTransientを起動する
          ("C-o" my:isearch-transient)

          ;; 上下の矢印キーで履歴を上下できるようにする
          ("<up>" isearch-ring-reteat)
          ("<down>" isearch-ring-advance)

          ;; 左右の矢印キーで前後に移動できるようにする。
          ("<right>" isearch-repeat-forward)
          ("<left>" isearch-repeat-backward)
          )
    (keymap-set isearch-mode-map (car it) (cadr it))))

auto-revert

(with-eval-after-load 'auto-revert
  ;; revertの間隔は5秒としておく
  (setopt auto-revert-interval 5)
  )

(with-low-priority-startup
  (global-auto-revert-mode +1))

savehist

コマンドの実行履歴を保存する。

(with-low-priority-startup
  (load-package savehist)

  (savehist-mode +1))

globalで有効なbindの設定

(seq-do (lambda (spec)
          (keymap-global-set (car spec) (cadr spec)))
        '(
          ;; C-zはmultistate modeで使うのでnilにしておく
          ("C-z" nil)
          ;; C-hは全体的にbackspaceとして扱う
          ("C-h" backward-delete-char)
          ;; C-hの内容は一応こっちに移す
          ("M-?" help-for-help)
          ;; C-mはenterとして扱う
          ("C-m" newline-and-indent)
          ("C-x /" dabbrev-expand)
          ("C-x ," delete-region)
          ("M-;" comment-dwim)
          ("C-x C-b" ibuffer)
          ("C-/" undo)
          ;; yank-popはconsultを利用する
          ("M-y" consult-yank-pop)
          ("C-<tab>" completion-at-point)
          ("M-i" backward-paragraph)
          ("M-o" forward-paragraph)
          ("C-;" consult-buffer)
          ;; F2はflymakeの移動で使うのでdefaultの挙動は廃止しておく
          ("<f2>" nil)
          )
        )

(keymap-set read-expression-map "TAB" #'completion-at-point)

マウスに関する設定

mouseの価値を見直すことにしたので、きちんと設定することにする。

;; スクロールしているときはpointを移動しないようにする
(setopt scroll-preserve-screen-position t)

;; マウスでdragした領域はcopyする。
(setopt mouse-drag-copy-region t)

;; focusしたときのtooltipなどを削除する
(setopt mouse-highlight nil)

;; 右クリックはcontext menuなので、一回切る
(keymap-global-unset "C-<down-mouse-3>")
;; Shift + clickを使いたいので、menu表示で使われているこのキーは廃止しておく。
(keymap-global-unset "S-<down-mouse-1>")
;; 右クリックで定義に飛ぶようにするが、このままだとmenuを出すのに吸われてしまうので、unsetしておく
(keymap-global-unset "C-<down-mouse-1>")

;; mouseだけで定義に飛んだり戻ったりできるようにする
(keymap-global-set "C-<mouse-1>" #'xref-find-definitions)
(keymap-global-set "C-<mouse-3>" #'xref-go-back)

標準処理に対するadvice

(with-low-priority-startup
  (defun my:no-kill-new-duplicate (yank &optional _)
    "kill-ringにおなじ内容が保存されないようにする"
    (setq kill-ring (delete yank kill-ring)))
  (advice-add 'kill-new :before #'my:no-kill-new-duplicate)

  (defun my:no-kill-empty-only-content (f &rest args)
    "空文字列に相当する場合はkill-ringに保存しないようにする"
    (let* ((yank (car args)))
      (unless (string-blank-p yank)
        (apply f args))))
  (advice-add 'kill-new :around #'my:no-kill-empty-only-content))

OSごとの設定

(linux!
 (when (eq window-system 'x)
   (setopt select-enable-clipboard t)
   (setopt select-enable-primary nil))
 
 (when (eq window-system 'pgtk)
   (defvar my:wl-copy-process nil)
   (defun my:wl-copy (text)
     (setq my:wl-copy-process (make-process :name "wl-copy"
                                            :buffer nil
                                            :command '("wl-copy" "-f" "-n")
                                            :connection-type 'pipe
                                            :noquery t))
     (process-send-string my:wl-copy-process text)
     (process-send-eof my:wl-copy-process))
   (defun my:wl-paste ()
     (if (and my:wl-copy-process (process-live-p my:wl-copy-process))
         nil ; should return nil if we're the current paste owner
       (shell-command-to-string "wl-paste -n | tr -d \r")))

   (declare-function my:wl-copy "init.el")
   (declare-function my:wl-paste "init.el")
   
   (setq interprogram-cut-function #'my:wl-copy)
   (setq interprogram-paste-function #'my:wl-paste)))

ユーザー定義

便利関数など

(defun my:buffer-name-list ()
  "Get list of buffer name"
  (mapcar (function buffer-name) (buffer-list)))

(defun my:delete-trailing-whitespace ()
  "delete trailing whitespace if the buffer is associated
a major mode in `my:trailing-whitespace-exclude-modes'"
  (unless (seq-some (lambda (x) (eq major-mode x)) my:trailing-whitespace-exclude-modes)
    (delete-trailing-whitespace)))

(defun my:minor-mode-active-p (mode)
  "return specified minor mode is active or not"
  (let ((active-modes (cl-remove-if-not (lambda (it) (and (boundp it) (symbol-value it))) minor-mode-list)))
    (member mode active-modes)))

(defun my:copy-with-system-clipboard (str)
  "Copy passed string to system clipboard.
This function does not add `str' to the kill ring."
  (when (display-graphic-p)
    (cond
     ((eq system-type 'darwin)
      (let ((proc (make-process :name "pbcopy" :buffer nil :command '("pbcopy") :connection-type 'pipe)))
        (process-send-string proc str)
        (process-send-eof proc)
        (kill-process proc)))
     ((and (eq system-type 'gnu/linux)
           (eq window-system 'pgtk)
           (executable-find "wl-copy"))
      (let ((proc (make-process :name "wl-copy"
                                :buffer nil
                                :command '("wl-copy" "-f" "-n")
                                :connection-type 'pipe)))
        (process-send-string proc str)
        (process-send-eof proc)
        (kill-process proc)))
     (t
      nil)
     )))

treesitを利用したexpand-region的な関数

https://github.com/magnars/expand-region.el/pull/279/files

上記のPrを参考に。

(with-low-priority-startup
  (defun my:treesit-expand-region--between-node (a b)
    "`(A B)' の間に存在するnodeを取得する"
    (let ((start (min a b))
          (end (max a b)))
      (treesit-parent-until
       (treesit-node-at start)
       (lambda (node) (< end (treesit-node-end node)))))
    )

  (defun my:treesit-expand-region--parent-node ()
    "pointの位置にあるnodeの親を取得する"
    (when-let* ((node (if (region-active-p)
                          (my:treesit-expand-region--between-node (region-beginning) (region-end))
                        (treesit-node-at (point)))))
      (goto-char (treesit-node-start node))
      (set-mark (treesit-node-end node))
      (activate-mark))
    )

  (defun my:treesit-expand-region ()
    "treesitが有効な場合にexpand regionを実施する。treesitが有効ではない場合はpuniを利用する"
    (interactive)
    (if (and (functionp 'treesit-available-p)
             (treesit-available-p)
             (treesit-language-at (point))
             )
        (my:treesit-expand-region--parent-node)
      (puni-expand-region))
    ))

既存機能の拡張

kill-regionの拡張

(with-low-priority-startup
  (defun my:kill-word-or-kill-region (f &rest args)
    "kill-regionにおいて、リージョンが選択されていない場合にはbackward-kill-wardを実行するように。"
    (if (and (called-interactively-p 'interactive) transient-mark-mode (not mark-active))
        (backward-kill-word 1)
      (apply f args)))

  (advice-add 'kill-region :around 'my:kill-word-or-kill-region))

kill-lineの拡張

(with-low-priority-startup
  (defun my:kill-line-and-fixup (f &rest args)
    "kill-lineの際に、次の行の行頭に連続している空白を削除する"
    (if (and (not (bolp)) (eolp))
        (progn
          (forward-char)
          (fixup-whitespace)
          (backward-char))
      (apply f args)))

  (advice-add 'kill-line :around 'my:kill-line-and-fixup))

downcase/upcase-char

なぜかこの処理が存在しなかったので追加する。

(with-low-priority-startup
  (defun my:upcase-char ()
    "upcase current point character"
    (interactive)
    (save-excursion
      (let* ((current-point (point))
             (upcased (s-upcase (buffer-substring-no-properties current-point (1+ current-point)))))
        (replace-region-contents current-point (1+ current-point) (lambda () upcased)))))

  (defun my:downcase-char ()
    "downcase current point character"
    (interactive)
    (save-excursion
      (let* ((current-point (point))
             (downcased (s-downcase (buffer-substring-no-properties current-point (1+ current-point)))))
        (replace-region-contents current-point (1+ current-point) (lambda () downcased))))))

scratchバッファの拡張

scratchバッファはlockしておいて、対応できるようにする。

(with-low-priority-startup
  (with-current-buffer "*scratch*"
    (emacs-lock-mode 'kill)))

mode-lineを削除する

hide-mode-lineの超簡易版。対象のbufferでだけmode lineを削除する。削除したものは復元できないものとする。

(defun my:hide-mode-line ()
  "hide mode line on current buffer"
  (setq mode-line-format nil))

side window

https://www.gnu.org/software/emacs/manual/html_node/elisp/Side-Windows.html

Side windowという形で、frameの特定の側にwindowを作成することができる。

(defvar my:display-buffer-list-in-side-window nil)
(setq my:display-buffer-list-in-side-window
      `(((0 left) . ,(rx (or
                          "*completion*"
                          "*Help*"
                          "*Messages*"
                          ;; magit-staus系統はside window
                          "magit: "
                          )))
        ((0 bottom) . ,(rx (or
                            ;; deepl系統もside window
                            "*DeepL Translate*"
                            "*vterm*"
                            (regexp "[wW]arnings\\*$")
                            (regexp "[oO]utput\\*$")
                            (regexp "^\\*Flymake diagnostics"))))
        ((1 right) . ,(rx (or
                           ;; xref-referenceとかで分割されるのが結構ストレスなので
                           "*xref*"
                           )))
        ((0 right) . ,(rx (or
                           ;; eldocのbuffer
                           (regexp "^\\*eldoc.*\\*$")
                           )))
        ((1 left) . ,(rx (or
                          ;; commit messageはmagitと並ぶ格好にする
                          "COMMIT_EDITMSG")))))

(with-low-priority-startup
  (setq display-buffer-alist nil)

  (seq-do (lambda (x)
              (let* ((config-slot (caar x))
                     (config-side (cadar x))
                     (config-buffer-regexp (cdr x)))
                (add-to-list 'display-buffer-alist
                             `(,config-buffer-regexp
                               (display-buffer-in-side-window)
                               (side . ,config-side)
                               (slot . ,config-slot)
                               (dedicated . t)
                               (window-width . 0.25)
                               (window-parameters . ((no-other-window . nil) ; disable because it makes me easier to switch window
                                                     (no-delete-other-windows . t)))))
                ))
            my:display-buffer-list-in-side-window))

deepl連携

deeplと連携して、翻訳した文章をコピペするための処理を提供する。

(defcustom my:deepl-auth-key nil
  "Auth key for deepl"
  :group 'my
  :type '(string))

(defcustom my:deepl-api-host "api-free.deepl.com"
  "The host for deepl API. Use `api-free' when your plan is free."
  :type 'string
  :group 'my)

(defcustom my:deepl-send-confirmation-threshold 3000
  "Threshold of string before sending deepl"
  :type 'string
  :group 'my)

(eval-when-compile
  (elpaca request))

(with-low-priority-startup
  (load-package request))

(cl-defun my:deepl-send-string-confirm (&key _)
  "Do confirmation before sending large string to deepl."
  (y-or-n-p
   (format "It's over %d characters, do you really want to send it" my:deepl-send-confirmation-threshold)))

(cl-defun my:deepl-translate-internal (text source-lang target-lang callback)
  "Call deepl translate with confirmation."
  (when (and (> (length text) my:deepl-send-confirmation-threshold)
             (not (my:deepl-send-string-confirm)))
    (cl-return-from my:deel-translate-internal))

  (request (format "https://%s/v2/translate" my:deepl-api-host)
    :method "POST"
    :data `(
            ("auth_key" . ,my:deepl-auth-key)
            ("text" . ,text)
            ("source_lang" . ,source-lang)
            ("target_lang" . ,target-lang))
    :parser 'json-read
    :success callback))

(cl-defun my:deepl-output-message (&key data &allow-other-keys)
  "Output and kill message with temporary buffer."
  (save-excursion
    (with-temp-buffer
      (rename-buffer "*DeepL Translate*")
      (switch-to-buffer (current-buffer))
      (let ((translated-text (cdr (assoc 'text (aref (cdr (assoc 'translations data)) 0)))))
        (insert translated-text)
        (when (y-or-n-p "Use this translation?")
          (kill-new translated-text))))))

(defun my:japanese-character-p (char)
  (or (<= #x3041 char #x309f) ; hiragana
      (<= #x30a1 char #x30ff) ; katakana
      (<= #x4e01 char #x9faf) ; kanji
      ))

(defun my:deepl-translate (start end)
  "Translate region via deepl."
  (interactive "r")
  (let ((region (buffer-substring-no-properties start end)))
    ;; 3文字以上日本語が含まれている場合は日本語と判断する。
    (if (>= (cl-count-if #'my:japanese-character-p region) 3)
        (my:deepl-translate-internal region "JA" "EN" #'my:deepl-output-message)
      (my:deepl-translate-internal region "EN" "JA" #'my:deepl-output-message))))

contult

(defun my:consult-project-buffer-dwim ()
  "Consult a project buffer with DWIM (Do What I Mean) behavior.
   The selected buffer should be added to `projectile-known-projects' and
   `projectile-project-root-files'.
   When no projectile projects are found, fall back to 'consult-buffer'."
  (interactive)
  (if (and (fboundp 'project-current) (project-current))
      (consult-project-buffer)
    (consult-buffer)))

editing

主に編集で利用する関数群。s.elの利用などもするが、基本的にはmodal editingで利用するための関数がほとんどである。

(defun my:kill-whole-line-or-region ()
  "regionがあればresionを、なければ行全体をkillする"
  (interactive)
  (if (region-active-p)
      (let ((beg (region-beginning))
            (end (region-end)))
        (kill-region beg end))
    (kill-whole-line)))

navigation

移動のための関数を定義する。

(defun my:page-up ()
  "scroll-up-commandを連続して実行することを可能にするcommand.

Ref: https://github.com/xahlee/xah-fly-keys/blob/master/xah-fly-keys.el
"
  (interactive)
  (scroll-up-command)
  (set-transient-map
   (let ((kmap (make-sparse-keymap)))
     (keymap-set kmap "<up>" #'my:page-up)
     (keymap-set kmap "<down>" #'my:page-down)
     kmap)))

(defun my:page-down ()
  "scroll-down-commandを連続して実行することを可能にするcommand.

Ref: https://github.com/xahlee/xah-fly-keys/blob/master/xah-fly-keys.el
"
  (interactive)
  (scroll-down-command)
  (set-transient-map
   (let ((kmap (make-sparse-keymap)))
     (keymap-set kmap "<up>" #'my:page-up)
     (keymap-set kmap "<down>" #'my:page-down)
     kmap)))

org-mode

org用にtransientで定義していたときのような動作を簡単に定義した関数。

(defsubst my:org-get-transient-navigation-map ()
  "navigation用のtraisient-mapとして利用するkeymapを返す"
  (let ((map (make-sparse-keymap)))
    (key-layout-mapper-keymap-set map "k" 'my:org-next-visible-heading)
    (key-layout-mapper-keymap-set map "j" 'my:org-previous-visible-heading)
    (key-layout-mapper-keymap-set map "h" #'my:org-up-heading)
    map))

(defun my:org-next-heading ()
  "次のheadに移動する。連続して実行できるようになっている。"
  (interactive)
  (org-next-visible-heading)
  (set-transient-map
   (my:org-get-transient-navigation-map)))
  
(defun my:org-previous-heading ()
  "前のheadに移動する。連続して実行できるようになっている。"
  (interactive)
  (org-previous-visible-heading)
  (set-transient-map
   (my:org-get-transient-navigation-map)))

(defun my:org-up-heading ()
  "一つ上の階層に移動する"
  (interactive)
  (outline-up-heading)
  (set-transient-map
   (my:org-get-transient-navigation-map)))

init.org関連の設定

(with-eval-after-load 'org
  (defun my:tangle-init-org ()
    (when (or (string=
               (expand-file-name "init.org" user-emacs-directory)
               (buffer-file-name))
              (string=
               (expand-file-name "early-init.org" user-emacs-directory)
               (buffer-file-name)))
      (when-let* ((fname (file-name-base (buffer-file-name)))
                  (elc (seq-concatenate 'string fname ".elc"))
                  (byte-compiled-file (expand-file-name elc user-emacs-directory)))
        (when (file-exists-p byte-compiled-file)
          (delete-file byte-compiled-file)))
      
      (org-babel-tangle)))

  (add-hook 'after-save-hook #'my:tangle-init-org)
  )

テーマ

modus-themes

(eval-when-compile
  (elpaca (modus-themes :type git :host github :repo "protesilaos/modus-themes"
                        :ref "817ff75d11599b65acf583e0f8b5d69163550299")))

(defun my:modus-mode-line-override ()
  "mode lineの表示が微妙だったので調整するhook"
  (let ((line (face-attribute 'mode-line :underline)))
    (set-face-attribute 'mode-line          nil :overline line :box nil )
    (set-face-attribute 'mode-line-inactive nil :overline line :box nil :underline line)))

(with-eval-after-load 'modus-themes
  (setopt modus-themes-slanted-constructs t)
  (setopt modus-themes-bold-constructs t)
  (setopt modus-themes-mixed-fonts nil)
  (setopt modus-themes-variable-pitch-ui nil)

  (set-face-attribute 'modus-themes-completion-selected nil :inherit nil)  )

(with-low-priority-startup
  (load-package modus-themes)
  (add-hook 'modus-themes-post-load-hook #'my:modus-mode-line-override)

  ;; load-themeだとhookが動かない様子なので、一旦これを利用する
  (modus-themes-select 'modus-vivendi-tinted))

package設定

原則は、1packageにつき1見出しであり、関連するパッケージはleaf側でくくるようにする。

major-modeなどという単位は、org側のoutlineで設定するようにする。

visual packages

spacious-padding

modusの作者が開発している、window/frameの間隔を調整するためのpackage。

https://github.com/protesilaos/spacious-padding?tab=readme-ov-file

(eval-when-compile
  (elpaca (spacious-padding :ref "a3151f3c99d6b3b2d4644da88546476b3d31f0fe")))

(with-eval-after-load 'spacious-padding
  (setopt spacious-padding-widths '(
                                    :internal-border-width 15
                                    :header-line-width 4
                                    ;; 設定しているmode lineとの相性が悪いので、0にしている
                                    :mode-line-width 0
                                    :tab-width 4
                                    :right-divider-width 30
                                    :left-fringe-width 8
                                    :right-fringe-width 8
                                    :scroll-bar-width 8))
  )

(with-high-priority-startup
  (load-package spacious-padding)

  (spacious-padding-mode +1))

library packages

dash.el

(eval-when-compile
  (elpaca (dash :ref "1de9dcb83eacfb162b6d9a118a4770b1281bcd84")))

(with-low-priority-startup
  (load-package dash)
  (require 'dash))

f.el

(eval-when-compile
  (elpaca (f :ref "1e7020dc0d4c52d3da9bd610d431cab13aa02d8c")))

(with-eval-after-load 'f)

(with-low-priority-startup
  (load-package f))

s

(eval-when-compile
  (elpaca (s :ref "dda84d38fffdaf0c9b12837b504b402af910d01d")))

(with-high-priority-startup
  (load-package s)

  (require 's))

gntp

Growlに対応するprotocolを提供するpackage。

(eval-when-compile
  (elpaca (gntp :ref "767571135e2c0985944017dc59b0be79af222ef5")))

(with-high-priority-startup
  (load-package gntp))

ht

(eval-when-compile
  (elpaca (ht :ref "1c49aad1c820c86f7ee35bf9fff8429502f60fef")))

(with-low-priority-startup
  (load-package ht))

xterm-color

(eval-when-compile
  (elpaca (xterm-color :ref "2ad407c651e90fff2ea85d17bf074cee2c022912")))

(with-low-priority-startup
  (load-package xterm-color))

key-layout-mapper

自作したkey layoutを切り替えて同一の場所のキーバインドを設定できるようにするライブラリー。

(eval-when-compile
  (elpaca (key-layout-mapper :type git :host github :repo "derui/key-layout-mapper"
                             :ref "589ca0b56a7885fa8444b077a284e6e47310c8c9")))

(with-eval-after-load 'key-layout-mapper
  (key-layout-mapper-set-layout 'sturdy))

(with-low-priority-startup
  (load-package key-layout-mapper)

  (require 'key-layout-mapper))

transient

magitで使われているUIをlibraryにしたもの。

(eval-when-compile
  (elpaca (transient :type git :host github :repo "magit/transient" :branch "main"
                     :ref "3430943eaa3222cd2a487d4c102ec51e10e7e3c9")))

(with-low-priority-startup
  (load-package transient)

  (eval-when-compile
    (autoload 'transient-define-prefix "transient")))

org

(with-low-priority-startup
  (transient-define-prefix my:org-transient ()
    "Prefix for Org-mode related"
    [["Navigation"
      ("J" "Forward heading same level" org-forward-heading-same-level :transient t)
      ("K" "Backward heading same level" org-backward-heading-same-level :transient t)
      ("j" "Next heading" org-next-visible-heading :transient t)
      ("k" "Previous heading" org-previous-visible-heading :transient t)
      ("u" "Up level" outline-up-heading :transient t)
      ("l" "Change TODO state" org-cycle :transient t)
      ("h" "Org heading" consult-org-heading)
      ]
     ["Change tree status"
      ("d" "Done TODO" my:org-done-todo)
      ("n" "Toggle narrow subtree" org-toggle-narrow-to-subtree)
      ]
     ["Clock"
      ("i" "Clock-in current heading" org-clock-in)
      ("o" "Clock-out current clock" org-clock-out)]
     ]
    ))

Mark/Replace

markしたりnarrow/widenしたりするcommandをまとめたtransient.

(with-low-priority-startup
  (transient-define-prefix my:mark/replace-transient ()
    "The prefix for mark/replace related commands"
    [
     ["Narrow/Widen"
      ("n" "Narrow to region" narrow-to-region)
      ("w" "Widen" widen)
      ]
     ["Replace"
      ("r" "Replace by visual" anzu-query-replace)
      ("t" "Replace thing at point by visual" anzu-query-replace-at-cursor-thing)
      ]
     ]))

Navigation

consultなどでの、buffer/fileなどでの移動をまとめるTransient

(with-low-priority-startup
  (transient-define-prefix my:navigation-transient ()
    "The prefix for navigation via consult and other commands."
    [
     ["Consult"
      ("b" "Buffer" consult-buffer)
      ("h" "Recentf" consult-recent-file)
      ("l" "Line" consult-line)
      ("o" "Outline" consult-outline)
      ("s" "Ripgrep" consult-ripgrep)
      ("F" "Search file by Fd" consult-fd)
      ("i" "Imenu list" consult-imenu)
      ]
     ["File and directory"
      ("e" "find file" find-file)
      ("d" "Dired jump" dired-jump)
      ("f" "Find file for project" project-find-file)
      ]
     ["Search by command"
      ("R" "Find by ripgrep" ripgrep-regexp)
      ]
     ])
  )

Perspective

perspective関連のcommandをまとめるTransient.

(with-low-priority-startup
  (transient-define-prefix my:persp-transient ()
    "The prefix for perspective command."
    [
     ["Buffer navigation"
      ("b" "Switch buffer" activities-switch-buffer)
      ]
     ["Manage perspective"
      ("o" "Create and open perspective" activities-define)
      ("k" "Kill perspective" activities-kill)
      ("R" "Rename perspective" activities-rename)
      ("r" "Resume perspective" activities-resume)
      ]
     ["Move between perspectives"
      ("s" "Switch between tabs" tab-bar-switch-to-tab)
      ("h" "Switch previous workspace" tab-bar-switch-to-prev-tab)
      ("l" "Switch next workspace" tab-bar-switch-to-next-tab)
      ]
     ]))

Development

(with-low-priority-startup
  (transient-define-prefix my:development-transient ()
    "The prefix for project-related command"
    [
     ["Open Project"
      ("o" "Open project" project-switch-project)
      ]
     ["Manage Project"
      ("D" "Forget project" project-forget-project)
      ("Z" "Forget zombie projects" project-forget-zombie-projects)
      ]
     ["LSP"
      ("R" "Restart lsp" eglot)
      ("r" "Rename" eglot-rename)]
     ["Show Diagnostics"
      ("a" "Show project-wide diagnostics" flymake-show-project-diagnostics)
      ("c" "Show buffer-wide diagnostics" flymake-show-buffer-diagnostics)
      ]])
  )

Window

主にace-windowを利用する前提で設定された、Window用のtransient.

(with-low-priority-startup
  (defun my:split-window-right-and-switch-buffer ()
    "Split the current window rightwards and switch to the new window."
    (interactive)
    (delete-other-windows)
    (split-window-right)
    (other-window 1)
    (consult-buffer))

  (defun my:split-window-below-and-switch-buffer ()
    "Split the current window below and switch to the new window."
    (interactive)
    (delete-other-windows)
    (split-window-below)
    (other-window 1)
    (consult-buffer))
  
  (transient-define-prefix my:window-transient ()
    "Transient for window management"
    [
     ["Basic navigations"
      ("<return>" "Select window by key" ace-window)
      ("h" "Select left" windmove-left)
      ("j" "Select down" windmove-down)
      ("k" "Select up" windmove-up)
      ("l" "Select right" windmove-right)]
     ["Split window"
      ("s" "Split vertically" split-window-vertically)
      ("v" "Split horizontally" split-window-horizontally)
      ("S" "Split vertically and switch to other" my:split-window-below-and-switch-buffer)
      ("V" "Split vertically and switch to other" my:split-window-right-and-switch-buffer)
      ]
     ["Manipulate window"
      ("d" "Delete current window" delete-window)
      ("D" "Select and delete window" ace-delete-window)
      ("b" "Balance window" balance-windows)
      ("o" "Only current window" delete-other-windows)
      ("O" "Select and only the window" ace-delete-other-windows)]]))

Puni’s structturing editing

puniが提供するStructuring edit を継続して実行するTransient。

(with-low-priority-startup
  (transient-define-prefix my:structuring-transient ()
    "The prefix for structuring editing command"
    [
     ["Quit"
      ("q" "Quit" ignore)
      ("<escape>" "Quit" ignore)]
     ["Move with structuring"
      ("h" "backward char" backward-char :transient t)
      ("j" "Next sexp" puni-forward-sexp :transient t)
      ("k" "Previous sexp" puni-backward-sexp :transient t)
      ("l" "Forward char" forward-char :transient t)
      ("H" "Beginning of sexp" puni-beginning-of-sexp :transient t)
      ("L" "End of sexp" puni-end-of-sexp :transient t)
      ("," "Backward punct" puni-syntactic-backward-punct :transient t)
      ("." "Forward punct" puni-syntactic-forward-punct :transient t)
      ]
     ["Basic editing"
      ("D" "Kill line balanced" puni-kill-line :transient t)
      ("x" "Delete character force" (lambda () (interactive) (forward-char) (puni-force-delete)) :transient t)
      ("d" "Delete backward" puni-backward-delete-char :transient t)
      ("C-w" "Kill active region" puni-kill-active-region :transient t)
      ("u" "undo" undo :transient t)
      ("U" "redo" vundo :transient t)]
     ["Mark And yank"
      ("w" "mark and expand thing" my:treesit-expand-region :transient t)
      ("y" "yank" yank :transient t)]
     ["Useful editing"
      ("s" "Sqeeze" puni-squeeze :transient t)
      ("b" "Barf forward" puni-barf-forward :transient t)
      ("B" "Barf backward" puni-barf-backward :transient t)
      ("f" "Slurp forward" puni-slurp-forward :transient t)
      ("F" "Slurp backward" puni-slurp-backward :transient t)]
     ["Advanced editing"
      ("r" "Raise current exp" puni-raise :transient t)
      ("(" "Wrap with ())" puni-wrap-round :transient t)
      ("<" "Wrap with <>" puni-wrap-angle :transient t)
      ("[" "Wrap with []" puni-wrap-square :transient t)
      ("{" "Wrap with {}" puni-wrap-curly :transient t)
      ]]))

isearch

isearch用のTransient。基本的にisearch の中で使うことを想定している。

(with-low-priority-startup
  (transient-define-prefix my:isearch-transient ()
    "isearch menu"
    [
     ["Edit isearch string"
      ("e" "Edit the search string" isearch-edit-string :transient nil)
      ("w" "Pull next word or character from buffer" isearch-yank-word-or-char :transient nil)
      ("s" "Pull next symbol or character from buffer" isearch-yank-symbol-or-char :transient nil)
      ("l" "Pull rest of line from buffer" isearch-yank-line :transient nil)
      ("y" "Pull string from kill-ring" isearch-yank-from-kill-ring :transient nil)
      ("t" "Pull thing from buffer" isearch-forward-thing-at-point :transient nil)
      ]
     ["Replace"
      ("r" "Replace by 'query-replace'" anzu-isearch-query-replace)
      ("x" "Replace by 'query-replace-regexp'" anzu-isearch-query-replace-regexp)
      ]
     ["Misc"
      ("o" "Start occur" isearch-occur)
      ("v" "Move result with avy" avy-isearch)
      ]]
    [["Toggle"
      ("X" "Toggle regexp searching" isearch-toggle-regexp)
      ("S" "Toggle symbol searching" isearch-toggle-symbol)
      ("W" "Toggle word searching" isearch-toggle-word)
      ("F" "Toggle case-fold" isearch-toggle-case-fold)
      ("H" "Toggle isearch highlight" isearch-exit)
      ]]
    ))

LLM

ellamaを利用して作成する感じ。

(with-low-priority-startup
  (transient-define-prefix my:llm-transient ()
    "Menus with LLM"
    [
     ["Code related"
      ("c" "Complete code" ellama-code-complete :transient nil)
      ("a" "Add code" ellama-code-add :transient nil)
      ("i" "Improve code" ellama-code-improve :transient nil)
      ("e" "Edit code" ellama-code-edit :transient nil)
      ("R" "Review code" ellama-code-review :transient nil)
      ]
     ["Generic usage"
      ("s" "Summary selected content or buffer" ellama-summarize :transient nil)
      ("t" "Translate selected content" ellama-translate :transient nil)
      ]
     ["Grammar"
      ("W" "Improve wording" ellama-improve-wording :transient nil)
      ("I" "Improve grammer" ellama-improve-grammar :transient nil)
      ]]
    [["Misc"
      ("*" "Open chat" ellama-chat :transient t)
      ]]
    ))

mode-line

主にモードラインに対するパッケージをまとめている。

moody

https://github.com/tarsius/moody

magitのメインコミッターが作成しているパッケージ。基本的には見た目を変えるためだけのものであり、それ以外については自前で色々やる必要がある。

(eval-when-compile
  (elpaca (moody :type git :host github :repo "tarsius/moody"
                 :ref "2f249978531ff1ec9f601c1e8f2ce83a1b50520e")))

(with-eval-after-load 'moody
  ;; 実際にはFont sizeから導出する。
  (setopt moody-mode-line-height (let* ((font (face-font 'mode-line)))
                                   (if font
                                       (* 2 (aref (font-info font) 2))
                                     30)))
  (setopt x-underline-at-descent-line t)
  ;; macOSの場合は若干設定が異なる
  (darwin!
   (setopt moody-slant-function #'moody-slant-apple-rgb))
  )

(with-low-priority-startup
  (load-package moody))

custom mode line definition

moodyを前提にしつつ、doom-modelineを利用しないので、自前で色々設定する

(defgroup my:mode-line nil
  "Custom mode line."
  :group 'my)

(defface my:buffer-position-active-face nil
  "Face for active buffer position indicator."
  :group 'my:mode-line)

(defface my:mode-line:vc-icon-face nil
  "Face for vcs icon"
  :group 'my:mode-line)

(defvar-local my:vc-status-text ""
  "Variable to store vc status text.")

(defcustom my:mode-line-read-only-icon ""
  "variable for read only icon"
  :group 'my:mode-line)
(defcustom my:mode-line-writable-icon ""
  "variable for writable icon on mode line"
  :group 'my:mode-line)
(defcustom my:mode-line-modified-icon ""
  "variable for modified icon on mode line"
  :group 'my:mode-line)

(defun my:mode-line-status ()
  "Return status icon for mode line status.
This function uses nerd-icon package to get status icon."
  (let ((read-only (and buffer-file-name buffer-read-only))
        (modified (and buffer-file-name (buffer-modified-p))))

    (cond 
     (modified my:mode-line-modified-icon)
     (read-only my:mode-line-read-only-icon)
     (t my:mode-line-writable-icon))))

(defun my:update-mode-line-vc-text ()
  "Update vcs text is used in mode-line"
  (setq my:vc-status-text
        (cond
         ((and vc-mode buffer-file-name)
          (let* ((backend (vc-backend buffer-file-name))
                 (branch-name (if vc-display-status
                                  ;; 5 is skipped Gitx
                                  (substring vc-mode 5)
                                ""))
                 (state (cl-case (vc-state buffer-file-name backend)
                          (added "")
                          (needs-merge "")
                          (needs-update "")
                          (removed "")
                          (t ""))))
            (concat (propertize state 'face 'my:mode-line:vc-icon-face) branch-name)))
         (t "")))
  )

(defun my:mode-line-buffer-position-percentage ()
  "Return current buffer position in percentage."
  (let ((pmax (point-max))
        (current (point)))
    (format "%d%%%%" (/ (* 100 current) pmax))))

(defvar-local my:mode-line-element-region-info ""
  "cache variable for region information to show")

(defun my:update-mode-line-active-region-info ()
  "Return active region information if exists."
  (setq my:mode-line-element-region-info
        (if (region-active-p)
            (let* ((region (car (region-bounds)))
                   (lines (count-lines (car region) (cdr region)))
                   (chars (seq-length (buffer-substring-no-properties (car region) (cdr region)))))
              (format " (L%d, C%d) " lines chars)
              )
          "  "))
  (force-mode-line-update))

(run-with-idle-timer 0.5 t #'my:update-mode-line-active-region-info)

(defun my:update-mode-line-multistate ()
  "Update multistate state"
  ;; multistate--stateはinternalな状態なのだが、hookだと渡してくれたりしないため、
  ;; 自分でstoreしている。内部の情報としてはhtableなのだが、ちょっと内部的な情報すぎるので、
  ;; 一旦pcaseで自分で対処している
  (let ((lighter (pcase multistate--state
                   (`normal "N-")
                   (`insert "I-")
                   (`visual "V-")
                   (`motion-kill "MK")
                   (`motion-change "MC")
                   (`motion-yank "MY")
                   (_ "U"))))
    (setq-local my:mode-line-multistate-state lighter)))

(defvar my:mode-line-multistate-state "")

;; definitions of mode-line elements
(defvar my:mode-line-element-buffer-status '(:eval (concat (my:mode-line-status)
                                                           )))
(defvar my:mode-line-element-major-mode '(:eval (concat " " (let ((name mode-name))
                                                            (cond
                                                             ((consp name) (car name))
                                                             (t name)))
                                                      " ")))
(defvar-local my:mode-line-element-vc-mode '(:eval (moody-tab my:vc-status-text))) 
(defvar my:mode-line-element-buffer-position '(:eval (moody-ribbon
                                                      (propertize
                                                       (my:mode-line-buffer-position-percentage)
                                                       'face 'my:buffer-position-active-face)
                                                      7)))
(defvar my:mode-line-element-pomodoro '(:eval (if (featurep 'simple-pomodoro)
                                                  (simple-pomodoro-mode-line-text)
                                                ""
                                                )))
(defvar my:mode-line-element-region '(:eval my:mode-line-element-region-info))
(defvar my:mode-line-element-multistate '(:eval (format "[%s]" my:mode-line-multistate-state)))
(defvar-local my:mode-line-buffer-identification
    '(:eval (moody-tab (format "%s %s"
                               (if (featurep 'nerd-icons)
                                   (or (and (buffer-file-name)
                                            (nerd-icons-icon-for-file (buffer-file-name)))
                                       (nerd-icons-icon-for-mode major-mode))
                                 "")
                               (car (propertized-buffer-identification (buffer-name)))
                               )
                       20 'down))
  "mode line element with icon if nerd-icons ie available")

(put 'my:mode-line-buffer-identification 'risky-local-variable t) 
(put 'my:mode-line-element-buffer-status 'risky-local-variable t)
(put 'my:mode-line-element-major-mode 'risky-local-variable t)
(put 'my:mode-line-element-vc-mode 'risky-local-variable t)
(put 'my:mode-line-element-buffer-position 'risky-local-variable t)
(put 'my:mode-line-element-pomodoro 'risky-local-variable t)
(put 'my:mode-line-element-region 'risky-local-variable t)
(put 'my:mode-line-element-multistate 'risky-local-variable t)

;; define default mode line format
(defun my:init-mode-line ()
  "Initialize mode line"
  (set-face-attribute 'my:buffer-position-active-face
                      nil
                      :inherit 'mode-line
                      :foreground (modus-themes-get-color-value 'red-warmer))

  (set-face-attribute 'my:mode-line:vc-icon-face
                      nil
                      :inherit 'mode-line
                      :foreground (modus-themes-get-color-value 'fg-alt))

  ;; replace mode line elements via moody
  (moody-replace-mode-line-front-space)
  (moody-replace-mode-line-buffer-identification)
  
  (setq-default mode-line-format
                '("%e"
                  moody-mode-line-front-space
                  my:mode-line-element-multistate
                  my:mode-line-element-buffer-status
                  my:mode-line-buffer-identification
                  my:mode-line-element-region
                  mode-line-format-right-align
                  my:mode-line-element-pomodoro
                  my:mode-line-element-buffer-position
                  my:mode-line-element-vc-mode
                  my:mode-line-element-major-mode)))

(with-low-priority-startup
  (add-hook 'find-file-hook #'my:update-mode-line-vc-text)
  (add-hook 'after-save-hook #'my:update-mode-line-vc-text)
  (add-hook 'multistate-change-state-hook #'my:update-mode-line-multistate)
  
  ;; should update status text after refresh state
  (advice-add #'vc-refresh-state :after #'my:update-mode-line-vc-text)

  (my:init-mode-line))

multistate

evilからvimのkeybindを除いたようなpackage。純粋にstateとmodalの管理しかしない。

ここでのmultistateの定義としては、以下のStateのみを定義する。

  • Emacs
  • Normal
  • Visual
  • Motion

Motionについては、visualを前提とした状態でもある。motionの処理そのものは、stateとして管理はするものの、which-keyを主に利用する形となる。

(eval-when-compile
  (elpaca (multistate :type git :host github :repo "emacsmirror/multistate"
                      :ref "a7ab9dc7aac0b6d6d2f872de4e0d1b8550834a9b")))

(with-eval-after-load 'multistate
  (defun my:multistate-disable ()
    "multistateを強制的に無効化する"
    (multistate-mode -1)))

(with-low-priority-startup
  (load-package multistate)

  ;; global-modeだと特殊なmodeを全部列挙しないといけないので、prog/text/confだけに一回絞っておく
  (add-hook 'prog-mode-hook #'multistate-mode)
  (add-hook 'text-mode-hook #'multistate-mode)
  (add-hook 'conf-mode-hook #'multistate-mode))

motion

(eval-when-compile
  (elpaca (motion :type git :host github :repo "derui/motion"
                  :ref "b67044122700eb02cf18223e5df7c8f8c3541131")))

(with-low-priority-startup
  (load-package motion)

  (motion-define my:motion-buffer
      "Motion for buffer"
    :forward
    (let ((current (point)))
      (cons current (point-max)))
    :backward
    (let ((current (point)))
      (cons (point-min) current)))
  
  (motion-define my:motion-char
      "Motion for character"
    :forward
    (let ((current (point)))
      (forward-char arg)
      (cons current (point)))
    :backward
    (let ((current (point)))
      (backward-char arg)
      (cons (point) current)))
  
  (motion-define-thing my:motion-word 'word)
  (motion-define-thing my:motion-symbol 'symbol)
  (motion-define-thing my:motion-line 'line)

  (motion-define-pair my:motion-single-quote '(?' . ?'))
  (motion-define-pair my:motion-double-quote '(?\" . ?\"))
  (motion-define-pair my:motion-paren '(?\( . ?\)) t)
  (motion-define-pair my:motion-square '(?\[ . ?\]) t)
  (motion-define-pair my:motion-curly '(?{ . ?}) t)
  (motion-define-pair my:motion-angle '(?< . ?>) t))

macros

multistateで利用するためのmacroを定義する。

(eval-when-compile
  (defmacro interactive! (&rest body)
    "interactiveなlambdaを定義するためのmacro.

prefixの引数として `it' を受け取ることができる"
    `(lambda (&optional it) (interactive "p")
       ,@body)
    )

  (defmacro insert-after! (&rest body)
    "`body' を実行したあとに、insert stateに入るcommandを定義する"
    `(lambda ()
       (interactive)
       ,@body
       (multistate-insert-state)))

  (defmacro normal-after! (&rest body)
    "`body' を実行したあとに、normal stateに入るcommandを定義する"
    `(lambda ()
       (interactive)
       ,@body
       (multistate-normal-state)))

  (defmacro set-key! (keymap key fn)
    "key-layout-mapperを前提としたkey bindingを設定するためのmacro"
    `(key-layout-mapper-keymap-set ,keymap ,key ,fn)))

normal state

navigationなどを行うための基本的なStateである。非常によく利用するcommand以外などではtransientで定義したPrefixなどを適宜利用していく。

(with-eval-after-load 'multistate
  (unless (fboundp 'multistate-normal-state)
    (multistate-define-state 'normal
                             :default t
                             :lighter "N"
                             :cursor 'box
                             :parent 'multistate-suppress-map))

  ;; hook
  (declare-function corfu-quit 'corfu)
  ;; normal stateに戻って来たら補完は消す
  (add-hook 'multistate-normal-state-enter-hook #'corfu-quit)

  (set-key! multistate-normal-state-map "q" (interactive!
                                             (if (> (seq-length (window-list)) 1)
                                                 (quit-window)
                                               (previous-buffer))))
  ;; right hand definition
  ;; 右手はnavigation/selectionを前提にする
  (set-key! multistate-normal-state-map "j" #'backward-char)
  (set-key! multistate-normal-state-map "k" #'next-line)
  (set-key! multistate-normal-state-map "i" #'previous-line)
  (set-key! multistate-normal-state-map "l" #'forward-char)
  
  (set-key! multistate-normal-state-map "o" #'forward-word)
  (set-key! multistate-normal-state-map "u" #'backward-word)
  (set-key! multistate-normal-state-map ";" #'end-of-line)
  (set-key! multistate-normal-state-map "h" #'back-to-indentation)

  (set-key! multistate-normal-state-map "," #'puni-backward-sexp)
  (set-key! multistate-normal-state-map "." #'puni-forward-sexp)

  (set-key! multistate-normal-state-map "m" #'my:treesit-expand-region)
  (set-key! multistate-normal-state-map "/" #'consult-line)
  (set-key! multistate-normal-state-map "<" #'mc/mark-previous-like-this)
  (set-key! multistate-normal-state-map ">" #'mc/mark-next-like-this)
  
  ;; undo/redo
  (set-key! multistate-normal-state-map "z z" #'undo)
  (set-key! multistate-normal-state-map "z v" #'vundo)
  
  ;; advanced move
  (set-key! multistate-normal-state-map "f" #'avy-goto-char-timer)
  (set-key! multistate-normal-state-map "X" #'goto-line)
  (set-key! multistate-normal-state-map "g" #'beginning-of-buffer)
  (set-key! multistate-normal-state-map "G" #'end-of-buffer)
  (set-key! multistate-normal-state-map "H" #'scroll-down-command)
  (set-key! multistate-normal-state-map "M-H" #'scroll-other-window-down)
  (set-key! multistate-normal-state-map "L" #'scroll-up-command)
  (set-key! multistate-normal-state-map "M-L" #'scroll-other-window)

  ;; left hand definition
  ;; 左手はedit/modificationを前提にする
  (set-key! multistate-normal-state-map "x" #'kill-region)
  (set-key! multistate-normal-state-map "c" #'kill-ring-save)
  (set-key! multistate-normal-state-map "v" #'yank)
  (set-key! multistate-normal-state-map "a" #'execute-extended-command)
  (set-key! multistate-normal-state-map "f" #'multistate-insert-state)
  (set-key! multistate-normal-state-map "e" #'delete-char)
  (set-key! multistate-normal-state-map "b" #'comment-dwim)
  (set-key! multistate-normal-state-map "t" #'my:kill-whole-line-or-region)

  (set-key! multistate-normal-state-map "3" #'split-root-window-right)
  (set-key! multistate-normal-state-map "4" #'split-root-window-below)
  (set-key! multistate-normal-state-map "2" #'ace-window)
  (set-key! multistate-normal-state-map "1" #'delete-other-windows)
  
  ;; global leader key
  (set-key! multistate-normal-state-map "SPC"
            (let ((keymap (make-sparse-keymap)))
              (set-key! keymap "k" #'kill-current-buffer)
              (set-key! keymap "s" #'save-buffer)
              (set-key! keymap "o" #'find-file)
              (set-key! keymap "d" #'dired-jump)
              (set-key! keymap "m" #'magit-status)
              (set-key! keymap "i" #'ibuffer)
              (set-key! keymap "g" #'consult-ripgrep)
              (set-key! keymap "h o" #'beginning-of-buffer)
              (set-key! keymap "h l" #'end-of-buffer)
              (set-key! keymap "h <up>" #'my:page-up)
              (set-key! keymap "h <down>" #'my:page-down)
              ;; (set-key! keymap "" #'ripgrep-regexp)
              (set-key! keymap "f" #'consult-fd)
              (set-key! keymap "#" #'server-edit)
              (set-key! keymap "v" #'vterm)
              (set-key! keymap "r" #'my:mark/replace-transient)
              (set-key! keymap "/" #'my:navigation-transient)
              (set-key! keymap "." #'my:persp-transient)
              (set-key! keymap "'" #'window-toggle-side-windows)
              (set-key! keymap "b c" #'org-capture)
              (set-key! keymap "b r" #'org-roam-capture)
              (set-key! keymap "," #'my:development-transient)
              (set-key! keymap ";" #'my:consult-project-buffer-dwim)
              
              (set-key! keymap "t t" #'my:deepl-translate)
              (set-key! keymap "t e" #'eval-expression)
              (set-key! keymap "t l" #'my:llm-transient)

              ;; flymake integration
              (declare-function flymake-goto-next-error 'flymake)
              (declare-function flymake-goto-prev-error 'flymake)
              (set-key! keymap "n n" #'flymake-goto-next-error)
              (set-key! keymap "n h" #'flymake-goto-prev-error)

              ;; org-mode
              (set-key! keymap "n o k" #'my:org-next-heading)
              (set-key! keymap "n o i" #'my:org-previous-heading)
              (set-key! keymap "n o d" #'my:org-done-todo)
              (set-key! keymap "n o h" #'consult-org-heading)
              (set-key! keymap "n o c" #'org-clock-in)
              (set-key! keymap "n o t" #'org-clock-out)
              
              keymap
              )
            )
  )

insert state

nvim/vimのinsert modeに相当する。ほぼEmacs stateと変らないが、keymap-global-setを利用したくないが、一定広く使いたいkeyを定義するのを想定。

(with-eval-after-load 'multistate
  (multistate-define-state 'insert
                           :lighter "I"
                           :cursor 'bar
                           :parent 'multistate-emacs-state-map)

  ;; ESCでnormal stateに戻る
  (keymap-set multistate-insert-state-map "<escape>" #'multistate-normal-state))

magit

Gitのinterface。

https://github.com/magit/with-editor https://github.com/magit/magit

(eval-when-compile
  (elpaca (with-editor :type git :host github :repo "magit/with-editor"
                       :ref "77cb2403158cfea9d8bfb8adad81b84d1d6d7c6a"))
  (elpaca (magit :type git :host github :repo "magit/magit"
                 :ref "93e86ceca7bf5b0ff9023d7f138e5f06990fc276"))
  (elpaca (magit-section :type git :host github :repo "magit/magit"
                         :ref "93e86ceca7bf5b0ff9023d7f138e5f06990fc276"))
  )

(with-eval-after-load 'magit
  ;; magitのbuffer切り替えを変える
  (setopt magit-display-buffer-function #'display-buffer)
  ;; diff hunksをすべて表示するようにする
  (setq-default magit-diff-refine-hunk 'all)

  (defun my:insert-commit-template-on-magit ()
    "Insert commit comment template after opened commit buffer on magit."
    (tempel-insert 'cc))

  (defun my:git-post-commit--delete-EDITMSG ()
    "EDITMSGを削除する"
    (when-let* ((target-name "COMMIT_EDITMSG")
                (buffer (seq-find (lambda (buf)
                                    (let ((name (buffer-name buf)))
                                      (string-match-p name target-name)))
                                  (buffer-list))))
      (condition-case nil
          (kill-buffer buffer)
        ((debug error) nil)
        )))

  (defun my:disable-multistate-on-commit ()
    "commitではmodal editingをinsert stateにする"

    (when (and (featurep 'multistate)
               (fboundp 'multistate-insert-state))
      (multistate-insert-state)))

  (add-hook 'git-commit-post-finish-hook #'my:git-post-commit--delete-EDITMSG)
  (add-hook 'git-commit-mode-hook #'my:insert-commit-template-on-magit)
  (add-hook 'git-commit-mode-hook #'my:disable-multistate-on-commit)
  (add-hook 'git-commit-mode-hook #'my:hide-mode-line)
  (add-hook 'magit-status-mode-hook #'my:hide-mode-line)
  (add-hook 'magit-revision-mode-hook #'my:hide-mode-line)
  (add-hook 'magit-log-mode-hook #'my:hide-mode-line)
  )

(with-low-priority-startup
  (load-package with-editor)
  (load-package magit)
  (load-package magit-section))

magit-delta

https://github.com/dandavison/magit-delta/tree/master deltaをmagitのdiffとしてつかえるようにする。

(eval-when-compile
  (elpaca (magit-delta :ref "5fc7dbddcfacfe46d3fd876172ad02a9ab6ac616")))

(with-eval-after-load 'magit
  (add-hook 'magit-mode-hook #'magit-delta-mode))

(with-low-priority-startup
  (load-package magit-delta))

completion UI関連

consult

https://github.com/minad/consult swiper/counselの置き換え。

(eval-when-compile
  (elpaca (consult :ref "4889458dccf842ab6223099f8a73ff8b147e9459")))

(defun my:consult-search-dwim (&optional prefix)
  "Merge version to search document via grep/rg.
Use fast alternative if it exists, fallback grep if no alternatives in system.
    "
  (interactive "P")
  (cond
   ((executable-find "rg") (consult-ripgrep prefix))
   (t (consult-grep prefix))))

;; hotfuzz-moduleが有効な場合は、この設定がないとconsultでの検索がerrorになる場合がある
(setq consult--tofu-char #x100000)
(setq consult--tofu-range #x00fffe)

;; recent fileでpreviewする場合は明示的に実行する
(with-eval-after-load 'consult
  (setopt consult-fd-args '((if (executable-find "fdfind" 'remote) "fdfind" "fd") "--full-path --color=never -H"))
  (setopt consult-ripgrep-args
          "rg --null --line-buffered --color=never --max-columns=1000 --path-separator / --smart-case --no-heading --with-filename --line-number --hidden")

  ;; previewはC-.を押したときだけ
  (setopt consult-preview-key "C-."))

(with-low-priority-startup
  (load-package consult))

embark

https://github.com/oantolin/embark Contextに応じたアクションを実行できる、というようなもの。embark-actを実行して、そこに対して特定のキーにバインドされているアクションを実行する形。大体はembark-exportでやればよい。

B
embark-become
S
embark-collect-snapshot
L
embark-collect-live
E
embark-export

というのがデフォルトのバインディングになっている。

(eval-when-compile
  (elpaca (embark :ref "19a13e344e04bbf861eaa74491b23da52b398672"))
  (elpaca (embark-consult :ref "19a13e344e04bbf861eaa74491b23da52b398672")))

(with-low-priority-startup
  (load-package embark)
  (load-package embark-consult)

  (keymap-global-set "M-a" #'embark-act)
  (keymap-global-set "<f1> B" #'embark-bindings)
  (add-hook 'embark-collect-mode-hook #'consult-preview-at-point-mode))

embarkのアクション

やりたいことベースでメモる。

  • consultで検索した結果をoccurして一括編集
    • consult-line (C-s) → embark-export
      • C-S-aしてからすぐ E
  • consultでファイルから検索した結果を一括編集
    • consult-ripgrep (, s) → embark-export
      • C-S-aしてからすぐ E

大体はexportするとwgrep/occur-editとかができるようになる、と覚えればよし。

marginalia

https://github.com/minad/marginalia minibufferの表示に対して注釈?を追加できるパッケージ。consult/embarkそれぞれのパッケージで利用が強く推奨されているので。

注釈というか、metaという情報らしい。

(eval-when-compile
  (elpaca (marginalia :ref "50a51c69f006ec8b3ba1c570555d279d4cff6d99")))

(with-eval-after-load 'marginalia
  (add-to-list 'marginalia-prompt-categories
               '("\\<File\\>" . file)))

(with-low-priority-startup
  (load-package marginalia)
  (marginalia-mode +1)

  ;; Either bind `marginalia-cycle` globally or only in the minibuffer
  (keymap-set minibuffer-local-map "M-A" #'marginalia-cycle))

vertico

https://github.com/minad/vertico

垂直補完UIを提供することのみを目的としたUIライブラリ。

(eval-when-compile
  (elpaca (vertico :type git :host github :repo "minad/vertico"
                   :ref "c682ef50e62237435e9fc287927ce4181b49be90")))

(with-eval-after-load 'vertico
  ;; 選択時にprefix iconを表示する
  ;; https://github.com/minad/vertico/wiki#prefix-current-candidate-with-arrow
  (defvar +vertico-current-arrow t)

  (with-eval-after-load 'nerd-icons
    (cl-defmethod vertico--format-candidate :around
      (cand prefix suffix index start &context ((and +vertico-current-arrow
                                                     (not (bound-and-true-p vertico-flat-mode)))
                                                (eql t)))
      (setq cand (cl-call-next-method cand prefix suffix index start))
      (let ((arrow (nerd-icons-faicon "nf-fa-hand_o_right")))
        (if (bound-and-true-p vertico-grid-mode)
            (if (= vertico--index index)
                (concat " " arrow " " cand)
              (concat #("_" 0 1 (display " ")) cand))
          (if (= vertico--index index)
              (concat " " arrow " " cand)
            (concat "    " cand))))))

  ;; 最大20件まで表示するように
  (setopt vertico-count 20)

  ;; vertico内でdirectory 移動を効率的に行うことができるようにする
  (keymap-set vertico-map "RET" #'vertico-directory-enter)
  (keymap-set vertico-map "<backspace>" #'vertico-directory-delete-char)
  (keymap-set vertico-map "M-DEL" #'vertico-directory-delete-char)

  ;; minibufではなく標準のバッファで表示する
  (vertico-buffer-mode +1)

  ;; bufferは分割の方向が混乱してしまうときが結構あるので、bottom固定とする
  ;; side window設定もできるのだが、そうしてしまうと、window の中身がずれてしまってかなりストレスだったので、
  ;; 通常のwindow にしている
  (setopt vertico-buffer-display-action `(display-buffer-at-bottom
                                          (window-height . ,(+ 3 vertico-count))))

  (vertico-multiform-mode +1)
  (add-to-list 'vertico-multiform-categories '(jinx grid (vertico-grid-annotate . 20)))
  )

(with-low-priority-startup
  (load-package vertico)
  (vertico-mode +1))

orderless

https://github.com/oantolin/orderless completionのstyleを変更するパッケージ。基本的には空白区切りでのfilteringを提供する。

(eval-when-compile
  (elpaca (orderless :ref "416c62a4a8e7199567a5df63d03cf320dc4d6ab0")))

(defun my:orderless-migemo (component)
  (if (featurep 'migemo)
      (condition-case nil
          (let ((pattern (migemo-get-pattern component)))
            (progn (string-match-p pattern "") pattern))
        (nil nil))
    nil))

(with-eval-after-load 'orderless
  (setq completion-category-overrides
        '((command (styles orderless-default-style))
          ;; ファイルの場合には、pathの部分matchをするように
          (file (styles orderless-migemo-style))
          (org-roam-node (styles . (partial-completion orderless-migemo-style)))
          (buffer (styles orderless-migemo-style))
          (symbol (styles orderless-default-style))
          (consult-location (styles orderless-migemo-style)) ; category `consult-location'`consult-line' などに使われる
          (consult-multi (styles orderless-migemo-style)) ; category `consult-multi'`consult-buffer' などに使われる
          (unicode-name (styles orderless-migemo-style))
          (variable (styles orderless-default-style)))))

(with-low-priority-startup
  (load-package orderless)
  (require 'orderless)

  (orderless-define-completion-style orderless-default-style
    (orderless-matching-styles '(orderless-literal
                                 orderless-regexp)))

  (orderless-define-completion-style orderless-migemo-style
    (orderless-matching-styles '(orderless-literal
                                 orderless-regexp
                                 my:orderless-migemo))))

hotfuzz

https://github.com/axelf4/hotfuzz built-inのflexに似た結果を生成するが、より高速かつ、単語間の切れめなどがよりわかりやすいようになっている。

(eval-when-compile
  (elpaca (hotfuzz :type git :host github :repo "axelf4/hotfuzz" :branch "master"
                   :files ("hotfuzz.el" "hotfuzz-module.c" "CMakeLists.txt")
                   :ref "622329477d893a9fc2528a75935cfe1f8614f4bc")))

(with-low-priority-startup
  (load-package hotfuzz)

  (add-to-list 'completion-styles 'hotfuzz)
  )

corfu

https://github.com/minad/corfu minimalなregion completion。child frameを利用しているのと、あくまでシンプルなUIのみを提供しているため、軽量かつ高速。

(eval-when-compile
  (elpaca (corfu :type git :host github :repo "minad/corfu" :branch "main"
                 :ref "fa2be6c66ff2eb10b4b609832f25df49e90381af")))

(with-eval-after-load 'corfu
  (setopt corfu-cycle t)                ;; Enable cycling for `corfu-next/previous'
  (setopt corfu-auto t)                 ;; Enable auto completion
  (setopt corfu-auto-delay 0.1)                 ;; 即時表示を試してみる
  (setopt corfu-count 15)                        ;; show more candidates
  ;; 2文字の入力でcorfuを表示する
  (setopt corfu-auto-prefix 2)
  (setopt corfu-max-width 300)               ;; max width of corfu completion UI
  ;; 単独で厳密マッチしたものがあった場合でも明示的な補完アクションを要求する
  (setopt corfu-on-exact-match nil)
  ;; 最初の候補を選択しない
  (setopt corfu-preselect 'directory)
  
  (defvar corfu--index)
  (defvar corfu-magic-insert-or-next-line
    `(menu-item "" nil :filter ,(lambda (&optional _)
                                  (when (>= corfu--index 0)
                                    'corfu-insert)))
    "If we made a selection during `corfu' completion, select it.")
  (keymap-set corfu-map "RET" corfu-magic-insert-or-next-line)
  
  (defvar corfu-magic-cancel-or-backspace
    `(menu-item "" nil :filter ,(lambda (&optional _)
                                  (when (>= corfu--index 0)
                                    'corfu-reset)))
    "If we made a selection during `corfu' completion, cancel it.")
  (keymap-set corfu-map "DEL" corfu-magic-cancel-or-backspace)
  (keymap-set corfu-map "<backspace>" corfu-magic-cancel-or-backspace))

(with-low-priority-startup
  (load-package corfu)

  (add-hook 'corfu-mode-hook #'corfu-popupinfo-mode)

  (global-corfu-mode +1))

cape

https://github.com/minad/cape capf = completion-at-point-functionを極限までシンプルに拡張するための処理。corfuなどとは独立していて、あくまでcapfを拡張するだけに留まっている。

(eval-when-compile
  (elpaca (cape :ref "9110956a5155d5e3c460160fa1b4dac59322c229")))

(declare-function eglot-completion-at-point 'eglot)
(declare-function tempel-complete 'tempel)

(with-eval-after-load 'eglot
  (defun my:eglot-capf ()
    "set capf for eglot"
    (setq-local completion-at-point-functions
                (list (cape-capf-case-fold
                       (cape-capf-super
                        #'eglot-completion-at-point
                        #'tempel-complete
                        #'cape-file)))))

  (add-hook 'eglot-managed-mode-hook #'my:eglot-capf))

(with-low-priority-startup
  (load-package cape)

  ;; Add `completion-at-point-functions', used by `completion-at-point'.
  (add-to-list 'completion-at-point-functions #'tempel-complete)
  (add-to-list 'completion-at-point-functions #'cape-file)
  (add-to-list 'completion-at-point-functions #'cape-keyword))

org

org-mode本体の設定をおこなう。

(eval-when-compile
  (elpaca (org :ref "233a0ced97366090c31ef94562879bb2f729b120")))

(with-eval-after-load 'org
  ;; org-mode内部のソースを色付けする
  (setopt org-src-fontify-natively t)
  ;; org-modeの開始時に、行の折り返しを無効にする。
  (setopt org-startup-truncated t)
  ;; follow-linkから戻ることを可能とする。
  (setopt org-return-follows-link t)
  ;; 自動的にタグをalignしない
  (setopt org-auto-align-tags nil)
  ;; tagをalign するカラム
  (setopt org-tags-column 0)
  (setopt org-catch-invisible-edits 'show-and-error)
  ;; 先頭にあるstarを隠す
  (setopt org-hide-leading-stars t)
  ;; org特有のCtrl-a/eの挙動を使う
  (setopt org-special-ctrl-a/e t)
  ;; 現在のsubtreeの後にheadingを追加するようにする
  (setopt org-insert-heading-respect-content t)

  ;; UTF8にあるentitiesを利用するようにする
  (setopt org-pretty-entities t)
  ;; outlineのellipsisで使う文字を指定する
  (setopt org-ellipsis "")
  ;; refileするときにfileを使う
  (setopt org-refile-use-outline-path 'file)
  (setopt org-outline-path-complete-in-steps nil)
  ;; doneしたら時刻を記録する
  (setopt org-log-done 'time)
  ;; TODOにおける区別
  (setopt org-todo-keywords '((sequence "TODO(t)" "WAITING(w)" "|" "DONE(d)" "CANCELED(c)")))

  ;; nodeのLevel に応じたインデントは行わない
  (setopt org-adapt-indentation nil)
  ;; <の後で使えるテンプレート
  (setopt org-structure-template-alist '(("s" . "src")
                                         ("e" . "example")
                                         ("c" . "center")
                                         ("q" . "quote")
                                         ("v" . "verse")
                                         ("C" . "comment")
                                         ("E" . "export")
                                         ("l" . "src emacs-lisp")
                                         ("h" . "export html")
                                         ("a" . "export ascii")))

  ;; 余計なmodule を初期から読み込まないために空にしている
  (setopt org-modules '())

  ;; electric-pair-modeで各Syntaxをwrapできるようにする
  (modify-syntax-entry ?/ "\"" org-mode-syntax-table)
  (modify-syntax-entry ?* "\"" org-mode-syntax-table)
  (modify-syntax-entry ?= "\"" org-mode-syntax-table)
  (modify-syntax-entry ?+ "\"" org-mode-syntax-table)
  (modify-syntax-entry ?_ "\"" org-mode-syntax-table)
  (modify-syntax-entry ?~ "\"" org-mode-syntax-table)
  )

(with-low-priority-startup
  (load-package org)

  (add-hook 'org-mode-hook #'electric-pair-local-mode))

(with-eval-after-load 'org
  (org-babel-do-load-languages 'org-babel-load-languages '((plantuml . t)))
  (setq org-plantuml-jar-path (expand-file-name (locate-user-emacs-file "plantuml.jar")))
  )

(with-low-priority-startup

  (defun my:org-capture ()
    "do capture fastest"
    (interactive)
    (org-capture nil "t"))

  (defun my:org-done-todo ()
    (interactive)
    (org-todo "DONE"))

  (defun my:org-current-is-todo ()
    (string= "TODO" (org-get-todo-state)))

  (defun my:org-roam-buffer-p (&optional buffer)
    "Return boolean that current buffer is roam buffer or not"
    (with-current-buffer (or buffer (current-buffer))
      (and buffer-file-name
           (string= (expand-file-name (file-name-as-directory my:org-roam-directory))
                    (expand-file-name (file-name-directory buffer-file-name))))))

  (defun my:org-roam-project-file-p (&optional buffer)
    "Return non-nil if current buffer has any todo entry"
    (org-element-map
        (org-element-parse-buffer 'headline)
        'headline
      (lambda (e) (eq (org-element-property :todo-type e) 'todo))
      nil 'first-match))

  (defun my:org-roam-update-roam-tags (&rest tags)
    "Update filetags with TAGS list"
    (let* ((tags (combine-and-quote-strings tags " ")))
      (my:org-set-keyword "filetags" tags)))

  (defun my:org-roam-project-update-tag ()
    "Update PROJECT tag in the current buffer."
    (when (and (not (active-minibuffer-window))
               (my:org-roam-buffer-p))
      (save-excursion
        (goto-char (point-min))
        (let* ((tags (or (my:org-get-keyword "filetags") ""))
               (tags (--map (s-replace-all '(("\"" . "")) it) (s-split " " tags)))
               (original-tags tags))
          (if (my:org-roam-project-file-p)
              (setq tags (seq-uniq (cons "project" tags)))
            (setq tags (remove "project" tags)))
          (unless (equal original-tags tags)
            (apply #'my:org-roam-update-roam-tags tags))))))

  (defun my:org-roam-project-files ()
    "Return a list of note files containing 'project' tag." ;
    (seq-uniq
     (seq-map
      #'car
      (org-roam-db-query
       [:select [nodes:file]
                :from tags
                :left-join nodes
                :on (= tags:node-id nodes:id)
                :where (like tag (quote "%\"project\"%"))]))))


  (defun my:org-set-keyword (keyword value &optional buffer)
    "Add or replace VALUE of KEYWORD of org-mode to current buffer. "
    (save-excursion
      (with-current-buffer (or buffer (current-buffer))
        (let* ((org-tree (org-element-parse-buffer))
               (el (org-element-map
                       org-tree
                       'keyword
                     (lambda (el) (let ((keyword-in-el (org-element-property :key el)))
                                    (and (string-match-p keyword keyword-in-el)
                                         el)))
                     nil 'first-match)))
          (when el
            (delete-region (org-element-property :begin el) (org-element-property :end el))
            (setq org-tree (org-element-parse-buffer)))

          (let* ((first-keyword (org-element-map org-tree 'keyword #'identity nil t))
                 (el (if (not el)
                         (let* ((el (org-element-create 'keyword))
                                (el (org-element-put-property el :key keyword))
                                (el (org-element-put-property el :value value)))
                           (goto-char (1+ (org-element-property :end first-keyword)))
                           (newline)
                           (insert (org-element-interpret-data el)))
                       (org-element-put-property el :value value))))

            (goto-char (org-element-property :end first-keyword))
            (insert (org-element-interpret-data el))
            (save-buffer))))))

  (defun my:org-get-keyword (keyword &optional buffer)
    "Get KEYWORD from BUFFER or current buffer. You can use regexp or raw string for KEYWORD."
    (with-current-buffer (or buffer (current-buffer))
      (let ((el (org-element-map
                    (org-element-parse-buffer)
                    'keyword
                  (lambda (el)
                    (when (string-match-p (s-upcase keyword) (org-element-property :key el)) el)) nil 'first-match)))
        (when el
          (org-element-property :value el)))))

  (defun my:org-global-props (&optional property buffer)
    "Get the plists of global org properties of current buffer."
    (unless property (setq property "PROPERTY"))
    (with-current-buffer (or buffer (current-buffer))
      (org-element-map
          (org-element-parse-buffer)
          'keyword
        (lambda (el) (when (string-match property (org-element-property :key el)) el)))))

  (defun my:org-add-ymd-to-archive (name)
    "replace anchor to YYYY-MM string"
    (let* ((ymd (format-time-string "%Y-%m")))
      (replace-regexp-in-string "#YM" ymd name)))

  (with-eval-after-load 'org
    (advice-add 'org-extract-archive-file :filter-return #'my:org-add-ymd-to-archive))

  (add-hook 'after-save-hook #'my:org-roam-project-update-tag)
  )

;; refileする対象を指定する
(with-eval-after-load 'org
  (let ((project (expand-file-name "project.org" my:org-roam-directory)))
    (setq org-refile-targets
          `((,project :maxlevel . 1))))
  )

;; archiveするときの設定
(with-eval-after-load 'org
  (when my:org-roam-directory
    (progn
      (let ((inbox (expand-file-name "inbox.org" my:org-roam-directory)))
        (setq org-capture-templates
              `(("t" "todo" plain (file ,inbox)
                 "* TODO %?\n%U\n" :clock-resume t))))

      (defun my:org-set-archive-name-for-month (&rest args)
        (setq-local org-archive-location (concat "./archives/"
                                                 (format-time-string "%Y%m" (current-time))
                                                 "-%s_archive::datetree/* Finished Tasks")))

      (advice-add 'org-archive-subtree :before #'my:org-set-archive-name-for-month)))
  )

org-clock

(defvar my:org-clocked-time-mode-line ""
  "org-clock-inしているときのmode line")

(defun my:org-clock-out-and-save-when-exit ()
  "Save buffers and stop clocking when kill emacs."
  (when (and (fboundp 'org-clocking-p)
             (org-clocking-p))
    (org-clock-out)
    (save-some-buffers t)))

(declare-function org-clock-get-clocked-time 'org-clock)

(defvar org-clock-effort)
(defun my:task-clocked-time ()
  "format of clocked time"
  (interactive)
  (let* ((clocked-time (org-clock-get-clocked-time))
         (h (truncate clocked-time 60))
         (m (mod clocked-time 60))
         (work-done-str (format "%d:%02d" h m)))
    (if org-clock-effort
        (let* ((effort-in-minutes
                (org-duration-to-minutes org-clock-effort))
               (effort-h (truncate effort-in-minutes 60))
               (effort-m (truncate (mod effort-in-minutes 60)))
               (effort-str (format "%d:%02d" effort-h effort-m)))
          (format "%s/%s" work-done-str effort-str))
      (format "%s" work-done-str))))

(defun my:update-task-clocked-time ()
  (setq my:org-clocked-time-mode-line (my:task-clocked-time)))

(with-eval-after-load 'org-clock
  ;; 0秒は記録しない
  (setopt org-clock-out-remove-zero-time-clocks t)
  ;; frameのtitleに経過時間を表示する
  (setopt org-clock-clocked-in-display 'frame-title)
  (setopt org-clock-frame-title-format '((:eval (format "%s %s"
                                                        (if (require 'org-clock-today nil t)
                                                            (if org-clock-today-count-subtree
                                                                (format "%s / %s"
                                                                        org-clock-today-subtree-time
                                                                        org-clock-today-buffer-time)
                                                              (format "%s" org-clock-today-buffer-time))
                                                          "")
                                                        org-mode-line-string)))))

(with-low-priority-startup
  (add-hook 'org-clock-out-hook #'org-update-all-dblocks)
  (add-hook 'kill-emacs-hook #'my:org-clock-out-and-save-when-exit))

org-tempo

(with-low-priority-startup
  (with-eval-after-load 'org
    (require 'org-tempo)))

org-onit

(eval-when-compile
  (elpaca (org-onit :type git :host github :repo "takaxp/org-onit"
                    :ref  "932ed472e46c277daf1edf0efb71fbac5ff45346")))

(with-eval-after-load 'org
  (declare-function org-onit-goto-anchor 'org-onit)
  (keymap-set org-mode-map "<f11>" #'org-onit-toggle-doing)
  (keymap-set org-mode-map "S-<f11>" #'org-onit-goto-anchor)
  )

(with-low-priority-startup
  (load-package org-onit))

ox-hugo

(eval-when-compile
  (elpaca (ox-hugo :ref "c4156d9d383bf97853ba9e16271b7c4d5e697f49"))
  (elpaca (tomelr :ref "670e0a08f625175fd80137cf69e799619bf8a381")))

(defun my:org-hugo-enable-if-hugo-buffer ()
  (let ((prop (my:org-global-props "HUGO_.\+" (current-buffer))))
    (when prop
      (org-hugo-auto-export-mode +1))))

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

(with-low-priority-startup
  (load-package tomelr)
  (load-package ox-hugo)

  (add-hook 'org-mode-hook #'my:org-hugo-enable-if-hugo-buffer))

emacsql-sqlite-builtin

emacsqlのバックエンドとしてsqliteを使うが、使うsqliteとしてemacsにbuiltinされているものを使う、というやつ。

(eval-when-compile
  (elpaca (emacsql :ref "5108c16c5e1d5bfdd41fcc0807241e28886ab763")))

(with-eval-after-load 'emacsql-sqlite-builtin)

(with-low-priority-startup
  (load-package emacsql))

org-roam

https://github.com/org-roam/

Roamというtoolをorg-mode上で再現しようとしているもの。

(eval-when-compile
  (elpaca (org-roam :type git :host github :repo "org-roam/org-roam"
                    :ref  "0b9fcbc97b65b349826e63bad89ca121a08fd2be")))

(with-eval-after-load 'org-roam
  (setopt org-roam-directory my:org-roam-directory)
  (setopt org-roam-db-update-on-save t)
  (setopt org-roam-db-location my:org-roam-db-location)
  (setopt org-roam-database-connector 'sqlite-builtin)
  (setopt org-roam-capture-ref-templates '(("r" "ref" plain "%?"
                                            :if-new (file+head "%<%Y-%m-%d--%H-%M-%SZ>--${slug}.org" "#+title: ${title}\n#+filetags: \n#+roam_key: ${ref}")
                                            :unnarrowed t)))
  (setopt org-roam-capture-templates '(("d" "default" plain
                                        "%?"
                                        :if-new (file+head "%<%Y-%m-%d--%H-%M-%SZ>--${slug}.org" "#+title: ${title}\n#+filetags: \n")
                                        :unnarrowed t)))
  )

(with-eval-after-load 'org
  (keymap-set org-mode-map "C-c r" #'org-roam-node-insert)
  (keymap-set org-mode-map "C-c t" #'org-roam-tag-add)
  )

(with-low-priority-startup
  (load-package org-roam)

  (org-roam-db-autosync-mode +1))

org-modern

https://github.com/minad/org-modern

org-modeの表現をモダンなものにしてくれるパッケージ。variable pitch的な挙動になるので、結構気をつける必要がある。

(eval-when-compile
  (elpaca (org-modern :ref "e306c7df4985f77e5c4e2146900259a23a76c974")))

(with-eval-after-load 'org-modern
  (setopt org-modern-block-fringe t)
  ;; UDEV Gothicだとガタつくので、ガタつかないのと視覚的にわかりやすいものを使う
  (setopt org-modern-star 'replace)
  (setopt org-modern-replace-stars "①②③④⑤")
  (setopt org-modern-hide-stars nil)
  )

(with-low-priority-startup
  (load-package org-modern)

  (add-hook 'org-mode-hook #'org-modern-mode))

org-agenda

(when my:org-roam-directory
  (defun my:org-agenda-files-update (&rest _)
    "Update the value of `org-agenda-files'."
    (setq org-agenda-files (my:org-roam-project-files))
    (add-to-list 'org-agenda-files (expand-file-name "inbox.org" my:org-roam-directory)))

  
  (defun my:org-agenda-category (&optional len)
    "Get category of item at point for agenda.

Category is defined by one of the following items:

- CATEGORY property
- TITLE keyword
- TITLE property
- filename without directory and extension

When LEN is a number, resulting string is padded right with
spaces and then truncated with ... on the right if result is
longer than LEN.

Usage example:

  (setq org-agenda-prefix-format
        '((agenda . \" %(my:org-agenda-category) %?-12t %12s\")))

Refer to `org-agenda-prefix-format' for more information."
    (let* ((file-name (when buffer-file-name
                        (file-name-sans-extension
                         (file-name-nondirectory buffer-file-name))))
           (title (my:org-get-keyword "title"))
           (category (org-get-category))
           (result
            (or (if (and
                     title
                     (string-equal category file-name))
                    title
                  category)
                "")))
      (if (numberp len)
          (s-truncate len (s-pad-right len " " result))
        result)))
  
  (with-eval-after-load 'org-agenda
    ;; Agendaで使える拡張コマンド
    (setopt org-agenda-custom-commands '((" " "Agenda"
                                          ((tags
                                            "REFILE"
                                            ((org-agenda-overriding-header "To refile")
                                             (org-tags-match-list-sublevels nil)))
                                           (tags
                                            "PROJECT"
                                            ((org-agenda-overriding-header "To project")
                                             (org-tags-match-list-sublevels nil)))))))
    ;; 現時点を示す文字列
    (setopt org-agenda-current-time-string "  now")
    ;; 時間をくぎる文字列
    (setopt org-agenda-time-grid '((daily today require-timed)
                                   (0700 0800 0900 01000 1100 1200 1300 1400 1500 1600 1700 1800 1900 2000 2100 2200 2300 2400)
                                   "-"
                                   "────────────────"))
    ;; それぞれのprefix
    (setopt org-agenda-prefix-format '((agenda . " %i %-15(my:org-agenda-category 15)%?-12t%s")
                                       (todo . " %i %-15(my:org-agenda-category 15) ")
                                       (tags . " %i %-15(my:org-agenda-category 15) ")
                                       (search . " %i %-15(my:org-agenda-category 15) ")))

    ;; clockreportの内容
    (setopt org-agenda-clockreport-parameter-plist '(
                                                     :maxlevel 5
                                                     :block t
                                                     :tstart t
                                                     :tend t
                                                     :emphasize t
                                                     :link t
                                                     :narrow 80
                                                     :indent t
                                                     :formula nil
                                                     :level 5
                                                     :tcolumns nil
                                                     :properties ("CATEGORY")
                                                     :hidefiles t)))

  (advice-add 'org-agenda :before #'my:org-agenda-files-update)

  (keymap-global-set "C-c a" #'org-agenda))

major-modes

go-mode

(eval-when-compile
  (elpaca (go-mode :ref "6f4ff9ef874d151ed8d297a80f1bf27db5d9dbf0")))

;; go.modがある場所をrootとする
(defun my:project-find-go-module (dir)
  (when-let ((root (locate-dominating-file dir "go.mod")))
    (cons 'go-module root)))

(cl-defmethod project-root ((project (head go-module)))
  (cdr project))

(defun my:go-mode-hook-1 ()
  ;; そのバッファでのみ有効にする
  (add-hook 'project-find-functions #'my:project-find-go-module 0 t)

  (eglot-ensure))

(with-eval-after-load 'go-mode
  (add-hook 'go-mode-hook #'my:go-mode-hook-1))

(with-low-priority-startup
  (load-package go-mode))

rust-mode

Rust用のmajor mode

(defun my:find-rust-project-root (dir)
  (when-let ((root (locate-dominating-file dir "Cargo.lock")))
    (list 'vc 'Git root)))

(defun my:rust-mode-hook ()
  (setq-local project-find-functions (list #'my:find-rust-project-root))
  (eglot-ensure))

(with-eval-after-load 'rust-ts-mode
  (setopt rust-ts-indent-offset 4))

(with-low-priority-startup
  
  (add-hook 'rust-ts-mode-hook #'my:rust-mode-hook)
  
  (add-to-list 'auto-mode-alist '("\\.rs\\'" . rust-ts-mode)))

python

python mode

(defun my:python-mode-hook-0 ()
  (setq-local indent-tabs-mode nil)
  (pyvenv-mode +1))

(with-low-priority-startup
  (add-hook 'python-mode-hook #'my:python-mode-hook-0))

pyvenv

venvを利用できるようにする。実際には、その時点で利用するvenvを変更する・・・みたいなこともできるみたいだが、まぁそこまではできなくてもいいかなっていう。

(eval-when-compile
  (elpaca (pyvenv :ref "31ea715f2164dd611e7fc77b26390ef3ca93509b")))

(defun my:pyvenv-activate-hook ()
  "pyvenvを有効にする"
  (pyvenv-activate my:virtualenv-path))

(with-low-priority-startup
  (load-package pyvenv)

  (add-hook 'python-mode-hook #'my:pyvenv-activate-hook))

emacs-lisp

(defun my:emacs-lisp-hooks ()
  (setq-local completion-at-point-functions
              (list (cape-capf-case-fold
                     (cape-capf-super
                      #'tempel-complete
                      #'elisp-completion-at-point)))))

(with-low-priority-startup
  (add-hook 'emacs-lisp-mode-hook #'my:emacs-lisp-hooks)
  )

ocaml

OPAMの動作が前提なので、最初にOPAMにあるやつを読み込めるようにしておく。

(eval-and-compile
  (defun my:opam-share-directory-p ()
    (let ((opam-share (ignore-errors (car (process-lines "opam" "config" "var" "share")))))
      (and opam-share (file-directory-p opam-share))))

  (defun my:opam-load-path ()
    (let ((opam-share (ignore-errors (car (process-lines "opam" "config" "var" "share")))))
      (when (and opam-share (file-directory-p opam-share))
        (expand-file-name "emacs/site-lisp" opam-share)))))

(when (my:opam-share-directory-p)
  (add-to-list 'load-path (my:opam-load-path)))

tuareg

caml-modeよりもこちらを利用する。

(eval-when-compile
  (elpaca (tuareg :ref "1d53723e39f22ab4ab76d31f2b188a2879305092")))

(with-eval-after-load 'tuareg
  ;; Global tuareg setting
  ;; ただしインデント系統はocamlformatでフォーマットされるので、ほぼここにある設定は意味がなくなっている
  (setopt tuareg-let-always-indent t)
  (setopt tuareg-function-indent 0)
  (setopt tuareg-match-indent 0)
  (setopt tuareg-sig-struct-indent 0)
  (setopt tuareg-match-patterns-aligned t)

  ;; use ocamllsp valid in eglot
  ;; https://github.com/joaotavora/eglot/issues/525
  (put 'tuareg-mode 'eglot-language-id "ocaml"))

(with-low-priority-startup
  (load-package tuareg)

  (add-hook 'tuareg-mode-hook #'eglot-ensure))

ocaml-ts-mode

Emacs29から組み込まれたtreesitterのmoduleを前提としたもの。

(eval-when-compile
  (elpaca (ocaml-ts-mode :type git :host github :repo "dmitrig/ocaml-ts-mode"
                         :ref "bb8c86bd49e4e98f41e45fb0ec82e38f90bc3ee4")))

(with-eval-after-load 'ocaml-ts-mode
  (defvar ocaml-ts-mode-map)
  (keymap-set ocaml-ts-mode-map "C-h" #'delete-backward-char)

  ;; use ocamllsp valid in eglot
  ;; https://github.com/joaotavora/eglot/issues/525
  (put 'ocaml-ts-mode 'eglot-language-id "ocaml"))

(with-low-priority-startup
  (load-package ocaml-ts-mode)
  (add-to-list 'auto-mode-alist '("\\.ml[ily]?\\'" . ocaml-ts-mode))
  (add-to-list 'auto-mode-alist '("\\.topml\\'" . ocaml-ts-mode))

  (add-hook 'ocaml-ts-mode-hook #'eglot-ensure)
  )

lua-mode

(eval-when-compile
  (elpaca (lua-mode :ref "d074e4134b1beae9ed4c9b512af741ca0d852ba3")))

(with-low-priority-startup
  (load-package lua-mode))

markdown-mode

(eval-when-compile
  (elpaca (markdown-mode :ref "0cdebc833ed9b98baf9f260ed12b1e36b0ca0e89")))

(with-low-priority-startup
  (load-package markdown-mode))

rst

わかりづらいが、reStructuredText。

(with-low-priority-startup
  (add-to-list 'auto-mode-alist '("\\.rst\\'" . rst-mode)))

css-mode

(defun my:css-setup ()
  (add-node-modules-path)

  (rainbow-mode +1))

(with-eval-after-load 'css-mode
  (setopt css-indent-offset 2)

  (add-hook 'css-ts-mode-hook #'my:css-setup)
  )

(with-low-priority-startup
  (add-to-list 'auto-mode-alist '("\\.s?css\\'" . css-ts-mode)))

colorful-mode

文字の名前やコードに対して色をつける。CSS書く場合はないと、一部の特殊な人間以外はわけわからなくなる。 rainbow-modeに対するalternativeとして開発されている。

https://github.com/DevelopmentCool2449/colorful-mode

(eval-when-compile
  (elpaca (colorful-mode :ref "706472ea7f0ee2fe5719cd91df7315fe9ca86114")))

(with-eval-after-load 'colorful-mode)

(with-low-priority-startup
  (load-package colorful-mode))

yaml-mode

https://github.com/zkry/yaml-pro

treesitサポートも組み込んだ、非常に高機能なyaml用のmode。

(eval-when-compile
  (elpaca (yaml-pro :ref "5f06949e92dc19dcc48dc31662b2aa958fe33726"))
  (elpaca (yaml :ref "70c4fcead97e9bd6594e418c922ae769818f4245")))

(with-eval-after-load 'yaml-pro
  )

(with-low-priority-startup
  (load-package yaml)
  (load-package yaml-pro)

  (add-hook 'yaml-ts-mode-hook #'yaml-pro-ts-mode)
  (add-to-list 'auto-mode-alist '("\\.ya?ml\\'" . yaml-ts-mode)))

web-mode

jsxを使うときにたまに使う。

(eval-when-compile
  (elpaca (web-mode :ref "005aa62d6f41fbf9bc045cac3b3b772716ee8ba7")))

(defun my:web-mode-hook-angular-service ()
  "Start angular lsp server if target is component"
  (when (and
         (string-match-p "\.component\.html\\'" (or buffer-file-name "")))
    (eglot-ensure)))

(with-eval-after-load 'web-mode
  (setopt web-mode-markup-indent-offset 2)
  (setopt web-mode-code-indent-offset 2))

(with-low-priority-startup
  (load-package web-mode)

  (add-to-list 'auto-mode-alist '("\\.html\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.rt\\'" . web-mode))

  (add-hook 'web-mode-hook #'my:web-mode-hook-angular-service))

JavaScript/TypeScript

ここも色々多いので、個別に記載していく。

add-node-modules-path

node_modules/.binをexec-pathに追加してくれる。

(eval-when-compile
  (elpaca (add-node-modules-path :ref "841e93dfed50448da66c89a977c9182bb18796a1")))

(with-eval-after-load 'add-node-modules-path
  ;; npm v9でbinが削除されてしまったので、npm rootを利用するように
  ;; https://github.com/codesuki/add-node-modules-path/issues/23
  (setopt add-node-modules-path-command '("echo \"$(npm root)/.bin\"")))

(with-high-priority-startup
  (load-package add-node-modules-path))

js-mode

(with-eval-after-load 'js-mode
  (setopt js-indent-level 2)
  )

(with-low-priority-startup
  (add-to-list 'auto-mode-alist '("\\.[cm]?js\\'" . js-mode)))

typescript-mode

(with-eval-after-load 'typescript-ts-mode
  (setopt typescript-ts-mode-indent-offset 2)

  (defvar typescript-ts-mode-map)
  ;; doc commentのときなどにきちんと動くようにする
  (keymap-set typescript-ts-mode-map "M-j" #'default-indent-new-line)
  )

(with-low-priority-startup
  (add-to-list 'auto-mode-alist '("\\.m?ts\\'" . typescript-ts-mode))
  (add-to-list 'auto-mode-alist '("\\.m?tsx\\'" . typescript-ts-mode))

  (add-hook 'typescript-ts-mode-hook #'add-node-modules-path)
  (add-hook 'typescript-ts-mode-hook #'eglot-ensure))

terraform-mode

(eval-when-compile
  (elpaca (terraform-mode :ref "a645c32a8f0f0d04034262ae5fea330d5c7a33c6"))
  (elpaca (hcl-mode :ref "37f2cb1bf6fb51fbf99d4fac256298fcd6d1dd24")))

(with-low-priority-startup
  (load-package hcl-mode)
  (load-package terraform-mode))

plantuml-mode

(eval-when-compile
  (elpaca (plantuml-mode :ref "ea45a13707abd2a70df183f1aec6447197fc9ccc")))

(with-eval-after-load 'plantuml-mode
  (defvar plantuml-output-type)
  (setq plantuml-output-type "png")
  (setopt plantuml-jar-args '("-charset UTF-8"))
  (setopt plantuml-default-exec-mode 'jar)

  (let ((plantuml-jar-file (expand-file-name (locate-user-emacs-file "plantuml.jar"))))
    (setopt plantuml-jar-path plantuml-jar-file)
    (unless (file-exists-p plantuml-jar-file)
      (call-process "curl" nil nil t "-L" "-o" plantuml-jar-file
                    "https://sourceforge.net/projects/plantuml/files/plantuml.jar/download")))
  )

(with-low-priority-startup
  (load-package plantuml-mode))

protobuf-mode

;; protobuf-modeが要求しているのでここで追加している
(eval-when-compile
  (elpaca (gtags-mode :ref "2f553d0c41c470c5d2ee4210267161333969e080"))
  (elpaca (protobuf-mode :type git :host github :repo "protocolbuffers/protobuf"
                         :ref "54d8f03974c108ef8fd0f26568cd9eb086165568")))

(defconst my:protobuf-style
  '((c-basic-offset . 2)
    (indent-tabs-mode . nil)))

(defun my:protobuf-mode-hook ()
  (c-add-style "my-protobuf-style" my:protobuf-style))

(with-low-priority-startup
  (load-package gtags-mode)
  (load-package protobuf-mode)

  (add-hook 'protobuf-mode-hook #'my:protobuf-mode-hook))

fish-mode

(eval-when-compile
  (elpaca (fish-mode :ref "2526b1803b58cf145bc70ff6ce2adb3f6c246f89")))

(with-low-priority-startup
  ;; emacs-fish is repository name of recipe
  (load-package fish-mode))

text-mode

(with-eval-after-load 'text-mode
  ;; emacs 30.1以降で追加されるオプションで、これがあるとcompleption-at-point-functionsが上書きされてしまうので、
  ;; 一旦切る。これはorg modeとかでも影響する。
  (setopt text-mode-ispell-word-completion nil))

nix-mode

Nixを活用するためのmode。

(eval-when-compile
  (elpaca (nix-mode :ref "719feb7868fb567ecfe5578f6119892c771ac5e5")))

(with-eval-after-load 'nix-mode
  )

(with-low-priority-startup
  (load-package nix-mode)

  (add-hook 'nix-mode-hook #'eglot-ensure))

just-mode

https://github.com/casey/just makeの大体として利用されるjustのsyntax highlighting。

(eval-when-compile
  (elpaca (just-mode :rev "4c0df4cc4b8798f1a7e99fb78b79c4bf7eec12c1")))

(with-eval-after-load 'just-mode
  )

(with-low-priority-startup
  (load-package just-mode))

minor-modes

eldoc

(with-eval-after-load 'eldoc
  ;; idle時にdelayをかけない
  (setopt eldoc-idle-delay 0)
  ;; echo areaに複数行表示を有効にする
  (setopt eldoc-echo-area-use-multiline-p nil)
  ;; bufferを基本的に利用する
  (setopt eldoc-echo-area-prefer-doc-buffer t)
  )

(add-hook 'emacs-lisp-mode-hook #'eldoc-mode)
(add-hook 'lisp-interaction-mode-hook #'eldoc-mode)
(add-hook 'ielm-mode-hook #'eldoc-mode)

cc-mode

(defun my:c-mode-hook ()
  ;; compile-windowの設定
  (defvar compilation-buffer-name)
  (setq compilation-buffer-name "*compilation*")
  (setq compilation-scroll-output t)
  (setq compilation-read-command t)
  (setq compilation-ask-about-save nil)
  (setq compilation-window-height 10)
  (setq compile-command "make")

  (defvar c-mode-base-map)
  ;; cc-mode内で定義されるキーバインド
  (keymap-set c-mode-base-map "C-c C-c"   'comment-region)
  (keymap-set c-mode-base-map "C-c C" 'my-c++-cast)
  (keymap-set c-mode-base-map "C-c C-M-c" 'uncomment-region)
  (keymap-set c-mode-base-map "C-c e"      'c-macro-expand)
  (keymap-set c-mode-base-map "C-c c"      'my-compile)
  (keymap-set c-mode-base-map "C-c M-c"   'compilation-close)
  (keymap-set c-mode-base-map "C-c g"      'gdb)
  (keymap-set c-mode-base-map "C-c t"      'toggle-source)
  (keymap-set c-mode-base-map "C-c C-d" 'c-down-conditional)
  ;; cc-modeに入る時に自動的にgtags-modeにする
  (gtags-mode +1))

(with-low-priority-startup
  (add-to-list 'auto-mode-alist '("\\.h\\'" . c++-mode))

  (add-hook 'c-mode-common-hook #'my:c-mode-hook)
  )

ace-window

https://github.com/abo-abo/ace-window ウィンドウ間を1キーで移動できるようにするための拡張。

(eval-when-compile
  (elpaca (ace-window :ref "77115afc1b0b9f633084cf7479c767988106c196")))

(with-eval-after-load 'posframe
  (ace-window-posframe-mode +1))

(with-eval-after-load 'ace-window
  (setopt aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l)))

(with-low-priority-startup
  (load-package ace-window))

tempel

https://github.com/minad/tempel tempoに似たような構文を持つ、シンプルなテンプレートエンジン。corfuなどと効果的に組み合わせることができるようなキーバインドを提供している。

(eval-when-compile
  (elpaca  (tempel :type git :host github :repo "minad/tempel" :branch "main"
                   :ref "317c0e41d542721db11a7a8a1c6b78762959259b")))

(defvar tempel-map)
(with-eval-after-load 'tempel
  (keymap-set tempel-map "C-." #'tempel-next)
  (keymap-set tempel-map "C-," #'tempel-previous))

(with-low-priority-startup
  (load-package tempel)

  ;; tempel-completeがautoload ではないので、明示的にautoload にする
  (autoload 'tempel-complete "tempel")
  (autoload 'tempel-next "tempel")
  (autoload 'tempel-previous "tempel"))

symbol-overlay

symbolをハイライトするfaceを提供する。lsp-modeとかと見事に競合するので、lsp-modeを利用する場合はオフにするのを推奨。

(eval-when-compile
  (elpaca (symbol-overlay :ref "de215fff392c916ffab01950fcb6daf6fd18be4f")))

(with-eval-after-load 'symbol-overlay
  (set-face-attribute 'symbol-overlay-default-face nil :inherit 'highlight :underline t)
  )

(with-low-priority-startup
  (load-package symbol-overlay)

  (add-hook 'prog-mode-hook #'symbol-overlay-mode))

pulsar

よりシンプルなbeacon https://protesilaos.com/emacs/pulsar

(eval-when-compile
  (elpaca (pulsar :type git :host github :repo "protesilaos/pulsar"
                  :ref "c3d2205dc58bf55e58e58544c39495f1fe64a181")))

(with-eval-after-load 'pulsar
  (setopt pulsar-face 'pulsar-magenta)
  )

(defun my:disable-pulsar-mode ()
  "pulsar-modeを明示的に無効化する"
  (pulsar-mode -1))

(with-low-priority-startup
  (load-package pulsar)

  (pulsar-global-mode +1)
  ;; eldocが利用するspecial-modeでは意味がないので無効化しておく
  (add-hook 'special-mode-hook #'my:disable-pulsar-mode))

imenu-list

(eval-when-compile
  (elpaca (imenu-list :ref "76f2335ee6f2f066d87fe4e4729219d70c9bc70d")))

(with-eval-after-load 'imenu-list
  (setopt imenu-list-size 0.25)
  (setopt imenu-list-auto-resize nil)
  (setopt imenu-list-focus-after-activation t)
  )

(with-low-priority-startup
  (load-package imenu-list))

whick-key

(eval-when-compile
  (elpaca (which-key :ref "1e89fa000e9ba9549f15ef57abccd118d5f2fe1a")))

(with-eval-after-load 'which-key
  (setopt which-key-max-description-length 40)
  (setopt which-key-use-C-h-commands t)
  )

(with-low-priority-startup
  (load-package emacs-which-key)

  (require 'which-key))

puni

https://github.com/AmaiKinono/puni smartparensと同じような、括弧をうまく扱うためのpackage.

(eval-when-compile
  (elpaca (puni :ref "72e091ef30e0c9299dbcd0bc4669ab9bb8fb6e47")))

(with-eval-after-load 'puni)

(with-high-priority-startup
  (load-package puni)

  ;; org-mode/dired-mode/vterm-mode ではあまり意味がないので無効化する
  (add-hook 'org-mode-hook #'puni-disable-puni-mode)
  (add-hook 'vterm-mode-hook #'puni-disable-puni-mode)
  (add-hook 'dired-mode-hook #'puni-disable-puni-mode)

  (puni-global-mode +1))

diff-hl

https://github.com/dgutov/diff-hl git-gutter系統をよりシンプルにしたもの。

(eval-when-compile
  (elpaca (diff-hl :ref "b80ff9b4a772f7ea000e86fbf88175104ddf9557")))

(with-eval-after-load 'diff-hl
  ;; do not display border on fringe
  (setopt diff-hl-draw-borders nil)
  ;; 非同期で更新する
  (setopt diff-hl-update-async t))

(with-low-priority-startup
  (load-package diff-hl)

  (add-hook 'prog-mode-hook #'diff-hl-mode)
  (add-hook 'text-mode-hook #'diff-hl-mode)
  (add-hook 'prog-mode-hook #'diff-hl-flydiff-mode)
  (add-hook 'text-mode-hook #'diff-hl-flydiff-mode)

  (add-hook 'magit-pre-refresh-hook #'diff-hl-magit-pre-refresh)
  (add-hook 'magit-post-refresh-hook #'diff-hl-magit-post-refresh)
  )

flymake

(with-eval-after-load 'flymake
  (keymap-global-set "<f2>" #'flymake-goto-next-error)
  (keymap-global-set "S-<f2>" #'flymake-goto-prev-error)
  )

flymake-collection

flymakeの設定を色々一般化して作製しやすくしたpackage。collectionということなので、色々なpackageも存在している。

(eval-when-compile
  (elpaca (flymake-collection :ref "ecc15c74630fa75e7792aa23cec79ea4afc28cc2")))

(with-eval-after-load 'flymake-collection
  )

(with-low-priority-startup
  ;; flymake-collection自体には必要なものがないので、使うものを個別に指定していく
  (load-package flymake-collection))

posframe

https://github.com/tumashu/posframe

(when (and window-system my:use-posframe)
  (eval-when-compile
    (elpaca (posframe :ref "570273bcf6c21641f02ccfcc9478607728f0a2a2")))

  (when (eq window-system 'x)
    (setq x-gtk-resize-child-frames 'resize-mode))

  (with-low-priority-startup
    (load-package posframe)))

eldoc-box

https://github.com/casouri/eldoc-box

eldocをchildframeで表示するようにしてくれる。

(eval-when-compile
  (elpaca (eldoc-box :ref "d3250fccf26649f250e8678f22276f375c01aec5")))

(with-eval-after-load 'eldoc-box
  ;; 複数行の場合だけ表示するようにする
  (setopt eldoc-box-only-multi-line t)
  )

(with-low-priority-startup
  (load-package eldoc-box))

vundo

昔使ってたundo-treeの別バージョン、みたいなもの。

https://github.com/casouri/vundo

(eval-when-compile
  (elpaca (vundo :ref "ca590c571546eb1d38c855216db11d28135892f2")))

(with-low-priority-startup
  (load-package vundo))

eglot

Emacs29から標準添付になったので、これを利用してみる。

(defvar eglot-server-programs)
(defvar eglot-mode-map)

(with-eval-after-load 'eglot
  ;; 補完候補を表示するときとかにあまりにでかすぎてスローダウンしているので0にしておく
  (setopt eglot-events-buffer-config '(:size 0 :format full))

  (add-to-list 'eglot-server-programs '(((ocaml-ts-mode :language-id)) . ("ocamllsp")))
  (add-to-list 'eglot-server-programs '(nix-mode . ("nixd")))

  ;; eglotでもhotfuzzを利用するようにする
  (add-to-list 'completion-category-overrides
               '(eglot (styles hotfuzz basic)))

  (declare-function eglot-rename 'eglot)
  (declare-function eglot-code-actions 'eglot)

  (defvar eglot-mode-map)
  (keymap-set eglot-mode-map "C-c r" #'eglot-rename)
  (keymap-set eglot-mode-map "C-<return>" #'eglot-code-actions)
  (keymap-set eglot-mode-map "M-m" #'eldoc-box-help-at-point)
  )

(defun my:enable-language-base-flymake-backend ()
  "languageごとに必要なflymakeのbackendを設定する"
  (cond
   ((or (eq major-mode 'typescript-ts-mode)
        (eq major-mode 'js-ts-mode))
    (add-hook 'flymake-diagnostic-functions #'flymake-collection-eslint nil t))
   (t nil)))

(with-low-priority-startup
  (add-hook 'eglot-managed-mode-hook #'my:enable-language-base-flymake-backend)
  (add-hook 'eglot-managed-mode-hook #'eglot-booster-mode))

eglot-booster

eglotのcommunicateにおいて、Rust製のprogramを利用することで、JSONのParseに伴う諸々の性能問題を解消しようとするpackage。

(eval-when-compile
  (elpaca (eglot-booster :type git :host github :repo "jdtsmith/eglot-booster"
                         :ref "e19dd7ea81bada84c66e8bdd121408d9c0761fe6")))

(with-low-priority-startup
  (load-package eglot-booster))

aggressive-indent

(eval-when-compile
  (elpaca (aggressive-indent :ref "a437a45868f94b77362c6b913c5ee8e67b273c42")))

(with-low-priority-startup
  (load-package aggressive-indent)

  (add-hook 'lisp-mode-hook #'aggressive-indent-mode)
  (add-hook 'emacs-lisp-mode-hook #'aggressive-indent-mode)
  )

copilot.el

https://github.com/copilot-emacs/copilot.el GitHub Copilotを利用するための設定。agent の起動が非常に重いので、基本的には必要になったときにだけ有効化する。

(eval-when-compile
  (elpaca (copilot :type git :host github :repo "copilot-emacs/copilot.el" :files ("dist" "*.el")
                   :ref "535ef61e82f09d744cd5b097b1fc99f08cce175c")))

(defun my:not-completion-in-region-mode-p ()
  "Predicate to check if `completion-in-region-mode' is enabled."
  (null completion-in-region-mode))

(defun my:insert-state-p ()
  "modal editingが起動していないかどうかを返す"
  (and (fboundp 'multistate-insert-state-p)
       (multistate-insert-state-p)))

(defun my:indent-for-tab-command-dwim ()
  "必要があればindent-for-tab-commandを呼び出す"
  (interactive)
  (or (and (fboundp 'copilot-accept-completion)
           (copilot-accept-completion))
      (indent-for-tab-command)))

(defvar copilot-mode-map)
(defvar copilot-enable-predicates)
(defvar copilot-major-mode-alist)
(with-eval-after-load 'copilot
  ;; 常時やってもあまり意味がないので、タイピングが続いている間はやらないようにする
  (setopt copilot-idle-delay 0.5)
  ;; ファイルを開く度にワーニングになるのだが、実害が基本的にないので、ワーニング自体を無視しておく
  (setopt copilot-indent-offset-warning-disable t)

  ;; evilを使っていないので、evil関連のものは抜いておき、そのかわりにmodalkaのものを入れておく
  (setq copilot-enable-predicates
        '(my:insert-state-p my:not-completion-in-region-mode-p copilot--buffer-changed))

  ;; tuaregはocamlにしてもらわないと困る
  (add-to-list 'copilot-major-mode-alist '("tuareg" . "ocaml"))

  (keymap-set copilot-mode-map "<tab>" #'my:indent-for-tab-command-dwim)
  (keymap-set copilot-mode-map "TAB" #'my:indent-for-tab-command-dwim)
  )

(with-low-priority-startup
  (load-package copilot))

ellama

https://github.com/s-kostyaev/ellama/

llm.elを利用して、ollamaとのinterfaceを提供するためのpackage。

(linux!
 (eval-when-compile
   (elpaca (ellama :ref "74767cbd6dc582bd6ce99a83bc84d41bfad4b4ee")))

 (with-eval-after-load 'ellama
   (setopt ellama-language "Japanese")
   (setopt ellama-provider
           (make-llm-ollama
            :chat-model "gemma2:9b-instruct-q4_K_S"
            :embedding-model "gemma2:9b-instruct-q4_K_S"))

   ;; namingに利用するproviderとschemaを定義する
   (setopt ellama-translation-provider
           (make-llm-ollama
            :chat-model "gemma2:9b-instruct-q4_K_S"
            :embedding-model "gemma2:9b-instruct-q4_K_S")
           )
   (setopt ellama-naming-scheme 'ellama-generate-name-by-llm)
   )

 (with-low-priority-startup
   (load-package ellama)))

goggles

volatile-highlightsの代替。標準で用意されている pulse というpackageが使われている。

https://github.com/minad/goggles

(eval-when-compile
  (elpaca (goggles :ref "41d3669d7ae7b73bd39d298e5373ece48b656ce3")))

(with-low-priority-startup
  (load-package goggles)

  (add-hook 'prog-mode-hook #'goggles-mode)
  (add-hook 'text-mode-hook #'goggles-mode)

  (setq-default goggles-pulse t)
  )

jinx

https://github.com/minad/jinx

高速かつ軽量なspell checker。基本的にはispell を利用する形か。

;; macOSの場合、segfaultが発生してしまうので、一旦止めておく
(linux!
 (eval-when-compile
   (elpaca (jinx :type git :host github :repo "minad/jinx" :branch "main"
                 :ref "cd827ee199efedc8f5e094001d90206e698f91e8")))

 (with-eval-after-load 'jinx
   (setopt jinx-languages "en_US ja_JP")
   )

 (with-low-priority-startup
   (load-package jinx)

   (add-hook 'with-editor-mode-hook #'jinx-mode))
 )

avy

abo-abo氏が作成している、keyによって移動することを可能とするためのpackage。

(eval-when-compile
  (elpaca (avy :ref "be612110cb116a38b8603df367942e2bb3d9bdbe")))

(with-low-priority-startup
  (load-package avy))

wgrep

https://github.com/mhayashi1120/Emacs-wgrep grepに対応するwdired的なpackage。

(eval-when-compile
  (elpaca (wgrep :ref "208b9d01cfffa71037527e3a324684b3ce45ddc4")))

(with-low-priority-startup
  (load-package wgrep))

tabby

https://github.com/alan-w-255/tabby.el https://github.com/TabbyML/tabby

tabbyというllama-serverをラップしているserverとつなぐためplugin。

(linux!
 (eval-when-compile
   (elpaca (tabby :type git
                  :host github
                  :files ("tabby.el" "node_scripts")
                  :repo "alan-w-255/tabby.el"
                  :ref "99a00416069e6e7869225b5ea1da6c3f14fe04f6")))

 (with-eval-after-load 'tabby
   ;; multistate-insertのときだけ有効にする
   (setopt tabby-enable-predicates '(multistate-insert-state-p))
   ;; completion-in-region-mode == corfuが有効になっているときは邪魔なだけなので表示させない
   (setopt tabby-disable-display-predicates '(my:not-completion-in-region-mode-p))
   
   ;; C-jでacceptする
   (keymap-set tabby-mode-map "C-j" #'tabby-accept-completion))

 (with-low-priority-startup
   (load-package tabby)

   (add-hook 'prog-mode-hook #'tabby-mode)))

utility packages

diminish

(eval-when-compile
  (elpaca (diminish :ref  "fbd5d846611bad828e336b25d2e131d1bc06b83d")))

(with-low-priority-startup
  (load-package diminish))

nerd-icons

https://github.com/rainstormstudio/nerd-icons.el#installing-fonts

all-the-iconsの代替とのこと。all-the-iconsはターミナルでは利用できないらしいが、これはnerd-fontsにだけ依存しているので利用できるらしい。

(eval-when-compile
  (elpaca (nerd-icons :type git :host github :repo "rainstormstudio/nerd-icons.el"
                      :ref "c3d641d8e14bd11b5f98372da34ee5313636e363")))

(with-high-priority-startup
  (load-package nerd-icons))

nerd-icons-completions

minibufferでの補完時などに、nerd-iconsを使ってアイコンを表示できるようにする。

https://github.com/rainstormstudio/nerd-icons-completion

(eval-when-compile
  (elpaca (nerd-icons-completion :ref "426a1d7c29a04ae8e6ae9b55b0559f11a1e8b420")))

(with-low-priority-startup
  (load-package nerd-icons-completion)

  (add-hook 'marginalia-mode-hook #'nerd-icons-completion-marginalia-setup)
  (nerd-icons-completion-mode +1)
  )

nerd-icons-dired

diredでnerd-iconsを利用できるようにする。

https://github.com/rainstormstudio/nerd-icons-dired

(eval-when-compile
  (elpaca (nerd-icons-dired :ref "c1c73488630cc1d19ce1677359f614122ae4c1b9")))

(with-low-priority-startup
  (load-package nerd-icons-dired)

  (add-hook 'dired-mode-hook #'nerd-icons-dired-mode))

nerd-icons-corfu

nerd-icons をcorfu用に利用できるようにするためのpackage。

https://github.com/LuigiPiucco/nerd-icons-corfu?tab=readme-ov-file

(eval-when-compile
  (elpaca (nerd-icons-corfu :ref "7077bb76fefc15aed967476406a19dc5c2500b3c")))

(with-eval-after-load 'corfu
  (setopt corfu-margin-formatters '(nerd-icons-corfu-formatter)))

(with-high-priority-startup
  (load-package nerd-icons-corfu))

emojify

emojiを表示するためのpackage 。GitHubみたいな :: で挾んだような形式や、英語圏のascii artとかを対象としている。

(eval-when-compile
  (elpaca (emojify :ref "1b726412f19896abf5e4857d4c32220e33400b55")))

(with-eval-after-load 'emojify
  (setopt emojify-display-style 'unicode)
  (setopt emojify-emoji-style '(unicode github))
  )

(with-low-priority-startup
  (load-package emojify)

  (global-emojify-mode +1))

exec-path-from-shell

(eval-when-compile
  (elpaca (exec-path-from-shell :ref "72ede29a0e0467b3b433e8edbee3c79bab005884")))

(with-high-priority-startup
  (load-package exec-path-from-shell)

  (exec-path-from-shell-initialize)

  (let ((envs '("GOROOT" "GOPATH" "PATH")))
    (exec-path-from-shell-copy-envs envs)))

ripgrep.el

rg.elだとtransientとのnative compで相性が悪かったので、こっちにもどす。

(eval-when-compile
  (elpaca (ripgrep :ref "b6bd5beb0c11348f1afd9486cbb451d0d2e3c45a")))

(with-eval-after-load 'ripgrep
  (setopt ripgrep-arguments '("--smart-case"
                              "--hidden"
                              )))

(with-low-priority-startup
  (load-package ripgrep))

mozc

(when my:mozc-helper-locate
  (eval-when-compile
    (elpaca (mozc :ref "7967c42e5585d0789fe6565bf366afba8b31fcbf"))
    (elpaca (mozc-posframe :type git :host github :repo "derui/mozc-posframe"
                           :files ("mozc-posframe.el")
                           :ref "9adb3a258ff0457dfb556c78ddbb3b8c014ceb60")))

  (defvar mozc-keymap-kana)
  (defvar mozc-helper-program-name)
  (with-eval-after-load 'mozc
    ;; ここで初期化をしておかないと動かない
    (mozc-posframe-initialize)
    
    (setq mozc-keymap-kana mozc-keymap-kana-101us)
    (setopt mozc-candidate-style 'posframe)
    (setq mozc-helper-program-name my:mozc-helper-locate))

  (with-low-priority-startup
    (load-package mozc)
    (load-package mozc-posframe)))

treesit

Emacs 29からはtreesitという形でtree-sitterが組み込みで利用できるようになっている。ただしこれ、現状だと *-ts-mode という標準モードでしか利用されていないらしく、かつそっちを利用しようとするとかなり大変なことになったりが多い。 treesit自体は魅力的なのだが、font-lockの仕組みそのものが別物ということのようなので、別物として作成しないといけない雰囲気が大分する。

(with-eval-after-load 'treesit
  ;; font lockで最大のレベルを利用しておく
  (setopt treesit-font-lock-level 4))


(eval-when-compile
  (elpaca (treesit-auto :ref "016bd286a1ba4628f833a626f8b9d497882ecdf3")))

(with-eval-after-load 'treesit-auto
  ;; 対象のパーサがすでにあったら自動的にインストールしてくれる
  (setopt treesit-auto-install t)
  )

(with-low-priority-startup
  (load-package treesit-auto)

  (autoload 'treesit-auto-mode "treesit-auto")

  (add-hook 'prog-mode-hook #'treesit-auto-mode))

diredfl

diredにfont-lockを適用していい感じにしてくれる。

(eval-when-compile
  (elpaca (diredfl :ref  "f6d599c30875ab4894c1deab9713ff2faea54e06")))

(with-low-priority-startup
  (load-package diredfl)

  (add-hook 'dired-mode-hook #'diredfl-mode))

rainbow-delimiters

定番のパッケージ。括弧を階層毎に色付けしてくれる。

(eval-when-compile
  (elpaca (rainbow-delimiters :ref  "f40ece58df8b2f0fb6c8576b527755a552a5e763")))

(with-low-priority-startup
  (load-package rainbow-delimiters)

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

breadcrumb

パンくずリスト。

https://github.com/joaotavora/breadcrumb

(eval-when-compile
  (elpaca (breadcrumb :ref "dcb6e2e82de2432d8eb75be74c8d6215fc97a2d3")))

(with-eval-after-load 'breadcrumb)

(with-low-priority-startup
  (load-package breadcrumb)

  (add-hook 'prog-mode-hook #'breadcrumb-local-mode))

chokan

自作のKanzen clone。

(eval-when-compile
  (elpaca (chokan :type git :host github :repo "derui/chokan"
                  :ref "206acbfdc9709fa0392c4ad97cc263394db4ac1a"))
  (elpaca (websocket :ref "40c208eaab99999d7c1e4bea883648da24c03be3")))


(with-eval-after-load 'chokan
  (chokan-websocket-setup)
  )

(with-low-priority-startup
  (load-package websocket)
  (load-package chokan))

anzu

https://github.com/emacsorphanage/anzu

結構昔からあるVisual replace を実現するためのpackage。どっちかというと、本来はmode Line にマッチ状況を表示することが目的というかなんというか、というものだったようだ。

(eval-when-compile
  (elpaca (anzu :ref  "26fb50b429ee968eb944b0615dd0aed1dd66172c")))

(with-eval-after-load 'anzu
  ;; mode lineは自分で制御したいので、勝手にCons するのは許容しない
  (setopt anzu-cons-mode-line-p nil)
  )

(with-low-priority-startup
  (load-package anzu)

  (global-anzu-mode +1))

indent-bars

https://github.com/jdtsmith/indent-bars

indent-guideを表示するpackage 。他のmode と比較して、軽量かつ高速である、とのこと。

(eval-when-compile
  (elpaca (indent-bars :type git :host github :repo "jdtsmith/indent-bars" :branch "main"
                       :ref "e28f3321a00159cff640ac2a7eb452b5a8a2e364")))

(defun my:indent-bars-mode-dwim ()
  "treesitが有効な場合は `indent-bars-ts-mode' を起動する。treesitが
有効ではないmode の場合は `indent-bars-mode' を代りに起動する"
  (if (and (functionp 'treesit-available-p)
           (treesit-available-p))
      (or (indent-bars-ts-mode +1)
          (indent-bars-mode +1))
    (indent-bars-mode +1)))

(with-eval-after-load 'indent-bars
  (setopt indent-bars-color '(highlight :face-bg t :blend 0.2))
  (setopt indent-bars-pattern " . .")
  (setopt indent-bars-width-frac 0.15)
  
  ;; treesitをサポートする
  (setopt indent-bars-treesit-support t)

  ;; treesitterにおいて、moduleという単位ではbar を表示しない
  (setopt indent-bars-treesit-ignore-blank-lines-types '("module"))
  )

(with-low-priority-startup
  (load-package indent-bars)

  (add-hook 'yaml-ts-mode-hook #'my:indent-bars-mode-dwim)
  (add-hook 'rust-ts-mode-hook #'my:indent-bars-mode-dwim)
  (add-hook 'typescript-ts-mode-hook #'my:indent-bars-mode-dwim)
  )

multiple-cursor.el

https://github.com/magnars/multiple-cursors.el

複数のカーソルを同時にあつかうことができるpackage 。

(eval-when-compile
  (elpaca (multiple-cursors :type git :host github :repo "magnars/multiple-cursors.el" :branch "master"
                            :ref "c870c18462461df19382ecd2f9374c8b902cd804")))

(with-eval-after-load 'multiple-cursors
  (defvar mc/cmds-to-run-for-all nil)
  (setq mc/cmds-to-run-for-all '(my:treesit-expand-region
                                 puni-kill-active-region))
  )

(defun my:normal-state-after-leave-mc ()
  "multiple-cursors-modeが終ったら、normal stateに戻る"
  (when (not multiple-cursors-mode)
    (multistate-normal-state)
    ))

(with-low-priority-startup
  (load-package multiple-cursors)

  ;; multiple-cursors-modeが終了したら、normal stateに戻る
  (add-hook 'multiple-cursors-mode-hook #'my:normal-state-after-leave-mc))

vterm

https://github.com/akermu/emacs-libvterm libvtermを利用したterminal emulator package.

(eval-when-compile
  (elpaca (vterm :type git :host github :repo "akermu/emacs-libvterm" :branch "master"
                 :ref "d9ea29fb10aed20512bd95dc5b8c1a01684044b1")))

(defvar vterm-mode-map)
(with-eval-after-load 'vterm
  (keymap-set vterm-mode-map "C-o" #'window-toggle-side-windows))

(with-low-priority-startup
  (load-package vterm))

migemo

(when  (and my:migemo-command (executable-find my:migemo-command))
  (eval-when-compile
    (elpaca (migemo :ref "7d78901773da3b503e5c0d5fa14a53ad6060c97f")))

  (with-eval-after-load 'migemo
    (setopt migemo-command my:migemo-command)
    (setopt migemo-options '("-q" "--emacs"))
    (setopt migemo-dictionary my:migemo-dictionary)
    (setopt migemo-user-dictionary nil)
    (setopt migemo-regex-dictionary nil)
    (setopt migemo-coding-system 'utf-8-unix)
    ;; 遅いのを防ぐためにキャッシュする。
    (setopt migemo-use-pattern-alist t)
    (setopt migemo-use-frequent-pattern-alist t)
    (setopt migemo-pattern-alist-length 1024))

  (with-low-priority-startup
    (load-package migemo)

    (autoload 'migemo-get-pattern "migemo")
    (require 'migemo)
    (migemo-init))
  )

dmacro

dynamic macro。key sequenceをある程度自動的に判別して、dynamicにmacroとして振る舞ってくれる。

(eval-when-compile
  (elpaca (dmacro :type git :host github :repo "emacs-jp/dmacro"
                  :ref "3480b97aaad9e65fa03c6a9d1a0a8111be1179f8")))

(with-eval-after-load 'dmacro)

(with-low-priority-startup
  (load-package dmacro)

  ;; customizeなのだが、modeの定義時に使われてしまうので、setqで定義しておく必要がある
  (setq dmacro-key (kbd "C-t"))
  (global-dmacro-mode +1))

envrc

direnvをbuffer-localに適用するためのpackage。

(eval-when-compile
  (elpaca (envrc :type git
                 :ref "2316e004c1574234fe4d991bd75a254cdeaa83ae")))

(with-eval-after-load 'envrc)

(defun my:disable-envrc-mode ()
  "envrc-modeを明示的に無効化する"
  (envrc-mode -1))

(with-low-priority-startup
  (load-package envrc)

  (envrc-global-mode +1)

  (add-hook 'special-mode-hook #'my:disable-envrc-mode))

SKK

ddskk

(defvar skk-user-directory (expand-file-name "skk" user-emacs-directory))
(leaf ddskk
  :straight t
  :if nil
  ;; ddskkは (provide 'skk) されているので、skkでrequireするようにする
  :commands skk-mode
  :bind (("<Hangul>" . my:enable-japanese-input)
         ("<henkan>" . my:enable-japanese-input)
         ("<f13>" . my:enable-japanese-input)
         ("<Hangul_Hanja>" . my:disable-japanese-input)
         ("<muhenkan>" . my:disable-japanese-input)
         ("C-<f13>" . my:disable-japanese-input))
  :preface
  (defun my:enable-japanese-input ()
    (interactive)
    (set-input-method my:input-method))

  (defun my:disable-japanese-input ()
    (interactive)
    (set-input-method nil))
  :init
  (setq default-input-method my:input-method
        skk-init-file (expand-file-name "init-ddskk.el" user-emacs-directory))

  (defun my:disable-skk-modeline-force-change (old-func &rest r)
    "そのままだとmode lineのフォーマットが勝手に変わってしまって非常に面倒なことになるため、
起動する瞬間だけ該当の処理をスキップする。
"
    (setq skk-status-indicator 'minor-mode)
    (apply old-func r)
    (setq skk-status-indicator 'left))

  (advice-add #'skk-mode-invoke :around  #'my:disable-skk-modeline-force-change))

ddskk-posframe

(leaf ddskk-posframe
  :straight t
  :if nil
  :global-minor-mode t)

yaskkserv2の設定

(leaf *skk-server
  :after f
  :if nil
  :init
  (let ((server-program (expand-file-name "yaskkserv2"  my:user-local-exec-path))
        (dictionary-program (expand-file-name "yaskkserv2_make_dictionary" my:user-local-exec-path)))
    (cond ((and my:build-skkserver
                (executable-find "cargo")
                (not (executable-find server-program))
                (not (executable-find dictionary-program)))
           (let ((base-path "/tmp/yaskkserv2"))
             (unless (f-exists? base-path)
               (call-process "git" nil nil t  "clone" "https://github.com/wachikun/yaskkserv2" "/tmp/yaskkserv2"))
             (call-process "cargo" nil nil t "build" "--release" "--manifest-path" (expand-file-name "Cargo.toml" base-path))
             (unless (f-exists? server-program)
               (f-copy (expand-file-name "target/release/yaskkserv2" base-path) server-program))
             (unless (f-exists? dictionary-program)
               (f-copy (expand-file-name "target/release/yaskkserv2_make_dictionary" base-path) dictionary-program))
             ))
          (t
           (let* ((target (cond ((eq window-system 'ns) "apple-darwin")
                                (t "uknown-linux-gnu")))
                  (path (format "https://github.com/wachikun/yaskkserv2/releases/download/%s/yaskkserv2-%s-x86_64-%s.tar.gz" my:yaskkserv2-version my:yaskkserv2-version target)))
             (call-process "curl" nil nil t "-L" path "-o" "/tmp/yaskkserv2.tar.gz")
             (call-process "tar" nil nil t "-zxvf" "/tmp/yaskkserv2.tar.gz" "-C" my:user-local-exec-path "--strip-components" "1"))))))

tab-bar

tab barを有効化する。

;; faceなどの定義まで行うために先頭で有効化しておく。
(tab-bar-mode +1)

(defface my:tab-bar-separator-face `((t (
                                         :weight light
                                         :height 1.2
                                         :background ,(face-attribute 'tab-bar-tab :background)
                                         :box (:line-width (12 . 8) :color nil :style flat-button)
                                         :inherit tab-bar
                                         )))
  "My tab separator face")

(defface my:tab-bar-inactive-separator-face `((t (:inherit my:tab-bar-separator-face)))
  "My tab separator face for inactive tab")

;; modus-themeが適用されることを前提とした動作になっているので、modus-themesを前提にする
(with-eval-after-load 'modus-themes
  (defvar my:tab-bar-format-function #'tab-bar-tab-name-format-default
    "formatting function to display tab name")

  (defvar my:tab-bar-face-function #'tab-bar-tab-face-default
    "Get face by tab")

  (defun my:tab-suffix ()
    "Empty suffix of tab."
    " ")

  ;; tab-barのstyleをmodusに適合するようにする
  (setq modus-themes-common-palette-overrides
        '((bg-tab-bar bg-active)
          (bg-tab-current bg-main)
          (bg-tab-other bg-active)))

  (with-eval-after-load 'tab-bar
    (setopt tab-bar-close-button-show nil)
    (setopt tab-bar-auto-width nil)

    ;; modus-themeに適合させつつ、modern-tab-barライクなstyleにする
    (set-face-attribute 'tab-bar nil
                        :box '(:line-width (12 . 8) :color nil :style flat-button)
                        :weight 'light)

    (defun my:tab-name-format-function (name tab i)
      "Tab nameの周辺にSpaceをいれるためのfunction"
      (let* ((separator-face (if (eq (car tab) 'current-tab)
                                 'my:tab-bar-separator-face
                               'my:tab-bar-inactive-separator-face)))
        (concat
         (propertize " "
                     'face separator-face)
         name
         (propertize " "
                     'face separator-face)
         )))

    (setopt tab-bar-format '(tab-bar-format-tabs my:tab-suffix))
    ;; 末尾に追加することで、セパレーターを調整する
    (add-to-list 'tab-bar-tab-name-format-functions #'my:tab-name-format-function t)
    (setopt tab-bar-separator ""))
  )

(defun my:tab-bar-face-change ()
  "tab-barで利用するfaceをloadしたthemeに合致させる"
  (set-face-attribute 'my:tab-bar-inactive-separator-face nil
                      :background (modus-themes-get-color-value 'bg-button-active)
                      :box `(:line-width (12 . 8) :color ,(modus-themes-get-color-value 'bg-button-active) :style flat-button)
                      )

  (set-face-attribute 'my:tab-bar-separator-face nil
                      :background (face-attribute 'tab-bar-tab :background)
                      :box `(:line-width (12 . 8) :color nil :style flat-button)
                      )
  )

(add-hook 'modus-themes-post-load-hook #'my:tab-bar-face-change)

activities.el

https://github.com/alphapapa/activities.el

KDE PlasmaにあるActivityをEmacsに持ってきたもの、という感じであるらしい。tab-bar modeとのintegrationが追加されているため、そのままで利用できる。

(eval-when-compile
  (elpaca (activities :rev "a341ae21c4ef66879fdfbc1260b9094b5bb60e9f")))

(with-eval-after-load 'activities
  ;; 終了するときには全体を保存するようにする。
  (add-hook 'kill-emacs-hook #'activities-save-all)
  )

(with-low-priority-startup
  (load-package activities)

  (activities-tabs-mode +1))

input method

(defun my:enable-japanese-input ()
  (interactive)
  (set-input-method my:input-method))

(defun my:disable-japanese-input ()
  (interactive)
  (set-input-method nil))

(setq default-input-method my:input-method)

(with-low-priority-startup
  (seq-each (lambda (v)
              (keymap-global-set (car v) (cadr v)))
            '(
              ("<Hangul>" my:enable-japanese-input)
              ("<henkan>" my:enable-japanese-input)
              ("<f13>" my:enable-japanese-input)
              ("<Hangul_Hanja>" my:disable-japanese-input)
              ("<muhenkan>" my:disable-japanese-input)
              ("C-<f13>" my:disable-japanese-input)
              )))

dashboard

(eval-when-compile
  (elpaca (dashboard :ref "3852301f9c6f3104d9cc98389612b5ef3452a7de")))

(with-eval-after-load 'dashboard
  (declare-function dashboard-modify-heading-icons 'dashboard)
  
  (dashboard-modify-heading-icons '((recents . "nf-oct-file")
                                    (projects . "nf-oct-project")
                                    (agenda . "nf-oct-calendar")))
  (setopt dashboard-display-icons-p t)
  (setopt dashboard-set-heading-icons t)
  (setopt dashboard-set-file-icons t)
  (setopt dashboard-icon-type 'nerd-icons)
  (setopt dashboard-vertically-center-content t)

  (setopt dashboard-startup-banner 'ascii)
  (setopt dashboard-set-navigator t)
  (setopt dashboard-set-init-info t)
  (setopt dashboard-items '((recents . 15)
                            (projects . 5)
                            (agenda . 5)))
  (setopt dashboard-banner-ascii "
  ____
 |  _ \\  ___ _ __ _   _  ___ _ __ ___   __ _  ___ ___
 | | | |/ _ \\ '__| | | |/ _ \\ '_ ` _ \\ / _` |/ __/ __|
 | |_| |  __/ |  | |_| |  __/ | | | | | (_| | (__\\__ \\
 |____/ \\___|_|   \\__,_|\\___|_| |_| |_|\\__,_|\\___|___/
")
  )

(with-eval-after-load 'diminish
  (diminish 'dashboard-mode))

(with-low-priority-startup
  (load-package dashboard))

footer

restore magic file

最後にmagic file を有効化する。

(with-low-priority-startup
  (setq file-name-handler-alist my-saved-file-name-handler-alist))

GCの設定

#x10000000 = 256MiB を閾値としておく。これはLSPの対策のため。

(with-low-priority-startup
  (setq gc-cons-threshold #x10000000)
  (setq gc-cons-percentage 0.5)
  (setq garbage-collection-messages t)
  ;; font cacheのcompact化を抑制する
  (setq inhibit-compacting-font-caches t))

provide

(provide 'init)