Author Archives: AKIYAMA

2025-12-13

Edebugチートシート

デバッグの準備

  • 関数内で C-u C-M-x
  • M-x edebug-all-defs を実行した上で eval-buffer, eval-region, eval-defun
  • M-x edebug-all-forms を実行した上で eval-buffer, eval-region を使うと(範囲内にある)定義以外のフォームもデバッグする
  • M-x edebug-eval-top-level-form を使うとポイントがある場所(直後またはフォーム内であればそのトップレベル)のフォームをデバッグする

(参考: Instrumenting)

ストップポイント

(defun fun (a b)
  .(if .(< a. b.).
      .(insert .(format "%s %s %s" '(1 2 3) [1 2 3] .`(1 2 ,.(+ a. b.).).).).
    .(fun b. a.).).)

要するにリストの前後と変数参照の後。リテラル的な物は除く。

(参考:Using Edebug)

コマンド一覧

分類 キー コマンド 説明
ポイントの移動 w edebug-where 現在のストップポイントへポイントを移動する
  B edebug-next-breakpoint 次のブレークポイントがある位置へポイントを移動する(関数内で循環移動)
実行モード S edebug-stop どこかのストップポイントで止める
  SPC edebug-step-mode 次のストップポイントまで進んで止める
  n edebug-next-mode 式の後にあるストップポイントまで進んで止める(式の前にあるストップポイントでは止まらない)
  g edebug-go-mode 次のブレークポイントまで進んで止める
  G edebug-Go-nonstop-mode ブレークポイントを無視して進む
  t edebug-trace-mode ストップポイント毎に1秒停止しながら進む
  T edebug-Trace-fast-mode ストップポイント毎に表示を更新しながら進む
  c edebug-continue-mode ブレークポイント毎に1秒停止しながら進む
  C edebug-Continue-fast-mode ブレークポイント毎に表示を更新しながら進む
  C-x C-a RET edebug-set-initial-mode 最初の実行モードを設定する
指定位置まで実行 h edebug-goto-here ポイントがある場所まで進む
  f edebug-forward-sexp ポイントから 式一つ分(もしくはprefix指定数)先まで進む (現在のストップポイントからではない)
  o edebug-step-out ポイントを包む フォームの直後まで進む
  i edebug-step-in ポイントの直後 の式が関数またはマクロ呼び出しの場合、その呼び出し先に入る(引数の評価は飛ばす)
デバッグの準備 I edebug-instrument-callee ポイントの直後の式が関数またはマクロ呼び出しの場合、その呼び出し先をデバッグ可能にする
脱出 q top-level quitする。途中ハンドラでデバッグに入る
  Q edebug-top-level-nonstop quitする。ハンドラがあってもデバッグに入らない
  a / C-] abort-recursive-edit 再帰編集を1段階戻る
ブレークポイント b edebug-set-breakpoint ポイントの場所にブレークポイントを設定する(prefix付きで一時的)
  C-c C-t (edebug-set-breakpoint t) ポイントの場所にブレークポイントを一時的に設定する
  u edebug-unset-breakpoint ポイントの場所のブレークポイントを解除する
  U edebug-unset-breakpoints 現在のフォーム内の全てのブレークポイントを解除する
  x edebug-set-conditional-breakpoint 条件付きブレークポイントを設定する
  D edebug-toggle-disable-breakpoint ブレークポイントの無効化状態をトグルする
ブレーク条件 X edebug-set-global-break-condition 場所に関係なく成立したら止まる条件を設定する
情報の表示 r edebug-previous-result 直前の式の評価結果を表示する
  d edebug-pop-to-backtrace バックトレースを表示する
  \= edebug-temp-display-freq-count 通過回数を一時的に表示する
  \= edebug-display-freq-count 通過回数を表示(挿入)する
  ? edebug-help ヘルプを表示する
  p edebug-bounce-point 一時的(1秒間)に実行中のカレントバッファのポイント位置を表示する
評価 e edebug-eval-expression ミニバッファから式を入力して評価する
  C-x C-e edebug-eval-last-sexp ポイントの直前の式を評価する
  E edebug-visit-eval-list 評価リストバッファを開く
画面構成 v edebug-view-outside edebugではないウィンドウ構成に切り替える(C-x X wで戻る)
  P edebug-view-outside  
  W edebug-toggle-save-windows  

Tips:

  • iを使うと引数の評価を飛ばしてしまう。それが嫌ならばIを押してからステップ実行する。一度関数をデバッグ対象にしてしまえば次からは何もしなくても中に入る。中に入りたくない場合はf、o、hを使う。nではダメ。hがあれば他要らなくね?

練習

前進:

;; (scratchバッファ上を想定)
(defun my-callee-function (a b)
  (message "a=%s b=%s" a b))

;; ↓ここで C-u C-M-x または M-x edebug-eval-top-level-form
(let (a b c d)
  ;; ■ここにいた場合
  (my-callee-function ;; ←i(関数の中)
   ;; ↓SPC (最初のストップポイント)
   (cons a ;; ←n(最初の後方ストップポイント)
         b)
   (cons c d) ) ;; ←f(my-callee-functionを呼び出す式の直後)
  (list a b c d) ) ;; ←o(let式の直後)

無限ループ:

(defun my-edebug-practice-infinite-loop ()
  (let ((count 0)
        (end nil)) ;; この変数を書き替えられれば無限ループを脱出できるのだが……
    (unwind-protect
        ;; 無限ループはt、T、c、C、g、Gの効果を試すのにうってつけ。
        ;; Sで停止。b、C-u b、u、U、x、Dでブレークポイント操作。
        (while (not end)
          ;; 1行進む。進めなかったらバッファの先頭に移動。
          (unless (zerop (forward-line))
            (goto-char (point-min)))
          ;; pを押すと今ポイントがどこに進んだかが分かる(1秒間)。
          (cl-incf count))
      ;; ループ中qを押すとここに来る。Qでは来ない。
      (message "count=%s" count)))) ;;←ここでC-u C-M-x

;; (my-edebug-practice-infinite-loop) ;;←ここでC-x C-e
2025-12-10 ,

org-captureをタッチで使いやすくする

Android版のEmacsを使ってみて、org-captureが使いづらいというのはすぐに気がつきました。私はorg-captureコマンドを C-c r に割り当てていますが、Ctrlを押さなければならない時点でタッチパネルからでは使いづらいです。その後もテンプレートを選ぶのにキー入力が必要ですし、最後の C-c C-cC-c C-k もタッチでは打ちづらいです。org-captureは「キーボードで素早くノートを取る」ことに最適化されていますが、これがタッチでは逆に障害となっています。

なので、まずはテンプレートの選択はGUIメニューで行うようにします。

テンプレート選択メニュー
テンプレート選択メニュー

そして入力が終わった後の C-c C-cC-c C-wC-c C-k と書いてある部分を押せるようにします。

ヘッダーラインにキャプチャ終了アクションが表示されている
ヘッダーラインにキャプチャ終了アクションが表示されている

作成したコードは次の通りです。

;;; org-captureをタッチで使いやすくする。

;; マウス/タッチ使用時はテンプレート選択をx-popup-menuにする。

(defun my-org-mks (table _title &optional prompt specials)
  (unless prompt (setq prompt "Select: "))
  (x-popup-menu t
                (list
                 prompt
                 (cons ""
                       (nconc
                        (cl-loop for item in table
                                 when (cddr item) ;; Prefixの説明を除く
                                 collect (cons (cadr item) item))
                        (cl-loop for item in specials
                                 collect (cons (cadr item) (car item))))))))

(defun my-org-mks-around (old-fun &rest args)
  (if (use-dialog-box-p)
      (apply #'my-org-mks args)
    (apply old-fun args)))

(advice-add 'org-mks :around 'my-org-mks-around)

;; header-lineのキーヘルプをボタンにする。

(defun my-org-capture-make-clickable (str keymap)
  "STRの中にあるfaceがhelp-key-bindingである部分をボタンにして押せるようにします。"
  (setq str (copy-sequence str))
  (let ((pos 0))
    (while pos
      (let ((face (get-text-property pos 'font-lock-face str))
            (next (next-property-change pos str)))
        (when (eq face 'help-key-binding)
          (let* ((end (or next (length str)))
                 (key-str (substring str pos end))
                 ;; ↓ここはKEY-STRによっては良くないかもしれない。
                 (command (lookup-key keymap (kbd key-str))))
            (when command
              (let ((km (make-sparse-keymap)))
                (define-key km [down-mouse-1] command)
                (define-key km [header-line down-mouse-1] command)
                (put-text-property pos end 'keymap km str)
                (put-text-property pos end 'pointer 'hand str)))))
        (setq pos next))))
  str)
;; EXAMPLE: (my-org-capture-make-clickable (substitute-command-keys "\\<org-capture-mode-map>Capture buffer.  Finish `\\[org-capture-finalize]', refile `\\[org-capture-refile]', abort `\\[org-capture-kill]'.") org-capture-mode-map)

(defun my-org-capture-init ()
  (defvar org-capture-mode-map)
  (setq header-line-format
        (my-org-capture-make-clickable
         ;; ここはheader-line-formatを指定しても良いのですが、そのままだと
         ;; 長すぎてC-c C-kが画面の右からはみ出して押せないので短くします。
         (substitute-command-keys "\\<org-capture-mode-map> Finish:\\[org-capture-finalize] Refile:\\[org-capture-refile] Abort:\\[org-capture-kill]")
         org-capture-mode-map)))

(add-hook 'org-capture-mode-hook 'my-org-capture-init)

テンプレート選択のGUIメニュー化はorg-mks関数にadviceを仕込むことで実現しました。そこではy-or-n-p等がやっているように、use-dialog-box-pを使ってマウス・タッチ操作からのコマンド起動かを判断して、必要ならGUIでメニューを表示します。 my-org-mksx-popup-menuを使ってテンプレート選択メニューを表示するorg-mksの代替物です。私はあまり複雑なテンプレートの指定をしていないので、これでorg-mksの全てのユースケースをカバーしているかは分かりません。org-mksはざっと調べた限りorg-captureorg-insert-structure-templateでしか使われていないみたいです。

終了アクションをタッチでできるようにするために header-line 上の C-c C-c などと書かれている部分をボタン化することにしました。 window-tool-bar を使って上に○×ボタンを表示するのも面白いかなと思ったのですが、面倒なのでこの方法にしました。 my-org-capture-make-clickable 関数は、文字列(header-line-format)中の特殊なface(help-key-binding)が指定されている部分に、keymapテキストプロパティを追加してdown-mouse-1イベントに反応するようにします。kbdを使っている部分がキー設定によっては正しく動かない可能性はありますが(キー割り当てが M-x org-capture-finalize のように表示されている場合など)、デフォルトの状態なら問題は無いでしょう。

org-captureコマンドはすでにメニューバーにも追加していましたが、ツールバーにも追加しておきます。

メニューバーに「+」ボタンを追加したところ
メニューバーに「+」ボタンを追加したところ

そのあたりのコードは以前の記事に追記しておきました。

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

これでスマートフォンからの入力がさらに便利になりました。

org-captureは前々から少し疑問を感じていた仕組みではありました。私は特定のファイルに新規エントリーを追加するという単純な目的にしか使っていません。それならもっと単純なやり方がある気がします。テンプレートを選んだら、即特定のファイルを開いて新規エントリーを挿入し、ポイントを適切な位置に移動すれば十分です。キャンセルしたければundoすれば良いのですから。なので今回の改良ではノートのキャプチャを行う仕組み自体を書き直そうかとも思いました。

しかしorg-captureにはノートを取った後に何事もなかったかのように元の状態に戻るという特徴があります。思いついた時にノートを取って、またすぐに元の作業に戻る。それがorg-captureの良いところなのかもしれないなと思い、今回はorg-captureを活かす改良で留めてみました。また不満を感じたら全体を書き直すかもしれません。

2025-12-09

Emacsのコンテキストメニューに「Close」を追加する

AndroidのEmacsを使っていると、なぜかふとバッファを長押ししてコンテキストメニューを表示し、そこから「Close」を選びたくなるときがあるんです。もちろんそこには「Close」なんて無いので「あっ……」と思うわけですが。

バッファを閉じたいならツールバーに×ボタンがありますし、メニューバーも出しているので「File」→「Close」を選ぶという手もあります。でもなぜかコンテキストメニューの中から「Close」を選びたくなるときがあるのです。

それがなぜかはよく分からないのですが、とりあえず追加してみます。

私はcontext-menu-mode(Emacs 28で追加)を有効にしていて、以前書いたようにタッチの長押しでそれが表示されるようにしているのでコンテキストメニュー自体はすでに表示できます。

長押しでコンテキストメニューを開く(Emacs 30) | Misohena Blog

なので後は単純にコンテキストメニューに項目を追加すればいいだけです。

コンテキストメニューに項目を追加するにはcontext-menu-functions変数に関数を追加すれば良いのでした。

(defun my-close-buffer-and-window ()
  (interactive)
  (let ((window (selected-window)))
    (when (kill-buffer)
      ;; すぐに削除するとタッチ処理部分のwith-selected-windowが削除
      ;; したウィンドウを選択しようとしてエラーになる。
      ;; なのでタイマーで遅延する。
      (run-with-timer
       0 nil
       (lambda ()
         (when (and (window-live-p window) (window-deletable-p window))
           (delete-window window)))))))

(defun my-context-menu-function (menu _click)
  (define-key menu [my-close-buffer-and-window]
              '(menu-item "Close" my-close-buffer-and-window))
  menu)

(add-hook 'context-menu-functions 'my-context-menu-function 0)

望んでいる動作というのはおそらくただバッファを閉じるだけでなくウィンドウも閉じたいのだと思います。

ウィンドウを閉じるだけなら以前モードラインをドラッグしてウィンドウを消せるようにしましたが、それだとバッファが残ってしまいますからね。

モードラインをドラッグしてウィンドウを消す | Misohena Blog

それに今はサイドウィンドウで仮想キーボードを表示しているので、うまく狙ったウィンドウが消せないことがあるのです。

最初はプレフィックス付きでquit-windowを呼び出そうと思いました。私はデスクトップ上では表示専用のバッファをよくC-u qで閉じることがあります。ポップアップしたウィンドウをバッファも含めて消してくれるので。

でもこの「Close」にquit-windowを割り当ててみてもどうもしっくりこないのです。quit-windowは前の状態を復元しようとしますが必ずウィンドウを閉じてくれませんからね。

なので上のコードでは素直にバッファとウィンドウを同時に削除することにしました。

ただし、長押しで無理矢理コンテキストメニューを出した弊害で、そこからウィンドウを削除するとどこからともなくwindowがらみのエラーが発生します。調べてみると、touch-screen.elの中でwith-selected-windowを使用しているところがあって、それが私が削除したウィンドウを選択しようとしてエラーが出るみたいなのです。なのでkill-buffer-and-windowは使えません。仕方が無いのでタイマーで後から削除するようにしました。

コンテキストメニューに「Close」を追加したところ
図1: コンテキストメニューに「Close」を追加したところ