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

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

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

Submit時のダブルクリック防止

運用している WEBサイトでフォームからの投稿ボタンやリセットボタンをダブルクリックされての不具合が頻発したので対策を施した。

スマホ端末のユーザーは問題ないのだが、PC環境でダブルクリックの癖が抜けない人が居るらしい。

参考にしたサイトは下記…というかそのまんまである。

JS本体

var dblClickFlag = null;

function ThroughDblClick() {
    // ダブルクリック(連続ポスト)の制御
    if (dblClickFlag == null) {
        dblClickFlag = 1;
        return true;
    } else {
        return false;
    }
}

onSubmitから呼び出す

<script>
var dblClickFlag = null;

function ThroughDblClick() {
    // ダブルクリック(連続ポスト)の制御
    if (dblClickFlag == null) {
        dblClickFlag = 1;
        return true;
    } else {
        return false;
    }
}
</script>

<form action="" method="post" onSubmit="return ThroughDblClick();">
   <input type="submit" value="送信">
</form>

2回目以降がFalseになるかをテスト

test.html に下記をコピペしてテストしてみる。

<script>
var dblClickFlag = null;

function ThroughDblClick() {
    console.log(dblClickFlag == null);
    // ダブルクリック(連続ポスト)の制御
    if (dblClickFlag == null) {
        dblClickFlag = 1;
        return true;
    } else {
        return false;
    }
}
</script>

<form action="" method="post" onSubmit="return ThroughDblClick();">
   <input type="submit" value="送信">
   <input type="button" value="test" onclick="ThroughDblClick();">
</form>
  1. Chromeで test.html ファイルを表示させてデベロッパーツールを起動
  2. testボタンをダブルクリック
  3. Consoleタブで 1度目 true / 2度目以降 false となることを確認できればOK

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 で設定を工夫することにした。

1回で複数のリモートoriginに`git push`する

SVCはバックアップという使命もあるので、特に重要なものを以下のTipsを参考に複数のリモートリポジトリにpushできるようにした。

*1回のgit pushで複数のoriginに送信する

サブのリモートに対して直接pushするとややこしくなると思うので、メインのリモートに不具合が生じたときなどにサブのリモートからでもpullできるというだけのメリットだが、これが保険として役に立つときがあるのも困るのである。

リモートリポジトリをGithubからXserverへ移転させる

Githubには、パブリックリポジトリとプライベートリポジトリがあって、Freeプランの場合、プライベートリポジトリのは容量的な制限があったが、最近仕様変更がありFreeプランでも容量無制限になったらしい。実行時間には制限があるが個人的な用途では実用上問題ないと思う。

ただ、現状のルールが未来永劫保証されることはなく、再び制限が厳しくなる可能性も考えられるので、プライベートリポジトリをレンタルサーバーのxserverへ引っ越すことにした。

Xserverへgitの最新版をインストールした

PrivateリモートリポジトリをGithubからxserverへ引越しさせた。xsrverには、デフォルトでgitインストール済みで特に不自由は感じなかったのだけれど、かなり古いバージョンだったので最新版をインストールした。

参考にしたTips

前者のTipsは、~/local/bin/ へinstallするというものだったけれど上手く行かなかった。 後者のTipsを試して最終的には成功したのですが、若干説明不足というか補足が必要な感じなので備忘録として残すことにした。

GNU gettextをインストール

$ cd tmp  # 作業ディレクトリへ移動
$ wget https://ftp.gnu.org/gnu/gettext/gettext-0.21.tar.gz
$ tar zxf gettext-0.21.tar.gz
$ rm gettext-0.21.tar.gz
$ cd gettext-0.21
$ ./configure --prefix=/home/YOUR USERNAME/opt
$ make install

インストールが成功したらmsgfmt のインストールを確認します。 無事バージョン情報が表示されたら成功です。

$ ~/opt/bin/msgfmt --version    # msgfmt のインスト―確認(インストールしたバージョン情報が表示されればOK)
$ cd  ..
$ rm -rf gettext-0.21  # 不要になったので削除
$ cd

パスを設定する

$ which msgfmt 

としても存在しないと叱られるので.basrc にPATHを設定します。 vim ~/.bashrc として下記の行を追加します。

# msgfmt
export PATH=$HOME/opt/bin:$PATH

終わったら、設定を適応させて確認します。

$ source .bashrc
$ which msgfmt       # パスを通したのでインストール先の確認
~/opt/bin/msgfmt   # インストール先が表示されればOK

gitをインストール

$ cd tmp  # 作業ディレクトリへ移動
$ wget https://www.kernel.org/pub/software/scm/git/git-2.30.0.tar.gz
$ tar zxf git-2.30.0.tar.gz
$ rm git-2.30.0.tar.gz
$ cd git-2.30.0
$ ./configure --prefix=/home/YOUR USERNAME/opt
$ make all
$ make install
$ ~/opt/bin/git --version   # git のインスト―確認(インストールしたバージョン情報が表示されればOK)
$ cd  ..
$ rm -rf git-2.30.0  # 不要になったので削除

先の設定で ~/opt/binにはすでにPATHが通っているので、確認する。

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で貼り付けできるので便利だ。自分的には、一番利用機会が多い。

Dockerを利用してCGI/SSI が動作するWEBサーバーを構築

Dockerの勉強を兼ねてCGI動作確認用のローカルWEBサーバーを構築してみた。

Docker Web サーバーの構築例は、nginx を使ったものが多かったが、自分が利用しているレンタルサーバー(xserver)の環境にあわせる必要があったので Apache で構成することにした。多くのTipsのうち偶然見つけた下記の記事がわかりやすく比較的楽に導入に成功した。

dockerインストール

自分の環境(Debian11)へのDockerインストールの手順は、下記makefile の通り。 ついでにdocker-composeもインストールして使えるようにした。

## makefile
docker: ## Install docker
	sudo apt-get install ca-certificates lsb-release
	curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
	echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian  $$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list
	sudo apt update
	sudo apt install docker-ce docker-ce-cli containerd.io

docker-compose: ## Install docker-compose
	sudo curl -L "https://github.com/docker/compose/releases/download/v2.0.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
	sudo chmod +x /usr/local/bin/docker-compose

dockerコマンドをsudoなしで実行できるようにする

dockerコマンドを入力するたびにsudoをつけるのは面倒なので下記の設定を行う。

# dockerグループを作る
$ sudo groupadd docker
# dockerを操作するユーザ(ここでは今のユーザ)をdockerグループに所属させる
$ sudo gpasswd -a $USER docker
# dockerデーモンを再起動する
$ sudo restart docker
# 再ログインすると反映される
$ exit
# Dockerをブート時に自動起動させる
$ sudo systemctl enable --now docker

頻繁の使うことになるコマンドは、zshrc (またはbashrc)に、aliasを設定しておくと更に便利