进阶专题

命令直通车

让短语与词库条目在选中时执行动作——打开网址、启动程序、模拟按键、切换输入法状态

v0.122.0

命令直通车(Command Bar)把短语 / 用户词库 / 系统词库的条目从"展开成文本"升级为"执行带副作用的动作":打开 URL、启动程序、模拟按键、写入剪贴板、加词到用户库、切换输入法状态等,让输入法兼作快捷命令启动器。

如何触发

命令用一段英文字母编码触发:输入编码 → 候选区出现命令条目 → 选中即执行。

输入选中后
cobd用默认浏览器打开 baidu.com
cojk输出 「」,光标停在中间,可用跳出键越过
coac把剪贴板内容加入用户词库
coen切换中英文模式

系统短语包已内置一批以 co 开头的示例命令,可在 设置 → 词库 → 短语 中浏览、开关或仿写。

触发码只能是英文字母

数字键会被"选中第 N 候选"逻辑吃掉,中文标点在按键阶段就被转换,二者都进不了短语层。因此触发码只能用 ASCII 字母(如 cobd),既不能含数字,也不能用中文标点。想携带查询内容时,用 clip() / last() 等数据源,而不是把词跟在编码后面。

编写位置

命令短语可放在三处,都会自动解析:

位置作用范围入口
快捷短语全局,跨方案有效设置 → 词库 → 短语
用户词库仅当前方案设置 → 词库 → 方案 → 用户词库
系统词库仅当前方案设置 → 词库 → 方案 → 系统词库

推荐放快捷短语

绝大部分命令放快捷短语,拼音 / 五笔 / 混输下都可用。只有方案专属的命令才放用户词库。

四种标记

短语与词库条目共用一个 text 字段,靠内容形式区分类型。命令能力通过以 $ 开头的标记触发。

标记含义前缀输入时完整编码时
$CC(显示名, 动作...)命令显示(可关)显示
$CC1(显示名, 动作...)命令,顶层等同 $CC(但不能嵌进 $SS显示(可关)显示
$AA(组名, 字符串)字符组显示组名导航展开为 N 个单字符
$SS(组名, 元素...)数组(字符组的一般形式)显示组名导航展开为 N 个成员
cobd = $CC("打开百度", open("https://baidu.com"))
cobd = $CC1("打开百度", open("https://baidu.com"))
zzbd = $AA("标点", "、。·ˉˇ¨〃々—~‖…")
zzgo = $SS("常用站点", $CC("百度", open("https://baidu.com")), $CC("GitHub", open("https://github.com")))

`$CC` 与 `$CC1` 在顶层等价

早期 $CC 只在完整编码时出现、$CC1 才参与前缀列举。现在两者都参与:判据是 「没有显式写 {prefix: false}」,而 $CC1 只是把 prefix: true 显式写了出来。 不想让某条命令出现在前缀列表里,给它加 {prefix: false}

cobd = $CC("打开百度", open("https://baidu.com"), {prefix: false})

唯一的例外在 $SS 的元素位置:那里只能嵌 $CC,写 $CC1 会让整条短语解析失败、 在候选里直接消失且没有任何提示。组前缀由 $SS 统一控制,元素不得自带 prefix

另外前缀列举本身还受 input.phrase.min_prefix 约束(默认 2), 打得太短不会列。

一步失败就别往下跑:{on_error: "stop"} 0.121 新增

动作链默认是「前一步失败,后一步照跑」。想要「前一步成功才做下一步」,在同一个修饰符袋里写 on_error

coim = $CC("导入词库", wind.cli("dict import D:\\words.txt"), ui.toast("导入成功"), {on_error: "stop"})

不写时默认 continue,与历史行为一致——默认不能改成 stop:已有词条依赖「前一步失败后一步照跑」(比如剪贴板被占用时仍要把光标移回去)。

不写 stop 的代价是假成功:上面那条链里 wind.cli 失败了,后面那句「导入成功」照样弹。这个修饰符本就是用来防假成功的,所以值写错会直接报错,而不是落回默认——失灵时的表现恰好就是它要防的那件事。

$AA$SS 的字符串简写:$AA 自动按字符拆开,$SS 显式列举每个元素,元素既可是字符串(选中上屏),也可是嵌入的 $CC(...)(选中执行动作)。输入前缀(如 zz)看到组名导航条目,选中后自动补全到完整码进入二级选择;输入完整码(如 zzbd)直接展开为成员候选。

`$SS` 的元素只能是字符串或 `$CC(...)`

元素位置上的 $CC 不得带 prefix 修饰符——组前缀由 $SS 统一控制。所以 $CC1(...) (它等价于显式写了 prefix: true)和 $CC(..., {prefix: false}) 在这里都会让整条短语 解析失败,现象是打出编码后候选里什么都没有、也没有任何报错。要控制这一组要不要参与前缀 列举,把 {prefix: false} 写在 $SS 自己的末尾。

表达式语法

$CC / $CC1 内部是表达式,只有三种形态:

形态例子
字面量"hello"423.14
标识符lastnowcode(等价零参调用)
函数调用open("https://...")clip.copy("x")key.tap("Enter")
  • 字符串内插:字符串中用 {expr} 内插子表达式,如 $CC("百度搜 {clip()}", open("https://baidu.com/s?wd={url(clip())}"))
  • $$ 转义:要输出字面 $ 就写 $$,否则 $M 之类会被当模板变量替换。例如 text = "工资: $$M" 显示 工资: $M
  • . 仅作命名空间. 只用于函数名分隔(clip.copykey.tap),不能取属性或索引。用 last(1) 而非 last.1,用 len(code) 而非 code.length
  • 具名参数:部分函数接受 名字=值 形式的可选参数(目前是 proc.runcwd / verb / showproc.shellcwd,以及 ime.pairjump),如 proc.run("dict.exe", cwd="D:\\Dict")。规则三条:必须写在所有位置参数之后、同一个名字不能写两次、名字必须是该函数登记过的(写错会报错,不会被当没写过)。值和普通参数一样可以内插,如 cwd="{clip()}"

`k=v` 与 `{k: v}` 不是一回事

k=v函数的参数,管这一次调用;$CC(..., {prefix: false}) 那种花括号是短语的修饰符,管这条候选怎么显示。两者位置和作用对象都不同,写错地方会直接报错而不是悄悄生效。

路径里的反斜杠要写两个

字符串里的 \转义引导符,写 Windows 路径时必须写成 \\

# ✅ 正确
cotmp = $CC("[打开临时目录]", open("D:\\notes\\temp"))

# ❌ 错误:`\n` 被当成换行、`\t` 被当成 Tab
cotmp = $CC("[打开临时目录]", open("D:\notes\temp"))

被吃掉的是这几个:\n \t \r 分别变成换行、Tab、回车,\\ \" \' \{ \} \( \) \$ 变成对应的单个字符。其余组合(如 \我\x)原样保留反斜杠——这正是坑人的地方D:\我的文档 恰好能用,于是很容易以为单反斜杠没问题,直到某天路径里出现 \notes\tools\report,才发现路径被截断成了带换行/Tab 的怪字符串,而且看不出哪里错了。

别靠「试出来能用」判断

路径写单反斜杠时能否工作,取决于下一个字母恰好是不是转义字母。同一条短语换个目录名就可能失效,且失败时只弹一句「命令执行失败」,不会告诉你是反斜杠的问题。统一写 \\,不要逐条去试。

网络共享路径开头是四个

UNC 路径(\\服务器\共享名本身就以两个反斜杠开头,每个都要按上面的规则写成两个,所以开头一共是四个:

想访问的路径命令里写
\\nas\share\dict.exe\\\\nas\\share\\dict.exe
\\192.168.1.5\tools\\\\192.168.1.5\\tools
conas = $CC("[打开共享]", open("\\\\nas\\share"))

规则本身没变,仍然是「一个 \ 写成 \\」——只是 UNC 的开头恰好有两个。写成两个(\\nas)解析出来是单个反斜杠开头的 \nas,那不是 UNC 路径,打不开;而这同样属于上面说的「不报错,只是打不开」。

换个地方写,还是两个

输入法内部只有一份真实文本:库里存的就是输入时用的,存储层对转义一无所知。转义只发生在文本进出系统的边界上,一共三类:

边界方向做的事
词库 / 短语文件(方案的 .dict.yaml、导入的 TSV/TXT、备份的 .wdict.yaml读入、导出\\\\n→换行、\t→Tab,其余组合原样保留
设置 → 词库 的编辑框显示、保存与上一行互逆:库里的换行显示成 \n、制表显示成 \t,保存时再还原回去
候选窗只显示换行画成 、制表画成 ;上屏的仍是真换行

命令栏语法条目的反斜杠不参与这三层——$CC / $CC1 / $SS / $AA 以及含 {} 的模板,它们的 \ 由表达式解析独占,边界再转一次就成了双重展开。所以上面那条「写两个」在哪儿写都一样:设置页的输入框、方案的词库文件、配置分发包里内嵌的码表,写法完全相同,照搬不用换算。

纯文本词条没有表达式那一层,边界规则直接落到它身上:

想得到的内容词库文件 / 设置页里写
C:\Users\noteC:\\Users\\note
两行文本第一行\n第二行
带一个制表符列一\t列二

0.114 – 0.117 的命令短语要写四个

  • 这几个版本里,命令短语经设置页词库文件写入时会被多转义一层:路径分隔符得写四个反斜杠(open("D:\\\\notes"))才能真正得到一个。写进方案自己的 TOML 不过这一层,仍是两个——同一条命令在不同载体规则不同,正是该修的地方。
  • 各处统一为两个 0.118 新增:命令栏语法条目在边界上原样穿过。已经按四个写好的短语不用动——库里存的本来就是两个,升级后照常工作,只是设置页里显示的数量从四个变回两个(导出的文件同理)。

内部目录变量

不想硬编码安装位置或用户名(换台机器、换个盘符就失效),可以用 ${变量名} 引用输入法自己的目录:

变量指向典型位置
${APP_DIR}程序安装目录(wind_input.exe 所在目录)C:\Program Files\WindInput
${USER_DATA}漫游用户数据目录%APPDATA%\WindInput
${LOCAL_DATA}本机用户数据目录(不随漫游同步)%LOCALAPPDATA%\WindInput
# 打开安装目录
coad = $CC("[打开安装目录]", open("${APP_DIR}"))

# 上屏安装目录路径
cotd = $CC("[输出安装目录]", type("${APP_DIR}"))

# 拼接子目录 —— 反斜杠同样要写两个
colg = $CC("[打开日志]", open("${LOCAL_DATA}\\logs"))

变量在解析阶段就被替换成绝对路径,可用在任何字符串位置(动作参数、显示名、拼接片段)。这三个变量与 CLI 的路径参数完全一致,写法可以互相照搬。

不认识的 ${...} 会原样保留

只有上表三个是内部目录变量。写错名字(如 ${APPDIR})不会报错,而是原样留下字面的 ${APPDIR} ——因为 ${YC} 这类短语模板变量也用同样的写法,命令栏不能把它们当错误吞掉。所以路径打不开时,先核对变量名拼写。

若你确实想输出字面的 $ 且后面紧跟 {,写 \${...}

常用示例

# 打开网址
cobd = $CC("打开百度", open("https://baidu.com"))
cogh = $CC("打开 GitHub", open("https://github.com"))

# 搜索剪贴板 / 上次上屏内容
cobs = $CC("百度搜 · {clip()}", web.search("baidu", clip()))
cozd = $CC("汉典 · {last()}", open("https://www.zdic.net/hans/{url(last())}"))

# 配对符号(光标落两段之间,可用跳出键越过右段)
cojk = $CC("「」", ime.pair("「", "」"))

# 删除当前行
codl = $CC("[删行]", key.seq("Home", "Shift+End", "Backspace"))

# 启动程序
cono = $CC("打开记事本", proc.run("notepad.exe"))

# 加词到用户库(编码自动生成)
coac = $CC("加词 · {clip()}", dict.add(clip()))

# 计算剪贴板表达式
coca = $CC("={calc(clip())}", type(calc(clip())))

# 切换输入法状态
coen = $CC("切中英", ime.toggle("cn-en"))
cots = $CC("切主题", ime.theme_cycle())

# 字符组
zzbd = $AA("标点", "、。·ˉˇ¨〃々—~‖…")
zzsx = $AA("数学", "+-<=>±×÷∈∏∑")

内置函数参考

函数只有一种返回值:字符串——数字、布尔在这里也是字符串(len("abc") 得到 3,可以直接拼进文本),动作函数返回空串。记号:s 字符串、n 整数、参数后 ? 表示可选。

每张表先给速查,表后的小节讲具体怎么写。

四条通用规则

  • 索引从 1 起,按字符数。 code(2) / sub(s, 2) / split(s, ",", 2) / dict.rev(s, 2) 里的 2 都指第 2 个字符(第 2 段);汉字、emoji 各算一个。subsplit 还接受负数,-1 是最后一个。
  • 取不到就给空串,不报错。 索引越界(sub("abc", 9))、历史不够长(last(9))、剪贴板为空,结果都是空串。这是刻意的:$SS 里固定写 N 条各查一个字,取不满是常态,报错会让整条短语连带消失。
  • 写错才报错,而报错等于候选消失。 函数名不存在(含拼写、大小写写错)、参数个数不对、该给数字的位置给了别的,都算错误。错误在显示名里 → 打出编码后候选里什么都没有、也没有提示;错误在动作里 → 选中时提示命令执行失败。所以"编码打出来却看不到候选",第一嫌疑就是函数名或参数写错。
  • 显示名里只能用纯函数。 $CC 的第一个参数只接受取值 / 文本 / 计算类函数;写 openconfig.setkey.tap 这类动作函数会被判为不纯、整条失效——候选被列出来的时候不该有副作用发生。

显示与执行各求值一次

显示名在候选生成时求值,动作在选中那一刻才求值。所以 type(last()) 取到的是选中时的历史;而 uuid() 在显示名和动作里各写一次会得到两个不同的 ID——要让看到的和打出来的一致,就只在 type() 里写,显示名写死。

取值函数(纯函数)

函数说明
code() / code(n)触发时组合区里的编码(第 n 字符起到末尾)
tail(s, n)字符串 s 从第 n 字符起到末尾
last() / last(n)最近 / 倒数第 n 次上屏文本
clip()当前剪贴板文本
app() / title() / sel()前台进程名 / 窗口标题 / 选中文本——Windows 上均返回空串(仅 macOS 上报;其中 title() 还需 macOS 的辅助功能授权,未授权返回空串)
date(fmt) / date(fmt, offset)日期,见下
time() / time(fmt)时间,默认 "HH:mm:ss"见下
now()等价 date("YYYY-MM-DD HH:mm:ss")
env(name)环境变量,见下
uuid() / uuid(flags)随机 UUID(v4),见下
config.get(key)读取配置项当前值,key配置文件里的路径,如 ui.theme.name
dict.rev(s) / dict.rev(s, n) 0.117 新增反查:s 中第 n 个字(1 起,默认 1)的编码与读音,见下文

日期与时间:date / time / now

fmt 里下面这些字母是占位符,其余字符(- / : 等)原样输出。三个函数用的是同一套占位符,time 只是默认值不同、now 是写死的格式。

占位符含义2026-06-14 09:05:07 时
YYYY四位年2026
YY两位年26
MM月,补零06
M月,不补零6
DD日,补零14
D日,不补零14
HH时,24 小时制补零09
h时,12 小时制不补零9
mm分,补零05
m分,不补零5
ss秒,补零07
s秒,不补零7

没有星期、上午/下午、月份英文名的占位符。24 小时制补零只有 HH(没有 H),12 小时制只有 h(没有 hh)。

常用配方:

想要写法结果
今天date("YYYY-MM-DD")2026-06-14
中文日期date("YYYY年M月D日")2026年6月14日
明天 / 昨天date("YYYY-MM-DD", "+1d") / date("YYYY-MM-DD", "-1d")2026-06-15 / 2026-06-13
上个月date("YYYY-MM", "-1M")2026-05
文件名时间戳date("YYYYMMDD-HHmmss")20260614-090507
只要时间time() / time("HH:mm")09:05:07 / 09:05
日期加时间now()2026-06-14 09:05:07
codt = $CC("{date("YYYY-MM-DD")}", type(date("YYYY-MM-DD")))
cotm = $CC("{date("YYYY-MM-DD", "+1d")}", type(date("YYYY-MM-DD", "+1d")))

大小写就是两个不同的占位符

MM 是月、mm 是分;DD 是日、HH 是时。date("YYYY-mm-DD") 不会报错,它会把分钟填进月份的位置,得到 2026-05-14 这种看着像日期、其实每分钟都在变的东西。

格式串里别混英文单词

单个 M D h m s 也是占位符,所以格式串里写英文会被就地替换:date("Monday") 得到的是 6ondayM → 月份 6)。要固定的英文文字请用 concat() 拼在 date() 外面,或者只在格式串里放中文与标点。

`%` 是保留字符,不要写进格式串

格式串里的 % 会被直接交给底层的时间格式化器:%Y 这类恰好合法的会按 strftime 解释,而落单的 %(或 % 后跟着不认识的字母)会让求值当场崩溃——不是弹个错误,是输入法直接退出。想输出百分号,用 concat(date("YYYY"), "%") 拼在外面。

offset:把日期平移一段。 形如 "+1d",三部分缺一不可:

部分规则
符号+-必须写"1d" 会报错
数字整数。"+1.5d" 报错
单位d 天 / w 周 / M 月 / y 年,大小写敏感

单位只有这四个:M 是月、小写 m 不是分钟而是非法单位,也没有小时/分钟级的偏移——要平移时间只能靠 date(fmt, offset),而它最小的单位是天。

月和年按自然月平移,落到不存在的日期时夹到当月最后一天:3 月 31 日 -1M 得到 2 月 28 日(闰年 29 日)。

编码、历史与剪贴板:code / tail / last / clip

写法取到什么
code()触发这条候选时组合区里已输入的编码
code(n)同上,但从第 n 个字符切到末尾;n ≤ 1 等于全部,超出长度得空串
tail(s, n)code(n) 同规则,但作用于任意字符串
last() / last(1)最近一次上屏的文本
last(2) / last(3)上上次 / 上上上次;n < 1 或超出历史长度得空串
clip()当前系统剪贴板里的纯文本;剪的是图片或剪贴板为空时得空串

历史只记真正上屏的文本,纯副作用命令(open / proc.run 等)不进历史,所以 last() 不会被命令自己污染(见行为细节)。

last() 与前台信息在命令触发的那一刻冻结成快照,之后动作里再读也是同一份。

环境变量与随机 ID:env / uuid

env(name) 读输入法进程的环境变量,读不到返回空串(不报错):

couser = $CC("{env("USERNAME")}", type(env("USERNAME")))
cohome = $CC("[打开用户主目录]", open(env("USERPROFILE")))

新加的环境变量要重启输入法才可见

进程的环境变量在启动时就固定了。刚在系统属性里新建的变量,输入法要退出重开才读得到。输入法自己的目录别用 env() 猜,直接用 ${APP_DIR} 一类的内部目录变量

uuid() 生成随机 UUID(v4),flags 控制形态,字母不区分大小写:

写法结果形如
uuid()9f1c2b7e-3a4d-4f65-8c21-0d5e7a9b1234
uuid("n")9f1c2b7e3a4d4f658c210d5e7a9b1234(去横杠)
uuid("u")9F1C2B7E-3A4D-...(转大写)
uuid("nu")两者组合

只认 nu,写别的字母(uuid("x"))会报错、候选消失——这是刻意的,静默忽略会让人以为"格式没生效"。短语层的 $uuid 模板变量走的是同一套实现。

文本处理(纯函数)

函数说明
len(s)字符数(按 rune)
upper(s) / lower(s)转大写 / 小写
trim(s) / trim(s, chars)去首尾空白 / 去首尾的指定字符
sub(s, start) / sub(s, start, end)切片,索引 1 起,双闭区间,支持负数
replace(s, old, new)字面替换(全部出现处)
regex(s, pat, rep)正则替换,见下
split(s, sep, n)按 sep 拆分取第 n 段(1 起,支持负数)
concat(...)拼接任意个片段
reverse(s)反转(按 rune)
url(s) / html(s) / json(s) / base64(s)URL / HTML / JSON / Base64 编码,见下
default(s, fallback)s 为空时返回 fallback

照着抄的例子(左边是写法,右边是结果):

写法结果
len("清风输入法")5
upper("Wind") / lower("Wind")WIND / wind
trim(" 两边有空格 ")两边有空格
trim("**重点**", "*")重点
sub("abcdef", 3)cdef
sub("abcdef", 2, 4)bcd
sub("abcdef", -2)ef
sub("abcdef", 1, -2)abcde
replace("2026/06/14", "/", "-")2026-06-14
split("张三,李四,王五", ",", 2)李四
split("a-b-c", "-", -1)c
concat("【", clip(), "】")剪贴板内容加书名号
reverse("abc")cba
default(clip(), "(先复制点东西)")剪贴板为空时给出提示语

trim(s, chars) 的第二个参数是字符集合不是子串:trim("xxabcxx", "x")trim("xxabcxx", "xy") 结果相同,都是 abc

正则替换:regex(s, pat, rep)

pat 用 Rust regex 语法(与 RE2 同族):\d \w \s[[:alpha:]]+ * ? {n,m}|、分组都支持,不支持反向引用和环视(?<=...)\1)。

  • 模式里的反斜杠按字符串规则写两个\\d 才是一个 \d(见路径里的反斜杠要写两个
  • 量词的大括号要转义成 \{ \}{ 在字符串里是插值语法"\\d{3}" 会被求值成 \d3——正则悄悄变成了另一个意思,还不报错
  • 替换串里用 ${1} ${2} 引用捕获组。${...} 不会被当插值(那是内部目录变量的写法,名字不认识就原样保留),可以照写;$1 也认,但后面紧跟字母数字时会被吞进组名,统一写 ${1} 更稳
  • 匹配处全部替换,没有"只换第一处"的开关
  • 正则本身写错(括号不配对等)→ 求值报错 → 候选消失

匹配换行要用 `\r?\n`,不能用 `\n`

clip() 从 Windows 剪贴板取到的文本,换行通常是 \r\n 两个字符(记事本、Word、浏览器复制都是这样);而 last()code 以及你自己写在字符串里的换行,往往只有 \n 一个。同一条正则要两种来源都能用,只有 \r?\n 一种写法:

模式\r\n 来源(剪贴板居多)\n 来源
"\n"❌ 只吃掉 \n剩下的 \r 原样上屏
"\r\n"一处都匹配不上,原文照抄
"\r?\n"

第一行剩下的那个 \r,在多数编辑器里既不显示也不换行,看上去就是「替换好像没生效」或者「行首多了个看不见的东西」。撞上之后最顺手的自救是把 \n 改成 \r\n——那恰好掉进第二行那个更隐蔽的坑:换个来源就一个都匹配不上,而且同样不报错。

这一条不受上面「反斜杠写两个」的约束"\r?\n""\\r?\\n" 结果完全相同(前者由字符串转义直接生成真的回车 / 换行字符,后者原样交给正则引擎去解释),写哪个都行。

# 把连续数字换成 #
como = $CC("[数字打码]", type(regex(clip(), "\\d+", "#")))

# 日期换成中文写法(捕获组用 ${1})
cocn = $CC("[日期转中文]", type(regex(clip(), "(\\d+)-(\\d+)-(\\d+)", "${1}年${2}月${3}日")))

# 手机号中间四位打码:注意量词的大括号写成 \{3\}
comask = $CC("[手机号打码]", type(regex(clip(), "(\\d\{3\})\\d\{4\}(\\d\{4\})", "${1}****${2}")))

# 多行粘贴合成一行:\r?\n 才能同时吃掉 \r\n 和 \n
cojoin = $CC("[多行并一行]", type(regex(clip(), "\\r?\\n", "、")))

四个编码函数:url / html / json / base64

写法结果用在哪
url("清风 输入")%E6%B8%85%E9%A3%8E+%E8%BE%93%E5%85%A5拼进查询串。空格变 +(query 编码),拼进路径段要留意
html("<b>&")&lt;b&gt;&amp;往 HTML 里贴文本
json("a\"b")"a\"b"带外层引号,出来的是一个完整的 JSON 字符串字面量
base64("hi")aGk=标准 Base64(带 = 补位)

url() 正是搜索类命令的关键一环——open("https://www.zdic.net/hans/{url(last())}") 里少了它,含空格或标点的词就会把 URL 拼坏。

计算与内省

函数说明
calc(expr)数学表达式求值,见下
num(s, base)进制转换(2/8/10/16),见下
help(name)返回指定函数的一行简介,查不到返回空串

算术:calc(expr)

支持 + - * / %(取余)、圆括号、一元正负号、小数与空格。

不支持:乘方 ^、科学计数法 1e3、千分位逗号、全角符号((1+2))、sin 之类的函数、变量。碰到不认识的字符就报错,整条候选消失。

写法结果
calc("1+2*3")7
calc("(1+2)*3")9
calc("6/2")3(整数结果不带 .0
calc("1/3")0.3333333333333333
calc("10%3")1
calc("")空串(不报错)
calc("1/0")报错,候选消失

最后两行是配剪贴板算式用的:calc(clip()) 在没复制东西时静默给空串,所以出厂那条 coca = $CC("={calc(clip())}", type(calc(clip()))) 只会显示一个 =,而不是整条消失。

进制:num(s, base)

s 的进制由前缀自动识别,base输出进制:

写法结果
num("0xff", 10)255
num("255", 16)ff(小写,不带 0x
num("10", 2)1010
num("0b1011", 10)11
num("0o17", 10)15
num("-255", 10)-255

输入前缀:0x/0X 十六进制、0o/0O 八进制、0b/0B 二进制、无前缀按十进制,可带正负号,首尾空格自动去掉。base 只能是 2 / 8 / 10 / 16,其它值报错;只处理整数,num("3.5", 10) 报错。

负数只在转十进制时正常

转成 2 / 8 / 16 进制时,负数按 64 位补码输出而不是带负号:num("-255", 16) 得到的是 ffffffffffffff01num("-10", 2) 是一长串 1。要给负数换进制,请自己把符号拆出来处理。

查手册:help(name)

help("date") 返回 date 的一行内置简介。配上剪贴板就是一条随手可查的手册:

cohelp = $CC("{default(help(clip()), "复制一个函数名再查")}", type(help(clip())))

动作函数(有副作用)

函数副作用
type(s)经 TSF 上屏文本,不污染剪贴板
open(target)打开 URL / 文件 / 程序(http(s) 走浏览器,其他走 ShellExecute)
proc.run(cmd, ...args, cwd?, verb?, show?)异步启动进程,三个具名参数见下
proc.shell(cmdline, cwd?)执行 shell 命令,cwd 见下
key.tap(combo) / key.seq(...combos)单次按键 / 按键序列
key.hold(combo) / key.release(combo)按下 / 抬起(须成对使用)
key.type(text)Unicode 直输,绕过键盘布局(走 SendInput,非 TSF)。文本里的换行与制表符会发成回车 / Tab 按键 0.118 新增
clip.copy(s) / clip.paste()写入剪贴板 / 模拟 Ctrl+V
dict.add(s) / dict.add(s, code)加词到用户库(不给 code 就按当前方案的取码规则推导)
ime.toggle(target)切换状态:cn-en / fullshape / layout / candwin / s2t / preedit / toolbar
ime.schema(id)切换并持久化方案,如 "wubi86" / "pinyin"
ime.theme(name) / ime.theme_cycle(dir?)切换主题 / 循环切换(dirnext / prev
ime.undo_commit()撤销上屏:删除最近一次上屏的内容,见下文
ime.pair(left, right, jump?)上屏配对符号:光标落两段之间,可用跳出键越过右段,见下文
setting.open(page, args?)打开设置的指定页(page 见下;args 为附加命令行参数)
web.search(engine, q)打开搜索页,enginebaidu / bing / google / zdic,见下
config.set(key, value) / config.toggle(key)写入配置 / 循环切换枚举或翻转布尔(返回新值)
wind.cli(...)调用命令行子命令,见下文
ui.toast(text, kind?, color?, pos?, ms?) 0.121 新增弹一条桌面提示,见下文

type(s) 是唯一被特殊对待的名字:它只能作为 $CC 的动作直接写出来,不能嵌进别的函数里、也不能出现在显示名中,且只接受 1 个参数、不接受具名参数。help("type") 也查不到它。别和 key.type 搞混——后者是模拟键盘直输,走的是另一条路径。

web.search(engine, q)engine 不区分大小写、首尾空格会被去掉;q 为空时不会打开浏览器,而是报错——避免开出一个空搜索页却让人以为命令没执行。zdic 走的是汉典的词条页而非搜索参数。

工作目录:cwd=

被启动的程序总有一个确定的工作目录,规则如下:

写法工作目录
proc.run("D:\\Dict\\dict.exe")D:\Dict(程序自己所在的目录)
proc.run("notepad.exe")用户主目录(%USERPROFILE%
proc.run("x.exe", cwd="D:\\Data")D:\Data
proc.shell("dict -q 词")用户主目录
proc.shell("dict -q 词", cwd="D:\\Dict")D:\Dict

不写 cwd 时,proc.run 默认取被启动程序所在的目录——等同于你在资源管理器里双击它。靠相对路径找数据文件的程序(各类词典、绿色版工具)正是按这个前提写的,所以多数情况下不需要写 cwd

proc.shell 是把整条命令行交给 shell,认不出目标程序,所以默认只能落到主目录。命令里带相对路径就必须显式写 cwd

cwd 支持内部目录变量,也支持内插:

# 用输入法自己的目录
cotool = $CC("[跑工具]", proc.run("${APP_DIR}\\tools\\t.exe", cwd="${APP_DIR}\\tools"))

# 查词:词库在程序目录下,靠相对路径加载
codc = $CC("查 {last(1)}", proc.run("D:\\Dict\\dict.exe", "{last(1)}", cwd="D:\\Dict"))

`cwd` 写了个不存在的目录会被忽略

此时程序仍会启动,但工作目录退回默认值,只在日志里留一条警告。之所以不直接失败,是因为你的目的是启动程序而不是校验路径——但这也意味着 cwd 路径写错时表面上一切正常,只是程序又找不到它的数据了。排查这类问题先检查路径拼写和反斜杠

管理员运行与窗口状态:verb= show=

只对 proc.run 有效,且只在 Windows 生效(macOS 会忽略并在日志留一条说明)。

verb效果
省略该文件类型的默认动作
open打开
runas以管理员身份运行(会弹 UAC 提示)
edit / print编辑 / 打印
explore / properties在资源管理器中浏览 / 打开属性对话框
show效果
省略 / normal正常窗口
min最小化启动,且不抢焦点
max最大化启动
hidden不显示窗口
# 以管理员身份跑一个维护脚本
coadm = $CC("[管理员运行]", proc.run("D:\\tools\\fix.exe", verb="runas"))

# 后台静默启动,不打断当前打字
cobg = $CC("[后台同步]", proc.run("D:\\tools\\sync.exe", show="hidden", cwd="D:\\tools"))

取值必须是上表里的小写词,写错(含大小写不符,如 verb="RUNAS")会直接报错并列出合法取值——这个校验在所有平台一致,所以在 macOS 上写错也会当场发现,不会等到换台 Windows 机器才炸。

`hidden` 不会让程序变成后台服务

它只是不显示窗口。程序本身如果会弹对话框或自己创建窗口,照样会出现;反过来,一个需要你操作的程序设了 hidden 就变成了看不见摸不着的进程,只能去任务管理器结束。

打开设置页:setting.open(page)

page 是目标页的规范 id,直接跳到对应标签页:

page页面
schema方案
input输入
keys按键
ui外观
dict词库
advanced高级
about关于
import不是独立标签页:切到高级页并弹出导入对话框,同时读一次剪贴板(用于配置分发包
""(空串)默认页
cost = $CC("打开设置", setting.open(""))
codc = $CC("词库管理", setting.open("dict"))

未知 id 会被设置端忽略并落到默认页。

附加参数:setting.open(page, args) 0.113 新增

第二个参数是原样直通给设置工具的命令行参数串,写法与设置工具的命令行参数完全一致。最常用的是直接落到某个方案的某类词库数据:

codw = $CC("五笔用户词库", setting.open("dict", "--schema=wubi86 --type=user-dict"))
cods = $CC("五笔候选调整", setting.open("dict", "--schema=wubi86 --type=shadow"))
copy = $CC("拼音词频", setting.open("dict", "--schema=pinyin --type=freq"))

输入法只负责转交,不解析这串参数——设置工具支持什么就能写什么,新版本加的参数无需等输入法跟进。参数不认识或方案不存在时,设置工具会退到最接近的位置并给出提示,不会打不开。

含空格的值要自己加引号

参数串会被重新切分成多个参数,--text=你 好 会断在空格处。含空格的值请写成 --text="你 好"

配对符号:ime.pair(left, right, jump?)

上屏 left + right、光标落在两段之间,并把这一层压入配对状态——之后按跳出键(在设置里配的 Tab / Enter)就能越过右段,和自动配对打出来的括号完全一样。

cojk = $CC("「」", ime.pair("「", "」"))
cozs = $CC("注释", ime.pair("<!--", "-->"))

两段都可以是任意文本,不限于单个符号:

输入后:  <!--|-->
按 Tab:  <!---->|

jump:跳出时光标右移的格数。 省略时按右段的字符数推导("-->" → 3),一般不用写。它存在是因为跳出靠合成方向键,而"一次右键越过多少内容"是宿主行为——多数宿主一次跨整个 emoji,少数按编码单元走。若在某个程序里发现跳出后光标位置差了几格,用它手动校准:

cozs = $CC("注释", ime.pair("<!--", "-->", jump=3))

分级跳出:同一条词条里写多个 ime.pair 即可,无需额外语法。后写的是内层,跳出时也先出内层:

cokh = $CC("(【】)", ime.pair("(", ")"), ime.pair("【", "】"))

输入后:  (【|】)
按 Tab:  (【】|)     ← 出内层
按 Tab:  (【】)|     ← 出外层

受「标点配对」总开关约束

ime.pair 跟着设置 → 标点配对的开关走(中文模式看中文配对、英文模式看英文配对)。关掉时它退化为纯上屏:整串照常上屏,但光标落在末尾、也不能跳出。

另外,「输入右符号跳出」对它不生效——那条路径只认单个标点按键。ime.pair 压入的配对只能用 Tab / Enter 跳出。

反查剪贴板里的字 0.117 新增

平时的反查要先把字打出来,才能在悬停提示里看它的编码和读音。碰到不认识的字(从网页、 PDF、聊天记录里看到的),这条路走不通——你根本不知道怎么打它。

dict.rev 反过来走:先复制,再查

出厂已带 cofc,直接可用:

cofc = $CC(default(dict.rev(clip()), "剪贴板反查(需先复制文字)"), type(dict.rev(clip())))

复制一个「我」,切回输入法打 cofc,候选就是:

我: q/trn/trnt wǒ

编码给的是全部码位(简码在前),因为反查回答的是「这个字怎么打」——q 这样的简码才是 最有用的答案。编码来自码表的反查索引,不是按取码规则算出来的,所以给出的码一定打得出来。

选中即上屏这行文字。

编码为空是正常的

dict.rev 查的是当前主码表。纯拼音方案下没有形码可查,编码段自然是空的,只出读音。

查第几个字

默认查第 1 个字。dict.rev(s, n) 查第 n 个(1 起);n 超出字数返回空串,不报错

想一次看多个字,用 $SS 列若干条、各查一个:

cofd = $SS("反查", "{dict.rev(clip(),1)}", "{dict.rev(clip(),2)}", "{dict.rev(clip(),3)}")

写几条就是最多查几个字。剪贴板不足位数时,多出来的那几条自动不出现,不会留空白候选。

换版式:format=

版式用候选注释的同一套 ${...} 模板语法, 变量名也一样,学一次即可:

写法结果
dict.rev(clip())我: q/trn/trnt wǒ(默认 ${char}: ${code_all} ${pinyin}
dict.rev(clip(), 1, format="${pinyin}")
dict.rev(clip(), 1, format="${code_all:/}")q/trn/trnt
dict.rev(clip(), 1, format="${char} ${chaizi}[${chaizi_code}]")形如 你 亻尔[wqiy](字根用私用区字形,需装字根字体才显示)

可用变量:${char}(被查的字)、${code_all}(全部码位)、${code}(只要最长的全码)、 ${pinyin}${chaizi}${chaizi_code}${dict}(注释词库释义)。

模板里要写 `${}` 不能写 `{}`

{...} 在命令栏字符串里是求值插值format="{code}" 会去调用 code() 函数(触发编码), 静默得到完全不相干的结果。变量一律写 ${code} 这种形式。

为什么用 $CC 而不是直接写模板

直接写 {dict.rev(clip())} 也能用,但候选的显示文本就是上屏文本,两者绑死。于是剪贴板 为空时只有两个都不好的结果:整条候选消失(分不清是没复制还是功能坏了),或者把提示语本身 打进文档。

$CC 把两者拆开:display 只管显示(查不到时用 default 回落到提示语),上屏走 type(dict.rev(clip()))(查不到时求值为空,选中什么也不打)。

撤销上屏:ime.undo_commit()

按上屏历史删除最近一次上屏的内容——记录上屏了几个字符,就往前删几个。连续触发可逐条回退。

coun = $CC("撤销上屏", ime.undo_commit())

配合快捷键使用更顺手:把这条短语绑一个好按的编码,或用 config.set 给它挂全局热键。

行为细节:

  • 正在打字时不动作 —— 输入缓冲非空(组合区有内容)时直接忽略,不会误删
  • 无上屏历史时删除 1 个字符
  • 每撤销一次就从历史中弹出一条,推送失败会回滚该条记录

撤销不校验光标前的内容

它只按历史记录的字符数往前删,不检查光标前实际是什么。上屏后如果你在输入法之外移动过光标、或改过文本,撤销会删错位置的内容。

另外计数按 UTF-16 单位,在使用退格兜底的宿主里遇到 emoji 可能多删。

调用命令行:wind.cli(...)

命令行工具的任意子命令挂到短语上,用主程序自身执行。两种传参形式:

# 单参形式:按空白拆分
cocr = $CC("重建词库缓存", wind.cli("schema rebuild"))
codf = $CC("停用辅码库", wind.cli("schema dict disable wubi86 fl"))

# 多参形式:逐个原样传递,用于含空格的路径
cobk = $CC("备份", wind.cli("backup", "create", "D:\\我的 备份\\wind.zip"))

# 配合内部目录变量,免去硬编码路径
cobu = $CC("备份到用户目录", wind.cli("backup", "create", "${USER_DATA}\\backups\\wind.zip"))

含空格的参数(尤其是文件路径)必须用多参形式,否则会被空白拆散成多个参数。路径里的反斜杠仍按短语字符串规则写成 \\(见路径里的反斜杠要写两个)——这一层与 CLI 无关,是短语先解析、再把结果交给 CLI。

照搬终端命令的两道坎

从终端里跑通一条命令、再原样贴进 wind.cli(...),是最常见的写法,也最容易连撞两道坎。拿一条真实的命令举例——把竖排候选的注释模板设成 {${chaizi}}{ [${chaizi_code}]}{ ${pinyin}}(这是模板本身的样子,下面写进短语时还要再转义一层):

# ❌ 错误:整条命令行塞进一个参数,大括号也没转义
[[phrases]]
code = 'comm'
text = '$CC1("切注释全", wind.cli("config set ui.candidate.comment_template_vertical {${chaizi}}{ [${chaizi_code}]}{ ${pinyin}}"))'

# ✅ 正确:按空格拆成 4 个参数,大括号写成 \{ \}
[[phrases]]
code = 'comm'
text = '$CC1("切注释全", wind.cli("config", "set", "ui.candidate.comment_template_vertical", "\{${chaizi}\}\{ [${chaizi_code}]\}\{ ${pinyin}\}"))'
weight = 1000
position = 1

第一道:参数得你自己拆开。 单参形式只是把整串按空白切一刀(每遇到空白就分出一个参数),它只适合参数本身不含空格的命令。上面那条模板串自己带空格,切完就散成了六七个参数,CLI 收到的东西跟你写的完全不是一回事。只要有任何一个参数可能含空格,就把命令行的每一段写成独立的字符串参数——包括 configset 这些子命令名。

第二道:\{\} 要转义。 字符串里的 {...}内插语法{${chaizi}} 会被当成「求值这个表达式」而不是字面的大括号。要输出大括号本身,必须按转义表写成 \{ \}(见路径里的反斜杠要写两个)。

${chaizi} 这类 ${名字} 不用转义:只有内部目录变量那三个名字会被替换,其余原样穿过,正好交给 CLI。

两道坎的失败现象不一样,可以据此判断撞的是哪道

症状撞的是
打出编码后候选里根本没有这条短语,也没有任何报错第二道。大括号没转义 → 整条短语解析失败被静默丢弃
候选正常出现、选中后也没提示,但配置没变第一道。参数没拆开 → CLI 收到错的参数,而 wind.cli 不回收错误

两种都不会弹错误框,所以别按「有没有报错」判断对错,要按上表对症状。

写在 TOML 里请用单引号

上面的 text = '...' 用的是 TOML 的单引号字面串,里面的 \ 原样保留,写法与设置页输入框、词库文件里完全一致。若改用双引号 text = "...",TOML 自己会先吃掉一层转义,而 \{ 在 TOML 里是非法转义,整个文件都会读不进去。

执行完给出反馈 0.121 新增

wind.cli等命令跑完,按退出码把结果弹成 toast:成功文案取 CLI 自己打印的最后一行(「✓ 用户词库: 新增 12 · 更新 3」),失败取 stderr 的首行。三个具名参数管这件事,都不影响命令本身:

参数默认说明
toaston执行结果提示。on = 成败都弹;off = 都不弹(此时也不等待);error = 只在失败时弹
ok省略成功时的自定文案。省略则用命令自身输出的最后一行,不自造第二套说法
wait10000等待命令结束的毫秒上限。0 = 不等待,此时无结果可报。超时会给一条「命令仍在执行,未能确认结果」的中性提示,迟到的真结果不再补弹
# 导入用户词库,失败时才提示
coim = $CC("导入词库", wind.cli("dict import D:\\words.txt", toast="error"))

# 服务侧本来就会弹提示的子命令,把这一层关掉,免得撞车
cort = $CC("重启服务", wind.cli("restart", toast="off"))

# 自定成功文案,并把等待放宽到 30 秒
corb = $CC("重建缓存", wind.cli("schema rebuild", ok="缓存已重建", wait="30000"))

哪些子命令该设 toast 为 off

服务侧本身就会弹提示的那些(restart / config set)。撞不撞车只有写词条的人当场知道,输入法不去维护一张子命令名单。

从短语调用时输入法服务必然在线,所以 schema / dict / phrase / backup 这些需要在线的子命令都能正常连上。

改配置优先用 config.set,不要绕 wind.cli

config.set(key, value)wind.cli("config set ...") 效果相同,但前者在进程内直接生效、错误就地返回,后者要另起一个进程再把结果读回来。改配置一律用 config.set / config.togglewind.cli 留给没有对应短语函数的子命令。

弹一条提示:ui.toast(...) 0.121 新增

# 最简形式
$CC("提示", ui.toast("已完成"))

# 指定类型与位置
$CC("提示", ui.toast("导入完成", kind="success", pos="top_right", ms="4000"))

# 自定强调色
$CC("提示", ui.toast("注意", color="#FF8800"))

除文案外全部具名、与顺序无关

参数默认取值
kindinfoinfo / success / error,决定强调条颜色
color省略自定强调色 #RRGGBB#RRGGBBAA
posbottom_centercenter / top / top_center / bottom_center / top_left / top_right / bottom_left / bottom_right
ms2500显示毫秒数

底色与文字色仍归主题,不开放到词条——只有强调色可以在这里改。

kind 与 color 只能给一个

两者都是「强调色」的写法,同时出现只能是写错了。此时会直接报错而不是猜一个——静默取一个会让另一个看起来「没生效」。

同理,postop_right 写成 rightms 写成 "5秒",都会当场报错,不会静默降级。

外部脚本与计划任务也能弹:命令行 wind_input ui toast "文案",见命令行工具

按键 combo 格式

key.* 的 combo 为 [修饰键+]...主键,大小写不敏感,+ 分隔。修饰键:CtrlShiftAltWin(各有别名如 Control / Menu / Super)。主键支持常见控制键(EnterTabEscapeHomeEndUp 等)、功能键 F1F24、字母数字键,以及标点键(CommaPeriodSlash 等)。

key.tap("Ctrl+C")                       # 复制
key.tap("Alt+F4")                       # 关闭窗口
key.seq("Home", "Shift+End", "Delete")  # 删除整行
key.type("Hello, 世界")                  # Unicode 直输

未列出的特殊键用 vk: 前缀指定 Windows 虚拟键值,0x 前缀为十六进制、无前缀为十进制,有效范围 0x010xFF

key.tap("vk:0x5D")    # 右键菜单键(VK_APPS)
key.tap("vk:0x60")    # 数字小键盘 0
key.tap("Shift+vk:0x5D")

标点键 VK 码基于美式布局

标点符号键的 VK 码按美式 US 键盘布局定义,非 US 布局下物理键位可能不同,此时改用 vk: 数值码指定精确虚拟键值。

macOS 上 key.* 需要「辅助功能」授权

key.tap / key.seq / key.hold / key.release 靠向前台应用注入键盘事件实现,macOS 把这类能力归在辅助功能权限下。未授权时系统会静默吞掉事件——命令看起来执行了,按键却没发出去。到 系统设置 → 隐私与安全性 → 辅助功能 打开「清风输入法」即可,见 macOS 版 · 授权辅助功能

key.type(文本直输)与 clip.paste 走的是输入法自己的文本插入接口,不需要这项授权。

行为细节

  • 权重:命令短语与普通短语一样默认权重 1000。命令用的精确英文编码极少与自然候选撞码,默认值通常已足够靠前;拼音方案下更应选一个不与自然词碰撞的编码,而非堆高 weight。
  • 自动上屏 / 顶码:纯文本命令(只产出文本)与短语一样可自动上屏、顶码上屏;含副作用的命令不自动上屏,始终等待手动选择,被顶屏时异步执行动作。只有短语的编码(如 date)不参与顶码,也不会被"满码空码清空"误清。
  • 不污染历史last() 只记录真正上屏的文本。有 type() 的命令进入历史;仅副作用命令(open / proc.run 等)不进入,避免显示文字污染下一次 last()
  • 显示与执行是两次独立求值:显示名在候选生成时算一遍,动作在选中时再算一遍。所以 uuid()now() 写在两处会得到两个值。想"切一次并显示新状态",让显示名用 config.get、动作用 config.toggle——把 config.toggle 写进显示名不会切换两次,而是整条候选直接失效(显示名不允许有副作用)。

临时拼音下不查快捷短语

进入临时拼音模式后不查询快捷短语层,输 zzbd 得到的是拼音候选而非字符组导航。但词库条目内嵌的 $ 语法在临拼下仍会正常展开与执行。

相关阅读

对这篇文档有疑问,或发现内容有误?

欢迎到文档仓库提 issue,写明问题时附上本页链接即可。

去提 issue →

本页目录