Emacs

概要

Emacsは非常に優れた拡張性を特徴とするテキストエディタ。 エディタに内蔵された言語Emacs Lispで柔軟に設定を変更できる。

できること。

  • 情報集積(org-mode, Org-roam)
  • タスク管理(org-mode)
  • Git(magit) + GitHub(magit-forge)
  • コマンド実行と結果出力(org-mode)
  • ウィンドウマネージャ(EXWM)
  • 静的サイトジェネレータ(org-mode)
  • ブラウザ(eww)
  • インターネットラジオプレーヤ(eradio)
  • フィードリーダー(elfeed.el)
  • ファイラ
  • 表計算
  • テスト実行(rspec-mode)
  • ターミナル実行(vterm)
  • Docker管理(docker-mode)
  • リマインダー(org-alert)
  • PDF閲覧(pdf-tools)

の機能を、共通の効率的なキーボード操作、プレーンテキストベースで行える。

自分の文芸的設定はkijimad Emacs Configにまとめてある。

Memo

guix でビルドする

ビルドしてからパッケージを登録する。

guix build emacs@29.1
guix package -i emacs@29.1
guix package --roll-back # ロールバックしたいとき

doom-modelineでアイコンが表示できないのを直す

(all-the-icons-install-fonts) しても直らないので困っていた。nerd-fontsでフォントを追加すると表示されるようになった。良い。

インストール方法のメモ。

$ git clone --depth 1 git@github.com:ryanoasis/nerd-fonts.git
$ ./install.sh

コンパイルする

Ubuntu 18.04.5 で Gcc Emacs をビルドする | Braindumpを参考にコンパイルする。

ソースコードをクローンしてくる。

$ git clone --single-branch --depth=1 https://github.com/emacs-mirror/emacs.git

.configureを生成する。

$ ./autogen.sh

configureを実行する。

$ ./configure --with-native-compilation=aot --with-modules --with-x && \
make && \
sudo make install

2つのregionを比較する

ediff-regions-wordwise を使う。

エラー時に表示する

M-x toggle-debug-on-error で、エラー時にトレースバッファが表示されるようになる。デバッグがしやすい。

subrを探索する

使用頻度の高いマクロが多数定義されている。

  • thread-first
  • defsubst
  • named-let

mermaidをorg-babelで実行する

  • mermaidインストールが面倒なのでDockerでやりたい
  • mermaid-modeは対応してるのだが、org-babelでのコマンドが間違っているので関数を修正
  • 画像は保存して、orgファイルにインライン表示したい
    • 名前がかぶって消されるとやっかいなので、ランダム文字列を自動生成する関数を作成

UbuntuでEmacsをビルドする

System Crafters Live! - August 20, 2021 - System Craftersを参考に、ローカルマシン(Ubuntu)でビルドする。

sudo apt install ncurses-dev gnutls-bin

あとはINSTALL.repoに乗っているコマンドを実行する。

./autogen.sh
./configure
make
./src/emacs

コマンドを再実行する

C-x z (repeat) で直前のコマンドを再実行できる。

C-x M-: (repeat-complex-command) で直前のコマンドをS式表示しつつ実行できる。

TODO 覚えるコマンド

移動に便利なコマンド。

  • M– マイナスの前置引数
    • 例えばC-M– C-f は1文字戻る
    • M– M-d 前の単語を削除
    • M– C-k kill-lineの逆、行の左側を削除
  • C-M-d, C-M-u S式の中、外に移動する
  • C-M-n, C-M-p 次のリスト、前のリストに移動する
  • C-M-k S式を削除する
  • M-}, M-{ パラグラフの最後、最初に移動する
  • M-a, M-e センテンスの先頭、最後に移動する
  • C-M-a, C-M-e defunの先頭、最後に移動する
  • C-M-v 別ウィンドウを下方にスクロール
  • M-r ポイントを左上、左、左下に移動

削除。

  • M-k センテンスを削除
  • C-M-k S式を削除
  • C-S-<backspace> 現在の行を削除

ブックマーク。

  • C-x r m ブックマークをセット

レジスター。

  • C-x r s レジスターにリージョンを保存
  • C-x r SPC レジスターにポイントを保存
  • C-x r j レジスターにジャンプ
  • C-x r i レジスターの内容を挿入

マーク。

  • M-h 次のパラグラフをマーク
  • C-M-h 次のdefunをマーク
  • C-x h バッファー全体をマーク
  • C-x C-p 次のページをマーク
  • M-@ 次の単語をマーク
  • C-M-@ 次のS式をマーク

検索。

  • C-M-s regexpインクリメンタル検索を開始する

キルリング。

  • C-M-w 次のコマンドがkillコマンドなら、killリングに追加する

入れ替え。

  • C-x C-t 行の入れ替え

コメント。

  • C-x C-; 行をコメント化あるいは非コメント化
  • M-x comment-box リージョンを囲み枠でコメント化する

大文字化。

  • M-c 次の単語をcapitalize(Word)
  • M-u 次の単語を大文字に(WORD)
  • M-l 次の単語を小文字に(word)

リンクを開くときにブラウザにフォーカスさせない

firefox側で設定する。about:config をURLバーに打ち込み、設定項目 browser.tabs.loadDivertedInBackground を true にする。 https://stackoverflow.com/questions/10506496/run-browse-url-in-emacs-without-giving-focus-to-the-browser

バッファ管理

rails開発で処理が多くのファイルに分散していて、同じような名前のファイルが多い場合、集中のスイッチングコストが大きくなる。今まではあまりファイル分散してなかったので counsel-switch-buffer ひとつで問題なかったが、タスクやコードの構造によって問題になりうる。ファイル移動しようとするたびに思考が中断される。

  • 方法1: projectile-railsのキーバインドを覚える https://qiita.com/yoshinarl/items/8e3c4b075a181f224591
    • projectile-rails-find-current-spec(C-c r P) 対応するテストファイルを開く
    • MVC系の移動 C-c r M、C-c r C、C-c r V
  • 方法2: バッファをリセットする
    • 無関係な似たような名前のバッファが溜まるのが問題であって、そもそもこまめにリセットすれば見やすくなる説
  • 方法3: ブックマーク
    • 必要なものはブックマークしておく。メンドイ。
  • 方法4: タブ表示
    • ブラウザのように、タブに表示する。結局ファイル名が長かったり、ファイル名が多くなるとやりにくくなるのだが、直近3つくらいのファイルが見られるだけでも改善する

良いページを発見した。Buffer management - WikEmacs

リストの重複を削除する

(defun delete-dups (list) “Destructively remove `equal’ duplicates from LIST. Store the result in LIST and return it. LIST must be a proper list. Of several `equal’ occurrences of an element in LIST, the first one is kept. See `seq-uniq’ for non-destructive operation.” (let ((l (length list))) (if (> l 100) (let ((hash (make-hash-table :test #’equal :size l)) (tail list) retail) (puthash (car list) t hash) (while (setq retail (cdr tail)) (let ((elt (car retail))) (if (gethash elt hash) (setcdr tail (cdr retail)) (puthash elt t hash) (setq tail retail))))) (let ((tail list)) (while tail (setcdr tail (delete (car tail) (cdr tail))) (setq tail (cdr tail)))))) list)

100以下の処理

リストの長さによって処理が分岐していることがわかる。まず100行以下での処理を見る。

(let ((tail list))
        (while tail
          (setcdr tail (delete (car tail) (cdr tail)))
          (setq tail (cdr tail))))

これで重複削除になるのか、と不思議な感じがする。不明な関数を調べる。

setcdr

Setcdr (GNU Emacs Lisp Reference Manual)

setcdrが非常に重要な処理をしているように見えるので調べる。

(setq x '(1 2 3))
(setcdr x '(4))
x

(1 4)

コンスセルのcdrを変更することで、他のリストに置き換える働きをする。

delete

(setq x '(1 2 3 4))
(delete x '(3))
x

(1 2 3 4)

処理に戻る

本筋に戻る。

(delete (car tail) (cdr tail))

は、値を決めて(car tail)、それをリストから取り除く。 例) ’(1 1 2 3) -> ’(2 3) になる。carの1を取り上げて、cdrに含まれる1を削除する。

(setcdr tail (delete (car tail) (cdr tail)))

そしてsetcdrによって、1つになった値と残りの値を連結する。 ’(1) + ’(2 3) -> ’(1 2 3)

(setq tail (cdr tail))

次の値を計算するために、単独になった値を除いたリストにする。 ’(1 2 3) -> ’(2 3)

ここでは、tailだけが次の計算に向けて再代入されたのであって、値の元になったlistは再代入されずに’(1 2 3)が入ったままになっている。すべての計算が終わったあとlistを評価すれば、望みの値が得られる。tailはループ用で、最終的にnilになる。

ハッシュテーブルを使うバージョン

リストの要素が100より大きいとき、ハッシュテーブルを使う実装になっている。100は何基準だろうか。

(let ((hash (make-hash-table :test #'equal :size l))
      (tail list) retail)
  (puthash (car list) t hash)
  (while (setq retail (cdr tail))
    (let ((elt (car retail)))
      (if (gethash elt hash)
          (setcdr tail (cdr retail))
        (puthash elt t hash)
        (setq tail retail)))))
  • ハッシュにすでに入っている値であれば、その要素を取り除いて次の探索へ
  • ハッシュに入っていなければ登録して次の探索

この方法だとループは多いけど、メモリ消費がとても少ないということか。一度のループで比較するのは最初の要素とハッシュの要素だけだ。

インライン展開するdefsubst

Emacs Lisp defsubstで定義した関数はbyte compileのときにインライン展開される。つまり、コンパイルして関数実行時の関数呼び出しのコストがなくなり高速になる。頻繁に用いられる小さな関数で有効。

(defun plus2 (x)
  (+ x 2))
(byte-compile 'plus2)

(defun foo (lst)
  (plus2 lst))
(byte-compile 'foo)

#[(lst) \301!\207 [lst plus2] 2]

(defsubst plus2 (x)
  (+ x 2))
(byte-compile 'plus2)

(defun foo (lst)
  (plus2 lst))
(byte-compile 'foo)

#[(lst) “\211\302\\)\207” [lst x 2] 3]

(defmacro defsubst (name arglist &rest body) “Define an inline function. The syntax is just like that of `defun’.

\(fn NAME ARGLIST &optional DOCSTRING DECL &rest BODY)“ (declare (debug defun) (doc-string 3) (indent 2)) (or (memq (get name ‘byte-optimizer) ’(nil byte-compile-inline-expand)) (error ”`%s’ is a primitive“ name)) `(prog1 (defun ,name ,arglist ,@body) (eval-and-compile ;; Never native-compile defsubsts as we need the byte ;; definition in `byte-compile-unfold-bcf’ to perform the ;; inlining (Bug#42664, Bug#43280, Bug#44209). ,(byte-run–set-speed name nil -1) (put ‘,name ‘byte-optimizer ‘byte-compile-inline-expand))))

編集関数ファイルsimple.el

/lisp/simple.elには基本的な編集関数がある。

;;; simple.el — basic editing commands for Emacs -- lexical-binding: t --

正規表現置換

入れ替えとかしたい。

検索結果に対する置換 counsel-git-grep-query-replace

counsel-git-grep したあとに counsel-git-grep-query-replace(M-q)することで、git grepした結果に対して置換をかけられる。

message関数はどう動いているか

message関数はメッセージをmessageバッファ、モードラインに表示する関数である。フォーマットを指定できる機能もある。これはどうやって動いているのだろうか。Lispの形をしたC言語で定義されている。

DEFUN (“message”, Fmessage, Smessage, 1, MANY, 0, doc: /* Display a message at the bottom of the screen. (ptrdiff_t nargs, Lisp_Object *args) { if (NILP (args[0])

  (STRINGP (args[0])

&& SBYTES (args[0]) == 0)) { ;; 最初の引数(表示文字列)がないときはクリア message1 (0); return args[0]; } else ;; 引数があるとき、オプションをLisp Objectにして関数に渡す { Lisp_Object val = Fformat_message (nargs, args); ;; フォーマット message3 (val); ;; 表示 return val;; } }

文字列のときはそのまま表示し、フォーマットが必要なときはフォーマットする、ということか。message3とmessage1の違いは何。この関数名の意味。

void message3 (Lisp_Object m) { clear_message (true, true); / メッセージ削除 cancel_echoing (); / エコーエリアをリセット

* First flush out any partial line written with print. * message_log_maybe_newline (); if (STRINGP (m)) { ptrdiff_t nbytes = SBYTES (m); bool multibyte = STRING_MULTIBYTE (m); char *buffer; USE_SAFE_ALLOCA; SAFE_ALLOCA_STRING (buffer, m); message_dolog (buffer, nbytes, true, multibyte); // 本筋の処理 SAFE_FREE (); } if (! inhibit_message) message3_nolog (m); }

本筋はmessage_dologを呼ぶこと。ほかはそのための準備とか、オプションに対応してる部分。

void message1 (const char *m) { message3 (m ? build_unibyte_string (m) : Qnil); }

void message_log_maybe_newline (void) { if (message_log_need_newline) message_dolog (“”, 0, true, false); }

#define USE_SAFE_ALLOCA \ ptrdiff_t sa_avail = MAX_ALLOCA; \ specpdl_ref sa_count = SPECPDL_INDEX ()

* SAFE_ALLOCA_STRING allocates a C copy of a Lisp string. *

#define SAFE_ALLOCA_STRING(ptr, string) \ do { \ (ptr) = SAFE_ALLOCA (SBYTES (string) + 1); \ memcpy (ptr, SDATA (string), SBYTES (string) + 1); \ } while (false)

/* Add a string M of length NBYTES to the message log, optionally terminated with a newline when NLFLAG is true. MULTIBYTE, if true, means interpret the contents of M as multibyte. This function calls low-level routines in order to bypass text property hooks, etc. which might not be safe to run.

This may GC (insert may run before/after change hooks), so the buffer M must NOT point to a Lisp string. */

void message_dolog (const char *m, ptrdiff_t nbytes, bool nlflag, bool multibyte) { const unsigned char *msg = (const unsigned char *) m;

if (!NILP (Vmemory_full)) return;

if (!NILP (Vmessage_log_max)) { struct buffer *oldbuf; Lisp_Object oldpoint, oldbegv, oldzv; int old_windows_or_buffers_changed = windows_or_buffers_changed; ptrdiff_t point_at_end = 0; ptrdiff_t zv_at_end = 0; Lisp_Object old_deactivate_mark;

old_deactivate_mark = Vdeactivate_mark; oldbuf = current_buffer;

* Sanity check, in case the variable has been set to something invalid. * if (! STRINGP (Vmessages_buffer_name)) Vmessages_buffer_name = build_string (“Messages”); * Ensure the Messages buffer exists, and switch to it. If we created it, set the major-mode. * bool newbuffer = NILP (Fget_buffer (Vmessages_buffer_name)); Fset_buffer (Fget_buffer_create (Vmessages_buffer_name, Qnil)); if (newbuffer && !NILP (Ffboundp (intern (“messages-buffer-mode”)))) call0 (intern (“messages-buffer-mode”));

bset_undo_list (current_buffer, Qt); bset_cache_long_scans (current_buffer, Qnil);

oldpoint = message_dolog_marker1; set_marker_restricted_both (oldpoint, Qnil, PT, PT_BYTE); oldbegv = message_dolog_marker2; set_marker_restricted_both (oldbegv, Qnil, BEGV, BEGV_BYTE); oldzv = message_dolog_marker3; set_marker_restricted_both (oldzv, Qnil, ZV, ZV_BYTE);

if (PT == Z) point_at_end = 1; if (ZV == Z) zv_at_end = 1;

BEGV = BEG; BEGV_BYTE = BEG_BYTE; ZV = Z; ZV_BYTE = Z_BYTE; TEMP_SET_PT_BOTH (Z, Z_BYTE);

* Insert the string–maybe converting multibyte to single byte or vice versa, so that all the text fits the buffer. * if (multibyte && NILP (BVAR (current_buffer, enable_multibyte_characters))) { * Convert a multibyte string to single-byte for the Message buffer. * for (ptrdiff_t i = 0; i < nbytes; ) { int char_bytes, c = check_char_and_length (msg + i, &char_bytes); char work = CHAR_TO_BYTE8 (c); insert_1_both (&work, 1, 1, true, false, false); i += char_bytes; } } else if (! multibyte && ! NILP (BVAR (current_buffer, enable_multibyte_characters))) { * Convert a single-byte string to multibyte for the Message buffer. * for (ptrdiff_t i = 0; i < nbytes; i++) { int c = make_char_multibyte (msg[i]); unsigned char str[MAX_MULTIBYTE_LENGTH]; int char_bytes = CHAR_STRING (c, str); insert_1_both ((char *) str, 1, char_bytes, true, false, false); } } else if (nbytes) insert_1_both (m, chars_in_text (msg, nbytes), nbytes, true, false, false);

if (nlflag) { ptrdiff_t this_bol, this_bol_byte, prev_bol, prev_bol_byte; intmax_t dups;

/* Since we call del_range_both passing false for PREPARE, we aren’t prepared to run modification hooks (we could end up calling modification hooks from another buffer and only with AFTER=t, Bug#21824). */ specpdl_ref count = SPECPDL_INDEX (); specbind (Qinhibit_modification_hooks, Qt);

insert_1_both (“\n”, 1, 1, true, false, false);

scan_newline (Z, Z_BYTE, BEG, BEG_BYTE, -2, false); this_bol = PT; this_bol_byte = PT_BYTE;

* See if this line duplicates the previous one. If so, combine duplicates. * if (this_bol > BEG) { scan_newline (PT, PT_BYTE, BEG, BEG_BYTE, -2, false); prev_bol = PT; prev_bol_byte = PT_BYTE;

dups = message_log_check_duplicate (prev_bol_byte, this_bol_byte); if (dups) { del_range_both (prev_bol, prev_bol_byte, this_bol, this_bol_byte, false); if (dups > 1) { char dupstr[sizeof “ [ times]”

  • INT_STRLEN_BOUND (dups)];

* If you change this format, don’t forget to also change message_log_check_duplicate. * int duplen = sprintf (dupstr, “ [%”PRIdMAX“ times]”, dups); TEMP_SET_PT_BOTH (Z - 1, Z_BYTE - 1); insert_1_both (dupstr, duplen, duplen, true, false, true); } } }

/* If we have more than the desired maximum number of lines in the Messages buffer now, delete the oldest ones. This is safe because we don’t have undo in this buffer. */

if (FIXNATP (Vmessage_log_max)) { scan_newline (Z, Z_BYTE, BEG, BEG_BYTE, -XFIXNAT (Vmessage_log_max) - 1, false); del_range_both (BEG, BEG_BYTE, PT, PT_BYTE, false); }

unbind_to (count, Qnil); } BEGV = marker_position (oldbegv); BEGV_BYTE = marker_byte_position (oldbegv);

if (zv_at_end) { ZV = Z; ZV_BYTE = Z_BYTE; } else { ZV = marker_position (oldzv); ZV_BYTE = marker_byte_position (oldzv); }

if (point_at_end) TEMP_SET_PT_BOTH (Z, Z_BYTE); else * We can’t do Fgoto_char (oldpoint) because it will run some Lisp code. * TEMP_SET_PT_BOTH (marker_position (oldpoint), marker_byte_position (oldpoint));

unchain_marker (XMARKER (oldpoint)); unchain_marker (XMARKER (oldbegv)); unchain_marker (XMARKER (oldzv));

/* We called insert_1_both above with its 5th argument (PREPARE) false, which prevents insert_1_both from calling prepare_to_modify_buffer, which in turns prevents us from incrementing windows_or_buffers_changed even if Messages is shown in some window. So we must manually set windows_or_buffers_changed here to make up for that. */ windows_or_buffers_changed = old_windows_or_buffers_changed; bset_redisplay (current_buffer);

set_buffer_internal (oldbuf);

message_log_need_newline = !nlflag; Vdeactivate_mark = old_deactivate_mark; } }

本体コードを読む

https://systemcrafters.net/live-streams/august-20-2021/ まずビルドする。

guix environment --pure emacs-next --ad-hoc git

ほとんどはEmacs Lispで書かれている。コアな部分はC languagesrc/* にはCのコードがある。 src/lisp.hEmacs Lispそのもののコードで重要。

How to read (and understand) GNU Emacs source code? : emacs

コードの読み方の参考。 ソースコードの読み方 - Solist Work Blog

プロセス管理

最初から入っている proced が便利。一覧に加えてkillもできる。

デフォルトの動的補完 dabbrev

dabbrevはEmacsデフォルトの補完機能。大雑把で高速。 dabbrev-expand (M-/)

デフォルトの定義探す系 xref

xref-find-definitions (M-.)
定義ジャンプ
xref-find-references (M-?)
参照箇所(使われてるところ)にジャンプ

lspと接続しているとそっちを使って辿るようだ。

LSP設定

LSPを設定する。

  • lsp-modeはLSP全般を扱うパッケージである。lsp-install-serverで各プログラミング言語のLSP serverをダウンロード・起動することで有効化できる。
  • lsp-ui-modeはlsp-modeのグラフィック面を強化するパッケージである(optional)。ドキュメント表示、エラー表示などができるようになる

ScalaLSPサーバーであるmetalsはGPGエラーでインストールできない状態だった。 (setq lsp-verify-signature nil) するとインストールできる。

https://github.com/coursier/coursier/issues/2346

corfu設定

git-timemachineで歴史を見る

emacsmirror/git-timemachine: Walk through git revisions of a file git-timemachine を使うと、ファイルごとで軽々とコミットを辿れて楽しい。すごいのにあまり有名ではないよう。

  • n で次のコミット、p で前のコミットに移る。
  • c でコミット内容を見る。ファイルビューではコミット時点のファイルだけなので、差分をみたいときにはこっちを使う。

faceを調べる

M-x list-faces-display

diredでzip展開

dired-modeで Z … dired-do-compress で展開か圧縮できる。

矩形選択する

rectangle-mark-mode – C-x SPC で矩形選択できる。

back-button-modeのキーバインドが衝突してたのでback-button-modeの方を無効化。

(define-key back-button-mode-map (kbd "C-x SPC") nil)

pdf-tools

pdf-toolsはEmacs内でPDFを閲覧できるパッケージ。 インストールしても、しばらくすると利用不可になってることがあるのでinstallしなおす。 インストールするとdiredから選択するとpdf-toolsが使われる。

(pdf-tools-install)

ivy-occurで一括置換する

occurの特徴はeでedit-modeに入れる点。一覧にして複数ファイルをそのまま置換できる。

プロジェクトのファイルにすべてに対してoccurしたいとき。 counsel-git-grep して絞り込んだものに対して (ivy-occur) = C-c C-o でoccurできる。

edit-modeに入るには。 (ivy-wgrep-change-to-wgrep-mode) = C-x C-q occurでの変更をコミットするには。 C-x C-s 。覚えにくい。

絵文字挿入

C-x 8 return

elisp-refs

Wilfred/elisp-refs: semantic code search for emacs lisp 関数や変数の参照を見つけるパッケージ。 じっさいに使われている例を探し出すことができる。

File: /gnu/store/lnwgc4ww47vkq2wv2ay3rdm0ppnmgyfy-emacs-27.2/share/emacs/27.2/lisp/format.el.gz
(setq prop (pop props))

File: /gnu/store/lnwgc4ww47vkq2wv2ay3rdm0ppnmgyfy-emacs-27.2/share/emacs/27.2/lisp/window.el.gz
(let ((window (pop window-state-put-stale-windows)))

キーバインドの一覧を出力するコマンド

キーバインドの一覧を出力するコマンド。 make-command-summary

複数行に空白挿入

複数行一気に空白を挿入してインデントを整えるとき。 C-x C-i で挿入モードにあるのであとは方向キーで微調整できる。

文字コード変更

改行コードなどで問題が生じたとき。^Mが表示されるとか。 C-x RET f utf-8-unix で文字コードを変更して保存する。

カウントダウンタイマー

カップ麺の3分を測りたい、みたいなとき。 (org-timer-set-timer)

company-documentation

Company is input complement package. Read document on complementation list. Ctrl + Shift + h

vtermのbash_profileを設定する

if [[ "$INSIDE_EMACS" = 'vterm' ]] \
    && [[ -n ${EMACS_VTERM_PATH} ]] \
    && [[ -f ${EMACS_VTERM_PATH}/etc/emacs-vterm-bash.sh ]]; then
    source ${EMACS_VTERM_PATH}/etc/emacs-vterm-bash.sh
fi

vterm-modeを使う - technokton

projectile-toggle-between-implementation-and-test (C-c p t)

テストとプロダクトコードを切り替える。

(magit-topic)

一覧でPRとIssueにアクセスできると行き来がしやすそう。 Forgeのトピック間移動がhelmでできたらいいのにな。

MySQLと接続してSQLを直に実行する

M-x customize-variable RET sql-connection-alist RET ...
M-x sql-connect
open buffer...
M-x sql-mode
M-x sql-set-sqli-buffer RET
sql-send-buffer (C-c C-b)
sqlup-capitalize-keywords-in-region (C-c u)

Macでの置換

  • Macでは query-replace (M-%)が奪われて押せないので、 query-replace-regexp (C-M-%)する。

spring RSpec

Railsでspringを走らせておくとRSpecが高速に動作する。

いつのまにかEmacsでspringが動かなくなっていた。コマンドでは spring rspec などで動かせるが、Emacsではふつうの bundle exec ... になっていた。前は早くできていたはず。ネットの記事を参考に修正した。

emacsのrspec-modeで、上記のようにしてspringを使うと、自動ではspringがONにならない。 これはtmpにpidファイルが作られないため。 強制的にspringを使うには、次のように設定する。

(require 'rspec-mode)
(defun rspec-spring-p ()
  (and rspec-use-spring-when-possible
       (stringp (executable-find "spring"))))

springを自動で検出してくれるらしい(何より今までできてた)。 spring status でspringが動いてるかチェックする。動いてなかったら spring rspec する。

デバッグ起動する

$ emacs -q
# init読み込まない
$ emacs --debug-init
# デバッグモードで起動する

System Crafters IRC

erc-tls irc.libera.chat 6697 /join #systemcrafters

mermaid.jsをEmacsで使う

mermaidはプレーンテキストで図を作れるツール。JavaScriptで書かれている。 これをEmacsで扱えるようにするのがmermaid-mode。

npm install -g @mermaid-js/mermaid-cli

して、C-cで画像にコンパイルできる。

各Window managerでEmacs風キーバインドにする

# gnome
gsettings set org.gnome.desktop.interface gtk-key-theme Emacs
# MATE
gsettings set org.mate.interface gtk-key-theme Emacs
# Cinnamon
gsettings set org.cinnamon.desktop.interface gtk-key-theme Emacs

counsel-find-fileで新しいファイルを作る

補完選択になってしまって新しくファイルが作れないときは、C-M-jで新規作成できる。

counselのリポジトリのREADMEの最後に書かれてた。

(setq ivy-use-selectable-prompt t)

をすると上下選択できるようになる。 選択一覧にはでないのでわかりにくい。

blame系関数

  • vc-annotate
  • magit-blame
  • blamer

keybinding一覧

(make-command-summary)(describe-bindings) で生成できる。

リンクを開くときにブラウザにフォーカスさせない

firefox側で設定する。about:config をURLバーに打ち込み、設定項目 browser.tabs.loadDivertedInBackground を true にする。 https://stackoverflow.com/questions/10506496/run-browse-url-in-emacs-without-giving-focus-to-the-browser

Tasks

TODO Evolution of Emacs Lisp

Emacs Lispの進歩。

TODO textlintのパッケージを作る

今のところ、ない。

TODO マシンごとの分岐をする

  • 仕事用とプライベート用の違い
  • デスクトップとラップトップの違い
  • モニター解像度の違い

があるので、判定できるようにする。

TODO denoteを読む

シンプルなノートパッケージ。

TODO rfc-modeを読む

rfcドキュメントを読むモード。あまり変化しない特定のドキュメントに特化したモードは便利そう。

TODO hydraはどうやって登録キーをキーマップに登録しているか   DontKnow

アルファベットで指定して初期化するだけでメニュー表示に追加し、キーバインドを作成する。これはどうやっているか。hydra起動中はキーバインドが一切衝突しないのも気になる。

(defhydra hydra-zoom (global-map "<f2>")
  "zoom"
  ("g" text-scale-increase "in")
  ("l" text-scale-decrease "out"))

(when overriding-terminal-local-map (internal-pop-keymap hydra-curr-map ’overriding-terminal-local-map))))

TODO バッファ管理

rails開発で処理が多くのファイルに分散していて、同じような名前のファイルが多い場合、集中のスイッチングコストが大きくなる。今まではあまりファイル分散してなかったので counsel-switch-buffer ひとつで問題なかったが、タスクやコードの構造によって問題になりうる。ファイル移動しようとするたびに思考が中断される。

  • 方法1: projectile-railsのキーバインドを覚える https://qiita.com/yoshinarl/items/8e3c4b075a181f224591
    • projectile-rails-find-current-spec(C-c r P) 対応するテストファイルを開く
    • MVC系の移動 C-c r M、C-c r C、C-c r V
  • 方法2: バッファをリセットする
    • 無関係な似たような名前のバッファが溜まるのが問題であって、そもそもこまめにリセットすれば見やすくなる説
  • 方法3: ブックマーク
    • 必要なものはブックマークしておく。メンドイ。
  • 方法4: タブ表示
    • ブラウザのように、タブに表示する。結局ファイル名が長かったり、ファイル名が多くなるとやりにくくなるのだが、直近3つくらいのファイルが見られるだけでも改善する

良いページを発見した。Buffer management - WikEmacs

TODO ParEdit チュートリアル

すごい便利らしいのでやってみる。

TODO use-packageでパッケージ設定を書き直す

パッケージごとの設定がわかりづらい状態なので、書き直す。とくにhook、keybindあたりはあちこちに散らかっている。

TODO 今日の残り時間をゲージで表示する

時計より視覚的にわかりやすそうなので、polybarで出すようにしよう。

TODO recompileが別ウィンドウで立ち上がるようにする

recompileは直前のcompilationの再実行関数。 実行して別ウィンドウのバッファが残るはずなのだが、何かウィンドウに関するパッケージを入れたせいで挙動が変わっている。 消えてしまう。

TODO 正規表現置換が2度目以降失敗する

プログラム関係のmodeで起こることを確認(orgでは起こらない)。

vr–perform-query-replace: Match data clobbered by buffer modification hooks

TODO ソースコードの読み方 - Solist Work Blog

Emacs, Magitで解説している。

TODO .emacs.d整理

package管理もちゃんと使って設定したい。

TODO popper(emacs package)

ウィンドウの出る方法を設定できるパッケージ。

TODO EmacsとレプリカDB接続

ローカルではできるが、SSHを使う方法がわかっていない。 SQLを打つときに不便なので。

TODO lsp-mode が何かと競合する

消したときに近くの矩形が消されてしまう謎の挙動。

TODO プロジェクトファイル限定のswitch-buffer。

projectile-switch-to-buffer (C-c p b)

TODO プロジェクトをag検索

projectile-ag (C-c p s s)

TODO vtermでカッコが入力できない

vtermは互換性が高いShellのターミナルパッケージ。

カッコを自動入力するモードにしていると入力できなる。 なのでカッコ関係はコピペする羽目になる。

TODO highlight-indent-guides-mode でコピペしたときにおかしい

highlight-indent-guides-mode でコピペしたときに表示がおかしい。 faceまでコピーするから仕方ないのかな。

TODO GTAGSを使えるようにする

読むときにたどれないと不便。

TODO 使ってないコマンドを開拓するパッケージ

最初にコマンド一覧をどこからか出力する。それから、何かコマンドを打つごとに数を保存する。そうしてしばらく使っていくと、使ったことのないコマンドが明らかになる。カバレッジ率的にやって面白そうだろう。

TODO Appendix D Tips and Conventions

Emacs Lisp Manual。いくつか面白そうな章がある。

References

Spacious Padding (spacious-padding.el) | Protesilaos Stavrou

Emacsのpaddingをいい感じにするパッケージ。

Use GNU Emacs

新しいチュートリアル。

Org Themes collection

Org Exportテーマのコレクション。

Configurations for GNU Emacs

文芸的プログラミング。

Free software, free society: Richard Stallman at TEDxGeneva 2014 - YouTube

リチャード・ストールマンのTED Talk。

cysh

独自のサイトジェネレータで作成されたサイト。

emacs.d でないところで init.el を育てる方法

ユーザディレクトリ以外に配置した.emacs.dを使う方法。

Pragmatic Emacs | practical tips for everyday emacs

役立つtipsが紹介されているサイト。

How Emacs changed my life

Matzのスライド。 Emacsを単に道具として使うだけでなく、Rubyを書くうえでの参考にもなったという。

EmacsWiki: Hacker Guide

Emacsの簡単な概要。

Hacking your way around in… by Marcin Borkowski

Emacsの本。買うか迷ってる。

A textlint flycheck checker in Emacs

EmacsでのTextlint設定方法。

A Simple Guide to Writing & Publishing Emacs Packages

パッケージを作ってみる記事。

alphapapa/emacs-package-dev-handbook

Emacsパッケージのハンドブック。

Making Emacs Work For Me

設定解説。

Emacs Lisp 組込み関数

基本的な関数をまとめたリスト。

(think)

Emacsのブログ。

The Kitchin Research Group

Emacsのブログ。

Theresa O’Connor

Emacsの情報集約サイト。

Emacs is Not Enough

読み物。

Archives

DONE git-linkでコミットハッシュからリンクを取る

デフォルトだと現在のブランチから名前を取るため、remoteに存在しないブランチのとき無効なリンクになる。 ブランチ名を固定もできるが、汎用性がなくなる。 コミットハッシュから取ればいい。

(setq git-link-use-commit t)

DONE esh-autosuggestを導入する

履歴を自動入力。

(use-package esh-autosuggest
  :hook (eshell-mode . esh-autosuggest-mode)
  :config
  (setq esh-autosuggest-delay 0.5)
  (set-face-foreground 'company-preview-common "#4b5668")
  (set-face-background 'company-preview nil))

DONE undo履歴を保持しないときがある

3つくらいしか戻れないときがあり、原因は不明。 undo-treeで戻れる。

DONE 列名を表示する

(global-display-line-numbers-mode) linum-modeよりこっちのほうがいいらしい。 linum-modeは重かった。 外観も綺麗。

DONE diredで直に編集する

(wdired-change-to-wdired-mode) 編集モードにして C-c C-c で実行する。

DONE 見出しレベル替え

(org-cycle-level) 何も書いてない見出しでtab。

DONE 見出し挿入

C-enter 前の項目がリストでも見出しが挿入できる。

DONE C-c n

roam のプレフィクスキー。

DONE projectile-find-file (C-c p f)

プロジェクト全体のファイル名検索。

DONE projectile-switch-project (C-c p p)

プロジェクトを切り替える。 CLOSED: [2021-06-03 木 21:42]

DONE robe-doc (C-c C-d)

Rubyメソッドを調べられる。gemがあるプロジェクトのGemfileで pry, pry-doc をインストールして実行するとpryが起動して、以後使えるようになる。これは補完の company.el と連携させているため、pryを起動しないことにはgemの補完は表示されない。

DONE vterm-copy-modeC-c C-t

vterm上で、eshellなどのように自由に動き回るモード。

DONE 使用パッケージで分岐するとき、どうやってbyte-compileエラーを回避するのか

たとえばhelmを使ってるときはこれ、ivyのときはこれとかでrequireするものは変わるものだが。既存パッケージはどうしているのだろう。

↓とかやった。外部のコマンドは最初に定義しておいた。

(defvar w3m-current-url)
(declare-function w3m-current-title "ext:w3m-util")

CLOSE Emacsをビルドしてみる

https://systemcrafters.net/live-streams/august-20-2021/

この通りにやって簡単にできた。

src/emacs にビルド結果が生成される。

DONE org-publishのスタイルを設定する

https://ogbe.net/blog/blogging_with_org.html

contentのhtmlをいじることができなかったが、とりあえずcssで指定してOK。

DONE autosaveが出てきてうざい

Guixでは編集すると毎回プロンプトが出てくる。

(defun ask-user-about-supersession-threat (fn)
  "blatantly ignore files that changed on disk"
)
(defun ask-user-about-lock (file opponent)
  "always grab lock"
t)

;; or

(setq revert-without-query '(".*"))

を実行したが、変わらなかった。

↓できた。

(setq auto-save-timeout 2)
(setq auto-save-visited-interval 2)
(setq auto-save-no-message t)
(auto-save-visited-mode)

CLOSE Projectileの幅がせまくて見づらい

ほかのcompletionは幅いっぱいにハイライトされるが、projectileは文字のあるところしかハイライトされないので短い検索のときに見えにくい。たとえば counsel-find-file とか counsel-find-file と比べるとわかる。

counsel-projectileを使えば問題ない。でもデフォルトの動作がおかしいので修正したいところ。 いや、ivyの問題ぽい。ivy yasnippetをすると同じような状態になる。テーマを変えてもそうなる。

org-refileでも同じような感じ。要調査。 共通点は、右側にアノテーションが出ないときか。そのときはfaceが設定されないので右側まで伸びない。

DONE org-mode のキーバインド

見出し移動とか。

DONE lsp setup(TypeScript)

  1. install language-server

https://deno.land/#installation

curl -fsSL https://deno.land/x/install/install.sh | sh
  1. Install lsp-mode package
  2. Add lsp settings to init.el
  3. emacsにlsp-mode入れた - takeokunn’s blog

DONE Vterm settings

  • prompt settings

DONE lispxmpを設定する

xmp-filterのelisp版。

(require 'lispxmp)
(define-key emacs-lisp-mode-map (kbd "C-c C-d") 'lispxmp)

DONE pareditを設定する

;;;括弧の対応を保持して編集する設定
(require 'paredit)
(add-hook 'emacs-lisp-mode-hook 'enable-paredit-mode)
(add-hook 'lisp-interaction-mode-hook 'enable-paredit-mode)
(add-hook 'lisp-mode-hook 'enable-paredit-mode)
(add-hook 'ielm-mode-hook 'enable-paredit-mode)

DONE リンクを保存する方法をどうにかする

ブラウザからコピペするのがメンドイのでどうにかする。 org-protocolを設定して簡単に保存できるようにした。

DONE 踏み台SSH接続を簡単にできるように

Emacsはあまり関係ない。 pecoを設定して簡単に接続できるようにした。 Ubuntuにpecoを導入する(for bash) (for fish) - Qiita

alias sshp='ssh $(grep Host ~/.ssh/config | grep -v HostName | cut -d" " -f2 | peco)'

DONE vterm-toggleで初回崩れるのを直す

READMEに書かれている設定を書いたら直った。前もコピペしてたはずなので、修正されたのだろう。

DONE hydra設定

すぐ自分で設定したキーバインドを忘れる対策で、hydraを設定した。

DONE leaf.elはどうやって動いているか   DontKnow

基本の動きは把握した。

キーワードを指定することで、インストール、フック、キーボードバインドまで、パッケージ周りの設定をうまくやってくれる。これはどうやっているのだろうか。

  • キーワードをマクロで変換して、処理を実行してるぽい。たとえばpackageキーワードの場合、最終的にpackage.elのpackage-installが呼ばれてインストールする、というように。leaf自体が処理を持っているわけではない
  • leaf関数がメイン。各キーワードをapplyして、対応する関数を実行している
  • leaf keywordで定義されているキーワードの処理の中身はhandler系
  • defmacroが大量に使われている。マクロを理解していないと読めない
(defmacro double (x) (list '* 2 x))
(double 3)

6

(defmacro double (x) (list '+ x x))
(defvar a1 2)
(double (incf a1))
;; (+ (incf a1) (incf a1))
;; (+ 3 4)

7

(defmacro double (x) (list '+ x x))
(defvar a1 2)
(macroexpand '(double (incf a1)))

(+ (incf a1) (incf a1))

バッククォートでマクロを表現。コンマで部分評価する。

(defmacro add(x y) (list '+ x y)) ; `を使わないと、若干面倒
(defmacro add(x y) `(+ ,x ,y)) ;; x, yを評価
(add 1 2)

3

リスト展開。

(defmacro if-do (test &body do-list)
  `(if ,test (progn ,@do-list)))
(if-do (> 5 3) (print "AAA") (print "BBB"))

DONE Mastering Emacs

Emacsの本。

メモ。

  • paragraph-start 巨大な正規表現…
  • リージョンの選択領域が出るのはtransient markモードを使用しているから。これがデフォルトになる前は記憶していた…

DONE effortをpolybarに表示する

  • [0:54/2:00] の時間を表示
    • 目標時間は org-clock-effort
  • effortが設定されてないときは設定してない表示をする
  • 開始してないときは何も表示しない

DONE 最後に開いていたworkspaceコマンドを開く関数を追加する

マルチディスプレイを使うときに不便なので、トグル形式で切り替えられるようにする。

DONE 設定をエクスポートして文芸的にする

自分だけでなく、他人も理解できるようにする。

CLOSE vtermでコピーしたときにキルリングに入らない

一度読み取り専用バッファにして、コピーしないといけない。

DONE 設定整理

  • 使ってないパッケージの整理
  • サジェストまわりの設定