Yearly Archives: 2024

2024-02-14

Corfuの自動補完と手動補完で補完スタイルを変える方法

そもそも補完スタイルとは何かについてはマニュアルを参照するのが手っ取り早いでしょう(Completion Styles (GNU Emacs Manual)(日本語訳)。簡単に言えば先頭一致とか部分一致とか曖昧一致とかそういうのです。入力したテキストと補完候補が一致していることをどのように判定するかのルールです。例えば basic はほぼ先頭一致ですがカーソル(ポイント)を左に移動したときの挙動が追加されています(純粋な先頭一致は emacs21emacs22)。 substring はほぼ部分一致です。 flex は含まれている文字が順番に登場すれば一致と見なします。このようなルールを変数completion-stylesで指定します。複数指定出来るのは、マッチする候補があるスタイルを順に探していく仕組みになっているからです。

それで以前Corfuを導入したときに、私は自動補完と手動補完で補完候補のソースを変える設定をしました。

companyからcorfuへ移行~自動と手動で補完候補を変える | Misohena Blog

これは自動補完の時に確度の低い候補が出てきてキー入力を奪ってしまうことを回避するのが目的でした。自動補完の時は補完候補の大本を確度の高い物だけに限定してしまおうということです。

しかしそれだけでは不十分で、補完スタイル、つまり補完候補と入力テキストとのマッチング方法によっても実際に出現する候補は変わってきます。例えば補完スタイルにflexなどを指定してしまうと、非常に多くの補完候補とマッチングしてしまい、自動補完のポップアップが頻繁に出現することになりかねません。かといって、手動で補完するときはより沢山候補を出してほしい場合もあるでしょう。自動と手動で補完スタイルを切り替えたいのは当然ではないでしょうか。

というわけで、それを行うコードは次のようになります。

(defun my-corfu--auto-complete-deferred--change-completion-styles (old-fun &rest args)
  ;; corfu-autoの作用で補完候補を出すときに呼び出される
  (let (;; 自動補完の時に使う補完スタイル
        (completion-styles '(basic)))
    ;; 元の処理
    (apply old-fun args)))

(advice-add 'corfu--auto-complete-deferred :around #'my-corfu--auto-complete-deferred--change-completion-styles)

corfu--auto-complete-deferredは自動補完時にのみ呼び出される関数です。そこで一時的にcompletion-stylesをbasicのみにしてしまうと、入力したテキストと先頭一致する候補しか出てこなくなります。

これでひとまず目的は達成できたのですが、結局不意に現れた自動補完ポップアップによって誤操作してしまうという問題は相変わらず完全には解決できていません。自動補完で出す候補を少なくすることは確かに誤操作をする機会を減らしますが、一方で補完できる機会も減らしてしまいます。

そもそも自動補完というのは何が良いのでしょうか。別に補完がしたければ明示的にC-M-iと押せば補完できるのですからそれで良いはずです。しかしそれは補完できることをあらかじめ知っていなければできません。自動補完の良い所は、ユーザーが「こんな補完ができるのか」と気がつけるところにあるのです。そう考えたときに、別の方法を思いつきました。

(続く)

2024-02-05 ,

org-modeにEmacs Lisp要素へのリンクタイプを追加する(org-elisp-link.el)

以前「Emacs Lisp要素へのorg-modeリンクをエクスポートする」や「Emacs Lisp要素へのリンクをorg-modeに追加する」という記事を書きましたが、そこで書いた物を org-elisp-link.el として一つのEmacs Lispにまとめました。

misohena/org-elisp-link: Org-mode Link Types for Emacs Lisp Elements

同様の事をやるEmacs Lispはいくつか見かけましたが、エクスポートまでするのは見つかりませんでした。org-modeのリンクタイプはエクスポートを実装していないものが多い気がします。もちろんEmacs内での作業に役に立てばほとんどの場合それで十分なのですが、たまにエクスポートすると「あれ?」と思うことがあります。

Emacs Lispの言語要素(関数、変数、フェイス、ライブラリ)へのリンクをエクスポートしたいなんて人はそう多くは無いでしょう。誰得? オレだよオレ、俺得。私はこのブログで関数名や変数名を書くことがありますし、自分で見返したときにいちいちEmacsで定義を見に行くよりもブラウザでソースコードへ飛べた方が便利なケースもあります(常にとは言わない)。

READMEにも書きましたが、このEmacs Lispを使うと次のような記述が可能になります。

[[elisp-function:track-mouse]]関数は[[elisp-library:subr;line=4530][subr.elの4530行目]]に定義されています。[[elisp-variable:track-mouse]]という変数も別に定義されています。[[elisp-function:track-mouse]]関数は例えば[[elisp-function:artist-mouse-draw-continously;library=artist]]で使われています。

もちろんC-c C-o (org-open-at-point)で飛べますし、C-c l (org-store-link)でのリンクストア操作にも対応しています。

エクスポートについては以前「Emacs Lisp要素へのorg-modeリンクをエクスポートする」に書いたとおり、現在インストールされているソースコードの中からファイル名と行番号を探し、それに対応するWeb上のコードブラウザへのURLを作成しています。実際に上をエクスポートすると下のようになります。

<p>
<code><a href="https://git.savannah.gnu.org/cgit/emacs.git/tree/lisp/subr.el?h=emacs-29.2#n4530">track-mouse</a></code>関数は<a href="https://git.savannah.gnu.org/cgit/emacs.git/tree/lisp/subr.el?h=emacs-29.2#n4530">subr.elの4530行目</a>に定義されています。<code><a href="https://git.savannah.gnu.org/cgit/emacs.git/tree/src/keyboard.c?h=emacs-29.2#n12850">track-mouse</a></code>という変数も別に定義されています。<code><a href="https://git.savannah.gnu.org/cgit/emacs.git/tree/lisp/subr.el?h=emacs-29.2#n4530">track-mouse</a></code>関数は例えば<code><a href="https://git.savannah.gnu.org/cgit/emacs.git/tree/lisp/textmodes/artist.el?h=emacs-29.2#n4899">artist-mouse-draw-continously</a></code>で使われています。
</p>

以前書いたEmacsをアップグレードしたときにエクスポート結果が変わってしまう問題に対処するため、いくつかオプションを指定出来るようにしました。

[[elisp-function:tetris-start-game;line=600;library=tetris;emacs-version=29.2][Emacs 29.2におけるtetris.el内の600行目にあるtetris-start-game関数]]

このように書けばEmacs 29.2におけるtetris.el内の600行目を指すURLが必ずエクスポートされます。まぁ、常にこのような記述をすべきだとは思いませんけど。

リンクのdescription部分を書いていないときに見た目が酷いことになるので、 :activate-function を使って、シンボル名以外の部分を隠す機能も用意しておきました。前から [[elisp-function:track-mouse]] と書くと elisp-function: の部分が邪魔だなぁと思っていたのでした。もちろんdescription部分も含めて [[elisp-function:track-mouse][track-mouse]] と書けば良いのですが、情報が重複してて嫌だなぁと思っていたのでした。

その他README.org(日本語)に色々説明を書いたので詳しくはそちらで。

以下メモ:

;や&を含む関数名は存在する(c-forward-to-nth-EOF-;-or-}c-semi&comma-inside-parenlist)。もちろん<は>はある(string<とか)。\を含むものは見当たらない。ちゃんとエスケープできるようにした。

org-link-parameters:activate-func は使い方が難しいのだけど(特に効果を打ち消す方。変更フックでは検出できないケースもあるので)、すでに隠している部分を少し広げるくらいなら問題ないと思う。

find-funcライブラリの中身を見てEmacsが各種定義場所を探すときに何をしているのか色々勉強になった。もうちょっと直交性がある感じで綺麗に書いて欲しい。関数がnilで変数がdefvarでフェイスがdeffaceとか終始そんな感じ。いや、そもそもライブラリ名がfind-funcだった。

find-function-regexp-alistが興味深い。その正規表現(find-function-regexpとか)を見ると、思っていたより色々関数や変数等を定義する書式があることが分かる。ただ、この正規表現は%s部分に名前を入れて関数や変数等の定義を探すためのものなので、今回の用途に直接使うのは難しい。

結構頑張ってdefcustomを沢山用意した。

先日Emacs Widget Libraryの勉強をしたのでdefcustomの:type部分を書くのがとても楽になった。

バッファ内オプション( #+HTML_LINK_???? みたいの)の処理とテンプレート文字列( <a href="{{{URL}}}">{{{CONTENTS}}}</a> みたいなの)の処理は以前org-geolinkを作ったときのものがわりとよく出来ていたのでそのまま持ってきた。バッファ内オプションを増やす方法はもうちょっとマシな方法がないのだろうか。それほどちゃんと調べていないのでよく分からない。

ELPAのURL変換はもうちょっと何とかならないだろうか。それと私はEmacs設定ディレクトリ(Gitで管理している)のsubmoduleにしているものも多いので(自分の作ったものは特に)、それを検出してGitHubへのURLを生成したい。

elisp-functionとelisp-funのどちらがいいか。elfunというのもあり? elvar、elface、ellib。

2024-01-31

Emacsでdiffの文字化けを回避する(様々な文字エンコーディングに対応する)

何だか時代錯誤感のあるタイトルで申し訳ないのですが、私は長年Emacsを使っていてもdiffやらgrepやら基本的なコマンドの使い方が分かっていない人間なのです。ご容赦ください。grepの方は最近はripgrepの登場で大分マシになりましたが。いや、そうじゃ無くて、2024年にもなって文字化けなどと書かねばならないというところですよ!

日常的に複数の文字エンコーディング(文字符号化方式、簡単に言えば文字コード、Emacs用語ではコーディングシステム)を使っている人はdiffをどうしているのでしょうか。まぁ、使う文字エンコーディングが一つに偏っているならそれに合わせて残りは場当たり的に対処すれば良いのでしょう。私もそうしていました。UTF-8以外使うな! などと過激なことを言う方も昨今いらっしゃいますが、私はそうは思いません。長年コンピュータを使ってきた人間にとって、過去に作った物を無かったことには出来ませんからね。

とは言えdiffを取ったときに文字化けしているバッファを見ると煩わしさを感じるのも事実です。

そういうときはdiffのバッファの中で M-x revert-buffer-with-coding-system (C-x RET r) の後、文字エンコーディングを選ぶのが簡単です。diffは取り直しになりますが。

他にもread onlyを解除して、バッファ全体をencode-coding-regionしてからdecode-coding-regionしてやると直せる場合もあります。diffの取り直しは回避できますが、常に直せるかはちょっと分からないです。

ediffで済むならそれを使うという手もあります。

いずれにせよ煩わしいことには変わりないので、ある程度自動的に対処するように次のようなコードを書きました。

(defun my-diff-detect-coding-system (file)
  "FILEのcoding systemを返す。分からなかったらnilを返す。"
  (let ((cs
         (when (file-regular-p file) ;;ディレクトリは除外する
           (with-temp-buffer
             (insert-file-contents file nil nil 1000000) ;;1MBくらい読んでおく?
             ;; これが一番簡単で確実っぽい
             last-coding-system-used))))
    (message "Detected coding system: %s" cs)
    (unless (memq cs '(nil undecided no-conversion)) ;;変なのは返さない
      cs)))

(defun my-diff-around (orig-fun old new &rest args)
  "diffにひっかけるaroundアドバイス。"
  ;; NEWのcoding systemに合わせてdiffを取る
  (let ((coding-system-for-read (or coding-system-for-read ;;すでに指定されている場合はそれを使う
                                    (my-diff-detect-coding-system new))))
    (apply orig-fun old new args)))

(advice-add 'diff :around 'my-diff-around)

要するにファイル(NEW側のみ)の文字エンコーディングを判別して、それをcoding-system-for-readに設定してからdiffを実行するだけです。

my-diffという関数を作ろうか迷いましたが、diffはいろんな場所から呼び出されているような気がしたので全てに適用させるためにdiffに対するadviceにしてみました。

文字エンコーディングを判別しているところですが、insert-file-contentsの後にlast-coding-system-usedを参照するのが見つけた方法の中では一番簡単でした。最初はdetect-coding-regionを使ったのですが、UTF-16が判別できないこととファイルローカル変数の指定が効かないことが問題になりました。UTF-16はどのみち別の問題があるので諦めるとして、 -*- coding:cp932 -*- のような指定は効いてほしいところ。半角カナでCP932(SJIS)で「ミエ」と書いたらUTF-8の「д」と区別が付かないんですよ(どんなシチュエーションだよ)。そんなときにcoding:の指定を入れれば解決できるわけです。set-auto-coding関数を使えばUTF-16(auto-coding-regexp-alist)やファイルローカル変数の判別が可能になるのですが、今度は行末タイプ(unix、dos、mac)が判別できません。行末タイプだけを判別するような関数を探したのですが見当たりませんでした。自分で \r\n を検索すれば良いのでしょうが、そんな面倒なことをするよりもlast-coding-system-usedを参照するだけで済むようでした。それらの判別処理は全てinsert-file-contentsの中で行われていますので。

UTF-16はどうしましょうね。こればっかりはUTF-8にでも変換してからdiffを取るくらいしか思いつきません。--textを指定するとして、diff自身が出力するヘッダーの文字エンコーディングと合いませんからね。

ディレクトリ単位の比較は相変わらず化けるので必要に応じて C-x RET r するということで。

あ、diff自体が出力する日本語メッセージが化けますね。「のみに存在」とかいうやつ。実行前に環境変数も変えようかな……。

こうして今日も一つ直すと何個も直すところが増えていくのでした。

まだまだdiffの事はよく分かりません。