mirror of
https://github.com/d0zingcat/rime_wanxiang.git
synced 2026-05-13 23:16:54 +00:00
285 lines
9.4 KiB
Lua
285 lines
9.4 KiB
Lua
--@amzxyz https://github.com/amzxyz/rime_wanxiang
|
||
|
||
--wanxiang_lookup: #设置归属于super_lookup.lua
|
||
--tags: [ abc ] # 检索当前tag的候选
|
||
--key: "`" # 输入中反查引导符,要添加到 speller/alphabet
|
||
--lookup: [ wanxiang_reverse ] #反查滤镜数据库,万象都合并为一个了
|
||
|
||
------------------------------------------------------------
|
||
-- 工具函数
|
||
------------------------------------------------------------
|
||
local function alt_lua_punc(s)
|
||
return s and s:gsub('([%.%+%-%*%?%[%]%^%$%(%)%%])', '%%%1') or ''
|
||
end
|
||
|
||
local function is_all_upper(s) return s:match('^%u+$') ~= nil end
|
||
local function is_all_lower(s) return s:match('^%l+$') ~= nil end
|
||
|
||
------------------------------------------------------------
|
||
-- 规则加载
|
||
------------------------------------------------------------
|
||
local function parse_and_separate_rules(schema_id)
|
||
if not schema_id or #schema_id == 0 then return nil, nil end
|
||
local schema = Schema(schema_id)
|
||
if not schema then return nil, nil end
|
||
local config = schema.config
|
||
if not config then return nil, nil end
|
||
local algebra_list = config:get_list('speller/algebra')
|
||
if not algebra_list or algebra_list.size == 0 then return nil, nil end
|
||
|
||
local main_rules, xlit_rules = {}, {}
|
||
for i = 0, algebra_list.size - 1 do
|
||
local rule = algebra_list:get_value_at(i).value
|
||
if rule and #rule > 0 then
|
||
if rule:match("^xlit/HSPZN/") then
|
||
table.insert(xlit_rules, rule)
|
||
else
|
||
table.insert(main_rules, rule)
|
||
end
|
||
end
|
||
end
|
||
if #main_rules == 0 and #xlit_rules == 0 then return nil, nil end
|
||
return main_rules, xlit_rules
|
||
end
|
||
|
||
local function get_schema_rules(env)
|
||
local config = env.engine.schema.config
|
||
local db_list = config:get_list("wanxiang_lookup/lookup")
|
||
if not db_list or db_list.size == 0 then return {}, {} end
|
||
local schema_id = db_list:get_value_at(0).value
|
||
if not schema_id or #schema_id == 0 then return {}, {} end
|
||
local main_rules, xlit_rules = parse_and_separate_rules(schema_id)
|
||
if not main_rules and not xlit_rules then return {}, {} end
|
||
return main_rules or {}, xlit_rules or {}
|
||
end
|
||
|
||
------------------------------------------------------------
|
||
-- 核心逻辑
|
||
------------------------------------------------------------
|
||
local function expand_code_variant(main_projection, xlit_projection, part)
|
||
local out, seen = {}, {}
|
||
local function add(s)
|
||
if s and #s > 0 and not seen[s] then
|
||
seen[s] = true
|
||
out[#out + 1] = s
|
||
end
|
||
end
|
||
|
||
add(part)
|
||
if main_projection then
|
||
local p = main_projection:apply(part, true)
|
||
if p and #p > 0 then add(p) end
|
||
end
|
||
|
||
local base = {}
|
||
for i = 1, #out do
|
||
local elem = out[i]
|
||
if is_all_lower(elem) then base[#base + 1] = elem end
|
||
end
|
||
for _, s in ipairs(base) do
|
||
if #s == 4 and is_all_lower(s) then
|
||
add(s:sub(1,1) .. s:sub(3,3))
|
||
end
|
||
end
|
||
if is_all_upper(part) and xlit_projection then
|
||
local xlit_result = xlit_projection:apply(part, true)
|
||
if xlit_result and #xlit_result > 0 then add(xlit_result) end
|
||
end
|
||
return out
|
||
end
|
||
|
||
local function build_reverse_group(main_projection, xlit_projection, db_table, text)
|
||
local group, seen = {}, {}
|
||
for _, db in ipairs(db_table) do
|
||
local code = db:lookup(text)
|
||
if code and #code > 0 then
|
||
for part in code:gmatch('%S+') do
|
||
local variants = expand_code_variant(main_projection, xlit_projection, part)
|
||
for _, v in ipairs(variants) do
|
||
if not seen[v] then
|
||
seen[v] = true
|
||
group[#group + 1] = v
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
return group
|
||
end
|
||
|
||
local function group_match(group, fuma)
|
||
if not group then return false end
|
||
for i = 1, #group do
|
||
if tostring(group[i]):sub(1, #fuma) == fuma then return true end
|
||
end
|
||
return false
|
||
end
|
||
|
||
------------------------------------------------------------
|
||
-- 过滤器主体
|
||
------------------------------------------------------------
|
||
local f = {}
|
||
|
||
function f.init(env)
|
||
local config = env.engine.schema.config
|
||
env.if_reverse_lookup = false
|
||
env.db_table = nil
|
||
|
||
local db = config:get_list("wanxiang_lookup/lookup")
|
||
if db and db.size > 0 then
|
||
env.db_table = {}
|
||
for i = 0, db.size - 1 do
|
||
table.insert(env.db_table, ReverseLookup(db:get_value_at(i).value))
|
||
end
|
||
env.if_reverse_lookup = true
|
||
else
|
||
return
|
||
end
|
||
|
||
local main_rules, xlit_rules = get_schema_rules(env)
|
||
env.main_projection = (type(main_rules) == 'table' and #main_rules > 0) and Projection() or nil
|
||
if env.main_projection then env.main_projection:load(main_rules) end
|
||
|
||
env.xlit_projection = (type(xlit_rules) == 'table' and #xlit_rules > 0) and Projection() or nil
|
||
if env.xlit_projection then env.xlit_projection:load(xlit_rules) end
|
||
|
||
env.search_key_str = config:get_string('wanxiang_lookup/key') or '`'
|
||
env.search_key_alt = alt_lua_punc(env.search_key_str)
|
||
|
||
local tag = config:get_list('wanxiang_lookup/tags')
|
||
if tag and tag.size > 0 then
|
||
env.tag = {}
|
||
for i = 0, tag.size - 1 do
|
||
table.insert(env.tag, tag:get_value_at(i).value)
|
||
end
|
||
else
|
||
env.tag = { 'abc' }
|
||
end
|
||
|
||
env.notifier = env.engine.context.select_notifier:connect(function(ctx)
|
||
local input = ctx.input
|
||
local code = input:match('^(.-)' .. env.search_key_alt)
|
||
if (not code or #code == 0) then return end
|
||
local preedit = ctx:get_preedit()
|
||
local no_search_string = input:match('^(.-)' .. env.search_key_alt)
|
||
local edit = preedit.text:match('^(.-)' .. env.search_key_alt)
|
||
if edit and edit:match('[%w;]') then
|
||
ctx.input = no_search_string .. env.search_key_str
|
||
else
|
||
ctx.input = no_search_string
|
||
env.commit_code = no_search_string
|
||
ctx:commit()
|
||
end
|
||
end)
|
||
|
||
-- 【安全缓存】初始化
|
||
env._global_group_cache = {}
|
||
env.cache_size = 0 -- 计数器
|
||
end
|
||
|
||
function f.func(input, env)
|
||
if not env.if_reverse_lookup then
|
||
for cand in input:iter() do yield(cand) end
|
||
return
|
||
end
|
||
|
||
local ctx_input = env.engine.context.input
|
||
local s_start, s_end = ctx_input:find(env.search_key_alt, 1, false)
|
||
|
||
if not s_start then
|
||
for cand in input:iter() do yield(cand) end
|
||
return
|
||
end
|
||
|
||
local fuma = ctx_input:sub(s_end + 1)
|
||
|
||
-- 【惰性检查】无辅码,直接显示,不查库
|
||
if #fuma == 0 then
|
||
for cand in input:iter() do yield(cand) end
|
||
return
|
||
end
|
||
|
||
local fuma_segments = {}
|
||
for segment in fuma:gmatch('[^' .. env.search_key_alt .. ']+') do
|
||
table.insert(fuma_segments, string.lower(segment))
|
||
end
|
||
|
||
local if_single_char_first = env.engine.context:get_option('char_priority')
|
||
local long_word_cands = {}
|
||
local cache = env._global_group_cache
|
||
|
||
-- 如果缓存条目超过 3000,清空重来
|
||
-- 3000个字足以覆盖99.9%的日常输入,且仅占用极小内存
|
||
if env.cache_size > 3000 then
|
||
env._global_group_cache = {}
|
||
env.cache_size = 0
|
||
cache = env._global_group_cache -- 更新引用
|
||
end
|
||
|
||
for cand in input:iter() do
|
||
if cand.type == 'sentence' then goto skip end
|
||
|
||
local cand_text = cand.text
|
||
|
||
-- 西文跳过
|
||
local b = string.byte(cand_text, 1)
|
||
if b and b < 128 then goto skip end
|
||
|
||
local cand_len = utf8.len(cand_text)
|
||
|
||
local characters = {}
|
||
local pos = 1
|
||
for i = 1, cand_len do
|
||
local next_pos = utf8.offset(cand_text, i + 1)
|
||
local char_str = cand_text:sub(pos, next_pos and next_pos - 1)
|
||
characters[i] = char_str
|
||
pos = next_pos
|
||
|
||
-- 【全局缓存】带计数
|
||
if not cache[char_str] then
|
||
cache[char_str] = build_reverse_group(env.main_projection, env.xlit_projection, env.db_table, char_str)
|
||
env.cache_size = env.cache_size + 1 -- 增加计数
|
||
end
|
||
end
|
||
|
||
local ok = true
|
||
if #fuma_segments == 1 and cand_len == 1 then
|
||
ok = group_match(cache[characters[1]], fuma_segments[1])
|
||
elseif #fuma_segments > 0 and cand_len > 1 then
|
||
local match_count = (#fuma_segments < cand_len) and #fuma_segments or cand_len
|
||
for i = 1, match_count do
|
||
if not group_match(cache[characters[i]], fuma_segments[i]) then
|
||
ok = false
|
||
break
|
||
end
|
||
end
|
||
else
|
||
if cand_len < #fuma_segments then ok = false end
|
||
end
|
||
|
||
if ok then
|
||
if if_single_char_first and cand_len > 1 then
|
||
table.insert(long_word_cands, cand)
|
||
else
|
||
yield(cand)
|
||
end
|
||
end
|
||
::skip::
|
||
end
|
||
|
||
for _, c in ipairs(long_word_cands) do yield(c) end
|
||
end
|
||
|
||
function f.tags_match(seg, env)
|
||
for _, v in ipairs(env.tag) do if seg.tags[v] then return true end end
|
||
return false
|
||
end
|
||
|
||
function f.fini(env)
|
||
if env.if_reverse_lookup and env.notifier then env.notifier:disconnect() end
|
||
env.db_table = nil
|
||
env._global_group_cache = nil
|
||
collectgarbage('collect')
|
||
end
|
||
|
||
return f |