1 Star 0 Fork 3

rookieagle/Elisp

forked from advanceflow/Elisp 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
16-加载.org 44.38 KB
一键复制 编辑 原始数据 按行查看 历史
advanceflow 提交于 2022-05-28 10:12 . finish s2

16 加载

加载 Lisp 代码文件意味着将其内容以 Lisp 对象的形式带入 Lisp 环境。 Emacs 找到并打开文件,读取文本,评估每个表单,然后关闭文件。 这样的文件也称为 Lisp 库。

load 函数计算文件中的所有表达式,就像 eval-buffer 函数计算缓冲区中的所有表达式一样。 不同之处在于加载函数读取和评估文件中在磁盘上找到的文本,而不是 Emacs 缓冲区中的文本。

加载的文件必须包含 Lisp 表达式,作为源代码或字节编译代码。 文件中的每个表单都称为顶级表单。 可加载文件中的表单没有特殊格式; 文件中的任何形式都可以直接输入缓冲区并在那里进行评估。 (事实上​​,大多数代码都是这样测试的。)最常见的形式是函数定义和变量定义。

Emacs 还可以加载已编译的动态模块:共享库,提供用于 Emacs Lisp 程序的附加功能,就像用 Emacs Lisp 编写的包一样。 当一个动态模块被加载时,Emacs 调用一个特别命名的初始化函数,该模块需要实现这个函数,并将附加的函数和变量暴露给 Emacs Lisp 程序。

对于预先知道某些 Emacs 原语需要的外部库的按需加载,请参阅动态加载的库。

16.1 程序如何加载

Emacs Lisp 有几个用于加载的接口。 例如,自动加载为文件中定义的函数创建一个占位符对象; 尝试调用自动加载函数会加载文件以获取函数的真实定义(请参阅自动加载)。 如果尚未加载,则 require 加载文件(请参阅功能)。 最终,所有这些设施都会调用 load 函数来完成工作。

Function: load filename &optional missing-ok nomessage nosuffix must-suffix ¶

此函数查找并打开一个 Lisp 代码文件,计算其中的所有表单,然后关闭该文件。

要查找该文件,load 首先查找名为 filename.elc 的文件,即查找名称为 filename 并附加扩展名“.elc”的文件。 如果存在这样的文件,并且 Emacs 是使用本地编译支持编译的(请参阅 Lisp 到本地代码的编译),则 load 尝试查找相应的 ‘.eln’ 文件,如果找到,则加载它而不是 filename.elc。 否则,它会加载 filename.elc。 如果没有该名称的文件,则 load 查找名为 filename.el 的文件。 如果该文件存在,则加载它。 如果 Emacs 编译时支持动态模块(请参阅 Emacs 动态模块),则 load next 会查找名为 filename.ext 的文件,其中 ext 是共享库的系统相关文件扩展名。 最后,如果没有找到这些名称,则 load 会查找名为 filename 且未附加任何内容的文件,如果存在则加载它。 (加载函数在查看文件名方面并不聪明。在名为 foo.el.el 的文件的反常情况下,评估 (load “foo.el”) 确实会找到它。)

如果启用自动压缩模式(默认情况下),那么如果 load 找不到文件,它会在尝试其他文件名之前搜索文件的压缩版本。 如果存在,它会解压缩并加载它。 它通过将 jka-compr-load-suffixes 中的每个后缀附加到文件名来查找压缩版本。 此变量的值必须是字符串列表。 它的标准值为(“.gz”)。

如果可选参数 nosuffix 不为 nil,则 load 不会尝试使用后缀“.elc”和“.el”。 在这种情况下,您必须指定所需的精确文件名,除非启用自动压缩模式,加载仍将使用 jka-compr-load-suffixes 查找压缩版本。 通过指定精确的文件名并使用 t 作为后缀,您可以防止尝试像 foo.el.el 这样的文件名。

如果可选参数 must-suffix 不为零,则 load 坚持使用的文件名必须以“.el”或“.elc”(可能使用压缩后缀扩展)或共享库扩展名结尾,除非它包含明确的目录名称。

如果选项 load-prefer-newer 不是 nil,那么在搜索后缀时,load 会选择最近修改过的文件版本(“.elc”、“.el”等)。 在这种情况下,即使存在“.eln”本机编译文件,load 也不会加载它。

如果 filename 是相对文件名,例如 foo 或 baz/foo.bar,则 load 使用变量 load-path 搜索文件。 它将文件名附加到加载路径中列出的每个目录,并加载它找到的第一个名称匹配的文件。 当前默认目录只有在 load-path 中指定时才会尝试,其中 nil 代表默认目录。 load 在 load-path 的第一个目录中尝试所有三个可能的后缀,然后在第二个目录中尝试所有三个后缀,依此类推。 请参阅图书馆搜索。

无论最终找到文件的名称是什么,以及 Emacs 找到它的目录,Emacs 都会将变量 load-file-name 的值设置为该文件的名称。

如果您收到 foo.elc 比 foo.el 旧的警告,则意味着您应该考虑重新编译 foo.el。 请参阅字节编译。

加载源文件(未编译)时,load 执行字符集转换,就像 Emacs 在访问文件时所做的那样。 请参阅编码系统。

当加载一个未编译的文件时,Emacs 会尝试扩展该文件包含的所有宏(参见宏)。 我们将此称为急切的宏扩展。 这样做(而不是将扩展推迟到相关代码运行)可以显着加快未编译代码的执行速度。 有时,由于循环依赖,这种宏扩展无法完成。 在最简单的示例中,您正在加载的文件是指在另一个文件中定义的宏,而该文件又需要您正在加载的文件。 这通常是无害的。 Emacs 会打印一个警告(’Eager macro-expansion skipped due to cycle…’)给出问题的详细信息,但它仍然会加载文件,只是暂时不展开宏。 您可能希望重组您的代码,以免发生这种情况。 加载已编译的文件不会导致宏扩展,因为这应该在编译期间已经发生。 请参阅宏和字节编译。

除非 nomessage 不为零,否则在加载过程中,诸如“正在加载 foo…”和“正在加载 foo…done”之类的消息会出现在回显区域中。 如果加载了本机编译的“.eln”文件,则消息会这样说。

加载文件时任何未处理的错误都会终止加载。 如果加载是为了自动加载而完成的,则在加载期间所做的任何函数定义都将被撤消。

如果加载找不到要加载的文件,则通常它会发出文件错误信号(带有“无法打开加载文件文件名”)。 但是如果 missing-ok 不是 nil,那么 load 只返回 nil。

您可以使用变量 load-read-function 来指定要使用的加载函数,而不是 read 来读取表达式。 见下文。

如果文件加载成功,则 load 返回 t。

Command: load-file filename ¶

此命令加载文件文件名。 如果 filename 是相对文件名,则假定为当前默认目录。 此命令不使用加载路径,也不附加后缀。 但是,它会查找压缩版本(如果启用了自动压缩模式)。 如果您希望精确指定要加载的文件名,请使用此命令。

Command: load-library library ¶

此命令加载名为 library 的库。 它等价于加载,除了它以交互方式读取其参数的方式。 请参阅 GNU Emacs 手册中的 Lisp 库。

Variable: load-in-progress ¶

如果 Emacs 正在加载文件,则此变量为非 nil,否则为 nil。

Variable: load-file-name ¶

当 Emacs 正在加载一个文件时,这个变量的值就是那个文件的名字,正如 Emacs 在本节前面描述的搜索中找到的那样。

Variable: load-read-function ¶

此变量为 load 和 eval-region 指定替代表达式读取函数,以代替读取。 该函数应该接受一个参数,就像 read 一样。

默认情况下,读取此变量的值。 请参阅输入函数。

与其使用这个变量,不如使用另一个更新的特性:将函数作为 read-function 参数传递给 eval-region。 见评估。

有关如何在构建 Emacs 中使用负载的信息,请参阅构建 Emacs。

16.2 加载后缀

我们现在描述一些关于加载尝试的确切后缀的技术细节。

Variable: load-suffixes ¶

这是一个后缀列表,表示(编译的或源代码的)Emacs Lisp 文件。 它不应包含空字符串。 load 在将 Lisp 后缀附加到指定文件名时按顺序使用这些后缀。 标准值是 (“.elc” “.el”),它产生上一节中描述的行为。

Variable: load-file-rep-suffixes ¶

这是表示同一文件的表示的后缀列表。 此列表通常应以空字符串开头。 当 load 搜索文件时,它会在此列表中按顺序将后缀附加到文件名,然后再搜索另一个文件。

启用自动压缩模式会将 jka-compr-load-suffixes 中的后缀附加到此列表中,禁用自动压缩模式会再次删除它们。 如果禁用自动压缩模式,则 load-file-rep-suffixes 的标准值为 (“”)。 鉴于 jka-compr-load-suffixes 的标准值为 (“.gz”),如果启用 Auto Compression 模式,则 load-file-rep-suffixes 的标准值为 (“” “.gz”)。

Function: get-load-suffixes ¶

当它的 must-suffix 参数为非 nil 时,此函数按顺序返回加载应尝试的所有后缀的列表。 这同时考虑了加载后缀和加载文件代表后缀。 如果 load-suffixes、jka-compr-load-suffixes 和 load-file-rep-suffixes 都有其标准值,则此函数返回 (“.elc” “.elc.gz” “.el” “.el.gz” ) 如果启用自动压缩模式,如果禁用自动压缩模式 (“.elc” “.el”)。

总而言之,加载通常首先尝试 (get-load-suffixes) 值中的后缀,然后是 load-file-rep-suffixes 中的后缀。 如果 nosuffix 为非 nil,则跳过前一组,如果 must-suffix 为非 nil,则跳过后一组。

User Option: load-prefer-newer ¶

如果此选项不为 nil,则不要在存在的第一个后缀处停止,而是对它们进行全部负载测试,并使用最新的文件。

16.3 库搜索

当 Emacs 加载 Lisp 库时,它会在由变量 load-path 指定的目录列表中搜索该库。

Variable: load-path ¶

此变量的值是加载文件时要搜索的目录列表。 每个元素都是一个字符串(必须是目录)或 nil(代表当前工作目录)。

当 Emacs 启动时,它会分几步设置 load-path 的值。 首先,它使用编译 Emacs 时设置的默认位置初始化加载路径。 通常,这是一个类似的目录

"/usr/local/share/emacs/version/lisp"

(在本示例和以下示例中,将 /usr/local 替换为适合您的 Emacs 的安装前缀。)这些目录包含 Emacs 附带的标准 Lisp 文件。 如果 Emacs 找不到它们,它将无法正确启动。

如果您从构建 Emacs 的目录(即尚未正式安装的可执行文件)运行 Emacs,则 Emacs 会使用包含构建源的目录中的 lisp 目录来初始化 load-path。 如果您在与源代码不同的目录中构建 Emacs,它还会从构建目录中添加 lisp 目录。 (在所有情况下,元素都表示为绝对文件名。)

除非您使用 –no-site-lisp 选项启动 Emacs,否则它会在 load-path 的前面添加另外两个 site-lisp 目录。 这些适用于本地安装的 Lisp 文件,通常采用以下形式:

"/usr/local/share/emacs/version/site-lisp"

"/usr/local/share/emacs/site-lisp"

第一个是针对特定 Emacs 版本的本地安装文件; 第二个是本地安装的文件,用于所有已安装的 Emacs 版本。 (如果 Emacs 正在卸载运行,它还会从源目录和构建目录中添加 site-lisp 目录,如果它们存在的话。通常这些目录不包含 site-lisp 目录。)

如果设置了环境变量 EMACSLOADPATH,它会修改上述初始化过程。 Emacs 根据环境变量的值初始化 load-path。

EMACSLOADPATH 的语法与用于 PATH 的语法相同; 目录由’:’(或’;’,在某些操作系统上)分隔。 以下是如何设置 EMACSLOADPATH 变量的示例(来自 sh 样式的 shell):

export EMACSLOADPATH=/home/foo/.emacs.d/lisp:

环境变量值中的空元素,无论是尾随(如上例)、前导还是嵌入,都将替换为由标准初始化过程确定的 load-path 的默认值。 如果没有这样的空元素,则 EMACSLOADPATH 指定整个加载路径。 您必须包含一个空元素,或者包含标准 Lisp 文件的目录的显式路径,否则 Emacs 将无法运行。 (修改加载路径的另一种方法是在启动 Emacs 时使用 -L 命令行选项;见下文。)

对于 load-path 中的每个目录,Emacs 然后检查它是否包含文件 subdirs.el,如果是,则加载它。 subdirs.el 文件是在构建/安装 Emacs 时创建的,其中包含使 Emacs 将这些目录的任何子目录添加到加载路径的代码。 添加了直接子目录和向下多层的子目录。 但它不包括名称不以字母或数字开头的子目录、名为 RCS 或 CVS 的子目录,以及包含名为 .nosearch 的文件的子目录。

接下来,Emacs 添加您使用 -L 命令行选项指定的任何额外加载目录(请参阅 The GNU Emacs Manual 中的 Action Arguments)。 它还会添加安装可选包的目录(如果有)(请参阅打包基础知识)。

通常将代码添加到一个初始化文件(请参阅初始化文件)以将一个或多个目录添加到加载路径。 例如:

(push "~/.emacs.d/lisp" load-path)

转储 Emacs 使用 load-path 的特殊值。 如果您使用 site-load.el 或 site-init.el 文件来自定义转储的 Emacs(请参阅构建 Emacs),这些文件对加载路径所做的任何更改都将在转储后丢失。

Command: locate-library library &optional nosuffix path interactive-call ¶

此命令查找库库的精确文件名。 它以与 load 相同的方式搜索库,并且参数 nosuffix 与 load 中的含义相同:不要将后缀“.elc”或“.el”添加到指定的名称库中。

如果路径不为零,则使用该目录列表而不是加载路径。

当从程序调用 locate-library 时,它将文件名作为字符串返回。 当用户以交互方式运行 locate-library 时,参数 interactive-call 为 t,这告诉 locate-library 在回显区域显示文件名。

Command: list-load-path-shadows &optional stringp ¶

这个命令显示了一个隐藏的 Emacs Lisp 文件的列表。 阴影文件是一个通常不会被加载的文件,尽管它位于加载路径上的目录中,因为在加载路径上较早的目录中存在另一个类似名称的文件。

例如,假设 load-path 设置为

("/opt/emacs/site-lisp" "/usr/share/emacs/23.3/lisp")

并且这两个目录都包含一个名为 foo.el 的文件。 然后 (require ‘foo) 永远不会将文件加载到第二个目录中。 这种情况可能表明 Emacs 的安装方式存在问题。

当从 Lisp 调用时,该函数会打印一条消息,列出被遮蔽的文件,而不是在缓冲区中显示它们。 如果可选参数 stringp 不为 nil,则它将阴影文件作为字符串返回。

如果 Emacs 是在支持原生编译的情况下编译的(参见 Lisp 到原生代码的编译),那么当通过搜索 load-path 找到“.elc”字节编译文件时,Emacs 将尝试寻找相应的“.eln”保存相应的本机编译代码的文件。 在 native-comp-eln-load-path 列出的目录中查找本机编译的文件。

Variable: native-comp-eln-load-path ¶

这个变量包含一个目录列表,Emacs 在其中查找本地编译的 ‘.eln’ 文件。 列表中非绝对的文件名被解释为相对于调用目录(请参阅操作系统环境)。 列表中的最后一个目录是系统目录,即 Emacs 构建和安装过程安装的带有“.eln”文件的目录。 在列表中的每个目录中,Emacs 在子目录中查找“.eln”文件,其名称由 Emacs 版本和取决于当前本地编译 ABI 的 8 字符散列构成; 此子目录的名称存储在变量 comp-native-version-dir 中。

16.4 加载非 ASCII 字符

当 Emacs Lisp 程序包含带有非 ASCII 字符的字符串常量时,这些常量可以在 Emacs 中表示为单字节字符串或多字节字符串(请参阅文本表示)。 使用哪种表示取决于如何将文件读入 Emacs。 如果通过解码读取成多字节表示,则 Lisp 程序的文本将是多字节文本,其字符串常量将是多字节字符串。 如果读取包含 Latin-1 字符(例如)的文件而不进行解码,则程序的文本将是单字节文本,其字符串常量将是单字节字符串。 请参阅编码系统。

在大多数 Emacs Lisp 程序中,非 ASCII 字符串是多字节字符串这一事实不应该引起注意,因为将它们插入单字节缓冲区会自动将它们转换为单字节。 但是,如果这确实产生了影响,您可以通过在局部变量部分写入“coding: raw-text”来强制将特定的 Lisp 文件解释为单字节文件。 使用该指示符,文件将无条件地解释为单字节。 这在对写为 ?vliteral 的非 ASCII 字符进行键绑定时可能很重要。

16.5 自动加载

自动加载工具允许您注册函数或宏的存在,但推迟加载定义它的文件。 对函数的第一次调用会自动加载适当的库,以便安装真实定义和其他相关代码,然后运行真实定义,就像它一直被加载一样。 自动加载也可以通过查找函数或宏的文档(参见文档基础)以及变量和函数名称的完成来触发(参见下面的按前缀自动加载)。

有两种方法可以设置自动加载函数:调用 autoload,以及在真正定义之前在源代码中编写“魔术”注释。 autoload 是自动加载的低级原语; 任何 Lisp 程序都可以随时调用 autoload。 对于与 Emacs 一起安装的包,魔术注释是使函数自动加载的最方便的方法。 这些注释本身没有任何作用,但它们充当命令 update-file-autoloads 的指南,该命令构造对 autoload 的调用并安排在构建 Emacs 时执行它们。

Function: autoload function filename &optional docstring interactive type ¶

该函数定义函数(或宏)命名函数,以便从文件名自动加载。 字符串文件名指定要加载的文件以获取函数的真实定义。

如果文件名不包含目录名或后缀 .el 或 .elc,则此函数坚持添加这些后缀之一,并且它不会从名称仅为文件名而没有添加后缀的文件加载。 (变量 load-suffixes 指定了所需的确切后缀。)

参数 docstring 是函数的文档字符串。 在对 autoload 的调用中指定文档字符串可以在不加载函数的真实定义的情况下查看文档。 通常,这应该与函数定义本身中的文档字符串相同。 如果不是,则函数定义的文档字符串在加载时生效。

如果 interactive 不为零,则表示可以交互调用函数。 这让 Mx 中的完成工作无需加载函数的真实定义。 这里没有给出完整的交互规范; 除非用户实际调用函数,否则不需要它,当这种情况发生时,是时候加载真正的定义了。

如果 interactive 是一个列表,则将其解释为该命令适用的模式列表。

您可以自动加载宏和键盘映射以及普通函数。 如果函数确实是宏,则将类型指定为宏。 如果函数确实是键映射,则将类型指定为键映射。 Emacs 的各个部分都需要知道这些信息,而无需加载真正的定义。

当前缀键的绑定是符号函数时,自动加载的键映射会在键查找期间自动加载。 对键盘映射的其他类型的访问不会发生自动加载。 特别是,当 Lisp 程序从变量的值中获取键映射并调用 define-key 时,不会发生这种情况。 即使变量名是相同的符号函数也不行。

如果 function 已经有一个非自动加载对象的非 void 函数定义,则此函数不执行任何操作并返回 nil。 否则,它会构造一个自动加载对象(请参阅自动加载类型),并将其存储为函数的函数定义。 自动加载对象具有以下形式:

(autoload filename docstring interactive type)

例如,

  (symbol-function 'run-prolog)
	   ⇒ (autoload "prolog" 169681 t nil)

在这种情况下,“prolog”是要加载的文件的名称,169681 是指 emacs/etc/DOC 文件中的文档字符串(参见文档基础),t 表示函数是交互式的,nil 表示它不是宏或键盘映射。

Function: autoloadp object ¶

如果 object 是自动加载对象,则此函数返回非 nil。 例如,要检查 run-prolog 是否定义为自动加载函数,请评估

(autoloadp (symbol-function 'run-prolog))

自动加载的文件通常包含其他定义,并且可能需要或提供一项或多项功能。 如果文件未完全加载(由于对其内容的评估错误),则在加载期间发生的任何函数定义或提供调用都将撤消。 这是为了确保下次尝试从该文件调用任何自动加载函数时将再次尝试加载该文件。 如果不是这样,那么文件中的某些函数可能由中止的加载定义,但由于缺少某些未成功加载的子例程而无法正常工作,因为它们在文件中稍后出现。

如果自动加载的文件未能定义所需的 Lisp 函数或宏,则会用数据“自动加载未能定义函数函数名”发出错误信号。

神奇的自动加载注释(通常称为自动加载 cookie)由单独一行的 ‘;;;###autoload’ 组成,就在其可自动加载源文件中函数的真正定义之前。 命令 Mx update-file-autoloads 将相应的自动加载调用写入 loaddefs.el。 (用作自动加载 cookie 的字符串和由 update-file-autoloads 生成的文件的名称可以从上述默认值更改,见下文。)构建 Emacs 加载 loaddefs.el 并因此调用 autoload。 mx make-directory-autoloads 更加强大; 它更新当前目录中所有文件的自动加载。

相同的魔术注释可以将任何类型的表单复制到 loaddefs.el 中。 魔术注释后面的形式被逐字复制,除非它是自动加载工具特别处理的形式之一(例如,通过转换为自动加载调用)。 未逐字复制的形式如下:

函数或类函数对象的定义:

defun 和 defmacro; 还有 cl-defun 和 cl-defmacro(参见 Common Lisp Extensions 中的参数列表)和 define-overloadable-function(参见 mode-local.el 中的注释)。 主要或次要模式的定义:

定义次要模式,定义全球化次要模式,定义通用模式,定义派生模式,easy-mmode-define-minor-mode,easy-mmode-define-global-mode,定义编译-模式和定义全局次要模式。 其他定义类型:

defcustom、defgroup、defclass(参见 EIEIO 中的 EIEIO)和 define-skeleton(参见 Autotyping 中的 Autotyping)。

您还可以使用魔术注释在构建时执行表单,而无需在加载文件本身时执行它。 为此,请将表单与魔术注释写在同一行。 由于它在注释中,因此在加载源文件时它什么也不做; 但是 Mx update-file-autoloads 将它复制到 loaddefs.el,它在构建 Emacs 时执行。

下面的例子展示了医生是如何准备用一个神奇的注释自动加载的:

;;;###autoload
(defun doctor ()
  "Switch to *doctor* buffer and start giving psychotherapy."
  (interactive)
  (switch-to-buffer "*doctor*")
  (doctor-mode))

这是在 loaddefs.el 中产生的内容:

(autoload 'doctor "doctor" "\
Switch to *doctor* buffer and start giving psychotherapy.

\(fn)" t nil)

双引号后的反斜杠和换行符是一种约定,仅在预加载的未编译的 Lisp 文件中使用,例如 loaddefs.el; 他们告诉 make-docfile 将文档字符串放在 etc/DOC 文件中。 请参阅构建 Emacs。 另请参阅 lib-src/make-docfile.c 中的注释。 当各种帮助函数(参见帮助函数)显示它时,文档字符串的使用部分中的“(fn)”被替换为函数的名称。

如果您使用非已知和公认的函数定义方法之一的异常宏编写函数定义,则使用普通的魔术自动加载注释会将整个定义复制到 loaddefs.el。 这是不可取的。 您可以通过编写以下代码将所需的自动加载调用放入 loaddefs.el 中:

;;;###autoload (autoload 'foo "myfile")
(mydefunmacro foo
  ...)

您可以使用非默认字符串作为自动加载 cookie,并将相应的自动加载调用写入名称与默认 loaddefs.el 不同的文件中。 Emacs 提供了两个变量来控制它:

Variable: generate-autoload-cookie ¶

这个变量的值应该是一个字符串,它的语法是一个 Lisp 注释。 Mx update-file-autoloads 将跟随 cookie 的 Lisp 表单复制到它生成的自动加载文件中。 此变量的默认值为“;;;###autoload”。

Variable: generated-autoload-file ¶

这个变量的值命名了一个 Emacs Lisp 文件,自动加载调用应该去的地方。 默认值为 loaddefs.el,但您可以覆盖它,例如,在 .el 文件的局部变量部分(请参阅文件局部变量)。 假定自动加载文件包含以换页符开头的预告片。

以下函数可用于显式加载由自动加载对象指定的库:

Function: autoload-do-load autoload &optional name macro-only ¶

该函数执行 autoload 指定的加载,应该是一个 autoload 对象。 可选参数名称,如果非零,应该是一个函数值为自动加载的符号; 在这种情况下,此函数的返回值是符号的新函数值。 如果可选参数 macro-only 的值为宏,则此函数避免加载函数,仅加载宏。

16.5.1 按前缀自动加载

在命令 describe-variable 和 describe-function 完成期间,Emacs 将尝试加载可能包含与正在完成的前缀匹配的定义的文件。 变量定义前缀包含一个哈希表,它将前缀映射到相应的文件列表以为其加载。 此映射的条目是通过调用由 update-file-autoloads 生成的 register-definition-prefixes 添加的(请参阅 Autoload)。 不包含任何值得加载的定义的文件(例如测试文件)应将 autoload-compute-prefixes 设置为 nil 作为文件局部变量。

16.5.2 何时使用自动加载

除非确实有必要,否则不要添加自动加载注释。 自动加载代码意味着它始终是全局可见的。 一旦一个项目被自动加载,就没有兼容的方式来转换回它不被自动加载(在人们习惯于能够在没有显式加载的情况下使用它之后)。

最常见的自动加载项是库的交互式入口点。 例如,如果python.el是一个定义了用于编辑Python代码的major-mode的库,则自动加载python-mode函数的定义,这样人们就可以简单地使用Mx python-mode来加载该库。 变量通常不需要自动加载。 一个例外是,如果变量本身通常很有用,而无需加载整个定义库。 (这方面的一个例子可能是 find-exec-terminator。) 不要自动加载用户选项,以便用户可以设置它。 永远不要添加自动加载注释以使另一个文件中的编译器警告静音。 在产生警告的文件中,使用 (defvar foo) 使未定义的变量警告静音,并使用 declare-function(请参阅告诉编译器已定义函数)使未定义的函数警告静音; 或要求相关图书馆; 或使用显式自动加载语句。

16.6 重复加载

您可以在 Emacs 会话中多次加载给定文件。 例如,在通过在缓冲区中编辑函数定义并重新安装函数定义后,您可能希望返回到原始版本; 您可以通过重新加载它来自的文件来做到这一点。

当您加载或重新加载文件时,请记住 load 和 load-library 函数会自动加载字节编译的文件,而不是类似名称的非编译文件。 如果你重写了一个你打算保存并重新安装的文件,你需要对新版本进行字节编译; 否则 Emacs 将加载旧的、字节编译的文件,而不是新的、未编译的文件! 如果发生这种情况,加载文件时显示的消息包括“(已编译;注意,源较新)”,以提醒您重新编译它。

在 Lisp 库文件中编写表单时,请记住该文件可能会被多次加载。 例如,考虑在重新加载库时是否应该重新初始化每个变量; 如果变量已经初始化,defvar 不会更改值。 (请参阅定义全局变量。)

将元素添加到 alist 的最简单方法是这样的:

(push '(leif-mode " Leif") minor-mode-alist)

但是,如果重新加载库,这将添加多个元素。 为避免此问题,请使用 add-to-list(请参阅修改列表变量):

(add-to-list 'minor-mode-alist '(leif-mode " Leif"))

有时你会想要明确地测试一个库是否已经被加载。 如果库使用 provide 来提供命名功能,您可以在文件的前面使用 featurep 来测试之前是否执行过提供调用(请参阅功能)。 或者,您可以使用以下内容:

(defvar foo-was-loaded nil)

(unless foo-was-loaded
  execute-first-time-only
  (setq foo-was-loaded t))

16.7 特征

调用一个特定的函数,但是当另一个程序第一次通过名称请求它时加载一个特性。

功能名称是代表函数、变量等集合的符号。定义它们的文件应提供该功能。 另一个使用它们的程序可以确保它们是通过要求该特性来定义的。 如果尚未加载定义文件,则会加载它。

要要求存在功能,请使用功能名称作为参数调用 require。 require 查看全局变量 features 以查看是否已经提供了所需的功能。 如果没有,它会从相应的文件中加载该功能。 该文件应在顶层调用提供以将功能添加到功能; 如果它没有这样做, require 会发出错误信号。

例如,在 idlwave.el 中,idlwave-complete-filename 的定义包括以下代码:

(defun idlwave-complete-filename ()
  "Use the comint stuff to complete a file name."
   (require 'comint)
   (let* ((comint-file-name-chars "~/A-Za-z0-9+@:_.$#%={}\\-")
	    (comint-completion-addsuffix nil)
	    ...)
	 (comint-dynamic-complete-filename)))

如果文件 comint.el 尚未加载,则表达式 (require ‘comint) 会加载文件,确保定义了 comint-dynamic-complete-filename。 功能通常以提供它们的文件命名,因此不需要为 require 提供文件名。 (请注意,要求语句位于 let 的主体之外很重要。在其变量为 let 绑定时加载库可能会产生意想不到的后果,即变量在 let 退出后变得未绑定。)

comint.el 文件包含以下顶级表达式:

(provide 'comint)

这会将 comint 添加到全局功能列表中,因此 (require ‘comint) 将从此知道无需执行任何操作。

当在文件的顶层使用 require 时,它​​会在您对该文件进行字节编译(请参阅字节编译)以及加载它时生效。 这是为了防止所需的包包含字节编译器必须知道的宏。 它还避免了对使用 require 加载的文件中定义的函数和变量的字节编译器警告。

尽管在字节编译期间会评估对 require 的顶级调用,但不会对提供调用进行评估。 因此,您可以确保在对定义文件进行字节编译之前加载定义文件,方法是在提供相同功能的同时包含一个要求,如下例所示。

(provide 'my-feature)  ; Ignored by byte compiler,
			 ;   evaluated by load.
(require 'my-feature)  ; Evaluated by byte compiler.

编译器忽略提供,然后通过加载相关文件来处理需求。 加载文件确实会执行 provide 调用,因此在加载文件时后续的 require 调用不会执行任何操作。

Function: provide feature &optional subfeatures ¶

此函数宣布该功能现在已加载或正在加载到当前 Emacs 会话中。 这意味着与功能相关的设施已经或将可用于其他 Lisp 程序。

调用 provide 的直接效果是如果 feature 不在该列表中,则将 feature 添加到 features 的前面,并调用任何等待它的 eval-after-load 代码(请参阅 Hooks for Loading)。 参数特征必须是符号。 提供退货功能。

如果提供,子功能应该是一个符号列表,指示此版本功能提供的一组特定子功能。 您可以使用 featurep 测试子功能的存在。 子功能的想法是,当包(这是一个功能)足够复杂时,您可以使用它们,以便为包的各个部分或功能命名有用,这些部分或功能可能会或可能不会被加载,或者可能会或可能不会出现在给定的版本中。 例如,请参阅测试网络功能的可用性。

  features
	   ⇒ (bar bish)

  (provide 'foo)
	   ⇒ foo
  features
	   ⇒ (foo bar bish)

当一个文件被加载以满足自动加载,并且由于对其内容的评估错误而停止时,加载期间发生的任何函数定义或提供调用都将被撤消。 请参阅自动加载。

Function: require feature &optional filename noerror ¶

该函数检查当前 Emacs 会话中是否存在特性(使用 (featurep feature);见下文)。 参数特征必须是符号。

如果该功能不存在,则 require 使用 load 加载文件名。 如果未提供文件名,则将符号特征的名称用作要加载的基本文件名。 但是,在这种情况下,require 坚持要查找添加了 ‘.el’ 或 ‘.elc’ 后缀的功能(可能使用压缩后缀进行扩展); 不会使用名称只是功能的文件。 (变量 load-suffixes 指定了所需的确切 Lisp 后缀。)

如果 noerror 不为零,则抑制文件实际加载的错误。 在这种情况下,如果加载文件失败,require 返回 nil。 通常,需要返回功能。

如果加载文件成功但未提供功能,则 require 会发出有关缺少功能的错误信号。

Function: featurep feature &optional subfeature ¶

如果在当前 Emacs 会话中提供了 feature(即,如果 feature 是 features 的成员),则此函数返回 t。如果 subfeature 不是 nil,则仅当也提供了该 subfeature 时,该函数才返回 t(即,如果subfeature 是特征符号的 subfeature 属性的成员。)

Variable: features ¶

此变量的值是符号列表,这些符号是当前 Emacs 会话中加载的功能。 每个符号都被放入此列表中,并调用提供。 特征列表中元素的顺序并不重要。

16.8 哪个文件定义了某个符号

Function: symbol-file symbol &optional type ¶

此函数返回定义符号的文件的名称。 如果 type 为 nil,那么任何类型的定义都是可以接受的。 如果 type 是 defun、defvar 或 defface,则仅指定函数定义、变量定义或面定义。

该值通常是绝对文件名。 如果定义不与任何文件关联,它也可以为 nil。 如果 symbol 指定了一个自动加载的函数,该值可以是一个相对文件名,不带扩展名。

符号文件的基础是变量加载历史中的数据。

Variable: load-history ¶

这个变量的值是一个列表,它将加载的库文件的名称与它们定义的函数和变量的名称以及它们提供或需要的特性相关联。

此列表中的每个元素都描述了一个已加载的库(包括在启动时预加载的库)。 它是一个列表,其 CAR 是库的绝对文件名(字符串)。 其余列表元素具有以下形式:

var

符号 var 被定义为一个变量。

(defun . fun)

定义了函数 fun。

(t . fun)

在此库将其重新定义为函数之前,函数 fun 以前是自动加载的。 以下元素总是 (defun . fun),表示将 fun 定义为函数。

(autoload . fun)

函数 fun 被定义为自动加载。

(defface . face)

面面被定义。

(require . feature)

该功能特性是必需的。

(provide . feature)

提供了功能特性。

(cl-defmethod method specializers)

命名方法是通过使用 cl-defmethod 定义的,并以 specialters 作为它的specialters。

(define-type . type)

类型类型已定义。

load-history 的值可能有一个 CAR 为 nil 的元素。 此元素描述了使用 eval-buffer 在不访问文件的缓冲区上所做的定义。

命令 eval-region 更新 load-history,但这样做是通过将定义的符号添加到正在访问的文件的元素中,而不是替换该元素。 见评估。

16.9 卸载

您可以丢弃库加载的函数和变量,为其他 Lisp 对象回收内存。 为此,请使用函数 unload-feature:

Command: unload-feature feature &optional force ¶

此命令卸载提供功能特性的库。 它使用 defun、defalias、defsubst、defmacro、defconst、defvar 和 defcustom 取消定义该库中定义的所有函数、宏和变量。 然后它会恢复以前与这些符号关联的任何自动加载。 (加载会将这些保存在符号的自动加载属性中。)

在恢复之前的定义之前,unload-feature 运行 remove-hook 以从某些挂钩中删除库定义的函数。 这些钩子包括名称以“-hook”(或已弃用的后缀“-hooks”)结尾的变量,以及 unload-feature-special-hooks 和 auto-mode-alist 中列出的变量。 这是为了防止 Emacs 停止运行,因为重要的钩子引用了不再定义的函数。

标准卸载活动还撤消该库中函数的 ELP 分析,取消提供该库提供的任何功能,并取消保存在该库定义的变量中的计时器。

如果这些措施不足以防止故障,库可以定义一个名为 feature-unload-function 的显式卸载程序。 如果该符号被定义为函数,则 unload-feature 在执行任何其他操作之前不带参数调用它。 它可以做任何适当的事情来卸载库。 如果它返回 nil,则 unload-feature 继续执行正常的卸载操作。 否则它认为工作已经完成。

通常, unload-feature 拒绝卸载其他已加载库所依赖的库。 (如果 a 包含对 b 的要求,则库 a 依赖于库 b。)如果可选参数 force 不为零,则忽略依赖关系,您可以卸载任何库。

unload-feature 函数是用 Lisp 编写的; 它的动作基于可变负载历史。

Variable: unload-feature-special-hooks ¶

此变量保存在卸载库之前要扫描的挂钩列表,以删除库中定义的函数。

16.10 装载钩子

您可以通过使用变量 after-load-functions 来请求每次 Emacs 加载库时执行代码:

Variable: after-load-functions ¶

加载文件后运行此异常挂钩。 挂钩中的每个函数都使用一个参数调用,即刚刚加载的文件的绝对文件名。

如果您希望在加载特定库时执行代码,请使用 with-eval-after-load 宏:

Macro: with-eval-after-load library body… ¶

该宏安排在加载文件库结束时评估正文,每次加载库时。 如果库已经加载,它会立即评估 body。

您不需要在文件名库中提供目录或扩展名。 通常,您只需提供一个裸文件名,如下所示:

(with-eval-after-load "js" (define-key js-mode-map "\C-c\C-c" 'js-eval))

要限制哪些文件可以触发评估,请在库中包含目录或扩展名或两者。 只有绝对真实名称(即,所有符号链接被排除的名称)与所有给定名称组件匹配的文件才会匹配。 在以下示例中,某个目录 …./foo/bar 中的 my_inst.elc 或 my_inst.elc.gz 将触发评估,但不会触发 my_inst.el:

(with-eval-after-load "foo/bar/my_inst.elc" …)

library 也可以是一个特征(即,一个符号),在这种情况下,body 会在调用(provide library)的任何文件的末尾进行评估。

正文中的错误不会撤消加载,但会阻止正文其余部分的执行。

通常,精心设计的 Lisp 程序不应该使用 with-eval-after-load。 如果您需要检查和设置另一个库中定义的变量(那些供外部使用的变量),您可以立即进行,无需等到库加载完毕。 如果您需要调用该库定义的函数,则应加载该库,最好使用 require(请参阅功能)。

16.11 Emacs 动态模块

动态 Emacs 模块是一个共享库,它提供了用于 Emacs Lisp 程序的附加功能,就像用 Emacs Lisp 编写的包一样。

加载 Emacs Lisp 包的函数也可以加载动态模块。 他们通过查看文件扩展名来识别动态模块,也就是“后缀”。 这个后缀是平台相关的。

Variable: module-file-suffix ¶

此变量保存模块文件的文件扩展名的系统相关值。 它的值在 POSIX 主机上是 .so,在 macOS 上是 .dylib,在 MS-Windows 上是 .dll。

在 macOS 上,除了 .dylib 之外,动态模块还可以具有后缀 .so。

每个动态模块都应该导出一个名为 emacs_module_init 的 C 可调用函数,Emacs 将调用该函数作为 load 或 require 加载模块的调用的一部分。 它还应该导出一个名为 plugin_is_GPL_compatible 的符号,以表明其代码是在 GPL 或兼容许可下发布的; 如果您的程序尝试加载不导出此类符号的模块,Emacs 将发出错误信号。

如果一个模块需要调用 Emacs 函数,它应该通过在 Emacs 发行版的头文件 emacs-module.h 中定义和记录的 API(应用程序编程接口)来实现。 有关在编写自己的模块时使用该 API 的详细信息,请参阅编写动态加载的模块。

模块可以创建 user-ptr Lisp 对象,这些对象嵌入指向模块定义的 C 结构的指针。 这对于保留由模块创建的复杂数据结构非常有用,以便传递回模块的函数。 User-ptr 对象也可以有关联的终结器——当对象被 GC 时运行的函数; 这对于释放为底层数据结构分配的任何资源很有用,例如内存、打开的文件描述符等。请参阅 Lisp 和模块值之间的转换。

Function: user-ptrp object ¶

如果它的参数是一个 user-ptr 对象,这个函数返回 t。

Function: module-load file ¶

Emacs 调用这个低级原语从指定文件加载模块并执行模块的必要初始化。 这是确保模块导出 plugin_is_GPL_compatible 符号、调用模块的 emacs_module_init 函数并在该函数返回错误指示或用户在初始化期间键入 Cg 时发出错误信号的原语。 如果初始化成功,module-load 返回 t。 请注意,文件必须已经具有正确的文件扩展名,因为此函数不会尝试查找具有已知扩展名的文件,这与加载不同。

与 load 不同,module-load 不会在 load-history 中记录模块,不会打印任何消息,也不会防止递归加载。 因此,大多数用户应该使用 load、load-file、load-library 或 require 来代替 module-load。

在配置时使用 –with-modules 选项启用 Emacs 中的可加载模块。

马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/rookieagle/elisp.git
git@gitee.com:rookieagle/elisp.git
rookieagle
elisp
Elisp
main

搜索帮助