Monthly Archives: 8月 2021

2021-08-05 ,

org-modeをData URI Schemeに対応させる

とりあえず対応させてみました。

↓はData URIなんですけど表示されてますか?

赤い丸(SVG)
図1: 赤い丸(SVG)
富士山!(JPEG)
図2: 富士山!(JPEG)

↑の書き方(org-modeのソース)は次の通りです。

#+CAPTION: 赤い丸(SVG)
[​[data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTAwIiB3aWR0aD0iMTAwIj4NCiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgcj0iNDAiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS13aWR0aD0iMyIgZmlsbD0icmVkIiAvPg0KPC9zdmc+DQo=]]

#+CAPTION: 富士山!(JPEG)
[​[data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAAeAAD/7gAOQWRvYmUAZMAAAAAB/9sAhAAQCwsLDAsQDAwQFw8NDxcbFBAQFBsfFxcXFxcfHhcaGhoaFx4eIyUnJSMeLy8zMy8vQEBAQEBAQEBAQEBAQEBAAREPDxETERUSEhUUERQRFBoUFhYUGiYaGhwaGiYwIx4eHh4jMCsuJycnLis1NTAwNTVAQD9AQEBAQEBAQEBAQED/wAARCAAyAGQDASIAAhEBAxEB/8QAgAAAAgMBAAAAAAAAAAAAAAAAAAMBAgQFAQADAQEBAAAAAAAAAAAAAAAAAQIDBAUQAAEDAgQDBQYFBQAAAAAAAAEAAgMRBCExQRJRYQVxgcEiE/CRoTJCI9FighQGsVJy0hURAAICAQUBAQAAAAAAAAAAAAABEQIhMUFxEgNhIv/aAAwDAQACEQMRAD8A3UUUTdqjavTk4RdEbUyiCESAvaooupadIkkHq3NYos9uTz/qtEsdrb27nmNvosxNBUuP0tbriVm/aqcL9cGi821LwcOmuiKLbYRsuZWzOFHuJLmfTUHIjgFlvz+1vZWBpNuTVnFoOnv0VL0Tt10ZLpCkXRFFdhZI3cw7hy8VO1XJIuiKJm1RtSkYuiEzahEgP2oppqcgujH0xxP3JABwYKn4pzH9Ptdwic3e2geR55MchgsLeyWn6LXm3rgxQdMuZaFwETDjV3zU/wAfxXStrG3tjuY3fIM5X+A0WC6/kNnECIj6zxlo2vMrmTfyK4kG07RGHCtBSrRiQeTslm36X+I0SrX6zvz3QeKM8zjXZ/aKYbnDXksPUqMsXNc5xeW9ri0GrqcOSLa7bJbC8lpFGRvcTjQDCpp8Fnml/emR0cgMM3kYW4kNaKf1xRWsPgbeOSttIYLtkjDSCaIFjnUFZCK4/mcnXVsJ9gFXCRo81RUO4HvzWQPt/wB62IncyGNkcUTfMXStzw4N1KnqN3Pbwhsbmm4jf6jY8Hbg75wOzNVuo1J2yZ32jo/uxbo5AS1zW4iozpXPsVhO5jWm4ZRrhhKzFv6hm1DOrCS29a5YXMeNskrRSrqV2kYeamoTY7m1lkMDHje5m9ocKB7TjjzotFd7ol1WxZu17dzCHNOoxCNqqLSMOJjJilpVpYaB1eNcCUmPqMBO18jX/mb5T3tPgq7Jk9WP2oR69tt3eo2iESEHMuuuTTuqS5wGFK+mwgalrfMa8ysU3ULqYU3emzSOIbG/DE96zjHPAozyxUqqWxTbJLzqUBzndgUGmqN3AYJiN111B/8Az4bGJ3kA3SvIoXGtdvYCskE8kIcYnFsjsNwz20xAPNUNCa1FSpa0AgjHgUsIeWaIb2W39F8IAfG1zXOI3Fwca0x0WaaaWaUzSuL5XUJec/grUaRiFDgKcxqhQGSwvJjEYH/ciOJa7Q8WnQpkVw5txFdOJl9LbRoIaQW5B3LsWanvUhrgQ4dyMCya7vqdzckZQxjFsTK0B1OKyGutVYgUrka5ckbTSoIPfihQMrsbtrTmhFDWuqEAW+p2eff3oOXt8EIQIjUeKDl3IQgZQ5J306+KEJMELf8AKc/1I4exQhADOHjn3KW/j2IQkUU+kZ69in2xQhMRX3IQhAj/2Q==]]
Emacs上での見た目
図3: Emacs上での見た目

Data URIを使うとorg-mode文書のサイズが肥大化するのと引き換えに外部ファイルを管理する手間が省けたりします(こうやってエクスポートしてWeb上に上げた場合はHTTPリクエストが減るとか、一方で画像キャッシュが効かないとか色々あると思います)。

Web上を検索したらorg-modeとData URIに関する話題はチラホラ見かけたのですがそのものズバリというものは見つからなかったんですよね。同じ事を考える人は沢山いそうなのですが……。

ソースは次の場所に置きました。

misohena/org-inline-image-fix: A collection of fixes related to the image display feature in org-mode

が、かなりヤバイクソコードなのでご了承ください。

インライン画像表示まわりは例によって org-display-inline-images 関数にadviceをかけて実現しているのですが、この関数は元々結構長くて改造が容易ではありません。以前は丸丸上書きするような修正の仕方もしたのですが、org-mode側に修正が入ると直さなければならなくなるので面倒です(ちょっと前にリモート画像対応が少し入りました)。なので今回は極力元の関数を活かす方向で修正したのですがそれが悪夢の始まりでした。

長ったらしい関数の中の挙動を修正するにはそこから呼び出す関数を書き替えるしか手が無く、 cl-letf で沢山の関数を上書きして無理矢理実現しています。まるで針の穴を通すようなプログラミングにゾクゾクしてしまいました。まるでパズルゲームです。何度もこれはもうダメか、書き直すしか無いかと諦めかけました。 org-element-property が実は defsubst でバイトコンパイルがかかると呼び出されないということに気がついたときにはどうしようかと思いました。追い詰められて plist-get とか format とか基本的な関数を書き替えているのでどこでおかしくなるか分かりません。 org-display-inline-images を書き替える他のプログラムを使ったときの動作は保証いたしかねます。正直全部上書きした方がまだマシだったかもしれませんね。どうせorg-mode側で修正がかかったら全部おじゃんでしょう。

:after adviceをかけて、本家の処理が終わった後にもう一度バッファを走査するのが一番安全かもしれません。大きな文書では効率が落ちるかもしれないので今回は回避しましたが(画像を扱っている時点で今更?)。

YouTube動画へのリンクを画像に置き換えるコード(TobiasZawada/org-yt: Youtube links in org-mode)を見つけたのですが、それは:afterアドバイスで再走査していました。皆さん苦労しているようです。

私も今 org-display-inline-images には三つもアドバイスがかかっているので苦労しています(上のリポジトリにはこれまでのものをまとめてあります: URLリンク対応, 画像リンク即時表示, サイズ制限)。

元々のコードがもう少し改造しやすくなっていると良いのですが……。

あ、あとエクスポートはHTMLだけ対応ですのであしからず。こちらも面倒でした。

2021-08-04

ivy-switch-bufferでブックマーク内のファイルをブックマーク名で検索できるようにする

Ivyでは ivy-use-virtual-bufferst にすると ivy-switch-buffer (C-x b) で(recentfや)ブックマーク内のファイルが選べるようになりますが、候補として登録されるのはあくまでファイル名(ivy-virtual-abbreviate を設定するとディレクトリパスも含めることは出来る)だけでブックマーク名は登録されません。なのでいくら分かりやすいブックマーク名を付けていても ivy-switch-buffer でそれを元にファイルを検索することは出来ません。なのでそれを出来るようにしてみました。

(require 'ivy)

(defun my-ivy-bookmark-name-filename-list ()
  "Return bookmark (name . filename) list as virtual buffer format."
  (delq nil
        (mapcar (lambda (record)
                  (when record
                    (let ((name (bookmark-name-from-full-record record))
                          (filename (bookmark-get-filename record)))
                      (when (and name filename)
                        (cons
                         (propertize
                          (format "*bookmark:%s" name)
                          'face 'ivy-virtual)
                         filename)))))
                bookmark-alist)))

(defun my-ivy-bookmark-append-advice (orig-fun)
  (let ((result-orig (funcall orig-fun))
        (my-virtual-list (my-ivy-bookmark-name-filename-list)))
    (setq ivy--virtual-buffers (append ivy--virtual-buffers my-virtual-list))
    (append result-orig (mapcar #'car my-virtual-list))))

(advice-add 'ivy--virtual-buffers :around 'my-ivy-bookmark-append-advice)
;;(advice-remove 'ivy--virtual-buffers 'my-ivy-bookmark-append-advice)

ivy-switch-buffer が扱うのはあくまでバッファ名です。現実にあるバッファの名前を選んでそこへ切り替えます。なのでrecentfやブックマークのファイル名はバッファ名ではないので「仮想バッファ」の名前として扱うことで無理矢理処理しています。仮想バッファ名リストを生成する関数が ivy--virtual-buffers です。その関数は、 ivy--virtual-buffers という変数(関数と同じ名前でややこしいですが)に仮想バッファ名とファイル名の対応表を構築した上で仮想バッファ名のリストを返します。

なので ivy--virtual-buffers 関数にadviceをかけて仮想バッファを付け足すのが上のコードです。

ivy-switch-bufferで選べる候補が気に入らないという方はこのようなやり方で候補を追加してみてはいかがでしょうか。

ちなみに、 *scratch* が無くても常に *scratch* を選べるようにするには my-ivy-bookmark-append-advice が返すリストに一つ付け加えるだけです。

(defun my-ivy-bookmark-append-advice (orig-fun)
  (let ((result-orig (funcall orig-fun))
        (my-virtual-list (my-ivy-bookmark-name-filename-list)))
    (setq ivy--virtual-buffers (append ivy--virtual-buffers my-virtual-list))
    (append result-orig (mapcar #'car my-virtual-list) '("*scratch*"))))

こうしておくと、もし知らないうちに *scratch* を閉じてしまっていても *scratch* というバッファ名を選べて新しくバッファを作成することが出来ます。

2021-08-04

WindowsでflycheckがNULというファイルを作ってしまう問題

以前flycheckを試したときにNULというファイルが出来て消すのが大変だったのでflymakeでいいやーと思ったことがあったのですが、最近また調べたところ対処法を書いているサイトを見つけました。

Emacs Flycheck

下記の記述だと、Windowsで、gcc によっては、同じディレクトリに "NUL" というファイルが生成されることがある。

:command ("gcc" "-c" "-I../../inc" "-I." "-O1" "-Wall" source "-o" null-device)

その場合、下記のように temporary-file-name を指定すると回避できる

:command ("gcc" "-c" "-I../../inc" "-I." "-O1" "-Wall" source "-o" temporary-file-name)

checker定義内の 'null-device は同名の変数null-deviceの値で置き換えられて、Windowsだと "NUL" になります。これをCygwinやMSYS2/MinGWのgccが受け取るとコンパイルが成功したときにそのままNULというファイルを出力してしまうのが原因のようです。

同じようにcheckerを再定義すれば良いのかなと思ったのですが、flycheck.elを見てみると沢山のcheckerが定義されていてその中でnull-deviceも沢山使われていました。

そもそもcheckerを再定義するのも面倒ですし、かといってnull-deviceの値を書き替えるのも(他にどこで使われているのか分からないので)怖いですし、手っ取り早くadviceでnull-deviceをtemporary-file-nameに置き換えてしまうことにしました。

;;; Windowsでnull-deviceを使わないようにする。
;; null-deviceはNULなので、CygwinやMinGWだとNULというファイルを作ってしまう。
;; flycheck-command-wrapper-function でNULを置き換えても良いのだが
;; flycheck-substitute-argument でやった方が確実だし簡単。
(when (locate-library "flycheck")
  (with-eval-after-load 'flycheck
    (defun my-flycheck-substitute-argument (old-func arg checker &optional rest)
      (when (eq arg 'null-device)
        (setq arg 'temporary-file-name))

      (apply old-func arg checker rest))
    (advice-add 'flycheck-substitute-argument :around 'my-flycheck-substitute-argument)))

最近はLSPの方でエラー箇所を検出してしまうので必要ないのかもしれませんけどまたNULファイルが出来ると面倒なので念のため。