26.12 实现“魔法”文件名机制

你可以为特定文件名实现特殊处理逻辑,这被称为将这些名称设为魔法(magic)文件名。该特性的主要用途是实现远程文件访问(see Remote Files in The GNU Emacs Manual)。

要定义一类魔法文件名,你需要提供一个正则表达式来界定该类名称(所有匹配该正则的名称),并提供一个处理器函数,为匹配的文件名实现所有 Emacs 基础文件操作。

变量 file-name-handler-alist 存储处理器列表,以及决定何时应用每个处理器的正则表达式。列表中每个元素的格式如下:

(regexp . handler)

所有用于文件访问和文件名转换的 Emacs 基础函数都会检查给定文件名是否匹配 file-name-handler-alist。 如果文件名匹配 regexp,这些基础函数会通过调用 handler 来处理该文件。

传给 handler 的第一个参数是基础函数名(符号形式),后续参数是传给该基础函数的参数。(这些参数中的第一个通常是文件名本身。)例如,执行以下代码时:

(file-exists-p filename)

如果 filename 对应的处理器是 handler,则 handler 会被这样调用:

(funcall handler 'file-exists-p filename)

当一个函数接收两个或更多必须是文件名的参数时,会检查每个参数对应的处理器。例如,执行以下代码时:

(expand-file-name filename dirname)

会先检查 filename 的处理器,再检查 dirname 的处理器。无论匹配到哪个,handler 都会被这样调用:

(funcall handler 'expand-file-name filename dirname)

此时 handler 需要自行判断是处理 filename 还是 dirname

如果指定文件名匹配多个处理器,则匹配位置在文件名中起始位置最晚的处理器优先。选择此规则是为了让解压缩等处理器先于远程文件访问等处理器执行。

以下是魔法文件名处理器需要处理的操作:

abbreviate-file-name, access-file, add-name-to-file, byte-compiler-base-file-name,
copy-directory, copy-file, delete-directory, delete-file, diff-latest-backup-file, directory-file-name, directory-files, directory-files-and-attributes, dired-compress-file, dired-uncache, exec-path, expand-file-name,
file-accessible-directory-p, file-acl, file-attributes, file-directory-p, file-equal-p, file-executable-p, file-exists-p, file-group-gid, file-in-directory-p, file-local-copy, file-locked-p, file-modes, file-name-all-completions, file-name-as-directory, file-name-case-insensitive-p, file-name-completion, file-name-directory, file-name-nondirectory, file-name-sans-versions, file-newer-than-file-p, file-notify-add-watch, file-notify-rm-watch, file-notify-valid-p, file-ownership-preserved-p, file-readable-p, file-regular-p, file-remote-p, file-selinux-context, file-symlink-p, file-system-info, file-truename, file-user-uid, file-writable-p, find-backup-file-name,
get-file-buffer, insert-directory, insert-file-contents,
list-system-processes, load, lock-file, make-auto-save-file-name, make-directory, make-lock-file-name, make-nearby-temp-file, make-process, make-symbolic-link,
memory-info, process-attributes, process-file, rename-file, set-file-acl, set-file-modes, set-file-selinux-context, set-file-times, set-visited-file-modtime, shell-command, start-file-process, substitute-in-file-name,
temporary-file-directory, unhandled-file-name-directory, unlock-file, vc-registered, verify-visited-file-modtime,
write-region.

处理 insert-file-contents 的处理器通常需要在 visit 参数非 nil 时清除缓冲区的修改标记(使用 (set-buffer-modified-p nil))。这同时也会解锁被锁定的缓冲区。

处理器函数必须处理上述所有操作,以及未来可能新增的其他操作。它无需自行实现所有操作——对于无需特殊处理的操作,可重新调用基础函数以常规方式处理。对于无法识别的操作,应始终重新调用基础函数。实现方式如下:

(defun my-file-handler (operation &rest args)
  ;; First check for the specific operations
  ;; that we have special handling for.
  (cond ((eq operation 'insert-file-contents) ...)
        ((eq operation 'write-region) ...)
        ...
        ;; Handle any operation we don’t know about.
        (t (let ((inhibit-file-name-handlers
                  (cons 'my-file-handler
                        (and (eq inhibit-file-name-operation operation)
                             inhibit-file-name-handlers)))
                 (inhibit-file-name-operation operation))
             (apply operation args)))))

当处理器函数决定调用 Emacs 常规基础函数处理当前操作时,需要防止基础函数再次调用同一个处理器,从而导致无限递归。 上述示例展示了如何通过变量 inhibit-file-name-handlersinhibit-file-name-operation 实现这一点。 务必严格按照示例中的方式使用这些变量——在多处理器场景下,以及处理包含多个可能各有处理器的文件名的操作时,细节对正确行为至关重要。

对于文件实际访问无需特殊处理的处理器(例如仅为远程文件名实现主机名补全的处理器),应设置非 nilsafe-magic 属性。 例如,Emacs 通常会保护 PATH 中找到的目录名,若其看起来像魔法文件名,则在前面添加 ‘/:’ 以避免被识别为魔法文件名。 但如果对应的处理器的 safe-magic 属性为非 nil,则不会添加 ‘/:’。

文件名处理器可设置 operations 属性,声明其需要做特殊处理的操作。 若该属性值非 nil,则应为操作列表;此时只有列表中的操作会调用该处理器。 这能避免低效调用,主要用于自动加载的处理器函数,确保仅在有实际工作要做时才加载它们。

简单地将所有操作委托给常规基础函数是不可行的。例如,如果文件名处理器作用于 file-exists-p,则必须自行处理 load,因为常规的 load 代码在此场景下无法正常工作。 但如果处理器通过 operations 属性声明不处理 file-exists-p,则无需对 load 做特殊处理。

Variable: inhibit-file-name-handlers

该变量存储当前被禁止用于特定操作的处理器列表。

Variable: inhibit-file-name-operation

当前禁止某些处理器生效的操作名称。

Function: find-file-name-handler file operation

该函数返回文件名 file 对应的处理器函数,若无则返回 nil。参数 operation 应为要对文件执行的操作——即调用处理器时作为第一个参数传入的值。 如果 operation 等于 inhibit-file-name-operation,或未出现在处理器的 operations 属性中,该函数返回 nil

Function: file-local-copy filename

如果文件 filename 不在本地主机上,该函数会将其复制到本地主机的普通非魔法文件中。 指向其他机器文件的魔法文件名应处理 file-local-copy 操作。 用于远程文件访问以外用途的魔法文件名不应处理 file-local-copy,此时该函数会将文件视为本地文件。

如果 filename 是本地文件(无论是否为魔法文件),该函数不执行任何操作并返回 nil。否则返回本地副本的文件名。

Function: file-remote-p filename &optional identification connected

该函数检查 filename 是否为远程文件。如果 filename 是本地文件(非远程),返回 nil。 如果确实是远程文件,返回标识远程系统的字符串。

该标识符字符串可包含主机名、用户名,以及指定访问远程系统方式的字符。例如,文件名 /sudo::/some/file 的远程标识符字符串是 /sudo:root@localhost:

如果 file-remote-p 对两个不同文件名返回相同标识符,表示它们存储在同一文件系统上,且可相对彼此本地访问。 这意味着例如可以启动一个远程进程同时访问这两个文件。文件名处理器的实现者需要确保该原则有效。

identification 指定要返回的标识符部分。identification 可以是符号 methoduserhostlocalname; 其他值视为 nil,表示返回完整的标识符字符串。在上述示例中,远程 user 标识符字符串为 root

如果远程文件 file 不包含访问方式、用户名或主机名,则返回相应的默认值。 identificationlocalname 时返回的字符串可能因是否存在现有连接而不同。 特定的文件名处理器实现可支持更多 identification 符号;例如 See Tramp 还支持 hop 符号。

如果 connectednil,即使 filename 是远程文件,若 Emacs 与对应主机无网络连接,该函数也返回 nil。 这在需要避免因无连接而产生连接延迟时非常有用。如果 connectednever,则**绝不**使用现有连接返回标识符(其他行为与 nil 相同)。 这可防止任何连接相关的逻辑,例如展开文件名的本地部分。

Function: unhandled-file-name-directory filename

该函数返回一个非魔法目录名。对于非魔法文件名 filename,返回对应的目录名(see 目录名)。 对于魔法文件名 filename,调用其文件名处理器,由处理器决定返回值。 如果 filename 无法被本地进程访问,文件名处理器应返回 nil 以标识此情况。

这在运行子进程时非常有用:每个子进程都需要一个非魔法目录作为当前目录,该函数是获取此类目录的理想方式。

Function: file-local-name filename

该函数返回 filename本地部分(local part)。这是文件名在远程主机上标识文件的部分,通常通过从远程文件名中移除指定远程主机和访问方式的部分得到。例如:

(file-local-name "/ssh:user@host:/foo/bar")
     ⇒ "/foo/bar"

对于远程文件名 filename,该函数返回的文件名可直接用作远程进程的参数(see Creating an Asynchronous Process 和 see Creating a Synchronous Process),也可作为要在远程主机上运行的程序名。 如果 filename 是本地文件,该函数原样返回。

User Option: remote-file-name-inhibit-cache

为提升性能,远程文件的属性可被缓存。如果这些属性在 Emacs 控制之外被修改,缓存值会失效,必须重新读取。

当该变量设为 nil 时,缓存值永不过期。使用此设置时需谨慎,仅当确定除 Emacs 外无其他程序修改远程文件时使用。 设为 t 时,永不使用缓存值。这是最安全的设置,但可能导致性能下降。

折中方案是将其设为正数,表示缓存值自缓存时起可使用指定秒数。 如果定期检查远程文件,可将该变量临时绑定为小于连续检查间隔的值。例如:

(defun display-time-file-nonempty-p (file)
  (let ((remote-file-name-inhibit-cache
         (- display-time-interval 5)))
    (and (file-exists-p file)
         (< 0 (file-attribute-size
               (file-attributes
                (file-chase-links file)))))))
Macro: without-remote-files body…

without-remote-files 宏在禁用远程文件的文件名处理器的情况下求值 body 中的表达式。 这些文件名会被按字面量处理。

该宏仅应在明确不会出现远程文件,或有意不处理远程文件名的场景中使用。 它还能减少对 file-name-handler-alist 的检查,提升代码性能。


emacs

Emacs

org-mode

Orgmode

Donations

打赏

Copyright

© Jasper Hsu

Creative Commons

Creative Commons

Attribute

Attribute

Noncommercial

Noncommercial

Share Alike

Share Alike