Files
msime-windows/engine/local_modes/symbol_query.cpp
T
Lu Fan(SonnyCalcr) 3cd429b19d feat(server): 加入 S 模式符号输入与符号混输 (#1047)
符号数据改用 rime-wanxiang 的符号表与 typst/codex 符号名,源数据放在
msime-dictionary 的 sources/symbols/,替换原来的 piliapp 符号表。

- 建库:新增 symbols_db.py,生成表情面板用的 symbol_catalog(中文分类)和
  S 模式、符号混输用的 symbol_code;剔除 wanxiang-unrenderable.txt 列出的
  Windows 自带字体显示不了的符号,以及与 ① ❶ 看不出区别的 Dingbats 带圈数字
- 新增 scripts/check-symbol-rendering.py,用 Windows 字体生成上面的剔除清单
- Shift+S 进入符号模式,按万象分组码(xl、sx、szq)、数字读音(yi → 一 ① ⑴)
  或符号名(alpha、infinity)检索
- 符号混输:整串精确等于编码时混入,首个紧跟首选;读得成拼音的编码只混数字
  序号写法;双拼先按方案换算成全拼再查(小鹤 sj → san → ③)
- 表情面板符号页:中文分类,搜索把整词命中的符号列在「最佳匹配」
- 配置:utility.symbol_mode(默认开)、general.symbol_mixed_input(默认关)

Refs #795
2026-10-18 02:08:39 +08:00

111 lines
4.4 KiB
C++

#include "../contracts/assets/assets.h"
#include "symbol_query.h"
#include "local_database.h"
#include "../core/data_path.h"
#include <cstdint>
#include <memory>
#include <unordered_set>
namespace metasequoia::local_modes
{
namespace
{
struct StatementCloser
{
void operator()(sqlite3_stmt *statement) const
{
sqlite3_finalize(statement);
}
};
// 编码只有字母;统一转小写,非字母返回空串(查不到任何符号)。
std::string normalize_code(const std::string &code)
{
std::string lower;
lower.reserve(code.size());
for (const unsigned char character : code)
{
if (character >= 'a' && character <= 'z')
lower.push_back(static_cast<char>(character));
else if (character >= 'A' && character <= 'Z')
lower.push_back(static_cast<char>(character - 'A' + 'a'));
else
return {};
}
return lower;
}
LocalQueryResult query_failure(const char *diagnostic)
{
return {{}, std::string(diagnostic)};
}
// ?1 = 编码,?2 = 前缀上界,?3 = limit。返回 symbol 一列,同一符号只取排位最好的那行。
LocalQueryResult run_query(const std::string &code, const std::filesystem::path &database_path, const char *sql,
int limit)
{
const std::string lower = normalize_code(code);
if (lower.empty() || limit <= 0)
return {};
if (database_path.empty())
return query_failure("Symbol database is unavailable.");
const std::shared_ptr<sqlite3> database = open_local_database(database_path);
if (!database)
return query_failure("Symbol database is unavailable.");
sqlite3_stmt *raw_statement = nullptr;
if (sqlite3_prepare_v2(database.get(), sql, -1, &raw_statement, nullptr) != SQLITE_OK)
return query_failure("Symbol database could not be queried.");
const std::unique_ptr<sqlite3_stmt, StatementCloser> statement(raw_statement);
std::string upper_bound = lower;
upper_bound.push_back(static_cast<char>(0x7f));
// 前缀查询的同一符号可能挂在好几个编码下(a 与 ao、codex 的 alpha 与 Alpha),多取一些再去重。
const int fetch_limit = limit <= (1 << 20) ? limit * 4 : limit;
if (sqlite3_bind_text(statement.get(), 1, lower.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK ||
sqlite3_bind_text(statement.get(), 2, upper_bound.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK ||
sqlite3_bind_int(statement.get(), 3, fetch_limit) != SQLITE_OK)
return query_failure("Symbol database could not be queried.");
LocalQueryResult result;
std::unordered_set<std::string> seen;
int step_result = SQLITE_ROW;
while ((step_result = sqlite3_step(statement.get())) == SQLITE_ROW &&
result.candidates.size() < static_cast<std::size_t>(limit))
{
const auto *symbol = reinterpret_cast<const char *>(sqlite3_column_text(statement.get(), 0));
if (symbol == nullptr || !seen.insert(symbol).second)
continue;
result.candidates.emplace_back(lower, symbol, 0, CandidateSource::Symbol);
}
if (step_result != SQLITE_ROW && step_result != SQLITE_DONE)
return query_failure("Symbol database could not be queried.");
const std::size_t count = result.candidates.size();
for (std::size_t index = 0; index < count; ++index)
result.candidates[index].weight = static_cast<std::int64_t>(count - index);
return result;
}
} // namespace
LocalQueryResult query_symbols(const std::string &code, int limit)
{
return query_symbols(code, data_file_path(metasequoia::assets::other_dictionary), limit);
}
LocalQueryResult query_symbols(const std::string &code, const std::filesystem::path &database_path, int limit)
{
// 整串命中的编码排最前;其余前缀命中按编码长短,短的通常是这一类的总表(x 先出 xl 再出 xlc)。
constexpr const char *kSql = "SELECT symbol FROM symbol_code WHERE code>=?1 AND code<?2 "
"ORDER BY code IS NOT ?1,length(code),sort_order LIMIT ?3";
return run_query(code, database_path, kSql, limit);
}
LocalQueryResult query_mixed_symbols(const std::string &code, const std::filesystem::path &database_path, int limit)
{
constexpr const char *kSql = "SELECT symbol FROM symbol_code WHERE code=?1 AND code<?2 AND mixed=1 "
"ORDER BY sort_order LIMIT ?3";
return run_query(code, database_path, kSql, limit);
}
} // namespace metasequoia::local_modes