透過自動完成功能改善我的 Emacs 體驗
我使用 Emacs 多年,用它來撰寫網站上的所有文章、寫書,以及大部分的程式碼。(例外是使用 IntellJ IDEA 編寫 Java,以及使用 RStudio 編寫 R。)因此,我很高興看到過去幾年來 Emacs 功能有了許多進展,讓它不再像演化的死胡同。使用正規表示式來產生自動完成清單,是我 Emacs 體驗中最大的進步之一。
許多 Emacs 指令會產生清單,供使用者選擇。我想要開啟一個檔案時,會輸入按鍵組合來尋找檔案,而 Emacs 會在小緩衝區(與指令互動的特殊區域)中彈出一個候選檔案清單。這些檔案清單可能會很長,特別是我要求列出目前專案中的所有檔案時。
為了指定我想要的檔案,我可以輸入一些文字來篩選清單,因此,如果我想要開啟檔案 articles/simple/2024-emacs-completion.md
,我可能會輸入 emacs
。我不一定要取得那個檔案,只要篩選到足夠小的清單通常就夠了。
有一種特定的正規表示式建構器樣式,我認為最有用,它會用空格分隔正規表示式。這允許我輸入 articles emacs
來取得任何檔案路徑中包含「articles」和「emacs」的檔案路徑清單。它基本上會將字串「articles emacs」轉換成正規表示式 \\(articles\\).*\\(emacs\\)
。更好的是,這種比對器允許我以任何順序輸入正規表示式,因此「emacs articles」也可以比對。這樣,一旦第一個正規表示式彈出篩選後的清單,我就可以使用第二個正規表示式來選擇我想要的,即使區別用的正規表示式早於我的初始搜尋。
安裝這種自動完成比對器對我使用 Emacs 的方式產生了顯著的影響,因為它可以在與指令互動時輕鬆篩選大型清單。其中最顯著的影響之一,是它改變了我使用 M-x
的方式,這個按鍵組合會顯示所有互動式 Emacs 函式的清單。透過正規表示式比對器來篩選清單,它允許我使用函式名稱來呼叫 Emacs 指令,只需幾個按鍵。這樣我就不必記住鍵盤快速鍵。有了這個功能,我可以透過 M-x
來呼叫我不常使用的指令。我不會很常列出所有開啟的緩衝區,所以與其試著記住它的按鍵組合,我只要輸入 M-x ib
,ibuffer
就會快速彈出。這要歸功於我用於 M-x
的指令(counsel-M-x
)會在正規表示式的第一個字元插入一個「^
」,將第一個正規表示式錨定在行的開頭。由於我所有的自寫函式都以 mf-
為前綴,即使函式名稱很長,我也可以輕鬆找到自己的函式。我寫了一個指令來從 URL 中移除網域,我稱它為 mf-url-remove-domain
,可以用 M-x mf url
來呼叫它。
Emacs 中有相當多的套件可以進行此類型的配對,多到令人困惑。我目前使用的套件是 Ivy。它預設使用以空白分隔的正規表示式配對器,但它不支援任何順序。為了按照我喜歡的方式設定它,我使用
(setq ivy-re-builders-alist '((t . ivy--regex-ignore-order)))
Ivy 是名為 counsel
的套件的一部分,其中包含各種增強此類選取的指令。
Ivy 並不是唯一可以執行此類操作的工具。事實上,Emacs 中的完成工具世界令我感到非常困惑:許多工具具有我並不真正理解的重疊和互動。此領域中的工具包括 Helm、company、Vertico 和 Consult。Mastering Emacs 有一篇文章 了解迷你緩衝區完成,但它沒有說明它所討論的機制如何與 Ivy 所做的相符,而且我沒有花時間弄清楚所有內容。
作為一般注意事項,我強烈推薦 Mastering Emacs 這本書,以了解如何使用這個令人難以置信的工具。Emacs 具有如此多的功能,甚至像我這樣的數十年使用者也發現這本書帶來了「我不知道它可以做到這一點」的時刻。
對於那些好奇的人,以下是我的 Emacs 設定檔中相關的片段
(use-package ivy
:demand t
:diminish ivy-mode
:config
(ivy-mode 1)
(counsel-mode 1)
(setq ivy-use-virtual-buffers t)
(setq ivy-use-selectable-prompt t)
(setq ivy-ignore-buffers '(\\` " "\\`\\*magit"))
(setq ivy-re-builders-alist '(
(t . ivy--regex-ignore-order)
))
(setq ivy-height 10)
(setq counsel-find-file-at-point t)
(setq ivy-count-format "(%d/%d) "))
(use-package counsel
:bind (
("C-x C-b" . ivy-switch-buffer)
("C-x b" . ivy-switch-buffer)
("M-r" . counsel-ag)
("C-x C-d" . counsel-dired)
("C-x d" . counsel-dired)
)
:diminish
:config
(global-set-key [remap org-set-tags-command] #'counsel-org-tag))
(use-package swiper
:bind(("M-C-s" . swiper)))
(use-package ivy-hydra)