前言
在 Emacs中
使用 pass
编辑密码的时候碰到如下错误
1
|
/opt/homebrew/bin/pass: line 503: /opt/homebrew/Cellar/emacs-plus\@30/30.2/bin/emacsclient: No such file or directory
|
先看结论,只需要把如下代码加到 Emacs
的配置中即可
1
|
(setq with-editor-emacsclient-executable "emacsclient")
|
过程
根据错误提示,打开 /opt/homebrew/bin/pass
并定位到 503 行,内容如下
1
|
${EDITOR:-vi} "$tmp_file"
|
优先使用 Editor
变量,没有就回退到 vi
。
从提示中可以看出 Editor
的值是 /opt/homebrew/Cellar/emacs-plus\@30/30.2/bin/emacsclient
这个路径也确实是存在的,没看出来有什么问题。
那就从 pass
中入手,在使用 pass
按下 e
进行编辑的时候调用的是 pass-edit
1
2
3
4
5
6
7
|
(defun pass-edit ()
"Edit the entry at point."
(interactive)
(pass--with-closest-entry entry
(when (or pass-suppress-confirmations
(yes-or-no-p (format "Do you want edit the entry %s? " entry)))
(password-store-edit entry))))
|
这里实际调用的是 password-store-edit
1
2
3
4
|
(defun password-store-edit (entry)
"Edit password for ENTRY."
(interactive (list (password-store--completing-read t)))
(password-store--run-edit entry))
|
password-store-edit
又调用了 password-store--run-edit
1
2
3
4
5
6
7
8
9
10
11
12
13
|
(defun password-store--run-edit (entry)
(password-store--run-async "edit"
entry))
(defun password-store--run-async (&rest args)
"Run pass asynchronously with ARGS.
Nil arguments are ignored. Output is discarded."
(let ((args (mapcar #'shell-quote-argument args)))
(with-editor-async-shell-command
(mapconcat 'identity
(cons password-store-executable
(delq nil args)) " "))))
|
调了一堆函数发现调用了 with-editor-async-shell-command
1
2
3
4
5
6
|
(defun with-editor-async-shell-command
(command &optional output-buffer error-buffer envvar)
(interactive (with-editor-shell-command-read-args "Async shell command: " t))
(let ((with-editor--envvar envvar))
(with-editor
(async-shell-command command output-buffer error-buffer))))
|
with-editor
是个宏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
(defmacro with-editor (&rest body)
"Use the Emacsclient as $EDITOR while evaluating BODY.
Modify the `process-environment' for processes started in BODY,
instructing them to use the Emacsclient as $EDITOR. If optional
ENVVAR is a literal string then bind that environment variable
instead.
\n(fn [ENVVAR] BODY...)"
(declare (indent defun) (debug (body)))
`(let ((with-editor--envvar ,(if (stringp (car body))
(pop body)
'(or with-editor--envvar "EDITOR")))
(process-environment process-environment))
(with-editor--setup)
,@body))
|
里面调用了 with-editor--setup
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
(defun with-editor--setup ()
(if (or (not with-editor-emacsclient-executable)
(file-remote-p default-directory))
(push (concat with-editor--envvar "=" with-editor-sleeping-editor)
process-environment)
;; Make sure server-use-tcp's value is valid.
(unless (featurep 'make-network-process '(:family local))
(setq server-use-tcp t))
;; Make sure the server is running.
(unless (process-live-p server-process)
(when (server-running-p server-name)
(setq server-name (format "server%s" (emacs-pid)))
(when (server-running-p server-name)
(server-force-delete server-name)))
(server-start))
;; Tell $EDITOR to use the Emacsclient.
(push (concat with-editor--envvar "="
;; Quoting is the right thing to do. Applications that
;; fail because of that, are the ones that need fixing,
;; e.g., by using 'eval "$EDITOR" file'. See #121.
(shell-quote-argument
;; If users set the executable manually, they might
;; begin the path with "~", which would get quoted.
(if (string-prefix-p "~" with-editor-emacsclient-executable)
(concat (expand-file-name "~")
(substring with-editor-emacsclient-executable 1))
with-editor-emacsclient-executable))
;; Tell the process where the server file is.
(and (not server-use-tcp)
(concat " --socket-name="
(shell-quote-argument
(expand-file-name server-name
server-socket-dir)))))
process-environment)
(when server-use-tcp
(push (concat "EMACS_SERVER_FILE="
(expand-file-name server-name server-auth-dir))
process-environment))
;; As last resort fallback to the sleeping editor.
(push (concat "ALTERNATE_EDITOR=" with-editor-sleeping-editor)
process-environment)))
|
这里最重要的参数出现了 with-editor-emacsclient-executable
来看下它是怎么赋值的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
(defcustom with-editor-emacsclient-executable (with-editor-locate-emacsclient)
"The Emacsclient executable used by the `with-editor' macro."
:group 'with-editor
:type '(choice (string :tag "Executable")
(const :tag "Don't use Emacsclient" nil)))
(defun with-editor-locate-emacsclient ()
"Search for a suitable Emacsclient executable."
(or (with-editor-locate-emacsclient-1
(with-editor-emacsclient-path)
(length (split-string emacs-version "\\.")))
(prog1 nil (display-warning 'with-editor "\
Cannot determine a suitable Emacsclient
Determining an Emacsclient executable suitable for the
current Emacs instance failed. For more information
please see https://github.com/magit/magit/wiki/Emacsclient."))))
(defun with-editor-locate-emacsclient-1 (path depth)
(let* ((version-lst (cl-subseq (split-string emacs-version "\\.") 0 depth))
(version-reg (concat "^" (string-join version-lst "\\."))))
(or (locate-file
(cond ((equal (downcase invocation-name) "remacs")
"remacsclient")
((bound-and-true-p emacsclient-program-name))
("emacsclient"))
path
(mapcan (lambda (v) (cl-mapcar (lambda (e) (concat v e)) exec-suffixes))
(nconc (and (boundp 'debian-emacs-flavor)
(list (format ".%s" debian-emacs-flavor)))
(cl-mapcon (lambda (v)
(setq v (string-join (reverse v) "."))
(list v
(concat "-" v)
(concat ".emacs" v)))
(reverse version-lst))
(cons "" with-editor-emacsclient-program-suffixes)))
(lambda (exec)
(ignore-errors
(string-match-p version-reg
(with-editor-emacsclient-version exec)))))
(and (> depth 1)
(with-editor-locate-emacsclient-1 path (1- depth))))))
|
到这里基本就知道 Editor
的值是怎么来的。
它是直接定位到 Emacs
的程序所在的路径,然后找到 /bin
目录,查找 emacsclient
。
一般情况下也不会有问题,但是在 Mac
下会有版本 emacs-plus@30
这个 @
会被转义,这时候就会坏事,所以我们直接把 with-editor-emacsclient-executable
改为 emacsclient
就行了,它会从环境变量中获取。