Configurations for GNU Emacs

Table of Contents

1. はじめに

Table 1: 設定ファイル名と役割
ファイル名 役割 行数
init.el 起動時に読み込まれる設定群 448
init-autoloads.el 起動時に読み込まれる定義群 843
early-init.el init.elより前に読み込まれる設定群 98
late-init.el ユーザアクションの発生後に読み込まれる設定群 2251
init-org.el ユーザアクションの発生後に読み込まれるorg-modeの設定群 2033
init-dired.el ユーザアクションの発生後に読み込まれるdired関連の設定群 57
utility.el 独自実装した関数群を集約 3102

2. 起動設定

基本的な設定群です.一部の関数は記述しておかないと動かない可能性があります.

普通は init.el に様々な設定をEmacsに読ませますが,私は諸事情から次のようにブートシーケンスを制御しています.

  1. .emacs の読み込み
  2. init.el の読み込み
  3. late-init.el の読み込み

.emacs では,起動の仕方を選択します.通常のモードでは init.el を読み込みますが,最小設定モードを読み込んで起動したり,spacemacs用設定を読み込んで起動したり制御できます.また, .emacs にはすぐ消すような実験的なコードを記載しています.気に入ったら, init.el に移します.

Org Mode の設定がかなり肥大化したため, init-org.el として分離して,遅延読み込みの対象にしています.設定ファイルの分離は, .el ファイルを tangle する時に自動的に実行されるように設定しています. postpone.el で遅延読み込みさせる設定を, late-init.el に分離しています.

early-init.el には,パスの設定,起動時間計測のためのフラグ群,パッケージ管理用の補助関数を設定しています.

late-init.el には,ほぼすべての独自作成した関数群を格納しています.各関数は autoload 指定していて,別途生成する init-autoload.el に関数と記述のあるファイルの対応関係が示され, init.el の冒頭で読み込みます.起動時にすべての独自関数全体を直接読まなくて済むため,高速化に繋がります.

これ以外に,バッチモードでバイトコンパイルするための init-eval.el も使います.このファイルには,バイトコンパイル時だけ必要なパッケージを列挙しています.

2.1. Spacemacs

たまに使いたくなるので,起動時に読み込む init.el を変えるだけで Spacemacs を読み込めるようにしています.

(Setup手順)

  1. ln -s ~/Dropbox/emacs.d/config/.spacemacs ~/
  2. git clone https://github.com/syl20bnr/spacemacs ~/.spacemacs.d

Then load the following at startup

;; (load (concat (setq user-emacs-directory "~/.spacemacs.d/") "init.el"))

2.2. init.el のヘッダ

;; init.el --- My init.el -*- lexical-binding: t -*-
;; Configurations for Emacs
;;                                                                                                                                                               Takaaki ISHIKAWA        <takaxp@ieee.org>
;; see also https://takaxp.github.io/init.html
(require 'init-autoloads nil t)
(when (and (boundp my-profiler-p)
                       my-profiler-p)
        (profiler-start 'cpu+mem))
(when (and (boundp my-profiler-p)
                       my-ad-require-p)
        (load "~/Dropbox/emacs.d/config/init-ad.el" nil t))
;; utility.el --- My utility.el -*- lexical-binding: t -*-
;; "my-" and "ad:" functions associated with my 'init.el'
(unless (featurep 'postpone)
  (call-interactively 'postpone-pre))
(unless noninteractive
  (defvar my-utility-start (current-time)))

2.3. [early-init.el] early-init.el を使う

early-init.el の読み込み時には, (display-graphic-p)window-system が共に nil に設定されるので要注意です.

;; (message "--- Window system (ns mac) %s, display-graphic-p %s, File %s" window-system (display-graphic-p) early-init-file)
;; References:
;; https://raw.githubusercontent.com/hlissner/doom-emacs/develop/early-init.el

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; (unless (getenv "LIBRARY_PATH")
;;   (setenv "LIBRARY_PATH"
;;           (string-join
;;            '("/opt/homebrew/opt/gcc/lib/gcc/14"
;;              "/opt/homebrew/opt/libgccjit/lib/gcc/14"
;;              "/opt/homebrew/opt/gcc/lib/gcc/14/gcc/aarch64-apple-darwin23/14")
;;            ":")))
;; for Intel mac user, replace "aarch64-apple-darwin23" with "x86_64-apple-darwin23".

(unless noninteractive
  (defvar my-early-start (current-time))
  (defvar my-early-init
    (format "%searly-init.el" (expand-file-name user-emacs-directory)))

  (message "Loading %s..." my-early-init)

  (setq package-enable-at-startup nil
        frame-inhibit-implied-resize t)
  (with-eval-after-load "moom"
    (setq frame-inhibit-implied-resize nil))

  (set-scroll-bar-mode nil)
  (menu-bar-mode -1)
  (tab-bar-mode -1)
  (tool-bar-mode -1))

2.4. 起動時間の計測

M-x emacs-init-time を実行すると,Emacsの起動にかかった時間が表示されます.個人的にはミリ秒表示が好きなので,手を加えて表示を変えます.元ネタはすぎゃーんメモからです.感謝.

(defun my-emacs-init-time ()
  "Emacs booting time in msec."
  (let ((inhibit-message t))
    (message "Emacs booting time: %4d [ms] = `emacs-init-time'."
             (* 1000
                (float-time (time-subtract
                             after-init-time
                             before-init-time))))))

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

ついでに, %.1f で出力されるいつもの emacs-init-time をハック.ただ最新の emacs では,デフォルトで細かい小数点以下表示に変更されているので不要です.

(with-eval-after-load "time"
  (defun ad:emacs-init-time ()
    "Return a string giving the duration of the Emacs initialization."
    (interactive)
    (let ((str
           (format "%.3f seconds"
                   (float-time
                    (time-subtract after-init-time before-init-time)))))
      (if (called-interactively-p 'interactive)
          (message "%s" str)
        str)))

  (advice-add 'emacs-init-time :override #'ad:emacs-init-time))

2.5. initファイルの読み込み時間計測

次の関数を init.el の最初に記載して,オプション付きで after-init-hook に追加します.すると,initファイルの読み込み時間を計測できます(GUIのフレーム生成を含まない).別途設定する emacs-init-time の派生関数とは違い, after-init-hook で実行される処理の時間もわかります.ただ,ほかの処理も微妙に入るので,あくまで目安です.

(defconst my-before-load-init-time (current-time)
        "Starting point to calculate Emacs booting time.        see `my-load-init-time'.")
(defun my-load-init-time ()
        "Loading time of user init files including time for `after-init-hook'."
        (let ((t-init-files (time-subtract after-init-time my-before-load-init-time))
                                (t-after-init (time-subtract (current-time) after-init-time))
                                (t-others (time-subtract my-before-load-init-time before-init-time))
                                (t-early-init (time-subtract my-early-end my-early-start))
                                (inhibit-message t))
                (message (concat
                                                        "        Loading init files: %4d [ms]\n"
                                                        "        Loading early-init: %4d [ms]\n"
                                                        "        Others(GUI etc.):       %4d [ms] includes `before-init-hook'\n"
                                                        "(`after-init-hook': %4d [ms])")
                                                 (* 1000 (float-time t-init-files))
                                                 (* 1000 (float-time t-early-init))
                                                 (* 1000 (- (float-time t-others) (float-time t-early-init)))
                                                 (* 1000 (float-time t-after-init)))))

(add-hook 'after-init-hook #'my-load-init-time t)

(defvar my-tick-previous-time my-before-load-init-time)
(defun my-tick-init-time (msg)
        "Tick boot sequence at loading MSG."
        (when (and my-loading-profile-p
                               (not my-profiler-p))
                (let ((ctime (current-time)))
                        (message "---- %4d[ms] %s"
                                                         (* 1000 (float-time
                                                                                                (time-subtract ctime my-tick-previous-time)))
                                                         msg)
                        (setq my-tick-previous-time ctime))))

after-init-hook の値をチェックして, my-load-init-time が最後に配置されていることに留意します.以下は, after-init-hook の値の例です.

(session-initialize recentf-mode my-emacs-init-time my-load-init-time)

2.5.1. Emacs 起動時の呼び出し順

Emacsは,以下の順番で起動します.

  1. Set before-init-time = (current-time) and Set after-init-time = nil
  2. Load early-init.el
  3. Run before-init-hook
  4. Run frame-initialize
  5. Set my-before-load-init-time = (current-time) => 独自変数で計測
  6. Load user's init files (.emacs, init.el, etc…)
  7. Set after-init-time = (current-time)
  8. Run after-init-hook
  9. Run emacs-startup-hook

emacs-init-time は,GUI生成と,GUI関連elの読み込み, before-init-hook で実行する関数の実行時間,およびearly-init.elとユーザ定義のinitファイル群の読み込み時間の合計値となる.

すでに after-init-time で時間計測が完了しているので,その後に続く after-init-hookemacs-startup-hook に登録された関数の実行時間は emacs-init-time の値に含まれない.

より端的に言えば, emacs-init-time = before-init-time - after-init-time である.

一方, my-before-load-init-timeafter-init-time を使って計算する算出する起動時間には,GUI生成とGUI関連elの読み込み,early-init.elの読み込み,after-init-hook と before-init-hook にある関数の実行が含まれていない.

see also Startup Summary - GNU Emacs Lisp Reference Manual

2.6. 実行時間の個別計測

計測したい関数を measure-exec-time-list にリストすることで,対象とする関数の実行時間を計測して *Messages* バッファに書き込みます.

(defvar measure-exec-time-list nil)
(dolist (f measure-exec-time-list)
  (advice-add f :around #'ad:measure-exec-time))
;;;###autoload
(defun ad:measure-exec-time (f &rest arg)
  "If `measure-exec-time-list' is non-nil, measure exe time for each function."
  (if measure-exec-time-list
      (let ((inhibit-message nil)
            (message-log-max 5000)
            (begin (current-time)))
        (apply f arg)
        (message
         (format "--- %.3f[ms] %S"
                 (* 1000 (float-time (time-subtract (current-time) begin)))
                 (if (byte-code-function-p f)
                     nil ;; not includes closure
                   f)))) ;; FIXME
    (apply f arg)))

2.7. GCサイズの最適化

起動時に発生するガベージコレクトを防きます.起動後に使用しているメモリサイズを超えていれば良さ気. garbage-collection-messages を設定しておくと,ガベージコレクトが生じる時にメッセージが出るようになります.どの程度の時間を要したかを知りたい場合は,下記のように post-gc-hook に表示用関数を設定しておけばよいです.

early-init.el で設定すると高速起動に効果があります.

(setq gc-cons-threshold (* 16 1024 1024)) ;; [MB]
;; (setq garbage-collection-messages t)
;; (defvar my-gc-last 0.0)
;; (add-hook 'post-gc-hook
;;           #'(lambda ()
;;               (message "GC! > %.4f[sec]" (- gc-elapsed my-gc-last))
;;               (setq my-gc-last gc-elapsed)))

2.8. Common Lisp を使う

一般的には次のように記載すればOKです.

(eval-when-compile
  (require 'cl-lib nil t))

しかし,いくつかのパッケージは,バイトパイル時の警告を回避するために, cl-lib と同様に eval-when-compile の中に記述する必要が出てきます. init.el の成長に伴い,対象パッケージが増えたので,別ファイル(init-eval.el)に括り出しました.バイトコンパイルするときだけ, init-eval.el を読み込みます.

例えば, init.el のコンパイルを次のようにします.

/Applications/Emacs.app/Contents/MacOS/Emacs -l ~/.emacs -l ~/Dropbox/emacs.d/config/init-eval.el -batch -f batch-byte-compile-if-not-done ~/Dropbox/emacs.d/config/init.el

これなら init.eleval-when-compile を書くのと同じに見えますが,そうしてしまうと,バイトコンパイルしない状態の init.el を読み込む場合に, eval-when-compile の中身が起動のたびにすべて実行されてしまいます.もちろん起動が重くなります.

そもそも init.org を編集対象にしていて, init.el を手動でバイトコンパイルすることはないので,私のフローには合っています.

(eval-when-compile
  (require 'init-eval nil t))

2.9. メッセージ出力の抑制

メッセージバッファに "Waiting for git…" が残るのを抑制します.ミニバッファにも表示しないので,存在を完全に消せます.非常に強力ですが,逆に,主に他の設定の読み込み順に起因して,意図しないところで別な関数が出力する(本来は出力させたい)メッセージも削除してしまう可能性があるので注意が必要です.

(defvar my-suppress-message-p t)
(defun ad:suppress-message (f &rest arg)
  (if my-suppress-message-p
      (let ((inhibit-message t)
            (message-log-max nil))
        (apply f arg))
    (apply f arg)))

;; Suppress printing "Waiting for git..." from version.el
(advice-add 'emacs-repository-branch-git :around #'ad:suppress-message)
(advice-add 'emacs-repository-version-git :around #'ad:suppress-message)

2.10. 警告の抑制

起動時に警告が出てうっとうしい場合に使います.起動直後に呼ばれるように, .emacs の上の方に書いておくとよいと思います.

(setq byte-compile-warnings
      '(not free-vars unresolved callargs redefine obsolete noruntime
            cl-functions interactive-only make-local))
;; (setq byte-compile-warnings '(not obsolete))
(setq ad-redefinition-action 'accept)

特定のパッケージの deprecated メッセージを抑制します. my-exclude-deprecated-packages にパッケージを列挙します.以下は cltls の出力だけ抑制して他のパッケージの場合は表示します.

;;;###autoload
(defun ad:do-after-load-evaluation (abs-file)
  "Evaluate all `eval-after-load' forms, if any, for ABS-FILE.
ABS-FILE, a string, should be the absolute true name of a file just loaded.
This function is called directly from the C code."
  ;; Run the relevant eval-after-load forms.
  (dolist (a-l-element after-load-alist)
    (when (and (stringp (car a-l-element))
               (string-match-p (car a-l-element) abs-file))
      ;; discard the file name regexp
      (mapc #'funcall (cdr a-l-element))))
  ;; Complain when the user uses obsolete files.
  (when (string-match-p "/obsolete/[^/]*\\'" abs-file)
    ;; Maybe we should just use display-warning?  This seems yucky...
    (let* ((file (file-name-nondirectory abs-file))
           (package (intern (substring file 0
                                                         (string-match "\\.elc?\\>" file))
                            obarray))
           (msg (unless (memq package my-exclude-deprecated-packages)
                  (format "Package %s is deprecated" package)))
                 (fun (lambda (msg) (message "%s" msg))))
      (when (or (not (fboundp 'byte-compile-warning-enabled-p))
                (byte-compile-warning-enabled-p 'obsolete package))
        (cond
               ((bound-and-true-p byte-compile-current-file)
                ;; Don't warn about obsolete files using other obsolete files.
                (unless (and (stringp byte-compile-current-file)
                                   (string-match-p "/obsolete/[^/]*\\'"
                                                               (expand-file-name
                                                                      byte-compile-current-file
                                                                      byte-compile-root-dir)))
                  (byte-compile-warn "%s" msg)))
         (noninteractive (funcall fun msg)) ;; No timer will be run!
               (t (run-with-idle-timer 0 nil fun msg))))))

  ;; Finally, run any other hook.
  (run-hook-with-args 'after-load-functions abs-file))
;; (setq byte-compile-warnings '(obsolete))
;; Suppress warning on cl.el loading
(defvar my-exclude-deprecated-packages '(cl tls))
(advice-add 'do-after-load-evaluation :override #'ad:do-after-load-evaluation)

2.11. エラー表示の抑制

普段使いでは要らないので抑制します.

(setq debug-on-error nil)

2.12. バッファの保存を静かにする

ビルトインの file.el にある変数を使うと,バッファ保存時に表示されるメッセージが減り "Saving file …" がメッセージバッファに記録されなくなります.バッファ保存時以外で,メッセージ出力を抑制したい場合は, shut-up.el が便利です.

(setq save-silently t) ;; No need shut-up.el for saving files.

2.13. 環境設定

el-get でインストールしているパッケージ群と,個別管理している org-mode を使えるようにパスを通します.

;; Build and check `my-package-dir'
(defvar my-package-dir nil)
(defvar my-use-el-get emacs-version ;; nil
  "If version number is provided, use packages installed via el-get.")
(defvar my-elget-package-dir (format "~/.emacs.d/%s/packages" my-use-el-get))
(when my-use-el-get
  (setq my-package-dir my-elget-package-dir))
(unless (file-directory-p my-package-dir)
  (user-error "%s does NOT exist. Run setup script first" my-package-dir))

;; setenv "SYNCROOT"
(unless (getenv "SYNCROOT")
  (setenv "SYNCROOT" (concat (getenv "HOME") "/Dropbox" )))

(defun my-path-setter (path-list target-path)
  "Utility function to set PATH-LIST to TARGET-PATH."
  (dolist (x path-list)
    (add-to-list target-path (file-name-as-directory x))))

;; (1) theme-path
(my-path-setter
 `(,my-package-dir "~/.emacs.d/lisp") 'custom-theme-load-path)

;; (2) exec-path
(my-path-setter
 `("/usr/bin" "/usr/local/bin" "/opt/homebrew/bin"
   ,(expand-file-name "~/.cask/bin")
   ,(expand-file-name "~/devel/git/tern/bin")
   ,(expand-file-name "~/.go/bin")
   ,(expand-file-name "~/Dropbox/emacs.d/bin")
   ,(expand-file-name "~/Dropbox/scripts")
   "/usr/local/opt/llvm/bin"
   "C:/cygwin64/bin" "C:/msys64/usr/bin" "C:/msys64/mingw64/bin"
   "/Applications/UpTex.app/teTeX/bin"
   "/Applications/UpTeX.app/Contents/Resources/TEX/texbin"
   "/Applications/LibreOffice.app/Contents/MacOS/"
   "/Applications/qt_color_picker.app/Contents/MacOS/"
   "/usr/local/opt/imagemagick@6/bin")
 'exec-path)

(setenv "PATH" (concat "/opt/homebrew/bin:" (getenv "PATH")))
(setenv "PATH" (concat "/usr/local/bin:" (getenv "PATH")))
(setenv "GOPATH" (concat (getenv "HOME") "/.go"))
;; you may want to use exec-path-from-shell.el.

;; 拡張パッケージにパスを通す
;; M-x list-load-path-shadows
(let* ((g "~/devel/git/")
       (od "org-mode")
       (l `("~/Dropbox/config"
            "~/.emacs.d/lisp"
            ,my-package-dir ;; may include a path to org
            ,(concat g od "/lisp") ;; override the path to org
            ,(concat g od "/contrib/lisp")
            )))
  (my-path-setter l 'load-path))

;; (require 'use-package nil t) ;; 24[ms]
;; (require 'leaf nil t) ;; 2[ms]
;; (when (require 'benchmark-init nil t)
;;   (add-hook 'after-init-hook #'benchmark-init/deactivate))

2.14. 後方互換設定

(when (version< emacs-version "29.0")
  (load "~/Dropbox/emacs.d/config/init-compat.el" nil t))

2.15. [my-load-package-p] 設定の読み込みフラグを確認する

autoload-if-found のサポート関数です.

my-disabled-packages 変数に,設定を無視するパッケージを記述しておくと, autoload-if-found 内でそれらを読み込まないようにします.読み込まなかったパッケージは,Messages バッファにそのことを報告します. autoload-if-found は本来の実装ではなく, my-disabled-packages を参照するように手を加えています.

(defun my-load-package-p (file)
  (let ((enabled t))
    (when (boundp 'my-disabled-packages)
      (dolist (package my-disabled-packages)
        (let ((name (car package))
              (flag (cdr package)))
          (when (and (stringp name)
                     (equal file name)
                     (not flag))
            (setq enabled nil)
            (message "--- `%s' was NOT loaded intentionally" name)))))
    enabled))

2.16. [autoload-if-found] 関数をリストで渡すautoload設定

この autoload-if-found の初出は,dot.emacs です.

autoload-if-foundwith-eval-after-load の組み合わせが基本です.最初に when 判定をすることで,パッケージが未インストールの状態に各種設定をスキップするので安全です.

(defvar my-skip-check-autoload-file t)
(defvar my-required-libraries nil)
(when (bound-and-true-p my-disabled-packages)
  (setq my-skip-check-autoload-file nil))

(defun autoload-if-found (functions file &optional docstring interactive type)
  "set autoload iff. FILE has found."
  (when (boundp 'my-required-libraries)
    (add-to-list 'my-required-libraries file)) ;; collect packages TBC later
  (when (or my-skip-check-autoload-file
            (and (my-load-package-p file)
                 (locate-library file))) ;; takes time here
    (dolist (f functions)
      (autoload f file docstring interactive type))
    t))

この init.el は,基本的にこの autoload-if-found に依存しています.遅延ロードに基づく高速化において必須の関数ですが,特にこだわらない人は use-package.el に依存するのをオススメします.以下, autoload-if-found を使った設定のテンプレートです.

(when (autoload-if-found
       '(f1 f2) ;; 関数リスト.要素の関数を呼ぶ時に,パッケージが読み込まれる
       "package-name" nil t) ;; 第4引数まで指定すると,M-x で補完対象になる.

  ;; (0) バイトコンパイル時に警告がでたら,対応するパッケージを記載
  ;; 現在は,バイトコンパイル時だけ,必要なパッケージの require を全て記載した
  ;; init-eval.el を読み込む方式に変更.ここでは eval-when-compile を使わない.
  ;; (eval-when-compile
  ;;   (require 'package-name nil t))

  ;; (1) パッケージが存在すれば, Emacs 起動時に読み込む設定群
  (push '("\\.hoge$" . package-name-mode) auto-mode-alist)
  (add-hook 'after-init-hook #'f1)
  (keymap-global-set "r" 'f2)
  (autoload-if-found '(hoge1 hoge2) "hoge" nil t)

  ;; (2) 遅延読み込みする設定群
  ;; autoload-if-found で遅延ロードする関数
  (with-eval-after-load "package-name"
    (setq v1 t)
    (setq v2 nil)
    (keymap-set package-name-map "q" 'f1))

  ;; 関連するパッケージの遅延ロード
  (with-eval-after-load "package-name2"
    (setq v3 t)
    (setq v4 nil))

  ;; 起動時に実行せず,最初に任意の発行するコマンドまで遅延ロードさせる
  ;; see https://github.com/takaxp/postpone
  (with-eval-after-load "postpone"
    (setq v5 nil)))

このカスタマイズされた autoload-if-found は引数にリストのみを受け付けます.欠点は,リスト内の全て関数に個別の docstring を設定できないため,ヘルプ画面で何の関数なのかの情報があまり表示されないことです.ただ, require でパッケージを読み込んでしまえば,本来の docstring の内容が正しく反映されるので,すべての情報を確認できるようになります.また, interactivet にすれば, M-x で当該関数がリストアップされるようになります.したがって, (docstring interactive) = (nil t) をデフォルトで指定すればOKです. type には, macrokeymap を 指定して,関数以外の情報をオートロード対象にします.

コンパイル時に Warning: the function ‘google-this’ is not known to be defined. のようなメッセージが出る時には, autoload-if-foundfunctions に当該関数を追加すると,警告されなくなります. declare-function を追記せずに済みます.

locate-library は,高速化を求める状況では,意外と高コストです.なので,それらを評価しないためのフラグ skip-file-checking を追加しました.各パッケージの設定は,基本的に with-eval-after-load に括られていることを前提にすれば,起動が停止する自体は避けられるので,悪くない選択です.もし変なことになってしまっても, skip-file-checkingnil にすれば,従来のようにパッケージの存在を確認し,安全に起動できます.

2.17. [postpone.el] Emacs起動してから使い始めるタイミングで実行する

helm-config に紐付けていた遅延呼び出し関数および設定を,自作の postpone.el に紐付けました. helm-config に紐付けていた時は, M-x を初めて使う時に複数の設定が読み込まれましたが,今回の設定では,Emacs起動後の最初のアクション時に読み込まれます(一部のコマンドを除く).これには M-x も含まれます.この init.el に記載された遅延読み込みは約660[ms]にも及びます.通常起動では約400[ms]を要している(コンソール起動の場合は,約100[ms])ので,もし遅延読み込みしなければ,Emacsの起動が1秒を超えてしまうことになります.

起動後,即座にEmacs終了するシーンでは, postpone.el に紐付けた設定が活性化すると無駄になります.それを回避するために, this-command の値を確認して,条件に合う場合だけ postpone.el が読み込まれる(結果的に紐付いている全ての設定が活性化する)ようにしています.

;; Copied from postpone-pre.el for speed up -- begin ;;;;;;;;;;;;;;;;;;;;;
(defvar postpone-pre-init-time nil
  "A variable to store the duration of loading postponed packages.")

(defcustom postpone-pre-exclude '(self-insert-command
                                  save-buffers-kill-terminal
                                  exit-minibuffer)
  "A list of commands not to activate `postpone-mode'."
  :type 'sexp
  :group 'postpone)

;;;###autoload
(defun postpone-pre ()
  (interactive)
  (unless (or my-secure-boot
              (memq this-command postpone-pre-exclude)
              postpone-pre-init-time)
    (message "Activating postponed packages...")
    (let ((t1 (current-time)))
      (postpone-kicker 'postpone-pre)
      (setq postpone-pre-init-time (float-time
                                    (time-subtract (current-time) t1))))
    (message "Activating postponed packages...done ( %4d [ms])"
             (* postpone-pre-init-time 1000))))

(autoload 'postpone-kicker "postpone" nil t)
(add-hook 'pre-command-hook #'postpone-pre) ;; will be removed in postpone.el.
;; Copied from postpone-pre.el for speed up -- end ;;;;;;;;;;;;;;;;;;;;;;;

(setq postpone-pre-exclude
      '(self-insert-command
        newline
        forward-char
        backward-char
        delete-char
        delete-backward-char
        save-buffer
        save-buffers-kill-terminal
        electric-newline-and-maybe-indent
        exit-minibuffer))

;; 起動後X秒何もしない場合は自動でキック (related to setting on org-agenda)
(defvar my-default-loading-delay 5) ;; [s]
(unless (or noninteractive my-secure-boot)
  (run-with-idle-timer (+ 5 my-default-loading-delay) nil #'postpone-pre))

2.18. [future-time-p] 指定時刻が本日の未来の時刻かを判定

今日の時刻に限定して,指定時刻が過去の時間かどうかを判定します. run-at-time が想定通りに動かず,起動時に run-at-time に登録した関数が走ってしまうので,この判定が non-nil の時だけタイマー登録します. timeHH:MM の書式で与えます.指定時刻と現在時刻が同じ場合は,過去とみなします.

;;;###autoload
(defun future-time-p (time)
  "Return non-nil if provided TIME formed of \"10:00\" is the future time."
  (not (time-less-p
        (apply 'encode-time
               (let ((t1 (decode-time))
                     (t2 (parse-time-string time)))
                 (setf (nth 0 t1) 0)
                 (setf (nth 1 t1) (nth 1 t2))
                 (setf (nth 2 t1) (nth 2 t2))
                 t1))
        (current-time))))

;; For instance,
;; (when (future-time-p "10:00") (run-at-time...))

以下は古い実装.指定時刻と現在時刻が同じ場合は,タイマー登録の動作確認が楽だったこともあり,未来とみなしていた.

(defun passed-clock-p (target)
  (let ((hour nil)
        (min nil)
        (current-hour nil)
        (current-min nil))
    (when (string-match "\\([0-2]?[0-9]\\):\\([0-5][0-9]\\)" target)
      (setq hour (substring target (match-beginning 1) (match-end 1)))
      (setq min (substring target (match-beginning 2) (match-end 2)))
      (setq current-hour (format-time-string "%H" (current-time)))
      (setq current-min (format-time-string "%M" (current-time)))
      (< (+ (* (string-to-number hour) 60)
            (string-to-number min))
         (+ (* (string-to-number current-hour) 60)
            (string-to-number current-min))))))

2.19. [library-p] load-path にライブラリがあるかを判定

パッケージが load-path に存在していて使える状態にあるかを調べます.もし存在しなければ,メッセージバッファに [NOT FOUND] を刻みます.

libraries には複数のパッケージ名を指定でき,すべてが使える状態の場合のみ t が返ります.

"org" を渡したり, '("org" "helm") を渡したりできます.

(defun library-p (libraries)
  "Return t when every specified library can be located. "
  (let ((result t))
    (mapc (lambda (library)
            (unless (locate-library library)
              (message "--- NOT FOUND: %s" library)
              (setq result nil)))
          (if (listp libraries)
              libraries
            (list libraries)))
    result))

2.20. run-at-time をハックして diary-lib.el の読み込みを抑制

run-at-time を使ったタイマー設定時に,時刻を文字列で指定すると, diary-lib.el を読み込み, diary-entry-time を使いますが,そのために起動時の時間を使うのはもったいないので, diary-entry-time だけを init.el に移植し, run-at-time をハックして diary-lib.el を読み込まないようにします.

init.elrun-at-time を使用する設定を記述すると,下記の設定を init.el に書く必要があるので, init.el では run-with-idle-timer を使うようにします.

;; originally defined in `diary-lib.el'
(defun diary-entry-time (s)
  "Return time at the beginning of the string S as a military-style integer.
For example, returns 1325 for 1:25pm.

Returns `diary-unknown-time' (default value -9999) if no time is recognized.
The recognized forms are XXXX, X:XX, or XX:XX (military time), and XXam,
XXAM, XXpm, XXPM, XX:XXam, XX:XXAM, XX:XXpm, or XX:XXPM.  A period (.) can
be used instead of a colon (:) to separate the hour and minute parts."
  (let (case-fold-search)
    (cond ((string-match                ; military time
            "\\`[ \t\n]*\\([0-9]?[0-9]\\)[:.]?\\([0-9][0-9]\\)\\(\\>\\|[^ap]\\)"
            s)
           (+ (* 100 (string-to-number (match-string 1 s)))
              (string-to-number (match-string 2 s))))
          ((string-match                ; hour only (XXam or XXpm)
            "\\`[ \t\n]*\\([0-9]?[0-9]\\)\\([ap]\\)m\\>" s)
           (+ (* 100 (% (string-to-number (match-string 1 s)) 12))
              (if (equal ?a (downcase (aref s (match-beginning 2))))
                  0 1200)))
          ((string-match        ; hour and minute (XX:XXam or XX:XXpm)
            "\\`[ \t\n]*\\([0-9]?[0-9]\\)[:.]\\([0-9][0-9]\\)\\([ap]\\)m\\>" s)
           (+ (* 100 (% (string-to-number (match-string 1 s)) 12))
              (string-to-number (match-string 2 s))
              (if (equal ?a (downcase (aref s (match-beginning 3))))
                  0 1200)))
          (t diary-unknown-time))))

;; Avoid to load diary-lib to use `diary-entry-time'
(defun run-at-time (time repeat function &rest args)
  "Perform an action at time TIME.
Repeat the action every REPEAT seconds, if REPEAT is non-nil.
REPEAT may be an integer or floating point number.
TIME should be one of:

- a string giving today's time like \"11:23pm\"
  (the acceptable formats are HHMM, H:MM, HH:MM, HHam, HHAM,
  HHpm, HHPM, HH:MMam, HH:MMAM, HH:MMpm, or HH:MMPM;
  a period `.' can be used instead of a colon `:' to separate
  the hour and minute parts);

- a string giving a relative time like \"90\" or \"2 hours 35 minutes\"
  (the acceptable forms are a number of seconds without units
  or some combination of values using units in `timer-duration-words');

- nil, meaning now;

- a number of seconds from now;

- a value from `encode-time';

- or t (with non-nil REPEAT) meaning the next integral multiple
  of REPEAT.  This is handy when you want the function to run at
  a certain \"round\" number.  For instance, (run-at-time t 60 ...)
  will run at 11:04:00, 11:05:00, etc.

The action is to call FUNCTION with arguments ARGS.

This function returns a timer object which you can use in
`cancel-timer'."
  (interactive "sRun at time: \nNRepeat interval: \naFunction: ")

  (when (and repeat
             (numberp repeat)
             (< repeat 0))
    (error "Invalid repetition interval"))

  (let ((timer (timer-create)))
    ;; Special case: nil means "now" and is useful when repeating.
    (unless time
      (setq time (current-time)))

    ;; Special case: t means the next integral multiple of REPEAT.
    (when (and (eq time t) repeat)
      (setq time (timer-next-integral-multiple-of-time nil repeat))
      (setf (timer--integral-multiple timer) t))

    ;; Handle numbers as relative times in seconds.
    (when (numberp time)
      (setq time (timer-relative-time nil time)))

    ;; Handle relative times like "2 hours 35 minutes".
    (when (stringp time)
      (when-let ((secs (timer-duration time)))
              (setq time (timer-relative-time nil secs))))

    ;; Handle "11:23pm" and the like.  Interpret it as meaning today
    ;; which admittedly is rather stupid if we have passed that time
    ;; already.  (Though only Emacs hackers hack Emacs at that time.)
    (when (stringp time)
      ;; (require 'diary-lib) ;; *Modified*
      (let ((hhmm (diary-entry-time time))
                  (now (decode-time)))
              (when (>= hhmm 0)
                (setq time (encode-time 0 (% hhmm 100) (/ hhmm 100)
                                  (decoded-time-day now)
                                                    (decoded-time-month now)
                                  (decoded-time-year now)
                                  (decoded-time-zone now))))))

    (timer-set-time timer time repeat)
    (timer-set-function timer function args)
    (timer-activate timer)
    timer))

2.21. TODO [gcmh.el] idle 中にGC実行

;; (eval-when-compile
;;   (message "Loading gcmh... %s" (featurep 'gcmh))
;;   (require 'gcmh))

(defvar my-gcmh-idlegc-p nil)

;;;###autoload
(defun ad:garbage-collect (f)
  (unless my-gcmh-idlegc-p
    (message "[gcmh] Garbage collecting...")
    (message "[gcmh] Garbage collecting...done (%.3fs)"
             (gcmh-time (funcall f)))))

;;;###autoload
(defun ad:gcmh-idle-garbage-collect (f)
  (let ((my-gcmh-idlegc-p t))
    (funcall f)))

;;;###autoload
(defun my-gcmh-activate ()
  (cancel-timer my-gcmh-timer)
  (gcmh-mode 1))
(when (autoload-if-found '(gcmh-time gcmh-mode) "gcmh" nil t)
  (defvar my-gcmh-timer
    (unless noninteractive
      (run-with-idle-timer (+ 10 my-default-loading-delay)
                           nil #'my-gcmh-activate)))

  (with-eval-after-load "gcmh"
    (setq gcmh-verbose nil)
    (advice-add 'garbage-collect :around #'ad:garbage-collect)
    (advice-add 'gcmh-idle-garbage-collect
                :around #'ad:gcmh-idle-garbage-collect)))

2.22. [shut-up.el] Messages 出力を封じる(制御する)

shut-up.el というマクロがあり,現在はそちらを使っています.非常に強力です.

(setq message-log-max 5000) ;; メッセージバッファの長さ
(defvar shutup-p nil)

2.22.1. with-suppressed-message   obsolete

以下はそれ以前のアプローチ. recentf-save-listfind-file-hook にぶら下げていますが,そのままだと org-agenda の初回実行時にたくさんのメッセージが出てしまうところ,このマクロを介すだけで抑制可能です. message-log-max で制御できるのがすごい.

(defmacro with-suppressed-message (&rest body)
  "Suppress new messages temporarily in the echo area and the `*Messages*' buffer while BODY is evaluated."
  (declare (indent 0))
  (let ((message-log-max nil))
    `(with-temp-message (or (current-message) "") ,@body)))

2.23. Native comp が使えるかを判定する

;;;###autoload
(defun my-native-comp-p ()
  (when (fboundp 'native-comp-available-p)
    (native-comp-available-p)))

ついでに NativeComp のオプションを指定します.

(with-eval-after-load "comp"
  (setq native-comp-async-query-on-exit t)
  (setf comp-num-cpus (max 1 (- (num-processors) 2))))

2.24. Native comp の警告を非表示にする

Native comp 実行時に大量に表示される警告を非表示にします.

;; Native Compiling の最終のワーニング等をウィンドウに出さない
(setq native-comp-async-report-warnings-errors nil)

2.25. Native comp がコンパイルした後にメッセージを表示する

;;;###autoload
(defun my-native-comp-packages-done ()
  (message "Native Compilation...done"))
(with-eval-after-load "comp"
  (add-hook 'native-comp-async-all-done-hook #'my-native-comp-packages-done))

2.26. custom.el の指定

;; Suppress exporting of custom-set-variables (25.1 or later)
(setq custom-file (locate-user-emacs-file "custom.el"))

2.27. elget パッケージ群を native comp する

2;;;###autoload
(defun my-elget-nativecomp-all-packages ()
  (interactive)
  (native-compile-async (format "~/.emacs.d/%s/el-get" emacs-version)
                        'recursively))

2.28. [exec-path-from-shell.el] PATH設定をシェルから継承する   obsolete

外部プログラムのサポートを得て動くパッケージは,設定の過程で「プログラムが見つからない」と怒られることがしばしばあります. exec-path-from-shell は,シェルに設定した PATH の情報を継承して exec-pathPATH を設定してくれます.私は起動時に環境を切り替えることがあるので使ってませんが,使われている方は多いようです.

(when (and (require 'exec-path-from-shell nil t)
           (memq window-system '(mac ns)))
  (exec-path-from-shell-initialize))

2.29. [eval-after-autoload-if-found] 遅延読み込み   obsolete

現在は使っていません.非推奨です.

with-eval-after-load とのペアでマクロ化したバージョンです.ただ,生成されるバイトコードに問題がありそうなので,最近は使っています.長らくお世話になっております.になりましたが,現在は, autoload-if-foundwith-eval-after-load の組み合わせを使っています.

Twitterでぼやいていたら @cvmatさんが降臨して次のマクロを作ってくださいました.感謝感謝.

autoload-if-found で遅延読み込みすると,eval-after-load と組み合わせるので,どうしてもインデントが増えてしまうのが欠点です.

例えば,cycle-buffer を遅延読み込みしたい場合, setq で変数を書き換えするために随分とインデントが進んでいます.

(when (autoload-if-found
       '(cycle-buffer cycle-buffer-backward) "cycle-buffer" nil t)

  (with-eval-after-load "cycle-buffer"
    (setq cycle-buffer-allow-visible t)
    (setq cycle-buffer-show-length 12)
    (setq cycle-buffer-show-format '(" [ %s ]" . " %s"))))

これをスッキリさせるために eval-after-autoload-if-found を導入します.記述がシンプルになり,行数も桁数もスッキリです.

(eval-after-autoload-if-found
 '(cycle-buffer cycle-buffer-backward) ;; autoload で反応させる関数
 "cycle-buffer" nil t nil      ;; 反応させた関数のコールで読むパッケージ指定
 '(;; パッケージ読み込み後の設定
   (setq cycle-buffer-allow-visible t)
   (setq cycle-buffer-show-length 12)
   (setq cycle-buffer-show-format '(" < %s >" . " %s"))))

さらに戻り値を判定して,グローバルなキーアサインもできます.存在しないパッケージの関数呼び出しを明示的に防ぐために有効です. hook 系の登録も同様です.

(when (eval-after-autoload-if-found
       '(cycle-buffer cycle-buffer-backward) "cycle-buffer" nil t nil
       '((setq cycle-buffer-allow-visible t)
         (setq cycle-buffer-show-length 12)
         (setq cycle-buffer-show-format '(" < %s >" . " %s"))
         ;; パッケージのキーアサインはこちら
         ;; (keymap-set xxx-map "q" 'hoge)
         ))
  ;; グローバルはこちら
  (keymap-global-set "M-]" 'cycle-buffer)
  (when (display-graphic-p)
    (keymap-global-set "M-[" 'cycle-buffer-backward))
  ;; パッケージに紐付いたフックはこちらへ
  ;; (add-hook 'xxx-hook #'hogehoge)
  ;;
  ;; ビルドインではないmodeの auto-mode-alist 設定も必要ならこちに記述
  ;; (push '("\\.hoge$" . hoge-mode) auto-mode-alist)
  )

なお,第四引数 (functions file docstring interactive) まで指定すれば, M-x の呼び出し候補に functions で指定した関数が補完表示されます.

2.29.1. 関数版

関数版にリスト my-disabled-packages を追加しました.このリストに事前に Lisp ファイル名を入れておくと,一切の設定をスキップするものです. eval-after-atoload-if-found を定義する前に次のような変数を設定しておきます.バイトコンパイルしていないファイルに書いておけば,パッケージのON/OFFを簡単に制御できます.

ただ問題点として,最後の引数に入れる関数がバイトコンパイル時に展開されないようで,出来上がったバイトコードが高速化に寄与しているのか不明です.簡易的な実験ではだいぶ差があるようで,最近は autoload-if-foundwith-eval-after-loadeval-when-compile の組み合わせで設定を書いて,遅延ロードと高速読み込みを実現しています. my-disabled-packagesautoload-if-found に組み込みました.

(setq my-disabled-packages ;; 追加されていない場合は標準で読み込む
      '(("web-mode" . nil) ;; 読み込まない
        ("org" . t))) ;; 読み込む

https://gist.github.com/3513287

;; https://github.com/zk-phi/setup
;; (when (require 'setup nil t)
;;   (setup-initialize))
(defun eval-after-autoload-if-found
    (functions file &optional docstring interactive type after-body)
  "Set up autoload and eval-after-load for FUNCTIONS iff. FILE has found."
  (let ((enabled t)
        (package nil))
    (message "--- %s" file)
    (when (bound-and-true-p my-disabled-packages)
      (dolist (package my-disabled-packages)
        (let ((name (car package))
              (flag (cdr package)))
          (when (and (stringp name) (equal file name))
            (unless flag
              (setq enabled nil)
              (message "--- A setting for `%s' was NOT loaded explicitly"
                       name))))))
    ;; if disabled then return nil.
    (when (and enabled (locate-library file))
      (mapc (lambda (func)
              (autoload func file docstring interactive type))
            (if (listp functions)
                functions
              (list functions)))
      (when after-body
        (eval-after-load file `(progn ,@after-body)))
      t)))

2.29.2. マクロ版

以下はマクロ版です.引数の渡し方が関数と少し違うので要注意です.

https://gist.github.com/3499459

(defmacro eval-after-autoload-if-found
    (functions file &optional docstring interactive type &rest after-body)
  "Set up autoload and eval-after-load for FUNCTIONS iff. FILE has found."
  `(let* ((functions ,functions)
          (docstring ,docstring)
          (interactive ,interactive)
          (type ,type)
          (file ,file))
     (when (locate-library file)
       (mapc (lambda (func)
               (autoload func file docstring interactive type))
             (if (listp functions)
                 functions
               (list functions)))
       ,@(when after-body
           `((eval-after-load file '(progn ,@after-body))))
       t)))

2.30. DONE [window-focus-p] フォーカス判定

フォーカスが当たっているのかを判定するための関数です.

(2020-06-04) frame-focus-state という標準関数がありました.

;;;###autoload
(defun window-focus-p ()
  (frame-parameter (selected-frame) 'last-focus-update)))

3. コア設定

Emacs を操作して文書編集する上で欠かせない設定です.

3.1. 言語/文字コード

徹底的にUTF-8に合わせます.

save-buffer-coding-system を設定すると, buffer-file-coding-system の値を無視して,指定した save-buffer-coding-system の値でバッファを保存する.つまり, buffer-file-coding-system に統一するなら設定不要.

set-default-coding-systemsprefer-coding-system を設定すると,同時に file-name-coding-system=,=set-terminal-coding-system=,=set-keyboard-coding-system も設定される. prefer-coding-system は,文字コード自動判定の最上位判定項目を設定する.

set-buffer-file-coding-system は,Xとのデータやりとりを設定する.

(prefer-coding-system 'utf-8-unix)
;; (set-language-environment "Japanese") ;; will take 20-30[ms]
(set-locale-environment "en_US.UTF-8") ; "ja_JP.UTF-8"
(set-default-coding-systems 'utf-8-unix)
(set-selection-coding-system 'utf-8-unix)
(set-buffer-file-coding-system 'utf-8-unix)
(setq locale-coding-system 'utf-8-unix)
(when (eq system-type 'windows-nt)
  (set-clipboard-coding-system 'utf-16le) ;; enable copy-and-paste correctly
  (setq system-time-locale "C")) ;; format-time-string %a, not 日 but Sun

3.2. 日本語入力

NSビルド用のインラインパッチを適用している場合に使います.Lion でも使える自分用にカスタマイズした inline-patch を使っています.

(when (fboundp 'mac-add-key-passed-to-system)
  (setq default-input-method "macOS")
  (mac-add-key-passed-to-system 'shift))
(when (eq system-type 'gnu/linux)
  (keymap-global-set "<hiragana-katakana>" 'toggle-input-method)
  (push "/usr/share/emacs/site-lisp/anthy" load-path)
  (push "/usr/share/emacs/site-lisp/emacs-mozc" load-path)
  (set-language-environment "Japanese")

  (if (require 'mozc nil t)
      (progn
        (setq default-input-method "japanese-mozc")
        (custom-set-variables
         '(mozc-candidate-style 'overlay)))

    (when (require 'anthy nil t) ;; sudo yum install emacs-anthy-el
      ;; if get error
      (load-file "/usr/share/emacs/site-lisp/anthy/leim-list.el")
      (setq default-input-method 'japanese-anthy))))

3.3. 基本キーバインド

次の機能にキーバインドを設定する.

  • Cmd+V でペースト(Mac用)
  • Cmd と Option を逆にする(Mac用)
  • 削除
(when (eq system-type 'darwin)
  (when (boundp 'ns-command-modifier) (setq ns-command-modifier 'meta))
  (when (boundp 'ns-alternate-modifier) (setq ns-alternate-modifier 'super))
  (when (boundp 'ns-pop-up-frames) (setq ns-pop-up-frames nil))
  (keymap-global-set "M-v" 'yank)
  (keymap-global-set "<ns-drag-file>" 'ns-find-file))
(keymap-global-set "<delete>" 'delete-char)
(keymap-global-set "<kp-delete>" 'delete-char)

3.4. ナローイングするか

ナローイングを有効にします.ナローイングを知らないユーザが「データが消えた!」と勘違いしないように,デフォルトでは無効になっています.

Org Mode でナローイングを使う場合は,特に設定しなくてもOKです.

(put 'narrow-to-region 'disabled nil)

fancy-narrow を使うと,通常のナローイングではバッファ上で表示しなくなる領域を目立たないように残すことができます.

;; late-init.el
(autoload-if-found
 '(fancy-narrow-to-region
   fancy-widen
   org-fancy-narrow-to-block
   org-fancy-narrow-to-element
   org-fancy-narrow-to-subtree)
 "fancy-narrow" nil t)

3.5. バッファの終わりでのnewlineを禁止する

;; Avoid adding a new line at the end of buffer
(setq next-line-add-newlines nil)

3.6. 常に最終行に一行追加する

;; Limit the final word to a line break code (automatically correct)
(setq require-final-newline t)

3.7. 長い文章を右端で常に折り返す

さらに折り返しのマークを右側の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)))
;; fringeに表示するマークの形状を変更
(define-fringe-bitmap 'right-curly-arrow
  [#b00000000
   #b00000000
   #b00000000
   #b00000000
   #b01111110
   #b01111110
   #b00000110
   #b00000110])

3.8. マウスで選択した領域を自動コピー

マウスで選択すると,勝手にペーストボードにデータが流れます.

(setq mouse-drag-copy-region t)

3.9. compilation buffer でのデータ表示で自動するスクロールする

nil のままだと,出力が続いてもスクロールされないので,自動的にスクロールされるように設定.

(setq compilation-scroll-output t)

3.10. NS版でスクロール時の表示乱れを補正

;; late-init.el
(when (eq window-system 'ns)
  ;; NS版でスクロール後に表示が消えるのを回避(ただしちらつく)
  (defun ad:scroll (_ARG)
    (redraw-display))
  (advice-add 'scroll-up :after #'ad:scroll)
  (advice-add 'scroll-down :after #'ad:scroll))

3.11. 水平方向の自動スクロール調整

  • 違和感なく水平方向にスクロールさせる.
(setq hscroll-margin 40)

3.12. C-x C-c で容易にEmacsを終了させないように質問する

y-or-n-p もしくは yes-or-no-p を指定するだけです.

(setq confirm-kill-emacs 'yes-or-no-p)

以前は, C-x C-c を以下の関数に割り当てて,任意の質問文で入力を求めていました.

;; A simple solution is (setq confirm-kill-emacs 'y-or-n-p).
(defun confirm-save-buffers-kill-emacs (&optional arg)
  "Show yes or no when you try to kill Emacs"
  (interactive "P")
  (cond (arg (save-buffers-kill-emacs))
        (t
         (when (yes-or-no-p "Are you sure to quit Emacs now? ")
           (save-buffers-kill-emacs)))))

(keymap-global-set "C-x C-c" 'confirm-save-buffers-kill-emacs)

3.13. パッケージ管理

Cask+Palletの環境を採用しました.それまでは,特定のディレクトリに必要な elisp をダウンロードしておいたり,git から取り寄せて,それらをload-pathに設定するスクリプトを準備するなど,個人的なルールで運用してきましたが,希望の機能をCaskが提供しているので,Emacs24.4になるタイミングで移行しました.

ただし,頒布元が危ういようなファイルはやはり個人で管理しておきたいので,Caskで管理する対象は,MEPLA経由で入手可能なメンテナンスが行き届いたパッケージに限定しています.また,普通の使い方(casl.elを読み込んで初期化)をしていると,起動時に少し時間を要するので,所定のディレクトリにCaskで取り寄せたすべてのファイルをコピーして,そのディレクトリだけをload-pathで指定するという使い方もしています.今のところ大きな問題は生じていません.

3.13.1. [cask-mode.el] モード設定

;; late-init.el
(when (autoload-if-found '(cask-mode) "cask-mode" nil t)
  (push '("/Cask\\'" . cask-mode) auto-mode-alist))

3.13.2. Cask のセットアップ

以下は自分用のメモです.

  1. curl -fsSkL https://raw.github.com/cask/cask/master/go | python
  2. ~/.cask/bin に PATH を通す (see .zshenv, export PATH="${HOME}/.cask/bin:{$PATH}")
  3. cask upgrade
  4. cd ~/.emacs.d
  5. cask init ;; ~/.emacs.d/Cask が存在しない場合だけ実行
  6. cask install

3.13.3. load-path を一箇所にして起動を高速化

Caskを使うと,個々のパッケージが独立にload-pathに設定されます.これにより依存関係がスッキリするわけですが,数が増えると起動時間が遅くなります.重いです.自分の例では,800[ms]のオーバーヘッドでした.これを避けるには,load-pathを一箇所に集約することが効きます.オーバーヘッドは約100[ms]まで削減できました.場合によっては依存関係に問題が生じる可能性がありますが,今のところは問題になっていません.

  1. ~/.emacs.d/.cask/package/<emacs-version> なるフォルダを作る
  2. ~/.emacs.d/.cask/24.4.1/elpa/*/*~/.emacs.d/.cask/24.4.1/elpa/*/lisp/* をすべて上記フォルダにコピー
  3. ~/.emacs で, ~/.emacs.d/.cask/package/<emacs-version> を load-path に設定し,Caskは読み込まない

M-x lis-packges を使って新しいパッケージをインストールする時だけ,以下のフラグを nil に書き換えてEmacsを起動します. load-path-setter は独自関数です(普通に add-to-list で追加するのと同じです)

(defconst cask-package-dir
  (format "~/.emacs.d/.cask/package/%s" emacs-version))
(if t
    (load-path-setter `(,cask-package-dir) 'load-path)
  (when (or (require 'cask "~/.cask/cask.el" t)
            (require 'cask "/usr/local/opt/cask/cask.el" t)) ;; Homebrew
    (when (fboundp 'cask-initialize) (cask-initialize))) ;; 800[ms]
  (when (require 'pallet nil t)
    (when (fboundp 'pallet-mode) (pallet-mode t))))

Cask で新しいパッケージを導入したり,既存のパッケージを更新したら,その都度,package ディレクトリにコピーします.手動でやると面倒なので,次のようなスクリプトで対処します.アルファリリースなどに対応するときなど,少し調整が必要です.

#!/bin/sh

LOADPATH=$HOME/.emacs.d/lisp
SOURCEPATH=$HOME/Dropbox/emacs.d
EVALWCOMPILE="$SOURCEPATH/config/init-eval.el"

while getopts t:hd opt
do
case ${opt} in
t)
TARGET=${OPTARG};;
d)
echo "--- Remove byte compiled files."
rm -rf $LOADPATH/*.elc
rm -rf $SOURCEPATH/config/*.elc
rm -rf ${HOME}/devel/git/org-mode/lisp/*.elc
exit 1;;
h)
echo "TARGET LIST:";
echo "  actex";
echo "  org-sync";
echo "  yatex";
echo "e.g."
echo "> $0 -t anything"
exit 1;;
\?)
exit 1;;
esac
exit 0
done

if [ ! -f $HOME/.zsh.local ]; then
    echo "~/.zsh.local does NOT exist."
    exit 0
fi

if [ ! -d $HOME/.emacs.d ]; then
    echo "~/.emacs.d does NOT exist."
    exit 0
fi

EMACS=`which emacs`
if [ $HOSTTYPE = "intel-mac" ]; then
    TARGETV=head # 25.1, 25.2, 25.3, 26.1, head
    EMACS="/Users/taka/devel/emacs/bin/$TARGETV/Emacs.app/Contents/MacOS/Emacs"
    if [ $TARGETV = "head" ]; then
        EMACS='/Applications/Emacs.app/Contents/MacOS/Emacs'
    fi
fi
# $EMACS -l ~/.emacs --script ~/Dropbox/config/tangle.el
$EMACS -q --script ~/Dropbox/config/tangle.el

echo "--- Starting batch-byte-compiles with GNU Emacs ($TARGETV)."

COMMAND="$EMACS -l ~/.emacs -l $EVALWCOMPILE -batch -f batch-byte-compile-if-not-done"

rm -rf $LOADPATH/*.elc
rm -rf $LOADPATH/utility-autoloads.el
rm -rf $SOURCEPATH/config/*.elc

cp -rf $SOURCEPATH/*.el $LOADPATH
cp -rf $SOURCEPATH/config/init.el $LOADPATH
cp -rf $SOURCEPATH/config/utility.el $LOADPATH

$COMMAND $LOADPATH/init.el
$COMMAND $LOADPATH/utility.el
#$COMMAND $SOURCEPATH/config/init.el $SOURCEPATH/config/utility.el $SOURCEPATH/*.el
$EMACS -Q --batch --eval="(update-file-autoloads \"$LOADPATH/utility.el\" t \"$LOADPATH/utility-autoloads.el\")"

if [ ! -f "$LOADPATH/utility-autoloads.el" ]; then
    echo "--- missing $LOADPATH/utility-autoloads.el!"
fi

# raw
# /Applications/Emacs.app/Contents/MacOS/Emacs -l ~/.emacs -l ~/Dropbox/emacs.d/config/init-eval.el -batch -f batch-byte-compile-if-not-done

# mv $SOURCEPATH/config/init.elc $HOME/.emacs.d
# mv $SOURCEPATH/config/utility.elc $HOME/.emacs.d
# mv $SOURCEPATH/*.elc $HOME/.emacs.d

echo "--- done."

なお,Emacs 29以降では, update-file-autoloads が使えなくなります.代わりに,指定ディレクトリに含まれる elisp ファイルに対して処理する loaddefs-generate を使います.定義先は, loaddefs-gen.el になります.引数が微妙に異なるので注意してください.

;; 28以前
(update-file-autoloads
 (concat (getenv "HOME") "/.emacs.d/lisp/late-init.el")
 t
 (concat (getenv "HOME") "/.emacs.d/lisp/init-autoloads.el"))

;; 29以降
(loaddefs-generate
 (concat (getenv "HOME") "/.emacs.d/lisp/")
 (concat (getenv "HOME") "/.emacs.d/lisp/init-autoloads.el"))

3.13.4. [paradox.el] パッケージ選択画面の改善

パッケージインストール用のバッファが多機能になります.スターが表示されたり,ミニバッファには様々な情報が表示されるようになります.基本の操作系は同じで,拡張部分は h を押すとミニバッファにディスパッチャが表示されます.

;; late-init.el
(when (autoload-if-found
       '(paradox-list-packages
         my-list-packages my-setup-cask
         my-reset-load-path
         advice:paradox-quit-and-close)
       "paradox" nil t)

  (with-eval-after-load "paradox"
    (defvar my-default-load-path nil)
    (defun my-list-packages ()
      "Call paradox-list-packages if available instead of list-packages."
      (interactive)
      (setq my-default-load-path load-path)
      (my-setup-cask)
      (if (fboundp 'paradox-list-packages)
          (paradox-list-packages nil)
        (list-packages nil)))

    (defun my-reset-load-path ()
      "Revert `load-path' to `my-default-load-path'."
      (shell-command-to-string "update-cask.sh link"))
    (setq load-path my-default-load-path)
    (message "--- Reverted to the original `load-path'."))

  ;; (declare-function advice:paradox-quit-and-close "init" (kill))

  (when (and (fboundp 'cask-load-path)
             (fboundp 'cask-initialize))
    (defun my-setup-cask ()
      "Override `load-path' to use cask."
      (when (or (require 'cask "/usr/local/opt/cask/cask.el" t)
                (require 'cask "~/.cask/cask.el" t))
        (setq load-path (cask-load-path (cask-initialize))))))

  (defun advice:paradox-quit-and-close (_kill)
    (my-reset-load-path))
  (advice-add 'paradox-quit-and-close :after
              #'advice:paradox-quit-and-close)

  (custom-set-variables
   '(paradox-github-token t))

  (unless noninteractive
    (when (fboundp 'paradox-enable)
      (paradox-enable))))

3.13.5. [el-get.el] パッケージ管理(GUI,CUI)

密かに el-get に移行.コマンドラインからも効率よく管理可能.

(autoload-if-found '(el-get-version
                     el-get-bundle my-elget-list my-elget-reset-links
                     el-get-cd el-get-remove el-get-update
                     el-get-install el-get-reinstall
                     my-elget-nativecomp-all-packages)
                   "elget-config" nil t)

3.14. インデント

オープンソース等で他の人のコードを修正する場合は,以下のような設定は良くないかもしれません.例えば差分を取ると見た目は変わらないのに,タブとスペースの違いから差分ありと判定されてしまい,意図しない編集履歴が残ることがあります.ただこの問題は,修正対象のファイルが限定されているならば, M-x tabifyM-x untabify で回避できそうです.

一方, org-mode のソースブロックは半角スペース統一されているため,この設定のほうが都合が良いです.

(setq-default tab-width 2)
(setq-default indent-tabs-mode nil)
(setq indent-line-function 'insert-tab)
(add-hook 'emacs-lisp-mode-hook #'my-emacs-lisp-mode-conf)
(add-hook 'emacs-lisp-mode-hook #'turn-on-font-lock)
;;;###autoload
(defun my-emacs-lisp-mode-conf ()
  (setq-local indent-tabs-mode t)
  (setq-local tab-width 8)
  (setq indent-line-function 'lisp-indent-line))

3.15. ファイルリンクを辿る時に確認のメッセージを出さない

そのまま辿ってファイルオープンします.

(setq vc-follow-symlinks t)

3.16. バッファが外部から編集された場合に自動で再読み込みする

auto-save-buffers を使っていれば,バッファは常に保存された状態になるため, revert が即座に反映されます.適宜バックアップツールと組み合わせないと,バッファが自動更新されてしまうので不安かもしれません.

;;;###autoload
(defun my-org-hide-drawers-all ()
  (when (eq major-mode 'org-mode)
    (org-cycle-hide-drawers 'all)))

;;;###autoload
(defun my-auto-revert-activate ()
  (global-auto-revert-mode 1)
  (remove-hook 'find-file-hook #'my-auto-revert-activate))
(unless noninteractive
  (add-hook 'find-file-hook #'my-auto-revert-activate)
  ;; revert されるのが org バッファのとき,自動的にドロワをたたむ
  ;; カーソルが (point-max) に移動してしまう場合は非推奨
  (with-eval-after-load "org"
    (add-hook 'after-revert-hook 'my-org-hide-drawers-all)))

3.17. マウススクロールをピクセル単位にする

(unless noninteractive
  (when (fboundp 'pixel-scroll-mode)
    (pixel-scroll-mode 1))) ;; 26.1

3.18. デフォルトディレクトリ

Version 27 から起動直後の C-x C-f のデフォルトが変わったようなので, ~/ に矯正する.

(when (version< "27.0" emacs-version)
  (with-eval-after-load "files"
    (defun ad:find-file-read-args (f prompt mustmatch)
      (when (equal default-directory "/")
        (setq default-directory "~/"))
      (funcall f prompt mustmatch))
    (advice-add 'find-file-read-args :around #'ad:find-file-read-args)))
;; see also a configuration of `directory-abbrev-alist'
;;;###autoload
(defun my-shorten-default-directory ()
  "Enforce to replace \"/home/...\" with \"~/\"."
  (setq default-directory (abbreviate-file-name default-directory)))
(add-hook 'find-file-hook #'my-shorten-default-directory 1)

3.19. TODO git用の設定ファイル読み込み

Emacs を高速に起動できれば,git のコミットメッセージを Emacs で編集できます.最低限の編集ができればよいので,通常は emacs -nw -Q あたりを core.editor に設定すると思いますが,それでも最低限の設定は加えておきたいものです.

Eamcs 29からは, --init-directory オプションで user-emacs-directory を変更して,任意の場所にある init.el を指定して起動できます.なので,例えば,下記の設定を init.el に記述して ~/.emacs.d/min 以下に配置する場合は,

emacs -nw --init-directory="$HOME/.emacs.d/min"

とすればOKです.

ただ NativeComp の場合には, ${HOME}/.emacs.d/min 以下に新たに eln-cache が生成されるので注意してください.

(add-to-list 'load-path (concat user-emacs-directory "min"))
(setq make-backup-files nil)
(setq auto-save-default nil)
(setq auto-save-list-file-prefix nil)
(setq line-number-display-limit-width 100000)
(setq vc-follow-symlinks t)
(setq confirm-kill-emacs 'y-or-n-p)
(keymap-global-set "RET" 'electric-newline-and-maybe-indent)
(keymap-global-set "C-M-t" 'beginning-of-buffer) ;; M-<
(keymap-global-set "C-M-b" 'end-of-buffer) ;; M->
(keymap-global-set "C-M-p" (lambda () (interactive) (other-window -1)))
(keymap-global-set "C-M-n" (lambda () (interactive) (other-window 1)))
(keymap-global-set "C-;" 'comment-dwim) ;; M-; is the defualt
(keymap-global-set "M-=" 'count-words)
(keymap-global-set "M-]" 'bs-cycle-next)
(keymap-global-set "M-[" 'bs-cycle-previous)
(keymap-global-set "C-c g" 'goto-line)
(defun my-open-scratch ()
  "Switch the current buffer to \*scratch\* buffer."
  (interactive)
  (switch-to-buffer "*scratch*"))
(keymap-global-set "C-M-s" #'my-open-scratch)
(when (eq system-type 'darwin)
  (when (boundp 'ns-command-modifier) (setq ns-command-modifier 'meta))
  (when (boundp 'ns-alternate-modifier) (setq ns-alternate-modifier 'super))
  (when (boundp 'ns-pop-up-frames) (setq ns-pop-up-frames nil))
  (keymap-global-set (kbd "M-v") 'yank)
  (keymap-global-set "<ns-drag-file>" 'ns-find-file))
(keymap-global-set "<delete>" 'delete-char)
(keymap-global-set "<kp-delete>" 'delete-char)

なお上記を回避するために, user-emacs-directory を変更せずに, min/init.el をロードすることもできます.だだし,非推奨です. .emacs で起動時に読み込む init 系ファイルを制御することになります.事前に early-init.el のどこかに (defvar my-boot-mode nil) を入れておいてください.

;; Boot mode selection
(cond
 ((eq my-boot-mode 'org) ;; To test the latest org
  (add-to-list 'load-path (expand-file-name "~/devel/git/org-mode/lisp"))
  (setq org-agenda-files '("~/Desktop/test/hoge.org")))
 ((eq my-boot-mode 'min) ;; minimum
  (load (concat user-emacs-directory "min/init.el"))) ;; ~/.emacs.d/min/init.el
 ((eq my-boot-mode 'space) ;; Spacemacs
  (load (concat (setq user-emacs-directory "~/.spacemacs.d/") "init.el")))
 (t ;; Normal mode. see also init-eval.el
  (require 'init nilt )))

CLIからは,次のように呼び出します.

emacs -nw --eval="(setq my-boot-mode 'min)" -l ~/.emacs

このやり方の問題点は,バッチモードで起動するときに,別途 (defvar my-boot-mode nil) をしてくれる .emacs ではないファイルを読み込まないといけません.バッチモードでは early-init.el が読み込まれないためです.なので,上記のように my-boot-mode で切り替えるやり方は,あまりオススメしません.

私の場合は,バッチモード用に専用の init-eval.el というファイルを読み込んで,この問題を回避しています.

3.20. [aggressive-indent.el] 即時バッファ整形

特定のメジャーモードで,とにかく整形しまくります. python-mode では意図しないインデントになったりします. web-mode だと異常に重かったりします.

(when (autoload-if-found '(aggressive-indent-mode)
                         "aggressive-indent" nil t)
  (dolist (hook
           '(;; python-mode-hook
             ;; nxml-mode-hook
             ;; web-mode-hook
             emacs-lisp-mode-hook
             lisp-mode-hook perl-mode-hook c-mode-common-hook))

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

3.21. [uniquify.el] 同じバッファ名が開かれた場合に区別する

ビルトインの uniquify を使います.モードラインの表示が変わります.

(setq uniquify-buffer-name-style 'post-forward-angle-brackets)

3.22. [ws-butler.el] 不要なスペースを自動除去

行末の不要なスペース等が残るのを回避できます.バッファ保存時の自動処理です.

(when (autoload-if-found '(ws-butler-mode ws-butler-global-mode)
                         "ws-butler" nil t)

  (dolist (hook '(emacs-lisp-mode-hook
                  lisp-mode-hook perl-mode-hook c-mode-common-hook))
    (add-hook hook #'ws-butler-mode))

  (with-eval-after-load "ws-butler"
    (custom-set-variables
     '(ws-butler-global-exempt-modes
       (append '(org-mode empty-booting-mode change-log-mode epa-mode)
                           ws-butler-global-exempt-modes)))))

3.23. [epa.el] GPGを使う

;;;###autoload
(defun my-private-conf-activate ()
  (cancel-timer my-private-conf-timer)
  ;; (require 'epa)
  (when (and (file-exists-p "~/Dropbox/config/private.el.gpg")
             (eq system-type 'darwin)
             (not (featurep 'private)))
    (unless (ignore-errors (require 'private "private.el.gpg" t))
      (user-error "GPG decryption error (private.el)"))))
(unless noninteractive
  (defvar my-private-conf-timer
    (run-with-idle-timer (+ 6 my-default-loading-delay)
                         nil #'my-private-conf-activate))
  (when (version< "27.0" emacs-version)
    ;; ミニバッファでパスワードを入力する
    (setq epg-pinentry-mode 'loopback)))

w32 の MSYS2 で gpg を使うと,バージョン情報が "2.2.19-unknown" などとなる.その状況で epg-find-configuration が呼ばれると,内部で生じるエラーが報告されず nil となり, epg.el が以下を報告する.

GPG error: "no usable configuration," OpenPGP

しかし実際には, epg-find-configurationepg-check-configuration のエラーを正しく処理していないのが原因であり,この関数のハックが必要となる.

(with-eval-after-load "org-crypt"
  ;; (when (eq window-system 'w32)
  ;;   ;; with export GNUPGHOME="/home/taka/.gnupg" in .bashrc
  ;;   (setq epg-gpg-home-directory ".gnupg")) ;; No need for zip downloaded Emacs
  ;; epg-gpg-home-directory が設定されていると,(epg-make-context nil t t) の戻り値に反映され,結果 epg-list-keys の戻り値が nil になり鍵をリストできなくなる.

  (defun my-epg-check-configuration (config &optional minimum-version)
    "Verify that a sufficient version of GnuPG is installed."
    (let ((version (alist-get 'version config)))
      (unless (stringp version)
        (error "Undetermined version: %S" version))
      ;; hack for w32
      (when (eq window-system 'w32)
        (setq version (or minimum-version
                          epg-gpg-minimum-version)))
      ;;
      (unless (version<= (or minimum-version
                             epg-gpg-minimum-version)
                         version)
        (error "Unsupported version: %s" version))))
  ;; (advice-add 'epg-check-configuration
  ;;             :override #'my-epg-check-configuration)
  )

3.24. [epa.el] GPGファイルの保存時のメッセージを停止する

GPGで暗号化されたファイルを編集すると,保存時に暗号化していることを知らせる情報が Messages バッファに出力されます.頻度が低ければ特に気にならないかもしれませんが,自動的にバッファを保存する設定を施していると,Messages バッファが埋め尽くされてしまうので困ります. epa-file-write-region をハックして黙らせます.

(with-eval-after-load "epa"
  ;; Suppress message when saving encrypted file (hoge.org.gpg)
  (advice-add 'epa-file-write-region :around #'ad:suppress-message))

3.25. メール設定の読み込み

(autoload 'mail "~/Dropbox/config/my-mail.el.gpg" nil t)

3.26. GPGファイルを一定時間後にロックする

次の例では, secret.org.gpg が開かれていて,1分間操作しなかったら安全のためバッファを閉じます.

;;;###autolaod
(defun my-lock-secret-buffer (&optional file)
  (when (and (stringp file)
             (buffer-live-p (get-buffer file)))
    (kill-buffer file)
    (let ((message-log-max nil))
      (message "--- %s is locked." file))))
(run-with-idle-timer 60 t #'my-lock-secret-buffer "secret.org.gpg")

3.27. NSビルド用設定   macOS

インラインパッチの適用が前提の設定です. M-SPC/S-SPC で日本語IMEのON/OFFができるようになります.インラインパッチの情報はリンク先にあります.

;;;###autoload
(defun my-isearch-ime-deactivate-sticky ()
  (unless (region-active-p)
    (mac-ime-deactivate-sticky)))

;;;###autoload
(defun my-toggle-ime-ns ()
  "Toggle IME."
  (interactive)
  (if (my-ime-active-p) (my-ime-off) (my-ime-on)))

;;;###autoload
(defun my-working-text-face-on ()
  (if (or isearch-mode
          (minibufferp))
      (custom-set-faces
       '(ns-working-text-face nil))
    (custom-set-faces
     '(ns-working-text-face
       ((((background dark))
         :background "#594d5d" :underline "LightSlateBlue")
        (t (:background "#fff0de" :underline "gray20")))))))

;;;###autoload
(defun my-working-text-face-off ()
  (if (or isearch-mode
          (minibufferp))
      (custom-set-faces
       '(ns-working-text-face nil))
    (custom-set-faces
     '(ns-working-text-face
       ((((background dark)) :background "#484c5c" :underline "white")
        (t (:background "#DEEDFF" :underline "DarkOrchid3")))))))

;;;###autoload
(defun my-ns-org-heading-auto-ascii ()
  "IME off, when the cursor on org headings."
  ;; (message "%s" (frame-focus-state (selected-frame)))
  (when (and
         (fboundp 'frame-focus-state)
                     (frame-focus-state)
         (eq major-mode 'org-mode)
         (boundp 'org-agenda-buffer-name)
         (or (looking-at org-heading-regexp)
             (equal (buffer-name) org-agenda-buffer-name))
         (my-ime-active-p))

    (my-ime-off)))
(keymap-global-set "M-SPC" 'my-toggle-ime-ns)
(keymap-global-set "S-SPC" 'my-toggle-ime-ns)
;; (keymap-set isearch-mode-map "M-SPC" 'my-toggle-ime-ns)
;; (keymap-set isearch-mode-map "S-SPC" 'my-toggle-ime-ns)
(when (fboundp 'mac-ime-toggle) ;; using ns-inline-patch
  (defalias 'my-toggle-ime-ns 'mac-ime-toggle)
  (defalias 'my-ime-active-p 'mac-ime-active-p)) ;; FIXME
(when (memq window-system '(ns nil))

  (custom-set-faces
   '(ns-marked-text-face
     ((t (:foreground "black"
                      :background "light pink" :underline "OrangeRed2"))))
   '(ns-unmarked-text-face
     ((t (:foreground "black"
                      :background "light sky blue" :underline "royal blue")))))

  (when (and (fboundp 'mac-get-current-input-source)
             (version< "27.0" emacs-version))
    ;; "com.apple.inputmethod.Kotoeri.RomajiTyping.Japanese" for Big Sur
    (custom-set-variables
     '(mac-default-input-source "com.google.inputmethod.Japanese.base"))
    (unless noninteractive
      (mac-input-method-mode 1))

    ;; see also activate-mark-hook, deactivate-mark-hook
    (add-hook 'isearch-mode-hook #'my-isearch-ime-deactivate-sticky)
    (add-hook 'isearch-mode-end-hook #'mac-ime-activate-sticky))

  (with-eval-after-load "org"
    ;; カーソル移動で heading に来たときは即座にIMEをOFFにする
    ;; (add-hook 'ah-after-move-cursor-hook #'my-ns-org-heading-auto-ascii)
    ;; カーソル移動で heading に留まった時にIMEをOFFにする
    (unless noninteractive
      (run-with-idle-timer 0.2 t #'my-ns-org-heading-auto-ascii)))

  (with-eval-after-load "hl-line"
    (add-hook 'input-method-activate-hook #'my-working-text-face-on)
    (add-hook 'input-method-deactivate-hook #'my-working-text-face-off)))

3.28. EMPビルド用設定   macOS

NSビルド版で生じた日本語入力時のチラつきを避けるために,EMP版ビルドに(一時期)浮気しました.以下はその時にNSビルドの振る舞いに近づけるためにがんばった設定です.詳細な情報は,リンク先の記事にあります.

(when (eq window-system 'mac)
  (keymap-global-set "M-SPC" 'mac-win-toggle-ime)
  (keymap-global-set "S-SPC" 'mac-win-toggle-ime)
  (declare-function mac-win-save-last-ime-status "init" nil)
  (declare-function ad:mac-auto-ascii-setup-input-source "init" nil)
  (declare-function mac-win-restore-ime "init" nil)
  (declare-function mac-win-restore-ime-target-commands "init" nil))

(when (and (eq window-system 'mac)
           (fboundp 'mac-select-input-source)
           (fboundp 'mac-auto-ascii-select-input-source)
           (fboundp 'mac-auto-ascii-setup-input-source)
           (fboundp 'mac-input-source)
           (fboundp 'mac-auto-ascii-mode))

  (defvar mac-win-last-ime-status 'off) ;; {'off|'on}
  (defun mac-win-save-last-ime-status ()
    (setq mac-win-last-ime-status
          (if (string-match "\\.\\(Roman\\|US\\)$" (mac-input-source))
              'off 'on)))
  (mac-win-save-last-ime-status) ;; 初期化

  (defun mac-win-restore-ime ()
    (when (and (bound-and-true-p mac-auto-ascii-mode)
               (eq mac-win-last-ime-status 'on))
      (mac-select-input-source
       "com.google.inputmethod.Japanese.base")))

  (defun ad:mac-auto-ascii-setup-input-source (&optional _prompt)
    "Extension to store IME status"
    (mac-win-save-last-ime-status))
  (advice-add 'mac-auto-ascii-setup-input-source :before
              #'ad:mac-auto-ascii-setup-input-source)

  (defvar mac-win-target-commands
    '(find-file save-buffer other-window delete-window split-window))

  (defun mac-win-restore-ime-target-commands ()
    (when (and (bound-and-true-p mac-auto-ascii-mode)
               (eq mac-win-last-ime-status 'on))
      (mapc (lambda (command)
              (when (string-match
                     (format "^%s" command) (format "%s" this-command))
                (mac-select-input-source
                 "com.google.inputmethod.Japanese.base")))
            mac-win-target-commands)))
  (add-hook 'pre-command-hook #'mac-win-restore-ime-target-commands)

  ;; バッファリストを見るとき
  (add-to-list 'mac-win-target-commands 'counsel-ibuffer)
  ;; ChangeLogに行くとき
  (add-to-list 'mac-win-target-commands 'add-change-log-entry-other-window)
  ;; 個人用の関数を使うとき
  ;; (add-to-list 'mac-win-target-commands 'my-)
  ;; 自分で作ったパッケージ群の関数を使うとき
  (add-to-list 'mac-win-target-commands 'change-frame)
  ;; org-mode で締め切りを設定するとき.
  (add-to-list 'mac-win-target-commands 'org-deadline)
  ;; org-mode で締め切りを設定するとき.
  ;; (add-to-list 'mac-win-target-commands 'org-capture)
  ;; query-replace で変換するとき
  (add-to-list 'mac-win-target-commands 'query-replace)

  ;; ミニバッファ利用後にIMEを戻す
  ;; M-x でのコマンド選択でIMEを戻せる.
  ;; これ移動先で q が効かないことがある
  (add-hook 'minibuffer-setup-hook #'mac-win-save-last-ime-status)
  (add-hook 'minibuffer-exit-hook #'mac-win-restore-ime)

  ;; タイトルバーの振る舞いを NS版に合わせる.
  (setq frame-title-format (format (if (buffer-file-name) "%%f" "%%b")))

  ;; なおテーマを切り替えたら,face の設定をリロードしないと期待通りにならない
  (when (require 'hl-line nil t)
    (custom-set-faces
     ;; 変換前入力時の文字列用 face
     `(mac-ts-converted-text
       ((((background dark)) :underline "orange"
         :background ,(face-attribute 'hl-line :background))
        (t (:underline "orange"
                       :background
                       ,(face-attribute 'hl-line :background)))))
     ;; 変換対象の文字列用 face
     `(mac-ts-selected-converted-text
       ((((background dark)) :underline "orange"
         :background ,(face-attribute 'hl-line :background))
        (t (:underline "orange"
                       :background
                       ,(face-attribute 'hl-line :background)))))))

  (when (fboundp 'mac-input-source)
    (run-with-idle-timer 3 t 'my-mac-keyboard-input-source))


  ;; あまりよいアプローチでは無い気がするけど,org-heading 上とagendaでは
  ;; 1秒アイドルすると,自動的に IME を OFF にする
  (defun my-mac-win-org-heading-auto-ascii ()
    (when (and (eq major-mode 'org-mode)
               (or (looking-at org-heading-regexp)
                   (equal (buffer-name) org-agenda-buffer-name)))
      (setq mac-win-last-ime-status 'off)
      (mac-auto-ascii-select-input-source)))
  (when (fboundp 'mac-auto-ascii-select-input-source)
    (run-with-idle-timer 1 t 'my-mac-win-org-heading-auto-ascii))

  ;; EMP版Emacsの野良ビルド用独自設定群
  ;; IME toggleを Emacs内で有効にする
  (defun mac-win-toggle-ime ()
    (interactive)
    (when (fboundp 'mac-input-source)
      (mac-select-input-source
       (concat "com.google.inputmethod.Japanese"
               (if (string-match "\\.base$" (mac-input-source))
                   ".Roman" ".base")))))

  ;; isearch 中にIMEを切り替えると,[I-Search] の表示が消える.
  ;; (keymap-set isearch-mode-map "M-SPC" 'mac-win-toggle-ime)
  (keymap-set isearch-mode-map "S-SPC" 'mac-win-toggle-ime)

  (when (boundp 'mac-win-ime-cursor-type) ;; Need update
    (setq mac-win-ime-cursor-type (plist-get my-cur-type-ime :on)))
  ;; minibuffer では↑の背景色を無効にする
  (when (fboundp 'mac-min--minibuffer-setup)
    (add-hook 'minibuffer-setup-hook #'mac-min--minibuffer-setup))
  ;; echo-area でも背景色を無効にする
  (when (boundp 'mac-win-default-background-echo-area)
    (setq mac-win-default-background-echo-area t));; *-textのbackgroundを無視
  ;; デバッグ用
  (when (boundp 'mac-win-debug-log)
    (setq mac-win-debug-log nil))
  ;; Testing...
  (when (boundp 'mac-win-apply-org-heading-face)
    (setq mac-win-apply-org-heading-face t))

  (unless noninteractive
    (mac-auto-ascii-mode 1)))

4. カーソル移動

カーソルの移動は,次のポリシーに従っています.デフォルトでは C-v/M-v で上下移動になっていますが, M-v は windows のペーストに対応するので混乱を招くので使っていません.

行移動 C-n/C-p
ページ移動(スクロール) C-v/C-t
ウィンドウ移動 C-M-n/C-M-p
バッファ切り替え M-]/M-[
バッファ先頭・末尾 C-M-t/C-M-b
編集点の移動 C-u C-SPC
タグジャンプ M-,/M-.

なお, C-M-b, C-M-n, C-M-p, はそれぞれ,本来S式の移動(backward-sexp, forward-list, backward-list)に割り振られています.また, C-M-t は,本来,2つのS式の入れ替え(transpose-sexps)に割り振られています.

4.1. バッファ内のカーソル移動

先頭に移動,最終行に移動,ページ単位の進む,ページ単位の戻る,行数を指定して移動.

(keymap-global-set "C-M-t" 'beginning-of-buffer)
(keymap-global-set "C-M-b" 'end-of-buffer)
;; Backward page scrolling instead of M-v
(keymap-global-set "C-t" 'scroll-down)
;; Frontward page scrolling instead of C-v
;; (keymap-global-set "M-n" 'scroll-up)
;; Move cursor to a specific line
(keymap-global-set "C-c g" 'goto-line)

4.2. バッファ間のカーソル移動

C-c o でもいいですが,ワンアクションで移動できるようが楽です.次のように双方向で使えるように設定しています.

(keymap-global-set "C-M-p" (lambda () (interactive) (other-window -1)))
(keymap-global-set "C-M-n" (lambda () (interactive) (other-window 1)))

4.3. 対応するカッコを選択(逆方向)

  • C-M-SPC (mark-sexp) は,カーソル位置から順方向に選択.
  • C-M-U (backward-up-list) は,一つ外のカッコの先頭にポイントを移す.

- C-M-9 (my-mark-sexp) は,カーソル位置から逆方向に選択. 以前は my-mark-sexp を定義して使っていましたが, mark-sexp を advice する方法に変えました.カーソルが行末だったり,後ろが空白文字の場合にのみ, mark-sexp-1 がわたるようにしました.

ad:er:mark-sexp を設定することで, C-M-SPC 時にカーソル以下の単語を選択するようになります. expand-region.el の機能を使っています.

(defun my-mark-sexp (&optional arg)
  "Move backward across one balanced expression and select it."
  (interactive)
  (mark-sexp (or arg -1)))
(keymap-global-set "C-M-9" #'my-mark-sexp)
;;;###autoload
(defun ad:mark-sexp (f &optional arg allow-extend)
  "Set mark ARG sexps from point.
When the cursor is at the end of line or before a whitespace, set ARG -1."
  (interactive "P\np")
  (funcall f (if (and (not (bolp))
                      (not (eq (preceding-char) ?\ ))
                      (not (memq (following-char) '(?\( ?\< ?\[ ?\{)))
                      (or (eolp)
                          (eq (following-char) ?\ )
                          (memq (preceding-char) '(?\) ?\> ?\] ?\}))))
                 -1 arg)
           allow-extend))

;;;###autoload
(defun ad:er:mark-sexp (f &optional arg allow-extend)
  "If the cursor is on a symbol, expand the region along the symbol."
  (interactive "P\np")
  (if (and (not (use-region-p))
           (symbol-at-point)
           (not (memq (following-char) '(?\( ?\< ?\[ ?\{)))
           (not (memq (preceding-char) '(?\) ?\> ?\] ?\}))))
      (er/mark-symbol)
    (funcall f arg allow-extend)))
(autoload-if-found '(er/mark-symbol) "expand-region" nil t)
(advice-add 'mark-sexp :around #'ad:mark-sexp)
(advice-add 'mark-sexp :around #'ad:er:mark-sexp)

4.4. スクロールを制御

一行づつスクロールさせます.デフォルトではバッファの端でスクロールすると,半画面移動します.また,上下の端にカーソルがどのくらい近づいたらスクロールとみなすかも指定できます.

非ASCII文字を扱っているときに一行づつスクロールしない場合は,scroll-conservatively の値を1ではなく大きい数字にすると直るかもしれません.マニュアルでは,100より大きい数値とあります.

scroll-margin を指定すると,カーソルがウィンドウの端から離れた状態でスクロールされます.

;; Scroll window on a line-by-line basis
(setq scroll-conservatively 1000)
(setq scroll-step 1)
(setq scroll-preserve-screen-position t) ;; スクロール時にスクリーン内で固定
;;  (setq scroll-margin 0) ; default=0

スクロール時のジャンプが気になる場合は次のパッケージを使うとよいです.

(when (require 'smooth-scrolling nil t)
  (setq smooth-scroll-margin 1))

4.5. スクロールで表示を重複させる行数

;; Scroll window on a page-by-page basis with N line overlapping
(setq next-screen-context-lines 10)

4.6. マーク箇所を遡る

C-u C-SPC で辿れるようになります.

(setq set-mark-command-repeat-pop t)
(setq mark-ring-max 32)
(setq global-mark-ring-max 64)

4.7. [ah.el] カーソル移動に反応するフック

カーソルを移動するインタラクティブ関数に反応する ah-before-move-cursor-hookah-after-move-cursor-hook を使います. ah.el に関数定義を切り出しました.

(unless noninteractive
  (when (require 'ah nil t)
    (setq ah-lighter "")
    (ah-mode 1)))

4.8. [smooth-scroll.el] 滑らかなスクロール

良い感じです.スススっとスクロールします.最初にスクロールする時にパッケージを読み込みます.

(when (autoload-if-found '(smooth-scroll-mode)
                         "smooth-scroll" nil t)

  (with-eval-after-load "smooth-scroll"
    (custom-set-variables
     '(smooth-scroll/vscroll-step-size 6)
     '(smooth-scroll/hscroll-step-size 6)))

  (unless noninteractive
    (smooth-scroll-mode t)))

4.9. [bs.el] 続・カレントバッファの表示切り替え

古くから cycle-buffer がありますが,基本機能だけで満足するようになったので,ビルトインの bs.el に移行しました. bs-cycle-next あるいは bs-cycle-previous でバッファを切り替えられます.

切り替えるときにミニバッファにバッファ名が列挙されますが,できればセパレータを表示したいところです.FontAwesome を使えば,だいぶ見やすくなるかも.

bs.el が表示するバッファリストが横長で見にくいので,縦表示する拡張パッケージを作りました.表示中のリストには番号を割り当てられていて,番号に対応するキーを押すとそのバッファに移動できます. bs.el のコマンドを advice しているだけなので,それを解けばオリジナルの bs.el の振る舞いに戻ります.そのための関数(M-x bsv-disable-advices)も準備しておきました.

(keymap-global-set "M-]" 'bs-cycle-next)
(when (display-graphic-p)
  (keymap-global-set "M-[" 'bs-cycle-previous))
(with-eval-after-load "bs"
  (custom-set-variables
   '(bs-cycle-configuration-name "files-and-scratch")
   '(bs-max-window-height 10))

  ;; リストを縦表示する
  (when (require 'bsv nil t)
    (setq bsv-max-height 5
          bsv-message-timeout 9)))

4.10. [bm.el] カーソル位置をブックマークして追う

bm.elは,カーソル位置をブックマークしておくためのツールです. point-undo と比較して,ユーザが明示的に位置を保存でき,見た目にも使いやすいです.以下の例では, org-mode のツリー内にブックマークがある時にも,上手い具合に表示ができるように調整してあります.カーソル移動は,順方向( bm-next )にだけ使っています.

org-mode との連携には, org-bookmark-heading があります.ただ,私は下記の設定だけでそれほど不自由していません.

;;;###autoload
(defun my-bm-save-all ()
  (bm-buffer-save-all)
  (bm-repository-save))

;;;###autoload
(defun my-toggle-bm ()
  "bm-toggle with updating history"
  (interactive)
  (let ((bm (concat
             (buffer-name) "::"
             (if (and (equal major-mode 'org-mode)
                      (not (org-before-first-heading-p)))
                 (nth 4 (org-heading-components))
               (format "%s" (line-number-at-pos))))))
    (if (bm-bookmark-at (point))
        (bookmark-delete bm)
      (bookmark-set bm)))
  (bm-toggle)
  (bm-buffer-save-all)
  (bm-repository-save))

;;;###autoload
(defun my-bm-next ()
  "bm-next with org-mode"
  (interactive)
  (bm-next)
  (when (and (equal major-mode 'org-mode)
             (not (org-before-first-heading-p)))
    (widen)
    (org-overview)
    (org-reveal)
    (org-cycle-hide-drawers 'all)
    (org-show-entry)
    (show-children)
    (org-show-siblings)))

;;;###autoload
(defun counsel-bm-get-list (bookmark-overlays)
  (-map (lambda (bm)
          (with-current-buffer (overlay-buffer bm)
            (let* ((line (replace-regexp-in-string
                          "\n$" ""
                          (buffer-substring (overlay-start bm)
                                            (overlay-end bm))))
                   ;; line numbers start on 1
                   (line-num
                    (+ 1 (count-lines (point-min) (overlay-start bm))))
                   (name (format "%s:%d - %s" (buffer-name) line-num line)))
              `(,name . ,bm))))
        bookmark-overlays))

;;;###autoload
(defun counsel-bm ()
  (interactive)
  (let* ((bm-list (counsel-bm-get-list (bm-overlays-lifo-order t)))
         (bm-hash-table (make-hash-table :test 'equal))
         (search-list (-map (lambda (bm) (car bm)) bm-list)))
    (-each bm-list (lambda (bm)
                     (puthash (car bm) (cdr bm) bm-hash-table)
                     ))
    (ivy-read "Find bookmark(bm.el): "
              search-list
              :require-match t
              :keymap counsel-describe-map
              :action (lambda (chosen)
                        (let ((bookmark (gethash chosen bm-hash-table)))
                          (switch-to-buffer (overlay-buffer bookmark))
                          (bm-goto bookmark)
                          ))
              :sort t)))

;;;###autoload
(defun ad:bm-show-mode ()
  "Enable truncate mode when showing bm list."
  (toggle-truncate-lines 1))
(when (autoload-if-found '(my-toggle-bm
                           my-bm-next bm-buffer-save bm-buffer-restore
                           bm-buffer-save-all bm-repository-save
                           bm-repository-load counsel-bm)
       "bm" nil t)

  ;; ファイルオープン時にブックマークを復帰
  (keymap-global-set "<f10>" 'my-toggle-bm)
  (keymap-global-set "C-<f10>" 'my-bm-next)
  (keymap-global-set "S-<f10>" 'bm-show-all)
  (add-hook 'find-file-hook #'bm-buffer-restore)

  ;; ビルトイン bookmark の配色を無効にする(as of 28.1)
  (setq bookmark-fontify nil)

  ;; ビルトイン bookmark がfringeに出すマークを無効にする(as of 28.1)
  (setq bookmark-set-fringe-mark nil)

  (with-eval-after-load "ivy"
    (keymap-global-set "S-<f10>" 'counsel-bm))

  (with-eval-after-load "bm"
    (advice-add 'bm-repository-load :around #'ad:suppress-message)

    ;; (setq bm-annotation-width 30)
    (setq-default bm-buffer-persistence t)
    (setq bm-restore-repository-on-load t)
    (setq bm-cycle-all-buffers t)
    ;; (setq bm-toggle-buffer-persistence t)
    (setq bm-buffer-persistence t)
    (setq bm-persistent-face 'bm-face)
    (setq bm-repository-file
          (expand-file-name
           (concat (getenv "SYNCROOT") "/emacs.d/.bm-repository")))

    (unless noninteractive
      (bm-repository-load)
      (add-hook 'kill-buffer-hook 'bm-buffer-save)
      (add-hook 'after-save-hook 'bm-buffer-save)
      (add-hook 'after-revert-hook 'bm-buffer-restore)
      (add-hook 'kill-emacs-hook #'my-bm-save-all))

    (advice-add 'bm-show-mode :after #'ad:bm-show-mode)))

4.11. [centered-cursor-mode.el] カーソル位置をバッファ中央に固定

isearch-mode の時だけ有効にしています.

;;;###autoload
(defun my-centered-cursor-activate () (centered-cursor-mode 1))

;;;###autoload
(defun my-centered-cursor-deactivate () (centered-cursor-mode -1))
(when (autoload-if-found '(centered-cursor-mode)
                         "centered-cursor-mode" nil t)

  (with-eval-after-load "isearch"
    ;; isearch の時はOFFにする
    (add-hook 'isearch-mode-hook #'my-centered-cursor-activate)
    (add-hook 'isearch-mode-end-hook #'my-centered-cursor-deactivate)))

4.12. [smart-mark] C-g後に元の場所へカーソルを戻す

すぐわかる例は, C-x h で全選択して何もせず, C-g する場合です.通常だとバッファの先頭にカーソルが置き去りにされますが, smart-mark を使うと,全選択を実行した時の位置に自動的に戻してくれます.

;;;###autoload
(defun my-smart-mark-activate ()
  (smart-mark-mode 1)
  (remove-hook 'find-file-hook #'my-smart-mark-activate))

;;;###autoload
(defun ad:smart-mark-restore-cursor ()
  "Restore cursor position saved just before mark."
  (when smart-mark-point-before-mark
    (when (> smart-mark-point-before-mark 1)
      ;; To avoid to jump to the beginning of the buffer
      (goto-char smart-mark-point-before-mark))
    (setq smart-mark-point-before-mark nil)))

;;;###autoload
(defun ad:smart-mark-set-restore-before-mark (&rest _arg)
  (unless (memq this-command
                '(er/expand-region er/mark-symbol er/contract-region))
    (setq smart-mark-point-before-mark (point))))

;;;###autoload
(defun ad:er:keyboard-quit ()
  (when (memq last-command '(er/expand-region er/contract-region))
    (when smart-mark-point-before-mark
      (goto-char smart-mark-point-before-mark))))

;;;###autoload
(defun ad:er:pre:keyboard-quit ()
  (when (memq last-command '(er/expand-region er/contract-region))
    (er/contract-region 0)
    ;; (when (> smart-mark-point-before-mark 1) ;; FIXME
    ;;   (goto-char smart-mark-point-before-mark))
    ))
(when (autoload-if-found '(smart-mark-mode)
                         "smart-mark" nil t)

  (add-hook 'find-file-hook #'my-smart-mark-activate)

  (with-eval-after-load "smart-mark"
    (progn ;; C-M-SPC SPC SPC ... C-g の場合に正しくカーソルと元に戻す.
      (advice-add 'smart-mark-restore-cursor :override
                  #'ad:smart-mark-restore-cursor)
      (advice-add 'smart-mark-set-restore-before-mark :override
                  #'ad:smart-mark-set-restore-before-mark)

      (when (require 'expand-region-core nil t)
        (advice-add 'keyboard-quit :after #'ad:er:keyboard-quit))
      ;; (advice-add 'keyboard-quit :before #'ad:er:pre:keyboard-quit)
      )))
;; (defun my-smart-mark-activate () (smart-mark-mode 1))
;; (defun my-smart-mark-dectivate () (smart-mark-mode -1))
;; (add-hook 'isearch-mode-hook #'my-smart-mark-dectivate)
;; (add-hook 'isearch-mode-end-hook #'my-smart-mark-activate)

4.13. [syntax-subword.el] M-f で移動する位置をより密にする

;;;###autoload
(defun my-syntax-subword-activate (&rest arg)
  (unless (featurep 'syntax-subword)
    (global-syntax-subword-mode 1))
  (advice-remove 'forward-word #'my-syntax-subword-activate)
  (advice-remove 'backward-word #'my-syntax-subword-activate)
  arg)

;;;###autoload
(defun ad:syntax-subword-kill (&optional n)
  "Replace `kill-region' with `delete-region'."
  (interactive "^p")
  (let ((beg (point))
        (end (save-excursion (syntax-subword-forward n) (point))))
    (delete-region beg end)))
(when (autoload-if-found '(global-syntax-subword-mode
                           syntax-subword-backward-kill
                           syntax-subword-mode syntax-subword-kill)
                         "syntax-subword" nil t)

  (advice-add 'forward-word :before #'my-syntax-subword-activate)
  (advice-add 'backward-word :before #'my-syntax-subword-activate)

  (keymap-global-set "C-<backspace>" #'syntax-subword-backward-kill)

  (with-eval-after-load "syntax-subword"
    ;; C-<backspace> で,削除領域をコピーしない.
    (advice-add 'syntax-subword-kill :override #'ad:syntax-subword-kill)))

4.14. [expand-region.el] カーソル箇所を起点に選択範囲を賢く広げる

er/expand-region を呼ぶと,カーソル位置を起点として前後に選択範囲を広げてくれます.2回以上呼ぶと,読んだ回数だけ賢く選択範囲が広がりますが,2回目は設定したキーバインドの最後の一文字を連打すればOKです.その場合,選択範囲を狭める時は - を押し, 0 を押せばリセットされます.

自分は selected.el とペアで使うように設定していて, C-SPCC-M-SPC から SPC を押すことで er/expand-region が呼ばれるように設定しています.

(with-eval-after-load "selected"
  (when (require 'expand-region nil t)
    (keymap-set selected-keymap "SPC" #'er/expand-region)))

4.15. DONE [goto-chg.el] 編集箇所を簡単に辿る   obsolete

編集結果を保持したまま編集箇所にカーソルを移すことができます. C-/ の Undo のような操作で,簡単かつ高速にカーソルを移動できます.ただ undo-tree 依存です.

(when (autoload-if-found '(goto-last-change goto-last-change-reverse)
                         "goto-chg" nil t)

  (keymap-global-set "C-," 'goto-last-change)
  (keymap-global-set "C-." 'goto-last-change-reverse)

  (with-eval-after-load "flyspell"
    (keymap-set flyspell-mode-map "C-," 'goto-last-change)
    (keymap-set flyspell-mode-map "C-." 'goto-last-change-reverse)))

4.16. DONE [cycle-buffer.el] カレントバッファの表示切り替え   obsolete

http://www.emacswiki.org/emacs/download/cycle-buffer.el

cycle-buffer を使うと,バッファの履歴をスライドショーのようにたどれます.ミニバッファに前後の履歴が表示されるので,何回キーを押せばいいかの目安になります.それを超える場合には,おとなしくバッファリストを使います.直近数件のバッファをたどるのに便利です. cycle-buffer は古めですが,個人的には気に入っています.

(when (autoload-if-found '(cycle-buffer cycle-buffer-backward)
                         "cycle-buffer" nil t)

  (keymap-global-set "M-]" 'cycle-buffer)
  (keymap-global-set "M-[" 'cycle-buffer-backward)

  (with-eval-after-load "cycle-buffer"
    (custom-set-variables
     '(cycle-buffer-allow-visible t)
     '(cycle-buffer-show-length 12)
     '(cycle-buffer-show-format '(" < %s >" . " %s")))))

4.17. DONE [back-button] マークをたどる   obsolete

現在のバッファと開いている全てのバッファのマークを辿ることができます.いま注目している位置が,マーク全体でどのあたりに位置するのかをミニバッファに表示してくれます.ツールバーを表示している場合は,マークを付けたり辿る用のボタンが追加されます. global-mark-ring-max を設定して,辿れるマークの数を拡張しておきます.

ただ C-x <SPC> が潰れるので要注意です.矩形選択で用いる rectangle-mark-mode に当てられているキーバインドです.

(when (autoload-if-found '(back-button-mode
                           back-button-local-forward back-button-global-forward)
                         "back-button" nil t)

  (with-eval-after-load "back-button"
    (setq global-mark-ring-max 64)
    (setq back-button-local-forward-keystrokes '("<f10>"))
    (setq back-button-global-forward-keystrokes '("C-<f10>"))
    (keymap-set back-button-mode-map
                (car back-button-local-forward-keystrokes)
                'back-button-local-forward)
    (keymap-set back-button-mode-map
                (car back-button-global-forward-keystrokes)
                'back-button-global-forward)
    (setq back-button-mode-lighter nil)
    (setq back-button-index-timeout 0))

  (unless noninteractive
    (back-button-mode 1)))

4.18. DONE [point-undo.el] カーソル位置を簡単にたどる   obsolete

autoloadautoload-if-found で定義すると,使いたい時に履歴が取れていないのでよろしくないです.起動時に有効化します. bm.el で明示的にマーカーを残して履歴をたどる方が気に入っているので,最近は point-undo を使っていません.シングルキーを割り当てておくと使いやすいです.

(when (require 'point-undo nil t)
  ;; [point-undo.el] Move the cursor to the previous position
  (keymap-global-set "<f7>" 'point-undo)
  ;; [point-undo.el] Redo of point-undo
  (keymap-global-set "S-<f7>" 'point-redo))

4.19. DONE [SmoothScroll.el] カーソル固定でスクロールする   obsolete

https://raw.github.com/takaxp/EmacsScripts/master/SmoothScroll.el https://github.com/pglotov/EmacsScripts/blob/master/SmoothScroll.el

カーソル位置と行を固定してバッファを背景スクロールできます.

オリジナルのままだとコンパイル時に警告がでるので, line-move-visual で書き換えています.残念ながら最近は使っていません.

(when (autoload-if-found '(scroll-one-up scroll-one-down)
                         "smoothscroll" nil t)

  (keymap-global-set "s-<up>" 'scroll-one-down)
  (keymap-global-set "s-<down>" 'scroll-one-up))

5. 編集サポート

5.1. エンターキーの挙動

好みの問題ですかね.標準では C-j が当てられています.

(keymap-global-set "RET" 'electric-newline-and-maybe-indent)

5.2. Yank時に装飾を取る

(setq yank-excluded-properties t)

5.3. 矩形編集/連番入力

24.4 からは, rectangle-mark-mode が使えるようになり, C-x SPC を押下すると矩形モードに入り直感的に矩形選択ができる.

標準の rect.el に以下の機能が実装されている.

矩形切り取り C-x r k
矩形削除 C-x r d
矩形貼り付け C-x r y
矩形先頭に文字を挿入 C-x r t
矩形を空白に変換する C-x r c

Built-in の cua-base.el(CUA-mode)を使うと,矩形選択は,領域選択後 cua-toggle-rectangle-mark でもできる.また,矩形選択した後に, M-n を押すと,連番をふれる.開始値,増加値を入力してから,hoge%03d.pgm などとすれば,hoge001,hoge002,,,と入力される.これと,org-mode の表機能( C-c | で選択部分を簡単に表にできる)を組み合わせれば,連番で数値をふったテーブルを容易に作れる.

(when (require 'cua-base)
  (cua-mode 1)
  (setq cua-enable-cua-keys nil))

5.4. ファイル保存時に時間を記録する

Built-in の time-stamp.el を使う.

バッファの保存時にタイムスタンプを記録する.以下の設定では,バッファの先頭から10行以内に,"#+date:" があると,"#+date: 2011-12-31" のようにタイムスタンプが記録される.Org Mode 用には "[2018-10-08 月 10:00]" のような形式でタイムスタンプを記録するようにしている.

ただし, time-stamp-start, time-stamp-end, time-stamp-line-limitinit.el で変更するのは推奨されていない(ファイルごとにするべき).

;;;###autoload
(defun my-time-stamp ()
  (setq time-stamp-format
        (if (eq major-mode 'org-mode)
            "[%Y-%02m-%02d %3a]" ;; "%04y %02H:%02M"
          "%Y-%02m-%02d"))
  (if (boundp 'org-tree-slide-mode)
      (unless org-tree-slide-mode
        (time-stamp))
    (time-stamp)))
(add-hook 'before-save-hook #'my-time-stamp)

(with-eval-after-load "time-stamp"
  (setq time-stamp-start "#\\+date:[ \t]*") ;; "Time-stamp:[ \t]+\\\\?[\"<]+"
  (setq time-stamp-end "$") ;; "\\\\?[\">]"
  (setq time-stamp-line-limit 10)) ;; def=8

5.5. 選択リージョンを使って検索

検索語をミニバッファに入力するのが面倒なので,リージョンをそのまま検索語として利用します.

;;;###autoload
(defun ad:isearch-mode (f forward &optional regexp op-fun recursive-edit
                          regexp-function)
  (if (and transient-mark-mode mark-active (not (eq (mark) (point))))
      (progn
        (isearch-update-ring (buffer-substring-no-properties (mark) (point)))
        (deactivate-mark)
        (funcall f forward regexp op-fun recursive-edit regexp-function)
        (if (not forward)
            (isearch-repeat-backward)
          (goto-char (mark))
          (isearch-repeat-forward)))
    (funcall f forward regexp op-fun recursive-edit regexp-function)))
(with-eval-after-load "isearch"
  (advice-add 'isearch-mode :around #'ad:isearch-mode)

  ;; C-g を isearch-exit に割り当てて途中中断とする.(カーソルを留めておきたい)カーソルを検索開始時点の場所に戻すには,別途 counsel-mark-ring を使う
  (keymap-set isearch-mode-map "C-g" 'isearch-exit))

5.6. ChangeLog モード

;; see private.el
(setq user-full-name "Your NAME")
(setq user-mail-address "your@address.com")
;;;###autoload
(defun my-orgalist-activate ()
  (when (require 'orgalist nil t)
    (orgalist-mode 1))) ;; originally orgstruct-mode

;;;###autoload
(defun ad:add-change-log-entry-other-window ()
  (when view-mode
    (View-exit-and-edit)))
(with-eval-after-load "add-log"
  (add-hook 'change-log-mode-hook
            (lambda ()
              (view-mode 1)
              (my-orgalist-activate)
              (setq tab-width 4)
              (setq left-margin 4)))

  (advice-add 'add-change-log-entry-other-window
              :before #'ad:add-change-log-entry-other-window))

5.7. テキストモード

http://d.hatena.ne.jp/NeoCat/20080211

とは言っても,Org-modeを知ってから .txt もテキストモードで開かなくなったので,ほぼ無意味な設定となりました.しかも, nxml-mode<tab> が効かなくなる現象が起きているので,以下の設定はしない方がよさげ.

(add-hook 'text-mode-hook
          (lambda()
              (setq tab-width 4)
              (setq indent-line-function 'tab-to-tab-stop)
              (setq tab-stop-list
                    '(4 8 12 16 20 24 28 32 36 40 44 48 52 56 60
                        64 68 72 76 80))))

5.8. C/C++モード

(when (autoload-if-found '(modern-c++-font-lock-mode)
                         "modern-cpp-font-lock" nil t)
  (push '("\\.[hm]$" . c++-mode) auto-mode-alist)
  (add-hook 'c-mode-hook #'modern-c++-font-lock-mode)
  (add-hook 'c++-mode-hook #'modern-c++-font-lock-mode))

5.9. C#モード

(when (autoload-if-found '(csharp-mode)
                         "csharp-mode" "Major mode for editing C# mode." t nil)

  (push '("\\.cs$" . csharp-mode) auto-mode-alist))

5.10. Infoモード

Org-mode の日本語翻訳済みinfoを読むための設定.翻訳プロジェクトで頒布しています.

;;;###autoload
(defun org-info-ja (&optional node)
  "(Japanese) Read documentation for Org-mode in the info system.
    With optional NODE, go directly to that node."
  (interactive)
  (info (format "(org-ja)%s" (or node ""))))
(with-eval-after-load "info"
  (add-to-list 'Info-additional-directory-list
               (expand-file-name "~/devel/mygit/org-ja/work/")))

5.11. Rモード

(when (autoload-if-found
       '(R-mode R)
       "ess-site" "Emacs Speaks Statistics mode" t)

  (push '("\\.[rR]$" . R-mode) auto-mode-alist))

5.12. nXMLモード

(add-hook 'nxml-mode-hook
          (lambda ()
            (keymap-set nxml-mode-map "RET" 'newline-and-indent)
            (auto-fill-mode -1)
            (setq indent-tabs-mode t)
            (setq nxml-slash-auto-complete-flag t)
            (setq tab-width 1)
            (setq nxml-child-indent 1)
            (setq nxml-attribute-indent 0)))

5.13. yamlモード

(when (autoload-if-found '(yaml-mode)
                         "yaml-mode" nil t)
  (push '("\\.yml$" . yaml-mode) auto-mode-alist))

5.14. jsonモード

バッファの保存時に json-mode-beautify を走らせます.

;;;###autoload
(defun my-json-mode-beautify ()
  (when (eq major-mode 'json-mode)
    (json-mode-beautify (point-min) (point-max))))

;;;###autoload
(defun my-json-pretty-print-buffer ()
  (when (eq major-mode 'json-mode)
    (json-pretty-print-buffer)))
(when (autoload-if-found '(json-mode)
                         "json-mode" nil t)
  (push '("\\.json$" . json-mode) auto-mode-alist)
  (with-eval-after-load "json-mode"
    (add-hook 'before-save-hook #'my-json-mode-beautify)
    (add-hook 'after-save-hook #'my-json-pretty-print-buffer)))

5.15. javascriptモード

(when (autoload-if-found '(js2-mode)
                         "js2-mode" nil t)
  (with-eval-after-load "js2-mode"
    (require 'js2-refactor nil t)
    (push '("\\.js$" . js2-mode) auto-mode-alist)

    (when (autoload-if-found
           '(ac-js2-mode ac-js2-setup-auto-complete-mode)
           "ac-js2" nil t)
      (add-hook 'js2-mode-hook #'ac-js2-mode))

    (if (executable-find "tern")
        (when (autoload-if-found
               '(tern-mode)
               "tern" nil t)
          (with-eval-after-load "tern"
            (tern-mode 1)
            ;; tern-command shall be overwritten by actual path
            (setq tern-command `("node" ,(executable-find "tern")))
            (when (require 'tern-auto-complete nil t)
              (tern-ac-setup)))
          (add-hook 'js2-mode-hook #'tern-mode))
      (message "--- tern is NOT installed in this system."))))

5.16. csvモード

(when (autoload-if-found '(csv-mode)
                         "csv-mode" nil t)
  (push '("\\.csv$" . csv-mode) auto-mode-alist))

5.17. asciiモード

カーソル下の文字のアスキーコードを別ウィンドウでリアルタイムに確認できます.

(autoload-if-found '(ascii-on ascii-off) "ascii" nil t)

5.18. javaモード

(when (autoload-if-found '(cc-mode)
                         "cc-mode" nil t)
  (push '("\\.pde$" . java-mode) auto-mode-alist) ;; Processing
  (push '("\\.java$" . java-mode) auto-mode-alist))

5.19. esモード

ElasticSearch のクエリを編集します.org-mode との連携もできます.

(when (autoload-if-found '(es-mode)
                         "es-mode" nil t)
  (push '("\\.es$" . es-mode) auto-mode-alist))

5.20. gnuplotモード

;; yes
(when (autoload-if-found '(gnuplot-mode)
                         "gnuplot-mode" nil t)
  (push '("\\.plt$" . gnuplot-mode) auto-mode-alist))

5.21. markdown-modeモード

(when (autoload-if-found '(markdown-mode)
                         "markdown-mode" nil t)
  (push '("\\.markdown$" . markdown-mode) auto-mode-alist)
  (push '("\\.md$" . markdown-mode) auto-mode-alist))

5.22. cmakeモード

(when (autoload-if-found '(cmake-mode)
                         "cmake-mode" nil t)
  (add-to-list 'auto-mode-alist '("CMakeLists\\.txt\\'" . cmake-mode))
  (add-to-list 'auto-mode-alist '("\\.cmake\\'" . cmake-mode))

  (with-eval-after-load "cmake-mode"
    (unless (executable-find "cmake")
      (message "--- cmake is NOT installed."))))

5.23. logviewモード

ログファイルが見やすくなり, n/p で移動可能になります.

(when (autoload-if-found '(logview-mode)
                         "logview" nil t)
  (push '("\\.log$" . logview-mode) auto-mode-alist))

5.24. viewモード

個別のファイルで view モードで開くことを指定するには次のようにします.

-*- mode:org; eval: (view-mode) -*-

一方,特定の拡張子に対して常に view モードで開きたい,例えば,gzされた elisp ソースを見るときに, view-mode を使います.また下記の設定では, my-auto-view-dirs に追加したディレクトリのファイルを開くと, view-mode が常に有効になります.

さらなる細かい制御が必要な場合は,viewer.el がおすすめです.

view-mode では独自のキーバインドが設定されているので,カーソル移動や <tab> を好みの状態に変えることで,より違和感なく使えるようになります. org バッファにおける n<tab> などの振る舞いですね.また origami.el があれば,~org~ バッファ以外でもFOLD機能を使って関数を簡略表示して,注目するもの(関数など)だけを読むことが可能です.

;;;###autoload
(defun my-auto-view ()
  "Open a file with `view-mode'."
  (when (file-exists-p buffer-file-name)
    (when (and my-auto-view-regexp
               (string-match my-auto-view-regexp buffer-file-name))
      (view-mode 1))
    (dolist (dir my-auto-view-dirs)
      (when (eq 0 (string-match (expand-file-name dir) buffer-file-name))
        (view-mode 1)))))

;;;###autoload
(defun my-org-view-next-heading ()
  (interactive)
  (if (and (derived-mode-p 'org-mode)
           (org-at-heading-p))
      (org-next-visible-heading 1)
    (next-line)))

;;;###autoload
(defun my-org-view-previous-heading ()
  (interactive)
  (if (and (derived-mode-p 'org-mode)
           (org-at-heading-p))
      (org-previous-visible-heading 1)
    (previous-line)))

;;;###autoload
(defun my-view-tab ()
  (interactive)
  (if (and (derived-mode-p 'org-mode)
           (or (org-at-heading-p)
               (org-at-property-drawer-p)))
      (let ((view-mode nil))
        (org-cycle))
    (when (require 'origami nil t)
      (origami-toggle-node (current-buffer) (point)))))

;;;###autoload
(defun my-view-shifttab ()
  (interactive)
  (if (derived-mode-p 'org-mode)
      (let ((view-mode nil))
        (org-shifttab))
    (when (require 'origami nil t)
      (origami-toggle-all-nodes (current-buffer)))))

;;;###autoload
(defun my-unlock-view-mode ()
  (when view-mode
    (View-exit-and-edit)))

;;;###autoload
(defun my-view-exit ()
  (interactive)
  (if (use-region-p) (my-eval-region) (View-exit)))

;;;###autoload
(defun ad:view--enable () (my-mode-line-on))

;;;###autoload
(defun ad:view--disable () (my-mode-line-off))

;;;###autoload
(defun ad:switch-to-buffer (&rest _arg)
  (when (and (not view-mode)
             (member (buffer-name) my-auto-view-buffers))
    (view-mode 1)))
;; 特定の拡張子・ディレクトリ
(defvar my-auto-view-regexp "\\.el.gz$\\|\\.patch$\\|\\.xml$\\|\\.gpg$\\|\\.csv$\\|\\.emacs.d/[^/]+/el-get\\|config")
(defvar my-auto-view-buffers '("*Messages*"))

;; 特定のディレクトリ(絶対パス・ホームディレクトリ以下)
(defvar my-auto-view-dirs nil)
(add-to-list 'my-auto-view-dirs "~/devel/emacs-head/emacs/")
(add-to-list 'my-auto-view-dirs "~/devel/git/org-mode/lisp/")
(when (eq window-system 'w32)
  (add-to-list 'my-auto-view-dirs "c:/msys64/mingw64"))

;; (autoload 'my-auto-view "view" nil t)
(add-hook 'find-file-hook #'my-auto-view)

(with-eval-after-load "view"
  ;; note: messages-buffer-mode-hook may not work
  (advice-add 'switch-to-buffer :after #'ad:switch-to-buffer)

  (keymap-set view-mode-map "i" 'View-exit-and-edit)
  (keymap-set view-mode-map "SPC" 'ignore)
  (keymap-set view-mode-map "<delete>" 'ignore)
  (keymap-set view-mode-map "S-SPC" 'mac-ime-toggle)
  (keymap-set view-mode-map "e" 'my-view-exit)
  (when (require 'helpful nil t)
    (keymap-set view-mode-map "h" 'helpful-at-point))
  (keymap-set view-mode-map "f" 'forward-char)
  (keymap-set view-mode-map "b" 'backward-char)
  (keymap-set view-mode-map "n" 'my-org-view-next-heading)
  (keymap-set view-mode-map "p" 'my-org-view-previous-heading)
  (keymap-set view-mode-map "g" #'my-google-this)
  (keymap-set view-mode-map "<tab>" 'my-view-tab)
  (keymap-set view-mode-map "S-<tab>" 'my-view-shifttab)
  (unless my-toggle-modeline-global
    (advice-add 'view--enable :before #'ad:view--enable)
    (advice-add 'view--disable :before #'ad:view--disable)))

5.25. Web/HTMLモード   web

HTML編集をするなら web-mode がお勧めです.古いHTMLモードを使っている方は,移行時期です.以下の my-web-indent-fold では, タブキーを打つたびにタグでくくられた領域を展開/非表示して整形します.Org-mode っぽい動作になりますが,操作の度にバッファに変更が加わったと判断されるので好みが分かれると思います.自動保存を有効にしているとそれほど気になりません.

;;;###autoload
(defun my-web-indent-fold ()
  (interactive)
  (web-mode-fold-or-unfold)
  (web-mode-buffer-indent)
  (indent-for-tab-command))
(when (autoload-if-found '(web-mode)
                         "web-mode" "web mode" t)
  ;; web-mode で開くファイルの拡張子を指定
  (push '("\\.phtml\\'" . web-mode) auto-mode-alist)
  (push '("\\.tpl\\.php\\'" . web-mode) auto-mode-alist)
  (push '("\\.jsp\\'" . web-mode) auto-mode-alist)
  (push '("\\.as[cp]x\\'" . web-mode) auto-mode-alist)
  (push '("\\.erb\\'" . web-mode) auto-mode-alist)
  (push '("\\.mustache\\'" . web-mode) auto-mode-alist)
  (push '("\\.djhtml\\'" . web-mode) auto-mode-alist)
  (push '("\\.html?\\'" . web-mode) auto-mode-alist)

  (with-eval-after-load "web-mode"
    (keymap-set web-mode-map "S-<tab>" 'my-web-indent-fold)

    ;; indent
    (setq web-mode-markup-indent-offset 1)

    ;; 色の設定
    (custom-set-faces
     ;; custom-set-faces was added by Custom.
     ;; If you edit it by hand, you could mess it up, so be careful.
     ;; Your init file should contain only one such instance.
     ;; If there is more than one, they won't work right.
     '(web-mode-comment-face ((t (:foreground "#D9333F"))))
     '(web-mode-css-at-rule-face ((t (:foreground "#FF7F00"))))
     '(web-mode-css-pseudo-class-face ((t (:foreground "#FF7F00"))))
     '(web-mode-css-rule-face ((t (:foreground "#A0D8EF"))))
     '(web-mode-doctype-face ((t (:foreground "#82AE46"))))
     '(web-mode-html-attr-name-face ((t (:foreground "#C97586"))))
     '(web-mode-html-attr-value-face ((t (:foreground "#82AE46"))))
     '(web-mode-html-tag-face ((t (:foreground "##4682ae" :weight bold))))
     '(web-mode-server-comment-face ((t (:foreground "#D9333F")))))))

5.26. POモード

;;(autoload 'po-mode "po-mode+" nil nil)
;;(autoload 'po-mode "po-mode" nil t)
(when (autoload-if-found '(po-mode)
                         "po-mode" nil t)
  (push '("\\.po[tx]?\\'\\|\\.po\\$" . po-mode) auto-mode-alist))

5.27. Goモード

(when (autoload-if-found '(go-mode)
                         "go-mode" nil t)
  (push '("\\.go\\'" . go-mode) auto-mode-alist))

5.28. スペルチェック

Built-in の ispell を使う.チェックエンジンは,aspell を利用する.そして, hunspell に移行した.

'ns sudo port install aspell aspell-dict-en
'x32 installer.exe and aspell-en from http://aspell.net/win32/

コマンドラインから aspell を使う時は,

aspell -l en -c <file>

とすると, ~/.aspell.en.pws を個人辞書として暗黙的に設定し,スペルチェックをしてくれる. hunspell が使える環境ならば,優先して使います.さらに, ~/.aspell.conf に,次を書いておきます.

lang en_US
(when (autoload-if-found '(ispell-region ispell-complete-word)
                         "ispell" nil t)

  ;; Spell checking within a specified region
  (keymap-global-set "C-c f 7" 'ispell-region)
  ;; 補完候補の表示(flyspell が使える時はそちらを優先して <f7> にする.
  (keymap-global-set "<f7>" 'ispell-word)

  (with-eval-after-load "ispell"
    ;; This could hild other messages from loading functions regarding org-mode.
    (advice-add 'ispell-init-process :around #'ad:suppress-message)

    ;; for English and Japanese mixed
    (add-to-list 'ispell-skip-region-alist '("[^\000-\377]+"))
    ;; http://endlessparentheses.com/ispell-and-org-mode.html
    (add-to-list 'ispell-skip-region-alist '("^#\\+begin_src" . "^#\\+end_src"))
    (add-to-list 'ispell-skip-region-alist '("~" "~"))
    (add-to-list 'ispell-skip-region-alist '("=" "="))
    (add-to-list 'ispell-skip-region-alist '(org-property-drawer-re))
    (setq ispell-encoding8-command t)

    (cond
     ((executable-find "hunspell")
      ;; (setenv "LC_ALL" "en_US") ;; Don't use this line.
      ;; (setq ispell-extra-args '("--lang=en_US"))
      ;; (setenv "DICPATH" "/Applications/LibreOffice.app/Contents/Resources/extensions/dict-en")
      (setenv "DICPATH" (concat (getenv "SYNCROOT") "/emacs.d/hunspell/dict-en"))
      (setq ispell-local-dictionary-alist
            '(("ja_JP" "[[:alpha:]]" "[^[:alpha:]]" "[']" nil
               ("-d" "en_US") nil utf-8)
              ("en_US" "[[:alpha:]]" "[^[:alpha:]]" "[']" nil
               ("-d" "en_US") nil utf-8)))
      (setq ispell-local-dictionary "en_US")
      (setq ispell-dictionary ispell-local-dictionary)
      (setq ispell-hunspell-dictionary-alist ispell-local-dictionary-alist)
      (if shutup-p
          ;; 必要.しかも ispell-program-name 指定の前で.
          ;; ただし,ispell-local-dictionary-alist の後で.
          (shut-up (ispell-change-dictionary "en_US" t))
        (ispell-change-dictionary "en_US" t))
      (setq-default ispell-program-name (executable-find "hunspell"))
      ;; Not regal way, but it's OK (usually ispell-local-dictionary-alist)

      (setq ispell-personal-dictionary
            (concat (getenv "SYNCROOT") "/emacs.d/hunspell.en.dic")))

     ((executable-find "aspell")
      ;; (message "--- aspell loaded.")
      (setq-default ispell-program-name "aspell")
      ;; (when (eq window-system 'w32)
      ;;   (setq-default ispell-program-name
      ;;                 "C:/Program Files/Aspell/bin/aspell.exe"))
      (setq ispell-dictionary "english")
      ;; This will also avoid an IM-OFF issue for flyspell-mode.
      ;; (setq ispell-aspell-supports-utf8 t) ;; Obsolete
      (setq ispell-local-dictionary-alist
            '((nil "[a-zA-Z]" "[^a-zA-Z]" "'" t
                   ("-d" "en" "--encoding=utf-8") nil utf-8)))
      (setq ispell-personal-dictionary
            (concat (getenv "SYNCROOT") "/emacs.d/config/aspell.en.pws")))
     (t
      nil))))

5.29. リアルタイムスペルチェック

Built-in の flyspell.el を使います.flyspell は内部で ispell を読み込んでいるので,辞書機能自体はそちらの設定が使われます.

http://www.morishima.net/~naoto/fragments/archives/2005/12/20/flyspell/

;;;###autoload
(defun my-flyspell-ignore-nonascii (beg end _info)
  "incorrect判定をASCIIに限定"
  (string-match "[^!-~]" (buffer-substring beg end)))
(add-hook 'flyspell-incorrect-hook #'my-flyspell-ignore-nonascii)

;;;###autoload
(defun my-flyspell-on ()
  (cond
   ((memq major-mode major-mode-with-flyspell)
    (turn-on-flyspell))
   ((memq major-mode major-mode-with-flyspell-prog)
    (flyspell-prog-mode))
   (t
    nil)))

;;;###autoload
(defun my-flyspell-off ()
  (when (memq major-mode my-flyspell-target-modes)
    (turn-off-flyspell)))
(when (autoload-if-found '(flyspell-mode-on
                           flyspell-prog-mode flyspell-mode my-flyspell-on)
                         "flyspell" nil t)
  (defvar major-mode-with-flyspell
    '(text-mode change-log-mode latex-mode yatex-mode
                git-commit-mode org-mode))
  (defvar major-mode-with-flyspell-prog
    '(c-mode-common emacs-lisp-mode perl-mode python-mode))
  (defvar my-flyspell-target-modes
    (append major-mode-with-flyspell
            major-mode-with-flyspell-prog))

  ;; バッファ内の全てをチェック対象にするモードの hook に flyspell 起動を登録
  (dolist (hook major-mode-with-flyspell)
    (add-hook (intern (format "%s-hook" hook)) #'flyspell-mode))

  ;; コメント行のみをチェック対象にする
  (dolist (hook major-mode-with-flyspell-prog)
    (add-hook (intern (format "%s-hook" hook)) #'flyspell-prog-mode))

  (with-eval-after-load "flyspell"
    ;; C-; をオーバーライド
    (keymap-set flyspell-mode-map "C-;" 'comment-dwim)
    (setq flyspell-duplicate-distance 0)
    ;; (setq flyspell-mode-line-string " F")
    (setq flyspell-mode-line-string "")
    ;; (setq flyspell-large-region 200)
    (set-face-attribute 'flyspell-duplicate nil
                        :foreground "#EA5506" :bold t
                        :background 'unspecified :underline t)
    (set-face-attribute 'flyspell-incorrect nil
                        :foreground "#BA2636" :bold nil
                        :background 'unspecified :underline t)

    ;; ispell-complete-word のキーバインドを上書き
    (keymap-global-set "<f7>" 'flyspell-correct-at-point)

    ;; ivy を用いる
    (when (require 'flyspell-correct-ivy nil t)
      (setq flyspell-correct-interface #'flyspell-correct-ivy))

    ;; Auto complete との衝突を回避
    (with-eval-after-load "auto-complete"
      (ac-flyspell-workaround))

    ;; [FIXME] nextstep+inline-patch版で flyspell すると,日本語nyuuのようになる場合があるので,それを回避(IME が ONになったら一時的に flyspell を止める)
    (add-hook 'input-method-activate-hook #'my-flyspell-off)
    (add-hook 'input-method-deactivate-hook #'my-flyspell-on)))

5.30. リージョン内の文字をカウントする

ビルドインの simple.el に十分な機能なのがある.

(keymap-global-set "M-=" 'count-words)

以前は,word-count.el を使用していた.

(when (autoload-if-found '(word-count-mode)
                         "word-count" "Minor mode to count words." t)
  (keymap-global-set "M-=" 'word-count-mode))

5.31. 世界時計を使う

M-x display-time-world で表示されます.次の設定を施して, shackle.el と組み合わせれば,表示後に q 押下でウィンドウを閉じてカーソルを元のバッファに戻せます.

世界の時刻を確認するために wclock.el がありました(参考:https://pxaka.tokyo/wiki/doku.php?id=emacs)が,現在はビルトインの time.eldisplay-time-world-mode として吸収されているようです. display-time-world-buffer-name に wclock が設定されているところが名残と思われます.

(with-eval-after-load "time"
  (keymap-set display-time-world-mode-map "q" 'delete-window))

tz の値は,システム内部 /usr/share/zoneinfo を見るか,公開情報から確認できます.

5.32. [counsel-world-clock.el] ivy でタイムゾーンの情報を選択する

(autoload-if-found '(counsel-world-clock) "counsel-world-clock" nil t)

5.33. [latex-math-preview.el] TeX数式をプレビュー   tex

以下の設定では, 数式で <f6> を押すとプレビューが走り,さらに <f6> を押すとプレビューウィンドウを閉じるように動作します.通常, q でプレビューを閉じられます.

(when (autoload-if-found '(latex-math-preview-expression
                           latex-math-preview-insert-symbol
                           latex-math-preview-save-image-file
                           latex-math-preview-beamer-frame)
                         "latex-math-preview" nil t nil)
  (keymap-global-set "<f6>" 'latex-math-preview-expression)
  (with-eval-after-load "latex-math-preview"
    (setq latex-math-preview-command-path-alist
          '((latex . "latex")
            (dvipng . "dvipng")
            (dvips . "dvips")))
    (keymap-set latex-math-preview-expression-mode-map "<f6>"
      'latex-math-preview-delete-buffer)))

5.34. [yatex.el] YaTeXでTex編集   tex

(when (autoload-if-found '(yatex-mode)
                         "yatex" "Yet Another LaTeX mode" t)
  (push '("\\.tex$" . yatex-mode) auto-mode-alist)

  (with-eval-after-load "yatex"
    ;; Disable auto line break
    (add-hook 'yatex-mode-hook
              (lambda ()
                (setq auto-fill-function nil)))

    ;; 1=Shift JIS, 2=JIS, 3=EUC, 4=UTF-8
    ;; (setq YaTeX-kanji-code nil)
    (modify-coding-system-alist 'file "\\.tex$'" 'utf-8)
    (keymap-set YaTeX-mode-map "C-M-SPC" 'mark-sexp)
    (keymap-set YaTeX-mode-map "C-M-@" 'mark-sexp)))

ショートカットの利用を強制するメッセージが出るので,抑制します.

;;;###autoload
(defun ad:YaTeX-insert-begin-end (env region-mode)
  "Insert \\begin{mode-name} and \\end{mode-name}.
This works also for other defined begin/end tokens to define the structure."
  (setq YaTeX-current-completion-type 'begin)
  (let*((ccol (current-column)) beg beg2 exchange
        (_arg region-mode)              ;for old compatibility
        (indent-column (+ ccol YaTeX-environment-indent))(_i 1) _func)
    (if (and region-mode (> (point) (mark)))
        (progn (exchange-point-and-mark)
               (setq exchange t
                     ccol (current-column)
                     indent-column (+ ccol YaTeX-environment-indent))))
    ;;VER2 (insert "\\begin{" env "}" (YaTeX-addin env))
    (setq beg (point))
    (YaTeX-insert-struc 'begin env)
    (setq beg2 (point))
    (insert "\n")
    (indent-to indent-column)
    (save-excursion
      ;;indent optional argument of \begin{env}, if any
      (while (> (point-beginning-of-line) beg)
        (skip-chars-forward "\\s " (point-end-of-line))
        (indent-to indent-column)
        (forward-line -1)))
    (require 'yatexenv)
    (if region-mode
        ;;if region-mode, indent all text in the region
        (save-excursion
          (if (fboundp (intern-soft (concat "YaTeX-enclose-" env)))
              (funcall (intern-soft (concat "YaTeX-enclose-" env))
                       (point) (mark))
            (while (< (progn (forward-line 1) (point)) (mark))
              (if (eolp) nil
                (skip-chars-forward " \t\n")
                (indent-to indent-column))))))
    (if region-mode (exchange-point-and-mark))
    (indent-to ccol)
    ;;VER2 (insert "\\end{" env "}\n")
    (YaTeX-insert-struc 'end env)
    (YaTeX-reindent ccol)
    (if region-mode
        (progn
          (insert "\n")
          (or exchange (exchange-point-and-mark)))
      (goto-char beg2)
      (YaTeX-intelligent-newline nil)
      (YaTeX-indent-line))
    (YaTeX-package-auto-usepackage env 'env)
    (if YaTeX-current-position-register
        (point-to-register YaTeX-current-position-register))))
(with-eval-after-load "yatex"
  (put 'YaTeX-insert-braces 'begend-guide 2)
  (advice-add 'YaTeX-insert-begin-end :override #'ad:YaTeX-insert-begin-end))

5.35. [auxtex.el] AUCTEXでTex編集   tex

実践未投入です.

(when (autoload-if-found '(autotex-latexmk)
                         "autotex-latexmk" nil t)
  (push '("\\.tex$" . autotex-latexmk) auto-mode-alist)
  (with-eval-after-load "autotex-latexmk"
    (setq-default TeX-master nil)
    (setq TeX-auto-save t)
    (setq TeX-parse-self t)
    (setq TeX-PDF-mode t)
    (auctex-latexmk-setup)))

5.36. [yasnippet.el] Emacs用のテンプレートシステム

実は余り使いこなせていません…

Org-modeとの衝突を避ける

↑のサイトで紹介されている回避策とは異なり,新たな my-yas-expand を作ることで,orgバッファのソースブロック中で <tab> 押下してもエラーを受けないようにしました.ソースコードは C-c ' で開く別バッファで編集します.

↑どうやら本家で対応がなされたようです. my-yas-expand なしで所望の動作になりました.ありがたや,ありがたや.

;; late-init.el
(when (autoload-if-found '(yas-minor-mode yas-global-mode)
                         "yasnippet" nil t)
  (dolist (hook
           (list
            'perl-mode-hook 'c-mode-common-hook 'js2-mode-hook 'org-mode-hook
            'python-mode-hook 'emacs-lisp-mode-hook))
    (add-hook hook #'yas-minor-mode))
  (with-eval-after-load "yasnippet"
    (setq yas-verbosity 2)
    (setq yas-snippet-dirs `(,(concat (getenv "SYNCROOT") "/emacs.d/yas-dict")))
    (unless noninteractive
      (yas-global-mode 1))))

以下は,本家できちんと対応されたので,不要になった.

(with-eval-after-load "yasnippet"
  (defun my-yas-expand-src-edit (&optional field)
    "Override `yas-expand'. Kick `org-edit-special' directly in src-block."
    (interactive)
    (cond ((and (equal major-mode 'org-mode)
                (org-in-src-block-p t))
           (org-edit-special))
          (t
           (yas-expand field))))

  (defun my-yas-expand (&optional field)
    "Disable `yas-expand' in src-block."
    (interactive)
    (cond ((and (equal major-mode 'org-mode)
                (org-at-heading-p))
           (org-cycle))
          ((and (equal major-mode 'org-mode)
                (org-in-src-block-p t)
                (not (and (fboundp 'org-src-edit-buffer-p)
                          (org-src-edit-buffer-p))))
           (org-cycle))
          (t (yas-expand field))))
  (keymap-set yas-minor-mode-map "<tab>" 'my-yas-expand))

5.36.1. [ivy-yasnippet.el] yasnippet.el の ivy インターフェイス

M-x ivy-yasnippet でスニペットを絞り込めます.

(with-eval-after-load "yasnippet"
  (require 'ivy-yasnippet nil t))

5.37. [osx-dictionary.el] macOSのdictionary.appで辞書をひく

osx-dictionary なるパッケージが存在します.さくさくと高速に動作します.なお, C-M-w は,本来,削除文字の連結(append-next-kill)にアサインされています.

(when (autoload-if-found '(osx-dictionary-search-pointer
                           osx-dictionary-search-input)
                         "osx-dictionary" nil t)
  (keymap-global-set "C-M-w" #'osx-dictionary-search-pointer)
  (keymap-global-set "C-c f w" #'osx-dictionary-search-input)
  (with-eval-after-load "osx-dictionary"
    (custom-set-variables
     '(osx-dictionary-dictionary-choice "英辞郎 第七版"))))

COBUILD5をデフォルトで使うには,次のサイト参照してください.

私の場合は,できあがった辞書を /Library/Dictionaries/ 以下に置いています.その状態で dictionary.app の設定で辞書の優先順位を変えることで,常にCOBUILD5の情報を引っ張り出せます.もしくは, osx-dictionary-dictionary-choice で辞書名を指定します.

5.38. [describe-number.el] 16進数などを確認

describe-number.el を使うと,16進数表示や文字コードを確認できます.

math-bignumcalc.el から削除されたため,依存している yabin が正常動作しない.

(autoload-if-found '(describe-number describe-number-at-point)
                   "describe-number" nil t) ;; FIXME

5.39. [emmet-mode.el] zencoding の後継   web

;; yes
(when (autoload-if-found '(emmet-mode)
                         "emmet-mode" nil t nil)
  (push '("\\.xml\\'" . nxml-mode) auto-mode-alist)
  (push '("\\.rdf\\'" . nxml-mode) auto-mode-alist)
  (dolist (hook
           '(sgml-mode-hook
             nxml-mode-hook css-mode-hook html-mode-hook web-mode-hook))
    (add-hook hook #'emmet-mode))
  (with-eval-after-load "emmet-mode"
    (setq emmet-indentation 2)
    (setq emmet-move-cursor-between-quotes t)))

5.40. [web-beautify.el] ソースコード整形   web

ソースコードを読みやすい表示に整形します.バッファの自動時に自動で整形を実施するには, after-save-hook を使えばOKですね.

JavaScript M-x web-beautify-js
HTML M-x web-beautify-html
CSS M-x web-beautify-css
(when (autoload-if-found '(js2-mode)
                         "js2-mode" nil t)
  (with-eval-after-load "js2-mode"
    (if (executable-find "js-beautify")
        (when (require 'web-beautify nil t)
          (keymap-set js2-mode-map "C-c b" 'web-beautify-js)
          (keymap-set js2-mode-map "C-c b" 'web-beautify-css))
      (message "--- js-beautify is NOT installed.")
      (message "--- Note: brew install node")
      (message "---       npm -g install js-beautify"))))

5.41. [smartparens.el] 対応するカッコの挿入をアシスト

;;;###autoload
(defun my-smartparens-mode ()
  (smartparens-global-mode)
  (remove-hook 'yatex-mode-hook #'my-smartparens-mode)
  (remove-hook 'org-mode-hook #'my-smartparens-mode))
(when (autoload-if-found '(smartparens-global-mode
                           turn-on-show-smartparens-mode)
                         "smartparens" nil t)
  (add-hook 'yatex-mode-hook #'my-smartparens-mode)
  (add-hook 'org-mode-hook #'my-smartparens-mode) ;; FIXME use activate()?
  (with-eval-after-load "smartparens"
    (setq-default sp-highlight-pair-overlay nil)
    (setq-default sp-highlight-wrap-overlay nil)
    (setq-default sp-highlight-wrap-tag-overlay nil)
    (sp-pair "`" nil :actions :rem)
    (sp-pair "'" nil :actions :rem)
    (sp-pair "[" nil :actions :rem)
    (sp-local-pair 'org-mode "=" "=")
    (sp-local-pair 'org-mode "$" "$" :actions '(wrap)) ;; 選択時のみ有効
    (sp-local-pair 'org-mode "'" "'" :actions '(wrap)) ;; 選択時のみ有効
    (sp-local-pair 'org-mode "<" ">" :actions '(wrap)) ;; 選択時のみ有効
    (sp-local-pair 'org-mode "_" "_" :actions '(wrap)) ;; 選択時のみ有効
    (sp-local-pair 'org-mode "~" "~" :actions '(wrap)) ;; 選択時のみ有効
    (sp-local-pair 'org-mode "[" "]" :actions '(wrap)) ;; 選択時のみ有効
    (sp-local-pair 'org-mode "+" "+" :actions '(wrap)) ;; 選択時のみ有効
    (sp-local-pair 'org-mode "/" "/" :actions '(wrap)) ;; 選択時のみ有効
    (sp-local-pair 'org-mode "*" "*" :actions '(wrap)) ;; 選択時のみ有効
    (sp-local-pair 'yatex-mode "$" "$" :actions '(wrap))))

5.42. TODO [grugru.el] 所定のキーワードをサイクルさせる

入力する候補が決まっているキーワード群について,それらをサイクルさせるようにするパッケージです.例えば, nil, t を高速に切り替えられるようになります.

(when (autoload-if-found '(grugru-default grugru)
                         "grugru-default" nil t)
  (keymap-global-set "C-9" #'grugru)
  (with-eval-after-load "grugru-default"
    (custom-set-faces
     '(grugru-edit-completing-function #'ivy-completing-read)
     '(grugru-highlight-face ((t (:bold t :underline "#FF3333"))))
     '(grugru-highlight-idle-delay 1))

    (add-hook 'grugru-before-hook #'my-unlock-view-mode)
    (add-hook 'grugru-after-hook #'save-buffer)
    (add-hook 'ah-after-move-cursor-hook #'grugru--highlight-remove)
    (grugru-define-on-major-mode 'org-mode 'word '("TODO" "DONE"))
    (grugru-define-global 'word '("True" "False"))
    (grugru-define-global 'word '("TRUE" "FALSE")) ;; FIXME
    (grugru-default-setup)
    (grugru-find-function-integration-mode 1)
    (grugru-highlight-mode 1)))

5.43. [replace-from-region.el] 選択領域を別の文字列に置き換える

通常の query-replace は,変更前と変更後の文字列を両方入力しますが, query-replace-from-region は,変更前の文字列を選択領域から自動抽出し,さらに同じ単語を変更後の文字列の候補として事前入力してくれます.したがって,文字列選択, query-replace-from-region 呼び出し,文字列を一部変更,実行,というフローになり簡略化されます.さらに, selected.el を使うことで,文字列選択後の query-replace-from-region 呼び出しは,シングルキーの押下で実現できます(私の場合は 5 の押下).

文字列選択は, C-M-SPC で簡単にできるので,つまり, C-M-SPC, 5 , 文字列を一部変更,実行となり,通常の query-replace よりもタイプ量を圧倒的に減らせます.

iedit.el の方が好みの人が多いかもしれません.

(autoload-if-found '(query-replace-from-region query-replace-regexp-from-region)
                   "replace-from-region" nil t)

5.44. [selected.el] リージョン選択時のアクションを制御

選択した後に右クリック的な感じでリージョンに対するアクションを制御できます.選択領域に対するスピードコマンドですね.普通にシングルキーを割り当てると,日本語IMEが有効な時に上手くいかないので, activate-mark-hookdeactivate-mark-hook に細工しています.

(autoload-if-found '(embark-act) "embark" nil t)
;;;###autoload
(defun my-activate-selected ()
  (require 'transient nil t)
  (selected-global-mode 1)
  (selected--on) ;; must call expclitly here
  (remove-hook 'activate-mark-hook #'my-activate-selected))

;;;###autoload
(defun my-helpful-variable ()
  (interactive)
  (let ((thing (symbol-at-point)))
    (if (helpful--variable-p thing)
        (helpful-variable thing)
      (call-interactively 'helpful-variable))))

(defvar my-eval-result "*eval-result*")

;;;###autoload
(defun my-eval-region ()
  (interactive)
  (when (use-region-p)
    (eval-region (region-beginning) (region-end)
                 (get-buffer-create my-eval-result))
    ;; Copy the result to kill-ring and print it
    (with-current-buffer (get-buffer-create my-eval-result)
      (delete-char -1)
      (goto-char (point-min))
      (delete-blank-lines)
      (mark-whole-buffer)
      (kill-ring-save (point-min) (point-max))
      (message "%s" (car kill-ring))
      (erase-buffer))
    ;; Jump to the end of the region
    (goto-char (max (or (mark) 0) (point)))
    (deactivate-mark)))

;;;###autoload
(defun my-eval-region-as-function ()
  (interactive)
  (when (use-region-p)
    (let ((region (intern (buffer-substring-no-properties
                           (region-beginning) (region-end)))))
      (funcall region))))

;;;###autoload
(defun my-describe-selected-keymap ()
  (interactive)
  (describe-keymap 'selected-keymap))

(when (autoload-if-found '(selected-global-mode)
                         "selected" nil t)
  (add-hook 'activate-mark-hook #'my-activate-selected)
  (with-eval-after-load "selected"
    (keymap-set selected-keymap "a" #'embark-act)
    (keymap-set selected-keymap ";" #'comment-dwim)
    (keymap-set selected-keymap "e" #'my-eval-region)
    (keymap-set selected-keymap "E" #'my-eval-region-as-function)
    ;; (keymap-set selected-keymap "=" #'count-words-region)
    (when (require 'helpful nil t)
      (keymap-set selected-keymap "h" #'helpful-at-point)
      (keymap-set selected-keymap "v" #'my-helpful-variable))
    (keymap-set selected-keymap "w" #'osx-dictionary-search-pointer)
    (keymap-set selected-keymap "d" #'osx-dictionary-search-pointer)
    (keymap-set selected-keymap "5" #'query-replace-from-region)
    (keymap-set selected-keymap "g" #'my-google-this)
    (keymap-set selected-keymap "s" #'osx-lib-say-region)
    (keymap-set selected-keymap "q" #'selected-off)
    (keymap-set selected-keymap "x" #'my-hex-to-decimal)
    (keymap-set selected-keymap "X" #'my-decimal-to-hex)

    ;; (defun my-eval-region ()
    ;;   (interactive)
    ;;   (when (use-region-p)
    ;;     (eval-region (region-beginning) (region-end) t)))

    (setq selected-org-mode-map (make-sparse-keymap))
    (keymap-set selected-org-mode-map "t" #'org-toggle-checkbox)
    (keymap-set selected-org-mode-map "-" #'my-org-bullet-and-checkbox)

    (when (require 'expand-region nil t)
      (keymap-set selected-keymap "SPC" #'er/expand-region))

    (when (require 'counsel-selected nil t)
      (keymap-set selected-keymap "l" 'counsel-selected))

    (when (require 'help-fns+ nil t)
      (keymap-set selected-keymap "H" #'my-describe-selected-keymap))))

5.45. [helm-selected.el] selecte.el のアクション候補を絞り込み

(with-eval-after-load "selected"
  (when (autoload-if-found '(helm-selected)
                           "helm-selected" nil t)
    (keymap-set selected-keymap "h" 'helm-selected)))

5.46. [counsel-selected.el] 続・selected.el のアクション候補絞り込み

ivy でも絞り込めるように拡張を作りました.

(with-eval-after-load "selected"
  (when (autoload-if-found '(counsel-selected)
                           "counsel-selected" nil t)
    (keymap-set selected-keymap "l" 'counsel-selected)))

5.47. TODO [multiple-cursors.el] 指定箇所を同時編集

;; late-init.el
(when (autoload-if-found '(mc/num-cursors
                           mc/edit-lines
                           hydra-multi-cursors/body)
                         "multiple-cursors" nil t)
  (keymap-global-set "C-c h m" #'hydra-multi-cursors/body)
  (with-eval-after-load "multiple-cursors"
    (when (require 'hydra nil t)
      ;; see https://github.com/abo-abo/hydra/wiki/multiple-cursors
      (defhydra hydra-multi-cursors (:hint nil)
        "
==================================================================
 Up^^             Down^^           Miscellaneous           % 2(mc/num-cursors) cursor%s(if (> (mc/num-cursors) 1) \"s\" \"\")
------------------------------------------------------------------
 [_p_]   Next     [_n_]   Next     [_l_] Edit lines  [_0_] Insert numbers
 [_P_]   Skip     [_N_]   Skip     [_a_] Mark all    [_A_] Insert letters
 [_M-p_] Unmark   [_M-n_] Unmark   [_s_] Search
 [Click] Cursor at point       [_q_] Quit"
        ("l" mc/edit-lines) ;;  :exit t
        ("a" mc/mark-all-like-this) ;;  :exit t
        ("n" mc/mark-next-like-this)
        ("N" mc/skip-to-next-like-this)
        ("M-n" mc/unmark-next-like-this)
        ("p" mc/mark-previous-like-this)
        ("P" mc/skip-to-previous-like-this)
        ("M-p" mc/unmark-previous-like-this)
        ("s" mc/mark-all-in-region-regexp) ;;  :exit t
        ("0" mc/insert-numbers) ;;  :exit t
        ("A" mc/insert-letters) ;;  :exit t
        ("<mouse-1>" mc/add-cursor-on-click)
        ;; Help with click recognition in this hydra
        ("<down-mouse-1>" ignore)
        ("<drag-mouse-1>" ignore)
        ("q" nil)))))

5.48. TODO [isolate.el] ブラケット等の入力をアシスト

;; late-init.el
(autoload-if-found '(isolate-quick-add
                     isolate-long-add isolate-quick-delete
                     isolate-quick-chnge isolate-long-change)
                   "isolate" nil t)

5.49. TODO [git-complete.el] GIT grep を使う補完エンジン

(when (autoload-if-found '(git-complete)
                         "git-complete" nil t)
  (keymap-global-set "C-c f <tab>" 'git-complete))

5.50. TODO [tiny.el] 連番入力をサポート

(when (require 'tiny nil t)
  (tiny-setup-default))

5.51. TODO [bratex.el] LaTeX 数式のブラケット入力をサポート

(when (autoload-if-found '(bratex-config)
                         "bratex" nil t)
  (add-hook 'yatex-mode-hook #'bratex-config))

5.52. DONE [iedit.el] バッファ内の同じ文字列を一度に編集する   obsolete

replace-from-region.elselected.el で呼び出すので満足しています.表示色の点では, highlight-symbol.el があるので,今からどれが編集されるのかもわかりやすいです.

iedit.el を使うと,バッファ内の同じ文字列を一度に編集することができる.部分重複のない変数名を置き換えるときに有用な場合がある.

(require 'iedit nil t)

5.53. DONE [lookup.el] 辞書   obsolete

最近使っていません.

;; .lookup/cache.el
(setq lookup-init-directory "~/env/dot_files/.lookup")

(autoload 'lookup "lookup" nil t)
(autoload 'lookup-region "lookup" nil t)
(autoload 'lookup-word "lookup" nil t)
(autoload 'lookup-select-dictionaries "lookup" nil t)

(setq lookup-search-modules
      '(("default"
         ("ndeb:/Users/taka/Dropbox/Dic/COBUILD5/cobuild" :priority t)
         ("ndeb:/Users/taka/Dropbox/Dic/COBUILD5/wordbank" :priority t)
         ("ndeb:/Users/taka/Dropbox/Dic/LDOCE4/ldoce4" :priority t)
         ("ndeb:/Users/taka/Dropbox/Dic/LDOCE4/bank" :priority t)
         ("ndeb:/Users/taka/Dropbox/Dic/LDOCE4/colloc" :priority t)
         ("ndeb:/Users/taka/Dropbox/Dic/LDOCE4/activ" :priority t))))

(setq lookup-agent-attributes
      '(("ndeb:/Users/taka/Dropbox/Dic/COBUILD5"
         (dictionaries "cobuild" "wordbank"))
        ("ndeb:/Users/taka/Dropbox/Dic/LDOCE4"
         (dictionaries "ldoce4" "bank" "colloc" "activ"))))

(setq lookup-dictionary-attributes
      '(("ndeb:/Users/taka/Dropbox/Dic/COBUILD5/cobuild"
         (title . "COBUILD 5th Edition")
         (methods exact prefix))
        ("ndeb:/Users/taka/Dropbox/Dic/COBUILD5/wordbank"
         (title . "Wordbank")
         (methods))
        ("ndeb:/Users/taka/Dropbox/Dic/LDOCE4/ldoce4"
         (title . "Longman 4th Edition")
         (methods exact prefix))
        ("ndeb:/Users/taka/Dropbox/Dic/LDOCE4/bank"
         (title . "LDOCE4 Examples and Phrases")
         (methods exact prefix menu))
        ("ndeb:/Users/taka/Dropbox/Dic/LDOCE4/colloc"
         (title . "LDOCE4 Collocation")
         (methods exact prefix))
        ("ndeb:/Users/taka/Dropbox/Dic/LDOCE4/activ"
         (title . "Longman Activator")
         (methods exact prefix menu))))

(setq lookup-default-dictionary-options
      '((:stemmer .  stem-english)))
(setq lookup-use-kakasi nil)
(keymap-global-set "<f6>" 'lookup-word)

;;; lookup for dictionary (require EB Library, eblook, and lookup.el)
;; package download: http://sourceforge.net/projects/lookup
;; http://lookup.sourceforge.net/docs/ja/index.shtml#Top
;; http://www.bookshelf.jp/texi/lookup/lookup-guide.html#SEC_Top
;;(load "lookup-autoloads") ; for 1.99
;;(autoload 'lookup "lookup" nil t)
;;(autoload 'lookup-region "lookup" nil t)
;;(autoload 'lookup-word "lookup" nil t)
;;(autoload 'lookup-select-dictionaries "lookup" nil t)
;; Search Agents
;; ndeb option requries "eblook" command
;; Use expand-file-name!
;;(setq lookup-search-agents `((ndeb ,(concat homedir "/Dropbox/Dic/COBUILD5"))
;;                            (ndeb ,(concat homedir "/Dropbox/Dic/LDOCE4"))))
;;(setq lookup-use-bitmap nil)
;;(setq ndeb-program-name "/usr/bin/eblook")
;;(when (eq window-system 'ns)
;;  (setq ndeb-program-name "/opt/local/bin/eblook")
;;  (setq ndeb-program-arguments '("-q" "-e" "euc-jp"))
;;  (setq ndeb-process-coding-system 'utf-8)) ; utf-8-hfs

5.54. DONE [cacoo] Cacoo で描く   obsolete

画像をリサイズしてバッファに表示する用途にも使える.

(when (autoload-if-found '(toggle-cacoo-minor-mode)
                         "cacoo" nil t)
  (with-eval-after-load "cacoo"
    (require 'cacoo-plugins))

  (keymap-global-set "M--" 'toggle-cacoo-minor-mode))

5.55. DONE [zencoding-mode] HTML編集の高速化   obsolete

zencoding でタグ打ちを効率化します.今は emmet-mode を使います.

(when (autoload-if-found '(zencoding-mode zencoding-expand-line)
                         "zencoding-mode" "Zen-coding" t)
  (with-eval-after-load "zencoding-mode"
    (keymap-set zencoding-mode-keymap "M-<return>" 'zencoding-expand-line))
  (add-hook 'sgml-mode-hook #'zencoding-mode)
  (add-hook 'html-mode-hook #'zencoding-mode)
  (add-hook 'web-mode-hook #'zencoding-mode))

5.56. DONE [sdic.el] 英辞郎で英単語を調べる   obsolete

  • osx-directory.el に移行しました.

http://www.namazu.org/~tsuchiya/sdic/index.html

Emacs から辞書を使う.lookup を使う方法もあるが,Emacsから使うのは英辞郎に限定.

(when (autoload-if-found '(sdic-describe-word sdic-describe-word-at-point)
                         "sdic" nil t)
  (with-eval-after-load "sdic"
    (setq sdic-face-color "#3333FF")
    (setq sdic-default-coding-system 'utf-8)
    ;; Dictionary (English => Japanese)
    (setq sdic-eiwa-dictionary-list
          '((sdicf-client "~/Dropbox/Dic/EIJIRO6/EIJI-128.sdic")))
    ;; Dictionary (Japanese => English)
    (setq sdic-waei-dictionary-list
          '((sdicf-client "~/Dropbox/Dic/EIJIRO6/WAEI-128.sdic"))))

  ;; カーソルの位置の英単語の意味を調べる
  (keymap-global-set "C-M-w" 'sdic-describe-word-at-point)
  ;; ミニバッファに英単語を入れて英辞郎を使う
  (keymap-global-set "C-c w" 'sdic-describe-word))

5.57. DONE [dictionary.app] macOSのdictionary.appでCOBUILD5をひく   obsolete

OS標準の辞書アプリ(dictionary.app)を経由して,バッファにCOBUILD5のデータを流し込むことができます.

以下の関数を準備します.

(defun dictionary ()
  "dictionary.app"
  (interactive)

  (let ((editable (not buffer-read-only))
        (pt (save-excursion (mouse-set-point last-nonmenu-event)))
        beg end)

    (if (and mark-active
             (<= (region-beginning) pt) (<= pt (region-end)) )
        (setq beg (region-beginning)
              end (region-end))
      (save-excursion
        (goto-char pt)
        (setq end (progn (forward-word) (point)))
        (setq beg (progn (backward-word) (point)))
        ))

    (let ((word (buffer-substring-no-properties beg end))
          ;;            (win (selected-window))
          (tmpbuf " * dict-process*"))
      (pop-to-buffer tmpbuf)
      (erase-buffer)
      (insert "Query: " word "\n\n")
      (start-process "dict-process" tmpbuf "dict.py" word)
      (goto-char 0)
      ;;        (select-window win)
      )))

これでカーソル以下の単語の情報が別ウィンドウに出ます.チェックし終わったら C-x 1 (delete-other-windows) で表示を閉じます. q で閉じられるようにしたり,ツールチップで表示したりもできるはずです.

マスタカさんのナイスソリューションをまだ試していないので,こちらの方がエレガントかもしれません.

なお,COBUILD5の辞書データをdictionary.appで引けるようにするには以下の操作が必要です.

私の場合は,できあがった辞書を /Library/Dictionaries/ 以下に置いています.その状態で dictionary.app の設定で辞書の優先順位を変えることで,常にCOBUILD5の情報を引っ張り出せます.

5.57.1. マイナーモード化

q で閉じたくなったのでマイナーモードを作りました.これまで通り, C-M-w でカーソル下の単語を調べてポップアップで表示.カーソルはその新しいバッファに移しておき, q で閉じられます.新しいバッファ内で別な単語を C-M-w で調べると,同じバッファに結果を再描画します.

マイナーモード化した elisp は,gistで公開しています.

5.57.2. キーバインド

マイナーモード化した dict-app を使う場合は以下のようにします.sdic を使っている人は,sdic 用の設定と衝突しないように気をつけます.

(when (autoload-if-found '(dict-app-search)
                         "dict-app" nil t)
  ;; カーソルの位置の英単語の意味を調べる
  (keymap-global-set "C-M-w" 'dict-app-search))

6. 表示サポート

6.1. キーコマンド入力中に入力過程をミニバッファに反映する

標準値は 1 です.例えば, C-c f r で発動する関数があるとき, C-c を入力するとその直後にはミニバッファに何も表示されませんが, echo-keystrokes だけ経過すると, C-c が表示されます. 0 に設定すると,いくら経過しても何も表示しません. which-key.el の設定で表示を 1.0 にすると,時系列的に, 0.5 秒でキー入力の状態を表示し, 1.0 秒で続くキーで入力可能なコマンドがリストアップ表示されます.

(setq echo-keystrokes 0.5)

6.2. TODO モードラインの色をナローイングで変える

;;;###autoload
(defun my-update-modeline-face ()
  (setq my-selected-window-last (frame-selected-window))
  ;; (message "--- %s" my-selected-window-last)
  (unless (minibufferp)
    (my-modeline-face (buffer-narrowed-p))))

;;;###autoload
(defun my-modeline-face (buffer-narrowed)
  "Update modeline color.
If BUFFER-NARROWED is nil, then change the color to indicating `widen'.
Otherwise, indicating narrowing."
  (unless (eq my-buffer-narrowed-last
              buffer-narrowed) ;; block unnecessary request
    (setq my-buffer-narrowed-last buffer-narrowed)
    ;; (message "--- %s %s %s" this-command last-command buffer-narrowed)
    (when (not (memq this-command '(save-buffer))) ;; FIXME
      (if buffer-narrowed
          (custom-set-faces
           `(mode-line ((t (:background
                            ,(nth 0 my-narrow-modeline)
                            :foreground
                            ,(nth 1 my-narrow-modeline))))))
        (custom-set-faces '(mode-line ((t nil))))))))

;;;###autoload
(defun my-update-modeline-color ()
  "Update modeline face of the current selected window.
Call this function at updating `mode-line-mode'."
  (when (eq my-selected-window-last (frame-selected-window))
    (my-modeline-face (buffer-narrowed-p))))
(defvar my-narrow-modeline '("#426EBB" "#FFFFFF")) ;; background, foreground
(defvar my-buffer-narrowed-last nil)
(make-local-variable 'my-buffer-narrowed-last)
(defvar my-selected-window-last nil)
(add-hook 'buffer-list-update-hook #'my-update-modeline-face)

6.3. TODO モードラインのNarrowを短くする

標準では「Narrow」と表示されますが,「N」に短縮します. all-the-icons を使って,画像に置き換えることも可能です.

(setq mode-line-modes
      (mapcar
       (lambda (entry)
         (if (equal entry "%n")
             '(:eval (progn
                       ;; org が widen を乱発するのでこちらをトリガーにする.
                       ;; 色の変更
                       (my-update-modeline-color)
                       ;; "Narrow" を "N" に短縮表示
                       (if (and (buffer-narrowed-p)
                                (fboundp 'icons-in-terminal-octicon))
                           (concat " " (icons-in-terminal-octicon
                                        "fold" :v-adjust 0.0)) "")))
           entry))
       mode-line-modes))

6.4. TODO [mlscroll.el] モードラインにバッファ内表示位置をバー形式で表示

mlscroll.el を使います.

;;;###autoload
(defun my-reload-mlscroll ()
  (mlscroll-mode -1)
  (setq mlscroll-border (ceiling (/ moom-font--size 4.0)))
  (mlscroll-mode 1))
(when (require 'mlscroll nil t)
  (custom-set-variables
   '(mlscroll-in-color "light coral") ;;  #FFA07A
   '(mlscroll-out-color "#FFFFEF")
   '(mlscroll-width-chars 10))
  (unless noninteractive
    (mlscroll-mode 1))

  (with-eval-after-load "moom"
    (add-hook 'moom-font-after-resize-hook #'my-reload-mlscroll)
    (add-hook 'moom-after-reset-hook #'my-reload-mlscroll)))

6.5. モードラインの節約(VC-mode編)

定形で表示されている Git をアイコン化します.

;;;###autoload
(defun my-mode-line-vc-mode-icon ()
  (if (string-match "^ Git:" vc-mode)
      (replace-regexp-in-string
       "^ Git:" (propertize " " 'face 'mode-line-vc-modified-face) vc-mode)
    (replace-regexp-in-string
     "^ Git-" (propertize " " 'face 'mode-line-vc-normal-face) vc-mode)))
(with-eval-after-load "icons-in-terminal"
  ;; 変更がアリ時は赤アイコン,そうでない時に緑アイコンをモードラインに表示
  (make-face 'mode-line-vc-normal-face)
  (make-face 'mode-line-vc-modified-face)
  (set-face-attribute 'mode-line-vc-normal-face nil :foreground "#AFFFAF")
  (set-face-attribute 'mode-line-vc-modified-face nil :foreground "#EEAFAF"))

(with-eval-after-load "bindings" ;; "bindings"
  (let ((vc (assq 'vc-mode mode-line-format)))
    ;; (message "--- %s" vc)
    (when vc (setcdr vc '((:eval (my-mode-line-vc-mode-icon)))))))

6.6. モードラインの色をカスタマイズする

配色は定期的に変えています.

(if (not (display-graphic-p))
    (progn ;; Terminal
      (set-face-foreground 'mode-line "#96CBFE")
      (set-face-background 'mode-line "#21252B"))

  ;; mode-line
  (set-face-attribute 'mode-line nil
                      :foreground "#FFFFFF"
                      :background "#a46398"
                      ;; :overline "#9d5446"
                      :box nil)
  ;; mode-line-inactive
  (set-face-attribute 'mode-line-inactive nil
                      :foreground "#FFFFFF"
                      :background "#c8a1b7"
                      ;; :overline "#FFFFFF"
                      :box nil))

6.6.1. 色セット例

  • 青/白
  background foreground overline
active 558BE2 FFFFFF 566f99
inactive 94bbf9 EFEFEF a4bfea
  background foreground overline
active b2cefb 203e6f 203e6f
inactive 94bbf9 94bbf9 94bbf9
  background foreground overline
active b1fbd6 206f47 206f47
inactive 95f9c7 95f9c7 95f9c7

6.7. visible-bell のカスタマイズ

最近は鬱陶しくなってしまい,ビープ音も無しかつ視覚効果も無しにしています.

;;  (setq visible-bell nil) ;; default=nil
(setq ring-bell-function 'ignore)

see http://yohshiy.blog.fc2.com/blog-entry-171.html

以前は,http://www.emacswiki.org/emacs/MilesBader を参考にカスタマイズしていました.現在は後継パッケージ(http://www.emacswiki.org/emacs/echo-bell.el)があり,MELPAから取れます.

visibl-bell を使うと,操作ミスで発生するビープ音を,視覚的な表示に入れ替えられます.ただ,デフォルトではバッファ中央に黒い四角が表示されて少々鬱陶しいので,ミニバッファの点滅に変更します.

(when (autoload-if-found '(echo-area-bell)
                         "echo-area-bell" nil t)
  (with-eval-after-load "echo-area-bell"
    (setq visible-bell t)
    (setq ring-bell-function 'echo-area-bell)))

;; パッケージ(echo-bell)の場合
(when (require 'echo-bell nil t)
  (setq echo-bell-string "")
  (setq echo-bell-background "#FFDCDC")
  (setq echo-bell-delay 0.1)
  (echo-bell-mode 1))

6.8. 常に scratch を表示して起動する

最近は,起動用にメジャーモードを書いて対応しています.詳しくはリンク先にて.

なお, C-M-s をスクラッチバッファを表示するために使用していますが,本来は,正規表現での検索(isearch-forward-regexp)に割り振られています.

パッケージとして切り出された別ファイルにすると,そのファイルをロードすることも起動時のコストになるため,現在は下記を init.el 内に直接記述しています.

(defun empty-booting-mode ()
  "Minimum mode for quick booting"
  (interactive)
  (setq mode-name "Empty")
  (setq major-mode 'empty-booting-mode)
  (setq header-line-format " No day is a good day.")
  ;;  (setq buffer-mode-map (make-keymap))
  ;;  (use-local-map buffer-mode-map)
  (run-hooks 'empty-booting-hook))
;; (setq initial-buffer-choice t) ;; 引数付き起動すると画面分割される
(setq initial-scratch-message nil)
(setq initial-major-mode 'empty-booting-mode)
(set-face-foreground 'header-line "#FFFFFF") ;; "#203e6f" #333333 "#FFFFFF"
(set-face-background 'header-line "#a46398") ;; "#ffb08c" "#7e59b5" ##5F7DB7
(set-face-attribute 'header-line nil
                    :inherit nil
                    :overline nil
                    :underline nil)
(unless noninteractive
  (my-empty-booting-header-line)) ;; Update header of scratch buffer
;;;###autoload
(defun my-open-scratch ()
  "Switch the current buffer to \*scratch\* buffer."
  (interactive)
  (switch-to-buffer "*scratch*"))

;;;###autoload
(defun ad:split-window-below (&optional _size)
  "An extention to switch to \*scratch\* buffer after splitting window."
  (my-open-scratch))
;; (advice-add 'split-window-below :after #'ad:split-window-below)
(if (< emacs-major-version 29)
    (keymap-global-set "C-M-s" #'my-open-scratch)
  (keymap-global-set "C-M-s" #'scratch-buffer))

session.eldesktop.el を使っていても,いつも *scratch* バッファを表示する.そうじゃないと安心できない人向け.

使われるメジャーモードと表示する文字列も制御できます.

;; Start Emacs with scratch buffer even though it call session.el/desktop.el
(add-hook 'emacs-startup-hook (lambda () (switch-to-buffer "*scratch*")))
(setq initial-major-mode 'text-mode)
(setq initial-scratch-message
      (concat "                                                              "
              (format-time-string "%Y-%m-%d (%a.)") "\n"
              "-----------------------------------------------------"
              "--------------------------\n"))

6.9. スクロールバーを非表示にする

スクロールバーを非表示にするには,nil を指定します. 右側に表示したい場合は,'right とします. スクロールバーの非表示は起動後に実施されるため,フレームがチラつきます.それを解消するために,現在は,ソースコードレベルでオフにしています.パッチに興味がある場合は,こちら.以下は,EMPビルド向けに設定しています.

;; Show scroll bar or not
(when (and (display-graphic-p)
           (not early-init-file)
           (memq window-system '(ns mac)))
  (set-scroll-bar-mode nil)) ; 'right

6.10. ツールバーを非表示にする

ツールバーは使わないので非表示にします. early-init.el にも設定があることを前提しています.

;; Disable to show the tool bar.
(when (and (boundp 'early-init-file)
                 (not early-init-file)
                 (display-graphic-p))
  (tool-bar-mode -1))

6.11. メニューバーを非表示にする

特にターミナルでは不要です.Windowsでも非表示にします. early-init.el にも設定があることを前提しています.

(when (and (boundp 'early-init-file)
                 (not early-init-file)
                 (or (not (display-graphic-p))
                     (eq system-type 'windows-nt)))
  (menu-bar-mode -1))

6.12. 起動時のスプラッシュ画面を表示しない

;; Disable to show the splash window at startup
(setq inhibit-startup-screen t)

6.13. ターミナル時のウィンドウ分割線を見やすくする

see https://www.reddit.com/r/emacs/comments/3u0d0u/how_do_i_make_the_vertical_window_divider_more/

;;;###autoload
(defun my-change-window-divider ()
  (interactive)
  (let ((display-table (or buffer-display-table
                                             standard-display-table
                                             (make-display-table))))
    (set-display-table-slot display-table 5 ?│)
    (set-window-display-table (selected-window) display-table)))
(unless (display-graphic-p)
  ;; ターミナルの縦分割線をUTF-8できれいに描く
  (add-hook 'window-configuration-change-hook 'my-change-window-divider))

6.14. default.el を探索させない

(setq inhibit-default-init t)

6.15. カーソル行の行数をモードラインに表示する

;; Show line number in the mode line.
(unless noninteractive
  (line-number-mode 1))

6.16. カーソル行の関数名をモードラインに表示する

  • emacs24.3 で重く感じるので外している.
;; Show function name in the mode line.
(which-function-mode t)

6.17. 行番号をバッファに表示する

Emacs Version 26.1 からネイティブ実装に切り替わった display-line-numbers.el を使います.普段は使わないので,必要に応じてグローバルにトグルできるようにしてあります.現在行の番号について face を変更したい場合は, line-number-current-line をカスタマイズすればOKです.

;;;###autoload
(defun my-update-display-line-numbers-face ()
  (custom-set-faces
   `(line-number-current-line
     ((t (:bold t :background ,(face-attribute 'hl-line :background)))))))

;;;###autoload
(defun my-display-line-numbers-width ()
  (when (< display-line-numbers-width 5)
    (setq display-line-numbers-width 5))
  (setq moom-display-line-numbers-width (+ 2 display-line-numbers-width)))

;;;###autoload
(defun my-display-line-numbers-mode-on ()
  "Trun on `display-line-numbers'."
  (interactive)
  (if (fboundp 'global-display-line-numbers-mode) ;; 26.1 or later
      (unless global-display-line-numbers-mode
        (global-display-line-numbers-mode 1)
        (line-number-mode -1))
    (user-error "The display-line-numbers is NOT supported")))

;;;###autoload
(defun my-display-line-numbers-mode-off ()
  "Trun off `display-line-numbers'."
  (interactive)
  (if (fboundp 'global-display-line-numbers-mode) ;; 26.1 or later
      (when global-display-line-numbers-mode
        (global-display-line-numbers-mode -1)
        (line-number-mode 1))
    (user-error "The display-line-numbers is NOT supported")))

;;;###autoload
(defun my-toggle-display-line-numbers-mode ()
  "Toggle variable `global-display-line-numbers-mode'."
  (interactive)
  (if (fboundp 'global-display-line-numbers-mode) ;; 26.1 or later
      (let ((flag (if global-display-line-numbers-mode -1 1)))
        (global-display-line-numbers-mode flag)
        (line-number-mode (- flag)))
    (user-error "The display-line-numbers is NOT supported")))
(when (autoload-if-found '(my-toggle-display-line-numbers-mode)
                         "display-line-numbers" nil t)
  (keymap-global-set "C-<f12>" 'my-toggle-display-line-numbers-mode)
  (with-eval-after-load "hl-line"
    (my-update-display-line-numbers-face)
    (add-hook 'my-ime-off-hline-hook #'my-update-display-line-numbers-face)
    (add-hook 'my-ime-on-hline-hook #'my-update-display-line-numbers-face))

  (with-eval-after-load "display-line-numbers"
    (require 'moom nil t)
    (custom-set-faces
     '(line-number-current-line
       ((t (:bold t)))))

    (custom-set-variables
     '(display-line-numbers-width-start t))

    ;; ウィンドウ左に表示する行数の幅を5以上に固定する.
    (add-hook 'display-line-numbers-mode-hook
              #'my-display-line-numbers-width)))

6.18. 行番号の表示制限を拡張する

デフォルトだと200桁までしかモードラインに表示されないので,それ以上の行数であっても,行数がいつも表示されるように拡張します.

さらにモードラインの表示をちょっとリッチにします.

(setq line-number-display-limit-width 100000)

;; モードラインの行数表示の前にアイコンを追加
(with-eval-after-load "icons-in-terminal"
  (setq mode-line-position-line-format
        `(,(icons-in-terminal-material "edit") "%3l")))

6.19. 時刻をモードラインに表示する

;; Show clock in in the mode line
(setq display-time-format "%H:%M w%V") ;; %y%m%d. ;; "%H%M.%S"
(setq display-time-interval 1)
(setq display-time-default-load-average nil)
(unless noninteractive
  (display-time-mode 1))

6.20. 対応するカッコをハイライトする

Built-in の paren.el が利用できる.拡張版として mic-paren.el があり,現在はこれを利用している.

2022-07-29: emacs 28.1 になった頃から paren.el でもいい感じになっているので,しばらく様子見ます.

;;;###autoload
(defun my-mic-paren-activate ()
  (paren-activate)
  (show-paren-mode -1)
  (remove-hook 'find-file-hook #'my-mic-paren-activate))

;;;###autoload
(defun ad:mic-paren-highlight (f)
  (if (active-minibuffer-window)
      (let ((paren-display-message 'never))
        (funcall f)
        paren-display-message)
    (funcall f)))
;; (eval-when-compile
;;   (require 'mic-paren nil t))

(when (autoload-if-found '(paren-activate)
                         "mic-paren" nil t)
  (add-hook 'find-file-hook #'my-mic-paren-activate)
  (with-eval-after-load "mic-paren"
    (setq paren-sexp-mode nil)
    (set-face-foreground 'paren-face-match "#FFFFFF")
    ;; Deep blue: #6666CC, orange: #FFCC66
    (set-face-background 'paren-face-match "#66CC66")

    ;; for ivy-mode, "Matches" と表示される関数との衝突をさける
    (advice-add 'mic-paren-highlight :around #'ad:mic-paren-highlight)))

paren.el の場合は以下の設定.

(setq show-paren-delay 0)
(show-paren-mode t)
;; (setq show-paren-style 'expression) ; カッコ内も強調
;;(set-face-background 'show-paren-match-face "#5DA4ff") ; カーソルより濃い青
(set-face-background 'show-paren-match-face "#a634ff")
(set-face-foreground 'show-paren-match-face "#FFFFFF")
(set-face-underline-p 'show-paren-match-face nil)
(setq show-paren-style 'parenthesis)

6.21. 全角スペースと行末タブ/半角スペースを強調表示する

英語で原稿を書く時に全角スペースが入っているを苦労するので,強調表示して編集中でも気づくようにします.また,行末のタブや半角スペースも無駄なので,入り込まないように強調しています.パッケージを使うと too much かなという印象があったので,個別の設定だけを使わせてもらっています.

;;;###autoload
(defun ad:font-lock-mode (&optional _ARG)
  (unless (memq major-mode '(vterm-mode))
    (font-lock-add-keywords major-mode
                            ;; "[\t]+$" 行末のタブ
                            '((" " 0 'my-face-b-1 append)
                              ("[ ]+$" 0 'my-face-b-3 append)
                              ("[\t]+$" 0 'my-face-b-2 append)))))
;; スペース
(defface my-face-b-1
  '((t (:background "gray" :bold t :underline "red")))
  nil :group 'font-lock-highlighting-faces)
;; タブだけの行
(defface my-face-b-2
  '((t (:background "orange" :bold t :underline "red")))
  nil :group 'font-lock-highlighting-faces)
;; 半角スペース
(defface my-face-b-3 '((t (:background "orange")))
  nil :group 'font-lock-highlighting-faces)
(advice-add 'font-lock-mode :before #'ad:font-lock-mode)

;;show EOF
(defun set-buffer-end-mark()
  (let ((overlay (make-overlay (point-max) (point-max))))
    (overlay-put overlay 'before-string #("[EOF]" 0 5 (face highlight)))
    (overlay-put overlay 'insert-behind-hooks
                 '((lambda (overlay after beg end &optional len)
                     (when after
                       (move-overlay overlay (point-max) (point-max))))))))
(add-hook 'find-file-hook #'set-buffer-end-mark)

6.21.1. emacs 28 からの表示制御

version 28 で全角スペースに黒下線が付くので回避します.

(unless (version< emacs-version "28.0")
  ;; 全角スペース" "にデフォルトで黒下線が付くのを回避する
  (setq nobreak-char-display nil))

6.22. バッファの終わりをフリンジで明示

以下の設定では,ウィンドウ以下にバッファが続いているかを表す矢印と,続いていないことを示すカギカッコをフリンジに表示します.

(setq-default indicate-buffer-boundaries
              '((top . nil) (bottom . right) (down . left)))

6.23. 文字エンコードの表示を明確化

デフォルトの表示は略されすぎていてわかりにくいので,表示内容を具体化します.

;; 文字エンコーディングの文字列表現

;;;###autoload
(defun my-coding-system-name-mnemonic (coding-system)
  (let* ((base (coding-system-base coding-system))
         (name (symbol-name base)))
    (cond ((string-prefix-p "utf-8" name) "U8")
          ((string-prefix-p "utf-16" name) "U16")
          ((string-prefix-p "utf-7" name) "U7")
          ((string-prefix-p "japanese-shift-jis" name) "SJIS")
          ((string-match "cp\\([0-9]+\\)" name) (match-string 1 name))
          ((string-match "japanese-iso-8bit" name) "EUC")
          (t "???"))))

;;;###autoload
(defun my-coding-system-bom-mnemonic (coding-system)
  (let ((name (symbol-name coding-system)))
    (cond ((string-match "be-with-signature" name) "[BE]")
          ((string-match "le-with-signature" name) "[LE]")
          ((string-match "-with-signature" name) "[BOM]")
          (t ""))))

;;;###autoload
(defun my-mode-line-icon-lock ()
  (if view-mode
      (concat (icons-in-terminal-faicon
               "lock" :face '(:foreground "#FF0000")) " ") ""))

;;;###autoload
(defun my-mode-line-icon-for-file ()
  (icons-in-terminal-icon-for-file
   (buffer-name) :v-adjust 0.03 :face 'mode-line-file-icon-face))

;;;###autoload
(defun my-buffer-coding-system-mnemonic ()
  "Return a mnemonic for `buffer-file-coding-system'."
  (let* ((code buffer-file-coding-system)
         (name (my-coding-system-name-mnemonic code))
         (bom (my-coding-system-bom-mnemonic code)))
    (if (version< emacs-version "29.0")
        (format "%s %s%s" (my-mode-line-icon-for-file) name bom )
      (format "%s%s" name bom ))))
;; 改行文字の文字列表現
(set 'eol-mnemonic-dos "CRLF")
(set 'eol-mnemonic-unix "LF")
(set 'eol-mnemonic-mac "CR")
(set 'eol-mnemonic-undecided "?")

(make-face 'mode-line-file-icon-face)
(custom-set-faces
 '(mode-line-file-icon-face
   ((((background dark)) :foreground "VioletRed1")
    (t (:foreground "LightGoldenrod1")))))

;; `mode-line-mule-info' の文字エンコーディングの文字列表現を差し替える
(setq-default mode-line-mule-info
              (cl-substitute '(:eval (my-buffer-coding-system-mnemonic))
                             "%z" mode-line-mule-info :test 'equal))

6.24. [delight.el] モードラインのモード名を短縮する

以前は diminish.el を使用していましたが,ELPA配布のパッケージに移行しました.メジャーモードの短縮表示もマイナーモードの場合と同様に設定できます.この点で,設定がスッキリしました.

;;;###autoload
(defun my-delight-activate ()
  (require 'delight nil t)
  (remove-hook 'find-file-hook #'my-delight-activate))
(add-hook 'find-file-hook #'my-delight-activate)

(with-eval-after-load "delight"
  (delight
   '(;; Major modes
     ;;     (c-mode "C" :major)
     ;;     (c++mode "C++" :major)
     (js2-mode "JS" :major)
     (csharp-mode "C#" :major)
     (prog-mode "Pr" :major)
     (emacs-lisp-mode "El" :major)
     (python-mode "Py" :major)
     (perl-mode "Pl" :major)
     (web-mode "W" :major)
     (change-log-mode "CLog" :major)
     (lisp-interaction-mode "Lisp" :major)

     ;; Shorten for minor modes
     (ggtags-mode " G" "ggtags")
     ;; (orgstruct-mode " OrgS" "org")
     (orgalist-mode " ol" "orgalist")
     (view-mode " V" "view")
     ;; Stop to display for minor modes
     (org-fancy-priorities-mode nil "org-fancy-priorities")
     (smooth-scroll-mode nil "smooth-scroll")
     (eldoc-mode nil "eldoc")
     (ivy-mode nil "ivy")
     (counsel-mode nil "counsel")
     (centered-cursor-mode nil "centered-cursor-mode")
     (volatile-highlights-mode nil "volatile-highlights")
     (aggressive-indent-mode nil "aggressive-indent")
     (all-the-icons-dired-mode nil "all-the-icons-dired")
     (icons-in-terminal-dired-mode nil "icons-in-terminal-dired")
     (yas-minor-mode nil "yasnippet")
     (auto-complete-mode nil "auto-complete")
     (company-mode nil "company")
     (ws-butler-mode nil "ws-butler")
     (isearch-mode nil "isearch")
     (auto-revert-mode nil "autorevert")
     (global-whitespace-mode nil "whitespace")
     (emmet-mode nil "emmet-mode")
     (abbrev-mode nil "abbrev")
     (doxymacs-mode nil "doxymacs")
     (editorconfig-mode nil "editorconfig")
     (rainbow-mode nil "rainbow-mode")
     (highlight-symbol-mode nil "highlight-symbol")
     (which-key-mode nil "which-key")
     (fancy-narrow-mode nil "fancy-narrow")
     (smartparens-mode nil "smartparens")
     (projectile-mode nil "projectile")
     (selected-minor-mode nil "selected")
     (skewer-html-mode nil "skewer-html")
     (org-extra-emphasis-intraword-emphasis-mode nil "org-extra-emphasis")
     (gcmh-mode nil "gcmh")
     (super-save-mode nil "super-save")
     (rainbow-csv-mode nil "rainbow-csv")))

  ;; Override by icon
  (when (require 'icons-in-terminal nil t)
    (delight
     `((view-mode ,(concat " " (icons-in-terminal-faicon "lock")) "view")))))

6.25. [migemo.el] ローマ字入力で日本語を検索する

以下は,cmigemo を使う設定です.正直なところ,それほどメリットを感じていません.

;;;###autoload
(defun my-migemo-activate ()
  (when (and (executable-find "cmigemo")
                   (require 'migemo nil t))
    (add-hook 'isearch-mode-hook #'migemo-init)
    (migemo-init))
  (remove-hook 'isearch-mode-hook #'my-migemo-activate))
;; late-init.el
(when (autoload-if-found '(migemo-init)
                         "migemo" nil t)
  ;; Tricky!
  (add-hook 'isearch-mode-hook #'my-migemo-activate)
  (with-eval-after-load "migemo"
    (custom-set-variables
     '(completion-ignore-case t) ;; case-independent
     '(migemo-command "cmigemo")
     '(migemo-options '("-q" "--emacs" "-i" "\a"))
     '(migemo-dictionary "/usr/local/share/migemo/utf-8/migemo-dict")
     '(migemo-user-dictionary nil)
     '(migemo-regex-dictionary nil)
     '(migemo-use-pattern-alist t)
     '(migemo-use-frequent-pattern-alist t)
     '(migemo-pattern-alist-length 1024)
     '(migemo-coding-system 'utf-8-unix))))

6.26. [git-gutter-fringe] 編集差分をフレーム端で視覚化

編集差分の視覚化は,元々 git-gutter が提供している機能です.しかし有効にするとフレームの幅が若干広がってしまうので,気になる人は git-gutter-fringe を使えばよいです.

(when (require 'git-gutter nil t)
  (dolist (hook
           '(emacs-lisp-mode-hook
             lisp-mode-hook perl-mode-hook python-mode-hook
             c-mode-common-hook nxml-mode-hook web-mode-hook))
    (add-hook hook #'git-gutter-mode))
  (custom-set-variables
   '(git-gutter:lighter "")))

(when (require 'git-gutter-fringe nil t)
  (custom-set-variables
   '(git-gutter-fr:side 'left-fringe)))

;;;###autoload
(defun my-fringe-helper ()
  (eval '(fringe-helper-define 'git-gutter-fr:modified nil
    "...XX..."
    "...XX..."
    "...XX..."
    "...XX..."
    "...XX..."
    "........"
    "...XX..."
    "...XX..."))
  (set-face-foreground 'git-gutter-fr:added    "#FF2600")
  (set-face-foreground 'git-gutter-fr:modified "orange")
  (set-face-foreground 'git-gutter-fr:deleted  "medium sea green"))

;;(add-hook 'git-gutter-mode-hook #'my-fringe-helper)
;; (eval-when-compile
;;   (message "Loading fringe-helper...")
;;   (require 'fringe-helper))

(when (autoload-if-found '(git-gutter-mode)
                         "git-gutter" nil t)
  (dolist (hook
           '(emacs-lisp-mode-hook
             lisp-mode-hook perl-mode-hook python-mode-hook
             c-mode-common-hook nxml-mode-hook web-mode-hook))
    (add-hook hook #'git-gutter-mode))

  (with-eval-after-load "git-gutter"
    (custom-set-variables
     '(git-gutter:lighter ""))

    (when (require 'git-gutter-fringe nil t)
      (custom-set-variables
       '(git-gutter-fr:side 'left-fringe))

      ;; (require 'fringe-helper nil t) ;; byte-compile 時に明示的に指定が必要.
      ;; "!"
      (eval '(fringe-helper-define 'git-gutter-fr:modified nil
               "...XX..."
               "...XX..."
               "...XX..."
               "...XX..."
               "...XX..."
               "........"
               "...XX..."
               "...XX..."))
      ;; "+"
      (eval '(fringe-helper-define 'git-gutter-fr:added nil
               "........"
               "...XX..."
               "...XX..."
               ".XXXXXX."
               ".XXXXXX."
               "...XX..."
               "...XX..."
               "........"))
      ;; "-"
      (eval '(fringe-helper-define 'git-gutter-fr:deleted nil
               "........"
               "........"
               "........"
               ".XXXXXX."
               ".XXXXXX."
               "........"
               "........"
               "........"))
      (set-face-foreground 'git-gutter-fr:added    "#FF2600")
      (set-face-foreground 'git-gutter-fr:modified "orange")
      (set-face-foreground 'git-gutter-fr:deleted  "medium sea green"))))
;; late-init.el
(when (autoload-if-found '(git-gutter-mode)
                         "git-gutter" nil t)
  (dolist (hook
           '(emacs-lisp-mode-hook
             lisp-mode-hook perl-mode-hook python-mode-hook
             c-mode-common-hook nxml-mode-hook web-mode-hook))
    (add-hook hook #'git-gutter-mode))

  (with-eval-after-load "git-gutter"
    (custom-set-variables
     '(git-gutter:lighter ""))

    (when (require 'git-gutter-fringe nil t)
      (custom-set-variables
       '(git-gutter-fr:side 'left-fringe))

      (require 'fringe-helper nil t) ;; byte-compile 時に明示的に指定が必要.
      ;; "!"
      (fringe-helper-define 'git-gutter-fr:modified nil
        "...XX..."
        "...XX..."
        "...XX..."
        "...XX..."
        "...XX..."
        "........"
        "...XX..."
        "...XX...")
      ;; "+"
      (fringe-helper-define 'git-gutter-fr:added nil
        "........"
        "...XX..."
        "...XX..."
        ".XXXXXX."
        ".XXXXXX."
        "...XX..."
        "...XX..."
        "........")
      ;; "-"
      (fringe-helper-define 'git-gutter-fr:deleted nil
        "........"
        "........"
        "........"
        ".XXXXXX."
        ".XXXXXX."
        "........"
        "........"
        "........")
      (set-face-foreground 'git-gutter-fr:added    "#FF2600")
      (set-face-foreground 'git-gutter-fr:modified "orange")
      (set-face-foreground 'git-gutter-fr:deleted  "medium sea green"))))

6.27. [japanese-holidays] カレンダーをカラフルにする

ビルドインの holidays と, japanese-holidays を使って,土日祝日に配色します.土曜日と日曜祝日で異なる配色にできます.

(keymap-global-set "M-c" #'calendar)
(with-eval-after-load "calendar"
  (when (require 'japanese-holidays nil t)
    (setq calendar-holidays
          (append japanese-holidays
                  holiday-local-holidays holiday-other-holidays))
    (setq calendar-mark-holidays-flag t)
    (setq mark-holidays-in-calendar t)
    ;; (setq japanese-holiday-weekend-marker
    ;;       '(holiday nil nil nil nil nil japanese-holiday-saturday))
    ;; (setq japanese-holiday-weekend '(0 6))
    (add-hook 'calendar-today-visible-hook #'japanese-holiday-mark-weekend)
    (add-hook 'calendar-today-invisible-hook #'japanese-holiday-mark-weekend))

  (add-hook 'calendar-today-visible-hook #'calendar-mark-today)
  ;; hl-line を有効化
  ;; (add-hook 'calendar-today-visible-hook #'my-hl-line-enable)
  ;; (add-hook 'calendar-today-invisible-hook #'my-hl-line-enable)
)

6.28. [calendar.el] カレンダーで週番号を表示する

ビルドインの calendar.el にある calendar-intermonth-text をカスタマイズすると,カレンダーに週番号を表示させることが可能です.

ただ, calendar.el に記載されている例だと, calendar-week-start-day1 以外の時に計算結果がおかしくなるので,次のように calendar-absolute-from-gregorian に渡す値を補正する必要があります.

↑要再検討(以前は, calendar-week-start-day が2の場合でうまく行っていたが,現在は6を用いており,翌月の第一週の週番号が正しく表示されない状態)

(autoload 'calendar-iso-from-absolute "cal-iso" nil t)
(autoload 'calendar-absolute-from-gregorian "calendar" nil t)

;;;###autoload
(defun my-get-week-number ()
  "Return the current week number."
  (format "%02d"
          (car
           (calendar-iso-from-absolute
            (calendar-absolute-from-gregorian
             (list (string-to-number (format-time-string "%m"))
                   (string-to-number (format-time-string "%d"))
                   (string-to-number (format-time-string "%y"))))))))

;;;###autoload
(defun my-week-number ()
  "Show the current week number."
  (interactive)
  (message "w%s" (my-get-week-number)))

;;;###autoload
(defun my-empty-booting-header-line ()
  (with-current-buffer "*scratch*"
    (let ((week (format "W%s: " (my-get-week-number)))
          (date (format-time-string "%Y-%m-%d %a.")))
      (setq header-line-format
            (concat
             " No day is a good day.                                       "
             week
             date
             (propertize " "
                         'display
                         `(space . (:align-to
                                    ,(- (frame-width)
                                        (length week)
                                        (length date))))))))))
(with-eval-after-load "calendar"
  (setq calendar-week-start-day 1)
  (copy-face 'default 'calendar-iso-week-header-face)
  (set-face-attribute 'calendar-iso-week-header-face nil
                      :height 1.0 :foreground "#1010FF"
                      :background (face-background 'default))
  (setq calendar-intermonth-header
        (propertize " w"
                    'font-lock-face 'calendar-iso-week-header-face))

  (copy-face font-lock-constant-face 'calendar-iso-week-face)
  (set-face-attribute 'calendar-iso-week-face nil
                      :height 1.0 :foreground "orange"
                      :background (face-background 'default))

  (setq calendar-intermonth-text
        '(propertize
          (format "%02d"
                  (car
                   (calendar-iso-from-absolute
                    (+ (calendar-absolute-from-gregorian
                        (list month day year))
                       calendar-week-start-day
                       ))))
          'font-lock-face 'calendar-iso-week-face)))

6.29. [calendar.el] カレンダーでカーソル下を強調表示

org-mode の org-eval-in-calendar を使って,カーソル下の日付を強調表示します.

(autoload 'org-eval-in-calendar "org" nil t)
;;;###autoload
(defun my-calendar-mark-selected ()
  (org-eval-in-calendar '(setq cursor-type nil) t))
(with-eval-after-load "calendar"
  (add-hook 'calendar-today-visible-hook #'my-calendar-mark-selected)
  (add-hook 'calendar-move-hook #'my-calendar-mark-selected))

6.30. [which-key] キーバインドの選択肢をポップアップする

guide-key.el の後発. guide-key.el の改良でもあり,ディスパッチャが見やすく,直感的でとても使いやすい.

(when (autoload-if-found '(which-key-mode)
                         "which-key" nil t)
  (with-eval-after-load "which-key"
    (custom-set-variables
     '(which-key-idle-delay 1.0)))

  (unless noninteractive
    (which-key-mode 1)))

6.31. [highlight-symbol] 同じ名前のシンボルをハイライトする

一定時間が過ぎると,カーソル下のワードをバッファ内で検索し,ハイライトしてくれる.殺風景なバッファに動きが出て良い.また, highlight-symbol-nav-mode を使うと,シンボル間を M-n/M-p で移動できるので,毎度検索しなくてよい.

(when (autoload-if-found '(highlight-symbol-mode highlight-symbol-nav-mode)
                         "highlight-symbol" nil t)
  (dolist (hook '(emacs-lisp-mode-hook c-mode-common-hook prog-mode-hook))
    (add-hook hook #'highlight-symbol-mode))
  (with-eval-after-load "highlight-symbol"
    (custom-set-variables
     '(highlight-symbol-idle-delay 0.5))))

6.32. [all-the-icons.el] フォントでアイコン表示

all-the-icons.el を使うと,バッファ内やモードライン,ミニバッファでアイコンを表示できるようになります.

domtronn/all-the-icons.el: A utility package to collect various Icon Fonts and propertize them within Emacs.

パッケージを使えるようにした後, M-x all-the-icons-install-fonts すると自動的にフォントがインストールされます.必要に応じて fc-cache -f -v を発行すればフォントが使えるようになります.

期待と異なるフォントが表示されたり,コピペするとフォントが入れ替わってしまう場合は, set-fontset-fontunicode を設定すると解決するかもしれません.

6.33. [all-the-icons-dired] dired でアイコンを表示

dired で,ファイルのアイコンを表示します. all-the-icons をインストール後に, M-x all-the-icons-install-fonts を忘れずに実行する必要があります. neotree の設定は別セクションに記載しています.

M-x all-the-icons-install-fonts をしてもうまくアイコンが表示されない場合は, ~/Library/Fonts/ 以下にある all-the-icons.ttf のサイズを確認してください.もしゼロバイトならインストールに失敗しています.その場合は,パッケージの all-the-icons/fonts 以下にあるフォントを直接インストールすればOKです.

font-lock-mode が無効の場合に,意図しないアイコンが表示されることがあります.

(when (autoload-if-found '(all-the-icons-dired-mode
                           ad:all-the-icons-dired--display)
       "all-the-icons-dired" nil t)
  (with-eval-after-load "all-the-icons"
    (setq all-the-icons-scale-factor 1.0)
    (add-to-list 'all-the-icons-dir-icon-alist
                 '("google[ _-]drive" all-the-icons-alltheicon "google-drive"
                   :height 1.0 :v-adjust -0.1))))
(when (autoload-if-found '(icons-in-terminal-dired-mode)
                         "icons-in-terminal-dired" nil t)
  (with-eval-after-load "icons-in-terminal"
    (setq icons-in-terminal-scale-factor 1.0)))

(cond ((require 'icons-in-terminal nil t)
       (add-hook 'dired-mode-hook #'icons-in-terminal-dired-mode))
      ((require 'all-the-icons nil t)
       (add-hook 'dired-mode-hook #'all-the-icons-dired-mode)))

6.34. [eldoc.el] コンテクストに応じてヘルプを表示

;;;###autoload
(defun my:elisp-eldoc (_callback)
  "Avoid hiding `hl-line' in `emacs-lisp-mode'."
  (when (fboundp 'hl-line-highlight)
    (hl-line-highlight)))

;;;###autoload
(defun ad:eldoc-message (f &optional string)
  (unless (active-minibuffer-window)
    (funcall f string)))
(when (autoload-if-found '(turn-on-eldoc-mode)
                         "eldoc" nil t)
  (dolist (hook '(emacs-lisp-mode-hook org-mode-hook c-mode-common-hook))
    (add-hook hook #'turn-on-eldoc-mode))
  (with-eval-after-load "eldoc"
    (advice-add 'elisp-eldoc-funcall :after #'my:elisp-eldoc)
    ;; (advice-add 'elisp-eldoc-var-docstring :after #'my:elisp-eldoc)

    ;; for ivy-mode
    (advice-add 'eldoc-message :around #'ad:eldoc-message)

    (custom-set-variables
     '(eldoc-idle-delay 1.0))))

6.35. [go-eldoc.el] Go用の eldoc

go get -v github.com/mdempsky/gocode
(with-eval-after-load "go-mode"
  (if (executable-find "gocode")
      (when (autoload-if-found '(go-mode)
                               "go-eldoc" nil t)
        (add-hook 'go-mode-hook #'go-eldoc-setup))
    (message "--- gocode is NOT installed.")))

6.36. [keycast.el] 入力しているキーとコマンドをモードラインに表示

;; late-init.el
(autoload-if-found '(keycast-mode) "keycast" nil t)

6.37. [keypression.el] 入力しているキーとコマンドを任意の場所に表示

  • keycast.el は表示がモードラインに限定されますが, keypression.el は任意の場所に入力状況を表示できます.一般的なキーキャストアプリとほぼ同じ振る舞いになります.
(when (autoload-if-found '(keypression-mode)
                         "keypression" nil t)
  (with-eval-after-load "keypression"
    (setq keypression-use-child-frame t)
    (setq keypression-frames-maxnum 3)
    (setq keypression-fade-out-delay 1.5)
    (setq keypression-font "Monaco")
    (setq keypression-font-face-attribute
          '(:width normal :height 200 :weight bold))
    ;; (progn
    ;;   (setq keypression-frame-origin 'keypression-origin-top-left)
    ;;   (setq keypression-x-offset -10)
    ;;   (setq keypression-y-offset +10))
    (progn
      (setq keypression-x-offset +8)
      (setq keypression-y-offset +16))
    (add-hook 'keypression-mode-hook #'dimmer-permanent-off)
    ;; (keypression-mode 1) ;; To start, M-x keypression-mode
    ))

6.38. [ivy.el] 続々・何でも絞り込みインターフェイス

helm から ivy に乗り換えました.移行話は Qiita で(長編)記事にしました.

(when (autoload-if-found '(ivy-hydra-read-action)
                         "ivy-hydra" nil t)

  ;; ivy-dispatching-done-hydra was depreciated in ivy 0.13
  ;; (with-eval-after-load "ivy-hydra"
  ;;   (defun ad:ivy-dispatching-done-hydra (f)
  ;;     (when (> ivy--length 0)
  ;;       (funcall f)))
  ;;   (advice-add 'ivy-dispatching-done-hydra
  ;;               :around #'ad:ivy-dispatching-done-hydra))
  )

;;;###autoload
(defun my-seq-sort-by (function pred sequence)
  "Sort SEQUENCE using PRED as a comparison function.
Elements of SEQUENCE are transformed by FUNCTION before being
sorted.  FUNCTION must be a function of one argument."
  (seq-sort (lambda (a b)
              (funcall pred
                       (funcall function a)
                       (funcall function b)))
            sequence))

;;;###autoload
(defun ivy--sort-by-len (name candidates)
  "Sort CANDIDATES based on similarity of their length with NAME."
  (let ((name-len (length name))
        (candidates-count (length candidates)))
    (if (< 500 candidates-count)
        candidates
      (seq-sort-by #'length
                   (lambda (a b)
                     (< (abs (- name-len a))
                        (abs (- name-len b))))
                   candidates))))

;;;###autoload
(defun my-disable-counsel-find-file (&rest args)
  "Disable `counsel-find-file' and use the original `find-file' with ARGS."
  (let ((completing-read-function #'completing-read-default)
              (completion-in-region-function #'completion--in-region))
    (apply #'read-file-name-default args)))

;; Common actions for counsel-ag, counsel-fzf, and counsel-recentf

;;;###autoload
(defun my-counsel-fzf-in-default-dir (_arg)
  "Search the current directory with fzf."
  (counsel-fzf ivy-text default-directory))

;;;###autoload
(defun my-counsel-fzf-in-dir (_arg)
  "Search again with new root directory."
  (counsel-fzf ivy-text
               (read-directory-name
                (concat (car (split-string counsel-fzf-cmd))
                        " in directory: "))))

;;;###autoload
(defun my-counsel-ag-in-dir (_arg)
  "Search again with new root directory."
  (let ((current-prefix-arg '(4)))
    (counsel-ag ivy-text nil ""))) ;; also disable extra-ag-args

;;;###autoload
(defun my-counsel-mark-ring ()
  "Browse `mark-ring' interactively.
Obeys `widen-automatically', which see."
  (interactive)
  (let* ((counsel--mark-ring-calling-point (point))
         (marks (copy-sequence mark-ring))
         (marks (delete-dups marks))
         (marks
          ;; mark-marker is empty?
          (if (equal (mark-marker) (make-marker))
              marks
            (cons (copy-marker (mark-marker)) marks)))
         (candidates (counsel-mark--get-candidates marks)))
    (delete-dups candidates) ;; [added] remove duplicated lines
    (if candidates
        (counsel-mark--ivy-read "Mark: " candidates 'counsel-mark-ring)
      (message "Mark ring is empty"))
    counsel--mark-ring-calling-point)) ;; To avoid an warning on lexical val.
(when (autoload-if-found '(counsel-ibuffer counsel-M-x counsel-yank-pop)
                         "counsel" nil t)

  (keymap-global-set "M-x" 'counsel-M-x)
  (keymap-global-set "M-y" 'counsel-yank-pop)
  (keymap-global-set "C-," 'counsel-mark-ring)
  (keymap-global-set "C-x C-b" 'counsel-ibuffer)
  (keymap-global-set "C-M-g" 'ivy-resume)

  (unless (fboundp 'seq-sort-by) ;; emacs25
    (defalias 'seq-sort-by 'my-seq-sort-by))

  (with-eval-after-load "flyspell"
    (keymap-set flyspell-mode-map "C-," 'counsel-mark-ring))

  (with-eval-after-load "org"
    (keymap-set org-mode-map "C-," 'counsel-mark-ring))

  (with-eval-after-load "ivy"
    ;; 同一行に複数の mark がある場合,一つだけを候補として表示する.
    ;; mark を正確に辿れなくなるが,当該行に移動できることを重視.
    (advice-add 'counsel-mark-ring :override #'my-counsel-mark-ring)

    ;; counsel-mark-ring のリストをソートさせない
    (setf (alist-get 'counsel-mark-ring ivy-sort-functions-alist) nil)

    ;; M-o を ivy-dispatching-done-hydra に割り当てる.
    ;; (keymap-set ivy-minibuffer-map "M-o" 'ivy-dispatching-done-hydra)
    ;; ivy-dispatching-done を使う.
    ;; (keymap-set ivy-minibuffer-map "M-o" 'ivy-dispatching-done)
    (setq ivy-read-action-function #'ivy-hydra-read-action)

    (setq ivy-use-virtual-buffers nil)
    (when (setq enable-recursive-minibuffers t)
      (minibuffer-depth-indicate-mode 1))
    (keymap-set ivy-minibuffer-map "<escape>" 'minibuffer-keyboard-quit)
    (setq ivy-count-format "%d.%d ")
    ;; (setq ivy-truncate-lines nil) ;; 選択候補も折り返されてしまう.
    ;; (setq ivy-wrap t)
    (ivy-mode 1))

  (with-eval-after-load "counsel"
    ;; counsel-M-x, see also prescient.el section
    (setq ivy-initial-inputs-alist
          '((org-agenda-refile . "^")
            (org-capture-refile . "^")
            (counsel-describe-function . "^")
            (counsel-describe-variable . "^")
            (Man-completion-table . "^")
            (woman . "^")))

    (when (require 'smex nil t)
      (setq smex-history-length 35)
      (setq smex-completion-method 'ivy))

    ;;  https://github.com/abo-abo/swiper/issues/1294
    (setf (alist-get 'counsel-M-x ivy-sort-matches-functions-alist)
          #'ivy--sort-by-len)

    ;; Disable counsel-find-file
    ;; https://emacs.stackexchange.com/questions/45929/disable-ivy-for-find-file
    (setq read-file-name-function #'my-disable-counsel-find-file)
    ;; (define-key counsel-mode-map [remap find-file]  nil) ;; TODO
    ;; (keymap-substitute counsel-mode-map 'find-file nil) ;; FIXME

    ;; オリジナルを非インタラクティブ化(上書きで可.advice不可)
    (when (require 'find-func nil t)
      (defun find-library (library)
        "Override the original `find-library' to hide in command list."
        (prog1
            (switch-to-buffer (find-file-noselect (find-library-name library)))
          (run-hooks 'find-function-after-hook))))))

6.39. [ivy.el] プロンプトをカスタマイズ

ivy-pre-prompt-function に文字列を返す任意の関数を与えると,プロンプトの前を修飾できます.好みの情報を出力しましょう.

;;;###autoload
(defun my-pre-prompt-function ()
  (cond (window-system
         (format "%s%s "
                 (if my-toggle-modeline-global "" ;; FIXME
                   (concat (make-string (frame-width) ?\x5F) "\n")) ;; "__"
                 (cond ((require 'icons-in-terminal nil t)
                        (icons-in-terminal-material "playlist_add_check"))
                       ((require 'all-the-icons nil t)
                        (all-the-icons-material "playlist_add_check"))
                       (t ""))))
        ;; ((eq system-type 'windows-nt)
        ;;  (format "%s%s "
        ;;          (if my-toggle-modeline-global "" ;; FIXME
        ;;            (concat (make-string (frame-width) ?\x5F) "\n")) ;; "__"
        ;;          ">>"))
        (t
         (format "%s\n" (make-string (1- (frame-width)) ?\x2D)))))
;; プロンプトをカスタマイズ
(with-eval-after-load "ivy"
  (setq ivy-pre-prompt-function #'my-pre-prompt-function))

6.40. [imenu-list.el] サイドバー的にファイル内容の目次要素を表示

↑のリポジトリは,オリジナルのコードに対して次の修正を加えています.

  1. トップレベルのツリーで ENT する時,カーソルが imenu-list 側に残さない
  2. 最初の試行で imenu-list のバッファを生成できない時に以後のフローを正しく動かす
;;;###autoload
(defun my-truncate-lines-activate ()
  "Truncate lines on `imenu-list' buffer."
  (toggle-truncate-lines 1))

;;;###autoload
(defun my-imenu-list-update ()
  "Expand frame width by `moom-change-frame-width'."
  (when (and (memq imenu-list-position '(right left))
             (not (get-buffer-window imenu-list-buffer-name t)))
    (moom-change-frame-width (+ (frame-width) imenu-list-size))))

;;;###autoload
(defun my-imenu-list-quit-window ()
  "Shrink frame width by `moom-change-frame-width'."
  (when (and (memq imenu-list-position '(right left))
             (not (get-buffer-window imenu-list-buffer-name t)))
    (moom-change-frame-width (- (frame-width) imenu-list-size))))
(when (autoload-if-found '(imenu-list)
                         "imenu-list" nil t)
  (with-eval-after-load "imenu-list"
    (setq imenu-list-size 40)
    (setq imenu-list-position 'left)

    (add-hook 'imenu-list-major-mode-hook #'my-truncate-lines-activate)

    (when (require 'moom nil t)
      (add-hook 'imenu-list-update-hook #'my-imenu-list-update)
      (advice-add 'imenu-list-quit-window :after #'my-imenu-list-quit-window))))

6.41. [prescient.el] リスト項目の並び替えとイニシャル入力機能(ivy and company)

コマンド履歴を保存.コマンドのイニシャル入力を可能にする.

(with-eval-after-load "prescient"
  (setq prescient-aggressive-file-save t) ;; Merged!
  (setq prescient-save-file
        (expand-file-name "~/.emacs.d/prescient-save.el"))
  (prescient-persist-mode 1))

(with-eval-after-load "ivy"
  (when (and (require 'prescient nil t)
             (require 'ivy-prescient nil t))
    (setq ivy-prescient-retain-classic-highlighting t)
    ;; (dolist (command '(counsel-world-clock ;; Merged!
    ;;                    counsel-app))
    ;;   (add-to-list 'ivy-prescient-sort-commands command t))
    (ivy-prescient-mode 1)
    (setf (alist-get 'counsel-M-x ivy-re-builders-alist)
          #'ivy-prescient-re-builder)
    (setf (alist-get t ivy-re-builders-alist) #'ivy--regex-ignore-order)))

(with-eval-after-load "company"
  (when (and (require 'prescient nil t)
             (require 'company-prescient nil t))
    (company-prescient-mode 1)))

6.42. [command-log-mode.el] 発行したコマンドの履歴をバッファに表示する

入力したコマンドの履歴をバッファに表示してくれます.Emacsのバッファを表示しながらお話する時に便利です.

プレゼンテーションという点で,さらに,キーキャストを実現する keypression.el と, Org Mode のプレゼンツールである org-tree-slide.el と組み合わせることで,視覚的なサポートが完成します.

command-log-mode.el の出力は,フレームの横幅を拡張してできる空間に表示するようにしています.フレームサイズの制御は moom.el で簡単にできます.

;;;###autoload
(defun my-command-log-mode-activate ()
  (interactive)
  (keypression-mode 1)
  (global-command-log-mode 1)
  (when (require 'moom nil t)
    (moom-delete-windows)
    (moom-change-frame-width 140)
    (moom--stay-in-region)
    (clm/open-command-log-buffer)))

;;;###autoload
(defun my-command-log-mode-deactivate ()
  (interactive)
  (keypression-mode -1)
  (global-command-log-mode -1)
  (when (require 'moom nil t)
    (moom-delete-windows)))
(when (autoload-if-found '(command-log-mode global-command-log-mode)
                         "command-log-mode" nil t)
  (with-eval-after-load "command-log-mode"
    (require 'keypression)
    (require 'moom)
    ;; (setq command-log-mode-window-font-size 0)
    (setq command-log-mode-key-binding-open-log nil)
    (setq command-log-mode-window-size 60)))

6.43. TODO [emacs-tree-sitter.el] tree-sitter でシンタックスハイライトする

;;;###autoload
(defun my-enable-tree-sitter ()
  (unless (featurep 'tree-sitter)
    (require 'tree-sitter)
    (require 'tree-sitter-hl)
    (require 'tree-sitter-debug)
    (require 'tree-sitter-query)
    (require 'tree-sitter-langs))
  (tree-sitter-hl-mode))
(let* ((elp (expand-file-name
                   (concat "~/.emacs.d/" (format "%s" emacs-version) "/el-get/")))
       (ets (concat elp "emacs-tree-sitter/"))
       (tsl (concat elp "tree-sitter-langs/")))
  ;; (add-to-list 'load-path (concat ets "langs"))
  (add-to-list 'load-path (concat ets "core"))
  (add-to-list 'load-path (concat ets "lisp"))
  (add-to-list 'load-path tsl))

(dolist (hook '(js-mode-hook))
  (add-hook hook #'my-enable-tree-sitter))

6.44. TODO [swiper.el] 文字列探索とプレビューを同時に行う

;;;###autoload
(defun ad:swiper-thing-at-point ()
  "`swiper' with `ivy-thing-at-point'."
  (interactive)
  (let ((thing (if (thing-at-point-looking-at "^\\*+") ;; org heading を除外
                   nil
                 (ivy-thing-at-point))))
    (when (use-region-p)
      (deactivate-mark))
    (swiper thing)))
(when (autoload-if-found '(swiper-thing-at-point swiper-all-thing-at-point)
                         "swiper" nil t)
  (keymap-global-set "M-s M-s" 'swiper-thing-at-point)
  (keymap-global-set "M-s M-a" 'swiper-all-thing-at-point)
  (with-eval-after-load "swiper"
    (advice-add 'swiper-thing-at-point :override #'ad:swiper-thing-at-point)))

6.45. TODO [all-the-icons-ivy] ivy インターフェイスでアイコンを表示する

(when (eq system-type 'darwin)
  (with-eval-after-load "ivy"
    (cond ((and (require 'icons-in-terminal nil t) ;; safeguard
                (require 'icons-in-terminal-ivy nil t))
           (dolist (command '(counsel-projectile-switch-project
                              counsel-ibuffer))
             (add-to-list 'icons-in-terminal-ivy-buffer-commands command))
           (icons-in-terminal-ivy-setup))
          ((and (require 'all-the-icons nil t) ;; safeguard
                (require 'all-the-icons-ivy nil t))
           (dolist (command '(counsel-projectile-switch-project
                              counsel-ibuffer))
             (add-to-list 'all-the-icons-ivy-buffer-commands command))
           (all-the-icons-ivy-setup)))))

6.46. TODO [rainbow-delimiters.el] 対応するカッコに色を付ける

複数のカッコが重なる言語では,カッコの対応関係がひと目で理解し難い場合があります. rainbow-delimiters を使うと,対応するカッコを七色に色付けして見やすくできます.デフォルトだと色がパステル調で薄いので,パラメータを追加して調整します.

org-block 内でうまく動かないようなので,本格導入は様子見中です.

(with-eval-after-load "rainbow-delimiters"
  ;; https://yoo2080.wordpress.com/2013/12/21/small-rainbow-delimiters-tutorial/
  (require 'cl-lib)
  (require 'color)
  (cl-loop
   for index from 1 to rainbow-delimiters-max-face-count
   do
   (let ((face (intern (format "rainbow-delimiters-depth-%d-face" index))))
     (cl-callf color-saturate-name (face-foreground face) 50))))

(add-hook 'prog-mode-hook
          (lambda ()
              (unless (equal (buffer-name) "*scratch*")
                (rainbow-delimiters-mode))))

6.47. TODO [yascroll.el] フリンジにスクロールバーを出す

yascroll を使います.

(when (require 'yascroll nil t)
  (setq yascroll:delay-to-hide 2)
  (setq yascroll:disabled-modes '(org-mode))
  (set-face-foreground 'yascroll:thumb-fringe "#b2cefb")
  (set-face-background 'yascroll:thumb-fringe "#b2cefb")
  (unless noninteractive
    (global-yascroll-bar-mode 1)))

6.48. TODO [dimmer.el] 現在のバッファ以外の輝度を落とす

;;;###autoload
(defun my-toggle-dimmer ()
  (interactive)
  (if (setq my-dimmer-mode (not my-dimmer-mode))
                  (dimmer-on) (dimmer-off)))

;;;###autoload
(defun dimmer-permanent-off ()
  (setq my-dimmer-mode nil)
  (dimmer-off))

;;;###autoload
(defun dimmer-off ()
  (dimmer-process-all)
  (dimmer-mode -1))

;;;###autoload
(defun dimmer-on ()
  (when my-dimmer-mode
          (dimmer-mode 1)
          (dimmer-process-all)))

;;;###autoload
(defun my-dimmer-update ()
        (if (frame-focus-state) (dimmer-on) (dimmer-off)))

;;;###autoload
(defun ad:dimmer-org-agenda--quit (&optional _bury)
  (when (fboundp 'dimmer-on)
          (setq my-dimmer-mode t)
          (dimmer-on)
          (redraw-frame)))

;;;###autoload
(defun my-dimmer-activate ()
  (setq my-dimmer-mode (dimmer-mode 1))
  (remove-hook 'window-configuration-change-hook #'my-dimmer-activate));; FIXME
(when (autoload-if-found '(dimmer-mode
                           dimmer-process-all dimmer-off dimmer-on
                           my-toggle-dimmer dimmer-permanent-off
                           ad:dimmer-org-agenda--quit)
                         "dimmer" nil t)
  (defvar my-dimmer-mode nil)
  (with-eval-after-load "dimmer"
    (custom-set-variables
     '(dimmer-exclusion-regexp
       "^\\*[Hh]elm\\|^ \\*Minibuf\\|^\\*scratch\\|^ \\*Neo\\|^ \\*Echo\\|^\\*Calendar\\|*Org\\|^ \\*LV*")
     '(dimmer-fraction 0.6))

    (if (version< emacs-version "27.1")
        (progn
          (add-hook 'focus-out-hook #'dimmer-off)
          (add-hook 'focus-in-hook #'dimmer-on))
      (add-function :before after-focus-change-function #'my-dimmer-update))

    ;; for org-agenda
    (add-hook 'org-agenda-mode-hook #'dimmer-permanent-off)
    (advice-add 'org-agenda--quit :after #'ad:dimmer-org-agenda--quit)

    ;; for swiper/helm-swoop
    (add-hook 'minibuffer-setup-hook #'dimmer-off)
    (add-hook 'minibuffer-exit-hook #'dimmer-on))

  (unless noninteractive
    (unless (version< "28.0" emacs-version)
      ;; FIXME
      (add-hook 'window-configuration-change-hook #'my-dimmer-activate))))

6.49. TODO [transient.el] コマンドディスパッチャを構築する

autoload-if-found を使ういつもの設定では正しく動作しないので, eval-when-compile を使用して require しています.

;; この場合は,interactive モードで init-eval.el にある記述をロードするはだめ.
;; (eval-when-compile
;;   (message "Loading transient...")
;;   (require 'transient))

(with-eval-after-load "transient"
  (transient-define-prefix my-org-bullet-and-checkbox ()
    "Commands to handle bullet and checkbox"
    [["Bullet"
      ("i" "insert" my-org-insert-bullet)
      ("d" "delete" my-org-delete-bullet)]
     ["Checkbox"
      ("[" "insert" my-org-insert-checkbox-into-bullet)
      ("]" "delete" my-org-delete-checkbox-from-bullet)
      ;;("a" "toggle checkbox" my-org-toggle-checkbox)
      ;;("h" "cycle" my-cycle-bullet-at-heading) ;; single line
      ]
     ["Bullet and Checkbox"
      ("I" "insert" my-org-insert-bullet-and-checkbox)
      ("D" "delete" my-org-delete-bullet-and-checkbox)]]))

6.50. TODO [rainbow-csv.el] CSVファイルをリッチな配色で表示する

https://github.com/emacs-vs/rainbow-csv

(when (autoload-if-found '(rainbow-csv-mode) "rainbow-csv" nil t)
  (add-hook 'csv-mode-hook 'rainbow-csv-mode))

6.51. DONE [hydra.el] コマンドディスパッチャを構築する   obsolete

ディスパッチャ内の docstring には, ?x? の形式で評価値を反映できる.

;; late-init.el
(when (autoload-if-found '(hydra-timestamp/body help/insert-datestamp)
                         "hydra" nil t)
  (keymap-global-set "C-c h t" #'hydra-timestamp/body)
  (keymap-global-set "C-c 0" #'help/insert-datestamp)
  (with-eval-after-load "hydra"
    (require 'org nil t)
    (keymap-global-set "C-c )" #'help/insert-currenttime)
    (custom-set-faces
     '(hydra-face-blue
       ((((background light))
         :foreground "orange red" :bold t)
        (((background dark))
         :foreground "orange" :bold t))))

    (defhydra hydra-timestamp (:color blue :hint none)
      "
   === Timestamp ===
0.  ?i? (_i_so 8601)    ?n? (_n_ow)    ?w? (_w_eek)    ?a? (week-d_a_y)
_1_.  ?t? (ISO 8601 including _t_imezone)
_2_.  ?r?    (Org Mode: _r_ight now)
_3_.  ?s?          (Org Mode: by _s_elect)                             _q_uit
"
      ("q" nil)
      ("i" help/insert-datestamp (format-time-string "%F"))
      ("n" help/insert-currenttime (format-time-string "%H:%M"))
      ("w" help/insert-week (format-time-string "%W"))
      ("a" help/insert-month-and-day (format-time-string "%m%d"))
      ("t" help/insert-timestamp (help/get-timestamp))
      ("r" help/org-time-stamp-with-seconds-now
       (format-time-string "<%F %a %H:%M>"))
      ("s" org-time-stamp (format-time-string "<%F %a>"))
      ("0" help/show-my-date)
      ("1" help/insert-timestamp)
      ("2" help/org-time-stamp-with-seconds-now)
      ("3" org-time-stamp))

    (defun help/show-my-date ()
      "Produces and show date and time in preferred format."
      (interactive)
      (message (format-time-string "%Y-%m-%d (%a.) W:%W @%H:%M"))
      (hydra-keyboard-quit))
    (defun help/insert-currenttime ()
      "Produces and inserts the current time."
      (interactive)
      (insert (format-time-string "%H:%M")))
    (defun help/insert-week ()
      "Produces and inserts the week number."
      (interactive)
      (insert (format-time-string "%W")))
    (defun help/insert-month-and-day ()
      "Inserts a month and day pair in 4-degits."
      (interactive)
      (insert (format-time-string "%m%d")))
    (defun help/insert-datestamp ()
      "Produces and inserts a partial ISO 8601 format timestamp."
      (interactive)
      (insert (format-time-string "%F")))
    (defun help/insert-timestamp ()
      "Inserts a full ISO 8601 format timestamp."
      (interactive)
      (insert (help/get-timestamp)))
    (defun help/org-time-stamp-with-seconds-now ()
      (interactive)
      (let ((current-prefix-arg '(16)))
        (call-interactively 'org-time-stamp)))
    (defun help/get-timestamp ()
      "Produces a full ISO 8601 format timestamp."
      (interactive)
      (let* ((timestamp-without-timezone (format-time-string "%Y-%m-%dT%T"))
             (timezone-name-in-numeric-form (format-time-string "%z"))
             (timezone-utf-offset
              (concat (substring timezone-name-in-numeric-form 0 3)
                      ":"
                      (substring timezone-name-in-numeric-form 3 5)))
             (timestamp (concat timestamp-without-timezone
                                timezone-utf-offset)))
        timestamp))))

6.52. DONE [helm.el] 続・何でも絞り込みインターフェイス   obsolete

ivyに移行しました.

M-shelm-swoop にあてていましたが, M-s を入り口にたくさんの検索系コマンドが割り振られているため M-s M-s に変えました.なお, C-M-r は本来,正規表現での逆検索(isearch-backward-regexp)に割り振らています.また, C-M-l は本来, reposition-window に割り当てられています.

以下の設定では, helm に関連するモジュールも多数含まれます. helm がキックされるタイミングで有効化させます.

  • helm-google
  • helm-swoop
  • helm-projectile
  • helm-bm
(when (autoload-if-found '(helm-google)
                         "helm-google" nil t)
  (with-eval-after-load "helm-google"
    (custom-set-variables
     '(helm-google-tld "co.jp"))))
(when (autoload-if-found '(helm-swoop)
                         "helm-swoop" nil t)
  (keymap-global-set "M-s M-s" 'helm-swoop)

  (with-eval-after-load "helm-swoop"
    (require 'helm-config nil t)
    ;; カーソルの単語が org の見出し(*の集まり)なら検索対象にしない.
    (setq helm-swoop-pre-input-function
          (lambda()
            (unless (thing-at-point-looking-at "^\\*+")
              (thing-at-point 'symbol))))
    ;; 配色設定
    (set-face-attribute
     'helm-swoop-target-line-face nil :background "#FFEDDC")
    (set-face-attribute
     'helm-swoop-target-word-face nil :background "#FF5443")))

(when (autoload-if-found '(helm-M-x
                           helm-buffers-list helm-recentf
                           helm-locate helm-descbinds
                           helm-occur helm-flycheck helm-bookmarks)
                         "helm-config" nil t)
  (keymap-global-set "C-x C-b" 'helm-buffers-list)
  (keymap-global-set "C-M-r" 'helm-recentf)
  (keymap-global-set "M-x" 'helm-M-x)

  (keymap-global-set "C-M-l" 'helm-locate)
  (keymap-global-set "C-c f b" 'helm-bookmarks)
  (keymap-global-set "C-c o" 'helm-occur)
  (keymap-global-set "C-h d" 'helm-descbinds)
'
  (with-eval-after-load "projectile"
    (when (require 'helm-projectile nil t)
      ;; M-x helm-projectile-find-file (C-c p f)
      (setq projectile-completion-system 'helm)
      ;; projectile.el のキーバインドをオーバーライド
      (helm-projectile-toggle 1)))

  (with-eval-after-load "helm-config"
    ;; (when (require 'helm-files nil t)
    ;;   (keymap-set helm-find-files-map
    ;;     "<tab>" 'helm-execute-persistent-action)
    ;;   (keymap-set helm-read-file-map
    ;;     "<tab>" 'helm-execute-persistent-action))

    (when (memq window-system '(mac ns))
      (setq helm-locate-command "mdfind -name %s %s"))

    ;; (require 'helm-css-scss nil t)
    ;; (require 'helm-emmet nil t)
    ;; (require 'helm-descbinds nil t)

    ;; Configure helm-completing-read-handlers-alist after `(helm-mode 1)'
    (when (require 'helm nil t)
      (helm-mode 1))

    ;; helm-find-files を呼ばせない
    ;; (add-to-list 'helm-completing-read-handlers-alist '(find-file . nil))
    ;; helm-mode-ag を呼ばせない
    (add-to-list 'helm-completing-read-handlers-alist '(ag . nil))
    ;; helm-mode-org-set-tags を呼ばせない
    (add-to-list 'helm-completing-read-handlers-alist '(org-set-tags . nil))

    (setq helm-display-source-at-screen-top nil)
    ;;         (setq helm-display-header-line nil)
    ;; helm-autoresize-mode を有効にしつつ 30% に固定
    (setq helm-autoresize-max-height 30)
    (setq helm-autoresize-min-height 30)
    (set-face-attribute 'helm-source-header nil
                        :height 1.0 :family "Verdana" :weight 'normal
                        :foreground "#666666" :background "#DADADA")
    (helm-autoresize-mode 1)))

6.53. DONE バッテリー情報をモードラインに表示する   obsolete

;; Show battery information on the mode line.
(display-battery-mode t)

6.54. DONE [mode-icons] 使用中のモード表示をアイコンで代替   obsolete

しばらく使ってみたが,統一感が失われるので使用停止中.

(when (require 'mode-icons nil t)
  ;; アイコンを保存しているディレクトリを指定
  (setq mode-icons--directory
        (expand-file-name "~/.emacs.d/.cask/package/icons"))
  (mode-icons-mode 1))

6.55. DONE [stock-ticker] 株価をモードラインに表示   obsolete

季節が過ぎ去りました.不使用です.

日経平均やダウ平均の状況をモードラインに表示します.表示が長くなる傾向があるので, stock-ticker--parse を再定義して,銘柄(3桁のみ)と変動率だけを表示しています.

起動時には不要なので,ウィンドウにフォーカスが移った時に開始して,さらに1分でモードラインから消えるようにしています.

色々と不安定になってきたので,最近は使っていません.

(when (autoload-if-found '(my-activate-stock-ticker stock-ticker-global-mode)
                         "stock-ticker" nil t)
  (with-eval-after-load "stock-ticker"
    (defun stock-ticker--parse (data)
      "Parse financial DATA into list of display strings."
      (let ((qs (assoc-default
                 'quote (assoc-default
                         'results (assoc-default 'query data)))))
        (mapcar
         (lambda (q)
           (let ((percent (assoc-default 'PercentChange q))
                 (name (assoc-default 'Name q))
                 (symbol (assoc-default 'Symbol q))
                 (change (assoc-default 'Change q)))
             (format " %s:%s"
                     (substring
                      (if (or
                           (string-match "=" symbol)
                           (string-match "\\^" symbol))
                          name symbol) 0 3)
                     (if percent percent ""))))
         qs)))
    (setq stock-ticker-display-interval 5)
    (setq stock-ticker-symbols '("^N225" "DOW"))
    (setq stock-ticker-update-interval 300)

    (defun my-activate-stock-ticker (&optional duration)
      "Activate stock-ticker within the given duration."
      (stock-ticker-global-mode 1)
      (unless (numberp duration)
        (setq duration 90))
      (run-with-timer duration nil 'stock-ticker-global-mode -1)))

  (add-hook 'focus-in-hook #'my-activate-stock-ticker))

6.56. DONE [zlc.el] find-file バッファを zsh ライクにする   obsolete

ファイル選択を zsh ライクに変更できます.しっくりこないので不使用.

(when (require 'zlc nil t)
  ;; http://d.hatena.ne.jp/mooz/20101003/p1
  (set-face-attribute 'zlc-selected-completion-face nil
                      :foreground "#000000" :background "#9DFFD2" :bold t)
  ;;    (setq zlc-select-completion-immediately t)
  (let ((map minibuffer-local-map))
    ;; like menu select
    (keymap-set map "C-n" 'zlc-select-next-vertical)
    (keymap-set map "C-p" 'zlc-select-previous-vertical)
    (keymap-set map "C-f" 'zlc-select-next)
    (keymap-set map "C-b" 'zlc-select-previous)
    ;; reset selection
    (keymap-set map "C-c" 'zlc-reset))

  (zlc-mode 1))

6.57. DONE [stripe-buffer.el] テーブルの色をストライプにする   obsolete

stripe-buffer.elを使います.重くツリーが多いOrgバッファだと激重になる可能性があります.

(when (autoload-if-found '(turn-on-stripe-table-mode)
                         "stripe-buffer" nil t)
  (add-hook 'org-mode-hook #'turn-on-stripe-table-mode))

6.58. DONE [guide-key] キーバインドの選択肢をポップアップする   obsolete

which-key に移行しました.

自分用の関数にキーバインドを付けたのはいいけど,覚えられない時に使っています.以下の例では, helm もしくは org が読み込まれた時についでに有効化し, C-c f を押して, 0.5秒経つと,その後ろに続くキーの一覧がポップします.すでに覚えたキーバインドならば,0.5秒以内に打てるでしょうから,ポップ表示無しで通常通りにコマンドが発行します.色分けも効くのでわかりやすいです.

(when (autoload-if-found '(guide-key-mode)
                         "guide-key" nil t)
  (with-eval-after-load "guide-key"
    (setq guide-key/guide-key-sequence '("C-c f" "C-c f c"))
    (setq guide-key/popup-window-position 'bottom)
    (setq guide-key/idle-delay 0.5)
    (setq guide-key/highlight-command-regexp
          '(("my-" . "red")
            ("takaxp:" . "blue"))))

  (with-eval-after-load "org"
    (guide-key-mode 1))

  (unless noninteractive
    (guide-key-mode 1)))

6.59. DONE [anything.el] 何でも絞り込みインターフェイス   obsolete

  • helm に移行しました.

http://svn.coderepos.org/share/lang/elisp/anything-c-moccur/trunk/anything-c-moccur.el http://d.hatena.ne.jp/IMAKADO/20080724/1216882563

(when (autoload-if-found '(anything-other-buffer
                           anything-complete anything-M-x
                           anything-c-moccur-occur-by-moccur)
                         "anything-startup" nil t)
  (with-eval-after-load "anything-startup"
    (require 'anything-c-moccur nil t)
    ;;  (setq moccur-split-word t)
    ;;  (setq anything-c-locate-options `("locate" "-w"))

    ;; M-x install-elisp-from-emacswiki recentf-ext.el
    ;; http://www.emacswiki.org/cgi-bin/wiki/download/recentf-ext.el
    ;;  (autoload-if-found 'recentf-ext "recentf-ext" nil t)
    ;;              (require 'recentf-ext nil t)

    (when (require 'migemo nil t)
      (setq moccur-use-migemo t))
    ;; M-x anything-grep-by-name
    (setq anything-grep-alist
          '(("Org-files" ("egrep -Hin %s *.org" "~/Dropbox/org/"))
            (".emacs.d" ("egrep -Hin %s *.el" "~/.emacs.d/"))
            ("ChangeLog" ("egrep -Hin %s ChangeLog" "~/"))))
    ;; ("Spotlight" ("mdfind %s -onlyin ~/Dropbox/Documents/Library/" ""))))

    (defun my-anything ()
      (interactive)
      (anything-other-buffer
       '(anything-c-source-recentf
         anything-c-source-file-name-history
         anything-c-source-buffers
         anything-c-source-emacs-commands
         anything-c-source-locate)
       " *my-anything*"))

    (defun my-anything-buffer ()
      (interactive)
      (anything-other-buffer
       '(anything-c-source-buffers)
       " *my-anthing-buffer*"))

    (when (memq window-system '(mac ns))
      (defun my-anything-spotlight ()
        "Spotlight search with anything.el"
        (interactive)
        (anything-other-buffer
         '(anything-c-source-mac-spotlight)
         " *anything-spotlight*")))

    (setq anything-candidate-number-limit 50) ; 50
    (setq anything-input-idle-delay 0.1)      ; 0.1
    (setq anything-idle-delay 0.5)            ; 0.5
    (setq anything-quick-update nil))        ; nil

  ;; Show ibuffer powered by anything
  ;;  (with-eval-after-load "anything-startup"
  (keymap-global-set "M-x" 'anything-M-x)
  (keymap-global-set "C-c o" 'anything-c-moccur-occur-by-moccur)
  (keymap-global-set "C-M-r" 'my-anything)
  (keymap-global-set "C-M-s" 'my-anything-spotlight)
  (keymap-global-set "C-x C-b" 'my-anything-buffer))

6.60. DONE [diminish.el] モードラインのモード名を短くする   obsolete

以前は自作したパッケージを使っていましたが,不具合も多く,調べると diminish.el という素晴らしいパッケージがあったので移行しました.これはマイナーモードの短縮表示なので,メジャーモードは個別にフックで mode-name を書き換えて対応します. use-package.el を使っていると依存関係から自動的にインストールされます.

diminish.el を使えば,短縮名に書き換えることも,存在自体を消してしまうこともできます.helm だけ行儀が悪いので,後段での設定時に diminish を呼ぶようにしています.代替パッケージに,rich-minority-modeがあります.

メジャーモードの短縮表示は diminish に頼らず,単純に各モードの hook で対処します.

(when (require 'diminish nil t)
  (with-eval-after-load "ggtags" (diminish 'ggtags-mode " G"))
  (with-eval-after-load "org" (diminish 'orgstruct-mode " OrgS"))
  (with-eval-after-load "centered-cursor-mode"
    (diminish 'centered-cursor-mode))
  (with-eval-after-load "volatile-highlights"
    (diminish 'volatile-highlights-mode))
  (with-eval-after-load "aggressive-indent"
    (diminish 'aggressive-indent-mode))
  (with-eval-after-load "all-the-icons-dired"
    (diminish 'all-the-icons-dired-mode))
  (with-eval-after-load "yasnippet" (diminish 'yas-minor-mode))
  (with-eval-after-load "auto-complete" (diminish 'auto-complete-mode))
  (with-eval-after-load "ws-butler" (diminish 'ws-butler-mode))
  (with-eval-after-load "isearch" (diminish 'isearch-mode))
  (with-eval-after-load "autorevert" (diminish 'auto-revert-mode))
  (with-eval-after-load "smooth-scroll" (diminish 'smooth-scroll-mode))
  (with-eval-after-load "whitespace" (diminish 'global-whitespace-mode))
  (with-eval-after-load "emmet-mode" (diminish 'emmet-mode))
  (with-eval-after-load "abbrev" (diminish 'abbrev-mode))
  (with-eval-after-load "doxymacs" (diminish 'doxymacs-mode))
  (with-eval-after-load "editorconfig" (diminish 'editorconfig-mode))
  (with-eval-after-load "rainbow-mode" (diminish 'rainbow-mode))
  (with-eval-after-load "guide-key" (diminish 'guide-key-mode))
  (with-eval-after-load "highlight-symbol" (diminish 'highlight-symbol-mode))
  (with-eval-after-load "which-key" (diminish 'which-key-mode))
  (with-eval-after-load "fancy-narrow" (diminish 'fancy-narrow-mode))
  (with-eval-after-load "smartparens" (diminish 'smartparens-mode))
  (with-eval-after-load "selected" (diminish 'selected-minor-mode))
  ;; (with-eval-after-load "org-autolist" (diminish 'org-autolist-mode))
   ;;;  (with-eval-after-load "helm" (diminish 'helm-mode " H"))
  )

;; メジャーモードの短縮
(add-hook 'c-mode-hook (lambda () (setq mode-name "C")))
(add-hook 'js2-mode-hook (lambda () (setq mode-name "JS")))
(add-hook 'c++-mode-hook (lambda () (setq mode-name "C++")))
(add-hook 'csharp-mode-hook (lambda () (setq mode-name "C#")))
(add-hook 'prog-mode-hook (lambda () (setq mode-name "Pr")))
(add-hook 'emacs-lisp-mode-hook (lambda () (setq mode-name "El")))
(add-hook 'python-mode-hook (lambda () (setq mode-name "Py")))
(add-hook 'perl-mode-hook (lambda () (setq mode-name "Pl")))
(add-hook 'web-mode-hook (lambda () (setq mode-name "W")))
(add-hook 'change-log-mode-hook (lambda () (setq mode-name "ChangeLog")))
(add-hook 'lisp-interaction-mode-hook (lambda () (setq mode-name "Lisp")))

7. メディアサポート

7.1. [emms.el] メディアプレーヤー

mpv を使い,emacs からメディアファイルを再生します. bongo.el よりも使いやすい印象.

(when (autoload-if-found '(emms-play-file
                           emms-play-playlist emms-play-directory my-play-bgm
                           emms-next emms-previous emms-stop emms-pause)
                         "emms" nil t)
  (keymap-global-set "C-c e b" 'my-play-bgm)
  (let ((base "C-c e "))
    (keymap-global-set (concat base "n") 'emms-next)
    (keymap-global-set (concat base "p") 'emms-previous)
    (keymap-global-set (concat base "s") 'emms-stop)
    (keymap-global-set (concat base "SPC") 'emms-pause))

  (with-eval-after-load "emms-mode-line"
    (defun ad:emms-mode-line-playlist-current ()
      "Display filename in mode-line, not full-path."
      (format emms-mode-line-format
              (file-name-nondirectory
               (emms-track-description
                (emms-playlist-current-selected-track)))))
    (advice-add 'emms-mode-line-playlist-current :override
                #'ad:emms-mode-line-playlist-current))

  (with-eval-after-load "emms-mode-line-icon"
    (setq emms-mode-line-format "%s")
    (defun ad:emms-mode-line-icon-function ()
      "Replace the default musical note icon with a Unicode character."
      (concat " "
              emms-mode-line-icon-before-format
              "♪"
              (emms-mode-line-playlist-current)))
    (advice-add 'emms-mode-line-icon-function :override
                #'ad:emms-mode-line-icon-function))

  (with-eval-after-load "emms"
    (when (require 'emms-setup nil t)
      (emms-standard)
      (emms-default-players))

    (unless noninteractive
      (require 'org-emms nil t)) ;; emms のリンクに対応させる
    ;; (require 'helm-emms nil t)

    (defun my-play-bgm ()
      (interactive)
      (let ((file "~/Dropbox/12-audio/ffximusic104.m4a"))
        (when (file-exists-p file)
          (emms-play-file file)
          (emms-toggle-repeat-track))))

    ;; setup an additional player
    (if (executable-find "mpv")
        (when (require 'emms-player-mpv nil t)
          (add-to-list 'emms-player-list 'emms-player-mpv)

          ;; (defvar emms-player-mpv-ontop nil)
          ;; (defun emms-player-mpv-toggle-ontop ()
          ;;   "Toggle float on top."
          ;;   (interactive)
          ;;   (if emms-player-mpv-ontop
          ;;       (emms-player-mpv-disable-ontop)
          ;;     (emms-player-mpv-enable-ontop)))

          ;; (defun emms-player-mpv-enable-ontop ()
          ;;   "Enable float on top."
          ;;   (let ((cmd (emms-player-mpv--format-command "set ontop yes")))
          ;;     (call-process-shell-command cmd nil nil nil))
          ;;   (setq emms-player-mpv-ontop t))

          ;; (defun emms-player-mpv-disable-ontop ()
          ;;   "Disable float on top."
          ;;   (let ((cmd (emms-player-mpv--format-command "set ontop no")))
          ;;     (call-process-shell-command cmd nil nil nil))
          ;;   (setq emms-player-mpv-ontop nil))

          ;; (keymap-global-set "C-c e t" 'emms-player-mpv-toggle-ontop)
          )

      (message "--- mpv is NOT installed."))))

;; ivy で過去再生した楽曲をたどる
(autoload-if-found '(ivy-emms) "ivy-emms" nil t)

7.2. [GoogleMaps.el] GoogleMaps を Emacs 内で使う

  • ネタとして最高の出来

http://julien.danjou.info/software/google-maps.el

M-x google-maps で起動します.

;; late-init.el
(when (autoload-if-found '(google-maps)
                         "google-maps" nil t)
  (with-eval-after-load "google-maps"
    (require 'org-location-google-maps nil t)))

+/- でズーム, 矢印 で移動, q で終了します.また, w でURLを取得してコピー, t で地図の種別を変更できます.

Org-mode を使っている場合には, C-c M-L で表示されるプロンプトで検索すると,プロパティにそのキーワードが記録されます.後から C-c M-l すれば,いつでも地図を表示できるようになります.

7.3. [japanlaw.el] Emacs 電子六法

Emacs で総務省の「法令データ提供システム」に登録された法令データを閲覧します. w3m が必要です.

(if (executable-find "w3m")
    (autoload-if-found '(japanlaw) "japanlaw" nil t)
  (message "--- w3m is NOT installed."))

7.4. [sunshine.el] 天気を知る

  • https://openweathermap.org/ にアカウントを作り,APIを呼び出すための専用IDを発行する必要があります.取得した idを, sunshine-appid に格納し, sunshine-location で対象地域を設定すれば, M-x sunshine-forecast で天気が表示されます. M-x sunshine-quick-format を使うと,結果がミニバッファに表示されます.
;; late-init.el
(when (autoload-if-found '(sunshine-forecast sunshine-quick-forecast)
                         "sunshine" nil t)
  (with-eval-after-load "sunshine"
    ;; (setq sunshine-location "Tokyo, Japan")
    ;; (setq sunshine-appid "................................")
    (custom-set-variables
     '(sunshine-show-icons t)
     '(sunshine-units 'metric))))

7.5. [org-google-weather.el] org-agenda に天気を表示する   obsolete

残念ながら Google API が変更になり動かなくなったそうです.

http://julien.danjou.info/software/google-weather.el

(require 'google-weather nil t)
(when (require 'org-google-weather nil t)
  '(org-google-weather-use-google-icons t))

7.6. [bongo.el] Emacsのバッファで音楽ライブラリを管理する   obsolete

iTunes の代わりに Emacs を使う

autoload を設定すると, *.bango-playlist*.bongo-library から起動できないので,明示的に require している.なお,bongo-mplayer を使う場合,bongo を先にrequireするとうまく動作しない(bongo.el の最後で,bongo-mplayer が provide されているからだと思われる).

以下の設定では,autoload で使いつつ,=M-x init-bongo= でプレイリストを読み込んでいる.これならば,Emacs起動時は軽量で,かつ,プレイリストの訪問で Bongo を開始できる.

(when (autoload-if-found '(bongo)
                         "bongo-mplayer" nil t)
  (with-eval-after-load "bongo-mplayer"
    (defun init-bongo ()
      (interactive)
      (bongo)
      (find-file "~/Desktop/next/Tidy/hoge.bongo-playlist"))

    ;; Volume control
    ;;         (require volume.el nil t)
    (setq bongo-mplayer-extra-arguments '("-volume" "1"))
    ;; Avoid error when editing bongo buffers
    (setq yank-excluded-properties nil)
    ;; Use mplayer
    (setq bongo-enabled-backends '(mplayer))))

org-player.el を使えば,org-mode のバッファから Bongo を操作できる.

(with-eval-after-load "org"
  (require 'org-player nil t))

音量コントロールには,volume.elが必要です.設定がうまくいかないので保留中

(autoload 'volume "volume" "Tweak your sound card volume." t)

8. 履歴/ファイル管理

8.1. 履歴サイズを大きくする

t で無限大に指定できる.

(setq history-length 2000)

8.2. Undoバッファを無限に取る

(setq undo-outer-limit nil)

8.3. 最近開いたファイルリストを保持

Built-in の recentf.el を使う.

recentf-auto-cleanup を 'mode などにすると起動時にファイルのクリーニングが行われるてしまうので, 'never で回避し,アイドルタイマーなどで対応する.これだけで50[ms]ほど起動を高速化できる.

;;;###autoload
(defun my-recentf-save-list-silence ()
  (interactive)
  (if shutup-p
            (shut-up (recentf-save-list))
          (let ((message-log-max nil))
            (recentf-save-list)))
  (message ""))

;;;###autoload
(defun my-recentf-cleanup-silence ()
  (interactive)
  (when (file-exists-p "/Volumes/orzHDn")
          (if shutup-p
              (shut-up (recentf-cleanup))
            (let ((message-log-max nil))
              (recentf-cleanup)))
          (message "")))

;;;###autoload
(defun my-counsel-recentf-action (file)
  (cond ((string-match "\\.numbers$\\|\\.xlsx$" file)
         (eval `(with-ivy-window (org-open-file ,file))))
        (t
         (eval `(with-ivy-window (find-file ,file))))))

;;;###autoload
(defun ad:counsel-recentf ()
  "Find a file on `recentf-list'."
  (interactive)
  (require 'recentf)
  (recentf-mode)
  (ivy-read "Recentf: "
                        (mapcar (lambda (x) (abbreviate-file-name  ;; ~/
                                                         (substring-no-properties x)))
                                      recentf-list)
                        :action #'my-counsel-recentf-action
                        :require-match t
                        :caller 'counsel-recentf))
(with-eval-after-load "recentf"
  (message "Loading recentf...done")
  (custom-set-variables
   '(recentf-auto-cleanup 'never)))

(when (autoload-if-found '(counsel-recentf)
                         "counsel" nil t)
  (keymap-global-set "C-M-r" 'counsel-recentf))

;; see https://github.com/mattfidler/EmacsPortable.App/issues/7
(when (eq system-type 'darwin)
  ;; Dropbox のエイリアスを展開されないようにする
  ;; find-file での表示も短縮される.
  (let ((provider (expand-file-name "~/Library/CloudStorage/")))
    (setq directory-abbrev-alist
                `((,(concat "\\`" provider "Dropbox") . "~/Dropbox")))))
(when (autoload-if-found '(rencetf-mode
                                                         my-recentf-save-list-silence
                                                         my-recentf-cleanup-silence
                                                         recentf-open-files)
                                                       "recentf" nil t)
  (with-eval-after-load "recentf"
    (custom-set-variables
     '(recentf-max-saved-items 2000)
     '(recentf-save-file (expand-file-name "~/.emacs.d/_recentf"))
     '(recentf-auto-cleanup 'never)
     '(recentf-exclude
       '(".recentf" "bookmarks" "org-recent-headings.dat" "^/tmp\\.*"
               "^/private\\.*" "^/var/folders\\.*" "/TAGS$")))

    (if (version< emacs-version "27.1")
              (progn
                      (add-hook 'focus-out-hook #'my-recentf-save-list-silence)
                      (add-hook 'focus-out-hook #'my-recentf-cleanup-silence))
      (add-function :before after-focus-change-function
                                            #'my-recentf-save-list-silence)
      (add-function :before after-focus-change-function
                                            #'my-recentf-cleanup-silence))

    (unless noninteractive
      (let ((message-log-max nil))
              (if (equal (system-name) "water.local")
                        (recentf-mode 1)
                      (message "--- recentf is not activated in %s" system-name)))))

  (with-eval-after-load "counsel"
    (advice-add 'counsel-recentf :override #'ad:counsel-recentf)
    (ivy-add-actions
     'counsel-recentf
     '(("g" my-counsel-ag-in-dir "switch to ag")
       ("r" my-counsel-fzf-in-dir "switch to fzf (in dir.)")
       ("z" my-counsel-fzf-in-default-dir "switch to fzf (default)")))))

;; (add-hook 'after-init-hook #'recentf-mode))

8.4. C-g した場所をブックマークする

C-g でカーソルが飛んだ時,元の場所へブックマークから戻れます.mark でも辿れるようにしておきます.

(defvar my-cg-bookmark "c-g-point-last")
;;;###autoload
(defun my-cg-bookmark ()
  (push-mark)
  (when (and buffer-file-name
             (eq major-mode 'org-mode)
             (not (org-before-first-heading-p))
             (> (org-current-level) 1)) ;; レベル1の heading を除外
    (bookmark-set my-cg-bookmark)
    (save-buffer)))
(with-eval-after-load "ah"
  (advice-add 'my-cg-bookmark :around #'ad:suppress-message)
  (add-hook 'ah-before-c-g-hook #'my-cg-bookmark))

8.5. 開いているバッファのcopy/rename/deleteと別アプリでの表示

curx.el から次の関数を拝借しています.

  1. M-x crux-copy-file-preserve-attributes
  2. M-x crux-rename-file-and-buffer
  3. M-x crux-delete-file-and-buffer
  4. M-x crux-open-with
    • diredでは, C-c C-o で呼べるように設定
;; init-dired.el --- config for dired-mode -*- lexical-binding: t -*-
(keymap-set dired-mode-map "C-c C-o" 'crux-open-with)
;;;###autoload
(defun crux-copy-file-preserve-attributes (visit)
  "[curx.el]
Copy the current file-visiting buffer's file to a destination.

This function prompts for the new file's location and copies it
similar to cp -p. If the new location is a directory, and the
directory does not exist, this function confirms with the user
whether it should be created. A directory must end in a slash
like `copy-file' expects. If the destination is a directory and
already has a file named as the origin file, offers to
overwrite.

If the current buffer is not a file-visiting file or the
destination is a non-existent directory but the user has elected
to not created it, nothing will be done.

When invoke with C-u, the newly created file will be visited.
"
  (interactive "p")
  (let ((current-file (buffer-file-name)))
    (when current-file
      (let* ((new-file (read-file-name "Copy file to: "))
             (abs-path (expand-file-name new-file))
             (create-dir-prompt "%s is a non-existent directory, create it? ")
             (is-dir? (string-match "/" abs-path (1- (length abs-path))))
             (dir-missing? (and is-dir? (not (file-exists-p abs-path))))
             (create-dir? (and is-dir?
                               dir-missing?
                               (y-or-n-p
                                (format create-dir-prompt new-file))))
             (destination (concat (file-name-directory abs-path)
                                  (file-name-nondirectory current-file))))
        (unless (and is-dir? dir-missing? (not create-dir?))
          (when (and is-dir? dir-missing? create-dir?)
            (make-directory abs-path))
          (condition-case nil
              (progn
                (copy-file current-file abs-path nil t t t)
                (message "Wrote %s" destination)
                (when visit
                  (find-file-other-window destination)))
            (file-already-exists
             (when (y-or-n-p (format "%s already exists, overwrite? " destination))
               (copy-file current-file abs-path t t t t)
               (message "Wrote %s" destination)
               (when visit
                 (find-file-other-window destination))))))))))

;;;###autoload
(defun crux-rename-file-and-buffer ()
  "[curx.el]
Rename current buffer and if the buffer is visiting a file, rename it too."
  (interactive)
  (when-let* ((filename (buffer-file-name))
              (new-name (or (read-file-name "New name: " (file-name-directory filename) nil 'confirm)))
              (containing-dir (file-name-directory new-name)))
    ;; make sure the current buffer is saved and backed by some file
    (when (or (buffer-modified-p) (not (file-exists-p filename)))
      (if (y-or-n-p "Can't move file before saving it.  Would you like to save it now?")
          (save-buffer)))
    (if (get-file-buffer new-name)
        (message "There already exists a buffer named %s" new-name)
      (progn
        (make-directory containing-dir t)
        (cond
         ((vc-backend filename)
          ;; vc-rename-file seems not able to cope with remote filenames?
          (let ((vc-filename (if (tramp-tramp-file-p filename) (tramp-file-local-name filename) filename))
                (vc-new-name (if (tramp-tramp-file-p new-name) (tramp-file-local-name filename) new-name)))
            (vc-rename-file vc-filename vc-new-name)))
         (t
          (rename-file filename new-name t)
          (set-visited-file-name new-name t t)))))))

;;;###autoload
(defun crux-delete-file-and-buffer ()
  "[curx.el]
Kill the current buffer and deletes the file it is visiting."
  (interactive)
  (let ((filename (buffer-file-name)))
    (when filename
      (if (vc-backend filename)
          (vc-delete-file filename)
        (when (y-or-n-p (format "Are you sure you want to delete %s? " filename))
          (delete-file filename delete-by-moving-to-trash)
          (message "Deleted file %s" filename)
          (kill-buffer))))))

;;;###autoload
(defun crux-open-with (arg)
  "[curx.el]
Open visited file in default external program.
When in dired mode, open file under the cursor.

With a prefix ARG always prompt for command to use."
  (interactive "P")
  (let* ((current-file-name
          (if (derived-mode-p 'dired-mode)
              (dired-get-file-for-visit)
            buffer-file-name))
         (open (pcase system-type
                 (`darwin "open")
                 ((or `gnu `gnu/linux `gnu/kfreebsd) "xdg-open")))
         (program (if (or arg (not open))
                      (read-shell-command "Open current file with: ")
                    open)))
    (call-process program nil 0 nil current-file-name)))

8.6. バッファ保存時にバックアップを生成させない   backup

;; *.~
(setq make-backup-files nil)
;; .#*
(setq auto-save-default nil)
;; auto-save-list
(setq auto-save-list-file-prefix nil)

8.7. 特定のファイルを Dropbox 以下にバックアップする   backup

複数の端末でEmacsを使うと,稀に端末の環境に依存した設定ファイルが必要になります.それを共有してそのまま使うことはできないないので,所定の場所での同期を避ける必要があります.私の場合は, recentf がそれに該当します.とは言えバックアップしていないのは不安なので,なんとかします.引数にバックアップ対象のファイルリストを渡せます.

事前に ~/Dropbox/backup の下に, system-name で得られる値のディレクトリを作成する必要があります. my-backup./utility.el で実装しています.

;;;###autoload
(defun my-backup-recentf ()
  (interactive)
  (my-backup recentf-save-file) ;; "~/.emacs.d/recentf"
  (my-backup (expand-file-name "~/.histfile")))
(with-eval-after-load "recentf"
  (run-with-idle-timer 60 t 'my-backup-recentf))

8.8. [backup-each-save.el] クラッシュに備える   backup

直近のファイルを常にバックアップします. backup-dir.el でも良いですが,バックアップの目的が,バッファ編集中に emacs が落ちる時の保険ならば, backup-each-save の方が適切な場合があります.以下の例では,すべてのファイルを保存の度に保存し, emacs 起動後に postpone モードが有効になる時点で7日前までのバックアップファイルをすべて削除するようにしています.

;;;###autoload
(defun my-auto-backup ()
  (unless (equal (buffer-name) "recentf")
    (backup-each-save)))

;;;###autoload
(defun my-backup-each-save-compute-location (filename)
  (let* ((containing-dir (file-name-directory filename))
         (basename (file-name-nondirectory filename))
         (backup-container
          (format "%s/%s"
                  backup-each-save-mirror-location
                  ;; "c:" is not allowed
                  (replace-regexp-in-string ":" "" containing-dir))))
    (when (not (file-exists-p backup-container))
      (make-directory backup-container t))
    (format "%s/%s-%s" backup-container basename
            (format-time-string backup-each-save-time-format))))
(when (autoload-if-found '(backup-each-save my-auto-backup)
                         "backup-each-save" nil t)
  (add-hook 'after-save-hook #'my-auto-backup)
  ;; %y-%m-%d_%M-%S で終わるファイルを本来のメジャーモードで開く
  (add-to-list 'auto-mode-alist '("-[0-9-]\\{8\\}_[0-9-]\\{5\\}$" nil t))

  (with-eval-after-load "backup-each-save"
    (setq backup-each-save-mirror-location "~/.emacs.d/backup")
    (setq backup-each-save-time-format "%y-%m-%d_%M-%S") ;; do not use ":" for w32
    (setq backup-each-save-size-limit 1048576))

  (when (eq window-system 'w32)
    (advice-add 'backup-each-save-compute-location :override
                #'my-backup-each-save-compute-location)))

8.9. [dired] ファイラのサポートツール   dired

dired.el をリッチに使うツール群です.

  • gited
  • dired-x
  • dired-narrow
  • dired-du
  • helm-dired-history or ivy-dired-history
;; late-init.el
(add-hook 'dired-mode-hook #'my-dired-activate)
(with-eval-after-load "dired"
  (setq dired-listing-switches "-lha"))
;;;###autoload
(defun my-dired-activate ()
  (unless (require 'init-dired nil t)
    (user-error "init-dired.el doesn't exist")))

明示的に dired を呼ばなくても, ibufferdired を読み込むことがわかったので, dired に関係する以下の設定は, init-dired.el に分離しました.下記の設定は,最初に M-x dired を実行したタイミングで読み込みます.

(setq completion-ignored-extensions
      (append completion-ignored-extensions
              '("./" "../" ".xlsx" ".docx" ".pptx" ".DS_Store")))

;; "dired-mode-map"
;; Use build-in `wdired-mode'.
;; (keymap-set dired-mode-map "R" 'wdired-change-to-wdired-mode)
;; http://elpa.gnu.org/packages/gited.html
(when (require 'gited nil t)
  (keymap-set dired-mode-map "C-x C-g" 'gited-list-branches))
;; https://github.com/Fuco1/dired-hacks
(when (require 'dired-narrow nil t)
  (keymap-set dired-mode-map "/" 'dired-narrow))
(require 'dired-du nil t)
(when (require 'ivy-dired-history nil t)
  ;; ivy-dired-history-variable は,session.el で明示的に管理中.
  ;; check session-globals-include
  (keymap-set dired-mode-map "," 'dired))
(declare-function dired-extra-startup "dired-x")
(when (require 'dired-x nil t)
  (dired-extra-startup))
(defun my-reveal-in-finder ()
  "Reveal the current buffer in Finder."
  (interactive)
  (shell-command-to-string "open ."))
;; dired-x を読み込んだあとじゃないとだめ
(keymap-set dired-mode-map "F" 'my-reveal-in-finder)
;; 上位ディレクトリへの移動
(keymap-set dired-mode-map "u" 'dired-up-directory)
;; Finder を使ったファイルオープン
(keymap-set dired-mode-map "f" 'ns-open-file-using-panel)

(keymap-set dired-mode-map "C-M-p" (lambda () (interactive) (other-window -1)))
(keymap-set dired-mode-map "C-M-n" (lambda () (interactive) (other-window 1)))

;; https://github.com/xuchunyang/emacs.d
;; type "!" or "X" in dired
(when (eq system-type 'darwin)
  (setq dired-guess-shell-alist-user
        (list
         (list (rx (and "."
                        (or
                         ;; Videos
                         "mp4" "avi" "mkv" "rmvb"
                         ;; Torrent
                         "torrent"
                         ;; PDF
                         "pdf"
                         ;; Image
                         "gif" "png" "jpg" "jpeg")
                        string-end)) "open"))))
(when (and nil
           (require 'hydra nil t)
           (require 'dired-recent nil t))

  ;; (keymap-set dired-mode-map "h" 'hydra-dired/body)
  ;; (keymap-set dired-mode-map "r" 'dired-recent-open)

  ;; https://github.com/abo-abo/hydra/wiki/Dired
  (defhydra hydra-dired (:hint nil :color pink)
            "
_+_ mkdir        _v_iew        _m_ark            _(_ details       _i_nsert-subdir
_C_opy           View _O_ther  _U_nmark all      _)_ omit-mode
_D_elete         _o_pen other  _u_nmark          _l_ redisplay     _w_ kill-subdir
_R_ename         _M_ chmod     _t_oggle          _g_ revert buf
_Y_ rel symlink  _G_ chgrp     _E_xtension mark  _s_ort            _r_ dired-recent-open
_S_ymlink        ^ ^           _F_ind marked     _._ toggle hydra  _?_ summary
_A_ find regexp  _Z_ compress  T - tag prefix
_Q_ repl regexp   [wdired] C-x C-q : edit / C-c C-c : commit / C-c ESC : abort
"
            ;; ("\\" dired-do-ispell)
            ("(" dired-hide-details-mode)
            (")" dired-omit-mode)
            ("+" dired-create-directory)
            ;; ("=" diredp-ediff)         ;; smart diff
            ("?" dired-summary)
            ;; ("$" diredp-hide-subdir-nomove)
            ("A" dired-do-find-regexp)
            ("C" dired-do-copy)        ;; Copy all marked files
            ("D" dired-do-delete)
            ("E" dired-mark-extension)
            ;; ("e" dired-ediff-files)
            ("F" dired-do-find-marked-files)
            ("G" dired-do-chgrp)
            ("g" revert-buffer)        ;; read all directories again (refresh)
            ("i" dired-maybe-insert-subdir)
            ("l" dired-do-redisplay)   ;; relist the marked or singel directory
            ("M" dired-do-chmod)
            ("m" dired-mark)
            ("O" dired-display-file)
            ("o" dired-find-file-other-window)
            ("Q" dired-do-find-regexp-and-replace)
            ("R" dired-do-rename)
            ("r" dired-recent-open)
            ;; ("r" dired-do-rsynch)
            ("S" dired-do-symlink)
            ("s" dired-sort-toggle-or-edit)
            ("t" dired-toggle-marks)
            ("U" dired-unmark-all-marks)
            ("u" dired-unmark)
            ("v" dired-view-file)      ;; q to exit, s to search, = gets line #
            ("w" dired-kill-subdir)
            ("Y" dired-do-relsymlink)
            ;; ("z" diredp-compress-this-file)
            ("Z" dired-do-compress)
            ("q" nil)
            ("." nil :color blue))
  )

8.10. [dired-recent.el] 訪問したディレクトリの履歴を取る   dired

(with-eval-after-load "dired"
  (require 'dired-recent nil t))

(when (autoload-if-found '(dired-recent-open dired-recent-mode)
                         "dired-recent" nil t)
  (keymap-global-set "C-x C-d" 'dired-recent-open)
  (with-eval-after-load "dired-recent"
    ;; (require 'helm-config nil t)
    (dired-recent-mode 1)))

8.11. [osx-trash] system-move-file-to-trash を有効にする

osx-trash は,OSXで system-move-file-to-trash を使えるようにする.単独で使うのはあまり考えられないので,読み込みを dired に紐付けます.

(with-eval-after-load "dired"
  (setq dired-use-ls-dired nil)
  (when (require 'osx-trash nil t)
    (setq delete-by-moving-to-trash t)
    (osx-trash-setup)))

8.12. [undo-fu.el] シンプルな redo を提供してくる

(when (autoload-if-found '(undo-fu-only-undo undo-fu-only-redo)
                         "undo-fu" nil t)
  (keymap-global-set "C-/" 'undo-fu-only-undo)
  (keymap-global-set "C-M-/" 'undo-fu-only-redo))

8.13. [super-save.el] 所定のタイミングでバッファを保存する

長らく auto-save-buffers.el のお世話になってきましたが, super-save.el に乗り換えました. super-save.el は,カレントバッファを切り替えるなどの任意のトリガーでバッファを保存したり, auto-save-buffers.el がするように,アイドル時にバッファを保存できます.

例外にするバッファ名を指定したり,特定の条件を満たしている場合に例外とするなどをカスタム変数で指定できるのが便利だと思います.

ただ, org-agenda のように別ファイルが直接更新された場合に,当該ファイルが自動保存されないので, super-save-commandmy-super-save-buffers-command で置き換えて使っています.このパッケージが本来持っているカレントバッファの変化に常時注目するという特徴が失われてしまいますけども…

;;;###autoload
(defun my-super-save-predicates-p ()
  "Return nil, if the buffer should not be saved."
  (not
   (cond ((memq major-mode '(undo-tree-visualizer-mode diff-mode)) t)
         ((when (eq major-mode 'org-mode)
            ;; when activating org-capture
            (or (bound-and-true-p org-capture-mode)
                (and (fboundp 'org-entry-get)
                     (equal "" (org-entry-get (point)
                                              "EXPORT_FILE_NAME"))))) t)
         ((let ((pt (point)))
            ;; .gpg で半角スペースの後ろのブリッツでは自動保存しない.
            ;; FIXME 半角スペース
            (when (and (string-match ".gpg" (buffer-name))
                       (not (eq pt 1))
                       (not (eq pt (point-min))))
              (string-match (buffer-substring (- pt 1) pt) " "))) t))))

;;;###autoload
(defun my-super-save-buffers-command ()
  "Save the buffer if needed.
see https://github.com/bbatsov/super-save/pull/20/files."
  (save-mark-and-excursion
    (dolist (buf (buffer-list))
      (set-buffer buf)
      (when (and buffer-file-name
                 (buffer-modified-p (current-buffer))
                 (file-writable-p buffer-file-name)
                 (if (file-remote-p buffer-file-name)
                     super-save-remote-files t))
        (save-buffer)))))

;;;###autoload
(defun my-super-save-activate ()
  (unless noninteractive
    (super-save-mode 1))
  (remove-hook 'find-file-hook #'my-super-save-activate))
(when (autoload-if-found '(super-save-mode) "super-save" nil t)
  (add-hook 'find-file-hook #'my-super-save-activate)
  (with-eval-after-load "super-save"
    (setq super-save-auto-save-when-idle t)
    (setq super-save-idle-duration 1.6)
    (setq super-save-exclude '("Org Src"))
    (add-to-list 'super-save-predicates
                 '(lambda () (my-super-save-predicates-p)) t)
    (advice-add 'super-save-command :override #'my-super-save-buffers-command)))

8.14. DONE [auto-save-buffers.el] 一定間隔でバッファを保存する   obsolete

同じ機能で比較的新しいパッケージに, real-auto-save.el があります.ただ,私の場合は,以下のようなモードごとの制御がうまくできなかったので移行していません.

;; utility.el
;;;###autoload
(defun my-ox-hugo-auto-saving-p ()
  (when (eq major-mode 'org-mode)
    (or (bound-and-true-p org-capture-mode) ;; when activating org-capture
        (and (fboundp 'org-entry-get)
             (equal "" (org-entry-get (point) "EXPORT_FILE_NAME"))))))

;;;###autoload
(defun my-auto-save-buffers ()
  (cond ((memq major-mode '(undo-tree-visualizer-mode diff-mode)) nil)
        ((string-match "Org Src" (buffer-name)) nil)
        ((let ((pt (point)))
           ;; .gpg で半角スペースの後ろのブリッツでは自動保存しない.FIXME 半角スペース
           (when (and (string-match ".gpg" (buffer-name))
                      (not (eq pt 1))
                      (not (eq pt (point-min))))
             (string-match (buffer-substring (- pt 1) pt) " ")))
         nil)
        ((my-ox-hugo-auto-saving-p) nil)
        (t
         (auto-save-buffers))))
;; late-init.el
(autoload 'auto-save-buffers "auto-save-buffers" nil t)
(run-with-idle-timer 1.6 t #'my-auto-save-buffers)

8.15. [session.el] 様々な履歴を保存し復元に利用する

http://emacs-session.sourceforge.net/

  • 入力履歴の保持(検索語,表示したバッファ履歴)
  • 保存時のカーソル位置の保持
  • キルリングの保持
  • 変更が加えられたファイル履歴の保持
  • session-initialize の呼び出しタイミングに注意.前回終了時の状況を保存しているため,他のパッケージの初期化が終った後に呼び出すと,パッケージが想定している初期化を上書きしてしまい,不安定になる. after-init-hook 推奨.
  • [ ] M-x session-save-session

session-undo-check を指定していると,保存時ではなくバッファを閉じるときの状態を保持する.

Org Mode と併用する場合は, my-org-reveal-session-jump の設定が必須.

上記のパッケージだと, session-set-file-name-exclude-regexpsession-file-alist に効かず,除外したはずのファイルのカーソル位置などが記録されてしまうので,それに対処したパッケージを自分用に作って使っています.

(when (autoload-if-found '(session-initialize)
                         "session" nil t)
  (unless (or noninteractive my-secure-boot)
    (add-hook 'after-init-hook #'session-initialize))
  (with-eval-after-load "session"
    (add-to-list 'session-globals-include 'ivy-dired-history-variable)
    (add-to-list 'session-globals-exclude 'org-mark-ring)
    (setq session-set-file-name-exclude-regexp "[/\\]\\.overview\\|[/\\]\\.session\\|News[/\\]\\|[/\\]COMMIT_EDITMSG")
    ;; Change save point of session.el
    (setq session-save-file
          (expand-file-name (concat (getenv "SYNCROOT") "/emacs.d/.session"))
          session-initialize '(de-saveplace session keys menus places)
          session-globals-include '((kill-ring 100)
                                    (session-file-alist 100 t)
                                    (file-name-history 200)
                                    ivy-dired-history-variable
                                    search-ring
                                    regexp-search-ring))
    (setq session-undo-check -1)))

次はテスト中.orgバッファを開いたらカーソル位置をorg-revealしたいが,time-stampなどと組み合わせたり,org-tree-slideと組み合わせていると,うまくいかない.バッファを表示した時に org-reveal (C-c C-r) を打つのをサボりたいだけなのだが...

http://www.emacswiki.org/emacs/EmacsSession

(when (autoload-if-found '(session-initialize)
                         "session" nil t)
  (add-hook 'after-init-hook #'session-initialize)
  (eval-after-load "session"
    '(progn
       ;; For Org-mode
       (defun my-maybe-reveal ()
         (interactive)
         (when (and (or (memq major-mode '(org-mode outline-mode))
                        (and (boundp 'outline-minor-mominor-de)
                             outline-minor-mode))
                    (outline-invisible-p))
           (if (eq major-mode 'org-mode)
               (org-reveal)
             (show-subtree))))

       (defun my-org-reveal-session-jump ()
         (message "call!")
         (when (and (eq major-mode 'org-mode)
                    (outline-invisible-p))
           (org-reveal)))

       ;; C-x C-/
       (add-hook 'session-after-jump-to-last-change-hook
                 #'my-maybe-reveal))))

8.16. [neotree.el] ディレクトリ情報をツリー表示

;;;###autoload
(defun ad:neotree-show ()
  "Extension to support change frame width when opening neotree."
  (unless (neo-global--window-exists-p)
    (when (and (require 'moom nil t)
               (not my-neo-activated))
      (setq moom-frame-width-single
            (+ moom-frame-width-single my-neo-adjusted-window-width))
      (setq moom-frame-width-double
            (+ moom-frame-width-double my-neo-adjusted-window-width)))
    (set-frame-width nil (+ (frame-width) my-neo-adjusted-window-width))
    (setq my-neo-activated t)))

;;;###autoload
(defun ad:neotree-hide ()
  "Extension to support change frame width when closing neotree."
  (when (neo-global--window-exists-p)
    (when (and (require 'moom nil t)
               my-neo-activated)
      (setq moom-frame-width-single
            (- moom-frame-width-single my-neo-adjusted-window-width))
      (setq moom-frame-width-double
            (- moom-frame-width-double my-neo-adjusted-window-width)))
    (set-frame-width nil (- (frame-width) my-neo-adjusted-window-width))
    (when (> 80 (frame-width)) ;; fail safe
      (set-frame-width nil 80))
    (setq my-neo-activated nil)))
(when (autoload-if-found '(neotree neotree-toggle)
                         "neotree" nil t)
  (keymap-global-set "C-c n" #'neotree-toggle)
  (with-eval-after-load "neotree"
    (custom-set-variables
     '(neo-show-hidden-files t)
     '(neo-theme 'arrow)
     '(neo-smart-open t)
     '(neo-window-width 25)
     '(neo-show-hidden-files nil)
     '(neo-window-position 'left))
    ;; (setq neo-vc-integration '(face char)) ;; It's heavy at 2017-08-31

    ;; アイコン表示
    (when (require 'all-the-icons-dired nil t)
      (setq neo-theme (if (display-graphic-p) 'icons 'arrow)))

    (defvar my-neo-activated nil) ;; fail save
    (defvar my-neo-adjusted-window-width (+ 3 neo-window-width))
    (advice-add 'neotree-show :before #'ad:neotree-show)
    (advice-add 'neotree-hide :before #'ad:neotree-hide)))

8.17. [helpful.el] リッチなヘルプページ

ヘルプの内容を見やすく構造化して表示してくれます.関数や変数のヘルプだけでなく,キーやマクロなども見やすく表示してくれます.

;;;###autoload
(defun ad:helpful-at-point ()
  (deactivate-mark))
(when (autoload-if-found '(helpful-key
                           helpful-function helpful-variable helpful-at-point
                           helpful-symbol)
                         "helpful" nil t)
  (keymap-global-set "<f1> k" 'helpful-key)
  (keymap-global-set "<f1> f" 'helpful-function)
  (keymap-global-set "<f1> v" 'helpful-variable)
  (keymap-global-set "<f1> m" 'helpful-macro)
  (keymap-global-set "<f1> @" 'helpful-at-point)
  (with-eval-after-load "helpful"
    (advice-add 'helpful-at-point :before #'ad:helpful-at-point)))

8.18. [facecheck.el] クリックしてfaceをチェック

マウスクリックでface情報を表示する.マイナーモードを有効にして使います.

(when (autoload-if-found '(facecheck-at-point facecheck-mode)
                         "facecheck" nil t)
  (with-eval-after-load "facecheck"
    (facecheck-mode 1)))

8.19. [keyfreq.el] コマンドログ

発行しているコマンドの使用頻度を記録し確認できます.デフォルトで, ~/.emacs.keyfreq に情報が記録されます.

;;;###autoload
(defun ad:keyfreq-show ()
  "Extension to make the buffer view-only."
  (interactive)
  (if shutup-p
      (shut-up (view-buffer keyfreq-buffer))
    (view-buffer keyfreq-buffer)))
(when (autoload-if-found '(keyfreq-mode keyfreq-autosave-mode ad:keyfreq-show)
                         "keyfreq" nil t) ;; will require 'cl and 'gv(10-20[ms])
  (with-eval-after-load "keyfreq"
    (advice-add 'keyfreq-show :after #'ad:keyfreq-show)
    ;; (keymap-set keyfreq-mode-map "q"
    ;;   (lambda () (interactive)
    ;;       (when (string= (buffer-name) keyfreq-buffer)
    ;;         (kill-buffer-and-window))))
    (setq keyfreq-file
          (expand-file-name (concat (getenv "SYNCROOT") "/emacs.d/.keyfreq")))
    (keyfreq-autosave-mode 1))
  (unless noninteractive
    (keyfreq-mode 1)))

8.20. [disk-usage.el] ディスク利用率を調べる

ncdu コマンドのように,システムのディスク利用率を調べてバッファに一覧表示します.ただ,非同期に対応していないので,普通にシェルで ncdu コマンドを使う方が良い気がします.

(when (autoload-if-found '(disk-usage)
                         "disk-usage" nil t)
  (with-eval-after-load "disk-usage"
    (when (eq system-type 'darwin)
      (custom-set-variables
       '(disk-usage-du-command "du")))))

8.21. [uptimes.el] Emacsの起動時間を記録する

davep/uptimes.el: Uptime tracking system for emacs. を使うと,過去数回の起動・稼働・終了履歴がわかります.

;; 
(require 'uptimes nil t)

M-x uptimes すると,以下のような履歴が表示されます.

Last 10 uptimes

Boot                Endtime             Uptime       This emacs
=================== =================== ============ ==========
2019-04-17 12:28:14 2019-04-17 12:29:37   0.00:01:23 <--
2019-04-17 12:28:18 2019-04-17 12:28:25   0.00:00:06
2019-04-17 11:47:13 2019-04-17 12:10:39   0.00:23:26
2019-04-17 11:13:13 2019-04-17 11:21:05   0.00:07:51

Top 10 uptimes

Boot                Endtime             Uptime       This emacs
=================== =================== ============ ==========
2019-04-17 11:47:13 2019-04-17 12:10:39   0.00:23:26
2019-04-17 11:13:13 2019-04-17 11:21:05   0.00:07:51
2019-04-17 12:28:14 2019-04-17 12:29:37   0.00:01:23 <--
2019-04-17 12:28:18 2019-04-17 12:28:25   0.00:00:06

8.22. [ag.el] 検索

検索には The Silver Searcher を使います.あらかじめインストールしておく必要があります.MacPorts の場合,the_silver_searcher の名称で頒布されています. exec-path/opt/local/bin が含まれていることを確認してください.

the_silver_searcher @0.18.1 (textproc)
 A code-searching tool similar to ack, but faster.

カスタマイズした関数を C-M-f にぶら下げています.helm インタフェースを使う helm-ag や ivy インターフェイスを使う counsel-ag もあります. C-M-f は,本来,S式の移動(forward-sexp)に使われています.

最近は, counsel-ag に移行しました.

(if (executable-find "ag")
    (when (autoload-if-found '(my-ag ag)
                             "ag" nil t)
      (autoload-if-found '(helm-ag) "helm-ag" nil t)
      (keymap-global-set "C-M-f" 'my-ag)

      (with-eval-after-load "ag"
        (custom-set-variables
         '(ag-highlight-search t)
         '(ag-reuse-buffers t)          ;; nil: 別ウィンドウが開く
         '(ag-reuse-window nil))        ;; nil: 結果を選択時に別ウィンドウに結果を出す

        ;; q でウィンドウを抜ける
        ;; (keymap-set ag-mode-map "q" 'delete-window)
        (defun my-ag ()
          "Switch to search result."
          (interactive)
          (call-interactively 'ag)
          (switch-to-buffer-other-frame "*ag search*"))))

  (unless noninteractive
    (message "--- ag is NOT installed in this system.")))

8.23. [counsel-ag.el] 高速全文検索

;;;###autoload
(defun ad:counsel-ag (f &optional initial-input initial-directory extra-ag-args ag-prompt caller)
  (apply f (or initial-input
               (and (not (thing-at-point-looking-at "^\\*+"))
                    (ivy-thing-at-point)))
         (unless current-prefix-arg
           (or initial-directory default-directory))
         extra-ag-args ag-prompt caller))
(when (autoload-if-found '(counsel-ag)
                         "counsel" nil t)
  (keymap-global-set "C-M-f" 'counsel-ag)
  (with-eval-after-load "counsel"
    (require 'thingatpt nil t)
    (advice-add 'counsel-ag :around #'ad:counsel-ag)

    ;; 2文字でも検索が発動するようにする
    (add-to-list 'ivy-more-chars-alist '(counsel-ag . 2))

    (ivy-add-actions
     'counsel-ag
     '(("r" my-counsel-ag-in-dir "search in directory")))))

8.24. [counsel-fzf.el] 高速ファイル検索

;;;###autoload
(defun ad:counsel-fzf (f &optional initial-input initial-directory fzf-prompt)
  (apply f (or initial-input
               (if (thing-at-point-looking-at "^\\*+") ;; org heading を除外
                   nil
                 (ivy-thing-at-point)))
         (or initial-directory (funcall counsel-fzf-dir-function))
         fzf-prompt))
(when (autoload-if-found '(counsel-fzf)
                         "counsel" nil t)
  (keymap-global-set "C-M-z" 'counsel-fzf)
  (with-eval-after-load "counsel"
    (advice-add 'counsel-fzf :around #'ad:counsel-fzf)
    (ivy-add-actions
     'counsel-fzf
     '(("r" my-counsel-fzf-in-dir "search in directory")))))

8.25. DONE [undo-propose] undo を編集不可モードで辿る   obsolete

最近は undo-fu.el を使っています.

undo する時に,バッファに意図しない変更が加わると面倒なことになります.そこで org-capture なキーバインドで動作する undo-propose を使います.

undo-propose でモードに入ったら, C-/undo して,確定するなら C-c C-c ,キャンセルするなら C-c C-k します.オリジナルのバッファを直接変更せず,一時的なバッファで undo してから反映させるのでより安全です.

次の設定では,標準の undo をオーバーライドするように undo-propose を使います.

;; late-init.el
(when (autoload-if-found '(undo-propose)
                         "undo-propose" nil t)

  (defun my-undo-propose ()
    (interactive)
    (if (or (buffer-narrowed-p)
            (eq major-mode 'org-mode))
        (undo)
      (undo-propose)))

  (keymap-global-set "C-/" 'my-undo-propose)

  ;; located here intended to share this command with `undo-tree'
  (defun my-org-reveal-and-focus (&optional _arg)
    "Reveal a heading and focus on the content."
    (when (eq major-mode 'org-mode)
      (org-overview)
      (unless (org-before-first-heading-p)
        (org-reveal)
        (org-cycle-hide-drawers 'all)
        (org-show-entry)
        (show-children)
        (org-show-siblings))))
  (advice-add 'undo :after #'my-org-reveal-and-focus)

  (eval-when-compile
    (require 'undo-propose nil t)) ;; for `undo-propose-wrap'

  (with-eval-after-load "undo-propose"
    (add-hook 'undo-propose-entry-hook #'undo) ;; immediate undo
    (undo-propose-wrap redo)

    (keymap-set undo-propose-mode-map "/" 'undo)
    (keymap-set undo-propose-mode-map "q" 'undo-propose-cancel)
    (advice-add 'undo-propose-commit :after #'my-org-reveal-and-focus)
    (advice-add 'undo-propose-squash-commit :after #'my-org-reveal-and-focus)

    ;; Moving coursor in `undo-propose-mode' will also commit changes.
    (defun my-undo-propose-commit ()
      (when undo-propose-mode
        (undo-propose-commit)))

    (defun my-undo-propose-autocommit-on ()
      (add-hook 'ah-after-move-cursor-hook 'my-undo-propose-commit))
    (advice-add 'undo-propose :after #'my-undo-propose-autocommit-on)

    (defun my-undo-propose-autocommit-off ()
      (remove-hook 'ah-after-move-cursor-hook 'my-undo-propose-commit))
    (add-hook 'undo-propose-done-hook #'my-undo-propose-autocommit-off)

    ;; SPC and <return> in `undo-propose-mode' will commit changes.
    ;; Additionally, the commands will be executed.
    (keymap-set undo-propose-mode-map "SPC" 'undo-propose-commit)
    (keymap-set undo-propose-mode-map "RET" 'undo-propose-commit)
    (defun my-undo-propose-key-through ()
      "Through SPC and <return>."
      (let ((command (this-command-keys)))
        (cond ((equal (kbd "SPC") command)
               (insert " "))
              ((equal (kbd "RET") command)
               (electric-newline-and-maybe-indent)))))
    (advice-add 'undo-propose-commit :after #'my-undo-propose-key-through)

    (defvar my-undo-propose-modeline '("#FF5d5d" "#FFFFFF"))
    (defun my-undo-propose-mode-line ()
      (custom-set-faces
       `(mode-line ((t (:background
                        ,(nth 0 my-undo-propose-modeline)
                        :foreground
                        ,(nth 1 my-undo-propose-modeline)))))))

    (defun my-undo-propose-mode-line-restore ()
      (custom-set-faces '(mode-line ((t nil)))))

    (add-hook 'undo-propose-entry-hook #'my-undo-propose-mode-line)
    (add-hook 'undo-propose-done-hook #'my-undo-propose-mode-line-restore)))

8.26. DONE [undo-tree] 編集履歴をわかりやすくたどる   obsolete

最近は undo-fu.el を使っています.

Undoのツリーが表示され,履歴をたどれます. C-x uq に対して,フレームサイズの変更を紐付けています.また, auto-save-buffers が org-files をどんどん保存して記録してしまうので,ツリーを選んでいる時に auto-save-buffers が発動するのを別途抑制しています.加えて, org-tree-slide でナローイングしていると,タイムスタンプが記録される時に履歴が辿れなくなるので, org-tree-slide が有効の時は,タイムスタンプを押させないように別途制限を加えています.

;; late-init.el
(when (autoload-if-found '(my-undo-tree-visualize)
                         "undo-tree" nil t)
  (keymap-global-set "C-x u" 'my-undo-tree-visualize)
  (with-eval-after-load "undo-tree"
    ;; (global-undo-tree-mode)
    (setq undo-tree-mode-lighter nil) ;; モードライン領域を節約

    (defvar my-undo-tree-active nil)
    (defvar my-undo-tree-width 90)

    (defun my-undo-tree-visualize ()
      (interactive)
      (undo-tree-mode 1)
      (if (require 'moom nil t)
          (moom-change-frame-width-double)
        (when (and (not my-undo-tree-active)
                   (not (eq buffer-undo-list t)))
          (set-frame-width nil (+ (frame-width) my-undo-tree-width))
          (setq my-undo-tree-active t)))
      (undo-tree-visualize))

    (keymap-set undo-tree-map "C-x u" 'my-undo-tree-visualize)

    (defun my-undo-tree-visualizer-quit ()
      (interactive)
      (undo-tree-visualizer-quit)
      (if (require 'moom nil t)
          (moom-change-frame-width-single)
        (delete-window)
        (when my-undo-tree-active
          (set-frame-width nil (- (frame-width) my-undo-tree-width))
          (setq my-undo-tree-active nil)))
      (when (< (frame-width) 80)
        (set-frame-width nil 80))
      (undo-tree-mode -1))

    (keymap-set undo-tree-visualizer-mode-map "q"
      'my-undo-tree-visualizer-quit))

  (advice-add 'undo-tree-undo-1 :after #'my-org-reveal-and-focus)
  (advice-add 'undo-tree-redo-1 :after #'my-org-reveal-and-focus))

8.27. DONE [wakatime-mode.el] WakaTime を利用して作業記録する   obsolete

  1. https://www.wakati.me/(API発行とログGUI表示)
  2. https://github.com/wakatime/wakatime(ログ記録用スクリプト)
  3. https://github.com/nyuhuhuu/wakatime-mode(Emacs用プラグイン)

利用開始前に,ログ表示サイトでルールをカスタマイズしておくとよい.例えば,拡張子が .org なファイルの場合,言語設定を Text にする,という具合に.すると,グラフ表示がわかりやすくなる.

(when (require 'wakatime-mode nil t)
  (setq wakatime-api-key "<insert your own api key>")
  (setq wakatime-cli-path "/Users/taka/Dropbox/emacs.d/bin/wakatime-cli.py")
  ;; すべてのバッファで訪問時に記録を開始
  ;; (global-wakatime-mode)
    )

8.28. DONE [backup-dir.el] バックアップファイルを一箇所に集める   obsolete

backup-each-save を使うようになりました.

(make-variable-buffer-local 'backup-inhibited)
(setq backup-files-store-dir "~/.emacs.d/backup")
(unless (file-directory-p backup-files-store-dir)
  (message "!!! %s does not exist. !!!" backup-files-store-dir)
  (sleep-for 1))
(when (require 'backup-dir nil t)
  (when (file-directory-p backup-files-store-dir)
    ;; backup path
    (setq bkup-backup-directory-info '((t "~/.emacs.d/backup" ok-create)))
    ;; for tramp
    (setq tramp-bkup-backup-directory-info bkup-backup-directory-info)
    ;; generation properties
    (setq delete-old-versions t
          kept-old-versions 0
          kept-new-versions 5
          version-control t)))

8.29. DONE バッファ保存時にバックアップファイルを生成する   obsolete

バッファが保存されるとき,必ずバックアップを生成する.

;; Backup the buffer whenever the buffer is saved
(keymap-global-set "C-x C-s" (lambda () (interactive) (save-buffer 16)))

8.30. DONE ミニバッファの履歴を保存しリストアする   obsolete

(when (require 'savehist nil t)
  ;; ヒストリファイルを明示的に指定
  (setq savehist-file "~/Dropbox/emacs.d/.history")
  (savehist-mode 1))

8.31. DONE Emacs終了時に開いていたバッファを起動時に復元する   obsolete

Built-in の desktop.el を使う.

org バッファを CONTENT view で大量に開いていると,再起動が非常に遅くなるので利用を中止した.代替手段として, session.elrecentf の組み合わせがある.最近利用したファイルとそのカーソル位置が保持されるため,最後に訪問していたファイルを比較的簡単に復元できる.頻繁に復元するバッファには,別途キーバインドを割り当てておけば問題ない.

(when (require 'desktop nil t)
  (setq desktop-files-not-to-save "\\(^/tmp\\|^/var\\|^/ssh:\\)")
  (unless noninteractive
    (desktop-save-mode 1)))

8.32. DONE 深夜にバッファを自動整理する   obsolete

私は頻繁に再起動する派なので,使っていません.

(when (require 'midnight nil t)
  (setq clean-buffer-list-buffer-names
        (append clean-buffer-list-kill-buffer-names
                '("note.txt")))
  (setq clean-buffer-list-delay-general 1)
  (setq clean-buffer-list-delay-special 10))

9. 開発サポート

9.1. 便利キーバインド

コメントアウトとコンパイルをすぐ呼べるようにします.

(keymap-global-set "C-;" 'comment-dwim) ;; M-; is the defualt
(keymap-global-set "C-c c" 'compile)

9.2. [gist.el] Gist インターフェイス

事前に github.usergithub.oauth-token を設定します.

(autoload-if-found '(gist-mode) "gist" nil t)

9.3. [doxymacs.el] Doxygen のコメントを簡単に入力する

(when (autoload-if-found '(doxymacs-mode)
                         "doxymacs" nil t)
  (add-hook 'c-mode-common-hook #'doxymacs-mode)
  (with-eval-after-load "doxymacs"
    (setq doxymacs-doxygen-style "JavaDoc")
    (add-hook 'font-lock-mode-hook
              (lambda ()
                  (when (memq major-mode '(c-mode c++-mode))
                    (doxymacs-font-lock))))
    (keymap-set doxymacs-mode-map "C-c C-s" 'ff-find-other-file)))

9.4. [matlab.el] Matlab用の設定

;; late-init.el
(when (and (memq window-system '(mac ns))
           (> emacs-major-version 23))
  (when (autoload-if-found '(matlab-mode matlab-shell)
                           "matlab" nil t)
    (push '("\\.m$" . matlab-mode) auto-mode-alist)))

9.5. [flycheck.el] 構文エラー表示

  • auto-complete より前に hook設定しておくと余計なエラーが出ないようです.
;; (eval-when-compile
;;   (message "Loading dash...")
;;   (require 'dash))

;;;###autoload
(defun counsel-flycheck-action (obj &rest _)
  (-when-let* ((err (get-text-property 0 'tabulated-list-id obj))
               (pos (flycheck-error-pos err)) )
    (goto-char (flycheck-error-pos err))))

(defvar counsel-flycheck-history nil "History for `counsel-flycheck'")

;;;###autoload
(defun counsel-flycheck ()
  (interactive)
  (if (not (bound-and-true-p flycheck-mode))
      (message "Flycheck mode is not available or enabled")
    (ivy-read "Error: "
              (let ((source-buffer (current-buffer)))
                (with-current-buffer
                    (or (get-buffer flycheck-error-list-buffer)
                        (progn
                          (with-current-buffer
                              (get-buffer-create flycheck-error-list-buffer)
                            (flycheck-error-list-mode)
                            (current-buffer))))
                  (flycheck-error-list-set-source source-buffer)
                  (flycheck-error-list-reset-filter)
                  (revert-buffer t t t)
                  (split-string (buffer-string) "\n" t " *")))
              :action 'counsel-flycheck-action ;; (lambda (s &rest _))
              :history 'counsel-flycheck-history
              :caller 'counsel-flycheck)))
(when (autoload-if-found '(flycheck-mode)
                         "flycheck" nil t)
  (dolist (hook
           '(go-mode-hook
             js2-mode-hook
             c-mode-common-hook
             perl-mode-hook
             python-mode-hook))
    (add-hook hook #'flycheck-mode))

  (with-eval-after-load "flycheck"
    (setq flycheck-gcc-language-standard "c++14")
    (setq flycheck-clang-language-standard "c++14")
    ;; TODO: really needed?
    ;; (when (require 'flycheck-clang-tidy nil t)
    ;;   (add-hook 'flycheck-mode-hook #'flycheck-clang-tidy-setup))
    ;; http://qiita.com/senda-akiha/items/cddb02cfdbc0c8c7bc2b
    ;; (when (require 'flycheck-pos-tip nil t)
    ;;   '(custom-set-variables
    ;;     '(flycheck-display-errors-function
    ;;       #'flycheck-pos-tip-error-messages)))
    ))

;; (flycheck-add-next-checker 'javascript-jshint
;; 'javascript-gjslint)

9.6. [origami.el] 関数の折りたたみ

(when (autoload-if-found '(origami-mode origami-toggle-node)
                         "origami" nil t)
  (dolist (hook '(emacs-lisp-mode-hook c-mode-common-hook yatex-mode-hook))
    (add-hook hook #'origami-mode))

  (with-eval-after-load "origami"
    (keymap-set origami-mode-map "C-t" #'origami-toggle-node)
    (keymap-set origami-mode-map "C-u C-t" #'origami-toggle-all-nodes)))

9.7. [auto-complete-clang.el] オムニ補完

C++バッファでメソッドを補完対象とする.try-catch を使っている場合, -fcxx-exceptions オプションが必要で,これはプリコンパイルヘッダを生成する時も同じだ.ここ示す設定では, ~/.emacs.d/ 以下に stdafx.pch を生成する必要があり,以下のコマンドを用いてプリコンパイルヘッダを生成する.ヘッダファイルのパスを適切に与えれば,Boostや自作のライブラリも補完対象に設定できる.

現状では,補完直後にデフォルトの引数がすべて書き込まれてしまう.なんかうまいことしたいものだ.

clang -cc1 -x c++-header -fcxx-exceptions -std=c++11 -stdlib=libc++ -emit-pch ~/.emacs.d/stdafx.h -o ~/.emacs.d/stdafx.pch -I/opt/local/include -I/opt/local/include/netpbm -I/Users/taka/devel/icp/lib

以下の設定は,先に auto-complete.el に関する設定を読み込んでいることを前提としている. ac-clang-flags の値は, stdafx.pch の作成で使用したパラメータと合わせないとエラーが生じる可能性があります.

(when (autoload-if-found '(auto-complete ac-cc-mode-setup)
                         "auto-complete" nil t)
  (add-hook 'c-mode-common-hook #'ac-cc-mode-setup)
  (with-eval-after-load "auto-complete"
    (if (not (require 'auto-complete-clang nil t))
        (defun ac-cc-mode-setup ()
          (warn "auto-complete-clang is NOT installed"))
      (setq ac-clang-executable (executable-find "clang"))
      ;; ac-cc-mode-setup のオーバーライド
      ;; "-w" "-ferror-limit" "1"
      (defun ac-cc-mode-setup ()
        (setq ac-clang-auto-save t)
        (setq ac-clang-prefix-header "~/.emacs.d/stdafx.pch")
        (setq ac-clang-flags '("-x" "c++-header" "-fcxx-exceptions"
                               "-std=c++14" "-stdlib=libc++"
                               "-I/opt/local/include"
                               "-I/opt/local/include/netpbm"
                               "-I/Users/taka/devel/icp/lib"))
        (setq ac-sources '(ac-source-clang
                           ac-source-yasnippet
                           ac-source-gtags))))))

次のコードを hoge.cpp として保存し, vt について補完できれば, STLBoost のプリコンパイルヘッダが有効になっていることを確認できる.

#include <iostream> #include <vector> #include <boost/timer.hpp>

int main(){ std::vector<int> v; v; // ここ boost::timer t; cout << t; // ここ return 1; }

9.8. [quickrun.el] お手軽ビルド

カレントバッファで編集中のソースコードをビルド・実行して,別バッファに結果を得ます.

(when (autoload-if-found '(quickrun)
                         "quickrun" nil t)
  (with-eval-after-load "go-mode"
    (keymap-set go-mode-map "<f5>" 'quickrun))
  (with-eval-after-load "c++-mode"
    (keymap-set c++-mode-map "<f5>" 'quickrun))
  (with-eval-after-load "python-mode"
    (keymap-set python-mode-map "<f5>" 'quickrun))
  (with-eval-after-load "perl-mode"
    (keymap-set perl-mode-map "<f5>" 'quickrun))
  (with-eval-after-load "gnuplot-mode"
    (keymap-set gnuplot-mode-map "<f5>" 'quickrun)))

9.9. [ggtags.el] タグジャンプ

(when (autoload-if-found '(ggtags-mode)
                         "ggtags" nil t)
  (dolist (hook (list 'c-mode-common-hook 'python-mode-hook))
    (add-hook hook (lambda () (ggtags-mode 1))))

  (with-eval-after-load "ggtags"
    (unless (executable-find "gtags")
      (message "--- global is NOT installed in this system."))

    ;; (setq ggtags-completing-read-function t) ;; nil for helm
    (keymap-set ggtags-mode-map "M-]" nil)))

(when (autoload-if-found '(counsel-gtags-mode)
                         "counsel-gtags" nil t)
  (dolist (hook '(c-mode-hook c++-mode-hook))
    (add-hook hook 'counsel-gtags-mode))
  (with-eval-after-load "counsel-gtags"
    (custom-set-variables
     '(counsel-gtags-update-interval-second 10))))
(when (autoload-if-found '(helm-gtags-mode)
                         "helm-gtags" nil t)
    (add-hook 'c-mode-common-hook #'helm-gtags-mode)
    (add-hook 'python-mode-hook #'helm-gtags-mode)
    (with-eval-after-load "helm-gtags"
      (custom-set-variables
       '(helm-gtags-mode-name ""))))

9.10. [0xc.el] N進数変換

  • 実施頻度の高い16進数と10進数の相互変換に重宝します.
;;;###autoload
(defun my-decimal-to-hex ()
  (interactive)
  (0xc-convert 16 (word-at-point)))

;;;###autoload
(defun my-hex-to-decimal ()
  (interactive)
  (0xc-convert 10 (word-at-point)))
(when (autoload-if-found '(0xc-convert
                           0xc-convert-point
                           my-decimal-to-hex my-hex-to-decimal)
       "0xc" nil t)
  (keymap-global-set "C-c f h" '0xc-convert))

9.11. [hexl.el] バイナリファイルを開く

ビルトインの hexl-mode を使います.

(with-eval-after-load "hexl"
  (add-hook 'hexl-mode-hook 'view-mode)
  (custom-set-variables
   '(hexl-bits 8)))

9.12. [uuid.el] UUID の生成

新しい UUID を生成してカーソル位置に書き出すように my-uuid-string を定義しています.

;;;###autoload
(defun my-uuid-string ()
  (interactive)
  (insert (uuid-string)))
(autoload-if-found '(uuid-string my-uuid-string) "uuid" nil t)

9.13. [package-lint.el] MEPLA登録用Lint

MELPAへの登録を目指す emacslisp パッケージの実装で,所定の書式で記述されているかを確認できます. docstring のチェックは, M-x checkdoc でできます.

(autoload-if-found '(package-lint-current-buffer) "package-lint" nil t)

9.14. [projectile.el] ディレクトリ単位でファイル群をプロジェクト扱いする

  • プロジェクト内に限定して検索をかける時に重宝する.
  • カレントバッファがプロジェクト内のファイルの場合は,タイトルバーにプロジェクト名を出すように設定しています.
;;;###autoload
(defun my-projectile-activate ()
  (interactive)
  (setq projectile-keymap-prefix (kbd "C-c p"))
  (projectile-mode 1)
  (remove-hook 'find-file-hook #'my-projectile-activate))

;;;###autoload
(defun ad:neotree-dir (path)
  "Extension to change the frame width automatically."
  (interactive "DDirectory: ")
  (unless (neo-global--window-exists-p)
    (neotree-show))
  (neo-global--open-dir path)
  (neo-global--select-window))

;;;###autoload
(defun ad:projectile-visit-project-tags-table ()
  "Extensions to skip calling `visit-tags-table'."
  nil)

;;;###autoload
(defun my-counsel-projectile-ag ()
  "Use `counsel-projectile-ag' in a projectile project except when `dired'.
Otherwise, use `counsel-ag'."
  (interactive)
  (if (or (and (eq projectile-require-project-root 'prompt)
               (not (projectile-project-p)))
          (eq major-mode 'dired-mode))
      (counsel-ag)
    (counsel-projectile-ag)))
(when (autoload-if-found '(projectile-mode)
                         "projectile" nil t)
  (with-eval-after-load "neotree"
    ;; (advice-add 'neotree-dir :override #'ad:neotree-dir) ;; FIXME
    ;; M-x helm-projectile-switch-project (C-c p p)
    (setq projectile-switch-project-action 'neotree-projectile-action))

  (with-eval-after-load "projectile"
    (advice-add 'projectile-visit-project-tags-table :override
                #'ad:projectile-visit-project-tags-table)

    (setq projectile-mode-line-lighter "")
    (setq projectile-dynamic-mode-line nil)
    (setq projectile-tags-command "gtags")
    (setq projectile-tags-backend 'ggtags)
    (setq projectile-tags-file-name "GTAGS")

    (setq projectile-use-git-grep t)
    ;; (setq projectile-mode-line
    ;;       '(:eval (format " P:%s" (projectile-project-name))))
    (setq projectile-mode-line "")

    (setq icon-title-format
          (setq frame-title-format
                '((:eval
                   (let ((project-name (projectile-project-name)))
                     (unless (string= "-" project-name)
                       (format "(%s) - " project-name))))
                  "%b")))

    ;; counsel-projectile
    (when (require 'counsel-projectile nil t)
      (add-to-list 'counsel-projectile-switch-project-action
                   '("z" my-counsel-fzf-in-default-dir
                     "switch to fzf") t)
      (add-to-list 'counsel-projectile-find-file-action
                   '("z" my-counsel-fzf-in-default-dir
                     "switch to fzf") t)

      (setq projectile-completion-system 'ivy)
      (setq counsel-projectile-sort-files t) ;; 当該プロジェクト内リストをソート
      (setq counsel-projectile-sort-projects t) ;; プロジェクトリストをソート
      (keymap-set projectile-mode-map "C-c p" 'projectile-command-map)
      (keymap-set projectile-mode-map "C-M-f" 'my-counsel-projectile-ag)
      (counsel-projectile-mode 1)))

  (unless noninteractive
    (add-hook 'find-file-hook #'my-projectile-activate)))

9.15. [relint.el] elispの正規表現用のlinter

正規表現をチェックしてくれます.

(autoload-if-found '(relint-current-buffer) "relint" nil t)

9.16. [magit.el] Gitクライアント

Emacs の中で Git リポジトリを操作できます. magit-repository-directories を設定しておけば,管理しているリポジトリの一覧を表示できます. helmivy と連携していれば,絞り込みも簡単です.さらに, M-x magit-list-repositories を実行すれば,各リポジトリのステータス全体を一覧表示できます.ただし,調査対象のリポジトリ数が多ければ,一覧表示に時間がかかります.

;;;###autoload
(defun ad:magit-mode-bury-buffer (&optional _bury)
  (when (fboundp 'dimmer-on)
    (setq my-dimmer-mode t)
    (dimmer-on)
    (redraw-frame)))
(when (autoload-if-found '(magit-status ad:magit-mode-bury-buffer)
                         "magit" nil t)
  (keymap-global-set "C-c m" 'magit-status)
  (with-eval-after-load "magit"
    (when (fboundp 'dimmer-off)
      (add-hook 'magit-status-mode-hook 'dimmer-off))
    (when (fboundp 'magit-mode-bury-buffer)
      (advice-add 'magit-mode-bury-buffer :before #'ad:magit-mode-bury-buffer))
    (when (and (boundp 'magit-completing-read-function)
               (require 'ivy nil t))
      ;; ivy を使う
      (setq magit-completing-read-function 'ivy-completing-read))
    (when (boundp 'magit-repository-directories)
      (setq magit-repository-directories
            '(("~/devel/git" . 1)
              ("~/devel/mygit" . 1))))))

9.17. TODO [EditorConfig] コードスタイルの強制

;;;###autoload
(defun my-editorconfig-activate ()
  (if (and (executable-find "editorconfig")
           (require 'editorconfig nil t)
           (require 'editorconfig-core nil t)  )
      (editorconfig-mode 1)
    (message "Editorconfig is not installed."))
  (remove-hook 'find-file-hook #'my-editorconfig-activate))
(add-hook 'find-file-hook #'my-editorconfig-activate)

例えば,次のようなファイルを共有プロジェクトに保存しておきます. .editorconfig として配置します.

root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true

9.18. TODO [cov] カバレッジの状態をフリンジで確認

gcov の結果をフリンジに表示します.

(autoload-if-found '(cov-mode) "cov" nil t)

9.19. TODO [format-all.el] コード整形

様々なプログラミング言語に対して,共通のコード整形のトリガー( M-x format-all-buffer )を提供します.整形するために使うフォーマッタは,原則的に言語ごとに異なる外部ツールを呼び出すので,必要に応じてそれらのツールを別途インストールします.

(autoload-if-found '(format-all-mode) "format-all" nil t)

9.20. TODO [emr.el] リファクタリング

(when (require 'emr nil t)
  (keymap-set prog-mode-map "M-<return>" 'emr-show-refactor-menu)
  (emr-initialize))

9.21. TODO [rmsbolt.el] アセンブリをバッファにリアルタイム表示

;; late-init.el
(autoload-if-found '(rmsbolt-mode) "rmsbolt" nil t)

9.22. TODO [semantic-refactor.el] リファクタリングツール

9.23. TODO [company.el] 続・自動補完機能

auto-complete.el がメンテナンスモードに入ったので company に移行しました.

company-org-block は,http://xenodium.com/emacs-org-block-company-completion/ で公開されている情報でしたが,パッケージ化されて,MELPAから入手できるようになりました.

;; utility.el
;;;###autoload
(defun my-company-activate ()
  (remove-hook 'emacs-lisp-mode-hook #'my-company-activate)
  (remove-hook 'org-mode-hook #'my-company-activate)
  (require 'company nil t))

;;;###autoload
(defun ad:company-idle-begin (f buf win tick pos)
  (unless (and (boundp 'ns-put-text-p) ns-put-text-p)
    (funcall f buf win tick pos)))

;;;###autoload
(defun ad:company-pseudo-tooltip--ujofwd-on-timer (f command)
  (unless (and (boundp 'ns-put-text-p) ns-put-text-p)
    (funcall f command)))
;; late-init.el
(add-hook 'emacs-lisp-mode-hook #'my-company-activate)
(add-hook 'org-mode-hook #'my-company-activate)

(with-eval-after-load "company"
  (keymap-set company-active-map "C-n" 'company-select-next)
  (keymap-set company-active-map "C-p" 'company-select-previous)
  (keymap-set company-active-map "<tab>" 'company-complete-selection)
  (keymap-set company-search-map "C-n" 'company-select-next)
  (keymap-set company-search-map "C-p" 'company-select-previous)
  ;; To complete file path, move `company-files' to the fist item of the list
  (delq 'company-files company-backends)

  (add-to-list 'company-backends 'company-files)
  (when (require 'company-org-block nil t)
    (setq company-org-block-edit-style 'inline) ;; 'auto, 'prompt, or 'inline
    (setq company-org-block-auto-indent nil)
    (add-to-list 'company-backends 'company-org-block))

  ;; 補完候補に番号を表示
  (setq company-show-numbers t)
  ;; 補完候補を出すまでの猶予
  (setq company-idle-delay 0.8)
  (setq company-tooltip-idle-delay 0.8)
  (global-company-mode)
  (when (require 'company-quickhelp nil t)
    (company-quickhelp-mode))

  (advice-add 'company-idle-begin :around #'ad:company-idle-begin)
  ;; (advice-add 'company-pseudo-tooltip--ujofwd-on-timer :around
  ;;             #'ad:company-pseudo-tooltip--ujofwd-on-timer)

  (when (boundp 'mac-ime-before-put-text-hook)
    ;; 補完候補が表示されたタイミングで入力を続けたら,補完候補を消す.
    (add-hook 'mac-ime-before-put-text-hook #'company-cancel)))

9.24. TODO [corfu.el] 続々・自動補完機能

company から corfu に移行してみます.関連して, corfu-prescient.el, kind-icon.el, cape.el, org-block-capf.el も追加しています.一般的には, orderless.el も同時に使うようですが,まだ利点が理解できていません.

;;;###autoload
(defun ad:minibuffer-complete (f)
  "Enforce to use `completion--in-region' when completing in minibuffer."
  (let ((completion-in-region-function #'completion--in-region))
    (funcall f)))

;;;###autoload
(defun my-advice-minibuffer-complete ()
  (advice-add 'minibuffer-complete :around #'ad:minibuffer-complete)
  (remove-hook 'minibuffer-setup-hook #'my-advice-minibuffer-complete))

;;;###autoload
(defun my-load-cape-modules-for-org ()
  ;; 1st: begin_src emacs-lisp..end_src 内でelispを補完可能にする.
  (add-hook 'completion-at-point-functions #'cape-elisp-block -2 'local)
  ;; 2nd: システムのファイルパスを補完可能にする
  (add-hook 'completion-at-point-functions #'cape-file -1 'local)
  ;; 3rd: 辞書 FIXME should be done by manually?
  (add-hook 'completion-at-point-functions #'cape-dict nil 'local))

;;;###autoload
(defun my-corfu-insert-separator (ARG)
  "Use C-SPC to insert the separator."
  (interactive "P")
  (if (corfu--continue-p) ;; (> corfu--total 0)
      (insert corfu-separator)
    (set-mark-command ARG)))
(when (autoload-if-found '(corfu-mode) "corfu" nil t)
  (add-hook 'emacs-lisp-mode-hook #'corfu-mode)
  (add-hook 'org-mode-hook #'corfu-mode)
  (add-hook 'sh-mode #'corfu-mode)
  (add-hook 'minibuffer-setup-hook #'my-advice-minibuffer-complete)

  (with-eval-after-load "corfu"
    (custom-set-variables
     ;; '(corfu-auto-prefix 2)
     '(corfu-min-width 20)
     '(corfu-count 5)
     '(corfu-auto-delay 0.5)
     '(corfu-auto t))

    (keymap-set corfu-mode-map "C-SPC" #'corfu-insert-separator)

    (advice-add 'corfu-insert-separator :override #'my-corfu-insert-separator)

    (when (require 'corfu-prescient nil t)
      (corfu-prescient-mode 1))

    (when (require 'kind-icon nil t)
      (setq kind-icon-default-face 'corfu-default)
      (add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter))))
(when (autoload-if-found '(org-block-capf-add-to-completion-at-point-functions)
                         "org-block-capf" nil t)
  (add-hook 'org-mode-hook
            #'org-block-capf-add-to-completion-at-point-functions -1)

  (with-eval-after-load "org-block-capf"
    (setq org-block-capf-edit-style 'inline)
    (setq org-block-capf-auto-indent nil)))

(when (autoload-if-found '(cape-elisp-block cape-file cape-dict)
                         "cape" nil t)
  (add-hook 'org-mode-hook #'my-load-cape-modules-for-org -2))

ターミナルで使用するには corfu-terminal-mode が必要になります.

(unless (display-graphic-p)
  (when (autoload-if-found '(corfu-terminal-mode) "corfu-terminal" nil t)
    (defvar corfu-terminal-mode nil) ;; To suppress showing a warning
    (add-hook 'emacs-lisp-mode-hook #'corfu-terminal-mode)
    (add-hook 'org-mode-hook #'corfu-terminal-mode)))

9.25. TODO [vterm.el] vterm を使う

vterm を使うとバッファをターミナルとして使えます.通常では C-n/C-p でコマンド履歴を辿ってしまうので,実行結果などをコピーして別バッファに貼り付けたい場合は, vterm-copy-mode (C-c C-t) を使えばよいです.

macOS の場合は brew で libvterm をインストールすれば, vterm を使えます.

(autoload-if-found '(vterm) "vterm"  nil t)

9.26. DONE [auto-complete.el] 自動補完機能   obsolete

http://cx4a.org/software/auto-complete/manual.ja.html

  • 辞書データを使う( ac-dictionary-directories
  • auto-complete.el, auto-complete-config.el, fuzzy.el, popup.el を使う.
  • 日本語マニュアル
  • ac-auto-start を 4 にしておけば,3文字までは <tab> を yasnippet に渡せる.

Org-mode ユーザにとって <tab> は非常に重要なコマンド.そこに auto-completeyasnippet<tab> を奪いに来るので,住み分けが重要になる.=ac-auto-start= を=4=にすると,<s=<tab>= によるソースブロックの短縮入力を yasnippet で実行できる(この目的だけならば=3=を指定してもいい).<sys などと4文字入力すると,=auto-complete= が動いて <system> などを補完してくれる.もちろん,見出しで <tab> を押すときには,ツリーの表示/非表示の切り替えになる.

情報源については,オンラインマニュアルを参照のこと.

auto-complete が正しく効いているかは,バッファ内で適当にパスを打ち込んで,補完候補が表示されるかで判定判定できると思います( /home を入力とか)

(when (autoload-if-found '(ac-default-setup ac-org-mode-setup)
                         "auto-complete" nil t)
  (dolist (hook
           (list 'org-mode-hook 'python-mode-hook
                 'perl-mode-hook 'objc-mode-hook))
    (add-hook hook #'ac-default-setup))

  ;; *scratch* バッファでは無効化
  (add-hook 'lisp-mode-hook
            (lambda () (unless (equal "*scratch*" (buffer-name))
                         (ac-default-setup))))
  (add-hook 'org-mode-hook #'ac-org-mode-setup)

  (with-eval-after-load "auto-complete"
    (require 'auto-complete-config nil t)
    (ac-config-default)
    ;; 追加のメジャーモードを設定
    (add-to-list 'ac-modes 'objc-mode)
    (add-to-list 'ac-modes 'org-mode)
    (add-to-list 'ac-modes 'latex-mode)
    (require 'ac-math nil t)

    ;; ac-modes にあるメジャーモードで有効にする
    ;;lisp, c, c++, java, perl, cperl, python, makefile, sh, fortran, f90
    (global-auto-complete-mode t)
    ;; 辞書
    (add-to-list 'ac-dictionary-directories "~/.emacs.d/ac-dict")
    ;; history
    (setq ac-comphist-file "~/Dropbox/config/ac-comphist.dat")
    ;; n文字以上で補完表示する("<s <tab>" の場合 yasnippet が呼ばれる)
    (setq ac-auto-start 4)
    ;; n秒後にメニューを表示
    (setq ac-auto-show-menu 2.0)
    ;; ツールチップの表示
    (setq ac-use-quick-help t)
    (setq ac-quick-help-delay 2.0)
    (setq ac-quick-help-height 10)
    ;; C-n/C-p でメニューをたどる
    (setq ac-use-menu-map t)
    ;; <tab> で補完(org-mode でも効くようにする)
    (keymap-set ac-completing-map "<tab>" 'ac-complete)
    ;; RET での補完を禁止
    (keymap-set ac-completing-map "RET" nil)
    ;; 補完メニューの表示精度を高める
    (setq popup-use-optimized-column-computation nil)
    ;;(setq ac-candidate-max 10)

    (defun ac-org-mode-setup ()
      ;;            (message " >> ac-org-mode-setup")
      (setq ac-sources '(
                         ac-source-abbrev ; Emacs の略語
                         ;; ac-source-css-property ; heavy
                         ac-source-dictionary ; 辞書
                         ac-source-features
                         ac-source-filename
                         ac-source-files-in-current-dir
                         ac-source-functions
                         ;; ac-source-gtags
                         ;; ac-source-imenu
                         ;; ac-source-semantic
                         ac-source-symbols
                         ac-source-variables
                         ;; ac-source-yasnippet
                         )))

    (defun ac-default-setup ()
      ;;            (message " >> ac-default-setup")
      ;; ac-source-words-in-same-mode-buffers
      (setq ac-sources '(ac-source-filename
                         ac-source-abbrev
                         ac-source-dictionary
                         )))))

9.27. DONE [hideshowvis.el] 関数の表示/非表示   obsolete

(when (and (memq window-system '(mac ns))
           (> emacs-major-version 23))
  (when (autoload-if-found '(hideshowvis-enable hideshowvis-minor-mode)
                           "hideshowvis" nil t)
    (dolist (hook (list 'perl-mode-hook 'c-mode-common-hook))
      (add-hook hook #'hideshowvis-enable))

    (add-hook 'emacs-lisp-mode-hook
              (lambda () (unless (equal "*scratch*" (buffer-name))
                             (hideshowvis-enable))))

    (with-eval-after-load "hideshowvis"
      (keymap-set hideshowvis-mode-map "C-(" 'hs-hide-block)
      (keymap-set hideshowvis-mode-map "C-)" 'hs-show-block))))

10. Org Mode

10.1. 基本設定

Org Mode は巨大なパッケージなので,設定項目が必然的に増えます.一度に全部を設定しようとせず,必須機能と見た目の設定から入り,興味があり使いたい機能の設定へと徐々に拡張するのが近道です.

C-M-o は,本来, split-line にアサインされています.

init-org.el として書き出し,遅延読み込みしています.

;;;###autoload
(defun my-org-modules-activate ()
  (interactive)
  (if (and (featurep 'org-tempo)
           (featurep 'org-id))
      (message "org-modules are previously loaded.")
    (message "Loading org-modules...")
    (setq org-modules my-org-modules) ;; revert to the original value
    ;; モジュールの追加
    (add-to-list 'org-modules 'org-id)
    (with-eval-after-load "org-agenda"
      ;; org-agenda を読んでしまうので org-mode 開始時には読み込ませない
      (add-to-list 'org-modules 'org-habit)) ;; require org and org-agenda
    (when (version< "9.1.4" (org-version))
      (add-to-list 'org-modules 'org-tempo))
    (when (require 'ol-bookmark nil t)
      ;; [[bookmark:hoge][hogehoge]] 形式のリンクを有効化
      (add-to-list 'org-modules 'ol-bookmark)
      (setq bookmark-save-flag 4) ;; N回 bookmark を操作したら保存
      ;; `bookmark-default-file' の読み込み
      (bookmark-maybe-load-default-file))

    ;; 不必要なモジュールの読み込みを停止する
    (delq 'ol-bbdb org-modules)
    (delq 'ol-irc org-modules)
    (delq 'ol-mhe org-modules)
    (delq 'ol-docview org-modules)
    ;; Reload
    (org-load-modules-maybe t)
    (org-element-cache-reset 'all) ;; FIXME use `custom-set-variables'
    (message "Loading org-modules...done")))

;;;###autoload
(defun my-open-default-org-file ()
  (interactive)
  (my-show-org-buffer "next.org")
  ;; (run-hooks 'org-mode-hook) ;; FIXME
)
;; ホームポジション的な Orgファイルを一発で開きます.
(keymap-global-set "C-M-o" #'my-open-default-org-file)
;; テキストファイルを Org Mode で開きます.
(push '("\\.txt$" . org-mode) auto-mode-alist)

;; Font lock を使う
(add-hook 'org-mode-hook #'turn-on-font-lock)

(keymap-global-set "C-c r" 'org-capture)
(keymap-global-set "C-c l" 'org-store-link)
(keymap-global-set "C-c a" 'org-agenda)

(with-eval-after-load "org"
  (defvar my-org-modules org-modules) ;; Tricky!!
  ;; (setq org-modules-loaded t) ;; not a good way
  (setq org-modules nil)
  (unless noninteractive
    (run-with-idle-timer (+ 8 my-default-loading-delay)
                         nil #'my-org-modules-activate)) ;; will take 350[ms]

  ;; タイトルを少し強調
  (custom-set-faces
   '(org-document-title ((t (:foreground "RoyalBlue1" :bold t :height 1.2))))
   '(org-document-info ((t (:foreground "DodgerBlue1" :height 1.0)))))

  ;; 関連モジュールの読み込み
  (autoload 'org-eldoc-load "org-eldoc" nil t)
  (defun my-org-eldoc-load ()
    "Set up org-eldoc documentation function."
    (interactive)
    (add-function :before-until (local 'eldoc-documentation-function)
                  #'org-eldoc-documentation-function))
  ;; 少なくとも org 9.5 では問題が発生しなくなったので,advice 停止.
  ;; (advice-add 'org-eldoc-load :override #'my-org-eldoc-load)
  (add-hook 'org-mode-hook #'org-eldoc-load)

  ;; org ファイルの集中管理
  (setq org-directory (concat (getenv "SYNCROOT") "/org/"))

  ;; org-store-link で heading に自動的に挿入される id を使う
  (setq org-id-link-to-org-use-id t)

  ;; アーカイブファイルの名称を指定
  (setq org-archive-location "%s_archive::")

  ;; タイムスタンプによるログ収集設定 DONE 時に CLOSED: を記入.
  (setq org-log-done 'time) ; 'time 以外に,'(done), '(state) を指定できる

  ;; ログをドロアーに入れる
  (setq org-log-into-drawer t)

  ;; indent を electric-indent-mode の振る舞いに合わせる
  ;; (setq org-adapt-indentation t) ;; t の場合,ドロアがインデントされる.

  ;; Set checksum program path for windows
  (when (eq window-system 'w32)
    (setq org-mobile-checksum-binary (concat (getenv "SYNCROOT") "/do/cksum.exe")))

  ;; Set default table export format
  (setq org-table-export-default-format "orgtbl-to-csv")

  ;; Toggle inline images display at startup
  (setq org-startup-with-inline-images t)

  ;; dvipng
  (setq org-export-with-LaTeX-fragments t)

  ;; 数式をハイライト
  (setq org-highlight-latex-and-related '(latex entities))

  ;; orgバッファ内の全ての動的ブロックを保存直前に変更する
  ;; (add-hook 'before-save-hook #'org-update-all-dblocks)

  ;; アンダースコアをエクスポートしない(_{}で明示的に表現できる)
  (setq org-export-with-sub-superscripts nil)

  ;; #+options: \n:t と同じ
  (setq org-export-preserve-breaks t)

  ;; タイマーの音
  ;; (lsetq org-clock-sound "");

  ;; org-clock の計測時間をモードラインではなくタイトルに表示する
  (setq org-clock-clocked-in-display 'frame-title)

  ;; 1分未満は記録しない
  (setq org-clock-out-remove-zero-time-clocks t)

  ;; 再起動後に clock を復帰させる(clock-out で抜けない限り終了中の期間も計上されてしまう)
  ;; check also org-clock-persist in org-clock.el
  (org-clock-persistence-insinuate)

  ;; org-clock-out 時にステータスを変える(also configure org-todo-keywords)
  (defun my-promote-todo-revision (state)
    (cond ((member state '("TODO")) "REV1")
          ((member state '("REV1")) "REV2")
          ((member state '("REV2")) "REV3")
          (t state)))
  ;; (setq org-clock-out-switch-to-state #'my-promote-todo-revision)

  ;; undo 時に reveal して表示を改善する
  ;; (defun ad:org:undo (&optional _ARG)
  ;;   (when (and (eq major-mode 'org-mode)
  ;;              (not (org-before-first-heading-p)))
  ;;     (org-overview)
  ;;     (org-reveal)
  ;;     (org-cycle-hide-drawers 'all)
  ;;     (org-show-entry)
  ;;     (show-children)
  ;;     (org-show-siblings)))
  ;; (advice-add 'undo :after #'ad:org:undo)

  ;; 非表示状態の領域への書き込みを防ぐ
  ;; "Editing in invisible areas is prohibited, make them visible first"
  (setq org-catch-invisible-edits 'show-and-error)
  (defun ad:org-return (f &optional arg)
    "An extension for checking invisible editing when you hit the enter."
    (interactive "P")
    (org-check-before-invisible-edit 'insert)
    (apply f arg))
  (advice-add 'org-return :around #'ad:org-return)

  ;; ブリッツにアルファベットを使う
  (setq org-list-allow-alphabetical t)

  ;; - を優先.親のブリッツ表示を継承させない
  (setq org-list-demote-modify-bullet
        '(("+" . "-")
          ("*" . "-")
          ("1." . "-")
          ("1)" . "-")
          ("A)" . "-")
          ("B)" . "-")
          ("a)" . "-")
          ("b)" . "-")
          ("A." . "-")
          ("B." . "-")
          ("a." . "-")
          ("b." . "-")))

  ;; 完了したタスクの配色を変える
  ;; https://fuco1.github.io/2017-05-25-Fontify-done-checkbox-items-in-org-mode.html
  (font-lock-add-keywords
   'org-mode
   `(("^[ \t]*\\(?:[-+*]\\|[0-9]+[).]\\)[ \t]+\\(\\(?:\\[@\\(?:start:\\)?[0-9]+\\][ \t]*\\)?\\[\\(?:X\\|\\([0-9]+\\)/\\2\\)\\][^\n]*\n\\)"
      1 'org-headline-done prepend))
   'append)

  ;; プロパティ等を自動的閉じる.
  (defun my-org-hide-drawers ()
    "Hide all drawers in an org tree."
    (interactive)
    (save-mark-and-excursion
      (beginning-of-line)
      (unless (looking-at-p org-drawer-regexp)
        (org-cycle-hide-drawers 'subtree))))
  (add-hook 'org-tab-first-hook 'my-org-hide-drawers)

  ;; CSV指定でテーブルを出力する.
  (defun my-org-table-export ()
    (interactive)
    (org-table-export nil "orgtbl-to-csv"))

  ;; すべてのチェックボックスの cookies を更新する
  (defun my-do-org-update-staistics-cookies ()
    (interactive)
    (message "Update statistics...")
    (org-update-statistics-cookies 'all)
    (let ((inhibit-message t)
          (message-log-max nil))
      (save-buffer))
    (message "Update statistics...done"))

  (keymap-set org-mode-map "C-c f 2" 'my-do-org-update-staistics-cookies)

  ;; C-c & が yasnippet にオーバーライドされているのを張り替える
  (keymap-set org-mode-map "C-c 4" 'org-mark-ring-goto)

  ;; (org-transpose-element) が割り当てられているので取り返す.
  (org-defkey org-mode-map "\C-\M-t" 'beginning-of-buffer))

(with-eval-after-load "ox"
  (add-to-list 'org-modules 'ox-odt)
  (add-to-list 'org-modules 'ox-org)
  (add-to-list 'org-modules 'ox-json)) ;; FIXME

(with-eval-after-load "org-tempo"
  ;; 空行のとき "<" をインデントさせない
  (defun ad:org-tempo-complete-tag (f &rest arg)
    (if (save-excursion
          (beginning-of-line)
          (looking-at "<"))
        (let ((indent-line-function 'ignore))
          (apply f arg))
      (apply f arg)))
  (advice-add 'org-tempo-complete-tag :around #'ad:org-tempo-complete-tag))
;; (Thanks to @conao3)
;; but when using `flet', byte-compile will warn a malformed function
;; and using `cl-flet' will not provide us the expected result...
;; (when (require 'cl-lib nil t)
;;   (defun ad:org-tempo-complete-tag (f &rest arg)
;;     (if (save-excursion
;;           (beginning-of-line)
;;           (looking-at "<"))
;;         (cl-flet ((indent-according-to-mode () #'ignore))
;;           (apply f arg))
;;       (apply f arg)))
;;   (advice-add 'org-tempo-complete-tag :around #'ad:org-tempo-complete-tag))

(with-eval-after-load "org-tempo"
  (defun my-org-tempo-add-block (entry)
    "Add block entry from `org-structure-template-alist'."
    (let* ((key (format "<%s" (car entry)))
                 (name (cdr entry))
                 (special nil)) ;; FIXED
      (tempo-define-template
       (format "org-%s" (replace-regexp-in-string " " "-" name))
       `(,(format "#+begin_%s%s" name (if special " " ""))
               ,(when special 'p) '> n '> ,(unless special 'p) n
               ,(format "#+end_%s" (car (split-string name " ")))'
               >)
       key
       (format "Insert a %s block" name)
       'org-tempo-tags)))
  ;; 更新
  (advice-add 'org-tempo-add-block :override #'my-org-tempo-add-block)
  ;; 反映
  (org-tempo-add-templates))

(with-eval-after-load "org-clock"
  ;; nil or 'history ならば,org-onit が org-clock-out を実行する.
  (setq org-clock-persist 'history) ;; {nil, t, 'clock, 'history}
  (setq org-clock-in-resume t)
  (setq org-clock-persist-query-resume nil)

  (advice-add 'org-clock-load :around #'ad:suppress-message)

  ;; 終了時に clock を止める.
  (defun my-org-clock-out-and-save-when-exit ()
    "Save buffers and stop clocking when kill emacs."
    (when (org-clocking-p)
      (org-clock-out)
      (save-some-buffers t)))
  ;; implemented in `org-onit.el'. No need to hook this.
  ;; (add-hook 'kill-emacs-hook #'my-org-clock-out-and-save-when-exit)
  )

(with-eval-after-load "org-table"
  ;; エコー表示前に保存する
  (defun ad:org-table-field-info (_arg)
    (save-buffer))
  (advice-add 'org-table-field-info :before #'ad:org-table-field-info))

10.2. contribution を使う

(setq load-path (append '("~/devel/git/org-mode/contrib/lisp") load-path))

10.3. iCal との連携

;; ~/Dropbox/Public は第三者に探索される可能性があるので要注意
;; default = ~/org.ics
;; C-c C-e i org-export-icalendar-this-file
;; C-c C-e I org-export-icalendar-all-agenda-files
;; C-c C-e c org-export-icalendar-all-combine-agenda-files
(when (autoload-if-found '(my-ox-icalendar
                           my-async-ox-icalendar my-ox-icalendar-cleanup)
                         "ox-icalendar" nil t)
  (with-eval-after-load "org"
    (keymap-set org-mode-map "C-c f 1" 'my-ox-upload-icalendar))

  (with-eval-after-load "ox-icalendar"
    (defvar org-ical-file-in-orz-server nil) ;; see private.el

    ;; 生成するカレンダーファイルを指定
    ;; 以下の設定では,このファイルを一時ファイルとして使う(削除する)
    (setq org-icalendar-combined-agenda-file "~/Desktop/org-ical.ics")

    ;; iCal の説明文
    (setq org-icalendar-combined-description "OrgModeのスケジュール出力")

    ;; カレンダーに適切なタイムゾーンを設定する(google 用には nil が必要)
    (setq org-icalendar-timezone "Asia/Tokyo")

    ;; DONE になった TODO はアジェンダから除外する
    (setq org-icalendar-include-todo t)

    ;; 通常は,<>--<> で区間付き予定をつくる.非改行入力で日付がNoteに入らない
    (setq org-icalendar-use-scheduled '(event-if-todo))

    ;; DL 付きで終日予定にする:締め切り日(スタンプで時間を指定しないこと)
    ;; (setq org-icalendar-use-deadline '(event-if-todo event-if-not-todo))
    (setq org-icalendar-use-deadline '(event-if-todo))

    (defun my-ox-upload-icalendar ()
      (interactive)
      (when (and org-ical-file-in-orz-server
                 (eq system-type 'darwin))
        (if (require 'async nil t)
            (my-async-ox-icalendar)
          (my-ox-icalendar))))

    (defun my-ox-icalendar ()
      (let ((message-log-max nil)
            (org-agenda-files '("~/Dropbox/org/org-ical.org")))
        ;; org-icalendar-export-to-ics を使うとクリップボードが荒れる
        (org-icalendar-combine-agenda-files))
      ;; 自サーバにアップロード
      (message "Uploading...")
      (if (eq 0 (shell-command
                 (concat "scp -o ConnectTimeout=5 "
                         org-icalendar-combined-agenda-file " "
                         org-ical-file-in-orz-server)))
          (message "Uploading...done")
        (message "Uploading...miss!"))
      (my-ox-icalendar-cleanup))

    (defun my-async-ox-icalendar ()
      (message "[async] Uploading...")
      (async-start
       `(lambda ()
          (when (and (load "~/.emacs" nil t)
                     (load "~/.emacs.d/lisp/init-org.el" nil t)
                     (require 'org nil t)
                     (require 'org-agenda nil t))
            (setq org-agenda-files '("~/Dropbox/org/org-ical.org"))
            (if (file-exists-p
                 (expand-file-name org-icalendar-combined-agenda-file))
                1
              (let ((ical (org-icalendar-combine-agenda-files))
                    (result (shell-command
                             (concat "scp -o ConnectTimeout=5 "
                                     ',org-icalendar-combined-agenda-file " "
                                     ',org-ical-file-in-orz-server))))
                (my-ox-icalendar-cleanup)
                result))))
       (lambda (result)
         (unless (active-minibuffer-window)
           (message (format "[async] Uploading...%s"
                            (cond ((eq result 0) "done")
                                  ((eq result 1) "skipped")
                                  (t "miss!"))))))))

    (defun my-ox-icalendar-cleanup ()
      (interactive)
      (when (file-exists-p
             (expand-file-name org-icalendar-combined-agenda-file))
        (shell-command-to-string
         (concat "rm -rf " org-icalendar-combined-agenda-file))))))

10.4. スピードコマンド

慣れてくると Org Mode の通常キーバインドでは満足できなくなります.コンテクストに応じて適切なキーバインドを設定するのが Emacs の醍醐味ですから,そういう欲求が出てきたらスピーコマンドの出番です.シングルキーで各ツリーのステータスを変更したり,任意のコマンドを呼び出せます.

(with-eval-after-load "org"
        (setq org-use-speed-commands t)

        (when (version< (org-version) "9.4.6")
                (defvaralias 'org-speed-commands 'org-speed-commands-user))

        ;; "C"(org-shifttab) をオーバーライド
        (add-to-list 'org-speed-commands '("C" org-copy-subtree))
        (add-to-list 'org-speed-commands '("d" my-done-with-update-list))
        ;; (add-to-list 'org-speed-commands '("S" call-interactively 'widen))
        (add-to-list 'org-speed-commands
                                                         '("D" my-org-todo-complete-no-repeat "DONE"))
        ;; (add-to-list 'org-speed-commands '("N" org-shiftmetadown))
        ;; (add-to-list 'org-speed-commands '("P" org-shiftmetaup))
        (add-to-list 'org-speed-commands '("H" my-hugo-export-upload))
        (add-to-list 'org-speed-commands '("h" org-hugo-export-wim-to-md))
        (add-to-list 'org-speed-commands '("E" my-export-subtree-as-html))
        (add-to-list 'org-speed-commands '("." my-org-deadline-today))
        (add-to-list 'org-speed-commands '("!" my-org-default-property))
        (add-to-list 'org-speed-commands '("y" my-org-yank))
        (add-to-list 'org-speed-commands '("x" my-org-move-subtree-to-the-last))
        (add-to-list 'org-speed-commands
                                                         '("$" call-interactively 'org-archive-subtree))

        ;; done にして,apptを更新する
        (defun my-done-with-update-list ()
                (interactive)
                (org-todo "DONE")
                (my-org-agenda-to-appt))

        ;; 周期タクスを終了させます.
        (defun my-org-todo-complete-no-repeat (&optional ARG)
                (interactive "P")
                (when (org-get-repeat)
                        (org-cancel-repeater))
                (if (eq (current-buffer) org-agenda-buffer)
              (org-agenda-todo ARG)
                        (org-todo ARG)))

        (defun my-org-replace-punc-in-buffer ()
                "Replace \",\" and \".\" with \"、\" and \"。\" in a buffer."
                (interactive)
                (goto-char (point-min))
                (while (re-search-forward "\\(\\)\\|\\(\\)" nil :noerror)
                        (let ((w (match-string-no-properties 0)))
                                (cond ((equal w ",") (replace-match "、"))
                                                        ((equal w ".") (replace-match "。"))))))

        (defun my-org-replace-punc-in-tree ()
                "Replace \",\" and \".\" with \"、\" and \"。\" in an org tree."
                (interactive)
                (org-back-to-heading t)
                (let* ((element (org-element-at-point))
                                         (begin (org-element-property :begin element))
                                         (end (org-element-property :end element)))
                        (when (eq (org-element-type element) 'headline)
                                (goto-char begin)
                                (while (re-search-forward "\\(\\)\\|\\(\\)" end :noerror)
                                        (let ((w (match-string-no-properties 0)))
                                                (cond ((equal w ",") (replace-match "、"))
                                                                        ((equal w ".") (replace-match "。")))))
                                (goto-char begin))))

        ;; Hugo の記事を書き出し&アップロード
        (defun my-hugo-export-upload ()
                "Export subtree for Hugo and upload the engty."
                (when (member (buffer-name) '("imadenale.org" "archive.org"))
                        (if (not (org-entry-is-done-p))
                                        (message "The state of the entry is not \"DONE\" yet.")
                                (my-org-replace-punc-in-tree)
                                (save-buffer)
                                ;; (let ((outfile (org-hugo-export-wim-to-md)))
                                ;;       (sit-for 2)
                                ;;       (when (and outfile
                                ;;                                                      (file-exists-p outfile))
                                ;;               (switch-to-buffer
                                ;;                      (find-file-noselect outfile)
                                ;;                      (my-org-replace-punc-in-buffer))))
                                (org-hugo-export-wim-to-md)
                                (let ((command "/Users/taka/Dropbox/scripts/push-hugo.sh")
                                                        (filename (org-entry-get (point) "EXPORT_FILE_NAME"))
                                                        (exported (format "[ox-hugo] \"%s\" has been exported."
                                                                                                                                (nth 4 (org-heading-components)))))
                                        (when filename
                                                ;; (when (file-exists-p (concat outfile ".md"))
                                                ;;       (switch-to-buffer
                                                ;;              (find-file-noselect (concat outfile ".md"))
                                                ;;              (my-org-replace-punc-in-buffer)
                                                ;;              (save-buffer)))
                                                (save-excursion
                                                        (save-restriction
                                                                (outline-up-heading 1)
                                                                (setq filename
                                                                                        (concat (nth 4 (org-heading-components)) "/" filename))
                                                                (setq command (concat command " -e " (downcase filename)))))
                                                (message "[hugo] %s" command)
                                                (if (require 'async nil t)
                                                                (progn
                                                                        (message "%s\n[async] Uploading..." exported)
                                                                        (async-start
                                                                         `(lambda () (shell-command-to-string ',command))
                                                                         `(lambda (result)
                                                                                        (message "%s\n[async] Uploading...%s"
                                                                                                                         ',exported (when result "done"))
                                                                                        (message "[log] %s" result))))
                                                        (message "%s\nUploading..." exported)
                                                        (message "%s" (shell-command-to-string command))
                                                        (message "%s\nUploading...done" exported)))))))

        ;; カーソル位置のサブツリーをデスクトップにHTMLエクスポートする
        (defun my-export-subtree-as-html ()
                (interactive)
                (let ((file "~/Desktop/note.html"))
                        (org-export-to-file 'html file nil t)
                        (org-open-file file)))

        ;; 締切を今日にする.agenda から起動したカレンダー内では "C-." でOK(標準)
        (defun my-org-deadline-today ()
                (when (org-entry-is-todo-p)
                        (let ((date (org-entry-get (point) "DEADLINE"))
                                                (today (format-time-string "%F")))
                                (org-deadline 'deadline
                                                                                        (if date
                                                                                                        (format "<%s%s"
                                                                                                                                        today
                                                                                                                                        (substring date 11 (string-width date)))
                                                                                                (format "<%s>" today))))))

        ;; 現在のツリーを畳んでから同じレベルの最後の要素として移動する
        (defcustom my-org-move-subtree-to-the-last-after-hook nil""
                :type 'hook :group 'org)
        (defun my-org-move-subtree-to-the-last ()
                "Move the current heading to the last one of the same level."
                (interactive)
                (let ((cnt 0) beg)
                        (org-back-to-heading)
                        (outline-hide-subtree)
                        (setq beg (point))
                        (while (and (funcall 'org-get-next-sibling)
                                                                        (looking-at org-outline-regexp))
                                (setq cnt (1+ cnt)))
                        (goto-char beg)
                        (when (> cnt 0)
                                (org-move-subtree-down cnt)
                                (goto-char beg)))
                (run-hooks 'my-org-move-subtree-to-the-last-after-hook)))

10.5. face 関連

Orgバッファは日々のタスク管理で閲覧する頻度が高くと期間が長いです.好みの状態にカスタマイズして使いましょう.

(with-eval-after-load "org"
  ;; Font lock を使う
  ;; (global-font-lock-mode 1) ;; see org-mode-hook

  ;; ウィンドウの端で折り返す
  (setq org-startup-truncated nil)

  ;; サブツリー以下の * を略式表示する
  (setq org-hide-leading-stars t)

  ;; Color setting for TODO keywords
  ;; Color for priorities
  ;; (setq org-priority-faces
  ;;  '(("?A" :foreground "#E01B4C" :background "#FFFFFF" :weight bold)
  ;;    ("?B" :foreground "#1739BF" :background "#FFFFFF" :weight bold)
  ;;    ("?C" :foreground "#575757" :background "#FFFFFF" :weight bold)))
  ;; Color setting for Tags

  ;; #CC3333
  (setq org-todo-keyword-faces
        '(("FOCUS"    :foreground "#FF0000" :background "#FFCC66")
          ("BUG"      :foreground "#FF0000" :background "#FFCC66")
          ("CHECK"    :foreground "#FF9900" :background "#FFF0F0" :underline t)
          ("ICAL"     :foreground "#33CC66")
          ("APPROVED" :foreground "#66CC66")
          ("QUESTION" :foreground "#FF0000")
          ("WAIT"     :foreground "#CCCCCC" :background "#666666")
          ("MAIL"     :foreground "#CC3300" :background "#FFEE99")
          ("PLAN"     :foreground "#FF6600")
          ("PLAN2"    :foreground "#FFFFFF" :background "#FF6600")
          ("REV1"     :foreground "#3366FF")
          ("REV2"     :foreground "#3366FF" :background "#99CCFF")
          ("REV3"     :foreground "#FFFFFF" :background "#3366FF")
          ("SLEEP"    :foreground "#9999CC")))

  ;; (:foreground "#0000FF" :bold t)     ; default. do NOT put this bottom
  (setq org-tag-faces
        '(("Achievement" :foreground "#66CC66")
          ("Bug"         :foreground "#FF0000")
          ("Report"      :foreground "#66CC66")
          ("Background"  :foreground "#66CC99")
          ("Chore"       :foreground "#6699CC")
          ("project"     :foreground "#6666CC")
          ("read"        :foreground "#6666CC")
          ("book"        :foreground "#6666CC")
          ("Doing"       :foreground "#FF0000")
          ("Draft"       :foreground "#9933CC") ;; Draft(r1,r2,r3)->Review(1,2)
          ("Review"      :foreground "#6633CC")
          ("Revisit"     :foreground "#6633CC")
          ("Redmine"     :foreground "#CC6666")
          ("Ongoing"     :foreground "#CC6666") ; for non scheduled/reminder
          ("Template"    :foreground "#66CC66")
          ("Repeat"      :foreground "#CC9999") ; for interval tasks
          ("Mag"         :foreground "#9966CC")
          ("buy"         :foreground "#9966CC")
          ("pay"         :foreground "#CC6699")
          ("try"         :foreground "#FF3366")
          ("secret"      :foreground "#FF0000")
          ("emacs"       :foreground "#6633CC")
          ("note"        :foreground "#6633CC")
          ("print"       :foreground "#6633CC")
          ("study"       :foreground "#6666CC")
          ("Implements"  :foreground "#CC9999" :weight bold)
          ("Coding"      :foreground "#CC9999")
          ("Editing"     :foreground "#CC9999" :weight bold)
          ("work"        :foreground "#CC9999" :weight bold)
          ("Survey"      :foreground "#CC9999" :weight bold)
          ("Home"        :foreground "#CC9999" :weight bold)
          ("Open"        :foreground "#CC9999" :weight bold)
          ("Blog"        :foreground "#9966CC")
          ("story"       :foreground "#FF7D7D")
          ("plan"        :foreground "#FF7D7D")
          ("issue"       :foreground "#FF7D7D")
          ("Test"        :foreground "#FF0000" :weight bold)
          ("attach"      :foreground "#FF0000")
          ("drill"       :foreground "#66BB66" :underline t)
          ("DEBUG"       :foreground "#FFFFFF" :background "#9966CC")
          ("EVENT"       :foreground "#FFFFFF" :background "#9966CC")
          ("Thinking"    :foreground "#FFFFFF" :background "#96A9FF")
          ("Schedule"    :foreground "#FFFFFF" :background "#FF7D7D")
          ("INPUT"       :foreground "#FFFFFF" :background "#CC6666")
          ("OUTPUT"      :foreground "#FFFFFF" :background "#66CC99")
          ("CYCLE"       :foreground "#FFFFFF" :background "#6699CC")
          ("weekend"     :foreground "#FFFFFF" :background "#CC6666")
          ("Log"         :foreground "#008500"))))

10.6. TODOキーワードのカスタマイズ

キーワードには日本語も使えます.

(with-eval-after-load "org"
  (setq org-todo-keywords
        '((sequence "TODO(t)" "PLAN(p)" "PLAN2(P)" "|" "DONE(d)")
          (sequence "FOCUS(f)" "CHECK(C)" "ICAL(c)"  "|" "DONE(d)")
          (sequence "WAIT(w)" "SLEEP(s)" "QUESTION(q)" "|" "DONE(d)")
          (sequence "REV1(1)" "REV2(2)" "REV3(3)" "|" "APPROVED(a@/!)")))

  ;; Global counting of TODO items
  (setq org-hierarchical-todo-statistics nil)

  ;; Global counting of checked TODO items
  (setq org-hierarchical-checkbox-statistics nil)

  ;; block-update-time
  (defun org-dblock-write:block-update-time (params)
    (let ((fmt (or (plist-get params :format) "%Y-%m-%d")))
      (insert "" (format-time-string fmt (current-time))))))

10.7. ImageMagick を使って様々な画像をインライン表示する

システムに OpenJPEGImageMagick がインストールされていれば,JPEG 2000 などの画像形式もバッファに表示できます.

(with-eval-after-load "org"
  (setq org-image-actual-width '(256))
  (add-to-list 'image-file-name-extensions "jp2")
  ;; (add-to-list 'image-file-name-extensions "j2c")
  (add-to-list 'image-file-name-extensions "bmp")
  (add-to-list 'image-file-name-extensions "psd"))

次の例では,同じ画像を2度インライン表示しようと指定ますが,前者は横幅が128ピクセルで表示され,後者は org-image-actual-width で指定した256ピクセルで表示されます.

#+attr_html: :width 128
[[~/Desktop/lena_std.jp2]]

[[~/Desktop/lena_std.jp2]]
(org-toggle-inline-images)

10.8. README を常に org-mode で開く

init-org.el として書き出して遅延読み込みしています.

(push '("[rR][eE][aA][dD][mM][eE]" . org-mode) auto-mode-alist)

10.9. ソースブロック等の大文字記述を一括で小文字に変える

https://scripter.co/org-keywords-lower-case/ で紹介されている関数を,ログ出力部分だけ加筆して使っています.

(with-eval-after-load "org"
  (defun my-lowercase-org-keywords ()
    "Lower case Org keywords and block identifiers."
    (interactive)
    (save-excursion
      (goto-char (point-min))
      (let ((case-fold-search nil)
            (count 0))
        (while (re-search-forward
                "\\(?1:#\\+[A-Z_]+\\(?:_[[:alpha:]]+\\)*\\)\\(?:[ :=~’”]\\|$\\)"
                nil :noerror)
          (setq count (1+ count))
          (let* ((prev (match-string-no-properties 1))
                 (new (downcase prev)))
            (replace-match new :fixedcase nil nil 1)
            (message "Updated(%d): %s => %s" count prev new)))
        (message "Lower-cased %d matches" count)))))

10.10. イベント通知

ここでは, terminal-notifier ではなく,事実上,後継アプリと言える alerter を使う設定です.

macOS の通知機能を使って,Emacsで発生するイベントをユーザに通知します. org-show-notification-handlerterminal-notifier.app を呼ぶ関数をぶら下げることで, org-notify で簡単に通知機能を使えるようになります. appt-disp-window をカスタマイズすれば, appt を経由して TODOのアイテムも通知されます.

独自に追加した ns-default-notification-sound に OSで定められたアラーム音を設定できます.音楽を流したい時は, org-show-notification-handler の引数で指定すると設定できます.

  sticky sound Function
pomodoro nil Glass my-pomodoro-notify
org-notify t default my-desktop-notification-handler
daily.org select default my-desktop-notify
appt select Glass/Pop ad:appt-disp-window
  Note
pomodoro pomodoro loop
org-notify orgからの通知
daily.org Alarms on a table
appt TODO items with deadline
;;;###autoload
(defun my-desktop-notification (title message &optional sticky sound timeout)
  "Show a message by `alerter' command."
  (if (eq ns-alerter-command 'script)
      (ns-do-applescript
       (format "display notification \"%s\" with title \"%s\""
               title message))
    (start-process
     "notification" "*notification*"
     ns-alerter-command
     "-title" title
     "-message" message
     "-sender" "org.gnu.Emacs"
     "-timeout" (format "%s" (if sticky 0 (or timeout 7)))
     "-sound" (or sound ns-default-notification-sound))))

;;;###autoload
(defun my-desktop-notification-handler (message)
  (my-desktop-notification "Message from org-mode" message t))
(with-eval-after-load "org"
  ;; Select from Preferences: { Funk | Glass | ... | Purr | Pop ... }
  (defvar ns-default-notification-sound "Pop")

  (defvar ns-alerter-command (concat (getenv "HOME") "/Dropbox/bin/alerter")
    "Path to alerter command. see https://github.com/vjeantet/alerter")
  (setq ns-alerter-command 'script) ;; the alerter is not work for now(2024-02-18).
  (unless ns-alerter-command
    (setq ns-alerter-command "")) ;; FIXME
  (when (or (eq ns-alerter-command 'script)
            (executable-find ns-alerter-command))
    (setq org-show-notification-handler #'my-desktop-notification-handler)))

10.11. org-mode でアラーム管理

(注)Growlnotify の代わりに terminal-notifier を使うこともできます.

[2018-03-19 月]: alerter に統一しました.

通知アプリと org-mode のバッファを組み合わせてアラームリストを管理しています.アラームをorgバッファに書き込むだけなので,とても楽です.機能としては,特定のorgバッファに,時刻とアラームの内容を表の形式として保存しておくだけで,Emacs が起動している限りにおいて通知アプリがそのアラームをデスクトップに表示してくれます.つまり,アラームリストは org-mode の表で一覧化されているので,管理も楽ですし,見た目もわかりやすいです.

アラームとして解釈される表は,オプション,時刻(HH:MM形式),アラーム内容の3列で構成していればOKです.オプションの列にXを入れておくと,通知アプリがStickyモードで動作するので,アラームを見逃しません.

アラームは複数登録することができます.不要になったアラームを削除するのは,単純に表から当該の行を削除するだけで済みます.実際のところは,バッファが保存される時にアラームリストの変更が自動的にシステムに反映されるので,余計な作業は不要です.

my-set-alarms-from-file は,utility.elに記述した関数です.

(unless noninteractive
  (with-eval-after-load "org"
    (let ((file "~/Dropbox/org/db/daily.org"))
      (when (and (file-exists-p file)
                 (require 'utility nil t))
        (my-set-alarms-from-file file) ;; init
        (add-hook 'after-save-hook #'my-update-alarms-from-file))))) ;; update

10.12. カウントダウンタイマー

M-x my-countdown-timer で呼び出します.ちょっと時間を測りたい時に使っています.

(with-eval-after-load "org"
  (defun my-countdown-timer-notify ()
    ;; (when mode-line-format
    ;;   (my-mode-line-off))
    (when ns-alerter-command
      (setq org-show-notification-handler #'my-desktop-notification-handler))
    (remove-hook 'org-timer-done-hook #'my-countdown-timer-notify)
    (remove-hook 'org-timer-stop-hook #'my-countdown-timer-notify)
    (my-desktop-notification "### Expired! ###" "Time is up!" t "Glass"))

  (defalias 'run-timer 'my-countdown-timer)
  (defun my-countdown-timer ()
    (interactive)
    ;; (unless mode-line-format
    ;;   (my-mode-line-on))
    (when (eq org-show-notification-handler #'my-desktop-notification-handler)
      (setq org-show-notification-handler nil))
    (with-temp-buffer
      (org-mode)
      (insert "* Countdown")
      (add-hook 'org-timer-done-hook #'my-countdown-timer-notify)
      (add-hook 'org-timer-stop-hook #'my-countdown-timer-notify)
      (org-timer-set-timer))))

10.13. リンクをエコーエリアに表示する

eldoc の機能を使うと簡単にできます.

(when (autoload-if-found '(org-mode my-load-echo-org-link)
                                           "org" nil t)
        (add-hook 'org-mode-hook #'my-load-echo-org-link)
        (with-eval-after-load "org"
                (defvar my-org-link-prompt "Link:")
                (defun my-echo-org-link ()
                        (when (org-in-regexp org-link-bracket-re 1)
              (let ((l (length my-org-link-prompt))
                                      (msg (org-link-unescape (match-string-no-properties 1))))
                      (put-text-property 0 l 'face 'minibuffer-prompt my-org-link-prompt)
                      (eldoc-message (format "%s %s" my-org-link-prompt msg)))))

                (defun my-load-echo-org-link ()
                        (add-function :before-until (local 'eldoc-documentation-function)
                                            #'my-echo-org-link)
                        ;; (setq-local eldoc-documentation-function #'my-echo-org-link)
                        )))

10.14. TODO HHKBで矢印を使わないように設定する

(with-eval-after-load "org"
  (org-defkey org-mode-map (kbd "M-p") #'my-org-meta-next)
  (org-defkey org-mode-map (kbd "M-n") #'my-org-meta-previous)
  (org-defkey org-mode-map (kbd "M-b") #'my-org-meta-backward)
  (org-defkey org-mode-map (kbd "M-f") #'my-org-meta-forward))

(defun my-org-item-has-child-p ()
  "Return t, if the item has at least a child item."
  (save-excursion
    (beginning-of-line)
    (org-list-has-child-p (point) (org-list-struct))))

(defun my-org-heading-has-child-p ()
  "Return t, if the heading has at least a child heading."
  (save-excursion
    (org-goto-first-child)))

(defun my-org-meta-previous ()
  "Move item or subtree down, otherwise `scroll-up'."
  (interactive)
  (cond ((org-at-item-p)
               (call-interactively 'org-move-item-down))
              ((or (looking-at org-heading-regexp)
             (and (org-at-heading-p) (eolp)))
               (call-interactively 'org-move-subtree-down))
        ((org-at-table-p)
         (call-interactively 'org-table-move-row))
              (t nil))) ;; (call-interactively 'scroll-up)

(defun my-org-meta-next ()
  "Move item or subtree up, otherwise `scroll-down'."
  (interactive)
  (cond ((org-at-item-p)
               (call-interactively 'org-move-item-up))
              ((or (looking-at org-heading-regexp)
             (and (org-at-heading-p) (eolp)))
               (call-interactively 'org-move-subtree-up))
        ((org-at-table-p)
         (org-call-with-arg 'org-table-move-row 'up))
              (t nil))) ;; (call-interactively 'scroll-down))))

(defvar my-org-promote-demote-independently nil)
(defun my-inherit-struct-p ()
  (and (not my-org-promote-demote-independently)
       (or (my-org-item-has-child-p) (my-org-heading-has-child-p))))

(defun my-org-at-meta-fb-p ()
  "Return t, if the cursor stay at item, heading, or table."
  (or (org-at-item-p)
      (looking-at org-heading-regexp)
      (and (org-at-heading-p) (eolp))
      (org-at-table-p)))

(defun my-org-meta-forward ()
  (interactive)
  (if (my-org-at-meta-fb-p)
      (if (my-inherit-struct-p)
          (org-shiftmetaright)
        (org-metaright)) ;; FIXME similar check to my-org-at-meta-fb-p
    (if (and (fboundp 'syntax-subword-mode)
             syntax-subword-mode)
        (call-interactively 'syntax-subword-forward)
      (forward-word))))

(defun my-org-meta-backward ()
  (interactive)
  (if (my-org-at-meta-fb-p)
      (if (my-inherit-struct-p)
          (org-shiftmetaleft)
        (org-metaleft)) ;; FIXME similar check to my-org-at-meta-fb-p
    (if (and (fboundp 'syntax-subword-mode)
             syntax-subword-mode)
        (call-interactively 'syntax-subword-backward)
      (backward-word))))

10.15. orgmode のテーブルを csv に変換する

標準関数の org-table-export を使えば,テーブルを csv 等の形式で外部出力できます.ただバッファ上で csv に変えたりする関数があると楽なので次の関数を使います.標準関数の一部を再利用しています.

M-x org-table-to-format を呼び出すとデフォルトでカンマ区切りに変換してくれます.さらにその情報をコピペできるようにクリップボードに流し込みます.

(defun my-org-table-copy-as (&optional format)
  "Copy converted table."
  (interactive)
  (let ((format (or format
                    (org-entry-get (point) "<tab>LE_EXPORT_FORMAT" t)
                    org-table-export-default-format)))
    (if (string-match "\\([^ \t\r\n]+\\)\\( +.*\\)?" format)
              (let ((transform (intern (match-string 1 format)))
                    (params (and (match-end 2)
                                             (read (concat "(" (match-string 2 format) ")"))))
                    (table (org-table-to-lisp)))
          (if (not (org-at-table-p))
              (user-error "The cursor is not at a table")
                  (with-temp-buffer
                          (insert (funcall transform table params) "\n")
              (clipboard-kill-ring-save (point-min) (point-max)))))
      (user-error "<tab>LE_EXPORT_FORMAT invalid"))))

(defun my-org-table-convert-to (&optional format)
  "Convert a table to FORMAT.
If FORMAT is nil, it is set equal to a property value specified
by \"<tab>LE_EXPORT_FORMAT\" or `org-table-export-default-format'.
Converted table is copied to kill ring for further use.
The core part is extracted from `org-table-export'."
  (interactive)
  (let ((format (or format
                    (org-entry-get (point) "<tab>LE_EXPORT_FORMAT" t)
                    org-table-export-default-format)))
    (if (string-match "\\([^ \t\r\n]+\\)\\( +.*\\)?" format)
              (let ((transform (intern (match-string 1 format)))
                    (params (and (match-end 2)
                                             (read (concat "(" (match-string 2 format) ")"))))
                    (table (org-table-to-lisp)))
          (if (not (org-at-table-p))
              (user-error "The cursor is not at a table")
                  (kill-region (org-table-begin) (org-table-end))
                  (let ((begin (point)))
                    (insert (funcall transform table params))
                    (clipboard-kill-ring-save begin (point))
              (insert "\n"))))
      (user-error "<tab>LE_EXPORT_FORMAT invalid"))))

10.16. ソースブロック内で eldoc の発動を抑制する

org バッファのソースブロックにカーソルがある時には, eldoc が発動しないようにします.

(with-eval-after-load "eldoc"
  (defvar my-eldoc-disable-in-org-block nil)
  (defun ad:eldoc-print-current-symbol-info (f &optional interactive)
    "Run `eldoc' when the cursor is NOT located in org source block."
    (interactive '(t))
    (unless (or my-eldoc-disable-in-org-block
                (and (eq major-mode 'org-mode)
                     (eq (car (org-element-at-point)) 'src-block)))
      (funcall f interactive)))
  (advice-add 'eldoc-print-current-symbol-info :around
              #'ad:eldoc-print-current-symbol-info))

10.17. 見出しの上で org-reveal する場合の挙動を変える

通常の org-reveal は,ドロワを展開しませんが,カーソルが見出し上にある時に org-reveal (C-c C-r) を呼ぶ時に,ドロワを展開して内容を表示させるようにします.

(defun ad:org-reveal (f &optional siblings)
  (interactive "P")
  (if (org-at-heading-p)
      (org-show-subtree)
    (funcall f siblings)))
(with-eval-after-load "org"
  (advice-add 'org-reveal :around #'ad:org-reveal))

10.18. TODO org-emphasis-alist の各要素を個別に配色する

下記で紹介されている設定を軽くカスタマイズして使用しています.

日本語を 入力 しています. 日本語を 入力 しています. 日本語を 入力 しています. 日本語を 入力 しています. 日本語を 入力 しています. 日本語を 入力 しています.

(defface my-org-emphasis-bold
        '((default :inherit bold)
                (((class color) (min-colors 88) (background light))
                 :foreground "#5b5caf" :background "#e6ebfa") ;; #a60000 #4E4F97 #c7e9fa
                (((class color) (min-colors 88) (background dark))
                 :foreground "#99B2FF")) ;; #ff8059 #BCBCDB #6666D6 #879EE2
        "My bold emphasis for Org.")

(defface my-org-emphasis-italic
        '((default :inherit italic)
                (((class color) (min-colors 88) (background light))
                 :foreground "#005e00" :background "#B4EAB4")
                (((class color) (min-colors 88) (background dark))
                 :foreground "#44bc44"))
        "My italic emphasis for Org.")

(defface my-org-emphasis-underline
        '((default :inherit underline)
                (((class color) (min-colors 88) (background light))
                 :foreground "#813e00")
                (((class color) (min-colors 88) (background dark))
                 :foreground "#d0bc00"))
        "My underline emphasis for Org.")

(defface my-org-emphasis-strike-through
        '((((class color) (min-colors 88) (background light))
                 :strike-through "#972500" :foreground "#505050")
                (((class color) (min-colors 88) (background dark))
                 :strike-through "#ef8b50" :foreground "#a8a8a8"))
        "My strike-through emphasis for Org.")

(with-eval-after-load "org"
        (custom-set-variables ;; call org-set-emph-re
         '(org-emphasis-alist '(("~" org-code verbatim)
                                                  ("=" org-verbatim verbatim)
                                                  ("*" my-org-emphasis-bold)
                                                  ("/" my-org-emphasis-italic)
                                                  ("_" my-org-emphasis-underline)
                                                  ("+" my-org-emphasis-strike-through))))

        (custom-set-faces
         '(org-code
                 ((t (:foreground "red" :background "pink" :inherit shadow))))
         '(org-verbatim
                 ((t (:foreground "#ff6059" :background "PeachPuff" :inherit shadow)))))

        (when (featurep 'org-extra-emphasis)
                (org-extra-emphasis-update))) ;; to apply configured `org-emphasis-alist'

10.19. 現在のアイテムをリストの最初・最後に移す

下記の設定では, C-c M-n でリストの末尾に, C-c M-p でリストの先頭に,現在のファイルを移します.カーソルは,移動せさせずに,元の場所に留めます.

(with-eval-after-load "org"
  (keymap-set org-mode-map "C-c x" #'my-org-move-item-end)
  (keymap-set org-mode-map "C-c X" #'my-org-move-item-begin))
;;;###autoload
(defun my-org-move-item-begin ()
  "Move the current item to the beginning of the list."
  (interactive)
  (unless (org-at-item-p) (error "Not at an item"))
  (let* ((col (current-column))
         (item (point-at-bol))
         (struct (org-list-struct))
         (prevs (org-list-prevs-alist struct))
         (prev-item (org-list-get-prev-item (point-at-bol) struct prevs)))
    (unless prev-item
      (user-error "Cannot move this item further up"))
    (setq struct (org-list-send-item item 'begin struct))
    (goto-char item)
    (org-list-write-struct struct (org-list-parents-alist struct))
    (org-move-to-column col)))

;;;###autoload
(defun my-org-move-item-end ()
  "Move the current item to the end of the list."
  (interactive)
  (unless (org-at-item-p) (error "Not at an item"))
  (let* ((col (current-column))
         (item (point-at-bol))
         (struct (org-list-struct))
         (prevs (org-list-prevs-alist struct))
         (next-item (org-list-get-next-item (point-at-bol) struct prevs)))
    (unless next-item
      (user-error "Cannot move this item further down"))
    (setq struct (org-list-send-item item 'end struct))
    (goto-char item)
    (org-list-write-struct struct (org-list-parents-alist struct))
    (org-move-to-column col)))

10.20. [org-capture] 高速にメモを取る

Emacs を起動している限り,いつでもどこでもメモを記録できます.

(when (autoload-if-found '(org-capture)
                         "org-capture" nil t)
  (with-eval-after-load "org"
    ;; キャプチャ時に作成日時をプロパティに入れる
    ;; Thanks to https://emacs.stackexchange.com/questions/21291/add-created-timestamp-to-logbook
    (defun my-org-default-property ()
      "Set the creation date and org-id."
      (interactive)
      (my-org-set-created-property)
      (org-id-get-create))
    (defvar my-org-created-property-name "CREATED"
      "The name of the org-mode property.
This user property stores the creation date of the entry")
    (defun my-org-set-created-property (&optional active NAME)
      "Set a property on the entry giving the creation time.

By default the property is called CREATED. If given the `NAME'
argument will be used instead. If the property already exists, it
will not be modified."
      (interactive)
      (let* ((created (or NAME my-org-created-property-name))
             (fmt (if active "<%s>" "[%s]"))
             (now (format fmt (format-time-string "%Y-%m-%d %a %H:%M")))
             (field (org-entry-get (point) created nil)))
        (unless (or field (equal "" field))
          (org-set-property created now)
          (org-cycle-hide-drawers 'children))))
    (defun ad:org-insert-todo-heading (_arg &optional _force-heading)
      (unless (org-at-item-checkbox-p)
        (my-org-default-property)))
    (advice-add 'org-insert-todo-heading :after #'ad:org-insert-todo-heading))

  (with-eval-after-load "org-capture"
    (defun my-toggle-org-block-visibility ()
      "Testing..."
      (interactive)
      (when (looking-at org-drawer-regexp)
        (org-flag-drawer                ; toggle block visibility
         (not (get-char-property (match-end 0) 'invisible)))))

    (add-hook 'org-capture-before-finalize-hook #'my-org-set-created-property)

    ;; 2010-06-13 の形式では,タグとして認識されない
    (defun get-current-date-tags () (format-time-string "%Y%m%d"))
    (setq org-default-notes-file (concat org-directory "next.org"))
    (defvar org-capture-academic-file (concat org-directory "academic.org"))
    (defvar org-capture-ical-file (concat org-directory "org-ical.org"))
    (defvar org-capture-buffer-file (concat org-directory "db/buffer.org"))
    (defvar org-capture-notes-file (concat org-directory "db/note.org"))
    (defvar org-capture-english-file (concat org-directory "db/english.org"))
    (defvar org-capture-diary-file (concat org-directory "log/diary.org"))
    (defvar org-capture-article-file (concat org-directory "db/article.org"))
    (defvar org-capture-blog-file
      (concat org-directory "blog/entries/imadenale.org"))

    ;; see org.pdf:p73
    (setq org-capture-templates
          `(("t" "TODO 項目を INBOX に貼り付ける" entry
             (file+headline ,org-default-notes-file "INBOX") "** TODO %?\n")
            ("a" "記事リストにエントリー" entry
             (file+headline ,org-capture-article-file "INBOX")
             "** READ %?\n\t")
            ("c" "同期カレンダーにエントリー" entry
             (file+headline ,org-capture-ical-file "Scheduled")
             "** TODO %?\n\t")
            ("d" "Doingタグ付きのタスクをInboxに投げる" entry
             (file+headline ,org-default-notes-file "INBOX")
             "** TODO %? :Doing:\n  - \n"
             :clock-in t
             :clock-keep t)
            ("l" "本日のチェックリスト" entry
             (file+headline ,org-capture-diary-file "Today")
             "** FOCUS 本日のチェックリスト %T\n(起床時間の記録)[[http://www.hayaoki-seikatsu.com/users/takaxp/][早起き日記]] \n(朝食)\n  - [ ] %?\n(昼食)\n(帰宅/夕食)\n----\n(研究速報)\n  - [ ] \n")
            ("i" "アイディアを書き込む" entry (file+headline ,org-default-notes-file "INBOX")
             "** %?\n  - \n\t%U")
            ("b" "Create new post for imadenale blog" entry
             (file+headline ,org-capture-blog-file ,(format-time-string "%Y"))
             "** TODO \n:PROPERTIES:\n:EXPORT_FILE_NAME: %?\n:EXPORT_HUGO_TAGS: \n:EXPORT_HUGO_LASTMOD: \n:EXPORT_HUGO_IMAGES: \n:END:\n{{< tweet user=\"takaxp\" id=\"\" >}}\n")
            ("B" "Create new post for imadenale blog (UUID)" entry
             (file+headline ,org-capture-blog-file ,(format-time-string "%Y"))
             "** TODO %?\n:PROPERTIES:\n:EXPORT_FILE_NAME: %(uuid-string)\n:EXPORT_HUGO_TAGS: \n:EXPORT_HUGO_LASTMOD: \n:EXPORT_HUGO_IMAGES: \n:END:\n{{< tweet user=\"takaxp\" id=\"\" >}}\n")
            ;; ("b" "Bug タグ付きの TODO 項目を貼り付ける" entry
            ;;  (file+headline ,org-default-notes-file "INBOX")
            ;;  "** TODO %? :bug:\n %i\n %a %t")
            ("T" "時間付きエントリー" entry (file+headline ,org-default-notes-file "INBOX")
             "** %? %T--\n")
            ("n" "ノートとしてINBOXに貼り付ける" entry
             (file+headline ,org-default-notes-file "INBOX")
             "** %? :note:\n\t%U")
            ("D" "「ドラッカー365の金言」をノートする" entry
             (file+headline ,org-capture-notes-file "The Daily Drucker")
             "** 「%?」\nDrucker) \n  - \n  - \nACTION POINT:\n  - \nQUESTION:\n  - \n")
            ("r" ,(concat "研究ノートを " org-capture-academic-file
                          " に書き込む")
             entry (file+headline ,org-capture-academic-file "Survey")
             "** %? :note:\n# \n  - \n\t%U")
            ("`" ,(concat "ノートをバッファ " org-capture-buffer-file
                          " に書き込む")
             entry (file+headline ,org-capture-buffer-file "Buffers")
             "** %(get-random-string 16) %U\n\n%?\n\n----")
            ("w" ,(concat "英単語を " org-capture-english-file
                          " に書き込む") entry
                          (file+headline ,org-capture-english-file "WORDS")
                          "** %? :%(get-current-date-tags):\n「」\n  - ")
            ("g" ,(concat "英語ノートを " org-capture-english-file
                          " に書き込む")
             entry (file+headline ,org-capture-english-file "GRAMMER")
             "** %? :%(get-current-date-tags):\n\n%U")
            ))))

10.21. [org-agenda] タスク/予定管理

;;;###autoload
(defun my-org-agenda-prepare-buffers ()
  (unless (featurep 'org-agenda)
    (when (require 'org-agenda nil t)
      (unless (and (featurep 'org-id)
                   (featurep 'org-tempo))
        (my-org-modules-activate)) ;; FIXME
      (unless (featurep 'ob-http) (my-org-babel-load-activate)) ;; FIXME
      (org-agenda-prepare-buffers org-agenda-files)
      (message "Building agenda buffers...done"))))

;;;###autoload
(defun my-recenter-top-bottom-top ()
  "Recenter the current line to the top of window."
  (set-window-start (get-buffer-window) (line-beginning-position)))
;; `org-agenda-prepare-buffers' は重い.agenda 実行時の最初に走るが,
;; 事前に走らせておくほうがいい.以下の例では,
;; 起動後,何もしなければ10秒後に org, org-agenda が有効になる
;; 起動後,org buffer を訪問して,10秒待つと,org-agenda が有効になる
;; 起動後,直接 org-agenda を叩く場合は重いまま(タイマー走ってもスルー)
;; これを (with-eval-after-load "org") の中に置くと振る舞いが変(2回実行)になる
(defvar my-org-agenda-pb-timer
  (unless noninteractive
    (run-with-idle-timer (+ 9 my-default-loading-delay)
                         nil #'my-org-agenda-prepare-buffers)))
(with-eval-after-load "org"
  ;; アジェンダ作成対象(指定しないとagendaが生成されない)
  ;; ここを間違うと,MobileOrg, iCal export もうまくいかない
  (dolist (file (mapcar
                 (lambda (arg)
                   (concat (getenv "SYNCROOT") "/org/" arg))
                 '("org-ical.org" "next.org" "db/cooking.org" "minutes/wg1.org"
                   "db/daily.org" "db/trigger.org"  "academic.org" "tr/work.org"
                   "org2ja.org" "itr.org" "db/books.org")))
    (when (file-exists-p (expand-file-name file))
      (add-to-list 'org-agenda-files file 'append)))
  (when (eq system-type 'windows-nt) ;; FIXME
    (setq org-agenda-files '("~/Dropbox/org/next.org"))))

(with-eval-after-load "org-agenda"
  ;; sorting strategy
  (setq org-agenda-sorting-strategy
        '((agenda habit-down time-up timestamp-up priority-down category-keep)
          (todo priority-down category-keep)
          (tags priority-down category-keep)
          (search category-keep)))

  ;; Set the view span as day in an agenda view, the default is week
  (setq org-agenda-span 'day)

  ;; アジェンダに警告を表示する期間
  (setq org-deadline-warning-days 0)

  ;; 時間幅が明示的に指定されない場合のデフォルト値(分指定)
  (setq org-agenda-default-appointment-duration 60)

  ;; アジェンダビューでFOLLOWを設定(自動的に別バッファに当該タスクを表示)
  (setq org-agenda-start-with-follow-mode t)

  ;; Customized Time Grid
  (setq org-agenda-time-grid ;; Format is changed from 9.1
        '((daily today require-timed)
          (0800 1000 1200 1400 1600 1800 2000 2200 2400)
          "......"
          "------------------------"
          ))

  ;; (setq org-agenda-current-time-string "<  d('- ' イマココ)")
  (setq org-agenda-current-time-string "<<< イマココ")
  (setq org-agenda-timegrid-use-ampm t)

  ;; org-agenda 表示の水平方向の冗長さを削減
  (setq org-agenda-prefix-format
        '((agenda  . "%-9c| %?-12t% s")
          (todo  . " %i %-12:c")
          (tags  . " %i %-12:c")
          (search . " %i %-12:c")))
  (setq org-agenda-remove-tags t)
  (setq org-agenda-scheduled-leaders '("[S]" "S.%2dx:\t"))
  (setq org-agenda-deadline-leaders '("[D]" "In %3d d.:\t" "%2d d. ago:\t"))

  (with-eval-after-load "moom"
    (defvar my-org-tags-column org-tags-column)
    ;; Expand the frame width temporarily during org-agenda is activated.
    (defun my-agenda-frame-width ()
      (let ((width (floor (* 1.2 moom-frame-width-single))))
        (setq org-tags-column (- org-tags-column (- width 80)))
        ;; (org-align-tags t)
        (moom-change-frame-width width)))
    ;; (add-hook 'org-agenda-mode-hook #'my-agenda-frame-width)

    (defun ad:org-agenda--quit (&optional _bury)
      (setq org-tags-column my-org-tags-column)
      ;; (org-align-tags t)
      (moom-change-frame-width))
    ;; (advice-add 'org-agenda--quit :after #'ad:org-agenda--quit)
    )

  ;; 移動直後にagendaバッファを閉じる(ツリーの内容はSPACEで確認可)
  (org-defkey org-agenda-mode-map [(tab)]
              (lambda () (interactive)
                (org-agenda-goto)
                (with-current-buffer "*Org Agenda*"
                  (org-agenda-quit))))

  ;; agenda アイテムの内容を別バッファに表示する時に,内容の全体を表示する
  (add-hook 'org-agenda-after-show-hook #'my-recenter-top-bottom-top)

  (custom-set-faces
   ;; '(org-agenda-clocking ((t (:background "#300020"))))
   '(org-agenda-structure ((t (:underline t :foreground "#6873ff"))))
   '(org-agenda-date-today ((t (:weight bold :foreground "#4a6aff"))))
   '(org-agenda-date ((t (:weight bold :foreground "#6ac214"))))
   '(org-agenda-date-weekend ((t (:weight bold :foreground "#ff8d1e"))))
   '(org-time-grid ((t (:foreground "#0a4796"))))
   '(org-warning ((t (:foreground "#ff431a"))))
   '(org-upcoming-deadline ((t (:inherit font-lock-keyword-face))))
   )

  ;; 所定の時刻に強制的にAgendaを表示
  (defvar my-org-agenda-auto-popup-list
    '("01:00" "11:00" "14:00" "17:00" "20:00" "23:00"))
  (defun my-popup-agenda ()
    (interactive)
    (let ((status use-dialog-box))
      (setq use-dialog-box nil)
      (when (y-or-n-p-with-timeout "Popup agenda now?" 10 nil)
        (org-agenda-list))
      (message "")
      (setq use-dialog-box status)))
  (defun my-popup-agenda-set-timers ()
    (interactive)
    (cancel-function-timers 'my-popup-agenda)
    (dolist (triger my-org-agenda-auto-popup-list)
      (when (future-time-p triger)
        (run-at-time triger nil 'my-popup-agenda))))
  (my-popup-agenda-set-timers)
  (run-at-time "24:00" nil 'my-popup-agenda-set-timers)

  ;; ついでに calendar.app も定期的に強制起動する
  (defun my-popup-calendar ()
    (interactive)
    (if (and (eq system-type 'darwin)
             (frame-focus-state))
        (shell-command-to-string "open -a calendar.app")
      (message "--- input focus is currently OUT.")))

  (defun my-popup-calendar-set-timers ()
    (interactive)
    (cancel-function-timers 'my-popup-calendar)
    (dolist (triger my-org-agenda-auto-popup-list)
      (when (future-time-p triger)
        (run-at-time triger nil 'my-popup-calendar))))

  (when (memq window-system '(mac ns))
    (my-popup-calendar-set-timers)
    (run-at-time "24:00" nil 'my-popup-calendar-set-timers))

  ;; org-agenda でも "d" 押下で "DONE" にする
  (defun my-org-agenda-done ()
    (interactive)
    (org-agenda-todo "DONE")
    (my-org-agenda-to-appt)) ;; call with async
  (org-defkey org-agenda-mode-map "d" 'my-org-agenda-done)
  (org-defkey org-agenda-mode-map "D" 'my-org-todo-complete-no-repeat)

  ;; org-agenda の表示高さを 50% に固定する
  (setq org-agenda-window-frame-fractions '(0.5 . 0.5)))

;; M-x calendar の動作に近づける.なお today への移動は,"C-." で可能.
(with-eval-after-load "org-keys"
  (org-defkey org-read-date-minibuffer-local-map (kbd "C-n")
              (lambda () (interactive)
                (org-eval-in-calendar '(calendar-forward-week 1))))
  (org-defkey org-read-date-minibuffer-local-map (kbd "C-p")
              (lambda () (interactive)
                (org-eval-in-calendar '(calendar-backward-week 1))))
  (org-defkey org-read-date-minibuffer-local-map (kbd "C-b")
              (lambda () (interactive)
                (org-eval-in-calendar '(calendar-backward-day 1))))
  (org-defkey org-read-date-minibuffer-local-map (kbd "C-f")
              (lambda () (interactive)
                (org-eval-in-calendar '(calendar-forward-day 1))))
  (org-defkey org-read-date-minibuffer-local-map (kbd "q")
              (lambda () (interactive)
                (org-eval-in-calendar '(minibuffer-keyboard-quit)))))

入門者にとって org-agenda は簡単ではないので,無理に使わなくて良いと思います.Org Modeの動作に十分慣れたら戻ってくる程度で十分です.

10.22. [org-onit.el] org-clock-in の自動化

org-onit.el を導入すると,タグを付けるタイミングで自動的に org-clock-in します.タグを取れば org-clock-out が実行されるようになります.

(when (autoload-if-found '(org-onit-toggle-doing
                           org-onit-mode
                           org-onit-toggle-auto org-clock-goto
                           my-sparse-doing-tree org-onit-clock-in-when-unfold
                           org-clock-goto org-onit-update-options)
                         "org-onit" nil t)
  (keymap-global-set "C-<f11>" 'org-clock-goto)

  (with-eval-after-load "org"
    (add-hook 'org-cycle-hook #'org-onit-clock-in-when-unfold)
    (keymap-set org-mode-map "<f11>" 'org-onit-toggle-doing)
    (keymap-set org-mode-map "M-<f11>" 'org-onit-toggle-auto)
    (keymap-set org-mode-map "S-<f11>" 'org-onit-goto-anchor)

    (defun my-sparse-doing-tree ()
      (interactive)
      (org-tags-view nil org-onit-tag)))

  (with-eval-after-load "org-onit"
    (autoload-if-found '(org-bookmark-jump org-bookmark-make-record)
                       "org-bookmark-heading" nil t)
    (when (require 'org-plist nil t)
      (add-to-list 'org-plist-dict '("OPTIONS_ONIT" org-onit-basic-options)))
    (custom-set-variables
     '(org-onit-basic-options '(:wakeup nil :nostate doing :unfold nil))))

  (with-eval-after-load "org-clock"
    (defun my-onit-reveal ()
      ;; (widen)
      (org-overview)
      (org-reveal)
      (org-cycle-hide-drawers 'all)
      (org-show-entry)
      (show-children)
      (org-show-siblings))
    (add-hook 'org-onit-after-jump-hook #'my-onit-reveal)

    (defun my-clear-undo-list ()
      (when (and (fboundp 'org-clocking-p)
                 (org-clocking-p))
        (setq buffer-undo-list nil)))
    (add-hook 'org-clock-in-hook #'my-clear-undo-list) ;; for testing...

    (setq org-clock-clocked-in-display 'frame-title) ;; or 'both
    (setq org-clock-frame-title-format
          '((:eval (format "%s%s |%s|%s"
                           (if (and (require 'org-clock-today nil t)
                                    org-clock-today-mode)
                               (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))
                             "")
                           (if org-onit--auto-clocking " Auto " "")
                           (org-onit-get-sign)
                           org-mode-line-string))
            " - %b"))))

10.23. [orgbox.el] スケジュール追加のわかりやすい入力

C-c C-s をオーバーライドして orgbox-schedule を実行する.

(when (autoload-if-found '(orgbox-schedule orgbox-agenda-schedule)
                         "orgbox" nil t)
  (with-eval-after-load "org"
    (org-defkey org-mode-map (kbd "C-c C-s") 'orgbox-schedule))
  (with-eval-after-load "org-agenda"
    (org-defkey org-agenda-mode-map (kbd "C-c C-s") 'orgbox-agenda-schedule)))
  ;; (require 'orgbox nil t)) ;; require org-agenda

10.24. [appt.el] アラーム設定

  • Growl や Terminal Notifier と連携していると,Emacsがバックグラウンドにあってもアラームに気づける.
(keymap-global-set "C-c f 3" #'my-org-agenda-to-appt)
(run-at-time "20 sec" nil #'my-org-agenda-to-appt)
;; (with-eval-after-load "org") 内で設定すると(何故か)複数回呼ばれてしまう.
(run-with-idle-timer 180 t #'my-org-agenda-to-appt)
;; org-agenda の内容をアラームに登録する

;; 重複実行の抑制用フラグ
(defvar my-org-agenda-to-appt-ready t)

;;;###autoload
(defun my-org-agenda-to-appt (&optional force)
  "Update `appt-time-mag-list'.  Use `async' if possible."
  (interactive)
  (unless (featurep 'org)
    (require 'org))
  (if (or (not (require 'async nil t))
          (not my-org-agenda-to-appt-async))
      (unless (active-minibuffer-window)
        ;; (org-agenda-to-appt t '((headline "TODO")))
        (org-agenda-to-appt t)
        (appt-check))
    (when force
      (setq my-org-agenda-to-appt-ready t))
    (if (not my-org-agenda-to-appt-ready)
        (message "[appt] Locked")
      (setq my-org-agenda-to-appt-ready nil)
      ;; (message "-------------------------")
      ;; (message "parent: %s"
      ;;          (format-time-string "%H:%M:%S.%3N" (current-time)))
      (async-start
       `(lambda ()
          (setq load-path ',load-path)
          (require 'org)
          (require 'appt)
          (setq org-agenda-files ',org-agenda-files)
          ;; (org-agenda-to-appt t '((headline "TODO")))
          (org-agenda-to-appt t)
          (appt-check) ;; remove past events
          ;; Remove tags
          (let ((msgs appt-time-msg-list))
            (setq appt-time-msg-list nil)
            (dolist (msg msgs)
              (add-to-list 'appt-time-msg-list
                           (let ((match (string-match
                                         org-tag-group-re (nth 1 msg))))
                             (if match
                                 (list (nth 0 msg)
                                       (org-trim (substring-no-properties
                                                  (nth 1 msg)
                                                  0 match))
                                       (nth 2 msg))
                               msg)
                             ) t))
            ;; just for sure
            (delq nil appt-time-msg-list)))
       `(lambda (result)
          ;; (message "child: %s"
          ;;          (format-time-string "%H:%M:%S.%3N" (current-time)))
          (setq appt-time-msg-list result) ;; nil means No event
          ;; (my-add-prop-to-appt-time-msg-list)
          (unless (active-minibuffer-window)
            (let ((cnt (length appt-time-msg-list))
                  (message-log-max nil))
              (if (eq cnt 0)
                  (message "[async] No event to add")
                (message "[async] Added %d event%s for today"
                         cnt (if (> cnt 1) "s" "")))))
          (setq my-org-agenda-to-appt-ready t))))))
(when (autoload-if-found '(appt ad:appt-display-message
                                ad:appt-disp-window appt-check)
                         "appt" nil t)
  (defvar my-org-agenda-to-appt-async t)
  (with-eval-after-load "appt"
    ;; モードラインに残り時間を表示しない
    (setq appt-display-mode-line nil)

    ;; window を フレーム内に表示する
    (setq appt-display-format 'echo)

    ;; window を継続表示する時間[s]
    (setq appt-display-duration 5)

    ;; ビープ音の有無
    (setq appt-audible nil)

    ;; 何分前から警告表示を開始するか[m]
    (setq appt-message-warning-time 10)

    ;; 警告表示開始から何分ごとにリマインドするか[m]
    (setq appt-display-interval 1)

    ;; appt-display-format が 'echo でも appt-disp-window-function を呼ぶ
    ;; Need review
    (defun ad:appt-display-message (string mins)
      "Display a reminder about an appointment.
The string STRING describes the appointment, due in integer MINS minutes.
The arguments may also be lists, where each element relates to a
separate appointment.  The variable `appt-display-format' controls
the format of the visible reminder.  If `appt-audible' is non-nil,
also calls `beep' for an audible reminder."
      (if appt-audible (beep 1))
      ;; Backwards compatibility: avoid passing lists to a-d-w-f if not necessary.
      (and (listp mins)
           (= (length mins) 1)
           (setq mins (car mins)
                 string (car string)))
      (cond ((memq appt-display-format '(window echo)) ;; Modified
             ;; TODO use calendar-month-abbrev-array rather than %b?
             (let ((time (format-time-string "%a %b %e ")))
               (condition-case err
                   (funcall appt-disp-window-function
                            (if (listp mins)
                                (mapcar #'number-to-string mins)
                              (number-to-string mins))
                            time string)
                 (wrong-type-argument
                  (if (not (listp mins))
                      (signal (car err) (cdr err))
                    (message "Argtype error in `appt-disp-window-function' - \
update it for multiple appts?")
                    ;; Fallback to just displaying the first appt, as we used to.
                    (funcall appt-disp-window-function
                             (number-to-string (car mins)) time
                             (car string))))))
             (run-at-time (format "%d sec" appt-display-duration)
                          nil
                          appt-delete-window-function))
            ((eq appt-display-format 'echo) ;; hidden
             (message "%s" (if (listp string)
                               (mapconcat #'identity string "\n")
                             string)))))

    (advice-add 'appt-display-message :override #'ad:appt-display-message)

    (defun ad:appt-disp-window (min-to-app _new-time appt-msg)
      "Extension to support appt-disp-window."
      (if (string= min-to-app "0")
          (my-desktop-notification "### Expired! ###" appt-msg t "Glass")
        (my-desktop-notification
         (concat "in " min-to-app " min.") appt-msg nil "Tink")))
    (cond
     ((eq appt-display-format 'echo)
      (setq appt-disp-window-function 'ad:appt-disp-window))
     ((eq appt-display-format 'window)
      (advice-add 'appt-disp-window :before #'ad:appt-disp-window))))

  (with-eval-after-load "ivy"
    (defvar counsel-appt-time-msg-list nil)
    (defun counsel-appt-list ()
      "Create a list of appt."
      (setq counsel-appt-time-msg-list nil)
      (when (boundp 'appt-time-msg-list)
        (dolist (msg appt-time-msg-list)
          (when msg
            (add-to-list 'counsel-appt-time-msg-list
                         (substring-no-properties (nth 1 msg)) t))))
      counsel-appt-time-msg-list)

    (defun counsel-appt ()
      "List active appt."
      (interactive)
      (ivy-read "Appt: "
                (counsel-appt-list)
                :require-match t
                :caller 'counsel-appt)))

  ;; (with-eval-after-load "org-agenda"
  ;;   (unless noninteractive
  ;;     (appt-activate 1)))

  (with-eval-after-load "org"
    ;; キャプチャ直後に更新
    (add-hook 'org-capture-before-finalize-hook #'my-org-agenda-to-appt)

    ;; アジェンダを開いたら・終了したらアラームリストを更新
    (unless noninteractive
      (add-hook 'org-agenda-mode-hook #'my-org-agenda-to-appt)
      (add-hook 'org-finalize-agenda-hook #'my-org-agenda-to-appt))

    ;; org-agenda-to-appt を非同期で使うための advice
    (defvar read-char-default-timeout 10)
    (defun ad:read-char-exclusive (f &optional PROMPT INHERIT-INPUT-METHOD SECONDS)
      (funcall f PROMPT INHERIT-INPUT-METHOD
               (or SECONDS read-char-default-timeout)))
    (advice-add 'read-char-exclusive :around #'ad:read-char-exclusive)

    (defun ad:org-check-agenda-file (file)
      "Make sure FILE exists.  If not, ask user what to do."
      (let ((read-char-default-timeout 0)) ;; not nil
        (unless (file-exists-p file)
          (message "Non-existent agenda file %s.  [R]emove from list or [A]bort?"
                         (abbreviate-file-name file))
          (let ((r (downcase (or (read-char-exclusive) ?r))))
            (cond
             ((equal r ?r)
                    (org-remove-file file)
                    (throw 'nextfile t))
             (t (user-error "Abort")))))))
    (advice-add 'org-check-agenda-file :override #'ad:org-check-agenda-file)

    (defun my-add-prop-to-appt-time-msg-list () ;; FIXME
      (let ((msgs appt-time-msg-list))
        (setq appt-time-msg-list nil)
        (dolist (msg msgs)
          (add-to-list 'appt-time-msg-list
                       (list (nth 0 msg)
                             (let ((str (nth 1 msg)))
                               (add-text-properties 6 10 '(org-heading t) str)
                               str)
                             (nth 2 msg))
                       ) t)
        ;; just for sure
        (delq nil appt-time-msg-list)))
    (when (eq window-system 'w32)
      (message "--- my-org-agenda-to-appt-async was changed to nil for w32")
      (setq my-org-agenda-to-appt-async nil))

    (when noninteractive
      (setq my-org-agenda-to-appt-ready nil)) ;; FIXME
    ))

10.25. [org-refile] orgツリーの高速移動

(with-eval-after-load "org"
  ;; リファイル先でサブディレクトリを指定するために一部フルパス化
  (let ((dir (expand-file-name org-directory)))
    (setq org-refile-targets
          `((,(concat dir "next.org") :level . 1)
            (,(concat dir "org-ical.org") :level . 1)
            (,(concat dir "itr.org") :level . 1)
            (,(concat dir "academic.org") :level . 1)
            (,(concat dir "tr/work.org") :level . 1)
            (,(concat dir "minutes/wg1.org") :level . 1)
            (,(concat dir "db/article.org") :level . 1)
            (,(concat dir "db/maybe.org") :level . 1)
            (,(concat dir "db/english.org") :level . 1)
            (,(concat dir "db/money.org") :level . 1))))

  ;; 不要な履歴が生成されるのを抑制し,常に最新を保つ.
  ;; [2/3]のような完了数が見出しにある時に転送先候補が重複表示されるため.
  (defun ad:org-refile (f &optional arg default-buffer rfloc msg)
    "Extension to support keeping org-refile-history empty."
    (save-excursion
      (save-restriction
        (let ((l (org-outline-level))
              (b (buffer-name)))
          (apply f arg default-buffer rfloc msg)
          (if (> l (org-outline-level))
              (outline-backward-same-level 1)
            (outline-up-heading 1))
          (org-update-statistics-cookies nil) ;; Update in source
          ;; (org-sort-entries nil ?O)
          (org-refile-goto-last-stored)
          (org-update-parent-todo-statistics) ;; Update in destination
          (outline-up-heading 1)
          (org-sort-entries nil ?o)
          (unless (equal b (buffer-name))
            (switch-to-buffer b)))
        (setq org-refile-history nil)
        (org-refile-cache-clear))))
  (advice-add 'org-refile :around #'ad:org-refile)

  (defun ad:org-sort-entries (&optional _with-case _sorting-type
                                        _getkey-func _compare-func
                                        _property _interactive?)
    (outline-hide-subtree)
    (org-show-hidden-entry)
    (org-show-children)
    (org-cycle-hide-drawers 'children))
  (advice-add 'org-sort-entries :after #'ad:org-sort-entries))

10.26. [org-babel] Orgバッファでソースコードを扱う

;;;###autoload
(defun my-org-babel-load-activate ()
  (if (featurep 'ob-http)
      (message "org-babel language packages are previously loaded.")
    (message "Loading org-babel language packages...")
    (require 'ob-http nil t)
    (require 'ob-gnuplot nil t)
    (require 'ob-octave nil t)
    (require 'ob-go nil t)
    (require 'ob-async nil t)
    (custom-set-variables ;; will call `org-babel-do-load-languages'
     '(org-babel-load-languages '((emacs-lisp . t)
                                  (dot . t)
                                  (C . t)
                                  (ditaa . t)
                                  (perl . t)
                                  (shell . t)
                                  (latex . t)
                                  (sqlite . t)
                                  (R . t)
                                  (python . t))))
    (message "Loading org-babel language packages...done")))
(with-eval-after-load "org"
  ;; will take 200[ms]
  (unless noninteractive
    (run-with-idle-timer (+ 7 my-default-loading-delay)
                         nil #'my-org-babel-load-activate)))

(with-eval-after-load "ob-src"
  ;; 実装済みの言語に好きな名前を紐付ける
  (add-to-list 'org-src-lang-modes '("cs" . csharp))
  (add-to-list 'org-src-lang-modes '("zsh" . sh)))

(with-eval-after-load "ob-core"
  ;; Suppress showing of "Indentation variables are now local."
  (advice-add 'sh-make-vars-local :around #'ad:suppress-message)
  ;; Suppress showing of "Setting up indent for shell type zsh" and
  ;; "Indentation setup for shell type zsh"
  (advice-add 'sh-set-shell :around #'ad:suppress-message)

  (setq org-edit-src-content-indentation 0)
  (setq org-src-fontify-natively t)
  (setq org-src-tab-acts-natively t)
  (setq org-confirm-babel-evaluate nil)
  (setq org-src-window-setup 'current-window)
  ;; org-src-window-setup (current-window, other-window, other-frame)

  ;; ditta
  ;; (when (and (not noninteractive)
  ;;            (not (executable-find "ditaa")))
  ;;   (message "--- ditaa is NOT installed."))

  ;; (my-org-babel-load-activate)
  )

10.27. [org-babel] ソースブロックの入力キーをカスタマイズ

ソースブロックを入力するときは, <+ <tab> でテンプレートを高速に入力できます.しかし,利用する言語までは指定できないので,特定の内容について対応するコマンドを割り当てて起きます.以下の例を設定として追加すると, <S+ <tab>emacs-lisp を, <C+ <tab> でコメントブロックを指定できます.

(with-eval-after-load "org"
  (add-to-list 'org-structure-template-alist
               (if (version< "9.1.4" (org-version))
                   '("S" . "src emacs-lisp")
                 '("S" "#+begin_src emacs-lisp\n?\n#+END_SRC" "<src lang=\"emacs-lisp\">\n\n</src>"))))

10.28. [org-babel] ソースブロックの配色

org-src-block-faces, org-block-begin-line, org-block-end-line をカスタマイズして,配色を変えます.さらに, prettify-symbols-mode を利用して begin_srcend_src の表示を別な文字列に変えます.

(with-eval-after-load "org-src"
  (defun my-org-src-block-face ()
    (setq org-src-block-faces
          (if (eq 'light (frame-parameter nil 'background-mode))
              '(("emacs-lisp" (:background "#F9F9F9" :extend t))
                ("conf" (:background "#F9F9F9" :extend t))
                ("org" (:background "#F9F9F9" :extend t))
                ("html" (:background "#F9F9F9" :extend t)))
            '(("emacs-lisp" (:background "#383c4c" :extend t))
              ("conf" (:background "#383c4c" :extend t))
              ("org" (:background "#383c4c" :extend t))
              ("html" (:background "#383c4c" :extend t)))))
    (dolist (buffer (buffer-list))
      (with-current-buffer buffer
        (when (derived-mode-p 'org-mode)
          (font-lock-flush)))))
  ;; (my-org-src-block-face)
  (add-hook 'ah-after-enable-theme-hook #'my-org-src-block-face)

  (custom-set-faces
   ;; org-block が効かない(2021-04-13@9.4.4),org-src-block-faces で対応
   ;; '(org-block
   ;;   ((((background dark)) (:background "#383c4c" :extend t)
   ;;     (t (:background "#F9F9F9" :extend t)))))
   '(org-block-begin-line
     ((((background dark))
       (:foreground "#669966" :weight bold)) ;; :background "#444444"
      (t (:foreground "#CC3333" :weight bold)))) ;; :background "#EFEFEF"
   '(org-block-end-line
     ((((background dark)) (:foreground "#CC3333" :weight bold))
      (t (:foreground "#669966" :weight bold))))
   ;; '(org-block-end-line
   ;;   ((((background dark)) (:inherit org-block-begin-line))
   ;;    (t (:inherit org-block-begin-line))))
   ))
(add-hook 'org-mode-hook 'prettify-symbols-mode)
(with-eval-after-load "icons-in-terminal"
  (setq-default prettify-symbols-alist '((":PROPERTIES:" . "") ;;  »
                                         (":LOGBOOK:" . "") ;;  
                                         (":END:" . "") ;;  
                                         ("#+begin_src" . "▨") ;; 
                                         ("#+end_src" . "▨")
                                         ("#+RESULTS:" . "")
                                         ("[ ]" .  "") ;; ☐ 
                                         ("[X]" . "" ) ;; ☑ 
                                         ("[-]" . "" )))) ;; ☒ 

10.29. [org-tree-slide] Org Modeでプレゼンテーション

パワーポイントはもう使わなくてよいのです.

(when (autoload-if-found '(org-tree-slide-mode)
                         "org-tree-slide" nil t)

  (keymap-global-set "<f8>" 'org-tree-slide-mode)
  (keymap-global-set "S-<f8>" 'org-tree-slide-skip-done-toggle)

  (with-eval-after-load "org-tree-slide"
    ;; <f8>/<f9>/<f10>/<f11> are assigned to control org-tree-slide
    (keymap-set org-tree-slide-mode-map "<f9>"
                'org-tree-slide-move-previous-tree)
    (keymap-set org-tree-slide-mode-map "<f10>"
                'org-tree-slide-move-next-tree)
    (unless noninteractive
      (org-tree-slide-narrowing-control-profile))
    (setq org-tree-slide-modeline-display 'outside)
    (setq org-tree-slide-skip-outline-level 5)
    (setq org-tree-slide-skip-done nil)))

Doing タグのトグルに f11 を割り当てたので,コンテンツモードへの切り替えは,異なるキーバインドに変更.

#+begin_src emacs-lisp :results silent
(with-eval-after-load "org-tree-slide"
  (org-tree-slide-presentation-profile))
#+end_src

これをプレゼンのヘッダに置いておけば,プロファイルの切り替えに便利です.

10.29.1. doom-modeline との共存

doom-modeline では widenadvice で拡張していますが, org-tree-slide から抜ける時に期待された効果が得られず, narrowing 状態を表すアイコンがモードロインに残ってしまいます.そこで org-tree-slide-stop-hook で明示的に処理します.

(with-eval-after-load "org-tree-slide"
  (when (and (eq my-toggle-modeline-global 'doom)
             (require 'doom-modeline nil t))
    (add-hook 'org-tree-slide-stop-hook
              #'doom-modeline-update-buffer-file-state-icon)))

10.30. [org-tree-slide] クロックインとアウトを自動化する

特定のファイルを編集している時, org-tree-slide でフォーカスしたら org-clock-in で時間計測を始めて,ナローイングを解く時や次のツリーに移る時に org-clock-out で計測を停止するように設定しています.基本的に org-tree-slide にあるhookに色々とぶら下げるだけです.

(with-eval-after-load "org-tree-slide"
  (defun my-tree-slide-autoclockin-p ()
    (save-excursion
      (save-restriction
        (widen)
        (goto-char (point-min))
        (let ((keyword "TREE_SLIDE:")
              (value "autoclockin")
              (result nil))
          (while
              (and (re-search-forward (concat "^#\\+" keyword "[ \t]*") nil t)
                   (re-search-forward value (point-at-eol) t))
            (setq result t))
          result))))

  (when (require 'org-clock nil t)
    (defun my-org-clock-in ()
      (unless (bound-and-true-p doom-modeline-mode)
        (setq vc-display-status nil)) ;; モードライン節約
      (when (and (my-tree-slide-autoclockin-p)
                 (looking-at (concat "^\\*+ " org-not-done-regexp))
                 (memq (org-outline-level) '(1 2 3 4)))
        (save-excursion
          (save-restriction
            (forward-line)
            (when (org-at-heading-p)
              (newline)))) ;; FIXME: remove empty line if clock will not be recorded.
        (org-clock-in)))

    (defun my-org-clock-out ()
      (setq vc-display-status t) ;; モードライン節約解除
      (when (org-clocking-p)
        (org-clock-out)))

    (add-hook 'org-tree-slide-before-move-next-hook #'my-org-clock-out)
    (add-hook 'org-tree-slide-before-move-previous-hook #'my-org-clock-out)
    ;; (add-hook 'org-tree-slide-before-content-view-hook #'my-org-clock-out)
    (add-hook 'org-tree-slide-stop-hook #'my-org-clock-out)
    (add-hook 'org-tree-slide-after-narrow-hook #'my-org-clock-in)))

10.31. [org-tree-slide] 特定のツリーをプロポーショナルフォントで表示する

ツリーのプロパティに,プロポーショナルで表示するか否かの制御フラグを加えます.ツリーにフォーカス時に PROPORTIONAL 指定がプロパティにあると,そのツリーを動的にプロポーショナルフォントでレンダリングします.変更は下位ツリーの全てに継承しています.

(when (autoload-if-found '(org-tree-slide-mode my-toggle-proportional-font)
                         "org-tree-slide" nil t)
  (with-eval-after-load "org-tree-slide"
    (defcustom use-proportional-font nil
      "The status of FONT property"
      :type 'boolean
      :group 'org-mode)

    (set-face-attribute 'variable-pitch nil
                        :family "Verdana"
                        ;; :family "Comic Sans MS"
                        :height 125)

    (defun my-toggle-proportional-font ()
      (interactive)
      (setq use-proportional-font (not use-proportional-font))
      (if use-proportional-font
          (org-entry-put nil "FONT" "PROPORTIONAL")
        (org-delete-property "FONT")))

    (add-hook 'org-tree-slide-before-narrow-hook
              (lambda ()
                  (if (equal "PROPORTIONAL"
                             (org-entry-get-with-inheritance "FONT"))
                      (buffer-face-set 'variable-pitch)
                    (buffer-face-mode 0))))
    (add-hook 'org-tree-slide-stop-hook
              (lambda ()
                  (buffer-face-mode 0)))))

10.32. [org-tree-slide] ヘッドラインをリッチにする

org-tree-slide が有効な時だけ org-bullets を有効にして,ヘッドラインをリッチにします.元ネタは, org-beautify-theme.el です.

(when (autoload-if-found '(org-bullets-mode)
                         "org-bullets" nil t)
  (add-hook 'org-tree-slide-play-hook (lambda () (org-bullets-mode 1)))
  (add-hook 'org-tree-slide-stop-hook (lambda () (org-bullets-mode -1))))

10.33. [org-tree-slide] 一時的に #+ATTR_ORG などを非表示にする

プレゼンテーション実行時に,制御コードが記述されている #+ATTR_ORG などの行を非表示(背景と同じ色)にします.

(defvar my-hide-org-meta-line-p nil)
(defun my-hide-org-meta-line ()
  (interactive)
  (setq my-hide-org-meta-line-p t)
  (set-face-attribute 'org-meta-line nil
                                        :foreground (face-attribute 'default :background)))
(defun my-show-org-meta-line ()
  (interactive)
  (setq my-hide-org-meta-line-p nil)
  (set-face-attribute 'org-meta-line nil :foreground nil))

(defun my-toggle-org-meta-line ()
  (interactive)
  (if my-hide-org-meta-line-p
            (my-show-org-meta-line) (my-hide-org-meta-line)))

(add-hook 'org-tree-slide-play-hook #'my-hide-org-meta-line)
(add-hook 'org-tree-slide-stop-hook #'my-show-org-meta-line)

;; Option
(defun my-update-org-meta-line ()
  (interactive)
  (when my-hide-org-meta-line-p
    (my-hide-org-meta-line)))
(add-hook 'ah-after-enable-theme-hook #'my-update-org-meta-line)

10.34. [calfw-org.el] calfw に org の予定を表示する

org-mode の表のようにフェイスを統一しています. calfw を起動する時に,自動的にフレームサイズを拡大するような独自関数をぶら下げています.

;; init-org.el
(when (autoload-if-found '(my-cfw-open-org-calendar cfw:open-org-calendar)
                         "calfw-org" "Rich calendar for org-mode" t)

  (keymap-global-set "C-c f c w" 'my-cfw-open-org-calendar)
  (with-eval-after-load "calfw-org"
    ;; icalendar との連結
    (custom-set-variables
     '(cfw:org-icalendars '("~/Dropbox/org/org-ical.org"))
     '(cfw:fchar-junction ?+) ;; org で使う表にフェイスを統一
     '(cfw:fchar-vertical-line ?|)
     '(cfw:fchar-horizontal-line ?-)
     '(cfw:fchar-left-junction ?|)
     '(cfw:fchar-right-junction ?|)
     '(cfw:fchar-top-junction ?+)
     '(cfw:fchar-top-left-corner ?|)
     '(cfw:fchar-top-right-corner ?|))

    (defun my-org-mark-ring-goto-calfw ()
      (interactive)
      (org-mark-ring-goto))

    (defun my-cfw-open-org-calendar ()
      (interactive)
      (moom-change-frame-width-double)
      (cfw:open-org-calendar))

    (defun my-cfw-burry-buffer ()
      (interactive)
      (bury-buffer)
      (moom-change-frame-width-single))

    (defun cfw:org-goto-date ()
      "Move the cursor to the specified date."
      (interactive)
      (cfw:navi-goto-date
       (cfw:emacs-to-calendar (org-read-date nil 'to-time))))

    (keymap-set cfw:calendar-mode-map "j" 'cfw:org-goto-date)
    (keymap-set cfw:org-schedule-map "q" 'my-cfw-burry-buffer)))

;;         (add-hook 'window-configuration-change-hook #'cfw:resize-calendar)
;; (defun cfw:resize-calendar ()
;;   (interactive)
;;   (when (eq major-mode 'cfw:calendar-mode)
;;     (cfw:refresh-calendar-buffer nil)
;;     (message "Calendar resized.")))

;; (defun open-calfw-agenda-org ()
;;   (interactive)
;;   (cfw:open-org-calendar))

;; (setq org-agenda-custom-commands
;;       '(("w" todo "FOCUS")
;;         ("G" open-calfw-agenda-org "Graphical display in calfw"))))))

10.35. [org-odt] ODT形式に出力

(when (autoload-if-found '(ox-odt)
                         "ox-odt" nil t)
  (with-eval-after-load "ox-odt"
    ;; (add-to-list 'org-odt-data-dir
    ;;              (concat (getenv "HOME") "/Dropbox/emacs.d/config/"))
    (setq org-odt-styles-file
          (concat (getenv "SYNCROOT") "/emacs.d/config/style.odt"))
    ;; (setq org-odt-content-template-file
    ;;       (concat (getenv "HOME") "/Dropbox/emacs.d/config/style.ott"))
    (setq org-odt-preferred-output-format "pdf") ;; docx
    ;; ;; ox-odt.el の 自作パッチの変数(DOCSTRINGが記述されていない)
    ;; (setq org-odt-apply-custom-punctuation t)
    (setq org-odt-convert-processes
          '(("LibreOffice"
             "/Applications/LibreOffice.app/Contents/MacOS/soffice --headless --convert-to %f%x --outdir %d %i")
            ("unoconv" "unoconv -f %f -o %d %i")))))

10.36. [ox-twbs] Twitter Bootstrap 互換のHTML出力

(with-eval-after-load "ox"
  (require 'ox-twbs nil t))

次のようなファイルを準備して org ファイルのヘッダで指定( #+setupfile: theme-readtheorg.setup )するだけで, https://takaxp.github.io/ のような HTML 出力もできます.

# -*- mode: org; -*-
#+html_head: <link rel="stylesheet" type="text/css" href="https://www.pirilampo.org/styles/readtheorg/css/htmlize.css"/>
#+html_head: <link rel="stylesheet" type="text/css" href="https://www.pirilampo.org/styles/readtheorg/css/readtheorg.css"/>

#+html_head: <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
#+html_head: <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
#+html_head: <script type="text/javascript" src="https://www.pirilampo.org/styles/lib/js/jquery.stickytableheaders.min.js"></script>
#+html_head: <script type="text/javascript" src="https://www.pirilampo.org/styles/readtheorg/js/readtheorg.js"></script>

また, (org-html-htmlize-generate-css) を使うとCSSを生成できます.

10.37. [org-crypt] ツリーを暗号化する

M-x org-encrypt-entry でカーソル位置のツリーを暗号化できます.復号は, M-x org-decrypt-entry にて.ただし,バッファのバックアップファイルが生成されていることに気をつけてください.自分の場合は,バックアップファイルは外部ホストに同期されない設定にしてあるので,とりあえず問題なしと考えています.

M-x org-encrypt-entries で,特定のタグが付けられたツリーを一括処理することもできますが,私は安全性を考慮して使っていません.

なお,実戦投入には十分なテストをしてからの方がよいでしょう.org バッファを外部ホストと同期している場合,転送先のホストでも暗号化/復号ができるかを確認するべきです.他方のホストでツリーにドロワーが付くと,復号できなくなったりします.その時は慌てずにプロパティのドロワーを削除すればOKです.

(with-eval-after-load "org"
  (when (require 'org-crypt nil t)
    (require 'epa)
    (setq org-crypt-key "") ;; <insert your key>
    ;; org-encrypt-entries の影響を受けるタグを指定
    (setq org-tags-exclude-from-inheritance (quote ("secret")))
    ;; 自動保存の確認を無効に
    (setq org-crypt-disable-auto-save 'nil)))

10.38. [org-crypt] ツリーを復号して中身を取り出す

(defvar my-org-delete-saved-item-timer nil)

;;;###autoload
(defun my-delete-last-saved-string ()
  (setq kill-ring (cdr kill-ring)))

;;;###autoload
(defun my-get-content-with-decrypt ()
  (interactive)
  (if (not (org-at-encrypted-entry-p))
      (echo "--- Do nothing, the subtree is NOT encrypted.")
    (outline-hide-subtree) ;; FIXME
    (org-decrypt-entry)
    (unless (org-at-heading-p)
      (org-back-to-heading))
    (org-end-of-meta-data t)
    (kill-ring-save (point)
                    (org-element-property :end (org-element-at-point)))
    (org-encrypt-entry)
    (outline-hide-subtree) ;; FIXME
    (org-back-to-heading)
    (if (org-at-encrypted-entry-p)
        (message "--- secured.")
      (error "Not secured"))
    (when (timerp my-org-delete-saved-item-timer)
      (cancel-timer my-org-delete-saved-item-timer))
    ;; FIXME should also run cancel-timer when yank only one time.
    (setq my-org-delete-saved-item-timer
          (run-with-timer 5 nil #'my-delete-last-saved-string))))

10.39. [org-mac-link] 外部アプリから情報を取る

org-mac-link を使うと,外部アプリの表示状態をリンクとして取得して,orgバッファに流し込めます.Mac環境用です.簡単な例ではURLで,取得したリンクを C-c C-o で開けばブラウザが起動してリンク先が表示できます.同じ話を,ファインダーで表示しているディレクトリ,メーラーで表示していた特定のメール,PDFビューアで表示していた特定のファイルの特定のページなどで実施できます.対応している外部アプリは,Finder, Mail.app, Outlook, Addressbook, Safari, Firefox, Chrome, そして Skimです.

次のように設定すると,org-modeの時に C-c c すればミニバッファにどのアプリからリンク情報を取るか選べます.Chrome には c が当たっているので,ブラウジング中に記になる記事があったらEmacsに切り替えて, C-c c c とすると,URLが自動でバッファに入ります.単なるURLではなく,タイトルで表示されるのでわかりやすいです.

(with-eval-after-load "org"
  ;; (add-to-list 'org-modules 'org-mac-iCal)
  ;; (add-to-list 'org-modules 'org-mac-link) ;; includes org-mac-message

  (autoload 'org-mac-link-get-link "org-mac-link" nil t)
  (keymap-set org-mode-map "C-c c" 'org-mac-link-get-link)
  (with-eval-after-load "org-mac-link"
    (require 'org-mac-iCal nil t)))

10.40. [org-download] org バッファにD&Dでファイルを保存

org-attach とペアで使うのが良いでしょう.

(with-eval-after-load "org-attach"
  (when (require 'org-download nil t)
    (setq org-download-screenshot-method 'screencapture)
    (setq org-download-method 'attach)))

10.41. [org-grep] org ファイルを grep する

(when (autoload-if-found '(org-grep)
                         "org-grep" nil t)
  ;;   (keymap-global-set "C-M-g" 'org-grep)
  (with-eval-after-load "org-grep"
    (setq org-grep-extensions '(".org" ".org_archive"))
    (add-to-list 'org-grep-directories "~/.emacs.d")
    (add-to-list 'org-grep-directories "~/.emacs.d/.cask/package")

    ;; "q"押下の挙動を調整
    (defun ad:org-grep-quit ()
      (interactive)
      (delete-window))
    (advice-add 'org-grep-quit :override #'ad:org-grep-quit)

    ;; for macOS
    (when (memq window-system '(mac ns))
      (defun org-grep-from-org-shell-command (regexp)
        (if org-grep-directories
            (concat "find -E "
                    (if org-grep-directories
                        (mapconcat #'identity org-grep-directories " ")
                      org-directory)
                    (and org-grep-extensions
                         (concat " -regex '.*("
                                 (mapconcat #'regexp-quote org-grep-extensions
                                            "|")
                                 ")$'"))
                    " -print0 | xargs -0 grep " org-grep-grep-options
                    " -n -- " (shell-quote-argument regexp))
          ":")))))

10.42. [ox-reveal] ナイスなHTML5プレゼンテーション出力

C-c C-e R エクスポータを呼び出せます.続けて B を押せば,ブラウザで出力後の見た目を確認できます.ただし別途 reveal.js が使える状態にないとダメです. org-reveal-root を設定すれば,clone した reveal.js の場所を指定できます.

(with-eval-after-load "ox"
  (when (and (require 'ox-reveal nil t)
             (version< "9.1.4" (org-version)))
    (setq org-reveal-note-key-char ?n)))

それ以外にも ox-s5org-ioslide があります.

10.43. [org-dashboard] 進捗をプログレスバーで確認

;; init-org.el
(when (autoload-if-found '(org-dashboard-display)
                         "org-dashboard" nil t)
  (with-eval-after-load "org"
    (keymap-set org-mode-map "C-c f y" 'org-dashboard-display)))

10.44. [org-clock-today] 今日の総作業時間をモードラインに表示

(with-eval-after-load "org-clock-today"
  (defun my-print-working-clocks ()
    (interactive)
    (let ((clocked-item (org-duration-from-minutes
                         (org-clock-get-clocked-time))))
      (if org-clock-today-mode
          (message "Today Subtree %s Total %s | Past %s"
                   org-clock-today--buffer-time
                   org-clock-today--subtree-time
                   clocked-item)
        (message "Past %s" clocked-item)))))

(with-eval-after-load "org-clock"
  (defun ad:org-clock-sum-today (&optional headline-filter)
    "Sum the times for each subtree for today."
    (let ((range (org-clock-special-range 'today nil t))) ;; TZ考慮
      (org-clock-sum (car range) (cadr range)
                     headline-filter :org-clock-minutes-today)))
  (advice-add 'org-clock-sum-today :override #'ad:org-clock-sum-today)

  ;; using folked package
  (when (require 'org-clock-today nil t)
    (unless noninteractive
      (setq org-clock-today-count-subtree t)
      (org-clock-today-mode 1))))

10.45. [org-random-todo] ランダムにタスクを選ぶ

  • デフォルトで org-agenda-files に登録されたファイルからランダムにタスクを選ぶ
  • org-random-todo はミニバッファに選択したタスクを表示するだけ.
  • org-random-todo-goto-current 最後に表示したタスクに移動する.
;; init-org.el
(autoload-if-found '(org-random-todo org-random-todo-goto-current)
                   "org-random-todo" nil t)

10.46. [ox-hugo] OrgファイルからHogoの記事をエクスポートする

  • Org Mode でブログを書いて, Hugoで静的サイトに出力します.
;;;###autoload
(defun my-add-ox-hugo-lastmod ()
  "Add `lastmod' property with the current time."
  (interactive)
  (org-set-property "EXPORT_HUGO_LASTMOD"
                    (format-time-string "[%Y-%m-%d %a %H:%M]")))

;;;###autoload
(defun ad:ox-hugo:org-todo (&optional ARG)
  "Export subtree for Hugo if the TODO status in ARG is changing to DONE."
  (when (and (equal (buffer-name) "imadenale.org")
             ;; FIXME C-c C-t d に反応しない.speed command はOK.
             (or (eq ARG 'done)
                 (equal ARG "DONE")))
    (org-hugo-export-wim-to-md)
    (message "[ox-hugo] \"%s\" has been exported."
             (nth 4 (org-heading-components)))
    (let ((command "/Users/taka/Dropbox/scripts/push-hugo.sh"))
      (if (require 'async nil t)
          (async-start
           `(lambda () (shell-command-to-string ',command)))
        (shell-command-to-string command)))))
;; see https://ox-hugo.scripter.co/doc/deprecation-notices/#org-hugo-auto-export-feature-now-a-minor-mode
;; (with-eval-after-load "org"
;; No need for latest ox-hugo
;;   ;; Require ox-hugo-auto-export.el explictly before loading ox-hugo.el
;;   (require 'ox-hugo-auto-export nil t))

(when (autoload-if-found
       '(org-hugo-export-wim-to-md)
       "ox-hugo" nil t)

  (with-eval-after-load "ox-hugo"
    (setq org-hugo-auto-set-lastmod nil) ;; see my-hugo-export-md
    (setq org-hugo-suppress-lastmod-period 86400.0) ;; 1 day
    ;; never copy files to under /static/ directory
    (setq org-hugo-external-file-extensions-allowed-for-copying nil)

    ;; see https://pxaka.tokyo/blog/2018/a-link-to-the-original-org-source-file
    (defun org-hugo-get-link-to-orgfile (uri alt)
      "Return a formatted link to the original Org file.
To insert the formatted into an org buffer for Hugo, use an appropriate
macro, e.g. {{{srclink}}}.

Note that this mechanism is still under consideration."
      (let ((line (save-excursion
                    (save-restriction
                      (org-back-to-heading t)
                      (line-number-at-pos)))))
        (concat "[[" uri (file-name-nondirectory (buffer-file-name))
                "#L" (format "%d" line) "][" alt "]]")))
    ;;    (advice-add 'org-todo :after #'ad:ox-hugo:org-todo)
    ))

10.47. [ox-html] HTML見出しへのリンクを固定する

標準の設定でHTMLを出力すると,各見出しへのリンクとして org0123456 のようなIDが振られる.しかしそれらのIDは,HTMLを出力するたびに更新されるため,出力したHTMLの見出しをパーマリンクとして使用できない.これを防ぐためには,プロパティで CUSTOM_ID を指定する必要がある.

これを簡単にするための補助関数が公開されている.この元ネタを参考に,少し改良して使用している.なお元ネタでは標準の org-id-new を直接利用しているが,以下の実装では, org の後ろにUUIDの先頭8桁が続くように,IDの内容に制限をかけている.

my-add-org-ids-to-headlines-in-file は,バッファの保存時点,或いは,HTMLエクスポートが走る直前のタイミングで自動実行させるのがスマートだが,当面は手動で運用する.

(with-eval-after-load "ox-html"
  (setq org-html-text-markup-alist
        '((bold . "<b>%s</b>")
          (code . "<code class=\"org-code\">%s</code>")
          (italic . "<i>%s</i>")
          (strike-through . "<del>%s</del>")
          (underline . "<span class=\"org-underline\">%s</span>")
          (verbatim . "<code class=\"org-verbatim\">%s</code>"))))

(with-eval-after-load "org"
  (defun my-add-custom-id ()
    "Add \"CUSTOM_ID\" to the current tree if not assigned yet."
    (interactive)
    (my-org-custom-id-get (point) t))

  (defun my-get-custom-id ()
    "Return a part of UUID with an \"org\" prefix.
e.g. \"org3ca6ef0c\"."
    (let* ((id (org-id-new "")))
      (when (org-uuidgen-p id)
        (downcase (concat "org"  (substring (org-id-new "") 0 8))))))

  (defun my-org-custom-id-get (pom &optional create)
    "Get the CUSTOM_ID property of the entry at point-or-marker POM.
If the entry does not have an CUSTOM_ID, the function returns nil.
However, when CREATE is non nil, create a CUSTOM_ID if none is present
already.  In any case, the CUSTOM_ID of the entry is returned.

See https://writequit.org/articles/emacs-org-mode-generate-ids.html"
    (interactive)
    (let ((id (org-entry-get nil "CUSTOM_ID")))
      (cond
       ((and id (stringp id) (string-match "\\S-" id))
        id)
       (create
        (setq id (my-get-custom-id))
        (unless id
          (error "Invalid ID"))
        (org-entry-put pom "CUSTOM_ID" id)
        (message "--- CUSTOM_ID assigned: %s" id)
        (org-id-add-location id (buffer-file-name (buffer-base-buffer)))
        id))))

  ;;;###autoload
  (defun my-add-org-ids-to-headlines-in-file ()
    "Add CUSTOM_ID properties to all headlines in the current file.
Which do not already have one.  Only adds ids if the
`auto-id' option is set to `t' in the file somewhere. ie,
#+options: auto-id:t

See https://writequit.org/articles/emacs-org-mode-generate-ids.html"
    (interactive)
    (save-excursion
      (widen)
      (goto-char (point-min))
      (when (re-search-forward "^#\\+options:.*auto-id:t" (point-max) t)
        (org-map-entries
         (lambda () (my-org-custom-id-get (point) 'create))))))

  (defvar md-link-format "^!\\[\\(.+\\)\\](\\(.+\\))$")
  (defun my-convert-md-link-to-html ()
    (interactive)
    (goto-char (point-min))
    (while (re-search-forward md-link-format nil :noerror)
      (let* ((prev (match-string-no-properties 0))
             (alt (match-string-no-properties 1))
             (src (match-string-no-properties 2))
             (new (concat "<p><img src=\"" src "\" alt=\"" alt "\" /></p>")))
        (replace-match new)
        (message "====\ninput:\t%s\noutput:\t%s" prev new)))
    (message "--- done.")))

10.48. [org-id.el] プロパティに ID を入れる

org.el に付属している org-id.el を使うと,各 heading のプロパティにユニークな id を付与できます.また, org-clock-in で工数計測を開始すると,自動的に付与されます.しかし, heading を不用意にコピペすると,id が重複するので,それらを削除するユーティリティを準備しました.

(with-eval-after-load "org"
  (defun my-delete-all-id-in-file ()
    (interactive)
    (goto-char 1)
    (while (not (eq (point) (point-max)))
      (org-next-visible-heading 1)
      (let ((id (org-entry-get (point) "ID")))
        (when id
          (message "ID: %s" id)
          (org-delete-property "ID"))))
    (message "--- done.")))

10.49. [orglink.el] Orgファイル以外でリンクを使う

指定したモードのバッファで, org mode と同じリンク機能を使えるようにします.

(when (autoload-if-found '(orglink-mode
                           global-orglink-mode my-orglink-mode-activate)
                         "orglink" nil t)
  (defun my-orglink-mode-activate ()
    (orglink-mode 1)
    (setq orglink-mode-lighter "")
    ;; バッファローカルに色つけを消す
    (face-remap-add-relative 'org-link
                             :underline nil
                             :inherit font-lock-comment-delimiter-face))

  (dolist (hook '(emacs-lisp-mode-hook c-mode-common-hook yatex-mode))
    (add-hook hook #'my-orglink-mode-activate))

  (with-eval-after-load "orglink"
    (delq 'angle orglink-activate-links)
    (keymap-set orglink-mouse-map "C-c C-o" 'org-open-at-point-global)
    (keymap-set orglink-mouse-map "C-c C-l" 'org-insert-link)))

;; (add-to-list 'orglink-activate-in-modes 'c++-mode)
;; (add-to-list 'orglink-activate-in-modes 'c-mode)
;; (when (require 'orglink nil t)
;;   (global-orglink-mode))

10.50. [org-trello.el] Trello と双方向同期する

Trello と特定の org バッファを同期します.コンフリクトを避けるために,明示的に pullpush をする関数を定義して使います.非常に強力で,外出時に簡易的にモバイル端末でタスクを追加したり,ブラウザでグラフィカルにタスクを追加できます.

落ち着いてEmacsを使う状況になたら, pull してバッファに反映します. DONE になったタスクや,別な org バッファで管理したいはタスクは, org-refile で移してしまえば良いです.そして,整理ができたら, push して,次のモバイル環境での編集に備えます.

;;;###autoload
(defun my-push-trello-card () (interactive) (org-trello-sync-card))

;;;###autoload
(defun my-pull-trello-card () (interactive) (org-trello-sync-card t))

;;;###autoload
(defun my-push-trello () (interactive) (org-trello-sync-buffer))

;;;###autoload
(defun my-pull-trello () (interactive) (org-trello-sync-buffer t))

;;;###autoload
(defun my-activate-org-trello ()
  (let ((filename (buffer-file-name (current-buffer))))
    (when (and filename
               (string= "trello" (file-name-extension filename))
               (require 'org-trello nil t))
      (org-trello-mode))))
;; 1. TODO/DOING/DONE に trello 側のカードを変えておく.
;; 2. M-x org-trello-install-key-and-token
;; ~/.emacs.d/.trello/<account>.el が作られる
;; 3. M-x org-trello-install-board-metadata
;; Trello 側の情報を基にして current-buffer にプロパティブロックが挿入される
;; 4. C-u M-x org-trello-sync-buffer で pull
;; 5. M-x org-trello-sync-buffer で push
(when (autoload-if-found '(my-push-trello my-pull-trello my-activate-org-trello)
                         "org-trello" nil t)
  (defvar org-trello-current-prefix-keybinding nil) ;; To avoid an error
  (add-to-list 'auto-mode-alist '("\\.trello$" . org-mode))
  (with-eval-after-load "org"
    (add-hook 'org-mode-hook #'my-activate-org-trello)))

10.51. [ox.el] ディスパッチャを水平方向の分割画面に表示

(with-eval-after-load "ox"
  (defvar my-org-export-before-hook nil)
  (defvar my-org-export-after-hook nil)
  (defvar my-org-export-last-buffer nil)

  (defun my-org-export--post-processing ()
    (when (eq this-command 'org-export-dispatch)
      (let ((moom-verbose nil))
        (run-hooks 'my-org-export-after-hook)
        moom-verbose) ;; to avoid a warning on lexical variable
      (remove-hook 'my-org-export-before-hook 'moom-split-window)
      (remove-hook 'my-org-export-before-hook 'split-window-horizontally)
      (remove-hook 'my-org-export-after-hook 'moom-delete-windows)
      (remove-hook 'my-org-export-after-hook 'delete-window))
    (when this-command
      (remove-hook 'post-command-hook #'my-org-export--post-processing)))

  (defun my-org-export-dispatch (f ARG)
    (cond
     (org-export-dispatch-use-expert-ui
      nil)
     ((eq (frame-width) 80)
      (when (require 'moom nil t)
        (add-hook 'my-org-export-before-hook 'moom-split-window)
        (add-hook 'my-org-export-after-hook 'moom-delete-windows)))
     ((> (frame-width) 160)
      (add-hook 'my-org-export-before-hook 'split-window-horizontally)
      (add-hook 'my-org-export-after-hook 'delete-window)))
    (when my-org-export-after-hook
      (add-hook 'post-command-hook #'my-org-export--post-processing))
    (run-hooks 'my-org-export-before-hook)
    (apply f ARG))
  (advice-add 'org-export-dispatch :around #'my-org-export-dispatch)

  (defun my-org-export-insert-default-template (f &optional backend subtreep)
    (let ((this-command nil))
      (apply f backend subtreep)))
  (advice-add 'org-export-insert-default-template :around
              #'my-org-export-insert-default-template)

  (defun my-org-export-to-buffer (_backend
                                  buffer
                                  &optional _async _subtreep _visible-only
                                  _body-only _ext-plist _post-process)
    (setq my-org-export-last-buffer buffer))
  (advice-add 'org-export-to-buffer :after #'my-org-export-to-buffer)

  (defun my-copy-exported-buffer ()
    (interactive)
    (when my-org-export-last-buffer
      (with-current-buffer my-org-export-last-buffer
        (mark-whole-buffer)
        (kill-ring-save (point-min) (point-max))
        (message "Copied: %s" my-org-export-last-buffer))
      (setq my-org-export-last-buffer nil)))
  (add-hook 'my-org-export-after-hook #'my-copy-exported-buffer))

10.52. TODO [org-extra-emphasis] 強調表現を拡張する

主にマーカー(黄色や赤色)を導入するために, org mode の強調表現を拡張します. !! で黄色マーキング, !@ で文字色を赤色に変更できます.

(autoload 'skewer-html-mode "skewer-html" nil t)
(unless noninteractive
  (add-hook 'org-mode-hook 'skewer-html-mode)
  (autoload-if-found '(org-extra-emphasis-mode) "org-extra-emphasis" nil t))

10.53. TODO [org-appear.el] カーソルを置いた時だけマークアップを表示する

太字や強調表示など,所定の記号でマークアップすると face を変えられますが,通常時には記号を表示しないで,カーソルを置いた時だけ表示するようにできます.

;;;###autoload
(defun my-toggle-org-show-emphasis-markers ()
  (interactive)
  (setq org-hide-emphasis-markers (not org-hide-emphasis-markers))
  (dolist (buffer (buffer-list))
    (with-current-buffer buffer
      (when (derived-mode-p 'org-mode)
        (font-lock-flush)))))
(when (autoload-if-found '(org-appear-mode)
                         "org-appear" nil t)
  (setq org-hide-emphasis-markers t)
  (add-hook 'org-mode-hook 'org-appear-mode)
  (with-eval-after-load "org-appear"
    (setq org-appear-trigger 'on-change))) ;; 編集中だけマークアップを表示できる

10.54. TODO [ob-async] ソースブロックの非同期実行

ソースブロックを非同期に実行します.各ソースブロックで :async を指定するだけで,非同期になります. ただし, ob-python はすでに :async を予約しているので, ob-async のオプションと衝突しないように,処理対象から除外しておきます.

ob-async のロードは, my-org-babel-load-activate で実行します.

(with-eval-after-load "ob-async"
  (custom-set-variables
   '(ob-async-no-async-languages-alist '("ipython"))))

10.55. TODO [org-tree-slide] BEGIN_SRCとEND_SRCを消して背景色を変える

hide-lines.el を使うことで,プレゼン時に BEGIN_SRCENC_SRC を非表示にしてすっきりさせます.さらに,ソースブロックの背景色を変えます(以下の例では, emacs-lisp を言語で指定している場合に限定).

(when (autoload-if-found '(org-tree-slide-mode)
                         "org-tree-slide" nil t)
  (with-eval-after-load "org-tree-slide"
    ;; FIXME 複数のバッファで並行動作させるとおかしくなる.hide-lines の問題?
    ;; prettify-symbols で置き換えるほうが良い
    (when (and nil (require 'hide-lines nil t))
      (defvar my-org-src-block-faces nil)
      (defun my-show-headers ()
        (setq org-src-block-faces 'my-org-src-block-faces)
        (hide-lines-show-all))
      (defun my-hide-headers ()
        (setq my-org-src-block-faces 'org-src-block-faces)
        (setq org-src-block-faces
              '(("emacs-lisp" (:background "cornsilk"))))
        (hide-lines-matching "#\\+BEGIN_SRC")
        (hide-lines-matching "#\\+END_SRC"))
      (add-hook 'org-tree-slide-play-hook #'my-hide-headers)
      (add-hook 'org-tree-slide-stop-hook #'my-show-headers)

      ;; (defun ad:org-edit-src-code (&optional code edit-buffer-name)
      (defun ad:org-edit-src-code ()
        (interactive)
        (my-show-headers))
      (advice-add 'org-edit-src-code :before #'ad:org-edit-src-code)
      ;; Block 外で呼ばれると,my-show-headers が呼ばれてしまう
      (defun ad:org-edit-src-exit ()
        (interactive)
        (my-hide-headers))
      (advice-add 'org-edit-src-exit :after #'ad:org-edit-src-exit))))

10.56. TODO [org-fstree] ディレクトリ構造を読み取る

(with-eval-after-load "org"
  (require 'org-fstree nil t))

10.57. TODO [ox.el] 出力形式の拡張

(with-eval-after-load "ox"
  ;; (setq org-export-default-language "ja")
  (if (eq system-type 'darwin)
      (require 'ox-pandoc nil t)
    (message "--- pandoc is NOT configured for Windows or Linux."))
  (require 'ox-qmd nil t) ;; Quita-style
  (require 'ox-gfm nil t)) ;; GitHub-style

10.58. TODO Parers3.app からリンクを取得する

(with-eval-after-load "org-mac-link"
  (defcustom org-mac-grab-Papers-app-p t
    "Add menu option [P]apers to grab links from the Papers.app."
    :tag "Grab Papers.app links"
    :group 'org-mac-link
    :type 'boolean)

  (defun org-mac-papers-insert-frontmost-paper-link ()
    (interactive)
    (let ((result (org-mac-papers-get-frontmost-paper-link)))
      (if result
          (insert result)
        (message "Please open Papers.app and select a paper."))))

  (defun org-mac-papers-get-frontmost-paper-link ()
    (interactive)
    (message "Applescript: Getting Papers link...")
    (let ((result (org-as-mac-papers-get-paper-link)))
      (if (or (eq result nil) (string= result ""))
          nil
        (org-mac-paste-applescript-links result))))

  (defun org-as-mac-papers-get-paper-link ()
    (do-applescript
     (concat
      "if application \"Papers\" is running then\n"
      " tell application \"Papers\" to activate\n"
      " delay 0.3\n"
      " set the clipboard to \"\"\n"
      " tell application \"System Events\" to tell process \"Papers\"\n"
      "         keystroke \"l\" using {command down, shift down}\n"
      " end tell\n"
      " delay 0.2\n"
      " set aLink to the clipboard\n"
      " tell application \"System Events\" to tell process \"Papers\"\n"
      ;; "              keystroke \"c\" using {command down, alt down\}\n"
      "         keystroke \"m\" using {command down, option down\}\n"
      " end tell\n"
      " delay 0.2\n"
      " set aName to the clipboard\n"
      " tell application \"Emacs\" to activate\n"
      " return (get aLink) & \"::split::\" & (get aName) as string\n"
      "else\n"
      " return\n"
      "end if\n")))

  (when (boundp 'org-mac-link-descriptors)
    (add-to-list 'org-mac-link-descriptors
                 `("P" "apers" org-mac-papers-insert-frontmost-paper-link
                   ,org-mac-grab-Papers-app-p) t)))

10.59. TODO Papers3.app のリンクを開けるようにする

Papers3.app は,各文献に papers3:// で始まるURIを割り当てています.このリンクを org バッファにペーストし, org-open-at-point (C-c C-o) で開けるようにします.

(with-eval-after-load "org"
  (when (eq system-type 'darwin)
    ;; Open `papers3://' link by C-c C-o.
    ;; (org-add-link-type will be obsoleted from Org 9.
    (when (fboundp 'org-link-set-parameters)
      (org-link-set-parameters
       "papers3"
       :follow (lambda (path)
                 (let ((cmd (concat "open papers3:" path)))
                   (shell-command-to-string (shell-quote-argument cmd))
                   (message "%s" cmd)))))))