Emacs

Install Emacs29.4 on Debian12

Debian12 「bookworm」に Emacs29.4 をインストールした。

Emacs29.1からEmacs29.4へのupdateである。 今回は、–with-native-compilation=aot としてみた。若干起動が早くなった感じがするが定かではない。

Install Emacs29.1 on Debian12

Debian12 「bookworm」に Emacs29.1 をインストールした。

Debianのバージンが12.7になったのを機にEmacs28.2からEmacs29.1にupdateした。インストールは以下の通りで問題なく完了、念の為に Pacckage群も全て削除して再インストールした。

Flycheck の leaf: Unrecognized keyword エラーを修正

私の emacsは、設定ファイルを分割して init-loader で読み込んでいます。

ところが Emacsの設定を use-package から leaf へ移行したら 各分割ファイルで flycheckが leaf: Unrecognized keyword :el-get (emacs-lisp) というようなエラーを吐くようになった。leaf で使える便利なキーワード(:hydra :chord :el-getなど)のいくつかがエラーに引っかかるみたい。

init.el(leaf-keywords-init)が宣言されているので、 分割ファイルでも問題なく leaf固有のキーワードを使うことができるのだが flycheck は分割ファイルごとに compileチェックをするのでエラーになるというのが原因のようだ。

そこで、各分割ファイルの冒頭に (eval-when-compile (leaf-keywords-init)) というのを書いてみたら警告が消えた。

Emacs設定ファイルの Byte Compileを自動化する

Debian Linux 上で GNU Emacs 27.2.50を使っています。 現状の emacs-init-time は、0.5秒前後で何ら不満はないのですが、さらなる起動時間の短縮にこだわって日々試行錯誤している Emacs馬鹿です。

設定ファイルを leaf に移行し、且つafter-init-hook を多用することでかなり短縮できました。で、最後にたどり着いたのが設定ファイルの全てを自動バイトコンパイルさせるという課題です。

Nano Modelineを試す

Emacs設定ファイルは、 Ladicle’ s Emacs Configuration ver.2022 を参考にしているのですが、最近モードラインが doom-modelineから nano-modelineに変更されていたので私も試してみることにしました。

nano-modeline

Ladicle’ s さんの設定で使われているパッケージは、nano-modelineは、GNU Emacs / N Λ NO という GNU Emacs 用の設定ファイルのセットに含まれています。

Ladiclsさんの設定をコピペするだけで簡単に確認することができます。

情報量は減りますがじつにシンプルでなかなかおもしろいと感じました。ただ残念ながら Evil-modeには対応していないようです。

doom-nano-modeline

いろいろ探していると Evil対応のパッケージが見つかりました。doom-nano-themeとともに Doom-emacs用として公開されているものです。

残念ながら themeパッケージは反映できませんでしたが modelineの方は流用できました。スクショと設定ファイルとを公開しておきます。

Screen Shot

カスタマイズ

デフォルトのままでも悪くはなかったのですが、とりあえず自分好みの色設定に変更しています。

私の場合、Veiw-mode 代わりに Evilを導入しており、 [insert-state = emacs-state] という使い方をしているので必要なものだけ変更しています。

(leaf doom-nano-modeline
  :doc "Nice look modeline based on N Λ N O"
  :url "https://github.com/ronisbr/doom-nano-modeline"
  :el-get "ronisbr/doom-nano-modeline"
  :hook (emacs-startup-hook . doom-nano-modeline-mode)
  :custom-face
  (region                                    . '((t (:background "#6272a4" :extend t))))
  (hl-line                                   . '((t (:background "#3B4252" :extend t))))
  (doom-nano-modeline-active-face            . '((t (:foreground "#f8f8f2" :background "#44475a" :weight bold))))
  (doom-nano-modeline-evil-emacs-state-face  . '((t (:foreground "#f4a460" :background "#6272a4" :weight bold))))
  (doom-nano-modeline-evil-normal-state-face . '((t (:foreground "#adff2f" :background "#3cb371" :weight bold))))
  (doom-nano-modeline-evil-visual-state-face . '((t (:foreground "#e0ffff" :background "#4682b4" :weight bold))))
  (doom-nano-modeline-cursor-position-face   . '((t (:foreground "#b0b8d1" :background "#44475a"))))
  (doom-nano-modeline-major-mode-face        . '((t (:foreground "#b0b8d1" :background "#44475a"))))
  (doom-nano-modeline-vc-branch-name-face    . '((t (:foreground "#b0b8d1" :background "#44475a"))))
  :preface
  (leaf hide-mode-line
	:doc "Hide modeline in current buffer"
	:ensure t
	:hook (after-init-hook . global-hide-mode-line-mode)))

hid-mode-line

neotreeや imenu-list などの場合は、何故か Emacsデフォルトのモードラインが表示されてしまうので、hide-mode-lineパッケージをインストールして強引に消しています。

EmacsメインのEvil設定

Evil導入の目的

EmacsでLispを閲覧しているときにうっかりミスでソースを書き換えてしまって動かなくなったりするので、Vimで修正したりということがよくある。 新しいパッケージや設定を試したりするときにも同様なことが起きるので、そんなときにもVimの出番となる。

そこで、Emacsを使いながらVimの操作を覚えるためにEvil導入は良策だと考えるに至った。Evil導入を試すにあたり、数少ないTipsを調べてみた。

Evilバイブルである @taraoさん のTipsからは多くのことを教えられた。けれど私が作りたいのは、「EmacsをVimのごとく使う」という環境ではない。

徹底してEmacsのVim化を図ることにそれほど価値があるとは思えないからで、究極を追求するくらいならあっさりVimを使えば事足りるからである。 私の場合は、あくまでEmacsがメインであって「Vimのいいとこどりができたらいいな」ということである。

この点をしっかり自覚してからEvil導入を進めないと迷路から出られなくなると思う。

Evil設定

(leaf evil
  :ensure t
  :hook ((after-init-hook . evil-mode)
		 (find-file-hook . my:evil-insert-state))
  :bind (("<zenkaku-hankaku>" . toggle-evil-mode)
		 (:evil-normal-state-map
		  ("?" . chromium-vim-chert)
		  ("C-e" . seq-end)
		  ("SPC" . evil-insert-state)
		  ("M-." . nil)	;; Use with other settings
		  ("<hiragana-katakana>" . my:evil-normal-state)
		  ([home] . open-dashboard)))
  :init
  ;; options for Evil, must be written before (require 'evil)
  (setq evil-insert-state-cursor '(bar . 4))
  (setq evil-want-C-u-scroll t)	;; Enable scrolling with C-u
  (setq evil-cross-lines t)
  (setq evil-ex-search-vim-style-regexp nil)
  (setq evil-search-module 'evil-search)
  (setq evil-undo-system 'undo-fu)
  :config
  ;; Use emacs key bindings in insert state
  (setcdr evil-insert-state-map nil)
  ;; Go back to normal state with ESC
  (define-key evil-insert-state-map [escape] 'my:evil-normal-state)
  ;; Hydra-select in visual-state region
  (define-key evil-visual-state-map (kbd ".") 'hydra-selected/body)

  ;; Use muhenkan key as ESC
  (define-key key-translation-map [muhenkan] 'evil-escape-or-quit)
  (define-key evil-operator-state-map [muhenkan] 'evil-escape-or-quit)

  ;; Set the initial state for major mode
  (evil-set-initial-state 'lisp-interaction-mode 'insert)
  (evil-set-initial-state 'fundamental-mode 'insert)
  (evil-set-initial-state 'easy-hugo-mode 'insert)

  (add-to-list 'evil-emacs-state-modes 'neotree-mode)
  (add-to-list 'evil-emacs-state-modes 'dired-mode)
  (add-to-list 'evil-emacs-state-modes 'dashboard-mode)
  (add-to-list 'evil-emacs-state-modes 'org-mode)

  ;; Customized functions
  (defun evil-swap-key (map key1 key2)
	"Swap KEY1 and KEY2 in MAP."
	(let ((def1 (lookup-key map key1))
          (def2 (lookup-key map key2)))
      (define-key map key1 def2)
      (define-key map key2 def1)))
  (evil-swap-key evil-motion-state-map "j" "gj")
  (evil-swap-key evil-motion-state-map "k" "gk")

  (defun toggle-evil-mode ()
	"Toggle on and off evil mode."
	(interactive)
	(if evil-mode (evil-mode 0)
	  (evil-mode 1)))

  (defun my:evil-normal-state ()
	"Turn off input-method then return to normal-state."
	(interactive)
	(if current-input-method (deactivate-input-method))
	(evil-normal-state))

  (defun evil-escape-or-quit (&optional prompt)
	"If in evil state to ESC, else muhenkan key."
	(interactive)
	(cond
	 ((or (evil-normal-state-p) (evil-insert-state-p) (evil-visual-state-p)
		  (evil-replace-state-p) (evil-visual-state-p)) [escape])
	 (t [muhenkan])))

  (defun my:evil-insert-state ()
	"New files are opened with insert-state."
	(interactive)
	(unless (file-exists-p buffer-file-name)
	  (evil-insert-state)))

  (defvar my:auto-insert-state-buffers '("COMMIT_EDITMSG"))
  (defun ad:switch-to-buffer (&rest _arg)
	"Set buffer for automatic inser-state"
	(when (member (buffer-name) my:auto-insert-state-buffers))
	(evil-insert-state))
  (advice-add 'switch-to-buffer :after #'ad:switch-to-buffer))


(leaf evil-plugins
  :doc "Plugin for Evil modeline"
  :el-get tarao/evil-plugins
  :after evil
  :require evil-mode-line)
  1. evil-local-mode を使って必要なメジャーモードだけEvil化する
  2. evil-mode をグローバル設定した上で、不要なEvil機能を除外する。

最初は、設定の簡単なevil-local-mode で試してみた。 ところが evil-modeline-color が動作しなかったり、C-[ による [escape] 機能が動作しなかったりというバグがあって不満だったので 最終的に evil-mode で設定を工夫することにした。

EmacsからQiitaの記事投稿用にAtomic Chromeを再導入

久しぶりにQiitaに投稿しようとしたら以前は出来たコピペができなくなっている。

Emacsのクリップボードの設定ミスを疑って調べたけれど問題ない。仕様が変更されたのかどうかはわからないが、ダイレクトで記事を書くなんてことは考えられない。みんなどうしてるんだろう?

はてさてと悩んで、前に使った atomic-chrome を再導入してみた。結果はGood! Chrome側の拡張機能は、atomic-chrome-extensions だったと記憶していたのだけれど見当たらず、 Gost Text に置き換わったみたい。このあたりは下記Tipsに詳しい。

Emacsの設定

取り急ぎ下記の設定で試したところ快適にコピペ出来た。

;; atomic-chrome
(leaf atomic-chrome
  :ensure t
  :hook (after-init-hook . atomic-chrome-start-server)
  :custom (atomic-chrome-buffer-open-style . 'full))

結構起動時間を消費するので、after-init-hook で遅延起動している。また、Splitバッファーで表示されるデフォルトのスタイルが気に入らなかったので full window に変更している。

Google-translateからDeepl-translateに乗り換えた話

Emacsから翻訳するのにGoogle-translateを愛用していたが、Slackのemacs-jpでGoogleよりも高性能だと評判の DeePL を知ったので、早速導入してみた。

EmacsからDeePLを使う

“EmacsからDeePLを使う” で調べると下記3種類のTipsが見つかった。いづれもリージョン選択範囲を翻訳させるものである。

1. masatoi/deepl.el

翻訳結果はミニバッファーに表示され、同じ内容がクリップボードにコピーされる。

オリジナルのコード (deepl.el ) は、load-file して使う仕様になっていたので、el-get でパッケージインストールできるようにアレンジしたものを自分のGitHubに置いている。DeePLにユーザー登録してAPIキーの取得が必要。

2. lorniu/go-translate.el

翻訳結果は、ウインドウ分割でOther-window-bufferに表示される。Google-translateとの同時使用が可能なので比較して選べる。

buffer表示のほかにposframeやpopup-windowなどカスタマイズできる。 Melpaからパッケージインストールできるが、こちらもDeePLユーザー登録してAPIキーの取得が必要。

3. kdmsnr/deepl-translate.el

brows-urlを使ってWEB版のDeePL翻訳を表示させるもの。リージョン選択範囲が自動で貼り付けられる。 パッケージツールではないので、設定コードをinit.el に貼り付けるだけで良く、APIキーも不要。

DeepL APIの認証キーの取得

EmacsからDeePLを使うためには、ユーザー登録をしてAPI認証キーを取得する必要がある。手順は下記の通り。 詳細は、こちら がわかりやすい。

  • 公式サイトにアクセスします。 DeepL
  • サイトの上の方に「API」と書いてあるところがあるのでそこをクリックします。
  • 無料で登録するをクリックしてアカウント登録を済ませます。
  • 登録後ログインしてアカウント情報を見るとAPI認証キーも表示されるのでコピーします。

Emacsの設定

とりあえず全部設定して使い分けてみることにした。

;; Deepl translation appears in minibuffer.
;; Also, the same content is copied to the clipboard
(leaf deepl-translate
  :el-get minorugh/deepl-translate
  :bind ("C-t" . deepl-translate)
  :custom (deepl-auth-key . "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX:XX"))


;; Deepl translate with go-translate
(leaf go-translate
  :ensure t
  :bind ("C-c t" . gts-do-translate)
  :config
  (setq gts-translate-list '(("en" "ja") ("ja" "en")))
  (setq gts-default-translator
		(gts-translator
		 :picker (gts-noprompt-picker)
		 :engines (list
				   (gts-deepl-engine :auth-key "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX:XX" :pro nil)
				   (gts-google-engine))
 		 :render (gts-buffer-render))))


;; Deepl translation on web page
(leaf my:deeple-traqslate
  :bind ("C-c d" . my:deepl-translate)
  :preface
  (defun my:deepl-translate (&optional string)
	(interactive)
	(setq string
          (cond ((stringp string) string)
				((use-region-p)
				 (buffer-substring (region-beginning) (region-end)))
				(t
				 (save-excursion
				   (let (s)
					 (forward-char 1)
					 (backward-sentence)
					 (setq s (point))
					 (forward-sentence)
					 (buffer-substring s (point)))))))
	(run-at-time 0.1 nil 'deactivate-mark)
	(browse-url
	 (concat
      "https://www.deepl.com/translator#en/ja/"
      (url-hexify-string string)
      ))))

評価

  1. deepl.el は、設定ファイルなどに書く短いコメント類の翻訳に適している。結果が気に入ればそのまま即yankで貼り付けできるので便利だ。自分的には、一番利用機会が多い。

競合コピーが生じないようにemacs-mozc辞書を共有する方法

mozc辞書をDropboxに置いて、そのシンボリックをそれぞれの各端末に貼って辞書共有をしている人は多いと思います。

リアルタイムで同時使用はしない…という使い方であれば何も問題ないのですが、 自分の場合は、基本メイン機のEmacsは起動しっぱなし(蓋閉じでSleep)なので、その状態でサブ機のEmacsを立ち上げると、mozcのON/OFFとは関係なく~/Dropbox/mozc/.mozc/ に競合コピーが量産されます。

そこで、簡単な回避方法を考えてみました。大げさなタイトルですが、Tipsといえるほどのものではありません。

ファイル体系

くどくど説明するより下図を見ていただければ、“な〜んだ” と理解いただけると思います。

メイン機の .mozc/ は、Dropboxに保存してシンボリックリンクで使い、サブ機で使うときはEmacsを起動するたびにDropboxにある最新の .mozc/ をコピーして使うという仕組みです。

※ maine-machine
~/.mozc <-- symbolic link -- ~/Dropbox/mozc/.mozc
                                | Copy latest every time
※ sub-mchine                    |
~/.mozc <-- symbolic link -- ~/Dropbox/backup/mozc/.mozc

Emacsの設定

emacsの設定ファイルはメイン機とサブ機で共有しているので、uname -n を条件子としてメイン機(自分の場合はe590)でない場合(サブ機のとき)は、emacsを起動したときにmozc辞書をコピーしています。シンボリックリンクは、一度貼っておけばコピーのたびに貼り直さなくても大丈夫です。

;; Clone the mozc dictionary placed in Dropbox to Nextcloud.
(defun mozc-copy ()
 "Copy mozc for submachine."
 (interactive)
 (unless (string-match "e590" (shell-command-to-string "uname -n"))
	 (compile "cp -rf ~/Dropbox/mozc/.mozc ~/Dropbox/backup/mozc")))
(add-hook 'emacs-startup-hook 'mozc-copy)

使い方

Dropboxに配置したmozc辞書は、メイン機での単語登録や入力履歴を記憶し常に最新の状態でバックアップされます。 サブマシーンの場合は、emacsを起動するたびに最新の辞書をコピーしてそれを使うという簡単な割り切りです。

サブマシーンで単語登録したら、元辞書へ書き戻すという仕組みも考えれますが、結局は、競合コピーをどう回避するかという課題になると思うので割り切ることにしました。良い方法があれば教えてください。

メイン機、サブ機の定義は特にありません。使用頻度の高い方をメイン機として構成すればいいかなと思います。

pangu-spacing.el : 全角と半角の間に自動でスペースを入れる

上記の Tipsを参考にして、markdown-modeに導入してみた。

日本語においては、いわゆる半角文字と全角文字の間にスペースを入れた方が見やすいと言われていて、pangu-spacing.el はそれを自動で行ってくれるというものです。

ただ、「Google日本語入力」などと書くときに「Google 日本語入力」ではおかしくなるし、なんとなく間延びした感じになるのが好みではなかったので アルファベット文字列の右端だけにスペースが入るようにカスタマイズした。

pangu-spacing.elのコードを見るとデフォルトでは、正規表現で [a-zA-Z0-9] なっているので半角数字の場合にも同様に処理されてしまう。 数字にも半角スペースが有効になると、[2021 年 11 月 16 日] という感じになるので、日付表示のケースでは面白くない。 そこで、正規表現の記述を [a-zA-Z] に変更して半角数字は対象外としアルファベットのみが pangu-spacingの対象となるようにしている。

(leaf pangu-spacing
  :ensure t
  :hook ((markdown-mode-hook text-mode-hook) . pangu-spacing-mode)
  :config
  (setq pangu-spacing-real-insert-separtor t)
  (setq pangu-spacing-include-regexp
		(rx (or (and (or (group-n 3 (any "。,!?;:「」()、"))
						 (group-n 1 (or (category japanese))))))
			(group-n 2 (in "a-zA-Z")))))

上記の例では、markdown-mode と text-modeに導入している。