Monthly Archives: 11月 2025

2025-11-29

よく分かるEmacs Lisp入力関数関連図

Emacsのキー入力関数ってなんか似たような名前が多くてすぐに理解できませんよね。調べてだいたい分かったつもりになっていても、しばらくしたら忘れている自信があります。なので図にしておきました。自分のために。

read_charread_key_sequenceread_key_sequence_vsread-key-sequenceread-key-sequence-vectorread_filtered_eventread-eventread-charread-char-exclusivecommand_looprecursive_edit_1read_minibufread-from-minibuffercompleting-read-defaultcompleting-readread-multiple-choiceread-keyread-char-choiceread-quoted-charx-popup-dialoglread.ckeyboard.csubr.elminibuf.c/minibuffer.elrmc.elsimple.elcompleting-read-functionmenu.c

Emacs Lisp リファレンスマニュアルで言うと「Reading Input (GNU Emacs Lisp Reference Manual)」に書いてある関数のことです。

Reading Input (GNU Emacs Lisp Reference Manual)

大別すると read_filtered_event を介して直接的に一つのイベントを読み取る関数群と、 read_key_sequence を介してキーマップによって決まる一続きのキー列を読み取る関数群とに分けられるようです。普段バッファの中で使っているのは後者ですね。コマンドループを通じて read_key_sequence を呼び出しています。それだけに read_key_sequence の方が複雑で難しいです。

read_filtered_event 系には三つの関数がありますが、文字入力イベントのみに限定するバージョン(read-charread-char-exclusive)と全てのイベントを読み取るバージョン(read-event)に分かれます。 read-charread-char-exclusive の違いは、文字以外のイベントが来たときにエラーにするか、排除して続行するかの違いです。

  • read_filtered_event
    • 文字のみ
      • 文字以外でエラー : read-char
      • 文字以外は無視 : read-char-exclusive
    • 全て : read-event

read-charread-char-exclusive は、 read-event に比べると次の処理が加わっています。

  • text-conversionの無効化と復元 (Androidの場合IMEによって直接バッファを書き替える仕組みが存在します)
  • 非文字イベント発生時のエラー(read-char)またはリトライ(read-char-exclusive)
  • switch-frameイベントの遅延 (非文字イベントだが特別扱い)
  • イベントタイプシンボルの文字コード化 (例えばtabを9にします)
  • 修飾キービットの正規化 (主にshiftとcontrolの処理です。例えば25ビット目(?\S-\0)が立っていてベース文字がアルファベット小文字なら大文字にしてビットを消します。control(26ビット目)が立っている文字を制御文字へ変換したりもします)

当然ですが read_filtered_event 系関数にはキーマップは作用しません。

read_filtered_eventread_key_sequence に共通な処理は read_char の中に色々入っています。 unread-command-events の処理とかキーマクロの再現に関するものとか。

2025-11-13

タッチスクリーンで慣性スクロールする

Android版のEmacsを使っているとすぐに慣性スクロールが無いことに気がつくでしょう。つまり画面をスワイプしてスクロールするときに、弾くように指を離したらそのまましばらく勢いでスクロールが継続して欲しいわけです。これが無いと指を離すたびに「ピタッ」とスクロールが止まるので、何画面分もスクロールしなければならないときにとても疲れます。

これは慣性スクロールを実装しなきゃダメかな……と憂鬱になりながら少し調べてみたところ、pixel-scroll.elpixel-scroll-*-momentum という名前の変数や関数が存在することに気がつきました。喜び勇んですぐに pixel-scroll-precision-use-momentumt にして、 pixel-scroll-precision-mode を有効にしてみましたが……あれ、何も変わりません。うーん、どうなっているんだろう。

Googleで検索したら次のredditの投稿が見つかりました。

How to config to enable pixel scroll precision momentum-based scrolling on Android? : r/emacs

おおー、素晴らしい!

というわけで次の設定をしたらちゃんと慣性が働くようになりました。

;; タッチによるスクロールをピクセル単位にする(必要?)
(setq touch-screen-precision-scroll t)

;; 慣性スクロールを有効にする
;; https://www.reddit.com/r/emacs/comments/1mtouxh/how_to_config_to_enable_pixel_scroll_precision/
(defun touch-scroll-momentum (_dx dy)
  (pixel-scroll-accumulate-velocity (- dy)))
(advice-add 'touch-screen-handle-scroll :before 'touch-scroll-momentum)
;; (2025-11-14追記:長押しスクロールが効かなくなってしまったので修正)
;;(keymap-global-set "<touchscreen-end>" 'pixel-scroll-start-momentum)
(defun my-touch-screen-handle-touch:scroll-end (event &rest _)
  (when (eq (car event) 'touchscreen-end)
    (pixel-scroll-start-momentum event)))
(advice-add 'touch-screen-handle-touch
            :before ;;afterではダメ
            'my-touch-screen-handle-touch:scroll-end)

(setq pixel-scroll-precision-use-momentum t)
(pixel-scroll-precision-mode)

ただ、私の環境だとorg-mode文書のスクロールはかなりカクつきます。org-modernやphscrollで色々凝ったことをしているせいかもしれませんが。

2025-11-12 ,

Emacsのツールバーをカスタマイズする

Android版のEmacsのためにツールバーをカスタマイズしました。

修正前の状態は次図の通り。

ツールバー修正前
図1: ツールバー修正前

今回はあくまで一番上の標準的なツールバー(tool-bar-mode)のお話しです。その下にあるキー入力用のバーは無視してください(これもそのうち何とかしなきゃいけませんが(2025-12-09追記:何とかしました))。

上図はデフォルトの状態なのですが、どのボタンが何をするか分かるでしょうか。私は正直よく分かっていませんでした。一番左からファイル新規作成、ファイルを開く、ディレクトリを開く、バッファを閉じる、セーブ、アンドゥ、カット、コピー、ペースト、インクリメンタル検索となっています。

一番問題なのは最初の「ファイル新規作成、ファイルを開く、ディレクトリを開く」の部分です。これって実際に何をするか分かりますか? Emacsでは通常全てfind-fileで行うものだと思います。実際に割り当てられているコマンドは左から、find-file、menu-find-file-existing、diredとなっています。find-fileだけでいいじゃん! (だいたい新規作成なら既存のファイルを選んだら「上書きしますか?」と聞かなければいけませんよ。しかし当然ですがfind-fileはそんなことはしません)(2025-12-10追記: このあたりはOSごとのファイル・ディレクトリ選択ダイアログによって状況が異なるかもしれません。Androidではダイアログが実装されておらず単にミニバッファからファイルやディレクトリを選択する流れになります。MS-Windowsではfind-fileで出現するダイアログではディレクトリが選択出来ません。たしかMacだと既存のファイルを選択するときの挙動も異なっていたような)

それとバッファを閉じるための×が押しにくいんですよね。一番左に配置しましょう。

カット、コピーは長押しのコンテキストメニューでやっているのでここには要らないかなーと思います。ペーストくらいは残しておいても良いかな? この辺りはまた後で変えるかも。

他にもいくつかよく使う操作をツールバーのボタンにしたいと思います。

それと全体的にアイコンが小さすぎません? いや、これはデフォルトフォントサイズの設定に合わせて変わってしまっています。私はデフォルトフォントを少し小さめにしてしまったので、ツールバーのボタンも一緒に小さくなって押しづらくなってしまいました。そしてツールバーのボタンのサイズだけを調整する方法が見当たりません。tool-barフェイスの:heightを変更してみてもこれはテキストのみにしか効果が無いらしくアイコンのサイズは変わりませんでした。うーん困った。

で、色々やってみた結果がこちら。

ツールバー修正後
図2: ツールバー修正後

ボタンの数が減ってシンプルで分かりやすくなりました。ボタンのサイズも大きくなって押しやすくなりました。

一番右に追加したボタンはbeginning-of-bufferとend-of-bufferです。長いファイルの途中にいるときにタッチによるスクロールだけで大きく移動するのは大変です。かといってM-<やM->を押すのもなかなか面倒なので追加してみました。

作成したコードは次のようになりました。

;; 項目のカスタマイズ(既存項目の削除、並び順の変更)
;; tool-bar-mapを直接変更します。
;; tool-bar-setupが呼び出された後じゃないと正しく動作しません。

(defun my-tool-bar-map--customize-items (map)
  "tool-bar-mapの項目をカスタマイズします。"
  ;; new-file、open-file、diredは一つに統合する
  ;; (OSによってはファイル選択ダイアログで狙ったファイルやディレクトリ
  ;; が選択出来ない場合もあるが)
  (define-key map [find-file]
              (list 'menu-item "Open file" #'find-file
                    :image (tool-bar--image-expression "open")))
  ;; 不要なアイコンを削除
  ;;   - new-file、open-file、diredは新しく定義し直す
  ;;   - cut、copy、pasteはメニューのEditの押しやすい位置にあるから不要
  (dolist (key '(new-file open-file dired cut copy paste))
    ;; 削除は (define-key map (vector key) nil t) でもよいが、remove引
    ;; 数はEmacs29が必要
    (setf (alist-get key (cdr map) nil t) nil))
  ;; separator-2と3を削除
  (setf (alist-get 'separator-2 (cdr map) nil t) nil)
  (setf (alist-get 'separator-3 (cdr map) nil t) nil)
  ;; kill-bufferをツールバーの先頭に移動
  ;; kill-bufferの右にseparator
  (let ((item (assq 'kill-buffer (cdr map))))
    (setf (alist-get 'kill-buffer (cdr map) nil t) nil)
    (setf (alist-get 'separator-0 (cdr map) nil t) nil)
    (setcdr map (cons item
                      (cons
                       (list 'separator-0 "--")
                       (cdr map))))))

;; 項目の追加

(defconst my-tool-bar-map--additional-items
  '(;; 2025-12-10追記: org-captureボタンを追加
    (org-capture "Org Capture" org-capture "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" width=\"24\" height=\"24\" viewBox=\"0 0 240 240\"><path d=\"M102 40H138V102H200V138H138V200H102V138H40V102H102Z\" fill=\"#444\" stroke=\"none\" /></svg>")
    (top "Top" beginning-of-buffer "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" version=\"1.1\"><path stroke=\"none\" d=\"M4 2H20V4H12L18 10H14V22H10V10H6L12 4H4Z\" fill=\"#444\" /></svg>")
    (bottom "Bottom" end-of-buffer "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" version=\"1.1\"><path stroke=\"none\" d=\"M4 22H20V20H12L18 14H14V2H10V14H6L12 20H4Z\" fill=\"#444\" /></svg>")))

(defun my-tool-bar--add-item (map id text cmd image-data)
  ;; 2025-12-10修正: define-key-afterを使うように修正
  (define-key-after map (vector id)
    (list 'menu-item text cmd
          :image
          (list 'quote
                (create-image image-data 'svg t :scale 'default)))))

(defun my-tool-bar-map--customize-additional (map)
  (dolist (spec my-tool-bar-map--additional-items)
    (apply #'my-tool-bar--add-item map spec)))

;; アイコンサイズの調整
;; image descriptorに:scaleを無理矢理追加して調整します。

(defconst my-tool-bar--icon-scale 2.8) ;; ★要調整

(defun my-tool-bar--adjust-image-scale (image)
  (when image
    (setf (plist-get (cdr image) :scale) my-tool-bar--icon-scale))
  image)

(defun my-tool-bar--adjust-map-image-scale (map)
  (dolist (item (cdr map))
    (when-let* ((image (plist-get (cddddr item) :image)))
      (setf (plist-get (cddddr item) :image)
            (list 'my-tool-bar--adjust-image-scale image)))))

(defun my-tool-bar-map--customize-icon-size (map)
  (my-tool-bar--adjust-map-image-scale map))

;; 初期化

(defun my-tool-bar-map--customize ()
  (let ((map (default-value 'tool-bar-map)))
    (my-tool-bar-map--customize-items map)
    (my-tool-bar-map--customize-additional map)
    (my-tool-bar-map--customize-icon-size map)))

(if (and (boundp 'tool-bar-map) (cdr-safe (default-value 'tool-bar-map)))
    ;; すでにtool-bar-mapが初期化されているときは更新
    (progn
      (my-tool-bar-map--customize)
      (tool-bar--flush-cache)
      (force-mode-line-update))
  ;; まだの時は初期化されるまで待つ
  (advice-add 'tool-bar-setup :after #'my-tool-bar-map--customize))

Emacsのツールバーはカスタマイズ性が悪いですね。どうせみんな使ってないんでしょう? いや、私もPC上では使っていませんが。まさかこの期に及んでツールバーをカスタマイズすることになるとは思いませんでした。

(2025-11-13:追記)org-mode時の折りたたみ操作もやりづらいので、org-mode用の項目も追加しました。

(defvar my-org-tool-bar-map
  (let ((map (make-sparse-keymap)))
    (define-key-after map [separator-org-1] menu-bar-separator)
    (define-key-after map [fold]
      `(menu-item
        "Fold" my-org-fold-current-subtree
        :help "Fold current subtree"
        :image (create-image "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" version=\"1.1\"><path d=\"M12 14 6 20H18\" fill=\"#444\" stroke=\"none\" /><path d=\"M12 10 18 4H6\" fill=\"#444\" stroke=\"none\" /></svg>" 'svg t :scale 'default)))
    map))

(defun my-org-tool-bar-map ()
  ;; make-composed-keymapは機能しない。
  ;; :imageの部分が展開されないから。(See: `tool-bar-make-keymap-1')
  ;; (make-composed-keymap (default-value 'tool-bar-map)
  ;;                       my-org-tool-bar-map)
  (let ((map (copy-keymap (default-value 'tool-bar-map))))
    (set-keymap-parent map my-org-tool-bar-map)
    map))

(defun my-org-fold-current-subtree ()
  "現在のサブツリーを折りたたみます。
ポイントが見出しにあり、その見出しがすでに折りたたまれている場合は、それ
を含む一つ上のサブツリーを折りたたみます。"
  (interactive)
  (if (org-at-heading-p)
      (if (org-fold-core-folded-p (pos-eol))
          ;; 折りたたまれている見出し上にいる場合
          (progn
            (outline-up-heading 1)
            (outline-hide-subtree))
        ;; 折りたたまれていない見出し上にいる場合
        (outline-hide-subtree)
        (unless (org-fold-core-folded-p (pos-eol))
          ;; 折りたためなかった場合、一つ上を試す
          ;; (空のエントリーの場合は折りたためない)
          (outline-up-heading 1)
          (outline-hide-subtree)))
    ;; 見出し以外にいる場合
    (outline-previous-heading)
    (outline-hide-subtree)))

(defun my-org-tool-bar-setup ()
  (setq-local tool-bar-map (my-org-tool-bar-map)))

(add-hook 'org-mode-hook 'my-org-tool-bar-setup)

org-modeの折りたたみ操作はキー操作においても常々不満があります。見出し上なら単に TAB で折りたためますが、エントリーの中では C-c C-p TAB としなければなりませんし、見出しの上でそれを含むサブツリーを折りたたむには C-c C-u TAB としなければならなかったりします。上に書いた my-org-fold-current-subtree コマンドは、どこであっても概ね狙った通りに折りたたむ(閉じる)ことが出来ます。