Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

加入 Lua 脚本扩展支持 #248

Closed
hchunhui opened this issue Jan 23, 2019 · 47 comments
Closed

加入 Lua 脚本扩展支持 #248

hchunhui opened this issue Jan 23, 2019 · 47 comments

Comments

@hchunhui
Copy link
Contributor

大家好!我对为 librime 加入脚本扩展的议题很感兴趣。
可惜我看到之前关于此的讨论一直没有后续进展。
我花了几周做了一个原型,期望一起讨论,最终将该功能合并到 librime 中。

我的分支在: https://github.com/hchunhui/librime/tree/lua-dev

下面是具体方案使用和编译的说明。欢迎大家试用和提意见!


背景:
librime 内部实现了一系列可组合的输入法模块,
用户通过 yaml 配置文件选择模块,并组合成所需的输入引擎。

然而现有的内置模块不能满足多样化的需求。
如字符集过滤,内置模块 charset_filter 只能过滤特定的字符集,
若用户要过滤其他的字符,除修改 librime 代码,没有直接的办法。
但 librime 为了通用性,不可能接受这些特定用途的修改。
类似需求还有候选排序、输入日期、输入大写数字等。


方案:
为 librime 引入脚本,将实现可变和多样功能的代码移出内核,让用户自行定义。
目前提供用脚本扩展新的 filter 和 translator。


使用说明:

目前实现了 lua_filter 和 lua_translator,
分别对应 rime 中的 filter 和 translator,可插入配置文件中。
如:

engine:
  ...
  tranlators:
    ....
    - lua_translator@mytranslator
    ...
  filters:
    ...
    - lua_filter@myfilter
    ...

myfilter 和 mytranslator 分别表示实现对应功能的函数在 lua 脚本中的名字。
lua_translator 和 lua_filter 也可以有多个实例。

目前 lua 脚本文件名是在代码中写死的,是 rime 的配置目录下的 "rime.lua" 文件。

在源码树 "data/lua/" 中有一脚本样例,可用于试用和参考。

下面以用 lua 实现字符过滤和输入日期为例,说明脚本的写法。

function myfilter(input)
   for cand in input:iter() do
      if (filter_cond(cand.text))
      then
	 yield(cand)
      end
   end
end

function mytranslator(input, seg)
   if (input == "/date") then
      -- Candidate(type, start, end, text, comment)
      yield(Candidate("date", seg.start, seg._end, os.date("%Y年%m月%d日"), " 日期"))
   end
end

根据配置,lua_filter, lua_translator 分别会激活 myfilter, mytranslator 来执行字符过滤功能。

myfilter 的输入输出均可看作候选项 candidate 组成的列表。
myfilter 首先对输入进行遍历,然后逐一检查候选项是否保留(filter_cond实现略)。
若是,则输出。这里采用了 yield 逐一按序输出每项候选,而不是返回整个候选列表。
这样写的目的是实现按需计算,避免因计算没被用户翻页看到的候选而浪费时间和空间。

mytranslator 的输入是由 segmentor 切分好的输入子串 input 和分段 seg,输出是候选项列表。
首先判断输入是否为 "/date",若是则生成一个当前日期的候选。


编译说明

需要事先安装 lua5.3 的开发包,其余同 librime。

@lotem
Copy link
Member

lotem commented Jan 23, 2019

嘿,不錯喲。
有沒有興趣,把這部分做成插件。

關於 librime 的插件,目前有這個:
https://github.com/rime/librime-legacy
但只包含在特定版本裏,用來給舊版本升級用戶詞典數據。

這裏 有個CMake選項可以把 rime-gears 分離出來,以插件形式使用。但這件事沒做完——插件的作用是把包含外部依賴的部分從核心程序庫分離,便於維護。比如理想中 simpilifier 組件就應該是單獨的項目,把 opencc 這個第三方庫從 librime 的外部依賴剝離出來。

@nameoverflow
Copy link
Member

是个狼灭

@hchunhui
Copy link
Contributor Author

赞同做成插件。我之前以为 librime 还没法支持插件呢。

插件是在运行时加载进去吗?它与主体的接口有哪些呢?

@lotem
Copy link
Member

lotem commented Jan 23, 2019

插件的功能是在一個獨立的編譯單元裏往中心註冊組件(Component)。
起作用的是這個地方
https://github.com/rime/librime-legacy/blob/master/src/legacy_module.cc#L27

使用者(如 ibus-rime 主程序)加載動態鏈接庫時會執行 rime_<module_name>_initialize 函數,在那裏面調用 rime::Registry::Register() 註冊組件。大概就這樣。

後話:只不過在Windows上動態鏈接庫不一定好使,如果靜態鏈接插件還要改動一些工程配置,插件化的倒退,比如這裏所做的。

@nameoverflow
Copy link
Member

可以先发个 PR 然后在 PR 串里讨论。

@nameoverflow
Copy link
Member

另外不考虑使用 LuaJIT

@hchunhui
Copy link
Contributor Author

@lotem 谢谢,我先研究一下插件。
现在动了几行 engine.cc 的代码
用来初始化 lua 虚拟机,可能要改一下。其他的应该可以直变成插件。
关于 librime 我还有不少地方没看太明白,后面我整理一下再请教。

@nameoverflow 谢谢。
在这里发 PR 不知道合不合适,因为前面建议用插件单独维护。
LuaJIT 我还没关注,不知道与 Lua 的兼容性如何,能直接换库切换过去吗?

@lotem
Copy link
Member

lotem commented Jan 24, 2019

@hchunhui 虛擬機是不是可以在某個組件(如 Translator)實例的構造函數裏面初始化,
Engine 和他通過配置加載的組件都是按輸入法會話(可能對應一個輸入目標應用)分配的,如果要共用一個虛擬機實例,就得讓 Component 對象(相當於 factory)持有,然後在 Component::Create() 把虛擬機的引用傳給每個生成的 Translator。

@nameoverflow
Copy link
Member

nameoverflow commented Jan 24, 2019

如果要用插件又得前端配置支持了。
@lotem 全用动态链接最现实的问题就是 JNI 和 iOS 怎么办……

@hchunhui 兼容 Lua 5.1。从现在的代码来看似乎没有问题。

@hchunhui
Copy link
Contributor Author

@lotem 多谢指导!我按照前面的提示修改了代码,理论上可以独立出来了。
只是我还有一事不明,即编出来的库应该如何加载使用?
(包括前面说的 rime-legacy 是怎么用的)

我理解的插件,应该是:

  1. 编写时是独立的,代码的组织是模块化的;
  2. 编译时是分离的,不同的模块可以分散到不同的文件中;
  3. 运行时可以通过配置动态地选择要加载哪些模块。

具体到 rime 来说,第一点是由 librime 头文件形成的接口和它 Module 系统共同实现的。
第二点是由精巧设计的 CMakefile 做到的。
对于第三点,根据我看代码的理解,设计上这不是 librime 的职责,
而是 librime 用户(ibus-rime,fcitx-rime等)的责任。

我用的是 fcitx-rime,我粗略看了一下并没有找到加载动态库的功能。
所以只能改 fcitx-rime 的代码,要么自行实现第三点,要么编译期写死让它用上新加的模块。

我的理解对吗?

@lotem
Copy link
Member

lotem commented Jan 25, 2019

@hchunhui 同意以上分析。
librime-legacy 是這樣使用的:
由 ibus-rime 調用 dlopen() 嘗試加載so,如果安裝了這個插件,就會加載so後自動執行插件的初始化函數完成註冊。相對於「構建時將主程序鏈接到so」,用dlopen()允許把這個插件變成可選加載的。
當然,還是要在使用者(ibus-rime)代碼裏寫入插件的模塊名。這件事如果要放在 librime 裏做,就不宜在程序裏寫死插件列表,因爲插件是開放給第三方開發的,不應在 librime 代碼庫裏維護一個「註冊表」。肯定要從某個配置文件裏讀取插件列表。這是一些要做的工作。
另外一個原因是我還沒顧上實現一個跨平臺的加載動態鏈接庫的方法,所以先把這塊交給前端來實現(不必考慮其他平臺)。

@bj1949007
Copy link

刚看到,有用,标记一下

@yanhuacuo
Copy link

missing: PKG_CONFIG_EXECUTABLE ,导致无法在win下编译通过:

CMake Error at C:/Program Files/CMake/share/cmake-3.13/Modules/FindPackageHandleStandardArgs.cmake:137 (message):
Could NOT find PkgConfig (missing: PKG_CONFIG_EXECUTABLE)
Call Stack (most recent call first):
C:/Program Files/CMake/share/cmake-3.13/Modules/FindPackageHandleStandardArgs.cmake:378 (_FPHSA_FAILURE_MESSAGE)
C:/Program Files/CMake/share/cmake-3.13/Modules/FindPkgConfig.cmake:39 (find_package_handle_standard_args)
CMakeLists.txt:136 (find_package)
-- Configuring incomplete, errors occurred!

@hchunhui
Copy link
Contributor Author

谢谢反馈。看错误是没有找到 lua 库。

我对 windows 不太熟悉,为了方便我直接把 lua 放进仓库了,不用再依赖外部 lua 库。
最新的代码应该可以通过编译了。


现在这版代码处于弃用状态,仅供试用。

按前面的讨论,我开了一个新的仓库,目标是做成 librime 的插件:

https://github.com/hchunhui/librime-lua

后续的开发会在新仓库中。
只是 rime 项目插件支持看起来还有些问题,特别是 windows 平台好像还不能动态链接,真正用起来还要 hack 前端的代码。

@yanhuacuo
Copy link

谢谢反馈。看错误是没有找到 lua 库。

我对 windows 不太熟悉,为了方便我直接把 lua 放进仓库了,不用再依赖外部 lua 库。
最新的代码应该可以通过编译了。

现在这版代码处于弃用状态,仅供试用。

按前面的讨论,我开了一个新的仓库,目标是做成 librime 的插件:

https://github.com/hchunhui/librime-lua

后续的开发会在新仓库中。
只是 rime 项目插件支持看起来还有些问题,特别是 windows 平台好像还不能动态链接,真正用起来还要 hack 前端的代码。

修改过的代码依然不能在win下编译通过,完整的编译信息在这里

@hchunhui
Copy link
Contributor Author

谢谢反馈。看错误是没有找到 lua 库。
我对 windows 不太熟悉,为了方便我直接把 lua 放进仓库了,不用再依赖外部 lua 库。
最新的代码应该可以通过编译了。
现在这版代码处于弃用状态,仅供试用。
按前面的讨论,我开了一个新的仓库,目标是做成 librime 的插件:
https://github.com/hchunhui/librime-lua
后续的开发会在新仓库中。
只是 rime 项目插件支持看起来还有些问题,特别是 windows 平台好像还不能动态链接,真正用起来还要 hack 前端的代码。

修改过的代码依然不能在win下编译通过,完整的编译信息在这里

似乎是 VC 的 bug:
https://developercommunity.visualstudio.com/content/problem/25334/error-code-c2971-when-specifying-a-function-as-the.html

我按照这里面方法改了一下,再帮忙试试?

@yanhuacuo
Copy link

谢谢反馈。看错误是没有找到 lua 库。
我对 windows 不太熟悉,为了方便我直接把 lua 放进仓库了,不用再依赖外部 lua 库。
最新的代码应该可以通过编译了。
现在这版代码处于弃用状态,仅供试用。
按前面的讨论,我开了一个新的仓库,目标是做成 librime 的插件:
https://github.com/hchunhui/librime-lua
后续的开发会在新仓库中。
只是 rime 项目插件支持看起来还有些问题,特别是 windows 平台好像还不能动态链接,真正用起来还要 hack 前端的代码。

修改过的代码依然不能在win下编译通过,完整的编译信息在这里

似乎是 VC 的 bug:
https://developercommunity.visualstudio.com/content/problem/25334/error-code-c2971-when-specifying-a-function-as-the.html

我按照这里面方法改了一下,再帮忙试试?

已经成功编译出 「rime.dll」

但是,看到了的 LUA 脚本中有一些预定义的函数,那么,能不能多写几组具体功能实现的调用示例?比如,「单字过滤」的功能,能写个示例吗?

@yanhuacuo
Copy link

yanhuacuo commented Feb 26, 2019

比如,LUA 脚本中有已经定义好的:

function single_char_filter(input)
   local l = {}
   for cand in input:iter() do
      if (utf8.len(cand.text) == 1) then
	 yield(cand)
      else
	 table.insert(l, cand)
      end
   end
   for i, cand in ipairs(l) do
      yield(cand)
   end
end

那么,在 schema 中引用「- lua_filter@single_char_filter」,还是另写新的调用函数?

@hchunhui
Copy link
Contributor Author

比如,LUA 脚本中有已经定义好的:

function single_char_filter(input)
local l = {}
for cand in input:iter() do
if (utf8.len(cand.text) == 1) then
yield(cand)
else
table.insert(l, cand)
end
end
for i, cand in ipairs(l) do
yield(cand)
end
end

那么,在 schema 中引用「- lua_filter@single_char_filter」,还是另写新的调用函数?

直接用「- lua_filter@single_char_filter」即可。

仓库里面的 rime.lua 中每个注释起头的小节都是一个例子。
基本上每个例子都是实现一个简单的功能,用来帮助理解 filter/translator 的写法。
它们都可以在 schema 中直接引用,然后重新部署看效果。

仓库中 rime.lua 只是参考,自己完全可以从头开始写,不必保留里面的任何内容。

@yanhuacuo
Copy link

yanhuacuo commented Feb 26, 2019

比如,LUA 脚本中有已经定义好的:

function single_char_filter(input)
local l = {}
for cand in input:iter() do
if (utf8.len(cand.text) == 1) then
yield(cand)
else
table.insert(l, cand)
end
end
for i, cand in ipairs(l) do
yield(cand)
end
end

那么,在 schema 中引用「- lua_filter@single_char_filter」,还是另写新的调用函数?

直接用「- lua_filter@single_char_filter」即可。

仓库里面的 rime.lua 中每个注释起头的小节都是一个例子。
基本上每个例子都是实现一个简单的功能,用来帮助理解 filter/translator 的写法。
它们都可以在 schema 中直接引用,然后重新部署看效果。

仓库中 rime.lua 只是参考,自己完全可以从头开始写,不必保留里面的任何内容。

请验证一下 「 single_char_filter 」的可用性,我和两个小伙伴都验证无效,不能实现「屏蔽候选条目中的词组」的效果,只能做到「词组后置」

@hchunhui
Copy link
Contributor Author

比如,LUA 脚本中有已经定义好的:

function single_char_filter(input)
local l = {}
for cand in input:iter() do
if (utf8.len(cand.text) == 1) then
yield(cand)
else
table.insert(l, cand)
end
end
for i, cand in ipairs(l) do
yield(cand)
end
end

那么,在 schema 中引用「- lua_filter@single_char_filter」,还是另写新的调用函数?

直接用「- lua_filter@single_char_filter」即可。
仓库里面的 rime.lua 中每个注释起头的小节都是一个例子。
基本上每个例子都是实现一个简单的功能,用来帮助理解 filter/translator 的写法。
它们都可以在 schema 中直接引用,然后重新部署看效果。
仓库中 rime.lua 只是参考,自己完全可以从头开始写,不必保留里面的任何内容。

请验证一下 「 single_char_filter 」的可用性,我和两个小伙伴都验证无效,不能实现「屏蔽候选条目中的词组」的效果,只能做到「词组后置」

“词组后置”是期望的行为,抱歉名字起得有点误导。

这里的 single_char_filter 函数对应实现的就是 librime 自带的 single_char_filter:
https://github.com/rime/librime/blob/master/src/rime/gear/single_char_filter.cc
其原始意义即“词组后置”。

这个例子的原意是供人对比 lua 和 C++ 的实现,进而说明用 lua 加上"yield"编程模型更为简易且有性能保证。

「屏蔽候选条目中的词组」的效果,可如下实现:

function single_char_filter(input)
  for cand in input:iter() do
    if (utf8.len(cand.text) == 1) then
      yield(cand)
    end
  end
end

@yanhuacuo
Copy link

function single_char_filter(input)
for cand in input:iter() do
if (utf8.len(cand.text) == 1) then
yield(cand)
end
end
end

非常感谢,成功啦。

@yanhuacuo
Copy link

yanhuacuo commented Feb 26, 2019

不过,好像有一个问题,似乎 「 lua_filter@xxx 」中的 「 xxx 」不能像内置的 「 filter 」对象一样,拥有独立的,可以被「key_binder / bindings」和 「 switches 」接纳的名称标识,从而无法在「快捷键」和「开关」中做进一步的绑定,以实现更快捷和直观的功能。

独立使用是有效的,但是绑到开关或快捷键中是无效的:

开关

快捷键

@hchunhui
Copy link
Contributor Author

不过,好像有一个问题,似乎 「 lua_filter@xxx 」中的 「 xxx 」不能像内置的 「 filter 」对象一样,拥有独立的,可以被「key_binder / bindings」和 「 switches 」接纳的名称标识,从而无法在「快捷键」和「开关」中做进一步的绑定,以实现更快捷和直观的功能。

独立使用是有效的,但是绑到开关或快捷键中是无效的:

是的,现在无法实现所需的功能。

解决这个问题需要在 lua 中绑定 context 相关的函数,让 filter 能够根据 context 做不同操作。

@yanhuacuo
Copy link

不过,好像有一个问题,似乎 「 lua_filter@xxx 」中的 「 xxx 」不能像内置的 「 filter 」对象一样,拥有独立的,可以被「key_binder / bindings」和 「 switches 」接纳的名称标识,从而无法在「快捷键」和「开关」中做进一步的绑定,以实现更快捷和直观的功能。
独立使用是有效的,但是绑到开关或快捷键中是无效的:

是的,现在无法实现所需的功能。

解决这个问题需要在 lua 中绑定 context 相关的函数,让 filter 能够根据 context 做不同操作。

嗯,那期待插件版的新版本吧。

@hchunhui
Copy link
Contributor Author

hchunhui commented Mar 2, 2019

不过,好像有一个问题,似乎 「 lua_filter@xxx 」中的 「 xxx 」不能像内置的 「 filter 」对象一样,拥有独立的,可以被「key_binder / bindings」和 「 switches 」接纳的名称标识,从而无法在「快捷键」和「开关」中做进一步的绑定,以实现更快捷和直观的功能。
独立使用是有效的,但是绑到开关或快捷键中是无效的:

是的,现在无法实现所需的功能。
解决这个问题需要在 lua 中绑定 context 相关的函数,让 filter 能够根据 context 做不同操作。

嗯,那期待插件版的新版本吧。

今天为 filter 和 translator 加上了环境,绑定了 context 相关函数。
此功能现在可类似如下实现:

function single_char(input, env)
   b = env.engine.context:get_option("single_char")
   for cand in input:iter() do
      if (not b or utf8.len(cand.text) == 1) then
         yield(cand)
      end
   end
end

get_option 中的字符串可换为其他的开关名字,不一定与函数名相同。

还有些新增功能有空再总结。

Windows 编译版:
https://ci.appveyor.com/api/buildjobs/monr1j237bymqv2j/artifacts/rime.zip

@yanhuacuo
Copy link

yanhuacuo commented Mar 3, 2019

不过,好像有一个问题,似乎 「 lua_filter@xxx 」中的 「 xxx 」不能像内置的 「 filter 」对象一样,拥有独立的,可以被「key_binder / bindings」和 「 switches 」接纳的名称标识,从而无法在「快捷键」和「开关」中做进一步的绑定,以实现更快捷和直观的功能。
独立使用是有效的,但是绑到开关或快捷键中是无效的:

是的,现在无法实现所需的功能。
解决这个问题需要在 lua 中绑定 context 相关的函数,让 filter 能够根据 context 做不同操作。

嗯,那期待插件版的新版本吧。

今天为 filter 和 translator 加上了环境,绑定了 context 相关函数。
此功能现在可类似如下实现:

function single_char(input, env)
   b = env.engine.context:get_option("single_char")
   for cand in input:iter() do
      if (not b or utf8.len(cand.text) == 1) then
         yield(cand)
      end
   end
end

get_option 中的字符串可换为其他的开关名字,不一定与函数名相同。

还有些新增功能有空再总结。

Windows 编译版:
https://ci.appveyor.com/api/buildjobs/monr1j237bymqv2j/artifacts/rime.zip

非常感谢,用上了,Lua灵活强大性能好,感觉可以实现很多功能。

但是有一个问题,

咱们 Lua 开关实现的功能也好,内置 Librime 对象实现的功能也好,

由「switches开关」和「快捷方式」调用时——受到 Librime 的逻辑设定

它们都是「临时有效,且仅对当前程序临时有效」,

于是:「字符集切换」和「候选过滤」(比如楼上实现的「单字模式」),都仅是临时生效的,光标焦点换个程序——还得从头开一次。

诚然,我们可以打开 「XXX.schema.yaml」用一次改一次,但这样太低效了

比如这个→ ISSUES 中提到的,「 Switches开关 」和「快捷键」调用相应功能的「生效范围」:

如果是「全局生效」,就不用担心输入法从 Chrome 换到 Office 时,上一分钟做过的「设定」失效的问题。

不知大神可有简便的方式,对 Librime 的这个逻辑做个修改。

@hchunhui
Copy link
Contributor Author

hchunhui commented Mar 3, 2019

但是有一个问题,

咱们 Lua 开关实现的功能也好,内置 Librime 对象实现的功能也好,

由「switches开关」和「快捷方式」调用时——受到 Librime 的逻辑设定

它们都是「临时有效,且仅对当前程序临时有效」,

于是:「字符集切换」和「候选过滤」(比如楼上实现的「单字模式」),都仅是临时生效的,光标焦点换个程序——还得从头开一次。

诚然,我们可以打开 「XXX.schema.yaml」用一次改一次,但这样太低效了

比如这个→ ISSUES 中提到的,「 Switches开关 」和「快捷键」调用相应功能的「生效范围」:

如果是「全局生效」,就不用担心输入法从 Chrome 换到 Office 时,上一分钟做过的「设定」失效的问题。

不知大神可有简便的方式,对 Librime 的这个逻辑做个修改。

我没有遇到这样的问题。

是不是你的 schema 配置有问题?
我看到前面的图片中每个开关下面都有一个 "reset",把所有 reset 去掉还会有这个问题吗?

@yanhuacuo

This comment has been minimized.

@yanhuacuo

This comment has been minimized.

@yanhuacuo
Copy link

未来,生手可以通过 「Switche」 可以获得可视化的设定菜单,熟手可以通过「快捷键」更加迅速地变更各自熟悉的,高频用到的功能细节,Lua扩展的实现,又可观地扩大了关于功能拓展的空间。比如上面用 GIF 演示的一些功能的实现。「Switches开关」和「快捷键」,这两个能即时修改librime的「扳手」,被限定为「临时有效」,太不合理了啊。

@hchunhui
Copy link
Contributor Author

hchunhui commented Mar 3, 2019

未来,生手可以通过 「Switche」 可以获得可视化的设定菜单,熟手可以通过「快捷键」更加迅速地变更各自熟悉的,高频用到的功能细节,Lua扩展的实现,又可观地扩大了关于功能拓展的空间。比如上面用 GIF 演示的一些功能的实现。「Switches开关」和「快捷键」,这两个能即时修改librime的「扳手」,被限定为「临时有效」,太不合理了啊。

此问题已超出本主题范围,建议另开新主题。

在新主题中,我认为您需要描述一下在不要 reset 项时与您的期望的差别在什么地方。
因为 reset 是在每一次输入法切换时都会发生的,这可能导致所谓的“临时有效”。
而没有 reset 时,同一个 context 中开关的值在切换过程中是保持的。

@bj1949007
Copy link

你好,目前在Android手机上使用一个基于同文开发的测试版本,开发者是@nirenr 同样支持lua脚本
不过它是基于按键操作的,具体流程是 点击按键->获取当前候选->脚本对候选进行指定操作

个人经常有这样的需要,如编程中输出长串英语单词时,有时候需要变形,比如book->Book以及后面自动加空格
再一个根据当前候选执行某些命令
个人感觉能否将rime_lua脚本绑定到指定按键上,通过不同按键执行不同脚本功能,而非全自动运行

@hchunhui
Copy link
Contributor Author

谢谢关注。上述功能现在应该已经可以做到。现在说明文档暂缺,若有兴趣做我们可以继续交流细节。

我从 librime-lua 能做的事和需求本身两方面来说明一下。

  1. 能做的事是用 Lua 扩展 RIME 中 translator, filter, processor, segmentor 四种类型的组件。
    理论上能用 C++ 改这四种组件做到的事,用 Lua 也可以做到。

    前两者的用法较明确,前面也举例了用法。如日期时间、数字大写等可以用 translator。候选过滤、排序、加注释、变形等可以用 filter。

    后两者最早是不支持的。我最近在代码中加了绑定,可以用了,但我暂时不明确可以用到哪些方面。
    也许您提到的需求可以算一种。

  2. 提到的需求可分成两部分:候选变形和执行命令。

    单词候选变形写一个 filter 就可以实现了。
    按键执行命令可能要写 processor。processor 可以取得用户输入的键码序列,然后分析做相应的事。

@lotem
Copy link
Member

lotem commented Mar 11, 2019

其實 ProcessorTranslator 是比較重要的組件類型,前者是處理按鍵的,後者是輸出文字候選的。
但想要做一個有用的 Processor,最終主要得操作 Context對象,這個不反映在 Processor 接口的定義上。
至於 Segmentor 是比較次要的,因爲預設組件 recognizer 提供了按照模式匹配分段、打標籤的功能,我很少遇到需要實現新的 Segmentor 的需求。

@hchunhui
Copy link
Contributor Author

其實 ProcessorTranslator 是比較重要的組件類型,前者是處理按鍵的,後者是輸出文字候選的。
但想要做一個有用的 Processor,最終主要得操作 Context對象,這個不反映在 Processor 接口的定義上。

是的。我也发现 Context 比较关键。我在这里稀里糊涂绑了一大堆相关的接口,但真正明白用途的没几个。

感觉 librime-lua 想做得好用,还需要好好把接口整理一遍。
我现在暂时没时间系统地做这件事,希望具体应用来驱动,慢慢完善整理。

至於 Segmentor 是比較次要的,因爲預設組件 recognizer 提供了按照模式匹配分段、打標籤的功能,我很少遇到需要實現新的 Segmentor 的需求。

@bj1949007
Copy link

@hchunhui 谢谢你的解答,目前电脑端在用编译出来的lua版本小狼毫,手机端也在制作一些有意思的脚本
下面这个录制视频是基于支持lua脚本的安卓手机端同文录制的,同样是基于rime核心的
演示视频

目前提出的想法是能不能在rime中提供基于热键的lua脚本。不是作为功能函数的开关,而是按下一次热键执行一次脚本函数。
一个常见的用法是,执行针对当前单个候选项的脚本。就是按下热键后,截取rime准备上屏的内容,用lua脚本处理后返回处理内容,选择上屏,或执行其它功能后取消上屏

按键设置方式推荐如下
- { when: composing, accept: Control+v, send: Page_Down }
这是rime原生的,将之移植到lua脚本里
- { when: composing, accept: Control+v, send: charset_comment_capital} #转大写

目前的脚本运作方式是当候选中出现相关关键字或出现指定编码时自动运行脚本,是一种被动式的,同时也是批量式的运作。
这是一种非常好的方式,我也向支持lua脚本的同文开发者提了相关意见
我所说的是一种主动式的,也就是正常情况下脚本功能并不会被触发,而是按下一次按键执行一次,不按不执行。

比如默认的空格键执行的功能是上屏选中候选内容。我的想法是将lua脚本功能和指定热键绑定,比如针对选中单个候选对象进行操作。也就是在上屏的过程中将准备上屏内容传递到lua脚本进行处理,返回处理后内容,或执行其它功能后取消上屏

适用于执行一些额外的功能,让同文的按键操作功能变得更高效。

@hchunhui
Copy link
Contributor Author

@bj1949007

  • { when: composing, accept: Control+v, send: charset_comment_capital} #转大写

此类功能,以 “Control+v 转大写,加空格” 为例,大概像如下实现:

local function do_capital_space(s)
   return string.upper(s .. " ")
end

function myproc(key, env)
   local engine = env.engine
   local context = engine.context

   --- accept: Control+v
   if (key:repr() == "Control+v") then
      --- when: composing
      if (context:is_composing()) then
         local s_orig = context:get_commit_text()
         --- do_capital_space
         local s = do_capital_space(s_orig)
         engine:commit_text(s)
         context:clear()
         return 0 -- kAccepted
      end
   end
   return 2 -- kNoop
end

在 schema 的 processors 中加入:

- lua_processor@myproc

即可。

上述实现中,触发按键和执行动作是写死在代码中的。
从 schema 中读取配置要复杂一些,可以从 env.schema.config 拿到。我暂时没有时间去展示了。
可以在看到 Config 绑定的函数。

@bj1949007
Copy link

好的,收到
lua脚本真的强大,和小狼毫一起简直是如虎添翼

下面这是一个网络请求,返回json内容,是一个拼音云结果,在同文里面已经可以实现了,目前小狼毫lua脚本支持吗

云输入内容2="http://olime.baidu.com/py?input="..编码.."&inputtype=py&bg=0&ed=20&result=hanzi&resultcoding=utf-8&ch_en=0&clientinfo=web&version=1"

因为有的时候需要通过网络获取内容,可否通过lua脚本实现,直接在小狼毫里面上屏

@bj1949007
Copy link

http://olime.baidu.com/py?input=pyll&inputtype=py&bg=0&ed=20&result=hanzi&resultcoding=utf-8&ch_en=0&clientinfo=web&version=1

这是一个关键字为pyll(input=pyll)的网络请求,可以测试一下,看能否支持一下

再一个发布的话能否提供编译好的rime.dll,方便快速测试,因为编译这个有一定难度,目前还不会

@hchunhui
Copy link
Contributor Author

编译好的 rime.dll 可以在 https://github.com/hchunhui/librime-lua/releases 找到。

云输入暂时做不了。主要问题是 librime 目前好像还做不到异步,在 translator 里面直接请求网络会塞死整个输入法。要做的话,也许要先动 librime 的代码(也许前端也要改?),然后再在 lua 层面上支持。
具体怎么改我暂时没思路。不知大家有没有想法?

@lotem
Copy link
Member

lotem commented Mar 25, 2019

@hchunhui 不要等網絡請求。本地結果應迅速展示,異步結果可以由前端發送無動作按鍵輪詢。或者利用提前請求得到的匹配結果。
這個話題建議另開貼討論,並參考以前的討論。

@lotem
Copy link
Member

lotem commented Mar 25, 2019

@hchunhui 另外我想和你討論的是如何自動構建、發佈。
我也做了一個插件,鑑於Windows上插件用法的限制,可能需要配置一個帶插件的librime CI工程。
@Prcuvu 也出出主意。

@hchunhui
Copy link
Contributor Author

@lotem 谢谢,云输入后面另外开贴,再慢慢弄。

关于构建,我觉得要先明确一下需求。
现在 windows 下的限制是不能动态加载插件,只能编译时决定。
因此是打算编译一个默认带所有插件的 librime ,或者是编译若干个不同插件组合的 librime 呢?

另外我在想的是有没有可能让 windows 平台也能支持动态加载。
我对现在不能动态加载的理解是,rime.dll 里面没有显式导出除 rime api 之外的符号。
我看到 cmake 有这个选项,可以在 windows 下默认导出所有符号。
不知道可不可行?

@lotem
Copy link
Member

lotem commented Mar 27, 2019

我覺得除了不帶插件的版本,再構建一個集成所有插件的版本。
只集成單個插件的話,用戶如果想嘗試不同插件的功能,就要頻繁替換rime.dll,不太方便。而且沒辦法同時使用多個插件。


現有配置下,阻礙插件動態加載的,還有一個內存管理問題。
動態加載Windows DLL可以支持,前提是所有C++代碼都動態鏈接。
目前librime的編譯選項有BUILD_STATIC=ON,其目的是靜態鏈接第三方庫和C++運行時庫,特別是後者,以免用戶在安裝輸入法之外還需下載安裝構建時所鏈接版本的VC運行時庫(msvcrtXX.dll)。Visual Studio會在開發機上安裝這些運行時庫,但用戶機器上一旦缺少對應版本的庫,動態鏈接到其上的程序就無法運行。為避免安裝麻煩或者增加安裝包的大小,我採用了靜態鏈接運行時庫的辦法。其弊端是在每一個模塊(exe或DLL)裏都有一份負責內存分配的代碼,C++對象必須在為他分配內存的模塊釋放內存,不能傳遞到其他模塊並在其他模塊釋放,否則會因為別的模塊沒有登記他的內存分配信息而崩潰。現在librime裏大量用智能指針管理的C++對象跨模塊使用都有這個風險。

@hchunhui
Copy link
Contributor Author

我覺得除了不帶插件的版本,再構建一個集成所有插件的版本。
只集成單個插件的話,用戶如果想嘗試不同插件的功能,就要頻繁替換rime.dll,不太方便。而且沒辦法同時使用多個插件。

現有配置下,阻礙插件動態加載的,還有一個內存管理問題。

@lotem
学习了,以前还真没仔细想过跨 dll 的内存管理问题。所以这个问题解决之前我还是先不折腾了。

集成所有插件的自动构建,我觉得是不是可以建一个专门的 build 仓库,里面以 submodule 的形式引用各插件和 librime 的代码,像这样:

.
├── librime@....
└── plugins
    ├── plugin1@...
    └── plugin2@...

构建脚本先把各插件移动到 plugins 目录下,然后再调用正常的流程。

另外可以考虑给 cmake 加一个选项,让 cmake 除了在 plugins 目录以外,可以搜索指定目录下的插件,这样就可以免去“移动”这一步。

@WhiteTiger-WT
Copy link

WhiteTiger-WT commented Dec 9, 2022

不过,好像有一个问题,似乎 「 lua_filter@xxx 」中的 「 xxx 」不能像内置的 「 filter 」对象一样,拥有独立的,可以被「key_binder / bindings」和 「 switches 」接纳的名称标识,从而无法在「快捷键」和「开关」中做进一步的绑定,以实现更快捷和直观的功能。
独立使用是有效的,但是绑到开关或快捷键中是无效的:

是的,现在无法实现所需的功能。
解决这个问题需要在 lua 中绑定 context 相关的函数,让 filter 能够根据 context 做不同操作。

嗯,那期待插件版的新版本吧。

今天为 filter 和 translator 加上了环境,绑定了 context 相关函数。 此功能现在可类似如下实现:

function single_char(input, env)
   b = env.engine.context:get_option("single_char")
   for cand in input:iter() do
      if (not b or utf8.len(cand.text) == 1) then
         yield(cand)
      end
   end
end

get_option 中的字符串可换为其他的开关名字,不一定与函数名相同。

还有些新增功能有空再总结。

Windows 编译版: https://ci.appveyor.com/api/buildjobs/monr1j237bymqv2j/artifacts/rime.zip

定义了一些直接上屏的快符 auto_select_pattern: ^;\w$,如 ;w 直接上屏 ;q 直接上屏 :“

开启此脚本后,;w 有效,;q 无效了,这是正常的。但想让其有效,改成下面代码似乎不行,求教!

function single_char(input, env)
   b = env.engine.context:get_option("single_char")
   for cand in input:iter() do
      if (not b or utf8.len(cand.text) == 1 or string.match(input, "^;[a-z]$") ~= nil) then
         yield(cand)
      end
   end
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants