feat: 支持导入自定义辅助码,内置“加加”更名为“拼音加加” (#477)
* feat(engine): 支持 helpcodes/custom 下的自定义辅助码 custom 目录里的每个 .txt 都是一套方案,方案名为 custom/<文件名>。 文件开头的 "# name:" / "# name_en:" 注释给出中英文显示名,缺省用文件名; 兼容 BOM、CRLF 和全角冒号。方案名不能跳出 custom 目录。 * feat(engine): WebView 契约新增 openHelpcodeDirectory 设置页请求打开自定义辅助码目录,仅允许 settings 页发送。同步 ui-html/webview2/shared。 * feat(server): 设置页列出自定义辅助码并可打开其目录 配置快照带上 helpcode.custom_schemas 与 helpcode.custom_directory,每次都会重新扫描; 方案文件被删除或改名后回退到 lantian。两个设置宿主都会处理 openHelpcodeDirectory。 * feat(ui-html): 辅助码设置显示拼音加加与自定义方案 内置方案“加加”改名为“拼音加加”。自定义方案追加在内置方案之后;新增说明区, 提供“打开目录”和“刷新方案”两个按钮,刷新完成后弹出提示。 * feat(installer): 升级与迁移数据目录时保留自定义辅助码 升级清理时只清掉 helpcodes 里的内置表,保留 custom 子目录;更换数据目录时一并迁移; 安装时建出 helpcodes\custom。
This commit is contained in:
1 parent
8b72f0d8da
commit
4375d80414
24 files changed
+522
-28
No files matched your search
@@ -30,21 +30,144 @@ bool is_han_code_point(std::uint32_t code_point)
|
||||
(code_point >= 0x20000 && code_point <= 0x2FA1F) || (code_point >= 0x30000 && code_point <= 0x323AF);
|
||||
}
|
||||
|
||||
constexpr char kUtf8Bom[] = "\xEF\xBB\xBF";
|
||||
|
||||
void strip_line(std::string &line, bool first_line)
|
||||
{
|
||||
if (first_line && line.rfind(kUtf8Bom, 0) == 0)
|
||||
line.erase(0, 3);
|
||||
if (!line.empty() && line.back() == '\r')
|
||||
line.pop_back();
|
||||
}
|
||||
|
||||
std::string trim(const std::string &text)
|
||||
{
|
||||
const auto begin = text.find_first_not_of(" \t");
|
||||
if (begin == std::string::npos)
|
||||
return {};
|
||||
const auto end = text.find_last_not_of(" \t");
|
||||
return text.substr(begin, end - begin + 1);
|
||||
}
|
||||
|
||||
// Empty when the schema is not a well-formed custom schema. The stem must stay a single file name
|
||||
// inside the custom directory.
|
||||
std::string custom_schema_stem(const std::string &schema)
|
||||
{
|
||||
const std::string prefix = HelpcodeUtils::kCustomHelpcodeSchemaPrefix;
|
||||
if (schema.size() <= prefix.size() || schema.compare(0, prefix.size(), prefix) != 0)
|
||||
return {};
|
||||
std::string stem = schema.substr(prefix.size());
|
||||
if (stem.front() == '.' || stem.find_first_of("/\\:*?\"<>|") != std::string::npos)
|
||||
return {};
|
||||
return stem;
|
||||
}
|
||||
|
||||
std::filesystem::path custom_schema_file(const std::filesystem::path &resources, const std::string &stem)
|
||||
{
|
||||
return HelpcodeUtils::custom_helpcode_directory(resources) / metasequoia::path_from_utf8((stem + ".txt").c_str());
|
||||
}
|
||||
|
||||
bool is_builtin_schema(const std::string &schema)
|
||||
{
|
||||
return std::any_of(metasequoia::assets::helpcodes.begin(), metasequoia::assets::helpcodes.end(),
|
||||
[&](const auto &entry) { return entry.schema == schema; });
|
||||
}
|
||||
|
||||
// Reads the leading comment block: "# name: 中文名" and "# name_en: English name".
|
||||
void read_custom_header(const std::filesystem::path &file, HelpcodeUtils::CustomHelpcodeSchema &schema)
|
||||
{
|
||||
std::ifstream input(file);
|
||||
std::string line;
|
||||
bool first_line = true;
|
||||
while (std::getline(input, line))
|
||||
{
|
||||
strip_line(line, first_line);
|
||||
first_line = false;
|
||||
line = trim(line);
|
||||
if (line.empty())
|
||||
continue;
|
||||
if (line.front() != '#')
|
||||
break;
|
||||
const std::string body = trim(line.substr(1));
|
||||
auto separator = body.find(':');
|
||||
auto separator_size = 1;
|
||||
if (const auto full_width = body.find("\xEF\xBC\x9A"); full_width < separator) // U+FF1A ':'
|
||||
{
|
||||
separator = full_width;
|
||||
separator_size = 3;
|
||||
}
|
||||
if (separator == std::string::npos)
|
||||
continue;
|
||||
const std::string key = boost::algorithm::to_lower_copy(trim(body.substr(0, separator)));
|
||||
const std::string value = trim(body.substr(separator + separator_size));
|
||||
if (key == "name")
|
||||
schema.name = value;
|
||||
else if (key == "name_en")
|
||||
schema.name_en = value;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace HelpcodeUtils
|
||||
{
|
||||
std::filesystem::path custom_helpcode_directory(const std::filesystem::path &resources)
|
||||
{
|
||||
return resources / "helpcodes" / "custom";
|
||||
}
|
||||
|
||||
std::vector<CustomHelpcodeSchema> list_custom_helpcode_schemas(const std::filesystem::path &resources)
|
||||
{
|
||||
std::vector<CustomHelpcodeSchema> result;
|
||||
std::error_code error;
|
||||
for (std::filesystem::directory_iterator it(custom_helpcode_directory(resources), error), end; !error && it != end;
|
||||
it.increment(error))
|
||||
{
|
||||
if (!it->is_regular_file(error) ||
|
||||
boost::algorithm::to_lower_copy(metasequoia::path_to_utf8(it->path().extension())) != ".txt")
|
||||
continue;
|
||||
CustomHelpcodeSchema schema;
|
||||
schema.file_stem = metasequoia::path_to_utf8(it->path().stem());
|
||||
schema.schema = kCustomHelpcodeSchemaPrefix + schema.file_stem;
|
||||
if (custom_schema_stem(schema.schema).empty())
|
||||
continue;
|
||||
read_custom_header(it->path(), schema);
|
||||
result.push_back(std::move(schema));
|
||||
}
|
||||
std::sort(result.begin(), result.end(), [](const auto &a, const auto &b) { return a.file_stem < b.file_stem; });
|
||||
return result;
|
||||
}
|
||||
|
||||
bool is_helpcode_schema_available(const std::filesystem::path &resources, const std::string &schema)
|
||||
{
|
||||
if (is_builtin_schema(schema))
|
||||
return true;
|
||||
const std::string stem = custom_schema_stem(schema);
|
||||
std::error_code error;
|
||||
return !stem.empty() && std::filesystem::is_regular_file(custom_schema_file(resources, stem), error);
|
||||
}
|
||||
|
||||
SharedKeymap load_helpcode_keymap(const std::filesystem::path &resources, const std::string &schema)
|
||||
{
|
||||
std::filesystem::path file;
|
||||
const auto found = std::find_if(metasequoia::assets::helpcodes.begin(), metasequoia::assets::helpcodes.end(),
|
||||
[&](const auto &entry) { return entry.schema == schema; });
|
||||
if (found == metasequoia::assets::helpcodes.end())
|
||||
if (found != metasequoia::assets::helpcodes.end())
|
||||
file = resources / found->path;
|
||||
else if (const std::string stem = custom_schema_stem(schema); !stem.empty())
|
||||
file = custom_schema_file(resources, stem);
|
||||
else
|
||||
throw std::invalid_argument("Unknown helpcode schema");
|
||||
auto result = std::make_shared<Keymap>();
|
||||
std::ifstream input(resources / found->path);
|
||||
std::ifstream input(file);
|
||||
std::string line;
|
||||
bool first_line = true;
|
||||
while (std::getline(input, line))
|
||||
{
|
||||
strip_line(line, first_line);
|
||||
first_line = false;
|
||||
if (!line.empty() && line.front() == '#')
|
||||
continue;
|
||||
const auto pos = line.find('=');
|
||||
if (pos == std::string::npos || pos == 0)
|
||||
continue;
|
||||
@@ -296,8 +419,8 @@ bool matches_double_helpcodes(const std::string &word, const std::string &help_c
|
||||
|
||||
bool is_supported_helpcode_schema(const std::string &schema)
|
||||
{
|
||||
return std::any_of(metasequoia::assets::helpcodes.begin(), metasequoia::assets::helpcodes.end(),
|
||||
[&](const auto &entry) { return entry.schema == schema; });
|
||||
// Syntax only: whether a custom file exists is is_helpcode_schema_available's question.
|
||||
return is_builtin_schema(schema) || !custom_schema_stem(schema).empty();
|
||||
}
|
||||
|
||||
bool select_helpcode_schema(const std::string &schema)
|
||||
|
||||
Reference in new issue
Block a user