/emacs.d

This is my Emacs configuration.

Primary LanguageEmacs Lisp

My Emacs configuration files.

https://res.cloudinary.com/symdon/image/upload/v1645157040/demo_spyojf.gif

This repository is settings for my Emacs.

File
./init.elRemember, the Force will be with you, always.
./themes/simple-darkness-theme.elThat leads to the dark side.
./distributions/Put submodules of Emacs distributions.
./spacemacs/Settings for spacemacs.
(message "Loading ~/.emacs.d/README.org")

Setup

git clone git@github.com:TakesxiSximada/emacs.d.git ~/.emacs.d
pip3 install -U --user -r python-mode-requirements.txt
npm install -g prettier

環境変数関連ユーティリティ

(defun getenv+ (name)
  "環境変数から値を取得し、さもなければシンボルから値を取得する"
  (or (getenv name)
      (symbol-value (intern-soft name))))

パッケージング関連パッケージ

(unless (package-installed-p 'use-package)
  (package-install 'use-package))
(require 'use-package)

(use-package quelpa :ensure t :defer t)
(use-package quelpa-use-package :ensure t :defer t)
(use-package el-get :ensure t :defer t
  :init
  (setq el-get-dir (expand-file-name "/opt/ng")))

package-selected-packagesで管理されているパッケージを全てインストールする。

(defun refresh-installed-all-packaes ()
  (interactive)
  (mapcar (lambda (pkg-symbol)
	    (if (not (package-installed-p pkg-symbol))
		(progn
		  (message (format "Installing.... %s" pkg-symbol))
		  (package-install pkg-symbol))
	      (message (format "Already installed: %s" pkg-symbol))))
	  package-selected-packages))

個人用パッケージ

(el-get-bundle eglot :type "git" :url "git@github.com:joaotavora/eglot.git")
(el-get-bundle change-case :url "git@github.com:TakesxiSximada/change-case.el.git" :type "git")
(el-get-bundle sudden-death :url "git@github.com:TakesxiSximada/sudden-death.el.git" :type "git")
(el-get-bundle swift-mode :type "git" :url "git@github.com:swift-emacs/swift-mode.git")

各種パッケージ

(use-package sgml-mode :ensure t :defer t
  :config
  (setq sgml-quick-keys 'close))
(use-package add-node-modules-path :ensure t :defer t)
(use-package ag :ensure t :defer t :no-require t)
(use-package avy-menu :ensure t :defer t)
(use-package csv-mode :ensure t :defer t)
(use-package db :ensure t :defer t)
(use-package dired-filter :ensure t :defer t)
(use-package fakir :ensure t :defer t)
(use-package flycheck :ensure t :defer t)
(use-package github-review  :ensure t :defer t)
(use-package google-translate :ensure t :defer t)
(use-package mew :ensure t :defer t)
(use-package monky :ensure t :defer t)
(use-package nginx-mode :ensure t :defer t)
(use-package ob-async :ensure t)
(use-package ob-restclient :ensure t :defer t)
(use-package pcre2el :ensure t :defer t)
(use-package request :ensure t :defer t)
(use-package restclient :ensure t :defer t)
(use-package s :ensure t :defer t)
(use-package smex :ensure t :defer t)
(use-package terraform-mode :ensure t :defer t)
(use-package transient :ensure t)
;; (use-package unicode-escape :ensure t :defer t)
(use-package vagrant-tramp :ensure t :defer t)
(use-package web :ensure t :defer t)
(use-package wgrep :ensure t :defer t)
(use-package wgrep-ag :ensure t :defer t)

IDO

(ido-mode 1)
(ido-everywhere 1)
(setq ido-enable-flex-matching t)
(use-package ido-vertical-mode :ensure t :defer
  :init
  (ido-vertical-mode)
  (add-hook 'ido-setup-hook #'ido-vertical-define-keys-custom)
  :custom
  (ido-default-file-method 'selected-window)
  (ido-default-buffer-method 'selected-window))

(defun ido-vertical-define-keys-custom ()
  (define-key ido-completion-map (kbd "M-n") 'ido-next-match)
  (define-key ido-completion-map (kbd "M-p") 'ido-prev-match)
  )

;; WHY DID I USE ido-completing-read+ PACKAGE?:
;;   I changed value t to ido-everywhere, but ido-vertical-mode did
;;   not work. Should be enabled ido-ubiquitous-mode to work it.

(use-package ido-completing-read+ :ensure t :defer t
  :init
  (ido-ubiquitous-mode 1))

OrgMode

(use-package org :ensure t :defer t
  :config
  (org-indent-mode)
  (setq org-startup-indented t
        org-archive-location (format-time-string "ARCHIVE_%Y.org::" (current-time))
        ))

(require 'org-clock)

:; automatic timeout timer
(setq org-clock-automatic-timeout (* 60 10))
(setq org-clock-automatic-timeout-timer
      (run-with-idle-timer org-clock-automatic-timeout
			   t 'org-clock-out))

org-scheduleで挿入される曜日を英語表記にする。 参考 :: https://qiita.com/tnoda_/items/9fefa1575f3bd5273b64

(setq system-time-locale "C")

company

(use-package company :ensure t :pin melpa
  :config
  (global-company-mode)
  (setq
   company-idle-delay 0 ; default = 0.5
   company-minimum-prefix-length 2 ; default = 4
   company-selection-wrap-around t ; 候補の一番下でさらに下に行こうとすると一番上に戻る
   company-tooltip-idle-delay nil)
  )

Language Server Protocol (eglot)

(use-package eglot :defer t :ensure t
  :init
  (defun eglot-install-language-server-python ()
    (interactive)
    (make-process :name "*EGLOT INSTALL*"
  		  :buffer (get-buffer-create "*EGLOT INSTALL*")
  		  :command `("pip" "install" "python-language-server")))

  :config
  (add-to-list 'eglot-server-programs '(vue-mode . ("vls")))

  (define-key eglot-mode-map (kbd "M-.") 'xref-find-definitions)
  (define-key eglot-mode-map (kbd "M-,") 'pop-tag-mark)

  ;; :if (eq system-type 'darwin)
  ;; :ensure-system-package
  ;; ("vls" . "npm install -g vls")
  )

edit-indirect

(use-package edit-indirect :ensure t :defer t
  :config
  (setq edit-indirect-guess-mode-function #'edit-indirect-custom-apply-major-mode))

(defun edit-indirect-custom-guess-major-mode (_parent-buffer _beg _end)
  "Guess major-mode to parent-buffer major-mode.

Returns symbol of major-mode.
"
  (with-current-buffer _parent-buffer
    (goto-char _beg)

    (if (eq major-mode 'org-mode)
	(if-let ((lang (nth 0 (org-babel-get-src-block-info))))
	    (intern (format "%s-mode" lang))
	  'org-mode)
      major-mode)))

(defun edit-indirect-custom-apply-major-mode  (_parent-buffer _beg _end)
  "Apply major-mode to parent-buffer major-mode."
  (funcall (edit-indirect-custom-guess-major-mode _parent-buffer _beg _end)))

Javascript and Typescript

(use-package typescript-mode :defer t :ensure t
  :config
  (setq typescript-indent-level 2))

(use-package js-mode :defer t
  :config
  (setq js-indent-level 2))
(use-package js2-mode :defer t :ensure t
  :config
  (setq js-indent-level 2))

Vue

(use-package vue-mode :ensure t :defer t
  :requires (vue-mode
	     vue-html-mode
	     css-mode
	     js-mode
	     typescript-mode)
  :config
  (define-key css-mode-map (kbd "C-c i") #'vue-mode-edit-all-indirect)
  (define-key css-mode-map (kbd "M-i") #'vue-mode-edit-indirect-at-point)
  (define-key js-mode-map (kbd "C-c i") #'vue-mode-edit-all-indirect)
  (define-key js-mode-map (kbd "M-i") #'vue-mode-edit-indirect-at-point)
  (define-key typescript-mode-map (kbd "C-c i") #'vue-mode-edit-all-indirect)
  (define-key typescript-mode-map (kbd "M-i") #'vue-mode-edit-indirect-at-point)
  (define-key vue-html-mode-map (kbd "C-c i") #'vue-mode-edit-all-indirect)
  (define-key vue-html-mode-map (kbd "M-i") #'vue-mode-edit-indirect-at-point)
  (define-key vue-mode-map (kbd "C-c i") #'vue-mode-edit-all-indirect)
  (define-key vue-mode-map (kbd "M-i") #'vue-mode-edit-indirect-at-point)

  (defun vue-mode-edit-all-indirect (&optional keep-windows)
    "Open all subsections with `edit-indirect-mode' in seperate windows.
  If KEEP-WINDOWS is set, do not delete other windows and keep the root window
  open."
    (interactive "P")
    (when (not keep-windows)
      (delete-other-windows))
    (save-selected-window
      (split-window-horizontally)
      (dolist (ol (mmm-overlays-contained-in (point-min) (point-max)))
        (let* ((window (split-window-below))
               (mode (or (plist-get vue-dedicated-modes (overlay-get ol 'mmm-mode))
                         (overlay-get ol 'mmm-mode)))
               (buffer (edit-indirect-region (overlay-start ol) (overlay-end ol))))
          (maximize-window)
          (with-current-buffer buffer
            (funcall mode))
          (set-window-buffer window buffer)))
      (balance-windows))
    (when (not keep-windows)
      (delete-window)
      (balance-windows)))
  )

Python

py-isort

isortはPythonのimport順序を整列する。 isortコマンドを外部から指定できるようにモンキーパッチを当てる。

(autoload 'py-isort-buffer "py-isort")
(autoload 'py-isort-region "py-isort")
(autoload 'py-isort-before-save "py-isort")

(with-eval-after-load 'py-isort
  (defcustom py-isort-executable "isort"
    "Name of the executable to run."
    :type 'string)

  (defun py-isort--call-executable (errbuf file)
    (let ((default-directory (py-isort--find-settings-path)))
      (zerop (apply 'call-process py-isort-executable nil errbuf nil
                    (append `(" " , file, " ",
                              (concat "--settings-path=" default-directory))
                            py-isort-options))))))

表示

可視性の向上のためカーソル位置の行にアンダーラインを表示する。

(global-hl-line-mode t)

ウィンドウの分割表示

Emacsでは、通常のOSなどでウィンドウと呼ばれている領域はフレームと呼び、 ウィンドウはEmacsの画面(フレーム)内に表示されている領域のことを指す。 できる限り文字を多く表示するためにウィンドウの分割線の幅を小さくする。 モードラインを表示しない場合、上下のウィンドウの境界がわからなくなって しまうのでウィンドウ下部に分割線を表示する。

(setq window-divider-default-bottom-width 1)
(setq window-divider-default-places 'bottom-only)

設定を反映する。

(window-divider-mode)

同様の理由からフリンジも表示しない。

(fringe-mode 0)

mode-line

モードラインは本当に必要だろうか。モードラインには文字コードや改行コード、バックグラウンドで実行しているジョブの状態など、さまざまな情報を表示できる。

それらは一見便利なようにも思えるが、何かを記述したり作業する時に本当に必要な集中力を阻害してしまう。どのような情報が必要かということについては、個人のもしくは作業のニーズによって異なる。そのためこの情報が常に表示されているべきということは言えない。

必ず必要な情報が何かが決められない以上、最初は全ての表示を無効にし、それぞれの必要性に応じて表示を追加していくことで、個人のニーズにあったモードラインとして成長していく。

(setq-default mode-line-format nil)

ウィンドウサイズの変更

(bind-key* "s-<up>" (lambda () (interactive) (window-resize nil -1)))
(bind-key* "s-<down>" (lambda () (interactive) (window-resize nil 1)))
(bind-key* "s-<right>" (lambda () (interactive) (window-resize nil 1 t)))
(bind-key* "s-<left>" (lambda () (interactive) (window-resize nil -1 t)))

追加のキーバインドの設定

(bind-key* "C-M-i" #'company-complete)
;; (bind-key* "C-c C-c M-x" #'execute-extended-command)
(bind-key* "C-t C-c" #'vterm-command)
(bind-key* "C-t C-t" #'other-frame)
(bind-key* "C-x C-v" #'magit-status)
(bind-key* "M-X" #'smex-major-mode-commands)
(bind-key* "M-x" #'smex)
(bind-key* "s-t" #'make-frame)
(define-key override-global-map (kbd "C-t C-i") #'org-clock-goto)

デバッガーの起動コマンドへのエイリアス

Emacsには標準でいくつかのデバッガーが付属していますが、それぞれのツールの名前がそのまま付いています。 M-x debug-on-XXXX で全てのデバッガーを起動できるようにエイリアスを設定しています。

(defalias 'debug-on-c 'gdb)
(defalias 'debug-on-java 'jdb)
(defalias 'debug-on-perl 'perldb)
(defalias 'debug-on-python 'pdb)
;; dbx
;; sdb

CSS

CSS編集のためのタブ幅などを設定します。

(require 'css-mode)

(setq css-indent-offset 2)

フロントエンドのコードフォーマッターとしてprettierを用いています。公式の拡張であるPrettier-js for Emacsもありますが、使用感が合わなかったので必要な機能だけを実装しました。

パッケージとして独立させるほどでもなかったため、このリポジトリの prettier ディレクトリにファイルを配置しました。そのためload-pathを追加し、 prettier-buffer をrequireします。

(add-to-list 'load-path (expand-file-name "~/.emacs.d/prettier"))

(require 'prettier-buffer)

実行は M-x prettier-buffer で実行できます。

org-src

コードブロックのインデントや見栄えをカスタマイズします。

(setq org-src-fontify-natively t
    org-src-window-setup 'current-window
    org-src-strip-leading-and-trailing-blank-lines t
    org-src-preserve-indentation t
    org-src-tab-acts-natively nil)

org-agenda

タスクの管理に org-agenda を使用しています。 agendaファイルを追加するには org-agenda-files にファイルパスを追加します。

今すべきタスクに集中するため概要では今日のタスクのみを表示します。

(setq org-agenda-span 'day)

デフォルトのアジェンダビューはタスクの見積もり時間と所要時間が表示され ていないためタスクのボリュームを判断できません。そこで見積もり時間と所 要時間を集計する関数を追加しそれを用いてアジェンダビューに表示するよう に変更します。

(require 'org)
(require 'org-clock)

(defun org-clock-get-item-content ()
  (save-excursion
    (let ((start-point (progn (org-back-to-heading t)
			      (point)))
	  (end-point (progn (org-end-of-subtree t t)
			    (point))))
      (buffer-substring-no-properties start-point end-point))))


(defun org-clock-sum-current-item-custom ()
  (interactive)
  (condition-case err-var
      (let* ((content (org-clock-get-item-content))
	     (minute (with-temp-buffer (insert content)
				       (org-clock-sum-current-item))))
	(if (> minute 0)
	    minute
	  ""))
    (error "-")))

アジェンダビューでタスクのタイトルだけではタスクの内容を推測しにくいた め親のタスクのタイトルも表示します。 %-10.20b などの表示を入れること で親タスクも表示できます。

各TODOに担当者を設定できるようにする。担当者は org-property を利用し ASSIGNEE 属性に担当者名を記述するようにする。 次の関数で現在位置のTODOの担当者を取得する。この関数はアジェンダビューで担当者を表示するために用いる。 タスクの管理手法は人によってかなり異なるが、チーム主体で考えると担当者が設定されていないTODOには担当者を割り振るほうがよく、個人主体で考えるのであれば担当者が設定されていないTODOは自分用のTODOという扱いにしたほうが、管理がしやすくなる。 ライブラリとして切り出すのであれば、このあたりを両方対応できるように、設計したほうがよい。 現状は特に何も考えず、担当者が設定されていないものはnullを返し、表示もnullとなる。

(setq org-assign-assignee-default-assignee nil)

(defun org-assign-assignee-value-custom ()
  "現在位置の担当者を取得する"
  (interactive)
  (or
    (cdr (assoc "ASSIGNEE" (org-entry-properties)))
    org-assign-assignee-default-assignee))
;(setq org-agenda-prefix-format
;      '((agenda . "%4(org-clock-sum-current-item-custom) %4e %6(org-assign-assignee-value-custom) %t %.4s %-6.6c %-25.50b ")
;        (todo . " %i %-12:c %-6e")
;        (tags . " %i %-12:c")
;        (search . " %i %-12:c")))

プロパティを表示する

(use-package org-agenda-property :ensure t :defer t)

参考: org-agendaのday viewでlocationの表示を行う

org-todo

org-todoの論理構造を強制します。 依存しているタスクが存在する場合、それらを完了していないと次のタスクに進めません。

(setq org-enforce-todo-dependencies t)

ただしチェックボックスは現在進行中のタスクを阻害してしまうので無効にします。 有効にするには org-enforce-todo-checkbox-dependencies を用います。

(setq org-enforce-todo-checkbox-dependencies nil)

org-todoの論理構造を視覚的に表示します。 まだ実行の条件を満たさないorg-todoはorg-agendaでグレーアウト表示になります。

(setq org-track-ordered-property-with-tag t)

org-priority

org-modeのタスクの優先度を設定します。

優先度としてA=Zの文字を使います。

(setq org-priority-lowest ?Z)

org-mode及びorg-agenda-modeではそれぞれ M-n M-p を用いて優先度を変更します。

(with-eval-after-load 'org
  (define-key org-mode-map (kbd "M-p") 'org-priority-up)
  (define-key org-mode-map (kbd "M-n") 'org-todo)

  (add-hook 'org-mode-hook #'visual-fill-column-mode)
  (add-hook 'org-mode-hook #'toggle-truncate-lines)
  )

(with-eval-after-load 'org-agenda
  (define-key org-agenda-mode-map (kbd "M-p") #'org-agenda-priority-up)
  (define-key org-agenda-mode-map (kbd "M-n") #'org-agenda-todo)
  )

Org Babel

Org Babelはorg-modeのコードブロックを実行する。ドキュメント内のコードを実行し、その出力をドキュメントに反映することができる。いわゆる文芸的プログラミングのようなことができる。

(org-babel-do-load-languages
 'org-babel-load-languages
 '(
   (ditaa . t)
   (scheme . t)
   (emacs-lisp . t)
   (python . t)
   (restclient . t)
   (http . t)
   (shell . t)
   (sql . t)))

Org Babelのコア部分はob-core.elに実装されている。例えばOrg-modeファイルのコードブロック内で C-c C-c を実行すると org-babel-execute-src-block 関数が呼び出される。この時に用いるOrg Babel用拡張がサードパーティによって実装された別のパッケージである場合、その拡張がまだ読み込まれていないことがある。その場合、Org Babelは処理の実行に失敗する。必要なパッケージをrequireで読み込み、再度 org-babel-execute-src-block 関数を実行すればよいのだが、その都度Lispを記述しevalするのは面倒だ。そこでorg-core.elが読み込まれたら、ここで使用可能な拡張もrequireすることにした。

(with-eval-after-load 'ob-core
  (require 'ob-async)
  (require 'ob-ditaa)
  (require 'ob-emacs-lisp)
  (require 'ob-http)
  (require 'ob-python)
  (require 'ob-restclient)
  (require 'ob-restclient)
  (require 'ob-scheme)
  (require 'ob-shell)
  (require 'ob-sql))

ユーティリティ

ここでは必要に応じて定義した様々な目的の関数を記述します。

バッファのファイルパスをクリップボードにコピーする

カレントバッファのファイルパスをクリップボードのコピーするコマンドを追加しています。

(defun our-buffer-copy-current-file-path ()
  "バッファのファイルパスをクリップボードにコピーする"
  (interactive)
  (let ((path (buffer-file-name)))
    (if path
  	(progn
         (kill-new path)
         (message (format "Copied: %s" path)))
      (message (format "Cannot copied")))))

face

現在のカーソル位置のface名を表示します。

(defun what-face (pos)
  "Display current position face name."
  (interactive "d")
  (if-let ((face-name (get-text-property pos 'face)))
      (message "Face: %s" face-name)))

AsciiDoc

AsciiDocはマークアップのため記法(Nortation)の1つです。 AsciiDocをEmacsで表示したり編集する場合様々な方法があります。

adoc-mode

adoc-modeはEmacs上でAsciiDoc形式のファイルを扱うためのメジャーモードです。 しかしデフォルトの設定ではコメントやメタ情報の表示サイズがとても小さくなっています。 これでは編集時に読めないので、ちょうどよい値に設定し直します。

(use-package adoc-mode :ensure t :defer t
  :config
  (set-face-attribute markup-comment-face nil :width 'normal :height 1)
  (set-face-attribute markup-meta-face nil :width 'normal :height 1 :foreground "red")
  )

asciidoc-view

ewwを用いてAsciiDocを表示する。

Font

フォントはSource Han Mono[fn:source-han-mono-repo]をインストールする。

フォントを調節して文字幅が合うようにする。

;; (progn
;;   (set-face-attribute 'default nil :family "源ノ等幅" :height 120)
;;   (set-fontset-font nil 'japanese-jisx0208 (font-spec :family "源ノ等幅" :size 16))
;;   (set-fontset-font nil 'japanese-jisx0208-1978 (font-spec :family "源ノ等幅" :size 16))
;;   (set-fontset-font nil 'japanese-jisx0212 (font-spec :family "源ノ等幅" :size 16))
;;   (set-fontset-font nil 'japanese-jisx0213.2004-1 (font-spec :family "源ノ等幅" :size 16))
;;   (set-fontset-font nil 'jisx0201 (font-spec :family "源ノ等幅" :size 12))
;;   (set-fontset-font nil 'symbol (font-spec :family "Apple Color Emoji" :size 12))
;;   (set-fontset-font nil '(?☺ . ?☺) (font-spec :family "Apple Color Emoji" :size 6))
;;   (set-fontset-font nil '(?🀄 . ?🀈) (font-spec :family "Apple Color Emoji" :size 9))
;;   (set-fontset-font nil '(?一 . ?一) (font-spec :family "源ノ等幅" :size 12))
;;   )
類似文字
l I 1
o O 0
q 9
s S 5
x X
z Z 2
一 ―
ずれ確認用 半角40字、全角20字
AIfUEaiueoAIUEOaiueoAIUEOaiueoAIUEOaiueoASCII英字
0123456789012345678901234567890123456789ASCII数字
アイウエオアイウエオアイウエオアイウエオアイウエオアイウエオアイウエオアイウエオJIS X 0201カナ
あいうえおあいうえおあいうえおあいうえおJIS X 0208ひらがな
アイウエオアイウエオアイウエオアイウエオ同カタカナ
ABCDEABCDEABCDEABCDE同英字
亜唖娃阿哀亜唖娃阿哀亜唖娃阿哀亜唖娃阿哀同漢字
𠀋𡈽𡌛𡑮𡢽𠀋𡈽𡌛𡑮𡢽𠀋𡈽𡌛𡑮𡢽𠀋𡈽𡌛𡑮𡢽JIS X 0213漢字
😃😇😍😜😸🙈🐺🐰👽🐉💰🏡🎅🍪🍕🚀🚻💩📷📦絵文字
☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺絵文字
🀄🀅🀆🀇🀈🀄🀅🀆🀇🀈🀄🀅🀆🀇🀈🀄🀅🀆🀇🀈絵文字
AIUEOaiueoASCII英字
1234567890ASCII英字
アイウエオアイウエオJIS X 0201カナ
あいうえおJIS X 0208ひらがな
アイウエオ同カタカナ
𠀋𡈽𡌛𡑮𡢽JIS X 0213漢字
😃😇😍😜😸絵文字
🙈🐺🐰👽🐉
💰🏡🎅🍪🍕
🚀🚻💩📷📦
☺☺☺☺☺絵文字
🀄🀄🀄🀄🀄
AAAAA
亜亜亜亜亜
ABCDE同英字
亜唖娃阿哀同漢字
🀅🀅🀅🀅🀅🀅
🀅🀆🀇🀈🀅

(この文字列は https://qiita.com/query1000/items/4b0b8db872adc1a5e2e9V から抜粋)

バッファの一部の領域を別のバッファに移して編集する機能をedit-indirectを用いて実現している。edit-indirectでは C-c C-c には edit-indirect-commit が割り当てられている。しかしOrg-modeなど C-c C-c を既に使っているメジャーモードの場合、その設定が邪魔になるので無効化する。またedit-indirectに入った時のメジャーモードの判定処理をカスタマイズする。

(with-eval-after-load 'edit-indirect
  (define-key edit-indirect-mode-map (kbd "C-c C-c") nil)


  (defun edit-indirect-custom-guess-major-mode (_parent-buffer _beg _end)
  "Guess major-mode to parent-buffer major-mode.

Returns symbol of major-mode.
"
  (with-current-buffer _parent-buffer
    (goto-char _beg)

    (if (eq major-mode 'org-mode)
	(if-let ((lang (nth 0 (org-babel-get-src-block-info))))
	    (intern (format "%s-mode" lang))
	  'org-mode)
      major-mode)))

  (defun edit-indirect-custom-apply-major-mode  (_parent-buffer _beg _end)
    "Apply major-mode to parent-buffer major-mode."
    (funcall (edit-indirect-custom-guess-major-mode _parent-buffer _beg _end)))
  )

タスク

タスク実行時の集中力の阻害を最小限にするため、関連する情報以外を見えないようにするコマンドを定義する。開始時に task-join 、終了時に task-leave を呼び出す。

(require 'edit-indirect)
(require 'org-clock)


(defun task-join ()
  "Join the task."
  (interactive)
  (org-narrow-to-subtree)
  (mark-whole-buffer)
  (switch-to-buffer
   (edit-indirect-region
    (region-beginning)
    (region-end)))
  (org-clock-in)
  )

(defun task-leave ()
  "Leave the clock-in task."
  (interactive)
  (if-let ((clock-buf (org-clock-is-active)))
      (with-current-buffer clock-buf
	(org-clock-out)))
  (edit-indirect-commit)
  (widen))

grip-mode

Org-modeやMarkdownの編集時にはリアルタイムプレビューがあると非常に捗る。 Emacsではgrip-modeを使うことで実現できる。

(use-package grip-mode :ensure t :defer t)

grip-modeは内部でGripというツールを使用している。このGripがリアルタイ ムレンダリングの機能を提供している。GripはPythonで実装されているので、 pipを用いてインストールする。

pip install grip

参考 :: https://blog.symdon.info/posts/1638063555/

org-export

org-exportはorg-mdoeで記述されたファイルを別の形式に変換する。

上付き文字(^で挟む)と下付き文字の記法(_で挟む)は通常の記述で使用するた め、更に{}の指定が必要になるように設定する。

(setq org-export-with-sub-superscripts '{})

参考 :: https://blog.symdon.info/posts/1605311844/

OrgファイルをPDFにエクスポート

LaTeXを使ってOrgファイルをPDFにエクスポートする。 org-latex-export-to-pdfが定義されているが、文字コード関連で動作しなかったためコマンドを直接起動する形で独自に実装した。

(defun org-pdf-export-to-pdf-via-latex ()
  "Export PDF file from org file via latex"
  (interactive)
  (let* ((tex-file-name (org-latex-export-to-latex))
	 (base-file-name (file-name-base tex-file-name))
	 (dvi-file-name (format "%s.dvi" base-file-name))
	 (pdf-file-name (format "%s.pdf" base-file-name))
	 (vterm-shell (format "bash -c 'platex %s && dvipdfmx %s'"
			      tex-file-name
			      dvi-file-name))
	 (vterm-buffer-name (format "*Org PDF Exporting: %s" pdf-file-name))
	 (vterm-kill-buffer-on-exit nil))
    (vterm)
    pdf-file-name))

aspell

スペルチェッカー。

http://aspell.net/

(setq-default ispell-program-name "aspell")
(with-eval-after-load "ispell"
  (setq ispell-local-dictionary "en_US")
  (add-to-list 'ispell-skip-region-alist '("[^\000-\377]+")))

aspell自体のインストールは Homebrewの場合 brew install aspell を実行する。

AquaSKK

IMEにはAquaSKKを使用している。aquaskk/keymap.conf を ~/Library/Application Support/AquaSKK/ 配下にコピーする。

mmm-mode

mmm-modeは1つのバッファ内で複数のメジャーモードを利用できるようにする。 ただしバージョン0.5.8にはvue-modeでファイルを開く時にエラーが発生する既知のバグ[fn:mmm-mode-issue-112]がある。 この問題を回避するにはいくつか方法が示されているが確認したところ以下の関数を評価することで回避できた[fn:mmm-mode-issue-112-wa]。

(require 'mmm-region)


(defun mmm-syntax-propertize-function (start stop)
  "Composite function that applies `syntax-table' text properties.
It iterates over all submode regions between START and STOP and
calls each respective submode's `syntax-propertize-function'."
  (let ((saved-mode mmm-current-submode)
        (saved-ovl  mmm-current-overlay))
    (mmm-save-changed-local-variables
     mmm-current-submode mmm-current-overlay)
    (unwind-protect
        (mapc (lambda (elt)
                (let* ((mode (car elt))
                       (func (get mode 'mmm-syntax-propertize-function))
                       (beg (cadr elt)) (end (nth 2 elt))
                       (ovl (nth 3 elt))
                       ;; FIXME: Messing with syntax-ppss-* vars should not
                       ;; be needed any more in Emacs≥26.
                       syntax-ppss-cache
                       syntax-ppss-last)
                  (goto-char beg)
                  (mmm-set-current-pair mode ovl)
                  (mmm-set-local-variables mode mmm-current-overlay)
                  (save-restriction
                    (when mmm-current-overlay
                      (narrow-to-region (overlay-start mmm-current-overlay)
                                        (overlay-end mmm-current-overlay))
                      (put-text-property
                       (point-min) (point-max)
                       'syntax-table (syntax-table)))
                    (cond
                     (func
                      (funcall func beg end))
                     (font-lock-syntactic-keywords
                      (let ((syntax-propertize-function nil))
                        (font-lock-fontify-syntactic-keywords-region beg end))))
                    (run-hook-with-args 'mmm-after-syntax-propertize-functions
                                        mmm-current-overlay mode beg end))))
              (mmm-regions-in start stop))
      (mmm-set-current-pair saved-mode saved-ovl)
      (mmm-set-local-variables (or saved-mode mmm-primary-mode) saved-ovl))))

リージョンの文字列を置き換えるユーティリティ

replace-region-contents をコマンドとして呼び出せるようにし、適応する文字列処理を任意に指定できるようにした。

(defun apply-and-replace-region-string (func beg end)
  "Replace after appling function the region string"
  (interactive "a\nr")
  (replace-region-contents
   beg end (lambda ()
	     (let ((txt (buffer-substring-no-properties beg end)))
	       (funcall func txt)))))

リージョンの浮動小数点形式のUNIXエポックタイムを時刻形式に変換する関数を実装した。

(defun float-time-to-datetime-string (float-style-string)
  "Convert unix epoc time (floating point style) string to date time formated string."
  (format-time-string
   "%Y-%m-%dT%H:%M:%S.%6N"
   (encode-time (decode-time
		 (string-to-number float-style-string)))))

EditorConfig

EditorConfigはプロジェクト毎のエディタの設定を統一する。

(use-package editorconfig
  :ensure t
  :config
  (editorconfig-mode 1))

Frame毎に色調を切り替える

(setq account-alist '((sym . "ffffff")))

(defun switch-to-color (label)
  (interactive (list (completing-read "Label: "
				      (mapcar 'car account-alist))))
  (if-let ((color-fg (cdr (assoc (intern label) account-alist))))
      (set-foreground-color color-fg)))

URL関連

(require 'url-util)

(defun our-url-encode ()
  (interactive)
  (kill-new
   (url-hexify-string
    (buffer-substring-no-properties
     (region-beginning) (region-end)))))

その他

(put 'narrow-to-region 'disabled nil)
(put 'dired-find-alternate-file 'disabled nil)

追加の設定の読み込み

各環境毎に読み込みするかどうかを切り替えたい設定もある。 それらを切り替えるために追加で読み込むファイルを環境変数から取得する。

(save-window-excursion
  (when (file-exists-p custom-env-file)
    (with-current-buffer (find-file-read-only custom-env-file)
      (dotenv-mode-apply-all))))

(mapc (lambda (path) (add-to-list 'custom-additional-load-file-list path))
      (split-string (getenv "EMACS_ADDITINONAL_LOAD_FILE_PATH") ":"))

(mapc (lambda (path) (load-file path))
      custom-additional-load-file-list)

Color Themeのカスタマイズ

(solarized-create-theme-file-with-palette 'dark 'simple-darkness
 '("#000000"  ;; darkest-base
   "#ffffff"  ;; brightest-base
   "#dbb32d"  ;; yellow
   "#e67f43"  ;; orange
   "#ed4a46"  ;; red
   "#eb6eb7"  ;; magenta
   "#a580e2"  ;; violet
   "#368aeb"  ;; blue
   "#3fc5b7"  ;; cyan
   "#70b433"  ;; green
   ))

S3

S3へのアクセスにはs3edを使用する。

(use-package s3ed :ensure t)

基本的にローカルでのみダミーサーバーに対して使用する。 その為に使用するコマンドをawslコマンドとして定義しているが、 それを利用できるようにaws cliのコマンドを返す関数を上書きする。

(defun s3ed-aws-cli (cmd)
  "Run the aws cli (s3) command with the configured arguments.
The given CMD string will be appended."
  (let* ((profile-arg (if s3ed-profile-name (format " --profile %s" s3ed-profile-name) "")))
    (format "awsl%s s3 %s" profile-arg cmd)))

Magit

MagitはEmacs用のGitユーティリティで、Gitコマンドのラッパーとして transientを用いて実装されている。仕様をカスタマイズするため、関数の上 書きをする。

magit-commit、magit-push、magit-rebaseの3つのEmacsのコマンドについて --no-verify オプションが用意されているが、オプション文字列が統一され ていない。magit-commitのみ -n で指定するようになっているため他の2つ のコマンドにならい -h で指定できるように修正する。

関数--no-verify のデフォルトの指定--no-verify の変更後の指定
magit-commit-n-h
magit-push-h-h
magit-rebase-h-h
(require 'magit)
(require 'transient)

(transient-define-prefix magit-commit ()
  "Create a new commit or replace an existing commit."
  :info-manual "(magit)Initiating a Commit"
  :man-page "git-commit"
  ["Arguments"
   ("-a" "Stage all modified and deleted files"   ("-a" "--all"))
   ("-e" "Allow empty commit"                     "--allow-empty")
   ("-v" "Show diff of changes to be committed"   ("-v" "--verbose"))
   ("-h" "Disable hooks"                          ("-n" "--no-verify"))
   ("-R" "Claim authorship and reset author date" "--reset-author")
   (magit:--author :description "Override the author")
   (7 "-D" "Override the author date" "--date=" transient-read-date)
   ("-s" "Add Signed-off-by line"                 ("-s" "--signoff"))
   (5 magit:--gpg-sign)
   (magit-commit:--reuse-message)]
  [["Create"
    ("c" "Commit"         magit-commit-create)]
   ["Edit HEAD"
    ("e" "Extend"         magit-commit-extend)
    ("w" "Reword"         magit-commit-reword)
    ("a" "Amend"          magit-commit-amend)
    (6 "n" "Reshelve"     magit-commit-reshelve)]
   ["Edit"
    ("f" "Fixup"          magit-commit-fixup)
    ("s" "Squash"         magit-commit-squash)
    ("A" "Augment"        magit-commit-augment)
    (6 "x" "Absorb changes" magit-commit-autofixup)
    (6 "X" "Absorb modules" magit-commit-absorb-modules)]
   [""
    ("F" "Instant fixup"  magit-commit-instant-fixup)
    ("S" "Instant squash" magit-commit-instant-squash)]]
  (interactive)
  (if-let ((buffer (magit-commit-message-buffer)))
      (switch-to-buffer buffer)
    (transient-setup 'magit-commit)))

push操作後にプロセスバッファをポップアップする

Gitフックなどでテストや整形すると、その結果を即座に確認したい。そのため、git push時にはMagitのプロセスバッファを表示する。

https://blog.symdon.info/posts/1654845224/

(add-hook 'magit-credential-hook #'magit-process-buffer)

集中力を維持するための工夫

https://blog.symdon.info/posts/1652142295

Frame Title

集中力を維持するため、frame-titleにorg-clock-inしたタスクの名称を表示する。

org-clock-in 及び org-clock-out された時に実行されるフックとして、自作のフレームタイトルを変更する関数を設定した。org-clock-inしていない時はフレームタイトルにはバッファ名を表示するようにした。

(defun change-frame-title-to-org-clock-current-task-name ()
  "Change frame title to org- clock current task name.

Display current buffer name if not clock in now."
  (interactive)
  (setq frame-title-format (or org-clock-current-task "%b")))

(add-hook 'org-clock-in-hook #'change-frame-title-to-org-clock-current-task-name)
(add-hook 'org-clock-out-hook #'change-frame-title-to-org-clock-current-task-name)

Mini Buffer

アイドル状態になったら作業中のタスク名をミニバッファに表示する。

(defun display-current-task-name-in-to-mini-buffer ()
  "Display current task name in to mini buffer"
  (interactive)
  (when org-clock-current-task
    (let ((minibuffer-message-timeout nil))
      (minibuffer-message org-clock-current-task))))

(setq display-current-task-name-in-to-mini-buffer-timer
      (run-with-idle-timer 3 t #'display-current-task-name-in-to-mini-buffer))

DDSKK

インプットメソッドにはDaredevil SKKを使用している。

https://github.com/skk-dev/ddskk

(global-set-key "\C-x\C-j" #'skk-mode)

(defun disable-mode-line ()
  (setq-local mode-line-format nil))

(autoload 'skk-mode "skk")

(with-eval-after-load 'skk
  ;; SKKモードに切り替わってもモードラインを表示しない
  (add-hook 'skk-mode-hook 'disable-mode-line)
  (setq-default mode-line-format nil)
  (setq-default skk-modeline-input-mode nil)

  ;; 絶対にモードラインを表示させたくないため
  ;; モードラインの設定関数を上書きする。
  (defun skk-setup-modeline () nil)

  ;; SKKの候補の表示方法
  (setq skk-show-tooltip nil)
  (setq skk-show-inline 'vertical)
  (setq skk-egg-like-newline nil)
  (setq skk-dcomp-activate t)
  (setq skk-dcomp-multiple-activate t)
  (setq skk-henkan-strict-okuri-precedence t)

  ;; カーソルの色を変更する
  (setq-default skk-cursor-latin-color "turquoise")
  (setq-default skk-cursor-hiragana-color "orange")
  (setq-default skk-cursor-katakana-color "systemGreenColor")

  (setq skk-show-mode-show t)
  (setq skk-show-mode-style "tooltip")

  (defun skk-isearch-setup-maybe ()
    (require 'skk-vars)
    (when (or (eq skk-isearch-mode-enable 'always)
  	    (and (boundp 'skk-mode)
  		 skk-mode
  		 skk-isearch-mode-enable))
      (skk-isearch-mode-setup)))

  (defun skk-isearch-cleanup-maybe ()
    (require 'skk-vars)
    (when (and (featurep 'skk-isearch)
  	     skk-isearch-mode-enable)
      (skk-isearch-mode-cleanup)))

  (add-hook 'isearch-mode-hook #'skk-isearch-setup-maybe)
  (add-hook 'isearch-mode-end-hook #'skk-isearch-cleanup-maybe)
  )

  ;; 学習
  (require 'skk-study)

safe-local-variable-valuesをcustom-fileに保存しないために

safe-local-variable-valuesを保存してしまうと、custom-fileファイルをGitに登録できなくなってしまうため、safe-local-variable-valuesはcustom-fileに反映しないように設定する。

(setq-default enable-local-variables :all)

Org Mode

Org Modeは巨大なドキュメントシステムであり、プロジェクト管理や表計算など様々な機能を提供している。

org-clock

org-clockはタスク(org-todo)の作業時間の計測を行う。作業時間の計測を簡 略化するためにキーバインドを変更する。

(with-eval-after-load 'org-clock
  (define-key org-mode-map (kbd "M-i") #'org-clock-in)
  (define-key org-mode-map (kbd "M-o") #'org-clock-out)
  )

org-agenda

org-agendaはタスクの状況を一覧で表示する。org-agendaの一覧表示からでも 作業を開始できるようにキーバインドを変更する。

(with-eval-after-load 'org-agenda
  (define-key org-agenda-mode-map (kbd "M-i") #'org-agenda-clock-in)
  (define-key org-agenda-mode-map (kbd "M-o") #'org-agenda-clock-out)
  )

org-super-agenda

org-agendaのレポート機能を強化したライブラリとして org-super-agenda がある。org-super-agendaを使用しているがカテゴリ別に見積の値をラベルに 集計するようにカスタマイズする。

(with-eval-after-load 'org-super-agenda

  (defun org-super-agenda-get-effort (item)
    (if-let ((item-todo-state (get-text-property 0 'todo-state item)))
        (get-text-property 0 'effort-minutes item-todo-state)))

  (defun org-super-agenda-summary-effort (items)
    (apply #'+
  	 (seq-filter
  	  (lambda (it) it)
  	  (mapcar #'org-super-agenda-get-effort items))))

  (defun org-super-agenda--make-agenda-header (name &optional items)
    "Return agenda header named NAME.
  If NAME is nil or `none', return empty string.  Otherwise, return
  string NAME prepended with `org-super-agenda-header-separator',
  which see.  NAME has the face `org-super-agenda-header' appended,
  and the text properties `keymap' and `local-map' set to the value
  of `org-super-agenda-header-map', which see."
    (pcase name
      ((or `nil 'none) "")
      (_ (let* ((properties (text-properties-at 0 name))
                (header (concat org-super-agenda-header-prefix name))
                (separator
                 (cl-etypecase org-super-agenda-header-separator
                   (character (concat (make-string (window-width) org-super-agenda-header-separator)
                                      "\n"))
                   (string org-super-agenda-header-separator))))
           (set-text-properties 0 (length header) properties header)
           (add-face-text-property 0 (length header) 'org-super-agenda-header t header)
           (org-add-props header org-super-agenda-header-properties
             'keymap org-super-agenda-header-map
             ;; NOTE: According to the manual, only `keymap' should be necessary, but in my
             ;; testing, it only takes effect in Agenda buffers when `local-map' is set, so
             ;; we'll use both.
             'local-map org-super-agenda-header-map)
           ;; Don't apply faces and properties to the separator part of the string.
           (concat separator header
  		 (format " (Effort => %d)"
  			 (org-super-agenda-summary-effort items)))))))

  (defun org-super-agenda--group-items (all-items)
    "Divide ALL-ITEMS into groups based on `org-super-agenda-groups'."
    (if (bound-and-true-p org-super-agenda-groups)
        ;; Transform groups
        (let ((org-super-agenda-groups (org-super-agenda--transform-groups org-super-agenda-groups)))
          ;; Collect and insert groups
          (cl-loop with section-name
                   for filter in org-super-agenda-groups
                   for custom-section-name = (plist-get filter :name)
                   for order = (or (plist-get filter :order) 0)  ; Lowest number first, 0 by default
                   for (auto-section-name non-matching matching) = (org-super-agenda--group-dispatch all-items filter)

                   do (when org-super-agenda-keep-order
                        (setf matching (sort matching #'org-entries-lessp)))

                   ;; Transformer
                   for transformer = (plist-get filter :transformer)
                   when transformer
                   do (setq matching (-map (pcase transformer
                                             (`(function ,transformer) transformer)
                                             ((pred symbolp) transformer)
                                             (_ `(lambda (it) ,transformer)))
                                           matching))

                   ;; Face
                   for face = (plist-get filter :face)
                   when face
                   do (let ((append (plist-get face :append)))
                        (when append (cl-remf face :append))
                        (--each matching
                          (add-face-text-property 0 (length it) face append it)))

                   ;; Auto category/group
                   if (cl-member auto-section-name org-super-agenda-auto-selector-keywords)
                   do (setq section-name (or custom-section-name "Auto category/group"))
                   and append (cl-loop for group in matching
                                       collect (list :name (plist-get group :name)
                                                     :items (plist-get group :items)
                                                     :order order))
                   into sections
                   and do (setq all-items non-matching)

                   ;; Manual groups
                   else
                   do (setq section-name (or custom-section-name auto-section-name))
                   and collect (list :name section-name :items matching :order order) into sections
                   and do (setq all-items non-matching)

                   ;; Sort sections by :order then :name
                   finally do (setq non-matching (list :name org-super-agenda-unmatched-name
                                                       :items non-matching
                                                       :order org-super-agenda-unmatched-order))
                   finally do (setq sections (--sort (let ((o-it (plist-get it :order))
                                                           (o-other (plist-get other :order)))
                                                       (cond ((and
                                                               ;; FIXME: This is now quite ugly.  I'm not sure that all of these tests
                                                               ;; are necessary, but at the moment it works, so I'm leaving it alone.
                                                               (equal o-it o-other)
                                                               (not (equal o-it 0))
                                                               (stringp (plist-get it :name))
                                                               (stringp (plist-get other :name)))
                                                              ;; Sort by string only for items with a set order
                                                              (string< (plist-get it :name)
                                                                       (plist-get other :name)))
                                                             ((and (numberp o-it)
                                                                   (numberp o-other))
                                                              (< o-it o-other))
                                                             (t nil)))
                                                     (push non-matching sections)))
                   ;; Insert sections
                   finally return (cl-loop for (_ name _ items) in sections
                                           when items
                                           collect (org-super-agenda--make-agenda-header name items)
                                           and append items)))
      ;; No super-filters; return list unmodified
      all-items))
      )

indent-guide

https://github.com/zk-phi/indent-guide

インデントの崩れを確認しやすくする。ただし常に表示されて見た目を損ないたくないため、必要な時に有効にする。

visual-fill-column

Auto Fillingを長らく使用してきたが、テキストをブログ用にレンダラに渡すと改行が空白に変換され、読みにくくなる、改行のせいで検索が困難になる、差分を判断しにくいなどの不都合があった。しかしエディタで読む場合、適当な行で折り返されていないと、それはそれで読みにくい。toggle-truncate-linesを用いて折り返す方法もあるが、それはウィンドウサイズに従って折り返されてしまうため、ウィンドウサイズを気にしなければならないが、それはそれで面倒だ。そのため visual-fill-column を用いて見た目上の折り返しを文字数で指定できるようにする。

https://codeberg.org/joostkremers/visual-fill-column

package-install RET visual-fill-column RET
(with-eval-after-load 'visual-fill-column
  (add-hook 'visual-line-mode-hook #'visual-fill-column-mode)
  (setq-default visual-fill-column-center-text nil))

脚注

[fn:source-han-mono-repo] https://github.com/adobe-fonts/source-han-mono [fn:mmm-mode-issue-112] dgutov/mmm-mode#112 [fn:mmm-mode-issue-112-wa] dgutov/mmm-mode#112 (comment)

末尾

(message "Loaded ~/.emacs.d/README.org")