不论是在手机还是电脑上,我们在与人交流的过程中常常会形成或用到一些重复性的表述,这些表述的内容长度可多可少,但由于它们被用到频率十分之高,所以我们通常会更喜欢用类似于缩写的方式来代替我们一字一句输入的过程,例如说「yyds」(永远滴神)、「dddd」(懂的都懂)、「nbnhhsh」(能不能好好说话)等。

这些缩写或缩略语通常也可以被看作是用户自定义词典,我们不仅可以在操作设备上自定义,也可以在输入法上自定义。不过它们也仅限于定义一些纯文字性质的内容且长度有限,同时这些自定义设置可能会和特定的操作系统相绑定又或是无法完全导出,迁移成本存在过高的情况。

但有这么一类应用和自定义词典类似,它们为了用户自定义输入的缩写内容而生,不仅能定义纯文字性质的内容,还具备较强的扩展性,也被称为是文本扩展器(Text Expander)。

文本扩展器的作用表现和我们在使用自定义词典时的方式别无二致,都是输入一个特定的表达式,然后就会被替换成我们事先定义好的内容,比如:

--------------------------------->
[input]     #42
----------------------------------
[output]    意大利面拌 42 号混凝土
<---------------------------------

当我们在文本扩展器中事先设置完整内容之后,只要输入 #42 后就会转换成「意大利面拌 42 号混凝土」完整的句子。

知名的文本扩展器有 TextExpander,不过它属于付费应用也不是我们此次讨论的主角;而本文我们所介绍的则是另外一款免费、开源、跨平台且基于 Rust 语言编写的文本扩展器——Espanso,它不仅简单易用、体积小巧,且可配置性强,用户不但可以基于命令行配置扩展内容,而且还可以安装来自于社区的第三方扩展包。

boxcnEEZRbFYITVMbERmtA5skXf

快速上手

因为 Espanso 是一个开箱即用的工具,其安装步骤简单到可以一笔带过:打开 Espanso 官网,点击推荐的系统版本,下载并安装。

注:安装完成并启动 Espanso 时需要额外的操作授权,否则 Espanso 可能无法正确运行。

当然我们主要是看看如何使用 Espanso。毕竟安装完毕后启动 Espanso 是不会直接显示什么华丽的界面,因为它本质可以算是一个名不见经传的后台程序,只不过以应用的方式活跃在系统后台。

boxcnvkylAlfRHTF4cqSXxWljVf

使用 Espanso 的方式十分简单,我们只需在任意一个可以输入内容的地方,像输入代码一样先按下英文冒号「:」,然后再输入剩下的指令就可以神奇地看到 Espanso 已经为我们补全了内容。其中由 : 开始的一系列内容称为触发器(Trigger),而补全后的内容也被称为是匹配项(Match)。

比如我们现在输入 :date 的触发器指令那么 Espanso 直接会就帮我们将内容替换为表示当天日期的匹配项:

boxcn4z9bbRJj1XJMi8hI3LVT5e

就是这么简单!

不过你可能会好奇,我是怎么知道 :date 匹配项的呢?我们也可以在状态栏中找到 Espanso 的图表并点击「Open Search Bar」选项或是按下 Option+空格(Windows 上是 Alt+空格)就可以弹出相应的列表与搜索界面:

boxcnYRm0E57iPGjkPVIiJrsxAg

除此之外,因为我前面说了 Espanso 本质是一个后台程序,所以我们可以通过命令行来与之交互(可以在终端上通过 espanso --help 命令获取所有命令)。这里我们为了查看有哪些匹配项,可以直接使用 espanso match list 命令:

espanso match list
:espanso - Hi there!
:date - {{ date }}

可以看到 Espanso 并没有内置过多的匹配项,所以要想它能为我们更好地自动补全内容,就需要我们像自定义缩略语或短语一样事先进行人为配置。

Espanso 的配置主要有两部分,我们可以通过 espanso path 命令来找到配置文件路径:

espanso path
Config: /Users/Bobot/Library/Preferences/espanso
Packages: /Users/Bobot/Library/Preferences/espanso/match/packages
Runtime: /Users/Bobot/Library/Application Support/espanso

上述结果中的 Config 部分即为 Espanso 在当前操作系统中的配置存放路径。

当中主要包含了两部分配置:

$CONFIG/
  config/
    default.yml
  match/
    base.yml

其中:

  • config 部分的配置主要是针对 Espanso 程序本身,包括自定义补全的触发符号(比如将前面的英文冒号 : 更改为其他符号)、是否只在特定应用内 Espanso 才会生效等等;
  • match 部分的配置主要就是我们需要自定义补全的部分,也即 :date 这样的匹配项具体设置。

默认情况下 Espanso 是对所有应用都生效,因此当我们在应用中输入时很有可能就会极易触发匹配项弄巧成拙,为了避免冲突我们需要在后续在 config 部分进行配置以进行过滤。

不论是 Espanso 本身的配置还是自定义匹配项,Espanso 都采用了目前主流的 YAML 配置文件,我们可以直接通过系统自带的文本编辑器打开。

但是由于 YAML 的格式有些特殊,对于语法格式有严格要求,所以建议你使用 VS Code 这类编程专用的编辑器并在当中安装对应的 YAML 插件来进行编辑、配置;除此之外,可能你还需要简单学习一下 YAML 语法 才能更好地自定义相关配置内容。

现在就让我们来试着定义一组触发器与匹配项练习一番,这里我就使用 VS Code 来进行操作。

首先通过前面的命令我们找到了 Espanso 的配置文件夹后点击并拖拽至 VS Code,然后我们可以在 match/base.yml 默认匹配项的基础上进行设置,也可以像我一样新建一个名为 custom.yml 的匹配项进行设置:

boxcn1wNKUgX8quM615tvTAe6Ph

接着在当中填入如下内容(注意:如果你是在默认的 base.yml 文件中进行设置,由于已经存在了 matches 键就不需要重复填写):

# custom.yml

matches:
  - trigger: ":tomorrow"
    replace: "{{mytime}}"
    vars:
      - name: mytime
        type: date
        params:
          format: "%Y-%m-%d"
          offset: 86400

  - trigger: ":yesterday"
    replace: "{{mytime}}"
    vars:
      - name: mytime
        type: date
        params:
          format: "%Y-%m-%d"
          offset: -86400

当中的内容是由 YAML 语法构成的配置内容,其中每一组由「-」符号并紧接着 trigger 开始到下一个 trigger 前的一整块内容都表示定义一个匹配项,里面包括了触发器以及对应的替换内容等等,关于这部分配置我们在后续深入展开,现在暂时按下不表。

检测到有文件修改或保存后 Espanso 会自动重新加载配置文件,之后当我们输入 :tomorrow:yesterday 时,就会看到 Espanso 帮我们进行了自动补全:

boxcnPYVSOsu0php55cV9f9dAFg

以上仅是对 Espanso 管中窥豹一番,虽然它用法简单,但如果我们想要最大程度地发挥它的作用,可能还有更多用法有待我们进一步探索,所以接下来就让我们再深入了解一下 Espanso 的相关细节。

深入了解 Espanso

Espanso 相关配置

Espanso 的设置默认会作用到所有应用,但这通常可能会导致一些意想不到地冲突,所以我们最好需要对其进行调整。

只在特定应用中生效

Espanso 的默认配置为 config/default.yml 文件,但我们也可以针对不同应用在同一层级下添加不同的配置文件,比如 config/vscode.yml。之后我们可以在 vscode.yml 文件中添加过滤选项,即不让 Espanso 在使用 VS Code 时生效:

# vscode.yml

filter_class: "com.microsoft.VSCode"
enable: false

当我写入这短短地两行内容并保存之后,Espanso 就不会在我使用 VS Code 时生效从而避免了冲突。

Espanso 提供了三种过滤应用的方式,包括上述示例即 filter_titlefilter_exec 以及 filter_class,它们也分别对应了按当前活跃窗口的文本标题过滤、按应用的可执行路径过滤以及按当前活跃窗口的类名(编程概念)过滤。

这三种方式默认使用正则表达式,倘若你不清楚具体的内容,那你就可以填写正则表达式:

filter_exec: "Chrome$"
enable: false

当然你也可能会好奇我前面的例子是如何找到 VS Code 对应的类名 com.microsoft.VSCode

因为 Espanso 内置了一个探测指令可以一次性帮我们生成对应三种过滤方式的内容。在未屏蔽 Espanso 的情况下,我们可以像使用匹配项那样输入 #detect# 来进行触发,比如我在 macOS 的微信随便选择一个对话框,然后输入该指令即可弹出如下界面。

boxcn7sN0WGrytICEEDUfdfLb2b

之后我们只需要点击拷贝到剪切板再自行粘贴选择即可。

建议你在配置时侧重选择除 filter_title 之外的另外两种方式,因为当前活跃窗口的标题文本内容变化幅度较大并不是很稳定。

但除了像上述示例那样直接使用 enable: false 这样简单粗暴的屏蔽方式之外,Espanso 也提供了另外一种模式,让你可以自主选择在特定应用中只屏蔽(exclude)掉对应的匹配项。比如我现在排除掉我前面自定义的 custom.yml 文件:

filter_class: "com.microsoft.VSCode"
extra_excludes:
  - "../match/custom.yml"

默认情况下的路径为当前的 config 路径,而匹配项则是在与 config 同级的路径中,所以我们需要通过 ../match 的方式返回到同级目录然后再选择 match 目录。

保存之后在 VS Code 中即便使用 :tomorrow:yesterday 都不会得到相应的匹配项。

如果要屏蔽的内容过多,你既可以使用 glob 模式来设置路径,也可以换一种思路,即只让某些匹配项——比如代码片段(Snippets)——在特定应用中生效,比如我现在有一个 match/pycode.yml 配置,当中准备了一些 Python 相关的代码片段,现在我只想这些片段只在 VS Code 中生效,那么此时就应该这样做:

filter_class: "com.microsoft.VSCode"
extra_includes:
  - "../match/pycode.yml"

修改 Espanso 默认设置

尽管说 Espanso 有提供了基本的 UI 界面并驻留在状态栏中,但 Espanso 的设置都是需要通过配置文件来修改,这样的方式缺点很明显,就是对于一般用户而言缺少图形化配置界面还需要额外的学习成本;不过对于喜欢折腾的玩家来说也好处颇多,一方面你可以随时随地同步配置文件在,在不同操作系统之间也能保持配置统一;另一方面,你可以根据自己直接手动快速修改。

比如 Espanso 默认的搜索框界面可以用 Option+空格(Windows 为 Alt+空格)打开,那么我们也可以直接修改为 Option+Shift+空格 打开:

search_shortcut: OPTION+SHIFT+SPACE

当然我们也可以禁用搜索框快捷键的同时,自定义快速弹出 Espanso 匹配项的搜索框界面的触发器:

# default.yml

search_shortcut: off
search_trigger: ">esp"

之后我们只要敲击 >esp 就能令搜索框直接弹出。

除此之外,如果你不喜欢每次修改配置文件时 Espanso 就自动重载,那么你也可以关闭该选项并在每次配置之后手动点击状态栏的驻留程序并选择「Reload config」选项:

auto_restart: false

如果你觉得 Espanso 在扩展文本时的速度有点慢,那么也可以直接选择将 pre_paste_delay 选项尽量调低一些(默认为毫秒 ms),以便快速替换而不是保留肉眼可见的延迟:

pre_paste_delay: 100

更多的匹配项用法

Espanso 相比于其他文本扩展器或用户自定义词典最为突出的地方在于它的匹配项是灵活且可配置的,并且还能搭配终端的命令行工具一起使用得到最后的匹配结果,当然这都需要我们进行配置。

静态匹配项

最简单的匹配项配置就是 Espanso 默认自带的例子,它们通常是由 triggerreplace 一组内容来组成:

- trigger: ":espanso"
  replace: "Hi, there!"

像这样内容既定的部分和我们自定义词典没有什么区别,也被称为静态匹配项(Static Matches)。借助于 YAML 语法我们也可以编写长串的替换文本,即:

- trigger: ":espanso"
  replace: "Hi,\nthere!"
- trigger: ":espanso"
  replace: |
    Hi,
    there!

两种方法是等价的方式,你既可以手动指定换行符 \n,也可以使用 YAML 支持的长文本语法在 replace 键后面空格之后紧接着一个 | 符号来开启多行模式。

需要注意的是,我们上面虽然设置了多个同名触发器的匹配项,但彼此并不会冲突且 Espanso 在替换时会主动弹出搜索框让我们自动去选择使用那一组匹配项来作为最终替换内容。

boxcnDO8vSgHWdSBlZzaydLYdJe

默认情况下 Espanso 会直接将 replace 的部分作为匹配项的描述内容,这当我们存在多个同名触发器且内容近似的匹配项时会很容易造成困惑,所以 Espanso 支持我们为匹配项加上对应的描述信息(label 标签),以便能更好地知道当前触发器所得到的内容是什么:

# custom.yml

- trigger: ":espanso"
  replace: "Hi, there!"
  label: "inline content"
- trigger: ":espanso"
  replace: "Hi,\nthere!"
  label: "content with newline(\\n)"
- trigger: ":espanso"
  replace: |
    Hi,
    there!
  label: "content with newline"

之后我们再次触发 :espanso 时就会能看到不同匹配项所对应的不同描述内容是什么。

boxcnUkmhmCpKCypqO306AJpOUU

类似地,我们也可以让一个匹配项拥有多个触发器,这里就需要用到 YAML 的数组语法来设置,例如:

- trigger: [":espanso", ":greet"]
  replace: "Hi, there!"
  label: "inline content"

但如果你要设置的匹配项其触发器虽然比较多但表达类似,那么 Espanso 也支持你使用正则表达式的方式来进行设置,我们只将 trigger 替换成 regex 并使用正则表达式即可。我们可以直接将上述示例稍作修改:

- regex: ":(espanso|greet)"
  replace: "Hi, there!"
  label: "inline content"

动态匹配项

除了静态匹配项之外,Espanso 也支持动态匹配项,这也是让 Espanso 灵活且强大之处,我前面给出的明天和昨天日期的示例就是如此。因为这两个匹配项有些重复,这里我就以明天时间日期为例:

- trigger: ":tomorrow"
  replace: "{{mytime}}"
  vars:
    - name: mytime
      type: date
      params:
        format: "%Y-%m-%d"
        offset: 86400

在当前的匹配项之下我们还可以自定义变量,也即对应着 vars 之下的内容。变量一词就是编程中经常用来保存内容的基本单位,而 Espanso 允许我们沿用这样的设定直接为匹配项设置一连串的变量,之后动态地生成内容,而不是像静态匹配项那样一成不变。

所以在设置 :tomorrow 所对应的匹配项时,我自定义了一个 mytime 变量,这个变量的类型(type)是日期类型,然后又分别表示有 format 用于表示日期展示样式和 offset 日期偏移两个参数。

像日期类型这样的变量设置在 Espanso 中又被称为扩展(Extension)。所以除了日期类型之外,Espanso 还支持多种类型以适应动态匹配项的需要,如多选类型、随机类型、脚本类型、Shell 命令类型等等。

其中脚本类型和 Shell 命令类型有些类似,只不过前者主要是针对于逻辑更为复杂的脚本文件,它们通常由某种编程语言来编写,而后者主要就是适用于我们所示的终端命令行工具:

# custom.yml

- trigger: ":pynow"
  replace: "{{now}}"
  vars:
    - name: now
      type: script
      params:
        args:
          - python3
          - -c 
          - |
            from datetime import datetime
            print(datetime.now())

- trigger: ":sspai_title"
  replace: "{{title}}"
  vars: 
    - name: title
      type: shell
      params: 
        cmd: "curl -s https://sspai.com | htmlq --text 'title'"
        shell: zsh

上述定义的 :pynow:sspai_title 两个动态匹配项里分别用到了脚本类型和 Shell 命令类型,前者是执行 Python 解释器及其来输出最终结果,而后者则是通过系统内的 curlhtmlq 两个命令行工具来访问少数派官方并通过 HTML 获得最终的标题内容。

除了脚本类型和 Shell 命令类型之外,Espanso 还有一种类型——Form 表单——适用于多处动态内容生成的情况,通常可以被作为模板来使用,比如邮件回复模板、问题反馈模板等等:

- trigger: ":reply"
  form: |
    Hey [[name]],
      Thank you for your email.
      We have already try to handle the [[number]] issue.

在上述匹配项中我们没有 replace 键,取而代之的是 form,并且当中的动态内容部分则是由之前的花括号变成了方括号。这是 Espanso 对于表单内容的一个简写方式,本质上等价于定义变量的方式:

- trigger: ":reply"
  replace: |
    Hey {{template.name}},

      Thank you for your email.
      We have already try to handle the {{template.number}} issue.
  vars:
    - name: template
      type: form
      params:
        layout: |
          Hey [[name]],

            Thank you for your email.
            We have already try to handle the [[number]] issue.

所以当我们配置完毕并保存后,输入 :reply 时会直接弹出 Espanso 为我们准备好的表单界面,我们只需填好其中的部分即可快速生成模板内容。

boxcn7rhNm2JgN8FykQcWXRtXOd

但 Espanso 也还支持某个匹配项在其他匹配项中被使用或引用,极大地提高了灵活性与复用性。比方说我们现在再为这个模板自动带上一个当前回复日期:

Hey 100gle,

  Thank you for your email.
  We have already try to handle the #314 issue.

  2022-12-12

那么我们就可以使用这种引用方式:

- trigger: ":today"
  replace: "{{today}}"
  vars:
    - name: today
      type: date
      params:
        format: "%Y-%m-%d"

- trigger: ":reply"
  replace: |
    Hey {{template.name}},

      Thank you for your email.
      We have already try to handle the {{template.number}} issue.
    
      {{today}}
  vars:
    - name: today
      type: match
      params:
        trigger: ":today"
    - name: template
      type: form
      params:
        layout: |
          Hey [[name]],

            Thank you for your email.
            We have already try to handle the [[number]] issue.

:reply 匹配项的变量中,我们会定义一个 today 变量,它的参数类型是特殊的 match 匹配项,然后当中的 trigger 参数与我们 :today 匹配项的触发器一致。

这样只要我们填写完姓名与问题 ID 之后就会在模板中还自动生成当前的回复日期。

安装社区第三方扩展包

自定义匹配项会是一个永无止境的过程,因为它会随着个人的需要而与日俱增。但人类知识是相通的,也有一些内容可能不仅个人会用到,在互联网某一隅的一群人也会用到,譬如说 Emoji

可你知道吗?在我们日常网络交流中经常使用的每一个 Emoji 表情,其实也都基本有一个相对应称呼(CLDR Short Name),这里我就直接引用 Unicode 官方给出的示例列表:

BrowserCLDR Short Name
😀grinning face
😃grinning face with big eyes
😄grinning face with smiling eyes

 

上述三个表情虽然都属于笑脸范畴,但是它们对应的 Unicode 编码不同,其文字称呼也不同;这也就意味着我们不仅能够通过 Unicode 编码来找到它们,同理文字称呼也适用。

所以你要是恰好知道某个 Emoji 的名称而又不像自己四处翻找,那么借助 Espanso 就可以直接类似 :grinning-face 以文字称呼得到 Emoji 表情。

Espanso 允许我们安装来自于社区其他人共享的第三方扩展包,它会类似于插件地形式来扩充我们的匹配项,可以让我们「站在巨人的肩膀」上去更好地使用 Espanso。我们可以直接到 Espanso 的官方 Hub 页面 去搜索。

比如我们直接就以前面提到的「Emoji」一词进行搜索就可以找到与之相关的扩展包:

boxcn0zHKrldbRpSPS1HT7CZOVf

但我们与扩展包相关的操作都需要通过命令行来完成:

espanso install all-emojis          # 安装 all-emojis 扩展包
espanso uninstall all-emojis        # 卸载 all-emojis 扩展包

espanso package list                # 查看 Espanso 中的所有扩展包
espanso package update all-emojis   # 更新 all-emojis 扩展包
espanso package update all          # 更新 Espanso 中的所有扩展包

安装完成之后我们就再次按照前面提到的查看匹配项的方式来检查是否安装成功。

boxcn0Knju2VM9rBWizJ5t197he

除上述方式之外 Espanso 还支持额外的安装方式,甚至你也可以自己去创建并分享一个扩展包,当然这些内容 Espanso 都在文档中找到详细说明,这里就不再额外赘述。

结尾

虽然 Espanso 在使用上直接了当并且功能强大,它不仅可以帮我们扩展静态的模板内容,也可以具备较强的扩展性,例如调用脚本或使用命令行来得到内容。

不过为了让它好用、易用可能还需要多花一点心思在配置上。受限于篇幅,本文仅介绍了 Espanso 中比较重要的一些配置内容;但像全局变量、导入外部配置、变量注入、如何分享自己的扩展包等等细节并没有一一涉及。

好在 Espanso 的官方文档已经给出了较为细致的说明,这些未提及的内容就留给你自行探索。

> 少数派请你做地图:城市声音收藏夹火热征集中,期待你创作的城市之声 🎧

> 下载少数派 2.0 客户端 、关注少数派公众号,解锁全新阅读体验 📰

> 实用、好用的正版软件,少数派为你呈现 🚀