RIME 或称中州韵输入法,另一个更风行的名字是小狼毫输入法,当然这并不准确,因为只有 Windows 平台上的 RIME 才称为小狼毫。不过也无妨,作为一款开源输入法,RIME 可以部署在 Windows、MacOS、Linux、Android 等多个平台上,实现大同小异的功能,大部分配置文件也都通用,用不着很仔细区分。
我很早就听说了 RIME,作为开源输入法,用户可以自己构建码表、输入方案,因而一问世就很受方言、汉字、打字爱好者的青睐。方言爱好者用 RIME 实现各种方言输入方案,汉字爱好者用来输入扩展区汉字,打字爱好者则是用来改进各种音码、形码方案,不一而足。
但早年间 RIME 的 bug 比较多,入门的门槛高,一直只在小圈子内流行。经过数次版本迭代后,现而今的 RIME 可以说是非常好用,哪怕是仅追求不窃取用户资料的「圈外人」也可以轻松体验。
网络上关于配置 RIME 的入门教程很多,我不在此赘言。这篇文章主要谈谈如何用 RIME 的 Lua 脚本实现一些高级输入,也是我最近折腾 RIME 的一些心得。
苏州码
苏州码也称苏州码子、花码等,是中国传统的记数符号,对照如下表所示:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|
〇 | 〡 | 〢 | 〣 | 〤 | 〥 | 〦 | 〧 | 〨 | 〩 |
在表示数字时,苏州码用一个符号表示一位数,从左向右书写,这与阿拉伯数字的计数方式相同。
苏州码还有一条规则,当「〡」「〢」「〣」中任意两者相邻时,首个用竖式,次一个用横式,再次一个又用回竖式,如此循环。
知道以上的规则就会识读苏州码了,例如:
18590
➔ 〡〨〥〩〇51203
➔ 〥〡二〇〣72132
➔ 〧〢一〣二
再来看几个加上单位的具体例子:
实际使用时,还会将
〡〨〥〣〤〦〥
万 块
可以直接读「一万八千五百三十四块六五」。由于排版不便,苏州码在互联网时代已经难觅踪迹了,但似乎在民间手写的场合还有孑余。
RIME
言归正传,一个个复制输入苏州码太不现实,那么如何优雅地用 RIME 输入苏州码呢?
挂载一个输入方案
从头构建输入方案太过复杂,我们可以通过修改现成的输入方案实现我们的想法。在 RIME 的官方仓库中就能找到很多输入方案,可以下载一个最熟悉的。
以 Windows 平台为例,正确安装 RIME 后,在右下角的任务栏中理应出现 RIME 图标。
- 右击 RIME 图标,选择
用户文件夹
,将下载的输入方案移入该文件夹中,文件夹中应具有许多.yaml
文件; - 右击 RIME 图标,选择
重新部署
; - 再右击 RIME 图标,选择
输入法设定
,就能找到下载的输入方案了。
深入输入方案
输入方案最基本的两个文件是 *.schema.yaml
和 *.dict.yaml
:
*.schema.yaml
用于实现输入功能,例如模糊音、中英文混打等功能都通过它实现;*.dict.yaml
是码表文件,用户一般不需要动它。
打开输入方案的 *.schema.yaml
,可以看到里面有一个名为 translators
的模块,该模块决定了打字时击入的编码如何转化为候选词。
我们要通过 Lua 脚本将输入的数字转为苏州码,在该模块下添加一项 lua_translator@number_translator
。lua_translator
告诉 RIME 我们要使用 Lua 生成候选词,number_translator
是函数名称。我修改后的 translators
模块为
translators:
- punct_translator
- table_translator@custom_phrase
- reverse_lookup_translator
- script_translator
- lua_translator@number_translator
Warning YAML 文件对缩进敏感,一定要检查缩进是否正确。
Lua 脚本
接着在用户文件夹,即 *.schema.yaml
所在文件夹中新建一个名为 rime.lua
的文件,写入
number_translator = require("number")
上述代码将 number.lua
脚本注册为 number_translator
函数。rime.lua
文件管理着接入 RIME 的所有 Lua 脚本,将相应脚本注释去,其功能就被禁用。
在用户文件夹中新建名为 lua
的文件夹,所有 Lua 脚本就存放在该目录下,在该目录中新建一个 number.lua
文件。如果仅列举关键文件,文件结构应为
RIME
├─*.dict.yaml
├─*.schema.yaml
├─lua
│ └─number.lua
└─rime.lua
在 number.lua
写入将数字字符串转为苏州码的核心函数:
local function contains(array, element)
for _, value in pairs(array) do
if value == element then
return true
end
end
return false
end
local function num2suzhou(num)
local suzhou = {"〇", "〡", "〢", "〣", "〤", "〥", "〦", "〧", "〨", "〩"}
local horizontalSuzhou = {"一", "二", "三"}
local oneTwoThree = {table.unpack(suzhou, 2, 4)} -- {"〡", "〢", "〣"}
local result = ""
if num == nil then return "" end
-- 遍历整个字符串
for pos = 1, string.len(num) do
-- 将每个字符转为数字
digit = tonumber(string.sub(num, pos, pos))
if pos > 1 then
-- 数字若为 {"〡", "〢", "〣"}
if digit > 0 and digit < 4 then
-- 且前一个字符也为 {"〡", "〢", "〣"}
-- `-3` 即取末一个汉字,utf-8 中一个汉字 3 字节
if contains(oneTwoThree, string.sub(result, -3)) then
-- 就使用横式的 {"一", "二", "三"}
result = result .. horizontalSuzhou[digit]
goto continue
end
end
end
-- 其他情况或其他数字都使用竖式
result = result .. suzhou[digit + 1]
::continue::
end
return result
end
num2suzhou()
实现了前文提到的数字与苏州码映射和横竖式转换两个规则,接下来要将封装成 RIME 的接口:
-- 若输入数字带有小数,将其切分为整数、小数点、小数 3 个部分
local function splitNumPart(str)
local part = {}
part.int, part.dot, part.dec = string.match(str, "^(%d*)(%.?)(%d*)")
return part
end
-- 字符串处理流程
function numberTranslatorFunc(num)
-- 切分小数
local numberPart = splitNumPart(num)
local result = {}
-- 整数和小数部分分别用 num2suzhou() 转换,再将整数、小数点、小数三者连起来
-- 最后将结果存入 result
table.insert(
result,
{
-- 候选结果
num2suzhou(numberPart.int) .. numberPart.dot .. num2suzhou(numberPart.dec),
-- 候选备注
"〔蘇州碼〕"
}
)
return result
end
-- 接入 RIME 引擎
function translator(input, seg)
local str, num, numberPart
-- 匹配 "S + 数字 + 小数点(可有可无) + 数字(可有可无)" 的模版
if string.match(input, "^(S%d+)(%.?)(%d*)$") ~= nil then
-- 去除字符串首的字母
str = string.gsub(input, "^(%a+)", "")
numberPart = numberTranslatorFunc(str)
if #numberPart > 0 then
for i = 1, #numberPart do
-- numberTranslatorFunc()
yield(
Candidate(
input,
seg.start,
seg._end,
numberPart[i][1], -- 候选结果
numberPart[i][2] -- 候选备注
)
)
end
end
end
end
return translator
处理字符串的过程都写在注释中了,这里仅具体说一下接入 RIME 的 translator()
函数。
translator(input, seg)
接受两个参数,input
为用户击入的字符,seg
推测是分词信息,一般用不到,可以当作固定模版。
正则 "^(S%d+)(%.?)(%d*)$"
用于匹配用户的 input
:
S
匹配大写字母「S」,作用类似于快捷键,也可以改为自己喜欢的键位;%d+
匹配一至多个数字;^
表示匹配句首,^(S%d+)
就表示只有以「S」和若干数字开头时才会转换;%.
匹配字符「.」,%.?
表示「.」可有可无;%d*
匹配零至多个数字。
用户输入的字符符合匹配规则,字符串经处理后用 yield(Candidate())
生成候选词。Candidate()
需要填入 5 个参数,不过其实也只用更改后两个参数就好。
完成后仍然要重新部署一下,就可以试试输入效果了~
了解在 RIME 上套用 Lua 脚本的方法后,相信编写自己的脚本也不觉得困难了,参考模版就能实现自己的奇思妙想。 librime-lua 提供了许多 Lua 脚本,已经实现了很多有意思的想法,供额外参考。
References
- Suzhou numerals - Wikipedia
- 李文化 & 陈虹. (2020).《癸亥年更流部》苏州码子释读. 南海学刊(04), 38-46.
- LEOYoon-Tsaw / Rime_collections - GitHub