16
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

emacsのライティング環境整備 2026 春

16
Last updated at Posted at 2026-03-02

はじめに

年末にemacsのライティング環境整備(2025-2026)を書いてから、少し時間をかけてinit.elファイルの見直しを行いました。
見直しに際し、留意した点は以下のとおりです:

  • 長年放置していたパッケージの扱いを、可能な限りuse-packageに統一する
  • 使用頻度が低いパッケージについては、遅延読み込み設定を徹底する
  • 設定やパッケージの読み込みの順番について、整合性を持たせる
  • 新たな便利関数を追加する
  • 自前関数の命名の見直しを行う
  • ショートカットキーの整合性について見直しを行う
    • prefix Ctrl-z: 使用頻度の高い外部パッケージと自前関数にあてる
    • prefix Ctrl-t: 主にorg-modeとdenote関連に当てる

以前の投稿と同様、このinit.elの特徴は以下のとおりです:

  • コメントを含め1,500行程度で、導入パッケージもそれほど多くありません
  • 文系の研究者や翻訳者に便利なパッケージ(電子辞書、翻訳、pdf、epubリーダーなど)の設定と関数定義が含まれています
    • 複雑な数式やチャートを書く機会が少ないため、LaTex環境は考慮していません
  • 全般にProtesilaos Stavroumodus系のテーマやdenoteの作者で、キプロスの哲学者/詩人/プログラマ)の影響を強く受けています
  • webや書籍で得たtipsを拝借した箇所も多いのですが、多くは引用元を辿れないため(いわゆる鰻のタレ状態)、特別なケースを除き、ソースへの参照は断念しました(偉大な先人に感謝します)
  • 設定や記法に問題があるかもしれませんが(少なくともわたくしの環境では)初期化および運用時に重大なエラーは発生していません

長くなりますが、みなさまの参考になれば幸いです。

Linux環境

現在のメイン執筆環境は以下のとおりです:

🌜 fastfetch
██████████████████  ████████    xxx@t430
██████████████████  ████████    -------
██████████████████  ████████    OS: Manjaro Linux x86_64
██████████████████  ████████    Host: xxxxxxx (ThinkPad T430)
████████            ████████    Kernel: Linux 6.12.73-1-MANJARO
████████  ████████  ████████    Uptime: 17 hours, 55 mins
████████  ████████  ████████    Packages: 2068 (pacman)[stable]
████████  ████████  ████████    Shell: zsh 5.9
████████  ████████  ████████    Display (SEC324C): 1600x900 in 14", 60 Hz [Built-in]
████████  ████████  ████████    DE: Xfce4 4.20
████████  ████████  ████████    WM: Xfwm4 (X11)
████████  ████████  ████████    WM Theme: WhiteSur-Dark-solid-nord
████████  ████████  ████████    Theme: Fusion [Qt], WhiteSur-Dark-solid-nord [GTK2/3/4]
████████  ████████  ████████    Icons: Papirus-Dark [Qt], Papirus-Dark [GTK2/3/4]
                                Font: Noto Sans CJK JP (8pt, Meium) [Qt], Noto Sans CJK JP (8pt, Medium) [GTK2/3/4]
                                Cursor: volantes
                                Terminal: tmux 3.6a
                                CPU: Intel(R) Core(TM) i5-3320M (4) @ 3.30 GHz
                                GPU: Intel 3rd Gen Core processor Graphics Controller @ 1.20 GHz [Integrated]
                                Memory: 2.34 GiB / 15.31 GiB (15%)
                                Swap: 0 B / 4.00 GiB (0%)
                                Disk (/): 84.08 GiB / 439.05 GiB (19%) - ext4
                                Disk (/mnt/Medium): 170.50 GiB / 439.54 GiB (39%) - ext4
                                Local IP (enp0s25): 192.168.xxx.xxx/24
                                Battery (45N1015): 74% [AC Connected]
                                Locale: ja_JP.UTF-8
  • ご覧の通りロースペックPCを10年来使用していますが、快適に動作しています(時間のかかるコンパイルだけはデスクトップ機に任せています)

init.el

ここから設定を書いていきます。

実行パスや環境変数、および特定のアプリケーションを呼び出すパッケージの設定については、ご自身の環境に合わせて適宜変更してください。

0. emacs起動中のGCの最適化

;;; === INITIAL OPTIMIZATION ===
(setq gc-cons-threshold most-positive-fixnum)
(setq read-process-output-max (* 1024 1024)) ;; 1MB

1. パッケージの初期設定

;; パッケージのアーカイブ
(setq package-archives
      '(
        ("melpa" . "https://melpa.org/packages/")
        ("gnu" . "https://elpa.gnu.org/packages/")
        ("nongnu" . "https://elpa.nongnu.org/nongnu/")
        ))

;; use-packageを使用
(require 'use-package)

;; インストール時に不要な警告を出さないようにする
(add-to-list 'display-buffer-alist
             '("\\`\\*\\(Warnings\\|Compile-Log\\)\\*\\'"
               (display-buffer-no-window)
               (allow-no-window . t)))

;; 自作パッケージのパスを追加
(add-to-list 'load-path "~/.emacs.d/mypac/")

;; 組み込みパッケージの更新
(setq package-install-upgrade-built-in t)

;; emacs本体のorg.elとのコンフリクト回避のため、先に読み込ませる
(use-package org
  :ensure t
  :demand t)

2. 日本語環境

  • Ctrl-oで日本語入力を切り替え
  • mozcの変換候補はエコーエリアで表示
;; 言語環境はutf-8で統一
(setenv "LANG" "ja_JP.UTF-8")
(set-language-environment 'Japanese)
(set-default-coding-systems 'utf-8)
(set-terminal-coding-system 'utf-8)
(setq default-file-name-coding-system 'utf-8)
(setq default-process-coding-system '(utf-8 . utf-8))
(prefer-coding-system 'utf-8)


;; MOZCの設定
(use-package mozc
  :ensure t
  :demand t
  :bind ("C-o" . toggle-input-method)
  :config
  (setq default-input-method "japanese-mozc")
  (setq mozc-candidate-style 'echo-area)
  (setq mozc-helper-program-name "mozc_emacs_helper")
  (setq mozc-leim-title "🍙 "))

3. 全般的な挙動

;; 自動生成される設定はcustom.elで管理
(setq custom-file (locate-user-emacs-file "custom.el"))
(load custom-file :no-error-if-file-is-missing)

;; 右から左に読む言語に対応させないことで描画高速化
(setq-default bidi-display-reordering nil)

;; 同じ内容を履歴に記録しないようにする
(setq history-delete-duplicates t)

;; C-u C-SPC C-SPC ...で過去のマークを遡る
(setq set-mark-command-repeat-pop t)

;; filename<dir> 形式のバッファ名にする
(setq uniquify-buffer-name-style 'post-forward-angle-brackets)
(setq uniquify-ignore-buffers-re "[^*]+")

;; ロックファイルを作成しない
(setq create-lockfiles nil)

;; バックアップファイルの保存先
(setq backup-directory-alist '((".*" . "~/Medium/backup_original/local_data")))

;; 初期メッセージ無効化
(setq initial-scratch-message "")

;; 保存時にファイルの最後に常に改行を自動的に追加
(setq-default require-final-newline t)

;; インデントにはスペースを使用
(setq-default indent-tabs-mode nil)
(setq-default tab-width 2)

;; Warningの表示レベル
(setq warning-minimum-level :emergency)
(setq warning-suppress-types '((comp)))

;; ナローイングとワイドニング
(put 'upcase-region 'disabled nil)
(put 'narrow-to-region 'disabled nil)

;; ログの記録行数を増やす
(setq message-log-max 10000)

;; 履歴の記録数を増やす
(setq history-length 1000)

;; 警告音をオフにする
(setq ring-bell-function 'ignore)

;; yes/noをy/nに短縮
(setq use-short-answers t)

;; 他プロセスの編集をバッファに反映
(global-auto-revert-mode +1)

;; 選択状態で入力したときに、region領域を削除して挿入
(delete-selection-mode 1)

;; C-x C-bでibufferを使う
(global-set-key [remap list-buffers] 'ibuffer)

;; C-gをより使いやすく(Protesilaosのtipsを参照)
(defun prot/keyboard-quit-dwim ()
  (interactive)
  (cond
   ((region-active-p)
    (keyboard-quit))
   ((derived-mode-p 'completion-list-mode)
    (delete-completion-window))
   ((> (minibuffer-depth) 0)
    (abort-recursive-edit))
   (t
    (keyboard-quit))))

(global-set-key (kbd "C-g") 'prot/keyboard-quit-dwim)

;; GCをgcmhに任せる
(use-package gcmh
  :ensure t
  :demand t
  :config
  (setq gcmh-idle-delay 10)
  (setq gcmh-high-cons-threshold (* 100 1024 1024))
  (gcmh-mode 1))

;; 実行パスの設定(zshの設定を認識しないケースがあるため)
(use-package exec-path-from-shell
  :ensure t
  :config
  (when (memq window-system '(mac ns x))
    (setq exec-path-from-shell-variables '("PATH" "MANPATH"))
    (exec-path-from-shell-initialize))
  (add-to-list 'exec-path (expand-file-name "~/.local/bin"))
  (setenv "PATH" (concat (expand-file-name "~/.local/bin") ":" (getenv "PATH")))
  (add-to-list 'exec-path (expand-file-name "~/.cargo/bin"))
  (setenv "PATH" (concat (expand-file-name "~/.cargo/bin") ":" (getenv "PATH"))))

;; 最後のカーソル位置を保存
(use-package saveplace
  :ensure nil
  :init
  (save-place-mode))

;; 削除したファイルをゴミ箱に移動させる
(setopt delete-by-moving-to-trash t)

;; キルリングに新しいテキストを追加する前にクリップボードの内容を保存
(setopt save-interprogram-paste-before-kill t)

;; ediff使用時に垂直分割する
(use-package ediff
  :ensure t
  :config
  (setopt ediff-split-window-function 'split-window-horizontally))

4. 環境依存の設定

  • X11デスクトップ環境で、クリップボード共有にはxclipを使用
  • Wayland環境ではXWaylandを有効にすること
;; Shell設定
(setq explicit-shell-file-name "/usr/bin/zsh")

;; xclipを使ったクリップボードとの連携
(unless window-system
  (when (getenv "DISPLAY")
    (defun xclip-cut-function (text &optional push)
      (with-temp-buffer
	      (insert text)
	      (call-process-region (point-min) (point-max) "xclip" nil 0 nil "-i" "-selection" "clipboard")))
    (defun xclip-paste-function()
      (let ((xclip-output (shell-command-to-string "xclip -o -selection clipboard")))
	      (unless (string= (car kill-ring) xclip-output)
          xclip-output )))
    (setq interprogram-cut-function 'xclip-cut-function)
    (setq interprogram-paste-function 'xclip-paste-function)
    (setopt save-interprogram-paste-before-kill t)))

;; xterm mouseのサポート
(use-package mouse
  :ensure nil)
(xterm-mouse-mode t)

5. キー割り当てと便利な関数の導入

自分の指癖とコマンドの使用頻度に応じて設定しているため、そのままコピペするとオリジナル環境との整合性が崩れます

;; C-zをprefixキーに割り当てる
(defvar my-keys-prefix-map (make-sparse-keymap)
  "My personal keybindings prefix map.")

(define-key global-map (kbd "C-z") my-keys-prefix-map)

;; C-tをもう一つのprefixキーに割り当てる(主にdenote関連のコマンド向け)
(defvar my-keys-prefix-map-bis (make-sparse-keymap)
  "My personal keybindings prefix map bis.")

(define-key global-map (kbd "C-t") my-keys-prefix-map-bis)

;; C-hでBackspace
(global-set-key (kbd "C-h") 'delete-backward-char)

;; 選択行のコピー(> 29.1: Ctrl+uで回数指定)
(global-set-key (kbd "C-c j") #'duplicate-dwim)

;; 画面の再描画
(global-set-key (kbd "<f7>") 'redraw-display)

;; C-x fをアンバインド(入力ミスが多いため)
(global-unset-key (kbd "C-x f"))

;; カーソル位置の前方を削除
(global-set-key (kbd "C-c u") '(lambda () (interactive) (kill-line 0)))

;; undoキーを追加(C-zを2回続けて入力)
(define-key my-keys-prefix-map (kbd "C-z") 'undo)

;; 日付の挿入(2025-12-22 mon 11:37 という形式)
(defun insert-current-time()
  (interactive)
  (insert (format-time-string "%Y-%m-%d %a %H:%M" (current-time))))

(define-key my-keys-prefix-map (kbd "C-d") 'insert-current-time)

;; denoteタイプの日付を挿入(20251222T114027 という形式)
(defun insert-denote-time()
  "Insert denote-type datetime"
  (interactive)
  (insert (format-time-string "%Y%m%dT%H%M%S" (current-time))))

(define-key my-keys-prefix-map-bis (kbd "C-d") 'insert-denote-time)

;; 絵文字の挿入
(define-key my-keys-prefix-map (kbd "C-e") 'emoji-insert)

;; kill-sexp (C-M-k: S式を削除)を追加
(define-key my-keys-prefix-map (kbd "k") 'kill-sexp)

;; 現在のバッファを消去
(define-key my-keys-prefix-map (kbd "x") 'kill-current-buffer)

;; カーソル位置からバッファの終端まで削除
(defun my-kill-from-point-to-end ()
  "Kill text from the current point to the end of the buffer."
  (interactive)
  (kill-region (point) (point-max))
  (message "Killed from point to end of buffer"))

(global-set-key (kbd "C-c k") 'my-kill-from-point-to-end)

;; カーソル位置から先頭まで削除
(defun my-kill-from-point-to-beginning ()
  "Kill text from the current point to the beginning of the buffer."
  (interactive)
  (kill-region (point) (point-min))
  (message "Killed from point to beginning of buffer"))

(global-set-key (kbd "C-c K") 'my-kill-from-point-to-beginning)

;; 行末の空白文字を削除
(global-set-key (kbd "C-c t") 'delete-trailing-whitespace)

;; カーソルの位置の行をコピー
(defun copy-whole-line ()
  "Copy the current entire line to the kill-ring."
  (interactive)
  (kill-ring-save (line-beginning-position) (line-end-position))
  (message "Line copied"))

(global-set-key (kbd "C-c w") 'copy-whole-line)

;; 入力キーをそのまま挿入
(define-key global-map (kbd "C-c q") #'quoted-insert)

;; F1をヘルプのプレフィックスキーとして設定
(define-key global-map (kbd "<f1>") 'help-command)

;; ** F12で自分用のヘルプドキュメントを読み込み専用で開く
(defun my-open-help-emacs-md ()
  "Open emacs help."
  (interactive)
  (find-file-read-only "~/Documents/DB/Help/emacs.md"))

(defun my-open-help-emacs-org ()
  "Open org-mode help."
  (interactive)
  (find-file-read-only "~/Documents/DB/Help/emacs_org.md"))

(defun my-open-help-emacs-pdf ()
  "Open emacs-pdf help."
  (interactive)
  (find-file-read-only "~/Documents/DB/Help/emacs_pdf.md"))

(defun my-open-help-emacs-regex ()
  "Open emacs-regex help."
  (interactive)
  (find-file-read-only "~/Documents/DB/Help/emacs_regex.md"))

(global-set-key (kbd "<f12> e") #'my-open-help-emacs-md)
(global-set-key (kbd "<f12> o") #'my-open-help-emacs-org)
(global-set-key (kbd "<f12> p") #'my-open-help-emacs-pdf)
(global-set-key (kbd "<f12> r") #'my-open-help-emacs-regex)

;; 選択範囲で一時バッファを生成
(defun my-create-temp-buffer-from-region (start end)
  "Create a temporary, unsaved buffer with the content of the selected region."
  (interactive "r")
  (let* ((region-text (buffer-substring-no-properties start end))
         (temp-buffer (generate-new-buffer "*temp-region*")))
    (switch-to-buffer temp-buffer)
    (insert region-text)
    (message "Copied region to temporary buffer: %s" (buffer-name temp-buffer))))

(global-set-key (kbd "C-z y") 'my-create-temp-buffer-from-region)

;; Ctrl+PageDownで次のタブへ移動
(global-set-key (kbd "C-<next>") #'tab-next)

;; Ctrl+PageUpで前のタブへ移動
(global-set-key (kbd "C-<prior>") #'tab-previous)

;; クエリ置換のキーを追加(M-%が打ちにくいので)
(define-key my-keys-prefix-map (kbd "C-r") 'query-replace)

;; 現在のバッファを完全削除
(defun my-delete-current-buffer-file ()
  "現在のバッファに関連付けられたファイルを削除し、バッファを閉じます。"
  (interactive)
  (let ((filename (buffer-file-name)))
    (if (and filename (file-exists-p filename))
        (when (or (not (buffer-modified-p))
                  (yes-or-no-p "未保存の変更があります。変更を破棄して削除処理を進めますか? "))
          (when (yes-or-no-p (format "本当にファイルを削除しますか? : %s " filename))
            (delete-file filename)
            (kill-buffer (current-buffer))
            (message "ファイルを削除し、バッファを閉じました。")))
      (message "このバッファはファイルに関連付けられていないか、ファイルが存在しません。"))))

(global-set-key (kbd "C-z C-k") 'my-delete-current-buffer-file)

6. 補完系パッケージ

  • consult + corfu + vertico + marginalia + orderless + embark + recentf + capeという組み合わせです
  • 日本語検索の補完のためにmigemoを導入しました
  • 各パッケージとも最小限の設定にとどめています
;; == vertico ==
(use-package vertico
  :ensure t
  :config
  (setq vertico-cycle t)
  (setq vertico-resize nil)
  (vertico-mode 1)
  (savehist-mode 1)
  (setq vertico-count 20))


;; == marginalia ==
(use-package marginalia
  :ensure t
  :config
  (marginalia-mode 1))


;; == embark ==
(use-package embark
  :ensure t
  :bind
  (("M-." . embark-act)
   ("C-c e" . embark-dwim)
   ("C-c E" . embark-bindings))
  :init
  (setq prefix-help-command #'embark-prefix-help-command)
  :config
  (add-to-list 'display-buffer-alist
               '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*"
                 nil
                 (window-parameters (mode-line-format . none)))))


;; == embark-consult ==
(use-package embark-consult
  :ensure t ; only need to install it, embark loads it after consult if found
  :hook
  (embark-collect-mode . consult-preview-at-point-mode))


;; == migemo ==
(use-package migemo
  :ensure t
  :init
  (setopt migemo-command "cmigemo")
  (setopt migemo-options '("-q" "--emacs"))
  ;; Migemo辞書のパス設定
  (setopt migemo-dictionary
          "/usr/share/migemo/utf-8/migemo-dict")
  (setopt migemo-user-dictionary nil)   ; ユーザー辞書は使用しない
  (setopt migemo-regex-dictionary nil)  ; 正規表現辞書は使用しない
  (setopt migemo-coding-system 'utf-8-unix) ; 辞書のエンコーディング
  :config
  (migemo-init))


;; == orderless ==
(use-package orderless
  :ensure t
  :config
  (defun orderless-migemo (component)
    (let ((pattern (migemo-get-pattern component)))
      (condition-case nil
          (progn (string-match-p pattern "") pattern)
        (invalid-regexp nil))))
  (setq orderless-matching-styles
        '(orderless-literal  ; そのままの文字列
          orderless-regexp   ; 正規表現
          orderless-migemo)) ; Migemo変換
  (setq completion-styles '(orderless basic)
        completion-category-defaults nil
        completion-category-overrides '((file (styles partial-completion)))))


;; == consult ==
(use-package consult
  :ensure t
  :demand t
  :bind (("C-s" . my-consult-line)
         ([remap goto-line] . consult-goto-line)
         ("M-g o" . consult-outline)
         ("C-x b" . consult-buffer)
         ("M-y" . consult-yank-pop)
         ("M-g r" . consult-ripgrep)
         ("M-g f" . consult-fd)
         ("M-g i" . consult-imenu)
         ("M-g I" . consult-imenu-multi)
         ("M-g m" . consult-mark)
         ("M-g M" . consult-global-mark)
         ("M-g l" . consult-line))
  :config
  ;; C-uを付けるとカーソル位置の文字列を使うmy-consult-lineコマンドを定義
  (defun my-consult-line (&optional at-point)
    "Consult-line uses things-at-point if set C-u prefix."
    (interactive "P")
    (if at-point
        (consult-line (thing-at-point 'symbol))
      (consult-line))))


;; == consult-eglot ==
(use-package consult-eglot
  :ensure t
  :after eglot
  :hook (eglot-managed-mode . (lambda () (local-set-key (kbd "M-g s") #'consult-eglot-symbols))))


;; == consult-dir ==
(use-package consult-dir
  :ensure t
  :bind (("C-x C-d" . consult-dir)
         :map minibuffer-local-completion-map
         ("C-x C-d" . consult-dir)
         ("C-x C-j" . consult-dir-jump-file)))


;; ** == recentf ==
(use-package recentf
  :ensure t
  :demand t
  :config
  (recentf-mode 1)
  (setq recentf-save-file "~/.emacs.d/recentf")
  (setq recentf-max-saved-items 3000)
  (setq recentf-exclude '("recentf" "\\.epub\\'" "/home/hy/Commune/Calibre_2/.*"))
  (setq recentf-auto-save-timer
        (run-with-idle-timer 1200 t 'recentf-save-list)))


;; == corfu ==
(use-package corfu
  :ensure t
  :demand t
  :config
  (global-corfu-mode +1)
  (setq corfu-cycle t)
  (setq corfu-auto t)
  (setq corfu-preselect 'prompt)
  (setq corfu-quit-no-match 'separator)
  (setq corfu-quit-at-boundary nil)
  (setq corfu-scroll-margin 2)
  (setq corfu-auto-delay 0.2)
  ;; ミニバッファでの補完を有効にする
  (defun corfu-enable-always-in-minibuffer ()
    "Enable Corfu in the minibuffer if Vertico/Mct are not active."
    (unless (or (bound-and-true-p mct--active)
                (bound-and-true-p vertico--input))
      (corfu-mode 1)))
  (add-hook 'minibuffer-setup-hook #'corfu-enable-always-in-minibuffer 1))


;; == corfu-terminal ==
(unless (display-graphic-p)
  (corfu-terminal-mode +1))


;; == cape ==
(use-package cape
  :ensure t
  :init
  (add-to-list 'completion-at-point-functions #'cape-dabbrev)
  (add-to-list 'completion-at-point-functions #'cape-file)
  (add-to-list 'completion-at-point-functions #'cape-keyword))

7. 各種パッケージの設定

  • コーディングとライティングの双方で使われる著名なパッケージと、検索コマンド関連の設定です
;; === dired ===
(use-package dired
  :ensure nil
  :commands (dired)
  :hook
  ((dired-mode . dired-hide-details-mode)
   (dired-mode . hl-line-mode))
  :config
  (setq dired-recursive-copies 'always)
  (setq dired-recursive-deletes 'always)
  (setq delete-by-moving-to-trash t)
  (setq dired-dwim-target t))

(use-package dired-subtree
  :ensure t
  :after dired
  :bind
  ( :map dired-mode-map
    ("<tab>" . dired-subtree-toggle)
    ("TAB" . dired-subtree-toggle)
    ("<backtab>" . dired-subtree-remove)
    ("S-TAB" . dired-subtree-remove))
  :config
  (setq dired-subtree-use-backgrounds nil))

(use-package dired-filter
  :ensure t
  :config
  (dired-filter-mode 1))


;; == apheleia(フォーマットの実行) ==
(use-package apheleia
  :ensure t
  :config
  (apheleia-global-mode +1)
  (setf (alist-get 'ruff-isort apheleia-formatters)
        '("ruff" "check" "--select" "I" "--fix" "--stdin-filename" filepath "-"))
  (setf (alist-get 'ruff-format apheleia-formatters)
        '("ruff" "format" "--stdin-filename" filepath "-"))
  (setf (alist-get 'python-ts-mode apheleia-mode-alist) '(ruff-isort ruff-format))
  (setf (alist-get 'python-mode apheleia-mode-alist) '(ruff-isort ruff-format))
  (setf (alist-get 'html-mode apheleia-mode-alist) '(prettier-html))
  (setf (alist-get 'css-mode apheleia-mode-alist) '(prettier-css))
  (setf (alist-get 'go-mode apheleia-mode-alist) '(goimports))
  (setf (alist-get 'go-ts-mode apheleia-mode-alist) '(goimports)))


;; == vterm(関連ライブラリのインストールや事前コンパイルが必要) ==
(use-package vterm
  :ensure t
  :commands vterm
  :bind (:map vterm-mode-map
              ("C-c C-y" . vterm-copy-mode)
              ("C-q" . vterm-send-next-key)
              ("C-c C-f" . find-file))
  :config
  (setq vterm-shell "zsh")
  (setq vterm-max-scrollback 10000)
  (setq vterm-keymap-exceptions
        '("<f1>" "<f2>" "<f5>" "<f6>" "<f7>" "<f8>" "<f9>" "<f12>"
          "C-x" "C-u" "C-g" "C-z" "C-t" "C-s" "C-v" "C-1"
          "M-x" "M-o" "M-v" "M-y" "M-." "M-i" "C-y"))
  (add-hook 'vterm-mode-hook
            (lambda ()
              (display-line-numbers-mode -1)
              (hl-line-mode -1)
              (setq-local truncate-lines t))))

;; vtermの終了時にmd形式のログファイルを自動保存する
(add-hook 'vterm-exit-functions
          (lambda (buffer _event)
            (with-current-buffer buffer
              (let ((filename (format "~/Documents/MyAgentLogs/vterm-%s.md" (format-time-string "%Y%m%d-%H%M%S"))))
                (write-region (point-min) (point-max) filename)
                (message "Vterm log saved to %s" filename)))))


;; == magit ==
(use-package magit
  :ensure t
  :defer t
  :bind (("C-x g" . magit-status))
  :config
  (defun mu-magit-kill-buffers ()
    "Restore window configuration and kill all Magit buffers."
    (interactive)
    (let ((buffers (magit-mode-get-buffers)))
      (magit-restore-window-configuration)
      (mapc #'kill-buffer buffers)))
  (bind-key "q" #'mu-magit-kill-buffers magit-status-mode-map))


;; == minions(マイナーモードの表示を簡素化) ==
(use-package minions
  :ensure t
  :demand t
  :bind (:map my-keys-prefix-map
              ("m" . minions-minor-modes-menu))
  :config
  (minions-mode 1)
  (setq minions-mode-line-lighter "[+]"))


;; == expand-region(C-c < で選択範囲を拡大) ==
(use-package expand-region
  :ensure t)

(global-set-key (kbd "C-c <") 'er/expand-region)


;; == symbol-overlay ==
(use-package symbol-overlay
  :ensure t)
(add-hook 'prog-mode-hook #'symbol-overlay-mode)
(add-hook 'markdown-mode-hook #'symbol-overlay-mode)
(global-set-key (kbd "M-i") 'symbol-overlay-put)
(define-key symbol-overlay-map (kbd "p") 'symbol-overlay-jump-prev) ; 前のシンボルへ
(define-key symbol-overlay-map (kbd "n") 'symbol-overlay-jump-next) ; 次のシンボルへ
(define-key symbol-overlay-map (kbd "C-g") 'symbol-overlay-remove-all) ; ハイライトキャンセル


;; == vundo ==
(use-package vundo
  :ensure t)

(global-set-key (kbd "C-?") 'vundo)
(define-key my-keys-prefix-map (kbd "u") 'vundo)


;; ** yasnippet
(use-package yasnippet
  :ensure t
  :defer t
  :init
  (add-hook 'prog-mode-hook #'yas-minor-mode)
  (add-hook 'text-mode-hook #'yas-minor-mode)
  :config
  (setq yas-snippet-dirs '("~/.emacs.d/mysnippets"))
  :bind
  ("C-z C-p" . yas-insert-snippet))


;; == eww ==
(setq eww-search-prefix "https://duckduckgo.com/?q=%s")
(setq url-user-agent "Mozilla/5.0 (X11; Linux x86_64; rv:126.0) Gecko/20100101 Firefox/126.0")
(setq shr-width 80)
(setq eww-image-max-width 600)


;; == ripgrep ==
(setq ripgrep-executable "/usr/bin/rg")
(setq ripgrep-arguments '("-S")) ; rgに渡すオプション
(setq xref-search-program 'ripgrep) ; 検索をripgrepに設定

(global-set-key (kbd "C-c r g") 'ripgrep-regexp)


;; == rg ==
(use-package rg
  :ensure t
  :config
  (define-key global-map (kbd "C-c r r") #'rg-project)
  (define-key global-map (kbd "C-c r m") #'rg-menu))


;; == wgrep ==
(use-package wgrep
  :ensure t
  :demand t)


;; == hunspell(hunspellと辞書のインストールが必要) ==
(setq-default ispell-program-name "hunspell")
(setq ispell-dictionary "en_US")
(with-eval-after-load "ispell"
  (setq ispell-hunspell-dictionary-alist
        '(("en_US" "[[:alpha:]]" "[^[:alpha:]]" "[']" nil ("-d" "en_US") nil utf-8)))
  (setq ispell-local-dictionary "en_US")
  (add-to-list 'ispell-skip-region-alist '("[^\000-\377]+")))

;; テキストモードで補完候補を表示しない
(defun my-disable-ispell-in-text-modes ()
  "Remove ispell completion function in text-based modes."
  (setq-local completion-at-point-functions
              (remq 'ispell-completion-at-point
                    (copy-sequence completion-at-point-functions))))
(add-hook 'text-mode-hook 'my-disable-ispell-in-text-modes)


;; == multiple-cursors(複数カーソル) ==
(use-package multiple-cursors
  :ensure t
  :bind (:map my-keys-prefix-map
              ("." . mc/edit-lines)
              (">" . mc/mark-next-like-this)
              ("<" . mc/mark-previous-like-this)
              ("," . mc/mark-all-like-this)))


;; == smartparens(コーディング時に対応するカッコを挿入) ==
(use-package smartparens
  :ensure t
  :hook (prog-mode . smartparens-mode)
  :bind ("C-\\" . smartparens-mode))


;; == iedit(複数個所を同時編集) ==
(use-package iedit
  :ensure t
  :demand t)


;; == quickrun(ターミナルを経由せずにコードを実行) ==
(use-package quickrun
  :ensure t
  :bind (:map my-keys-prefix-map
              ("c" . quickrun)))


;; == direnv(ディレクトリの環境変数を自動認識)==
(use-package direnv
  :ensure t
  :config
  (direnv-mode))


;; == ace-window(ウィンドウの賢い切り替え) ==
(use-package ace-window
  :ensure t
  :defer t
  :bind
  ("M-o" . ace-window))


;; == avy(キー操作で選択 ➔ アクション) ==
(use-package avy
  :ensure t
  :config (setq avy-style 'pre)
  :bind (("C-c h" . avy-goto-char-timer)
         ("C-c H" . avy-goto-line)))

8. コーディング関連

  • 本稿の趣旨とずれるため、詳細には触れません
  • この項目の設定は、各言語のエキスパート諸氏の設定を参照してください
;; == eglot ==
(use-package eglot
  :ensure t
  :defer t
  :init
  (setq eglot-events-buffer-size 0)
  :config
  (setq eglot-ignored-server-capabilities
        '(:documentHighlightProvider
          :workspace/didChangeWorkspaceFolders)))


;; == eglot-booster ==
(use-package eglot-booster
  :ensure t
  :after eglot
  :config
  (eglot-booster-mode))


;; == tree-siter ==
(use-package treesit
  :ensure nil
  :config
  (setq treesit-font-lock-level 4)
  (setq treesit-language-source-alist
        '((typescript "https://github.com/tree-sitter/tree-sitter-typescript" "master" "typescript/src")
          (tsx "https://github.com/tree-sitter/tree-sitter-typescript" "master" "tsx/src"))))


;; == LANGUAGE SETTINGS ==
;; Python
(setq auto-mode-alist (cons '("\\.py$" . python-ts-mode) auto-mode-alist))
(use-package python-ts-mode
  :ensure nil
  :hook
  ((python-ts-mode . eglot-ensure)
   (python-ts-mode . (lambda () (setq-local python-indent-offset 4))))
  :config
  (add-to-list 'eglot-server-programs
               '(python-ts-mode "pyright-langserver")))

;; pet
(use-package pet
  :ensure t
  :defer t
  :config
  (add-hook 'python-ts-mode-hook 'pet-mode -10))


;; Clang
(use-package c-ts-mode
  :ensure nil
  :hook
  (c-ts-mode . eglot-ensure)
  :if (treesit-language-available-p 'c)
  :custom
  (c-ts-mode--indent-styles 'cpp)
  :init
  ;; Remap the standard C/C++ modes
  (add-to-list 'major-mode-remap-alist '(c-mode . c-ts-mode))
  (add-to-list 'major-mode-remap-alist '(c++-mode . c++-ts-mode))
  (add-to-list 'major-mode-remap-alist '(c-or-c++-mode . c-or-c++-ts-mode)))


;; Rust
(use-package rust-ts-mode
  :ensure nil
  :mode ("\\.rs\\'" . rust-mode)
  :hook
  (rust-ts-mode . eglot-ensure))


;; Javascript
(use-package js-ts-mode
  :ensure nil
  :mode ("\\.js\\'" . js-ts-mode)
  :hook
  (js-ts-mode . eglot-ensure))


;; Typescript
(use-package typescript-ts-mode
  :ensure t
  :mode ("\\.ts\\'" . typescript-ts-mode)
  :hook (typescript-ts-mode . eglot-ensure))


;; Shellscript
(use-package sh-base-mode
  :ensure nil
  :hook
  (sh-base-mode . eglot-ensure))


;; Sly/Clisp
(use-package sly
  :ensure t
  :hook ((lisp-mode . font-lock-mode)
         (sly-mode . font-lock-mode))
  :config
  (customize-set-variable
   'inferior-lisp-program
   "sbcl --noinform --no-sysinit"))


;; Scheme
(use-package geiser
  :ensure nil
  :mode (("\\.scm\\'" . scheme-mode)
         ("\\.ss\\'" . scheme-mode))
  :hook (geiser-mode . (lambda ()
                         (eldoc-mode 1)
                         (corfu-mode 1)
                         (flymake-mode 1)))
  :config
  (setq geiser-active-implementations '(guile))
  (setq geiser-repl-print-level 3)
  (setq geiser-repl-print-length 3)
  (require 'geiser-guile))


;; Golang
(use-package go-mode
  :ensure t
  :mode "\\.go\\'"
  :config
  (add-hook 'go-mode-hook #'eglot-ensure))

9. 翻訳

  • 日本語と英語、イタリア語の翻訳のための設定です

9.1 Google翻訳

(use-package gt
  :ensure t
  :bind ("C-z t" . gt-translate)
  :config
  (setq gt-langs '(ja en it))
  (setq gt-default-translator
        (gt-translator
         :taker   (gt-taker :text 'buffer :pick 'paragraph)
         :engines (list
                   (gt-google-engine)
                   )
         :render  (gt-buffer-render))))

(defun my-focus-gt-result-buffer-after-translate (&rest _)
  "翻訳後に*gt-result*バッファが表示されているウィンドウにフォーカスを移す。"
  (let ((result-window (get-buffer-window "*gt-result*" 'visible)))
    (when result-window
      (select-window result-window))))
(advice-add 'gt-translate :after #'my-focus-gt-result-buffer-after-translate)

9.2 deepl-cli.el (DeepL翻訳)

  • pythonのdeeplパッケージを導入してください
    • 例: uv tool install deepl
  • 下記のdeepl-cli.eladd-to-list 'load-pathで指定したディレクトリに保存してください
;;; deepl-cli.el --- DeepL CLI integration -*- lexical-binding: t; -*--

(require 'cl-lib)

;; APIキー設定用の変数(未定義の場合はnil)
(defvar deepl-auth-key nil)
(defvar deepl-confirmation-threshold 3000)

;; 長文送信時の確認
(cl-defun confirm-send-long-string (&key retry)
  (let ((send-it-p
         (read-from-minibuffer
          (if retry
              "Please answer with \"yes\" or \"no\". [yes/no]: "
            (format "It's over %S characters, do you really want to send it? [yes/no]: "
                    deepl-confirmation-threshold))))) 
    (cond ((equal send-it-p "yes") t)
          ((equal send-it-p "no") nil)
          (t (confirm-send-long-string :retry t)))))

;; 翻訳結果を処理する関数
(defun deepl--handle-result (translated-text)
  (kill-new translated-text)
  (message "%s" translated-text))

;; 内部関数: deeplコマンドを呼び出す
(cl-defun deepl-translate-internal-cli (text source-lang target-lang callback)
  "deeplコマンドを使って翻訳を行います。"
  ;; キーのチェック
  (unless (and (boundp 'deepl-auth-key) deepl-auth-key)
    (user-error "deepl-auth-key is not set. Please set it in your config: (setq deepl-auth-key \"YOUR_KEY_HERE\")"))

  ;; 文字数チェック
  (when (and (> (length text) deepl-confirmation-threshold)
             (not (confirm-send-long-string)))
    (cl-return-from deepl-translate-internal-cli))

  ;; プロセス作成と実行
  (let* ((auth-env (format "DEEPL_AUTH_KEY=%s" deepl-auth-key))
         (process-environment (cons auth-env process-environment))
         (out-buf (generate-new-buffer " *deepl-output*"))
         (proc (make-process
                :name "deepl-cli"
                :buffer out-buf
                :command (list "deepl" "text" "--to" target-lang "--from" source-lang "-")
                :coding 'utf-8
                :connection-type 'pipe
                :sentinel (lambda (proc event)
                            (when (memq (process-status proc) '(exit signal))
                              (let ((exit-code (process-exit-status proc)))
                                (if (zerop exit-code)
                                    ;; 成功時
                                    (with-current-buffer (process-buffer proc)
                                      (let ((result (string-trim (buffer-string))))
                                        (if (string-empty-p result)
                                            (message "DeepL CLI returned empty result.")
                                          (funcall callback result)))
                                      (kill-buffer (process-buffer proc)))
                                  ;; エラー時
                                  (progn
                                    (message "DeepL CLI Error (Code %d). Buffer content: %s"
                                             exit-code
                                             (with-current-buffer (process-buffer proc) (buffer-string)))
                                    (kill-buffer (process-buffer proc))))))))))
    ;; 標準入力へテキストを送信
    (if (process-live-p proc)
        (progn
          (process-send-string proc text)
          (process-send-eof proc))
      (message "Failed to create DeepL process"))))

;; --- イタリア語翻訳用関数 ---

(defun deepl-ij (start end)
  "イタリア語から日本語に翻訳します。"
  (interactive "r")
  (let ((region (buffer-substring-no-properties start end)))
    (deepl-translate-internal-cli region "IT" "JA" #'deepl--handle-result)))

(defun deepl-ji (start end)
  "日本語からイタリア語に翻訳します。"
  (interactive "r")
  (let ((region (buffer-substring-no-properties start end)))
    (deepl-translate-internal-cli region "JA" "IT" #'deepl--handle-result)))

(defun deepl-translate2 (start end)
  "日本語とイタリア語を自動判別して相互に翻訳します。"
  (interactive "r")
  (let ((region (buffer-substring-no-properties start end)))
    (if (ja-string-p region)
        (deepl-translate-internal-cli region "JA" "IT" #'deepl--handle-result)
      (deepl-translate-internal-cli region "IT" "JA" #'deepl--handle-result))))

;; --- 英語翻訳用関数 ---

(defun deepl-ej (start end)
  "英語から日本語に翻訳します。"
  (interactive "r")
  (let ((region (buffer-substring-no-properties start end)))
    (deepl-translate-internal-cli region "EN" "JA" #'deepl--handle-result)))

(defun deepl-je (start end)
  "日本語から英語に翻訳します。"
  (interactive "r")
  (let ((region (buffer-substring-no-properties start end)))
    (deepl-translate-internal-cli region "JA" "EN-US" #'deepl--handle-result)))

(defun deepl-translate1 (start end)
  "日本語と英語を自動判別して相互に翻訳します。"
  (interactive "r")
  (let ((region (buffer-substring-no-properties start end)))
    (if (ja-string-p region)
        (deepl-translate-internal-cli region "JA" "EN-US" #'deepl--handle-result)
      (deepl-translate-internal-cli region "EN" "JA" #'deepl--handle-result))))

;; 共通ヘルパー関数

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

(defun ja-string-p (str)
  (>= (cl-count-if #'ja-char-p str) 3))

(provide 'deepl-cli)

init.elには次のように記述します。

(use-package deepl-cli
  :ensure nil
  :bind (:map my-keys-prefix-map
              ("e" . deepl-translate1)  ; 日英
              ("i" . deepl-translate2)) ; 日伊
  :init
  (setq deepl-auth-key "ご自身のAPIキー"))

10. 電子辞書

  • sdcvgoldendictがスタンドアローンで動作することを確認してください
  • 辞書のリストはご自身の環境と合わせてください
;; == quick sdcv(辞書検索の結果を別バッファに表示)==
(use-package quick-sdcv
  :ensure t
  :custom
  (quick-sdcv-dictionary-prefix-symbol "►")
  (quick-sdcv-ellipsis " ▼ ")
  :config
  (define-key quick-sdcv-mode-map (kbd "q") 'quit-window))
(setq quick-sdcv-dictionary-complete-list
      '("EIJIRO"
        "新明解国語辞典"
        "広辞苑"
        "American Heritage Dictionary 4th Ed. (En-En)"
        "Oxford English Dictionary 2nd Ed. P1"
        "Oxford English Dictionary 2nd Ed. P2"
        ))
(define-key my-keys-prefix-map (kbd "s") 'quick-sdcv-search-at-point) ;; カーソル位置の単語検索
(define-key my-keys-prefix-map (kbd "S") 'quick-sdcv-search-input) ;; キーボード入力で単語検索


;; == goldendict(辞書検索の結果を外部アプリのポップアップ表示) ==
;; goldendictのscan-popupオプションをONにしておくこと
(defun look-up-dict (word)
  (start-process "goldendict" nil "goldendict" word))

(defun look-up-dict-marked ()
  (interactive)
  (let ((word ""))
    (if (equal major-mode 'pdf-view-mode)
        (setq word (car (pdf-view-active-region-text)))
      (setq word  (buffer-substring (mark) (point))))
    (look-up-dict word)))

(define-key my-keys-prefix-map (kbd "g") 'look-up-dict-marked)


;; == websearch(ブラウザ経由でGoogle検索) ==
(use-package emacs-websearch
  :ensure nil
  :bind (("C-z h" . emacs-websearch))
  :config
  (setq emacs-websearch-engine 'google)
  (setq emacs-websearch-async t))

11. LLM関連の設定

  • gemini-cliに関連した設定だけを書きました
  • emacsとAIコーディングエージェントの統合については、まだ納得できる環境が構築できていません
  • gemini-cligmngemini-cliよりスタートアップが37倍高速)の事前インストールが必要です。

11.1 emacsからgemini-cliを呼び出す

選択したリージョンをgemini-cli経由で質問すると、結果をmarkdown形式で表示してくれる関数です。

;; == Gemini CLI ==
(defun my-gemini-cli-chat-region (start end)
  "リージョンをGeminiに質問し、結果をMarkdown形式で表示する"
  (interactive "r")
  (let* ((question (buffer-substring-no-properties start end))
         (command (concat "gmn -p " (shell-quote-argument question)))
         (output-buffer-name "*Gemini Chat Output*")
         (buf (get-buffer-create output-buffer-name)))
    (message "Geminiに問い合わせ中...")

    (with-current-buffer buf
      (setq buffer-read-only nil)
      (erase-buffer)
      ;; Markdownモードを適用
      (when (fboundp 'markdown-mode)
        (markdown-mode))
      ;; ANSIカラーを有効にする設定
      (setq-local ansi-color-for-comint-mode t))

    (let ((proc (start-process-shell-command "gemini-process" buf command)))
      (set-process-sentinel proc
                            (lambda (p e)
                              (when (string= e "finished\n")
                                (message "Geminiからの回答が完了しました。")
                                ))))
    (display-buffer buf)))

(global-set-key (kbd "C-z C-a") 'my-gemini-cli-chat-region)

11.2 チャットの内容をorg-mode形式へ変換する

gemini-cliには/copyコマンドでチャット内容をmarkdown形式でクリップボードに送信してくれる機能があります。これをpandoc経由でogr-modeのファイル形式に変換し、貼り付ける関数です。

;;; === Gemini CLI to Org ===
(defun my-paste-gemini-chat-as-org ()
  "Get Gemini chat log from clipboard (Markdown), convert it to Org format, and insert."
  (interactive)
  (let* ((md-text (current-kill 0))
         (pandoc-command "pandoc")
         (org-text (shell-command-to-string
                    (format "echo %s | %s -f markdown -t org"
                            (shell-quote-argument md-text)
                            pandoc-command))))
    (with-temp-buffer
      (insert org-text)
      (goto-char (point-min))
      (while (re-search-forward "^#\\+BEGIN_QUOTE\n\\(.*?\\)\n#\\+END_QUOTE" nil t)
        (replace-match "* \\1" t t))

      (goto-char (point-min))
      (while (re-search-forward "^----*$" nil t)
        (replace-match "" t t))

      (goto-char (point-min))
      (while (re-search-forward "\n\n\n+" nil t)
        (replace-match "\n\n" t t))
      (kill-ring-save (point-min) (point-max)))
    (yank)
    (message "Gemini chat log converted to Org format and pasted.")))

(global-set-key (kbd "C-z C-y") 'my-paste-gemini-chat-as-org)

11.3 LLM Agentのログを検索

geminiやclaudeとのチャット内容は、すべて~/Documents/MyAgentLogs/に保存しています。大量のログファイルの中から、目的のキーワードを素早く検索するための関数を定義しました。

;; == LLM Agentのログを検索 ==
(defun my-consult-ripgrep-agent-logs ()
  "Search in MyAgentLogs directory using `consult-ripgrep' with Japanese support."
  (interactive)
  (let* ((consult-ripgrep-args (concat consult-ripgrep-args " -a --encoding utf-8"))
         (coding-system-for-read 'utf-8)
         (coding-system-for-write 'utf-8))
    (consult-ripgrep (expand-file-name "~/Documents/MyAgentLogs/"))))

(global-set-key (kbd "C-z a") 'my-consult-ripgrep-agent-logs)

11.4 agent-shellの導入

emacs内とllmエージェントとの統合のためにagent-shellを導入しました。

  • ターミナル上のgemini-cliと一部キーバインディングが異なる
  • Gemini 3.1 proの登場後、特に動作が不安定になった

という理由で、最近はあまり使っていません。日本語入力の不便さに目をつぶれば、vterm上でgemini-cliを起動した方が快適です(vtermを閉じたとき、ログを自動保存する設定をしています)。

;; == agent-shell ==
(use-package agent-shell
  :ensure t
  :vc (:url "https://github.com/google/gemini-cli"
            :lisp-dir "emacs/agent-shell")
  :bind (("C-z C-g" . agent-shell-google-start-gemini))
  :config
  (setq agent-shell-transcript-file-path-function
        (lambda ()
          (let* ((log-dir "~/Documents/MyAgentLogs/")
                 (filename (format-time-string "%Y%m%dT%H%M%S--agent-log__llm.md"))
                 (filepath (expand-file-name filename log-dir)))
            filepath))))

11.5 選択テキストをQRコードに変換して表示

Linuxからスマホへ:ネットワークを介さない「物理層」コピペを参照してください。

;; == QR Code Generation ==
(defun my-region-to-qrcode (start end)
  "選択範囲を一時ファイル経由でQRコードに変換して表示(nohup版)"
  (interactive "r")
  (let ((text-file (make-temp-file "qr-text" nil ".txt"))
        (img-file (make-temp-file "qr-image" nil ".png"))
        (exit-code 0))
    (write-region start end text-file)
    (setq exit-code
          (call-process "qrencode" nil nil nil "-s" "10" "-r" text-file "-o" img-file))
    (if (and (= exit-code 0) (file-exists-p img-file))
        (progn
          (message "QRコードを表示: %s" img-file)
          (call-process-shell-command
           (format "nohup xdg-open %s >/dev/null 2>&1 &" img-file)
           nil 0))
      (message "QRコード生成失敗 (Exit code: %d)" exit-code))))

(define-key my-keys-prefix-map (kbd "q") 'my-region-to-qrcode)

12. org-mode

  • org-modeの設定は膨大かつ十人十色なので、設定だけ書くことにします(いつか自分のワークフローを交えて、org-modeに関する記事を書きたいと思います)
  • denoteを核としたプレーンなテキスト環境です
  • rgfdといった高速な検索コマンドとの連携で(わたくしの仕事の範囲では)快適に動作しています

12.1 org-mode関連のファイル置き場

;; file directory
(setq org-directory "~/Documents/MyOrg")
(setq org-default-notes-file "20250401T010000--mynotes__memo_private.org")

;; org-captureのキーシーケンス
(global-set-key (kbd "C-c c") 'org-capture)

;; org-captureのテンプレート(メニュー)の設定
(setq org-capture-templates
      '(
        ("n" "Note" entry (file+headline "~/Documents/MyOrg/notes/20250401T010000--mynotes__memo_private.org" "Notes") "* %?\nEntered on %U\n %i\n %a")
        ("t" "Todo" entry (file+headline "~/Documents/MyOrg/notes/20250401T030000--mytask__private.org" "Capture") "** TODO %? \n   SCHEDULED: %^t \n")
        ("w" "Work" entry (file+headline "~/Documents/MyOrg/notes/20250401T040000--mywork__work.org" "Work") "* %?\nEntered on %U")
        ("v" "Voca" entry (file+headline "~/Documents/MyOrg/notes/20250325T000000--english-glossary__database.org" "2025 (voca)")"* %?")
        ("a" "AI" entry (file+headline "~/Documents/MyOrg/notes/20250401T050000--myai__ai_llm.org" "Q_board") "* %?\nEntered on %U\n %i\n %a")
        ("b" "Bulk" entry (file+headline "~/Documents/MyOrg/notes/20250825T184025--mybulk-news-link__actuality_portal.org" "misc") "* %?\nEntered on %U\n %i\n %a")
        ))


;; mynotes.orgをC-c nで参照
(defun show-org-buffer (file)
  "Show my org-file FILE on the current buffer."
  (interactive)
  (if (get-buffer file)
      (let ((buffer (get-buffer file)))
        (switch-to-buffer buffer)
        (message "%s" file))
    (find-file (concat "~/Documents/MyOrg/notes/" file))))
(global-set-key (kbd "C-c n") #'(lambda () (interactive)
                                 (show-org-buffer "20250401T010000--mynotes__memo_private.org")))

12.2 表示関連

;; org-modeで行を折り返さない
(setq org-startup-truncated nil)

;; 階層に合わせて左にインデント
(setq org-startup-indented t)

;; 見出しをインデントした時にアスタリスクが減るのを防ぐ
(setq org-indent-mode-turns-on-hiding-stars nil)

;; インデントの幅を設定
(setq org-indent-indentation-per-level 2)

;; 見出しの初期状態
(setq org-startup-folded t)
(setq org-startup-with-inline-images t)

;; 表示設定(プロポーショナルフォント表示)
(add-hook 'org-mode-hook (lambda () (setq line-spacing 0.1)))

(with-eval-after-load 'org
  ;; Header scaling and colors
  (set-face-attribute 'org-level-1 nil :inherit 'variable-pitch :height 1.1 :weight 'bold :foreground "#62b2ff")
  (set-face-attribute 'org-level-2 nil :inherit 'variable-pitch :height 1.1 :weight 'bold :foreground "#19e37d")
  (set-face-attribute 'org-level-3 nil :inherit 'variable-pitch :height 1.0 :weight 'bold :foreground "#f0ce43")
  (set-face-attribute 'org-level-4 nil :inherit 'variable-pitch :height 1.0 :weight 'bold :foreground "#ff79c6")
  (set-face-attribute 'org-level-5 nil :inherit 'variable-pitch :height 1.0 :weight 'bold :foreground "#bd93f9")
  (set-face-attribute 'org-level-6 nil :inherit 'variable-pitch :height 1.0 :weight 'bold :foreground "#46d9ff")

  ;; Ensure structural elements use fixed-pitch font
  (set-face-attribute 'org-block nil :inherit 'fixed-pitch)
  (set-face-attribute 'org-code nil :inherit 'fixed-pitch)
  (set-face-attribute 'org-table nil :inherit 'fixed-pitch)
  (set-face-attribute 'org-verbatim nil :inherit 'fixed-pitch)
  (set-face-attribute 'org-formula nil :inherit 'fixed-pitch)
  (set-face-attribute 'org-special-keyword nil :inherit 'fixed-pitch)
  (set-face-attribute 'org-meta-line nil :inherit 'fixed-pitch)
  (set-face-attribute 'org-checkbox nil :inherit 'fixed-pitch)

  ;; Quote style matching markdown-mode
  (set-face-attribute 'org-quote nil :inherit 'variable-pitch :slant 'italic))

;; 画像のサイズ
(setq org-image-actual-width 300)

;; サブツリー内の画像の表示を制御
(defun org-toggle-inline-images-in-subtree ()
  "Show images in the subtree only."
  (interactive)
  (save-excursion
    (save-restriction
      (org-narrow-to-subtree)
      (org-toggle-inline-images)
      (widen))))
(global-set-key (kbd "C-c v") 'org-toggle-inline-images-in-subtree)

;; ビュレット
(use-package org-bullets
  :ensure t
  :config (setq org-bullets-bullet-list '("∑" "⨕" "∫" "Ω" "☿" "∅" "∢" "⚕"))
  :hook (org-mode . org-bullets-mode))

;; Emphaisis markers(*、/、=など)を非表示にする
(setq org-hide-emphasis-markers t)

12.3 リンク関連

;; org-store-linkのキーシーケンス
(global-set-key (kbd "C-c l") 'org-store-link)
(global-set-key (kbd "<f8>") 'org-store-link)

;; org-insert-linkのキーシーケンス
(global-set-key (kbd "C-c C-l") 'org-insert-link)
(global-set-key (kbd "<f9>") 'org-insert-link)

;; リンク表示(トグル)
(global-set-key (kbd "C-c f") 'org-toggle-link-display)

;; カーソル位置のリンクをミニバッファに表示する
(with-eval-after-load 'org
  (defun my-org-display-raw-link-at-point ()
    "Display the raw link when the cursor is on an Org mode link.
This command is intended to be called interactively."
    (interactive)
    ;; org-agenda-mode で org-element-context を使用すると警告が出ることがあるため抑制
    (let ((element (let ((warning-minimum-level :error))
                     (org-element-context))))
      ;; カーソル位置の要素がリンクであるかを確認
      (if (and element (eq (car element) 'link))
          ;; リンクの場合、*Messages* バッファに記録せずにミニバッファ(echo area)に表示
          (let ((message-log-max nil))
            (message "%s" (propertize (org-element-property :raw-link element) 'face 'org-link)))
        ;; リンクでない場合、ユーザーに通知
        (message "No link at point"))))
  ;; org-mode で "C-z t" にコマンドを割り当て
  (define-key org-mode-map (kbd "C-t v") #'my-org-display-raw-link-at-point)
  ;; 元のコードと同様に org-agenda-mode でも利用可能にする

  (define-key org-agenda-mode-map (kbd "C-t v") #'my-org-display-raw-link-at-point))

;; Org-download
(use-package org-download
  :ensure t
  :demand t)
(setq-default org-download-image-dir "~/Documents/MyOrg/metadata")
(add-hook 'dired-mode-hook 'org-download-enable) ; Drag-and-drop to `dired`

(define-key org-mode-map (kbd "C-c L") 'org-download-yank)

12.4 機能設定

;; org-sparse-treeを別バッファで表示
(defun org-sparse-tree-indirect-buffer (arg)
  (interactive "P")
  (let ((ibuf (switch-to-buffer (org-get-indirect-buffer))))
    (condition-case _
        (org-sparse-tree arg)
      (quit (kill-buffer ibuf)))))
(define-key org-mode-map (kbd "C-c /") 'org-sparse-tree-indirect-buffer)

;; org-backward-heading-same-level
(define-key org-mode-map (kbd "C-z z") 'org-backward-heading-same-level)

;; org-tempoを有効化
(use-package org-tempo
  :ensure nil
  :after org)

;; Speed-commandsをONにする
(setq org-use-speed-commands t)

;; outline-hide-by-heading-regexpのキーを変更
(define-key org-mode-map (kbd "C-c s") 'outline-hide-by-heading-regexp)
(define-key org-mode-map (kbd "C-c S") 'outline-show-by-heading-regexp)

;; org-babel(ほとんど使っていない)
(org-babel-do-load-languages
 'org-babel-load-languages
 '((C . t)
   (emacs-lisp . t)
   (python . t)
   (shell . t)))

;; org-modeのheadingで日本語入力をオフ
(defun my-org-disable-ime-on-heading ()
  "When the cursor is on a heading's asterisk in Org mode, disable the input method."
  (when (and (eq major-mode 'org-mode)
             (bolp)
             (looking-at "\\*+"))
    (when current-input-method
      (deactivate-input-method))))
(add-hook 'post-command-hook #'my-org-disable-ime-on-heading)

;; org-qlでアジェンダファイルを高速検索
(use-package org-ql
  :ensure t
  :after (org consult)
  :bind (("C-t q" . org-ql-find)
         ("C-t Q" . org-ql-search))
  :config
  (setq org-ql-find-source (org-agenda-files)))

12.5 ファイル変換とエクスポート

;; 選択領域をmarkdownからorg形式へ
(defun my-md-to-org-region (start end)
  "Convert region from markdown to org, replacing selection"
  (interactive "r")
  (shell-command-on-region start end "pandoc -f markdown -t org" t t))

(global-set-key (kbd "C-c m") 'my-md-to-org-region)

;; markdownへの変換パッケージの呼び出し
(use-package ox-md
  :ensure nil
  :after org)
    
;; 選択範囲をorgからmdに変換してkill
(use-package org-to-markdown-and-kill
  :ensure nil
  :bind ("C-c p" . org-to-markdown-and-kill))
    
;; pandocでエクスポート
(use-package ox-pandoc
  :ensure t
  :after org
  :config
  ;;  docx 出力時に特定のテンプレートを使う場合など
  ;; (setq org-pandoc-options-for-docx '((reference-doc . "~/.emacs.d/template.docx")))
  )

12.6 Markdownの表示設定

(use-package markdown-mode
  :ensure t
  :mode ("\\.md\\'" . markdown-mode)
  :hook ((markdown-mode . markdown-toggle-inline-images)
         (markdown-mode . (lambda ()
                            (setq line-spacing 0.1)
                            (local-set-key (kbd "<tab>") 'markdown-cycle)
                            (local-set-key (kbd "S-<tab>") 'markdown-shifttab))))
  :config
  (setq markdown-hide-markup t)
  (setq markdown-header-scaling t)
  (setq markdown-header-scaling-values '(1.1 1.1 1.0 1.0 1.0 1.0))

  ;; Header colors matching org-mode
  (set-face-attribute 'markdown-header-face-1 nil :inherit 'variable-pitch :height 1.1 :weight 'bold :foreground "#62b2ff")
  (set-face-attribute 'markdown-header-face-2 nil :inherit 'variable-pitch :height 1.1 :weight 'bold :foreground "#19e37d")
  (set-face-attribute 'markdown-header-face-3 nil :inherit 'variable-pitch :height 1.0 :weight 'bold :foreground "#f0ce43")
  (set-face-attribute 'markdown-header-face-4 nil :inherit 'variable-pitch :height 1.0 :weight 'bold :foreground "#ff79c6")
  (set-face-attribute 'markdown-header-face-5 nil :inherit 'variable-pitch :height 1.0 :weight 'bold :foreground "#bd93f9")
  (set-face-attribute 'markdown-header-face-6 nil :inherit 'variable-pitch :height 1.0 :weight 'bold :foreground "#46d9ff")
  
  (set-face-attribute 'markdown-code-face nil :inherit 'fixed-pitch)
  (set-face-attribute 'markdown-inline-code-face nil :inherit 'fixed-pitch)
  (set-face-attribute 'markdown-table-face nil :inherit 'fixed-pitch)
  (set-face-attribute 'markdown-metadata-key-face nil :inherit 'fixed-pitch)
  (set-face-attribute 'markdown-metadata-value-face nil :inherit 'fixed-pitch)
  (set-face-attribute 'markdown-language-keyword-face nil :inherit 'fixed-pitch)
  (set-face-attribute 'markdown-blockquote-face nil :inherit 'variable-pitch :slant 'italic)

  (setq markdown-cycle-global-at-bob t)
  (setq markdown-max-image-size '(300 . 300))
  (setq markdown-fontify-code-blocks-natively t))

13. denote

  • denoteの詳細については、開発者の公式ドキュメントをご参照ください
  • denoteの哲学や使い方を理解するには、Protesilaos本人が解説したデモが役立つでしょう
(use-package denote
  :ensure t
  :hook ((dired-mode . denote-dired-mode))
  :config
  (setq denote-directory "~/Documents/MyOrg/notes")
  (setq denote-file-type 'org)
  (setq denote-save-buffers nil)
  (setq denote-known-keywords '("coding" "work" "private"))
  (setq denote-infer-keywords t)
  (setq denote-sort-keywords t)
  (setq denote-prompts '(title keywords))
  (setq denote-excluded-directories-regexp nil)
  (setq denote-excluded-keywords-regexp nil)
  (setq denote-rename-confirmations '(rewrite-front-matter modify-file-name))
  (denote-rename-buffer-mode 1)
  :bind (("C-t n" . denote)
         ("C-t l" . denote-link)
         ("C-t L" . denote-add-links)
         ("C-t b" . denote-backlinks)
         ("C-t r" . denote-rename-file)
         ("C-t R" . denote-rename-file-using-front-matter)
         ("C-t d" . denote-dired)
         ))

;; context-menuをフック
(add-hook 'context-menu-functions #'denote-context-menu)


;; == consult-denote ==
(use-package consult-denote
  :ensure t
  :bind
  (("C-t f" . consult-denote-find)
   ("C-t g" . consult-denote-grep))
  :config
  (setq consult-denote-find-command #'consult-fd)
  (setq consult-denote-grep-command #'consult-ripgrep)
  (consult-denote-mode 1))


;; == denote-org ==
(use-package denote-org
  :ensure t
  :after denote)


;; == denote-journal ==
(use-package denote-journal
  :ensure t
  :after denote
  :config
  (setq denote-journal-title-format "Journal %Y-%m-%d")
  (setq denote-journal-keyword "journal"))

(global-set-key (kbd "C-t j") 'denote-journal-new-or-existing-entry)



;;; === TODO/AGENDA ===
;; TODO状態
(setq org-todo-keywords
      '((sequence "TODO(t)" "WAIT(w)" "|" "DONE(d)" "SOMEDAY(s)")))

;; DONEの時刻を記録
(setq org-log-done 'time)

;; org-agendaのキー設定
(global-set-key (kbd "C-c a") 'org-agenda)

;; Target files for agenda
(setq org-agenda-files (list org-directory))

;; org-refile
(setq org-agenda-files '(
                         "~/Documents/MyOrg/notes/20250401T030000--mytask__private.org"
                         "~/Documents/MyOrg/notes/20250401T010000--mynotes__memo_private.org"
                         "~/Documents/MyOrg/notes/20250401T040000--mywork__work.org"
                         "~/Documents/MyOrg/notes/20250825T184025--mybulk-news-link__actuality_portal.org"
                         "~/Documents/MyOrg/notes/20250401T050000--myai__ai_llm.org"
                         ))
(setq org-refile-targets '((org-agenda-files :maxlevel . 2)))

;; prot's suggestions
(setq org-M-RET-may-split-line '((default . nil)))
(setq org-insert-heading-respect-content t)
(setq org-log-into-drawer t)

;; 選択範囲を内容として、新しいDenoteノートを作成
(defun my-denote-create-from-agent-log (start end)
  "選択範囲を内容として、新しいDenoteノートを作成し、ログへのリンクを付与する。"
  (interactive "r")
  (let ((content (buffer-substring-no-properties start end))
        (source-file (buffer-file-name)))
    (denote (read-string "Title: ") '("ailog" "fragment"))
    (insert content)
    (insert (format "\n\nSource: [[file:%s][Original Log]]" source-file))))

14. 画面の設定

;; 画面透過度をインタラクティブに変更する関数
(defun set-frame-alpha (alpha)
  "Set the alpha transparency of the current frame."
  (interactive "nAlpha (0-100): ")
  (set-frame-parameter (selected-frame) 'alpha (cons alpha '(100))))

(define-key my-keys-prefix-map (kbd "b") 'set-frame-alpha)

;; テーマ(F5で明/暗を切り替え)
(load-theme 'modus-vivendi-tritanopia)
(setq modus-themes-bold-constructs t
      modus-themes-italic-constructs t)

(define-key global-map (kbd "<f5>") #'modus-themes-toggle)

;; 行と列の番号表示
(line-number-mode t)
(column-number-mode t)

;; 時計の表示
(setq display-time-24hr-format t)
(display-time-mode t)

;; 対応するカッコを強調表示
(show-paren-mode t)

;; prog-modeで行番号を表示
(add-hook 'prog-mode-hook #'display-line-numbers-mode)

;; 行番号表示のトグル
(define-key my-keys-prefix-map (kbd "n") 'display-line-numbers-mode)

;; Menu Bar無効化
(menu-bar-mode -1)

;; Tool Bar無効化
(tool-bar-mode -1)

;; Scroll Bar無効化
(scroll-bar-mode -1)

;; 末尾のスペースやタブを可視化(現状はオフ)
;; (setq-default show-trailing-whitespace t)

;; 高速スクロール
(setq fast-but-imprecise-scrolling t)

;; fringeに表示するマークの形状を変更
(setq truncate-lines nil)
(setq truncate-partial-width-windows nil)
(setq-default fringe-indicator-alist
              (append (list '(continuation . (nil right-curly-arrow)))
                      (remove (assoc 'continuation fringe-indicator-alist)
                              fringe-indicator-alist)))
(define-fringe-bitmap 'right-curly-arrow
  [#b00000000
   #b00000000
   #b00000000
   #b00000000
   #b01111110
   #b01111110
   #b00000110
   #b00000110])

15. フォントとアイコンの設定

  • fontaineパッケージを導入し、インタラクティブなフォント切り替えに対応しました
;; == fontaine ==
(use-package fontaine
  :ensure t
  :demand t
  :bind ("C-z f" . fontaine-set-preset)
  :config
  (setq fontaine-latest-state-file
        (locate-user-emacs-file "fontaine-latest-state.eld"))

  (setq fontaine-presets
        '((small
           :default-family "PlemolJP Console NF"
           :default-height 95
           :variable-pitch-family "IBM Plex Sans JP")
          (regular)
          (medium
           :default-weight SemiBold
           :default-height 110
           :bold-weight extrabold)
          (large
           :inherit medium
           :default-height 120)
          (presentation
           :default-height 150)
          (serif
           :default-family "Noto Serif CJK JP"
           :default-height 100
           :default-weight Medium)
          (t
           :default-family "PlemolJP Console NF"
           :default-weight SemiBold
           :default-height 105

           :fixed-pitch-family "PlemolJP Console NF"
           :fixed-pitch-weight SemiBold
           :fixed-pitch-height 105

           :fixed-pitch-serif-family "PlemolJP Console NF"
           :fixed-pitch-serif-weight SemiBold
           :fixed-pitch-serif-height 105

           :variable-pitch-family "IBM Plex Sans JP"
           :variable-pitch-weight Medium
           :variable-pitch-height 105

           :mode-line-active-family nil
           :mode-line-active-weight nil
           :mode-line-active-height 0.9

           :mode-line-inactive-family nil
           :mode-line-inactive-weight nil
           :mode-line-inactive-height 0.9

           :header-line-family nil
           :header-line-weight nil
           :header-line-height 0.9

           :line-number-family nil
           :line-number-weight nil
           :line-number-height 0.9

           :tab-bar-family nil
           :tab-bar-weight nil
           :tab-bar-height 1.0

           :tab-line-family nil
           :tab-line-weight nil
           :tab-line-height 1.0

           :bold-family nil
           :bold-weight bold

           :italic-family nil
           :italic-slant italic

           :line-spacing nil)))

  (fontaine-set-preset (or (fontaine-restore-latest-preset) 'regular))
  (fontaine-mode 1))


;; 不要なフォント表示化を抑制
(setq redisplay-skip-fontification-on-input t)
(setq inhibit-compacting-font-caches t)


;; variable-pitch-mode と visual-line-mode を一括で切り替える
(defun my-toggle-variable-pitch-and-visual-line ()
  "Toggle `variable-pitch-mode' and `visual-line-mode' simultaneously."
  (interactive)
  (require 'face-remap)
  (variable-pitch-mode 'toggle)
  (visual-line-mode 'toggle)
  (force-window-update (selected-window))
  (redisplay t))

(global-set-key (kbd "<f6>") 'my-toggle-variable-pitch-and-visual-line)

;; == nerd-icons ==
(use-package nerd-icons
  :ensure t
  :demand t
  :config
  (add-to-list 'corfu-margin-formatters #'nerd-icons-corfu-formatter))

;; 各モードで nerd-icons の機能拡張を有効にします
(use-package nerd-icons-dired
  :ensure t
  :hook
  (dired-mode . nerd-icons-dired-mode))

(use-package nerd-icons-ibuffer
  :ensure t
  :hook (ibuffer-mode . nerd-icons-ibuffer-mode))

(use-package nerd-icons-completion
  :ensure t
  :after marginalia
  :config
  (nerd-icons-completion-mode)
  :hook (marginalia-mode-hook . #'nerd-icons-completion-marginalia-setup))

16. 電子書籍

  • novパッケージは日本語のレンダリングの問題もあり、あまり読書体験がよくありません
    • 最近はDocViewモードでも、ある程度快適にepubファイルが閲覧できるようになりました
  • calibredbパッケージを導入し、emacs上で書籍データベースを管理しています
    • calibreアプリのmetadata.dbのパスを確認して設定してください
;; == nov (EPUB reader) ==
(use-package novinit
  :ensure nil)

;; == calibredb ==
(use-package calibredb
  :ensure t
  :commands calibredb
  :config
  (setq calibredb-root-dir "~/Commune/Calibre_2")
  (setq calibredb-db-dir (expand-file-name "metadata.db" calibredb-root-dir))
  (setq calibredb-library-alist '(("~/Commune/Calibre_2" (name . "Calibre"))))
  (setq calibredb-id-width 6)
  (setq calibredb-format-width 4)
  (setq calibredb-title-width 80)
  (setq calibredb-comment-width 0)
  (setq calibredb-date-width 0)
  (setq calibredb-format-nerd-icons t))

;; doc-view-mode のパフォーマンスと品質を向上させる設定
(with-eval-after-load 'doc-view
  (setq doc-view-pdf-program "pdftocairo")
  (setq doc-view-resolution 150)
  (setq doc-view-slice-size 3))

17. pdf関連

  • pdf-toolsを導入しています
(use-package pdf-tools
  :ensure t
  :mode ("\\.pdf\\'" . pdf-view-mode)
  :config
  ;; (pdf-tools-install :no-query)
  (add-hook 'pdf-view-mode-hook (lambda() (display-line-numbers-mode -1)))
  (setq pdf-annot-activate-created-annotations t)
  (define-key pdf-view-mode-map (kbd "C-s") 'isearch-forward)
  (define-key pdf-view-mode-map (kbd "C-c C-a C-a") 'pdf-annot-minor-mode)
  (define-key pdf-view-mode-map (kbd "C-c C-a C-o") 'pdf-outline)
  (setq pdf-view-resize-factor 1.1)
  (setq pdf-cache-size 500))

18. 最終工程

;; デスクトップセーブモードを有効化
(desktop-save-mode 1)
(setq desktop-save t)

おわりに

以上、長々とお付き合いいただきましてありがとうございました。
誤った設定や非推奨の設定があればお知らせください。
先人のinit.elに感謝申し上げます。

16
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?