ちみもの三姉妹の目の描き方が三人とも違うのが好き。
ちみもの三姉妹の目の描き方が三人とも違うのが好き。
再現コード:
(progn (require 'svg) (goto-char (point-min)) ;; (insert "\n") ;;これがあると発生しづらい(スクロールして画像を一番上にもってくれば発生する) (let ((ov (make-overlay (point) (progn (insert " ") (point)) nil t))) (overlay-put ov 'evaporate t) (overlay-put ov 'pointer 'arrow) (overlay-put ov 'line-prefix "XXX") ;;これが無ければ発生しない(実際の状況ではこれが無くてもorg-indentがテキストプロパティのline-prefixを変更するといったことでこれと同じ事になる) (overlay-put ov 'display (let ((svg (svg-create 400 300))) (svg-rectangle svg 0 0 400 300 :fill "blue") (svg-text svg "Click here" :x 200 :y 150 :text-anchor "middle" :fill "white") (svg-image svg))) (overlay-put ov 'keymap (let ((km (make-sparse-keymap))) (define-key km [mouse-1] (lambda (ev) (interactive "e") (message "xy=%s" (posn-object-x-y (event-start ev))))) km))) (insert "\n"))
上のコードをscratchバッファで実行すると画像が表示される。その画像をクリックするとミニバッファに座標が表示されるが、画像の左上からの座標になっていないことが分かる。原点は画像の左上では無く「XXX」の左上になっている。
この現象は画像がウィンドウの先頭にあり、その左にline-prefixによる文字列があるときのみ起こる。(line-prefixはテキストプロパティ、オーバーレイプロパティ、line-prefix変数のいずれで設定しても現象は起きる)
Emacs Lisp Reference Manualの22.7.4 Click Eventsには、マウスイベントのdx,dyはobjectの左上角を(0 . 0)とする相対座標と書かれている。今回の場合はobjectは画像('imageで始まるdisplayプロパティ)になるので、画像の左上からの座標になっていなければならない。しかし、画像がウィンドウの先頭にあって、かつ、line-prefixが使われている時に限り、line-prefix文字列の左上からの座標になってしまっている。画像がウィンドウの先頭に無いときや、line-prefixが使われていないときは正しく画像左上からの座標になっている。動作に一貫性が無く、文書化されている仕様と異なる結果が生じているのでEmacsの問題と言って良いのではないかと思う。
回避策としては、マウスの入力を必要とする時だけline-prefixを無効化するといったことが考えられる。
実際にel-easydrawにおいて作図エディタがウィンドウの一番上にあるときに座標がずれる問題が発生していた。org-indentを有効にすると発生する。org-indentはline-prefixやwrap-prefixを使用するので、まさに今回のケースに当てはまる。コミット701bfaaにて一時的にline-prefixを無効化することで回避した。
おそらくEmacsのバグだと思うが、この問題に気がつく人はどのくらいいるのだろうか。Emacs内に画像を表示してその中に対してマウス入力するなんてことを伊達や酔狂で無く本気でやっている人はどのくらいいるのだろうか。
(2025-02-24追記: Emacs 30.1での設定はこちら)
(2024-02-19追記: 手っ取り早く使い方を知りたい人はMS-Windows版 Emacs 29.1への移行作業をご覧下さい)
前回の続き。
MSYS2のセットアップについては割愛します。Emacsはlibgccjitを使用してネイティブコンパイルを行うため、それに関連したファイル群をMSYS2から入手する必要があります。おそらく base-devel, mingw-w64-x86_64-toolchain, mingw-w64-x86_64-libgccjit あたりのパッケージが入っていれば良いのだと思います。
ただし実際にEmacsを使用するときにはMSYS2全体は必ずしも必要なく、少数のファイルだけコピーして他のPCに移すことも出来ます。
ここでは公式配布の emacs-28.1.zip を前提に説明しますが、自分で --with-native-compilation
を指定してビルドしたEmacsを他の環境へ移す場合にも同じ考え方が適用出来ると思います。ちなみに emacs-28.1.zip は --with-native-compilation
を指定してビルドされていますが(C-h v system-configuration-optionsで確認できる)ネイティブコンパイルに必要なDLL等が含まれていない状態です。
Emacsが起動する前に、環境変数PATHにMSYS2内の mingw64/bin
ディレクトリが含まれていればネイティブコンパイルは正常に動作します(もちろんlibgccjitパッケージがインストールされていること)。
Windowsの「システムの詳細設定」で環境変数を変更するか、次のようなbatファイルを経由するといった方法が考えられます。
set PATH=c:/hogehoge/msys/mingw64/bin;%PATH% c:/hogehoge/emacs-28.1/bin/runemacs.exe
mingw64/bin/には libgccjit-0.dll, as.exe, ld.exe が含まれている必要があります。また、 mingw64/lib/以下には必要なライブラリが含まれている必要があります。
ここで大事なのは「Emacsの起動前から」という点です。
環境変数を変更するには early-init.el や init.el 内でsetenv関数を呼び出す方法もありますが、それだと起動後すぐのネイティブコンパイルには適用されません。一番最初のネイティブコンパイルは early-init.el よりも前に起動する場合があります。なので early-init.el で (setenv "PATH" ~) しても手遅れな場合があります。
この方法は環境変数PATHを常時変更するため、人によっては許容できない場合があります。例えばCygwinやその他GNUツールを含むプロダクトに既にPATHが通っていてEmacs使用中にそのコマンドを使いたい場合です。
alpha.gnu.org has shiny new Emacs 28.0.91 Windows binaries : emacs のコメントによれば、ネイティブコンパイルに必要なファイルは次の17個だそうです。
これらをemacs-28.1.zipを展開して出来たディレクトリの適切なディレクトリへコピーするとネイティブコンパイルが条件付きで動作するようになります。libgccjit-0.dll は bin (既にrunemacs.exe等があるディレクトリ)へ、それ以外は lib (既にemacs/やsystemd/がある)の下にgccというディレクトリを作成してその中へ入れてください。
これだけである程度の割合でネイティブコンパイルが成功するようになるのですが、自動で起動する非同期コンパイルがなぜか失敗する場合があります(カレントディレクトリがbinやlib等の階層に無い場合にas.exeが無いと言われます)。
そこで early-init.el (あるいはinit.el)に次のコードを追加します。
(2022-09-23追記: Windows版Emacsを28.1に上げたのでNative Compilationフィーバーに便乗する - Qiitaにあるように native-comp-driver-options 変数に -B オプションを指定した方が良さそうです。私はearly-init.elで (setq native-comp-driver-options (list "-B" (expand-file-name (file-name-concat invocation-directory "../lib/gcc")) ))
のようにしました)
(when (and (fboundp #'native-comp-available-p) ;;emacs-28以降 (native-comp-available-p) ;;libgccjitが使える (eq system-type 'windows-nt)) ;;Windowsの場合 (他必要に応じて条件を追加すること) (defun my-comp-wrap-process-call (orig-fun &rest args) (let* (;; emacs.exeのあるディレクトリの一つ上のlib/gcc (lib-dir (expand-file-name (file-name-concat invocation-directory "../lib/gcc"))) ;; 環境変数PATHとLIBRARY_PATHを一時的に変更 (process-environment (append (list (concat "PATH=" lib-dir ";" (getenv "PATH")) (concat "LIBRARY_PATH=" lib-dir)) process-environment))) ;; 元の関数を呼び出す (apply orig-fun args))) ;; コンパイル用にemacsを起動する関数をラップする (advice-add #'comp-final :around #'my-comp-wrap-process-call) (advice-add #'comp-run-async-workers :around #'my-comp-wrap-process-call))
このコードはネイティブコンパイルのために別プロセスでemacsを起動するときにだけ環境変数PATHとLIBRARY_PATHを変更します。起動されたemacsは最初からネイティブコンパイルに必要なコマンドにPATHが通った状態になります。
つまり、early-init.elより前に起動するネイティブコンパイルについてはlib/gccに置いたas.exeやld.exeが使われることによって解決し、それ以降なぜかエラーになるケースについては環境変数の一時的な変更で解決します。
ちなみに前者はあらかじめ必要なファイルをネイティブコンパイルしておくことによって回避可能です。エラーが出るファイルを手動でネイティブコンパイルしたり、試してはいませんが NATIVE_FULL_AOT=1 でビルドすると回避できるかもしれません。
なお、既にCygwin等にPATHが通っていると、設定が間違っていてもCygwinのas.exeやld.exeが起動する場合があるので注意が必要です。
なぜか失敗するケースは、調べた限りコンパイル時のカレントディレクトリによるようなので次のようなコードでも良いかもしれません。ただ、ネイティブコンパイルがカレントディレクトリに依存している(または将来するようになる)とまずいかもしれません。
(when (and (fboundp #'native-comp-available-p) ;;emacs-28以降 (native-comp-available-p) ;;libgccjitが使える (eq system-type 'windows-nt)) ;;Windowsの場合 (他必要に応じて条件を追加すること) (defun my-comp-wrap-process-call (orig-fun &rest args) ;; 一時的にカレントディレクトリを emacs-28.1/bin にする ;; でないと emacs-28.1/lib/gcc/as.exe を見つけてくれない (let ((default-directory invocation-directory)) ;; 元の関数を呼び出す (apply orig-fun args))) ;; コンパイル用にemacsを起動する関数をラップする (advice-add #'comp-final :around #'my-comp-wrap-process-call) (advice-add #'comp-run-async-workers :around #'my-comp-wrap-process-call))
最も単純な方法は comp.el を修正することだと思います。
前述の通りネイティブコンパイルはearly-init.elよりも前に起動する場合があり、comp.elもその時にロードされます。
従ってearly-init.elやinit.elで挙動を完全に修正するのは無理なので、comp.elを直接書き替えてしまった方が素直な方法となるでしょう。(Emacsの初期化プロセスについて詳しくないので他に何か方法があったらすみません)
具体的には、前と同じようなことをcomp.elの末尾に書き加えてやれば良いでしょう。例えば:
;; share/emacs/28.1/lisp/emacs-lisp/comp.el の末尾、(provide 'comp)の前に以下を追加 (defconst my-comp-tool-path "c:/hogehoge/msys/mingw64/bin") ;;自分のmingw64/binの場所 (defun my-comp-wrap-process-call (orig-fun &rest args) (let* ((process-environment (cons (concat "PATH=" my-comp-tool-path ";" (getenv "PATH")) process-environment))) ;; 元の関数を呼び出す (apply orig-fun args))) ;; コンパイル用にemacsを起動する関数をラップする (advice-add #'comp-final :around #'my-comp-wrap-process-call) (advice-add #'comp-run-async-workers :around #'my-comp-wrap-process-call)
my-comp-tool-pathにはmingw64/binの場所を指定してください。もしくは前にやったようにemacs.exeの位置から相対的に割り出しても良いでしょう(関連ファイルのコピーが必要になりますが)。
ファイル(comp.el)の途中を書き替えるのも分かりづらいかなと思ったのでadviceのままにしてあります。make-processやcall-processの前後を直接書き替えても良いでしょう。
comp.elを書き替えたら対応する.elcや.elnを削除するのもお忘れ無く。
Emacs 28.2がリリースされてWindows版の公式ビルドも公開されました。
Index of /gnu/emacs/windows/emacs-28
emacs-28.2.zipを展開して、MSYS2のlibgccjit-0.dllとlibisl-23.dllをemacs-28.2/binへ、その他のライブラリやらas.exe、ld.exeやらをemacs-28.2/lib/gcc/へコピーし、early-init.elで (setq native-comp-driver-options (list "-B" (expand-file-name (file-name-concat invocation-directory "../lib/gcc")) ))
と指定しただけで問題なくNative Compileできるようになりました。
ちなみに、最初MSYS2最新のlibgccjit-0.dllをbinに入れてもNative Compileが有効になりませんでした((native-comp-available-p)がnil)。
それをTwitterに書いたところ、libisl-23.dllも必要になったとの情報を頂きました。
まとめ参考になりました。
libisl -23.dll も必要みたいです。— re102951 (@re102951) September 23, 2022
調べてみると確かに最新のlibgccjit-0.dll内にはlibisl-23.dllという文字列があります。libgccjitのバージョンアップに伴い依存するdllが増えたようです。libisl-23.dllもemacs-28.2/binへコピーしたところnative-comp-available-pがtになりました。
こういうことがあるからバイナリ配布するならlibgccjit(や関連ファイル)も一緒に配布してほしいものです。
MS-Windows版 Emacs 29.1へ移行に書きました。dllが二つ増えていただけです。