From 3cbc9cbc3e9983c106ea66e6684aa2cf53623fbd Mon Sep 17 00:00:00 2001 From: Chen Gong Date: Tue, 6 Mar 2018 00:10:44 +0800 Subject: [PATCH 01/25] fix(user_db): unwanted implicit instantiation of UserDbFormat template Fixes #188: UserDbFormat::extension not working --- src/rime/dict/level_db.cc | 8 ++++++-- src/rime/dict/user_db.cc | 13 +++++++++---- src/rime/dict/user_db.h | 17 +++-------------- src/rime/lever/user_dict_manager.cc | 9 +++------ 4 files changed, 21 insertions(+), 26 deletions(-) diff --git a/src/rime/dict/level_db.cc b/src/rime/dict/level_db.cc index 31e41b7dff..21c1066117 100644 --- a/src/rime/dict/level_db.cc +++ b/src/rime/dict/level_db.cc @@ -345,10 +345,14 @@ bool LevelDb::CommitTransaction() { } template <> -const string UserDbFormat::extension(".userdb"); +string UserDbComponent::extension() const { + return ".userdb"; +} template <> -const string UserDbFormat::snapshot_extension(".userdb.txt"); +string UserDbComponent::snapshot_extension() const { + return ".userdb.txt"; +} template <> UserDbWrapper::UserDbWrapper(const string& db_name) diff --git a/src/rime/dict/user_db.cc b/src/rime/dict/user_db.cc index 32a8a8691d..d97b304575 100644 --- a/src/rime/dict/user_db.cc +++ b/src/rime/dict/user_db.cc @@ -53,11 +53,17 @@ bool UserDbValue::Unpack(const string& value) { return true; } +static const string plain_userdb_extension(".userdb.txt"); + template <> -const string UserDbFormat::extension(".userdb.txt"); +string UserDbComponent::extension() const { + return plain_userdb_extension; +} template <> -const string UserDbFormat::snapshot_extension(".userdb.txt"); +string UserDbComponent::snapshot_extension() const { + return plain_userdb_extension; +} // key ::= code phrase @@ -110,8 +116,7 @@ bool UserDbHelper::UpdateUserInfo() { } bool UserDbHelper::IsUniformFormat(const string& file_name) { - return boost::ends_with(file_name, - UserDbFormat::snapshot_extension); + return boost::ends_with(file_name, plain_userdb_extension); } bool UserDbHelper::UniformBackup(const string& snapshot_file) { diff --git a/src/rime/dict/user_db.h b/src/rime/dict/user_db.h index 168f6af963..a3e1047fd4 100644 --- a/src/rime/dict/user_db.h +++ b/src/rime/dict/user_db.h @@ -99,27 +99,16 @@ class UserDbWrapper : public BaseDb { } }; -/// Provides information of the db file format by its base class. -template -struct UserDbFormat { - static const string extension; - static const string snapshot_extension; -}; - /// Implements a component that serves as a factory for a user db class. template class UserDbComponent : public UserDb::Component { public: - virtual Db* Create(const string& name) { + Db* Create(const string& name) override { return new UserDbWrapper(name + extension()); } - virtual string extension() const { - return UserDbFormat::extension; - } - virtual string snapshot_extension() const { - return UserDbFormat::snapshot_extension; - } + string extension() const override; + string snapshot_extension() const override; }; class UserDbMerger : public Sink { diff --git a/src/rime/lever/user_dict_manager.cc b/src/rime/lever/user_dict_manager.cc index 515521f24f..2a23f049da 100644 --- a/src/rime/lever/user_dict_manager.cc +++ b/src/rime/lever/user_dict_manager.cc @@ -67,8 +67,7 @@ bool UserDictManager::Backup(const string& dict_name) { return false; } } - string snapshot_file = - dict_name + UserDbFormat::snapshot_extension; + string snapshot_file = dict_name + user_db_component_->snapshot_extension(); return db->Backup((dir / snapshot_file).string()); } @@ -178,8 +177,7 @@ bool UserDictManager::UpgradeUserDict(const string& dict_name) { return false; } } - string snapshot_file = - dict_name + UserDbFormat::snapshot_extension; + string snapshot_file = dict_name + user_db_component_->snapshot_extension(); fs::path snapshot_path = trash / snapshot_file; return legacy_db->Backup(snapshot_path.string()) && legacy_db->Close() && @@ -199,8 +197,7 @@ bool UserDictManager::Synchronize(const string& dict_name) { } } // *.userdb.txt - string snapshot_file = - dict_name + UserDbFormat::snapshot_extension; + string snapshot_file = dict_name + user_db_component_->snapshot_extension(); for (fs::directory_iterator it(sync_dir), end; it != end; ++it) { if (!fs::is_directory(it->path())) continue; From 831ffba2ce0d3209e713168fe3da8c1a26280c70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=85=E6=88=8E=E6=B0=8F?= Date: Thu, 8 Mar 2018 00:55:30 +0800 Subject: [PATCH 02/25] fix(config_compiler): "/" mistaken as path separator in merged map key (#192) Fixes #190 --- src/rime/config/config_compiler.cc | 42 ++++++++++++++++------------- src/rime/config/config_data.cc | 43 ++++++++++++++++++++---------- 2 files changed, 53 insertions(+), 32 deletions(-) diff --git a/src/rime/config/config_compiler.cc b/src/rime/config/config_compiler.cc index 8af3de0327..30557b077a 100644 --- a/src/rime/config/config_compiler.cc +++ b/src/rime/config/config_compiler.cc @@ -155,33 +155,39 @@ inline static string StripOperator(const string& key, bool adding) { } // defined in config_data.cc -bool TraverseCopyOnWrite(an root, const string& path, - function target)> writer); +an TypeCheckedCopyOnWrite(an parent, + const string& key); +an TraverseCopyOnWrite(an head, + const string& path); -static bool EditNode(an target, +static bool EditNode(an head, const string& key, const an& value, bool merge_tree) { - DLOG(INFO) << "EditNode(" << key << "," << merge_tree << ")"; + DLOG(INFO) << "edit node: " << key << ", merge_tree: " << merge_tree; bool appending = IsAppending(key); bool merging = IsMerging(key, value, merge_tree); - auto writer = [=](an target) { - if ((appending || merging) && **target) { - DLOG(INFO) << "writer: editing node"; - return !value || - (appending && (AppendToString(target, As(value)) || - AppendToList(target, As(value)))) || - (merging && MergeTree(target, As(value))); - } else { - DLOG(INFO) << "writer: overwriting node"; - *target = value; - return true; - } - }; string path = StripOperator(key, appending || merging); DLOG(INFO) << "appending: " << appending << ", merging: " << merging << ", path: " << path; - return TraverseCopyOnWrite(target, path, writer); + auto find_target_node = + merge_tree ? &TypeCheckedCopyOnWrite : &TraverseCopyOnWrite; + auto target = find_target_node(head, path); + if (!target) { + // error finding target node; cannot write + return false; + } + if ((appending || merging) && **target) { + DLOG(INFO) << "writer: editing node"; + return !value || // no-op + (appending && (AppendToString(target, As(value)) || + AppendToList(target, As(value)))) || + (merging && MergeTree(target, As(value))); + } else { + DLOG(INFO) << "writer: overwriting node"; + *target = value; + return true; + } } bool PatchLiteral::Resolve(ConfigCompiler* compiler) { diff --git a/src/rime/config/config_data.cc b/src/rime/config/config_data.cc index 980c1042da..43783f147f 100644 --- a/src/rime/config/config_data.cc +++ b/src/rime/config/config_data.cc @@ -159,37 +159,52 @@ class ConfigDataRootRef : public ConfigItemRef { ConfigData* data_; }; -bool TraverseCopyOnWrite(an root, const string& path, - function target)> writer) { +an TypeCheckedCopyOnWrite(an parent, + const string& key) { + // special case to allow editing current node by __append: __merge: /+: /=: + if (key.empty()) { + return parent; + } + bool is_list = ConfigData::IsListItemReference(key); + auto expected_node_type = is_list ? ConfigItem::kList : ConfigItem::kMap; + an existing_node = *parent; + if (existing_node && existing_node->type() != expected_node_type) { + LOG(ERROR) << "copy on write failed; incompatible node type: " << key; + return nullptr; + } + return Cow(parent, key); +} + +an TraverseCopyOnWrite(an head, + const string& path) { DLOG(INFO) << "TraverseCopyOnWrite(" << path << ")"; if (path.empty() || path == "/") { - return writer(root); + return head; } - an head = root; vector keys = ConfigData::SplitPath(path); size_t n = keys.size(); for (size_t i = 0; i < n; ++i) { const auto& key = keys[i]; - bool is_list = ConfigData::IsListItemReference(key); - auto expected_node_type = is_list ? ConfigItem::kList : ConfigItem::kMap; - an existing_node = *head; - if (existing_node && existing_node->type() != expected_node_type) { - LOG(ERROR) << "copy on write failed; incompatible node type: " << key; - return false; + if (auto child = TypeCheckedCopyOnWrite(head, key)) { + head = child; + } else { + LOG(ERROR) << "while writing to " << path; + return nullptr; } - head = Cow(head, key); } - return writer(head); + return head; } bool ConfigData::TraverseWrite(const string& path, an item) { LOG(INFO) << "write: " << path; auto root = New(this); - return TraverseCopyOnWrite(root, path, [=](an target) { + if (auto target = TraverseCopyOnWrite(root, path)) { *target = item; set_modified(); return true; - }); + } else { + return false; + } } vector ConfigData::SplitPath(const string& path) { From cafd5c454569fff9fbde951e0bf2fbffcb6a13cb Mon Sep 17 00:00:00 2001 From: Chen Gong Date: Thu, 8 Mar 2018 23:27:22 +0800 Subject: [PATCH 03/25] fix(ConfigFileUpdate): no need to create user build if shared build is up-to-date --- src/rime/lever/deployment_tasks.cc | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/rime/lever/deployment_tasks.cc b/src/rime/lever/deployment_tasks.cc index 9b7656228f..cb4b94ffc3 100644 --- a/src/rime/lever/deployment_tasks.cc +++ b/src/rime/lever/deployment_tasks.cc @@ -386,16 +386,23 @@ static bool ConfigNeedsUpdate(Config* config) { "config_source_file", "", ".yaml" })); for (auto entry : *timestamps.AsMap()) { - fs::path source_file_path = resolver->ResolvePath(entry.first); - if (!fs::exists(source_file_path)) { - LOG(INFO) << "source file no longer exists: " << source_file_path.string(); - return true; - } auto value = As(entry.second); int recorded_time = 0; - if (!value || !value->GetInt(&recorded_time) || - recorded_time != (int) fs::last_write_time(source_file_path)) { - LOG(INFO) << "source file changed: " << source_file_path.string(); + if (!value || !value->GetInt(&recorded_time)) { + LOG(WARNING) << "invalid timestamp for " << entry.first; + return true; + } + fs::path source_file = resolver->ResolvePath(entry.first); + if (!fs::exists(source_file)) { + if (recorded_time) { + LOG(INFO) << "source file no longer exists: " << source_file.string(); + return true; + } + continue; + } + if (recorded_time != (int) fs::last_write_time(source_file)) { + LOG(INFO) << "source file " << (recorded_time ? "changed: " : "added: ") + << source_file.string(); return true; } } From 45a04dd165df08c714f3c91b0e20363245a3c032 Mon Sep 17 00:00:00 2001 From: Chen Gong Date: Thu, 8 Mar 2018 23:35:53 +0800 Subject: [PATCH 04/25] fix(SchemaUpdate): read compiled schema from shared build if there is no user build --- src/rime/lever/deployment_tasks.cc | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/rime/lever/deployment_tasks.cc b/src/rime/lever/deployment_tasks.cc index cb4b94ffc3..1431eab116 100644 --- a/src/rime/lever/deployment_tasks.cc +++ b/src/rime/lever/deployment_tasks.cc @@ -185,9 +185,8 @@ bool WorkspaceUpdate::Run(Deployer* deployer) { int failure = 0; map schemas; the resolver( - Service::instance().CreateResourceResolver({ - "schema", "", ".schema.yaml" - })); + Service::instance().CreateResourceResolver( + {"schema", "", ".schema.yaml"})); auto build_schema = [&](const string& schema_id) { if (schemas.find(schema_id) != schemas.end()) // already built return; @@ -348,9 +347,11 @@ bool SchemaUpdate::Run(Deployer* deployer) { if (verbose_) { dict_compiler.set_options(DictCompiler::kRebuild | DictCompiler::kDump); } - ResourceResolver resolver({"compiled_schema", "build/", ".schema.yaml"}); - resolver.set_root_path(user_data_path); - auto compiled_schema = resolver.ResolvePath(schema_id).string(); + the resolver( + Service::instance().CreateResourceResolver( + {"compiled_schema", "build/", ".schema.yaml"})); + resolver->set_root_path(user_data_path); + auto compiled_schema = resolver->ResolvePath(schema_id).string(); if (!dict_compiler.Compile(compiled_schema)) { LOG(ERROR) << "dictionary '" << dict_name << "' failed to compile."; return false; @@ -382,9 +383,8 @@ static bool ConfigNeedsUpdate(Config* config) { return true; } the resolver( - Service::instance().CreateResourceResolver({ - "config_source_file", "", ".yaml" - })); + Service::instance().CreateResourceResolver( + {"config_source_file", "", ".yaml"})); for (auto entry : *timestamps.AsMap()) { auto value = As(entry.second); int recorded_time = 0; From 3d28bf6facfcf1f9124dee8ae0c61bd9405c3f00 Mon Sep 17 00:00:00 2001 From: Chen Gong Date: Fri, 9 Mar 2018 23:04:34 +0800 Subject: [PATCH 05/25] chore(release tag): deprecating tag name prefix 'rime-' in favor of conventional 'v' BREAKING CHANGE: After 1.3.0 release, we'll no longer be creating tags in the format 'rime-X.Y.Z'. Downstream packagers please change automated scripts accordingly. --- .npmrc | 1 - 1 file changed, 1 deletion(-) diff --git a/.npmrc b/.npmrc index 75451031e4..2e18064938 100644 --- a/.npmrc +++ b/.npmrc @@ -1,2 +1 @@ -tag-version-prefix="rime-" message="chore(release): %s :tada:" From 424051167e0c143e3d6ce42dc0c74084c4bd53ba Mon Sep 17 00:00:00 2001 From: Chen Gong Date: Fri, 9 Mar 2018 23:35:17 +0800 Subject: [PATCH 06/25] chore(CMakeLists.txt): bump version to 1.3.0 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 48836af1e3..175188646f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ set(CMAKE_USER_MAKE_RULES_OVERRIDE_CXX ${CMAKE_CURRENT_SOURCE_DIR}/cmake/cxx_fla project(rime) cmake_minimum_required(VERSION 2.8.11) -set(rime_version 1.2.10) +set(rime_version 1.3.0) set(rime_soversion 1) add_definitions(-DRIME_VERSION="${rime_version}") From 854ad2b1d5979f95561db5f58c78ce39f68046cb Mon Sep 17 00:00:00 2001 From: Chen Gong Date: Fri, 9 Mar 2018 23:38:07 +0800 Subject: [PATCH 07/25] chore(release): 1.3.0 :tada: --- CHANGELOG.md | 25 +++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 526f3f86bc..41b5831f7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,28 @@ + +# 1.3.0 (2018-03-09) + + +### Bug Fixes + +* **CMakeLists.txt, build.bat:** install header files (public API) ([06c9e86](https://github.com/rime/librime/commit/06c9e86)) +* **config_compiler:** "/" mistaken as path separator in merged map key ([#192](https://github.com/rime/librime/issues/192)) ([831ffba](https://github.com/rime/librime/commit/831ffba)), closes [#190](https://github.com/rime/librime/issues/190) +* **ConfigFileUpdate:** no need to create user build if shared build is up-to-date ([cafd5c4](https://github.com/rime/librime/commit/cafd5c4)) +* **SchemaUpdate:** read compiled schema from shared build if there is no user build ([45a04dd](https://github.com/rime/librime/commit/45a04dd)) +* **simplifier:** fix typo ([9e1114e](https://github.com/rime/librime/commit/9e1114e)), closes [#183](https://github.com/rime/librime/issues/183) +* **user_db:** unwanted implicit instantiation of UserDbFormat template ([3cbc9cb](https://github.com/rime/librime/commit/3cbc9cb)), closes [#188](https://github.com/rime/librime/issues/188) + + +### Chores + +* **release tag:** deprecating tag name prefix 'rime-' in favor of conventional 'v' ([3d28bf6](https://github.com/rime/librime/commit/3d28bf6)) + + +### BREAKING CHANGES + +* **release tag:** After 1.3.0 release, we'll no longer be creating tags in the format 'rime-X.Y.Z'. Downstream packagers please change automated scripts accordingly. + + + ## 1.2.10 (2018-02-21) diff --git a/package.json b/package.json index ee072e0b94..16b39d53b2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "librime", - "version": "1.2.10", + "version": "1.3.0", "description": "Rime Input Method Engine", "main": "index.js", "directories": { From 6f7be0ddb9a9ba0d86950e87049ff3aacdb797db Mon Sep 17 00:00:00 2001 From: Chen Gong Date: Sun, 11 Mar 2018 01:22:58 +0800 Subject: [PATCH 08/25] chore(tags): adopt semver "X.Y.Z" without prefix [ci skip] --- .npmrc | 1 + CHANGELOG.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.npmrc b/.npmrc index 2e18064938..ed67c5cc01 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,2 @@ +tag-version-prefix="" message="chore(release): %s :tada:" diff --git a/CHANGELOG.md b/CHANGELOG.md index 41b5831f7d..b569529646 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ ### Chores -* **release tag:** deprecating tag name prefix 'rime-' in favor of conventional 'v' ([3d28bf6](https://github.com/rime/librime/commit/3d28bf6)) +* **release tag:** deprecating tag name prefix 'rime-' in favor of semver 'X.Y.Z' ### BREAKING CHANGES From 8d8d2e6c2725041552bb17c787978cf319460d44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=9B=E6=8C=AF?= Date: Fri, 16 Mar 2018 12:32:14 +0800 Subject: [PATCH 09/25] fix(config_file_update): clean up deprecated user copy (#193) * fix(config_file_update): trash deprecated user copy created by older rime version * fix(config_file_update): create trash directory when needed to trash config files * fix(config_file_update): prefer rime-installed user copy to shared minimal version if numbers match' --- src/rime/lever/deployment_tasks.cc | 112 +++++++++++++++-------------- 1 file changed, 57 insertions(+), 55 deletions(-) diff --git a/src/rime/lever/deployment_tasks.cc b/src/rime/lever/deployment_tasks.cc index 1431eab116..ec8f5ca934 100644 --- a/src/rime/lever/deployment_tasks.cc +++ b/src/rime/lever/deployment_tasks.cc @@ -251,55 +251,66 @@ SchemaUpdate::SchemaUpdate(TaskInitializer arg) : verbose_(false) { } } -static bool IsCustomizedCopy(const string& file_name); +static bool MaybeCreateDirectory(fs::path dir) { + if (!fs::exists(dir)) { + boost::system::error_code ec; + if (!fs::create_directories(dir, ec)) { + LOG(ERROR) << "error creating directory '" << dir.string() << "'."; + return false; + } + } + return true; +} -static bool TrashCustomizedCopy(const fs::path& shared_copy, - const fs::path& user_copy, - const string& version_key, - const fs::path& trash) { +static bool RemoveVersionSuffix(string* version, const string& suffix) { + size_t suffix_pos = version->find(suffix); + if (suffix_pos != string::npos) { + version->erase(suffix_pos); + return true; + } + return false; +} + +static bool TrashDeprecatedUserCopy(const fs::path& shared_copy, + const fs::path& user_copy, + const string& version_key, + const fs::path& trash) { if (!fs::exists(shared_copy) || !fs::exists(user_copy) || fs::equivalent(shared_copy, user_copy)) { return false; } - if (IsCustomizedCopy(user_copy.string())) { - string shared_copy_version; - string user_copy_version; - Config shared_config; - if (shared_config.LoadFromFile(shared_copy.string())) { - shared_config.GetString(version_key, &shared_copy_version); - } - Config user_config; - if (user_config.LoadFromFile(user_copy.string()) && - user_config.GetString(version_key, &user_copy_version)) { - size_t custom_version_suffix = user_copy_version.find(".custom."); - if (custom_version_suffix != string::npos) { - user_copy_version.erase(custom_version_suffix); - } - } - if (CompareVersionString(shared_copy_version, user_copy_version) >= 0) { - fs::path backup = trash / user_copy.filename(); - boost::system::error_code ec; - fs::rename(user_copy, backup, ec); - if (ec) { - LOG(ERROR) << "error trashing file " << user_copy.string(); - return false; - } - return true; + string shared_copy_version; + string user_copy_version; + Config shared_config; + if (shared_config.LoadFromFile(shared_copy.string())) { + shared_config.GetString(version_key, &shared_copy_version); + // treat "X.Y.minimal" as equal to (not greater than) "X.Y" + // to avoid trashing the user installed full version + RemoveVersionSuffix(&shared_copy_version, ".minimal"); + } + Config user_config; + bool is_customized_user_copy = + user_config.LoadFromFile(user_copy.string()) && + user_config.GetString(version_key, &user_copy_version) && + RemoveVersionSuffix(&user_copy_version, ".custom."); + int cmp = CompareVersionString(shared_copy_version, user_copy_version); + // rime-installed user copy of the same version should be kept for integrity. + // also it could have been manually edited by user. + if (cmp > 0 || (cmp == 0 && is_customized_user_copy)) { + if (!MaybeCreateDirectory(trash)) { + return false; } - } - return false; -} - -static bool MaybeCreateDirectory(fs::path dir) { - if (!fs::exists(dir)) { + fs::path backup = trash / user_copy.filename(); boost::system::error_code ec; - if (!fs::create_directories(dir, ec)) { - LOG(ERROR) << "error creating directory '" << dir.string() << "'."; + fs::rename(user_copy, backup, ec); + if (ec) { + LOG(ERROR) << "error trashing file " << user_copy.string(); return false; } + return true; } - return true; + return false; } bool SchemaUpdate::Run(Deployer* deployer) { @@ -416,10 +427,10 @@ bool ConfigFileUpdate::Run(Deployer* deployer) { fs::path source_config_path(shared_data_path / file_name_); fs::path dest_config_path(user_data_path / file_name_); fs::path trash = user_data_path / "trash"; - if (TrashCustomizedCopy(source_config_path, - dest_config_path, - version_key_, - trash)) { + if (TrashDeprecatedUserCopy(source_config_path, + dest_config_path, + version_key_, + trash)) { LOG(INFO) << "deprecated user copy of '" << file_name_ << "' is moved to " << trash; } @@ -523,13 +534,8 @@ bool BackupConfigFiles::Run(Deployer* deployer) { if (!fs::exists(user_data_path)) return false; fs::path backup_dir(deployer->user_data_sync_dir()); - if (!fs::exists(backup_dir)) { - boost::system::error_code ec; - if (!fs::create_directories(backup_dir, ec)) { - LOG(ERROR) << "error creating directory '" - << backup_dir.string() << "'."; - return false; - } + if (!MaybeCreateDirectory(backup_dir)) { + return false; } int success = 0, failure = 0, latest = 0, skipped = 0; for (fs::directory_iterator iter(user_data_path), end; @@ -587,12 +593,8 @@ bool CleanupTrash::Run(Deployer* deployer) { boost::ends_with(filename, ".reverse.kct") || boost::ends_with(filename, ".userdb.kct.old") || boost::ends_with(filename, ".userdb.kct.snapshot")) { - if (!success && !failure && !fs::exists(trash)) { - boost::system::error_code ec; - if (!fs::create_directories(trash, ec)) { - LOG(ERROR) << "error creating directory '" << trash.string() << "'."; - return false; - } + if (!success && !MaybeCreateDirectory(trash)) { + return false; } fs::path backup = trash / entry.filename(); boost::system::error_code ec; From 6f6056a7b1bcf9053ca6d6e39d2f8e5cb6c8690f Mon Sep 17 00:00:00 2001 From: Chen Gong Date: Mon, 19 Mar 2018 00:33:44 +0800 Subject: [PATCH 10/25] fix(thirdparty/src/leveldb): do not link to snappy library --- thirdparty/src/leveldb/build_detect_platform | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/thirdparty/src/leveldb/build_detect_platform b/thirdparty/src/leveldb/build_detect_platform index bb76c4f22e..8f7f0309d7 100755 --- a/thirdparty/src/leveldb/build_detect_platform +++ b/thirdparty/src/leveldb/build_detect_platform @@ -190,6 +190,11 @@ EOF COMMON_FLAGS="$COMMON_FLAGS -DLEVELDB_PLATFORM_POSIX" fi +</dev/null </dev/null < Date: Sun, 1 Apr 2018 09:49:19 +0800 Subject: [PATCH 11/25] chore(bump-version.sh): npm version script --- bump-version.sh | 12 ++++++++++++ package.json | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100755 bump-version.sh diff --git a/bump-version.sh b/bump-version.sh new file mode 100755 index 0000000000..0585d8e740 --- /dev/null +++ b/bump-version.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -e + +version=$(node -p 'require("./package.json").version') + +sed -i'~' 's/set(\(rime_version\) .*)/set(\1 '$version')/' CMakeLists.txt +rm 'CMakeLists.txt~' +git add CMakeLists.txt + +conventional-changelog -p angular -i CHANGELOG.md -s +git add CHANGELOG.md diff --git a/package.json b/package.json index 16b39d53b2..c77ef43d95 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "version": "conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md" + "version": "./bump-version.sh" }, "repository": { "type": "git", From bec7e87aabec5b532a15b8cd9dd7bdc8b67b55f3 Mon Sep 17 00:00:00 2001 From: Chen Gong Date: Sun, 1 Apr 2018 09:56:49 +0800 Subject: [PATCH 12/25] chore(release): 1.3.1 :tada: --- CHANGELOG.md | 11 +++++++++++ CMakeLists.txt | 2 +- package.json | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b569529646..ff00f18e76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ + +## [1.3.1](https://github.com/rime/librime/compare/1.3.0...1.3.1) (2018-04-01) + + +### Bug Fixes + +* **config_file_update:** clean up deprecated user copy ([#193](https://github.com/rime/librime/issues/193)) ([8d8d2e6](https://github.com/rime/librime/commit/8d8d2e6)) +* **thirdparty/src/leveldb:** do not link to snappy library ([6f6056a](https://github.com/rime/librime/commit/6f6056a)) + + + # 1.3.0 (2018-03-09) diff --git a/CMakeLists.txt b/CMakeLists.txt index 175188646f..4c4e22ea61 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ set(CMAKE_USER_MAKE_RULES_OVERRIDE_CXX ${CMAKE_CURRENT_SOURCE_DIR}/cmake/cxx_fla project(rime) cmake_minimum_required(VERSION 2.8.11) -set(rime_version 1.3.0) +set(rime_version 1.3.1) set(rime_soversion 1) add_definitions(-DRIME_VERSION="${rime_version}") diff --git a/package.json b/package.json index c77ef43d95..32cf6c66fd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "librime", - "version": "1.3.0", + "version": "1.3.1", "description": "Rime Input Method Engine", "main": "index.js", "directories": { From 794898b1ff8d89aa2c042fae01ac0eb6d1bc4586 Mon Sep 17 00:00:00 2001 From: Chen Gong Date: Sun, 1 Apr 2018 20:27:59 +0800 Subject: [PATCH 13/25] docs(README): replace rime/brise with rime/plum --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9c5a8caddd..1c154a6ef9 100644 --- a/README.md +++ b/README.md @@ -79,10 +79,9 @@ Plugins Related works === - - [brise](https://github.com/rime/brise): Rime schema repository - - Combo Pinyin: an innovative chord-typing practice to input Pinyin + - [plum](https://github.com/rime/plum): Rime configuration manager and input schema repository + - [Combo Pinyin](https://github.com/rime/home/wiki/ComboPinyin): an innovative chord-typing practice to input Pinyin - essay: the vocabulary and language model for Rime - - [rimekit](https://github.com/lotem/rimekit): configuration tools for Rime (under construction) - [SCU](https://github.com/neolee/SCU/): Squirrel Configuration Utilities Credits From 1a6e0219e32091993f729e23ae1da35e41285bd4 Mon Sep 17 00:00:00 2001 From: Weitian Leung Date: Mon, 16 Apr 2018 21:50:22 +0800 Subject: [PATCH 14/25] Add ENABLE_ASAN option This enable the Address Sanitizer memory detect when turns on, make us easy to find out the memory problems. --- CMakeLists.txt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c4e22ea61..f56e6004cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,8 +17,9 @@ option(BUILD_SEPARATE_LIBS "Build a separate rime-gears library" OFF) option(ENABLE_LOGGING "Enable logging with google-glog library" ON) option(BOOST_USE_CXX11 "Boost has been built with C++11 support" OFF) option(BOOST_USE_SIGNALS2 "Boost use signals2 instead of signals" ON) +option(ENABLE_ASAN "Enable Address Sanitizer (Unix Only)" OFF) -SET(rime_data_dir "/share/rime-data" CACHE STRING "Target directory for Rime data") +set(rime_data_dir "/share/rime-data" CACHE STRING "Target directory for Rime data") if(WIN32) set(ext ".exe") @@ -27,6 +28,15 @@ endif(WIN32) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake") set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} "${PROJECT_SOURCE_DIR}/thirdparty") +if (ENABLE_ASAN) + set(asan_cflags "-fsanitize=address -fno-omit-frame-pointer") + set(asan_lflags "-fsanitize=address -lasan") + set(CMAKE_C_FLAGS "${asan_cflags} ${CMAKE_C_FLAGS}") + set(CMAKE_CXX_FLAGS "${asan_cflags} ${CMAKE_CXX_FLAGS}") + set(CMAKE_EXE_LINKER_FLAGS "${asan_lflags} ${CMAKE_EXE_LINKER_FLAGS}") + set(CMAKE_SHARED_LINKER_FLAGS "${asan_lflags} ${CMAKE_SHARED_LINKER_FLAGS}") +endif() + set(Boost_USE_STATIC_LIBS ${BUILD_STATIC}) set(Gflags_STATIC ${BUILD_STATIC}) set(Glog_STATIC ${BUILD_STATIC}) From 030c389c3bf396c7169eadc3967bebdde6a3babf Mon Sep 17 00:00:00 2001 From: Weitian Leung Date: Mon, 16 Apr 2018 21:54:09 +0800 Subject: [PATCH 15/25] Fix a heap-use-after-free error found by asan ctx->composition().Forward() might invalidated the iterator when did a push_back, we can't use `seg` anymore. --- src/rime/engine.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/rime/engine.cc b/src/rime/engine.cc index 420abd0121..33e1a183e0 100644 --- a/src/rime/engine.cc +++ b/src/rime/engine.cc @@ -257,8 +257,9 @@ void ConcreteEngine::OnSelect(Context* ctx) { ctx->composition().Forward(); } else { + bool updateCaret = (seg.end >= ctx->caret_pos()); ctx->composition().Forward(); - if (seg.end >= ctx->caret_pos()) { + if (updateCaret) { // finished converting current segment // move caret to the end of input ctx->set_caret_pos(ctx->input().length()); From 55b487f46380878aceb83e991dcdfc6a050dde08 Mon Sep 17 00:00:00 2001 From: Chen Gong Date: Tue, 17 Apr 2018 22:31:46 +0800 Subject: [PATCH 16/25] chore(engine.cc): Google code style; more informative name --- src/rime/engine.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rime/engine.cc b/src/rime/engine.cc index 33e1a183e0..5d839f4ed2 100644 --- a/src/rime/engine.cc +++ b/src/rime/engine.cc @@ -257,9 +257,9 @@ void ConcreteEngine::OnSelect(Context* ctx) { ctx->composition().Forward(); } else { - bool updateCaret = (seg.end >= ctx->caret_pos()); + bool reached_caret_pos = (seg.end >= ctx->caret_pos()); ctx->composition().Forward(); - if (updateCaret) { + if (reached_caret_pos) { // finished converting current segment // move caret to the end of input ctx->set_caret_pos(ctx->input().length()); From 253d8d1ff39c769e4e0affbcf15422ed15f9dc31 Mon Sep 17 00:00:00 2001 From: Chen Gong Date: Sat, 21 Apr 2018 14:51:06 +0800 Subject: [PATCH 17/25] chore(REAME.md): require boost>=1.48, for boost::locale [ci skip] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1c154a6ef9..b97d0d31de 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Build dependencies --- - compiler with C++11 support - cmake>=2.8 - - libboost>=1.46 + - libboost>=1.48 - libglog (optional) - libleveldb - libmarisa From 0784eb0f0621612a42052d11047390915f05b86d Mon Sep 17 00:00:00 2001 From: Chen Gong Date: Wed, 25 Apr 2018 00:53:10 +0800 Subject: [PATCH 18/25] fix(config_compiler): support creating list in-place by __patch and __merge --- data/test/config_merge_test.yaml | 8 ++++++++ src/rime/config/config_compiler.cc | 9 +++++++-- test/config_compiler_test.cc | 6 ++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/data/test/config_merge_test.yaml b/data/test/config_merge_test.yaml index 6a660131ff..3c570a46bb 100644 --- a/data/test/config_merge_test.yaml +++ b/data/test/config_merge_test.yaml @@ -46,3 +46,11 @@ merge_tree: zerg: # overwrite existing list ground_units: [] + +create_list_with_inplace_patch: + # map node without data key-value (exclude compiler directives) can be converted to list + all_ground_units: + __patch: + - __append: [scv, marine, firebat, vulture, tank] + - __append: {__include: starcraft/protoss/ground_units} + - __append: {__include: starcraft/zerg/ground_units} diff --git a/src/rime/config/config_compiler.cc b/src/rime/config/config_compiler.cc index 30557b077a..4741078401 100644 --- a/src/rime/config/config_compiler.cc +++ b/src/rime/config/config_compiler.cc @@ -97,8 +97,13 @@ static bool AppendToList(an target, an list) { return false; auto existing_list = As(**target); if (!existing_list) { - LOG(ERROR) << "trying to append list to other value"; - return false; + if (!(**target)->empty()) { + LOG(ERROR) << "trying to append list to incompatible node type"; + return false; + } + // convert empty node (usually map with only compiler directives) to list; + // refer to test case RimeConfigMergeTest.CreateListWithInplacePatch + existing_list = target->AsList(); } if (list->empty()) return true; diff --git a/test/config_compiler_test.cc b/test/config_compiler_test.cc index fbd905c6fa..0c83c1fd73 100644 --- a/test/config_compiler_test.cc +++ b/test/config_compiler_test.cc @@ -251,6 +251,12 @@ class RimeConfigCircularDependencyTest : public RimeConfigCompilerTestBase { } }; +TEST_F(RimeConfigMergeTest, CreateListWithInplacePatch) { + const string& prefix = "create_list_with_inplace_patch/"; + EXPECT_TRUE(config_->IsList(prefix + "all_ground_units")); + EXPECT_EQ(16, config_->GetListSize(prefix + "all_ground_units")); +} + TEST_F(RimeConfigCircularDependencyTest, BestEffortResolution) { const string& prefix = "test/"; EXPECT_TRUE(config_->IsNull(prefix + "__patch")); From 99573e367edc053073963bda9a3e3c97dbba5452 Mon Sep 17 00:00:00 2001 From: Chen Gong Date: Tue, 8 May 2018 22:45:05 +0800 Subject: [PATCH 19/25] fix(CMakeLists.txt): do not link binaries when building static library --- CMakeLists.txt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f56e6004cc..b66850f31c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -196,8 +196,11 @@ else() endif() add_subdirectory(src) -add_subdirectory(tools) -if(GTEST_FOUND) - add_subdirectory(test) +if(BUILD_SHARED_LIBS) + add_subdirectory(tools) + + if(GTEST_FOUND) + add_subdirectory(test) + endif() endif() From 7f9b0567594f7a9f092ff9531e678c7c05e43583 Mon Sep 17 00:00:00 2001 From: Chen Gong Date: Sat, 9 Jun 2018 15:48:54 +0800 Subject: [PATCH 20/25] refactor(editor): extract helper class key_binding_processor --- src/rime/common.h | 2 + src/rime/gear/editor.cc | 80 +++---------------- src/rime/gear/editor.h | 9 +-- src/rime/gear/key_binding_processor.h | 47 +++++++++++ src/rime/gear/key_binding_processor_impl.h | 91 ++++++++++++++++++++++ 5 files changed, 151 insertions(+), 78 deletions(-) create mode 100644 src/rime/gear/key_binding_processor.h create mode 100644 src/rime/gear/key_binding_processor_impl.h diff --git a/src/rime/common.h b/src/rime/common.h index 76164f3aa3..0a5f47f6e8 100644 --- a/src/rime/common.h +++ b/src/rime/common.h @@ -35,6 +35,8 @@ // call a pointer to member function on this #define RIME_THIS_CALL(f) (this->*(f)) +#define RIME_THIS_CALL_AS(T, f) ((T*)this->*(f)) + namespace rime { using std::function; diff --git a/src/rime/gear/editor.cc b/src/rime/gear/editor.cc index 021394d2c2..27e0e276b1 100644 --- a/src/rime/gear/editor.cc +++ b/src/rime/gear/editor.cc @@ -4,23 +4,19 @@ // // 2011-10-23 GONG Chen // -#include #include -#include #include #include #include #include #include #include +#include #include namespace rime { -static struct EditorActionDef { - const char* name; - Editor::HandlerPtr action; -} editor_action_definitions[] = { +static Editor::ActionDef editor_action_definitions[] = { { "confirm", &Editor::Confirm }, { "toggle_selection", &Editor::ToggleSelection }, { "commit_comment", &Editor::CommitComment }, @@ -33,7 +29,7 @@ static struct EditorActionDef { { "delete_candidate", &Editor::DeleteCandidate }, { "delete", &Editor::DeleteChar }, { "cancel", &Editor::CancelComposition }, - { "noop", nullptr } + Editor::kActionNoop }; static struct EditorCharHandlerDef { @@ -45,7 +41,8 @@ static struct EditorCharHandlerDef { { "noop", nullptr } }; -Editor::Editor(const Ticket& ticket, bool auto_commit) : Processor(ticket) { +Editor::Editor(const Ticket& ticket, bool auto_commit) + : Processor(ticket), KeyBindingProcessor(editor_action_definitions) { engine_->context()->set_option("_auto_commit", auto_commit); } @@ -55,27 +52,9 @@ ProcessResult Editor::ProcessKeyEvent(const KeyEvent& key_event) { int ch = key_event.keycode(); Context* ctx = engine_->context(); if (ctx->IsComposing()) { - if (Accept(key_event)) { - return kAccepted; - } - if (key_event.ctrl() || key_event.alt()) { - return kNoop; - } - if (key_event.shift()) { - KeyEvent shift_as_ctrl{ - key_event.keycode(), - (key_event.modifier() & ~kShiftMask) | kControlMask - }; - if (Accept(shift_as_ctrl)) { - return kAccepted; - } - KeyEvent remove_shift{ - key_event.keycode(), - key_event.modifier() & ~kShiftMask - }; - if (Accept(remove_shift)) { - return kAccepted; - } + auto result = KeyBindingProcessor::ProcessKeyEvent(key_event, ctx); + if (result != kNoop) { + return result; } } if (char_handler_ && @@ -89,53 +68,12 @@ ProcessResult Editor::ProcessKeyEvent(const KeyEvent& key_event) { return kNoop; } -bool Editor::Accept(const KeyEvent& key_event) { - auto binding = key_bindings_.find(key_event); - if (binding != key_bindings_.end()) { - auto action = binding->second; - RIME_THIS_CALL(action)(engine_->context()); - DLOG(INFO) << "editor action key accepted: " << key_event.repr(); - return true; - } - return false; -} - -void Editor::Bind(KeyEvent key_event, HandlerPtr action) { - if (action) { - key_bindings_[key_event] = action; - } - else { - key_bindings_.erase(key_event); - } -} - void Editor::LoadConfig() { if (!engine_) { return; } Config* config = engine_->schema()->config(); - if (auto bindings = config->GetMap("editor/bindings")) { - for (auto it = bindings->begin(); it != bindings->end(); ++it) { - auto value = As(it->second); - if (!value) { - continue; - } - auto* p = editor_action_definitions; - while (p->action && p->name != value->str()) { - ++p; - } - if (!p->action && p->name != value->str()) { - LOG(WARNING) << "invalid editor action: " << value->str(); - continue; - } - KeyEvent ke; - if (!ke.Parse(it->first)) { - LOG(WARNING) << "invalid edit key: " << it->first; - continue; - } - Bind(ke, p->action); - } - } + KeyBindingProcessor::LoadConfig(config, "editor"); if (auto value = config->GetValue("editor/char_handler")) { auto* p = editor_char_handler_definitions; while (p->action && p->name != value->str()) { diff --git a/src/rime/gear/editor.h b/src/rime/gear/editor.h index dcaa0577da..72a00a7ea8 100644 --- a/src/rime/gear/editor.h +++ b/src/rime/gear/editor.h @@ -11,18 +11,16 @@ #include #include #include +#include namespace rime { class Context; -class Editor : public Processor { +class Editor : public Processor, public KeyBindingProcessor { public: - typedef void Handler(Context* ctx); typedef ProcessResult CharHandler(Context* ctx, int ch); - using HandlerPtr = void (Editor::*)(Context* ctx); using CharHandlerPtr = ProcessResult (Editor::*)(Context* ctx, int ch); - using KeyBindings = map; Editor(const Ticket& ticket, bool auto_commit); ProcessResult ProcessKeyEvent(const KeyEvent& key_event); @@ -44,11 +42,8 @@ class Editor : public Processor { CharHandler AddToInput; protected: - bool Accept(const KeyEvent& key_event); - void Bind(KeyEvent key_event, HandlerPtr action); void LoadConfig(); - KeyBindings key_bindings_; CharHandlerPtr char_handler_ = nullptr; }; diff --git a/src/rime/gear/key_binding_processor.h b/src/rime/gear/key_binding_processor.h new file mode 100644 index 0000000000..97d644c4ad --- /dev/null +++ b/src/rime/gear/key_binding_processor.h @@ -0,0 +1,47 @@ +// +// Copyright RIME Developers +// Distributed under the BSD License +// +#ifndef RIME_KEY_BINDING_PROCESSOR_H_ +#define RIME_KEY_BINDING_PROCESSOR_H_ + +#include +#include +#include + +namespace rime { + +class Config; +class Context; + +template +class KeyBindingProcessor { + public: + typedef void Handler(Context* ctx); + using HandlerPtr = void (T::*)(Context* ctx); + struct ActionDef { + const char* name; + HandlerPtr action; + }; + + static const ActionDef kActionNoop; + + KeyBindingProcessor(ActionDef* action_definitions) + : action_definitions_(action_definitions) {} + ProcessResult ProcessKeyEvent(const KeyEvent& key_event, Context* ctx); + bool Accept(const KeyEvent& key_event, Context* ctx); + void Bind(KeyEvent key_event, HandlerPtr action); + void LoadConfig(Config* config, const string& section); + + private: + ActionDef* action_definitions_; + + using KeyBindingMap = map; + KeyBindingMap key_bindings_; +}; + +} // namespace rime + +#include + +#endif // RIME_KEY_BINDING_PROCESSOR_H_ diff --git a/src/rime/gear/key_binding_processor_impl.h b/src/rime/gear/key_binding_processor_impl.h new file mode 100644 index 0000000000..e4b89471e6 --- /dev/null +++ b/src/rime/gear/key_binding_processor_impl.h @@ -0,0 +1,91 @@ +// +// Copyright RIME Developers +// Distributed under the BSD License +// + +namespace rime { + +template +const typename KeyBindingProcessor::ActionDef + KeyBindingProcessor::kActionNoop = { "noop", nullptr }; + +template +ProcessResult KeyBindingProcessor::ProcessKeyEvent( + const KeyEvent& key_event, Context* ctx) { + // exact match + if (Accept(key_event, ctx)) { + return kAccepted; + } + // fallback: compatible modifiers + if (key_event.ctrl() || key_event.alt()) { + return kNoop; + } + if (key_event.shift()) { + KeyEvent shift_as_ctrl{ + key_event.keycode(), + (key_event.modifier() & ~kShiftMask) | kControlMask + }; + if (Accept(shift_as_ctrl, ctx)) { + return kAccepted; + } + KeyEvent ignore_shift{ + key_event.keycode(), + key_event.modifier() & ~kShiftMask + }; + if (Accept(ignore_shift, ctx)) { + return kAccepted; + } + } + // not handled + return kNoop; +} + +template +bool KeyBindingProcessor::Accept(const KeyEvent& key_event, Context* ctx) { + auto binding = key_bindings_.find(key_event); + if (binding != key_bindings_.end()) { + auto action = binding->second; + RIME_THIS_CALL_AS(T, action)(ctx); + DLOG(INFO) << "action key accepted: " << key_event.repr(); + return true; + } + return false; +} + +template +void KeyBindingProcessor::Bind(KeyEvent key_event, HandlerPtr action) { + if (action) { + key_bindings_[key_event] = action; + } + else { + key_bindings_.erase(key_event); + } +} + +template +void KeyBindingProcessor::LoadConfig(Config* config, const string& section) { + if (auto bindings = config->GetMap(section + "/bindings")) { + for (auto it = bindings->begin(); it != bindings->end(); ++it) { + auto value = As(it->second); + if (!value) { + continue; + } + auto* p = action_definitions_; + while (p->action && p->name != value->str()) { + ++p; + } + if (!p->action && p->name != value->str()) { + LOG(WARNING) << "[" << section << "] invalid action: " << value->str(); + continue; + } + KeyEvent ke; + if (!ke.Parse(it->first)) { + LOG(WARNING) << "[" << section << "] invalid key: " << it->first; + continue; + } + Bind(ke, p->action); + } + } +} + +} // namespace rime From 122893d026429afcfe6b52b57a512a0fa1053c21 Mon Sep 17 00:00:00 2001 From: Chen Gong Date: Tue, 12 Jun 2018 08:41:26 +0800 Subject: [PATCH 21/25] refactor(navigator): key bindings --- src/rime/gear/key_binding_processor.h | 5 +- src/rime/gear/navigator.cc | 123 ++++++++++++++++---------- src/rime/gear/navigator.h | 23 +++-- 3 files changed, 96 insertions(+), 55 deletions(-) diff --git a/src/rime/gear/key_binding_processor.h b/src/rime/gear/key_binding_processor.h index 97d644c4ad..1df8a71dc6 100644 --- a/src/rime/gear/key_binding_processor.h +++ b/src/rime/gear/key_binding_processor.h @@ -6,14 +6,13 @@ #define RIME_KEY_BINDING_PROCESSOR_H_ #include +#include +#include #include #include namespace rime { -class Config; -class Context; - template class KeyBindingProcessor { public: diff --git a/src/rime/gear/navigator.cc b/src/rime/gear/navigator.cc index 17e0d3acfc..13d499b877 100644 --- a/src/rime/gear/navigator.cc +++ b/src/rime/gear/navigator.cc @@ -11,63 +11,96 @@ #include #include #include +#include #include #include namespace rime { +static Navigator::ActionDef navigation_actions[] = { + { "rewind", &Navigator::Rewind }, + { "left_by_char", &Navigator::LeftByChar }, + { "right_by_char", &Navigator::RightByChar }, + { "left_by_syllable", &Navigator::LeftBySyllable }, + { "right_by_syllable", &Navigator::RightBySyllable }, + { "home", &Navigator::Home }, + { "end", &Navigator::End }, + Navigator::kActionNoop +}; + +Navigator::Navigator(const Ticket& ticket) + : Processor(ticket), KeyBindingProcessor(navigation_actions) { + // Default key binding. + Bind({XK_Left, 0}, &Navigator::Rewind); + Bind({XK_Left, kControlMask}, &Navigator::LeftBySyllable); + Bind({XK_KP_Left, 0}, &Navigator::LeftByChar); + Bind({XK_Right, 0}, &Navigator::RightByChar); + Bind({XK_Right, kControlMask}, &Navigator::RightBySyllable); + Bind({XK_KP_Right, 0}, &Navigator::RightByChar); + Bind({XK_Home, 0}, &Navigator::Home); + Bind({XK_KP_Home, 0}, &Navigator::Home); + Bind({XK_End, 0}, &Navigator::End); + Bind({XK_KP_End, 0}, &Navigator::End); + + Config* config = engine_->schema()->config(); + KeyBindingProcessor::LoadConfig(config, "navigator"); +} + ProcessResult Navigator::ProcessKeyEvent(const KeyEvent& key_event) { if (key_event.release()) return kNoop; Context* ctx = engine_->context(); if (!ctx->IsComposing()) return kNoop; - int ch = key_event.keycode(); - if (ch == XK_Left || ch == XK_KP_Left) { - BeginMove(ctx); - if (key_event.ctrl() || key_event.shift()) { - size_t confirmed_pos = ctx->composition().GetConfirmedPosition(); - JumpLeft(ctx, confirmed_pos) || End(ctx); - } - else { - // take a jump leftwards when there are multiple spans, - // but not from the middle of a span. - (spans_.Count() > 1 && - spans_.HasVertex(ctx->caret_pos()) - ? JumpLeft(ctx) : Left(ctx)) || End(ctx); - } - return kAccepted; - } - if (ch == XK_Right || ch == XK_KP_Right) { - BeginMove(ctx); - if (key_event.ctrl() || key_event.shift()) { - size_t confirmed_pos = ctx->composition().GetConfirmedPosition(); - JumpRight(ctx, confirmed_pos) || End(ctx); - } - else { - Right(ctx) || Home(ctx); - } - return kAccepted; - } - if (ch == XK_Home || ch == XK_KP_Home) { - BeginMove(ctx); - Home(ctx); - return kAccepted; - } - if (ch == XK_End || ch == XK_KP_End) { - BeginMove(ctx); - End(ctx); - return kAccepted; - } - // not handled - return kNoop; + return KeyBindingProcessor::ProcessKeyEvent(key_event, ctx); +} + +void Navigator::LeftBySyllable(Context* ctx) { + BeginMove(ctx); + size_t confirmed_pos = ctx->composition().GetConfirmedPosition(); + JumpLeft(ctx, confirmed_pos) || GoToEnd(ctx); +} + +void Navigator::LeftByChar(Context* ctx) { + BeginMove(ctx); + MoveLeft(ctx) || GoToEnd(ctx); +} + +void Navigator::Rewind(Context* ctx) { + BeginMove(ctx); + // take a jump leftwards when there are multiple spans, + // but not from the middle of a span. + ( + spans_.Count() > 1 && spans_.HasVertex(ctx->caret_pos()) + ? JumpLeft(ctx) : MoveLeft(ctx) + ) || GoToEnd(ctx); +} + +void Navigator::RightBySyllable(Context* ctx) { + BeginMove(ctx); + size_t confirmed_pos = ctx->composition().GetConfirmedPosition(); + JumpRight(ctx, confirmed_pos) || GoToEnd(ctx); +} + +void Navigator::RightByChar(Context* ctx) { + BeginMove(ctx); + MoveRight(ctx) || GoHome(ctx); +} + +void Navigator::Home(Context* ctx) { + BeginMove(ctx); + GoHome(ctx); +} + +void Navigator::End(Context* ctx) { + BeginMove(ctx); + GoToEnd(ctx); } void Navigator::BeginMove(Context* ctx) { ctx->ConfirmPreviousSelection(); // update spans - size_t caret_pos = ctx->caret_pos(); - if (input_ != ctx->input() || caret_pos > spans_.end()) { + if (input_ != ctx->input() || ctx->caret_pos() > spans_.end()) { input_ = ctx->input(); spans_.Clear(); for (const auto &seg : ctx->composition()) { @@ -109,7 +142,7 @@ bool Navigator::JumpRight(Context* ctx, size_t start_pos) { return false; } -bool Navigator::Left(Context* ctx) { +bool Navigator::MoveLeft(Context* ctx) { DLOG(INFO) << "navigate left."; size_t caret_pos = ctx->caret_pos(); if (caret_pos == 0) @@ -118,7 +151,7 @@ bool Navigator::Left(Context* ctx) { return true; } -bool Navigator::Right(Context* ctx) { +bool Navigator::MoveRight(Context* ctx) { DLOG(INFO) << "navigate right."; size_t caret_pos = ctx->caret_pos(); if (caret_pos >= ctx->input().length()) @@ -127,7 +160,7 @@ bool Navigator::Right(Context* ctx) { return true; } -bool Navigator::Home(Context* ctx) { +bool Navigator::GoHome(Context* ctx) { DLOG(INFO) << "navigate home."; size_t caret_pos = ctx->caret_pos(); const Composition& comp = ctx->composition(); @@ -151,7 +184,7 @@ bool Navigator::Home(Context* ctx) { return false; } -bool Navigator::End(Context* ctx) { +bool Navigator::GoToEnd(Context* ctx) { DLOG(INFO) << "navigate end."; size_t end_pos = ctx->input().length(); if (ctx->caret_pos() != end_pos) { diff --git a/src/rime/gear/navigator.h b/src/rime/gear/navigator.h index 39d109f480..f85194e196 100644 --- a/src/rime/gear/navigator.h +++ b/src/rime/gear/navigator.h @@ -10,24 +10,33 @@ #include #include #include +#include #include namespace rime { -class Navigator : public Processor { +class Navigator : public Processor, public KeyBindingProcessor { public: - Navigator(const Ticket& ticket) : Processor(ticket) {} + explicit Navigator(const Ticket& ticket); - virtual ProcessResult ProcessKeyEvent(const KeyEvent& key_event); + ProcessResult ProcessKeyEvent(const KeyEvent& key_event) override; + + Handler Rewind; + Handler LeftByChar; + Handler RightByChar; + Handler LeftBySyllable; + Handler RightBySyllable; + Handler Home; + Handler End; private: void BeginMove(Context* ctx); bool JumpLeft(Context* ctx, size_t start_pos = 0); bool JumpRight(Context* ctx, size_t start_pos = 0); - bool Left(Context* ctx); - bool Right(Context* ctx); - bool Home(Context* ctx); - bool End(Context* ctx); + bool MoveLeft(Context* ctx); + bool MoveRight(Context* ctx); + bool GoHome(Context* ctx); + bool GoToEnd(Context* ctx); string input_; Spans spans_; From b86b6474043038101524d8612c49e74baebfd507 Mon Sep 17 00:00:00 2001 From: Chen Gong Date: Mon, 2 Jul 2018 11:03:48 +0800 Subject: [PATCH 22/25] fix(config_compiler): ambiguous operator overload with cmake option ENABLE_LOGGING=OFF Fixes #211 --- src/rime/config/config_compiler.cc | 8 ++++++++ src/rime/config/config_compiler.h | 6 ++---- src/rime/config/config_compiler_impl.h | 6 ++---- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/rime/config/config_compiler.cc b/src/rime/config/config_compiler.cc index 4741078401..053ab948fc 100644 --- a/src/rime/config/config_compiler.cc +++ b/src/rime/config/config_compiler.cc @@ -9,6 +9,14 @@ namespace rime { +std::ostream& operator<< (std::ostream& stream, const Reference& reference) { + return stream << reference.repr(); +} + +std::ostream& operator<< (std::ostream& stream, const Dependency& dependency) { + return stream << dependency.repr(); +} + struct ConfigDependencyGraph { map> resources; vector> node_stack; diff --git a/src/rime/config/config_compiler.h b/src/rime/config/config_compiler.h index a08fd01697..a6cdd45c05 100644 --- a/src/rime/config/config_compiler.h +++ b/src/rime/config/config_compiler.h @@ -5,6 +5,7 @@ #ifndef RIME_CONFIG_COMPILER_H_ #define RIME_CONFIG_COMPILER_H_ +#include #include #include #include @@ -35,10 +36,7 @@ struct Reference { string repr() const; }; -template -StreamT& operator<< (StreamT& stream, const Reference& reference) { - return stream << reference.repr(); -} +std::ostream& operator<< (std::ostream& stream, const Reference& reference); class ConfigCompilerPlugin; class ResourceResolver; diff --git a/src/rime/config/config_compiler_impl.h b/src/rime/config/config_compiler_impl.h index 00c1df7eef..bd4cf701c1 100644 --- a/src/rime/config/config_compiler_impl.h +++ b/src/rime/config/config_compiler_impl.h @@ -5,6 +5,7 @@ #ifndef RIME_CONFIG_COMPILER_IMPL_H_ #define RIME_CONFIG_COMPILER_IMPL_H_ +#include #include #include #include @@ -32,10 +33,7 @@ struct Dependency { virtual bool Resolve(ConfigCompiler* compiler) = 0; }; -template -StreamT& operator<< (StreamT& stream, const Dependency& dependency) { - return stream << dependency.repr(); -} +std::ostream& operator<< (std::ostream& stream, const Dependency& dependency); struct PendingChild : Dependency { string child_path; From 9f774e7046a119592446acb68c8b8e81a5372cb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=9B=E6=8C=AF?= Date: Fri, 3 Aug 2018 04:09:13 +0800 Subject: [PATCH 23/25] feat(language): shared user dictionary per language (Closes #184) (#214) --- src/rime/dict/user_dictionary.cc | 13 ++++++------ src/rime/dict/user_dictionary.h | 2 +- src/rime/gear/memory.cc | 13 +++++++++--- src/rime/gear/memory.h | 10 ++++----- src/rime/gear/poet.h | 8 +++---- src/rime/gear/table_translator.cc | 17 +++------------ src/rime/gear/table_translator.h | 14 ++++++------ src/rime/gear/translator_commons.h | 8 +++---- src/rime/language.cc | 18 ++++++++++++++++ src/rime/language.h | 34 ++++++++++++++++++++++++++++++ 10 files changed, 91 insertions(+), 46 deletions(-) create mode 100644 src/rime/language.cc create mode 100644 src/rime/language.h diff --git a/src/rime/dict/user_dictionary.cc b/src/rime/dict/user_dictionary.cc index 078d3d9848..2c7b9869ab 100644 --- a/src/rime/dict/user_dictionary.cc +++ b/src/rime/dict/user_dictionary.cc @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -116,8 +117,8 @@ bool UserDictEntryIterator::Next() { // UserDictionary members -UserDictionary::UserDictionary(const an& db) - : db_(db) { +UserDictionary::UserDictionary(const string& name, an db) + : name_(name), db_(db) { } UserDictionary::~UserDictionary() { @@ -494,10 +495,8 @@ UserDictionary* UserDictionaryComponent::Create(const Ticket& ticket) { // user specified name } else if (config->GetString(ticket.name_space + "/dictionary", &dict_name)) { - // {dictionary: lunapinyin.extra} implies {user_dict: luna_pinyin} - size_t dot = dict_name.find('.'); - if (dot != string::npos && dot != 0) - dict_name.resize(dot); + // {dictionary: luna_pinyin.extra} implies {user_dict: luna_pinyin} + dict_name = Language::get_language_component(dict_name); } else { LOG(ERROR) << ticket.name_space << "/dictionary not specified in schema '" @@ -519,7 +518,7 @@ UserDictionary* UserDictionaryComponent::Create(const Ticket& ticket) { db.reset(component->Create(dict_name)); db_pool_[dict_name] = db; } - return new UserDictionary(db); + return new UserDictionary(dict_name, db); } } // namespace rime diff --git a/src/rime/dict/user_dictionary.h b/src/rime/dict/user_dictionary.h index 67f1427fb0..4601a22f6a 100644 --- a/src/rime/dict/user_dictionary.h +++ b/src/rime/dict/user_dictionary.h @@ -50,7 +50,7 @@ struct Ticket; class UserDictionary : public Class { public: - explicit UserDictionary(const an& db); + UserDictionary(const string& name, an db); virtual ~UserDictionary(); void Attach(const an& table, const an& prism); diff --git a/src/rime/gear/memory.cc b/src/rime/gear/memory.cc index 644bfd7bb7..bde9a47854 100644 --- a/src/rime/gear/memory.cc +++ b/src/rime/gear/memory.cc @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -65,6 +66,13 @@ Memory::Memory(const Ticket& ticket) { } } + // user dictionary is named after language; dictionary name may have an + // optional suffix separated from the language component by dot. + language_.reset(new Language{ + user_dict_ ? user_dict_->name() : + Language::get_language_component(dict_->name()) + }); + Context* ctx = ticket.engine->context(); commit_connection_ = ctx->commit_notifier().connect( [this](Context* ctx) { OnCommit(ctx); }); @@ -100,7 +108,7 @@ void Memory::OnCommit(Context* ctx) { for (auto& seg : ctx->composition()) { auto phrase = As(Candidate::GetGenuineCandidate( seg.GetSelectedCandidate())); - bool recognized = phrase && phrase->language() == language(); + bool recognized = Language::intelligible(phrase, this); if (recognized) { commit_entry.AppendPhrase(phrase); } @@ -119,8 +127,7 @@ void Memory::OnDeleteEntry(Context* ctx) { return; auto phrase = As(Candidate::GetGenuineCandidate( ctx->GetSelectedCandidate())); - bool recognized = phrase && phrase->language() == language(); - if (recognized) { + if (Language::intelligible(phrase, this)) { const DictEntry& entry(phrase->entry()); LOG(INFO) << "deleting entry: '" << entry.text << "'."; user_dict_->UpdateEntry(entry, -1); // mark as deleted in user dict diff --git a/src/rime/gear/memory.h b/src/rime/gear/memory.h index 5ec8145f76..0b065491fb 100644 --- a/src/rime/gear/memory.h +++ b/src/rime/gear/memory.h @@ -17,6 +17,7 @@ class Context; class Engine; class Dictionary; class UserDictionary; +class Language; class Phrase; class Memory; @@ -31,9 +32,6 @@ struct CommitEntry : DictEntry { bool Save() const; }; -class Language { -}; - class Memory { public: Memory(const Ticket& ticket); @@ -45,11 +43,11 @@ class Memory { bool FinishSession(); bool DiscardSession(); - Language* language() { return &language_; } - Dictionary* dict() const { return dict_.get(); } UserDictionary* user_dict() const { return user_dict_.get(); } + const Language* language() const { return language_.get(); } + protected: void OnCommit(Context* ctx); void OnDeleteEntry(Context* ctx); @@ -57,12 +55,12 @@ class Memory { the dict_; the user_dict_; + the language_; private: connection commit_connection_; connection delete_connection_; connection unhandled_key_connection_; - Language language_; }; } // namespace rime diff --git a/src/rime/gear/poet.h b/src/rime/gear/poet.h index 55f0c36b81..df27da5498 100644 --- a/src/rime/gear/poet.h +++ b/src/rime/gear/poet.h @@ -21,12 +21,12 @@ class Language; class Poet { public: - Poet(Language* language) : language_(language) {} + Poet(const Language* language) : language_(language) {} + + an MakeSentence(const WordGraph& graph, size_t total_length); - an MakeSentence(const WordGraph& graph, - size_t total_length); protected: - Language* language_; + const Language* language_; }; } // namespace rime diff --git a/src/rime/gear/table_translator.cc b/src/rime/gear/table_translator.cc index 6f6d0c8927..988a21577d 100644 --- a/src/rime/gear/table_translator.cc +++ b/src/rime/gear/table_translator.cc @@ -28,21 +28,10 @@ static const char* kUnitySymbol = " \xe2\x98\xaf "; // TableTranslation TableTranslation::TableTranslation(TranslatorOptions* options, - Language* language, + const Language* language, const string& input, - size_t start, size_t end, - const string& preedit) - : options_(options), language_(language), - input_(input), start_(start), end_(end), preedit_(preedit) { - if (options_) - options_->preedit_formatter().Apply(&preedit_); - set_exhausted(true); -} - -TableTranslation::TableTranslation(TranslatorOptions* options, - Language* language, - const string& input, - size_t start, size_t end, + size_t start, + size_t end, const string& preedit, const DictEntryIterator& iter, const UserDictEntryIterator& uter) diff --git a/src/rime/gear/table_translator.h b/src/rime/gear/table_translator.h index 6b5767310b..cd4b01dbe9 100644 --- a/src/rime/gear/table_translator.h +++ b/src/rime/gear/table_translator.h @@ -50,13 +50,13 @@ class TableTranslator : public Translator, class TableTranslation : public Translation { public: - TableTranslation(TranslatorOptions* options, Language* language, - const string& input, size_t start, size_t end, - const string& preedit); - TableTranslation(TranslatorOptions* options, Language* language, - const string& input, size_t start, size_t end, + TableTranslation(TranslatorOptions* options, + const Language* language, + const string& input, + size_t start, + size_t end, const string& preedit, - const DictEntryIterator& iter, + const DictEntryIterator& iter = DictEntryIterator(), const UserDictEntryIterator& uter = UserDictEntryIterator()); virtual bool Next(); @@ -74,7 +74,7 @@ class TableTranslation : public Translation { } TranslatorOptions* options_; - Language* language_; + const Language* language_; string input_; size_t start_; size_t end_; diff --git a/src/rime/gear/translator_commons.h b/src/rime/gear/translator_commons.h index 566a522d20..3367bf41e0 100644 --- a/src/rime/gear/translator_commons.h +++ b/src/rime/gear/translator_commons.h @@ -70,7 +70,7 @@ class Language; class Phrase : public Candidate { public: - Phrase(Language* language, + Phrase(const Language* language, const string& type, size_t start, size_t end, const an& entry) : Candidate(type, start, end), @@ -93,14 +93,14 @@ class Phrase : public Candidate { double weight() const { return entry_->weight; } Code& code() const { return entry_->code; } const DictEntry& entry() const { return *entry_; } - Language* language() const { return language_; } + const Language* language() const { return language_; } Spans spans() { return syllabifier_ ? syllabifier_->Syllabify(this) : Spans(); } protected: - Language* language_; + const Language* language_; an entry_; an syllabifier_; }; @@ -109,7 +109,7 @@ class Phrase : public Candidate { class Sentence : public Phrase { public: - Sentence(Language* language) + Sentence(const Language* language) : Phrase(language, "sentence", 0, 0, New()) { entry_->weight = 1.0; } diff --git a/src/rime/language.cc b/src/rime/language.cc new file mode 100644 index 0000000000..5626499f81 --- /dev/null +++ b/src/rime/language.cc @@ -0,0 +1,18 @@ +// +// Copyright RIME Developers +// Distributed under the BSD License +// +#include +#include + +namespace rime { + +// "luna_pinyin.extra" has language component "luna_pinyin". +string Language::get_language_component(const string& name) { + size_t dot = name.find('.'); + if (dot != string::npos && dot != 0) + return name.substr(0, dot); + return name; +} + +} // namespace rime diff --git a/src/rime/language.h b/src/rime/language.h new file mode 100644 index 0000000000..dbc8c0a2b3 --- /dev/null +++ b/src/rime/language.h @@ -0,0 +1,34 @@ +// +// Copyright RIME Developers +// Distributed under the BSD License +// +#ifndef RIME_LANGUAGE_H_ +#define RIME_LANGUAGE_H_ + +#include + +namespace rime { + +class Language { + const string name_; + + public: + Language(const string& name) : name_(name) {} + string name() const { return name_; } + + bool operator== (const Language& other) const { + return name_ == other.name_; + } + + template + static bool intelligible(const T& t, const U& u) { + return t && t->language() && u && u->language() && + *t->language() == *u->language(); + } + + static string get_language_component(const string& name); +}; + +} // namespace rime + +#endif // RIME_LANGUAGE_H_ From 74e31bc3a04691c94d1be41d8d7497a635c6946e Mon Sep 17 00:00:00 2001 From: nameoverflow Date: Wed, 15 Aug 2018 00:14:30 +0800 Subject: [PATCH 24/25] fix(table_translator): enable encoding uniquified commit history --- src/rime/gear/table_translator.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rime/gear/table_translator.cc b/src/rime/gear/table_translator.cc index 988a21577d..86a83b5eaf 100644 --- a/src/rime/gear/table_translator.cc +++ b/src/rime/gear/table_translator.cc @@ -335,7 +335,7 @@ bool TableTranslator::Memorize(const CommitEntry& commit_entry) { } string phrase; for (; it != history.rend(); ++it) { - if (it->type != "table" && it->type != "sentence") + if (it->type != "table" && it->type != "sentence" && it->type != "uniquified") break; if (phrase.empty()) { phrase = it->text; // last word From 19cea07e0f313e1dcb34b52f5fc9b37cba92a825 Mon Sep 17 00:00:00 2001 From: Radium Date: Wed, 22 Aug 2018 16:22:38 +1000 Subject: [PATCH 25/25] feat: always_show_comments option (#220) * feat: add always_show_comments option * fix: address feedback Line wraps --- src/rime/gear/script_translator.cc | 6 +++++- src/rime/gear/script_translator.h | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/rime/gear/script_translator.cc b/src/rime/gear/script_translator.cc index 9853e508b0..8a168c4780 100644 --- a/src/rime/gear/script_translator.cc +++ b/src/rime/gear/script_translator.cc @@ -130,6 +130,8 @@ ScriptTranslator::ScriptTranslator(const Ticket& ticket) return; if (Config* config = engine_->schema()->config()) { config->GetInt(name_space_ + "/spelling_hints", &spelling_hints_); + config->GetBool(name_space_ + "/always_show_comments", + &always_show_comments_); } } @@ -345,7 +347,9 @@ an ScriptTranslation::Peek() { } if (sentence_->comment().empty()) { auto spelling = syllabifier_->GetOriginalSpelling(*sentence_); - if (!spelling.empty() && spelling != sentence_->preedit()) { + if (!spelling.empty() && + (translator_->always_show_comments() || + spelling != sentence_->preedit())) { sentence_->set_comment(/*quote_left + */spelling/* + quote_right*/); } } diff --git a/src/rime/gear/script_translator.h b/src/rime/gear/script_translator.h index 0a9c6d6e40..2eab5cf55f 100644 --- a/src/rime/gear/script_translator.h +++ b/src/rime/gear/script_translator.h @@ -38,9 +38,11 @@ class ScriptTranslator : public Translator, // options int spelling_hints() const { return spelling_hints_; } + bool always_show_comments() const { return always_show_comments_; } protected: int spelling_hints_ = 0; + bool always_show_comments_ = false; }; } // namespace rime