chore:新的根节点

This commit is contained in:
amzxyz
2026-01-21 17:43:36 +08:00
commit 274c2e8d00
94 changed files with 3245054 additions and 0 deletions

734
lua/super_sequence.lua Normal file
View File

@@ -0,0 +1,734 @@
-- 万象拼音 · 手动自由排序
-- 核心规则: 向前移动 = "Control+j", 向后移动 = "Control+k", 重置 = "Control+l", 置顶 = "Control+p
-- 1) p>0有效排序DB upsert + 导出)
-- 2) p=0墓碑DB 删除 + 导出墓碑)
-- 3) 初始化:先 flush 本机增量到导出 → 外部合并(所有设备文件+本机DBLWW) → 重写本机导出(含墓碑) → 导入覆盖DBp=0删除键不导入
-- 4) 关于同步的使用方法先点击同步确保同步目录已经创建建立sequence_device_list.txt设备清单内部填写不同设备导出文件名称
-- sequence_ff9b2823-8733-44bb-a497-daf382b74ca5.txt
-- sequence_deepin.txt
-- 可能是自定义名称,可能是随机串号
-- sequence_开头后面跟着installation_id这个参数来自用户目录installation.yaml
-- 清单有什么文件就会读取什么文件
-- 仅使用 installation.yaml 的 sync_dir读不到就回退到 user_dir/sync
-- 核心规则: 向前移动 = "Control+j", 向后移动 = "Control+k", 重置 = "Control+l", 置顶 = "Control+p"
-- 1) p>0有效排序DB upsert + 导出)
-- 2) p=0墓碑DB 删除 + 导出墓碑)
-- 3) 初始化:先 flush 本机增量到导出 → 外部合并(所有设备文件+本机DBLWW) → 重写本机导出(含墓碑) → 导入覆盖DBp=0删除键不导入
-- 4) 同步路径策略:能从 installation.yaml 读取到 sync_dir 就用它;读不到才用默认 user_dir/sync
local wanxiang = require("wanxiang")
local userdb = require("lib/userdb")
------------------------------------------------------------
-- 一、常量与键位
------------------------------------------------------------
local DEFAULT_SEQ_KEY = { up = "Control+j", down = "Control+k", reset = "Control+l", pin = "Control+p" }
local SYNC_FILE_PREFIX, SYNC_FILE_SUFFIX = "sequence", ".txt"
-- 运行期是否立刻写出到导出文件(只在重新部署时写出→设为 false
local RUNTIME_EXPORT = false
-- ☆☆ 前向声明,避免被当作全局导致 nil ☆☆
local _normalize_path, _is_abs_path, _path_join, _manifest_path
------------------------------------------------------------
-- 二、通用工具(仅处理 "\" 与 "\\", 统一成 "/"
------------------------------------------------------------
_normalize_path = function(p)
if not p or p == "" then return "" end
if p:sub(1, 2) == "\\\\" then
-- UNC\\server\share\foo -> //server/share/foo
return "//" .. p:sub(3):gsub("\\", "/"):gsub("/+", "/")
else
-- 普通D:\dir\\file -> D:/dir/file
return p:gsub("\\", "/"):gsub("/+", "/")
end
end
_is_abs_path = function(p)
p = _normalize_path(p)
return p:sub(1, 2) == "//" or p:match("^[A-Za-z]:/")
end
_path_join = function(a, b)
a = _normalize_path(a)
b = _normalize_path(b)
if not a or a == "" then return b end
if not b or b == "" then return a end
if _is_abs_path(b) then return b end
if a:sub(-1) ~= "/" then a = a .. "/" end
return a .. b
end
_manifest_path = function(dir)
return _path_join(dir, "sequence_device_list.txt")
end
local function _read_lines(path)
local t, f = {}, io.open(path, "r")
if not f then return t end
for line in f:lines() do t[#t + 1] = line end
f:close()
return t
end
local function _write_lines(path, lines)
local f = io.open(path, "w"); if not f then return false end
for _, line in ipairs(lines) do f:write(line, "\n") end
f:close()
return true
end
local function _trim(s) return (s:gsub("^%s+", ""):gsub("%s+$", "")) end
local function _file_exists(path)
if not path or path == "" then return false end
local f = io.open(path, "r"); if f then f:close(); return true end
return false
end
------------------------------------------------------------
-- 三、安装信息 & 同步目录(仅看 YAML读不到就默认
------------------------------------------------------------
local function _read_installation_yaml()
local user_dir = rime_api.get_user_data_dir()
if not user_dir or user_dir == "" then return nil, nil end
local path = _path_join(user_dir, "installation.yaml")
local f = io.open(path, "r"); if not f then return nil, nil end
local installation_id, sync_dir
for line in f:lines() do
line = line:gsub("%s+#.*$", "")
local key, val = line:match("^%s*([%w_]+)%s*:%s*(.+)$")
if key and val then
-- 去引号
val = val:gsub('^%s*"(.*)"%s*$', "%1"):gsub("^%s*'(.*)'%s*$", "%1")
val = val:gsub("^%s+", ""):gsub("%s+$", "")
if key == "installation_id" then
installation_id = val
elseif key == "sync_dir" then
sync_dir = _normalize_path(val)
end
end
end
f:close()
return installation_id, sync_dir
end
-- 只看 installation.yaml读到就用读不到就 user_dir/sync
local function _sync_dir()
local user_dir = rime_api.get_user_data_dir() or ""
local _, ysync = _read_installation_yaml()
local function fix(x)
if not x or x == "" then return "" end
if x == "sync" then
return (user_dir ~= "" and _path_join(user_dir, "sync")) or "sync"
end
return _normalize_path(x)
end
if ysync and ysync ~= "" then
return fix(ysync)
end
return _path_join(user_dir, "sync")
end
local function _sync_ready()
local install_id, ysync = _read_installation_yaml()
local user_dir = rime_api.get_user_data_dir() or ""
local dir
if ysync and ysync ~= "" then
dir = _normalize_path(ysync)
if dir == "sync" then dir = _path_join(user_dir, "sync") end
else
dir = _path_join(user_dir, "sync")
end
local ok = (install_id and install_id ~= "") and (dir and dir ~= "")
return ok, dir, install_id
end
local function _detect_device_name()
local installation_id = select(1, _read_installation_yaml())
local function _san(s) return tostring(s):gsub("[%s/\\:%*%?\"<>|]", "_") end
if installation_id and installation_id ~= "" then return _san(installation_id) end
local dir = _sync_dir()
for _, raw in ipairs(_read_lines(_manifest_path(dir))) do
local name = _trim(raw or "")
local m = name:match("^sequence_(.+)%.txt$")
if m and not _is_abs_path(name) then return _san(m) end
end
return "device"
end
------------------------------------------------------------
-- 四、时间
------------------------------------------------------------
local function get_timestamp()
local ms = type(rime_api.get_time_ms) == "function" and tonumber(rime_api.get_time_ms()) or nil
return ms and (os.time() + ms / 1000.0) or os.time()
end
------------------------------------------------------------
-- 五、DB 与状态
------------------------------------------------------------
local seq_db = userdb.LevelDb("lua/sequence")
local seq_property = {
ADJUST_KEY = "sequence_adjustment_code",
}
---@param context Context
function seq_property.get(context)
return context:get_property(seq_property.ADJUST_KEY)
end
---@param context Context
function seq_property.reset(context)
local code = seq_property.get(context)
if code ~= nil and code ~= "" then
context:set_property(seq_property.ADJUST_KEY, "")
end
end
local curr_state = {}
curr_state.ADJUST_MODE = { None = -1, Reset = 0, Pin = 1, Adjust = 2 }
curr_state.default = {
selected_phrase = nil, offset = 0, mode = curr_state.ADJUST_MODE.None,
highlight_index = nil, adjust_code = nil, adjust_key = nil,
dirty = false, last_dirty_ts = 0,
}
function curr_state.reset()
if curr_state.mode == curr_state.ADJUST_MODE.None then return end
for k, v in pairs(curr_state.default) do curr_state[k] = v end
end
function curr_state.is_pin_mode() return curr_state.mode == curr_state.ADJUST_MODE.Pin end
function curr_state.is_reset_mode() return curr_state.mode == curr_state.ADJUST_MODE.Reset end
function curr_state.is_adjust_mode() return curr_state.mode == curr_state.ADJUST_MODE.Adjust end
function curr_state.has_adjustment() return curr_state.mode ~= curr_state.ADJUST_MODE.None end
------------------------------------------------------------
-- 六、关键日志(精简)
------------------------------------------------------------
--[[
local function _print_sync_probe(phase)
local user_dir = tostring(rime_api.get_user_data_dir() or "")
local iid, ysync = _read_installation_yaml()
local chosen = _sync_dir()
local inst_yaml = _path_join(user_dir, "installation.yaml")
log.warning(string.format(
"[sequence][%s] installation_id=%s yaml_sync_dir=%s chosen_sync_dir=%s inst_yaml=%s exists=%s",
phase, tostring(iid), tostring(ysync), tostring(chosen),
inst_yaml, tostring(_file_exists(inst_yaml))
))
end
local function _debug_paths_once()
local dir = _sync_dir()
local device_name = _detect_device_name()
local export_name = string.format("%s_%s%s", SYNC_FILE_PREFIX, device_name, SYNC_FILE_SUFFIX)
local export_path = _path_join(dir, export_name)
log.info(string.format("[sequence] chosen_sync_dir=%s manifest_exists=%s",
tostring(dir), tostring(_file_exists(_manifest_path(dir)))))
log.info(string.format("[sequence] export_path=%s exists=%s",
tostring(export_path), tostring(_file_exists(export_path))))
end ]]--
------------------------------------------------------------
-- 七、记录解析(新格式)
------------------------------------------------------------
local function parse_adjustment_value_item(value_item)
local item, p, o, t = value_item:match("i=(.+) p=(%S+) o=(%S*) t=(%S+)")
if not item then return nil, nil end
return item, { fixed_position = tonumber(p) or 0, offset = tonumber(o) or 0, updated_at = tonumber(t) }
end
local function parse_adjustment_values(values_str)
local mp = {}
for seg in values_str:gmatch("[^\t]+") do
local item, adj = parse_adjustment_value_item(seg)
if item then mp[item] = adj end
end
return next(mp) and mp or nil
end
local function get_input_adjustments(input)
if not input or input == "" then return nil end
local value_str = seq_db:fetch(input)
return value_str and parse_adjustment_values(value_str) or nil
end
------------------------------------------------------------
-- 八、导出缓冲(去重 + 节流)
------------------------------------------------------------
local seq_data = {
status = "pending",
device_name = "device",
last_export_ts = 0,
export_interval = 1.2, -- 秒
pending_map = {}, -- key: input.."\t"..item => line
}
local function _pending_count() local n = 0; for _ in pairs(seq_data.pending_map) do n = n + 1 end; return n end
function seq_data._current_paths()
local dir = _sync_dir()
local device_name = seq_data.device_name or "device"
local export_name = string.format("%s_%s%s", SYNC_FILE_PREFIX, device_name, SYNC_FILE_SUFFIX)
local export_path = _path_join(dir, export_name)
local manifest = _manifest_path(dir)
return dir, device_name, export_name, export_path, manifest
end
function seq_data._ensure_export_file()
local ok = _sync_ready()
if not ok then
--log.info("[sequence] installation_id 或 sync_dir 缺失,跳过导出")
return false
end
local _, _, export_name, export_path, manifest = seq_data._current_paths()
if not _file_exists(manifest) then
local mf = io.open(manifest, "w"); if not mf then return false end; mf:close()
end
if not _file_exists(export_path) then
local f = io.open(export_path, "w"); if not f then return false end
local user_id = wanxiang.get_user_id()
if user_id then f:write("\001/user_id\t", user_id, "\n") end
f:write("\001/device_name\t", seq_data.device_name or "device", "\n")
f:close()
end
local names = _read_lines(manifest)
local seen = {}; for _, n in ipairs(names) do seen[_trim(n)] = true end
if not seen[export_name] then names[#names + 1] = export_name; _write_lines(manifest, names) end
return true
end
local function _enqueue_export(input, item, adj)
local k = input .. "\t" .. item
seq_data.pending_map[k] = string.format("%s\ti=%s p=%s o=%s t=%s\n",
input, item, adj.fixed_position or 0, adj.offset or 0, adj.updated_at or "")
end
function seq_data.flush_pending(max_lines)
if _pending_count() == 0 then return end
if not seq_data._ensure_export_file() then return end
local _, _, _, export_path = seq_data._current_paths()
local f = io.open(export_path, "a"); if not f then return end
local wrote = 0
for _, line in pairs(seq_data.pending_map) do
if max_lines and wrote >= max_lines then break end
f:write(line); wrote = wrote + 1
end
f:close()
seq_data.pending_map = {}
end
function seq_data.maybe_export(force)
if force then
seq_data.flush_pending(nil)
seq_data.last_export_ts = get_timestamp()
return
end
if _pending_count() == 0 then return end
local now = get_timestamp()
if now - (seq_data.last_export_ts or 0) < (seq_data.export_interval or 1.2) then return end
seq_data.flush_pending(200)
seq_data.last_export_ts = now
end
------------------------------------------------------------
-- 九、保存本机操作p=0 也导出墓碑运行期不写盘DB 暂存墓碑以便重部署覆盖)
------------------------------------------------------------
local function save_adjustment(input, item, adjustment, no_export)
if not input or input == "" or not item or item == "" then return end
local p = tonumber(adjustment.fixed_position) or 0
local o = tonumber(adjustment.offset) or 0
local t = adjustment.updated_at
local mp = get_input_adjustments(input) or {}
if p <= 0 then
-- 关键DB 内也保留 p=0 墓碑(含时间戳),用于重部署时 LWW 覆盖外部文件
mp[item] = { fixed_position = 0, offset = o, updated_at = t }
else
mp[item] = { fixed_position = p, offset = o, updated_at = t }
end
local arr = {}
for it, a in pairs(mp) do
arr[#arr + 1] = string.format("i=%s p=%s o=%s t=%s",
it, a.fixed_position, a.offset or 0, a.updated_at or "")
end
seq_db:update(input, table.concat(arr, "\t"))
-- 仅在允许运行期写出时才入队(默认 RUNTIME_EXPORT=false不入队
if (not no_export) and RUNTIME_EXPORT then
_enqueue_export(input, item, { fixed_position = p, offset = o, updated_at = t }) -- 包含 p=0 墓碑
end
end
------------------------------------------------------------
-- 十、合并器:收集“所有文件 + 本机DB”按 t 取最新(包含 p=0
------------------------------------------------------------
local function _keep_latest(latest, input, item, adj)
latest[input] = latest[input] or {}
local prev = latest[input][item]
if (not prev) or ((adj.updated_at or 0) > (prev.updated_at or 0)) then
latest[input][item] = {
fixed_position = tonumber(adj.fixed_position) or 0,
offset = tonumber(adj.offset) or 0,
updated_at = tonumber(adj.updated_at) or 0
}
end
end
local function collect_latest_from_all_sources()
local latest = {}
-- A) 本机 DB包含 p=0 墓碑:让 DB 能覆盖外部)
seq_db:query_with("", function(key, value)
local mp = parse_adjustment_values(value)
if mp then
for item, a in pairs(mp) do
_keep_latest(latest, key, item, a)
end
end
end)
-- B) 清单里的所有导出文件(包含 p=0
local dir = _sync_dir()
local names = _read_lines(_manifest_path(dir))
for _, raw in ipairs(names) do
local name = _trim(raw or "")
if name ~= "" and name:sub(1, 1) ~= "#" then
if name:sub(1, #SYNC_FILE_PREFIX) == SYNC_FILE_PREFIX
and name:sub(-#SYNC_FILE_SUFFIX) == SYNC_FILE_SUFFIX then
local path = _is_abs_path(name) and name or _path_join(dir, name)
local f = io.open(path, "r")
if f then
for line in f:lines() do
if line ~= "" and line:sub(1, 2) ~= "\001" .. "/" then
local key, value = line:match("^(%S+)\t(.+)$")
if key and value then
local item, adj1 = parse_adjustment_value_item(value)
if item then
_keep_latest(latest, key, item, adj1)
else
local mp = parse_adjustment_values(value)
if mp then for it, a in pairs(mp) do _keep_latest(latest, key, it, a) end end
end
end
end
end
f:close()
end
end
end
end
return latest
end
------------------------------------------------------------
-- 十一、把“合并结果”重写到我机导出(含 p=0
------------------------------------------------------------
local function rewrite_export_from_latest(latest)
local ok = _sync_ready()
if not ok then return end
local dir = _sync_dir()
local installation_id = select(1, _read_installation_yaml())
local device_name = (installation_id and installation_id ~= "") and tostring(installation_id):gsub("[%s/\\:%*%?\"<>|]", "_") or "device"
local export_name = string.format("%s_%s%s", SYNC_FILE_PREFIX, device_name, SYNC_FILE_SUFFIX)
local export_path = _path_join(dir, export_name)
local manifest = _manifest_path(dir)
if not _file_exists(manifest) then local mf = io.open(manifest, "w"); if mf then mf:close() end end
do
local names = _read_lines(manifest); local seen = {}; for _, n in ipairs(names) do seen[_trim(n)] = true end
if not seen[export_name] then names[#names + 1] = export_name; _write_lines(manifest, names) end
end
local f = io.open(export_path, "w"); if not f then return end
local user_id = wanxiang.get_user_id()
if user_id then f:write("\001/user_id\t", user_id, "\n") end
f:write("\001/device_name\t", device_name, "\n")
local inputs = {}
for input, _ in pairs(latest) do inputs[#inputs + 1] = input end
table.sort(inputs)
for _, input in ipairs(inputs) do
local items, keys = latest[input], {}
for item, _ in pairs(items) do keys[#keys + 1] = item end
table.sort(keys)
for _, item in ipairs(keys) do
local a = items[item]
f:write(string.format("%s\ti=%s p=%s o=%s t=%s\n",
input, item, a.fixed_position or 0, a.offset or 0, a.updated_at or ""))
end
end
f:close()
--log.info(string.format("[sequence] export rewritten (merged LWW, incl tombstones): %s", export_path))
end
------------------------------------------------------------
-- 十二、把“合并结果”导入覆盖 DBp<=0 删)
------------------------------------------------------------
local function apply_latest_to_db(latest)
local updated_keys = 0
for input, kv in pairs(latest) do
local keep = {}
for item, a in pairs(kv) do
if (tonumber(a.fixed_position) or 0) > 0 then
keep[item] = { fixed_position = a.fixed_position, offset = a.offset or 0, updated_at = a.updated_at }
end
end
if next(keep) == nil then
seq_db:erase(input)
else
local arr = {}
for item, a in pairs(keep) do
arr[#arr + 1] = string.format("i=%s p=%s o=%s t=%s", item, a.fixed_position, a.offset or 0, a.updated_at or "")
end
seq_db:update(input, table.concat(arr, "\t"))
end
updated_keys = updated_keys + 1
end
--log.info(string.format("[sequence] DB applied from merged LWW: %d keys", updated_keys))
end
------------------------------------------------------------
-- 十三、初始化先导出→合并→重写导出→导入DB
------------------------------------------------------------
local function init_once()
-- 1) 先导出:把本机 pending 增量写出去如果是旧版本留下的队列这里可一次性落盘RUNTIME_EXPORT 与此无关)
seq_data._ensure_export_file()
seq_data.maybe_export(true)
-- 2) 外部合并(所有设备文件 + 本机 DBLWW含 p=0
local latest = collect_latest_from_all_sources()
-- 3) 用合并结果重写我机导出(包含 p=0——始终写盘
rewrite_export_from_latest(latest)
-- 4) 导入合并结果覆盖 DBp<=0 删)
apply_latest_to_db(latest)
end
------------------------------------------------------------
-- 十四、PipelineP / F
------------------------------------------------------------
local P = {}
function P.init(env)
seq_db:open()
seq_data.device_name = _detect_device_name()
--_print_sync_probe("init") -- 关键:一次性输出最终使用的 sync_dir
--_debug_paths_once() -- 关键:简要输出导出与清单路径是否存在
init_once()
end
local function process_adjustment(context)
local c = context:get_selected_candidate()
curr_state.selected_phrase = c and c.text or nil
context:refresh_non_confirmed_composition()
if context.highlight and curr_state.highlight_index and curr_state.highlight_index > 0 then
context:highlight(curr_state.highlight_index)
end
end
-- 辅助:判断是否单个 ASCII 小写字母
local function _is_single_lowercase_letter(s)
return type(s) == "string" and #s == 1 and s:match("^[a-z]$") ~= nil
end
function P.func(key_event, env)
local context = env.engine.context
-- 不要在早期就重置 offset保持原代码行为
curr_state.reset()
local selected_cand = context:get_selected_candidate()
if not context:has_menu() or not selected_cand or not selected_cand.text then
return wanxiang.RIME_PROCESS_RESULTS.kNoop
end
-- 先判断当前的 adjust_code与 extract_adjustment_code 的逻辑一致)
local function get_adjust_code()
if wanxiang.is_function_mode_active(context) then
local code = seq_property.get(context)
if code and code ~= "" then return code end
return nil
end
return context.input:sub(1, context.caret_pos)
end
local adjust_code = get_adjust_code()
-- 如果不是 function-mode 且 adjust_code 是单个小写字母,则按键不应改变 curr_state.offset因为单字母存在时间复杂度
if (not wanxiang.is_function_mode_active(context)) and _is_single_lowercase_letter(adjust_code) then
return wanxiang.RIME_PROCESS_RESULTS.kNoop
end
local key_repr = key_event:repr()
local function get_seq_key(type)
return env.engine.schema.config:get_string("key_binder/sequence/" .. type) or DEFAULT_SEQ_KEY[type]
end
if key_repr == get_seq_key("up") then
curr_state.offset = -1; curr_state.mode = curr_state.ADJUST_MODE.Adjust
elseif key_repr == get_seq_key("down") then
curr_state.offset = 1; curr_state.mode = curr_state.ADJUST_MODE.Adjust
elseif key_repr == get_seq_key("reset") then
curr_state.offset = nil; curr_state.mode = curr_state.ADJUST_MODE.Reset
elseif key_repr == get_seq_key("pin") then
curr_state.offset = nil; curr_state.mode = curr_state.ADJUST_MODE.Pin
else
return wanxiang.RIME_PROCESS_RESULTS.kNoop
end
process_adjustment(context)
return wanxiang.RIME_PROCESS_RESULTS.kAccepted
end
local F = {}
function F.fini()
-- 退出时不落盘(仅在重新部署 init_once 重写导出)
if RUNTIME_EXPORT then
seq_data.maybe_export(true)
end
end
local function apply_prev_adjustment(cands, prev)
local list = {}
for _, info in pairs(prev or {}) do
if info.raw_position then info.from_position = info.raw_position; table.insert(list, info) end
end
table.sort(list, function(a, b) return (a.updated_at or 0) < (b.updated_at or 0) end)
local n = #cands
for i, record in ipairs(list) do
local fromp = record.from_position
if fromp and (record.fixed_position or 0) > 0 then
local top = (record.offset == 0) and record.fixed_position or (record.raw_position + record.offset)
if top < 1 then top = 1 elseif top > n then top = n end
if fromp ~= top then
local cand = table.remove(cands, fromp)
table.insert(cands, top, cand)
local lo, hi = math.min(fromp, top), math.max(fromp, top)
for j = i, #list do
local r = list[j]
if lo <= r.from_position and r.from_position <= hi then
r.from_position = r.from_position + ((top < fromp) and 1 or -1)
end
end
end
end
end
end
local function apply_curr_adjustment(candidates, curr_adjustment)
if curr_adjustment == nil then return end
---@type integer | nil
local from_position = nil
for position, cand in ipairs(candidates) do
if cand.text == curr_state.selected_phrase then
from_position = position
break
end
end
if from_position == nil then return end
local to_position = from_position
if curr_state.is_adjust_mode() then
to_position = from_position + curr_state.offset
curr_adjustment.offset = to_position - curr_adjustment.raw_position
curr_adjustment.fixed_position = to_position
local min_position, max_position = 1, #candidates
if from_position ~= to_position then
if to_position < min_position then
to_position = min_position
elseif to_position > max_position then
to_position = max_position
end
local candidate = table.remove(candidates, from_position)
table.insert(candidates, to_position, candidate)
-- 运行期仅写 DB不入导出队列
save_adjustment(curr_state.adjust_code, curr_state.adjust_key, curr_adjustment, true)
end
end
curr_state.highlight_index = to_position - 1
end
local function extract_adjustment_code(context)
if wanxiang.is_function_mode_active(context) then
local code = seq_property.get(context)
if code and code ~= "" then return code end
return nil
end
return context.input:sub(1, context.caret_pos)
end
function F.func(input, env)
local function original_list() for cand in input:iter() do yield(cand) end end
local context = env.engine.context
local adjustment_allowed = not (wanxiang.is_function_mode_active(context) and seq_property.get(context) == nil)
if not adjustment_allowed then
--log.warning("[sequence] 当前指令不支持手动排序")
return original_list()
end
local adjust_code = extract_adjustment_code(context)
if not adjust_code then return original_list() end
local prev_adjustments = get_input_adjustments(adjust_code)
local curr_adjustment = curr_state.has_adjustment() and { fixed_position = 0, offset = 0, updated_at = get_timestamp() } or nil
if (not curr_adjustment) and (not prev_adjustments) then return original_list() end
local cands, seen = {}, {}
local is_fun_mode = wanxiang.is_function_mode_active(context)
local pos = 0
for candidate in input:iter() do
local phrase = candidate.text
if not seen[phrase] then
seen[phrase] = true; pos = pos + 1; table.insert(cands, candidate)
local curr_key = is_fun_mode and tostring(pos - 1) or phrase
if curr_adjustment and curr_state.selected_phrase == phrase then
curr_state.adjust_code = adjust_code
curr_state.adjust_key = curr_key
curr_adjustment.raw_position = pos
end
if prev_adjustments and prev_adjustments[curr_key] then
prev_adjustments[curr_key].raw_position = pos
end
end
end
prev_adjustments = prev_adjustments or {}
-- 非位移:置顶/重置立即仅保存到 DB不入队
if curr_adjustment and not curr_state.is_adjust_mode() then
curr_adjustment.offset = 0
local key = tostring(curr_state.adjust_key)
if curr_state.is_reset_mode() then
curr_adjustment.fixed_position = 0
prev_adjustments[key] = nil
save_adjustment(curr_state.adjust_code, curr_state.adjust_key, curr_adjustment, true)
elseif curr_state.is_pin_mode() then
curr_adjustment.fixed_position = 1
prev_adjustments[key] = curr_adjustment
save_adjustment(curr_state.adjust_code, curr_state.adjust_key, curr_adjustment, true)
end
end
apply_prev_adjustment(cands, prev_adjustments)
apply_curr_adjustment(cands, curr_adjustment)
for _, cand in ipairs(cands) do yield(cand) end
-- 运行期不写盘;如需调试可改为 if RUNTIME_EXPORT then ... end
if RUNTIME_EXPORT and (not curr_state.is_reset_mode()) then
seq_data.maybe_export(false)
end
end
return { P = P, F = F }