From 133e1e8d5a7c0e9e9655edf96b5850655aa9694d Mon Sep 17 00:00:00 2001 From: Radium Date: Tue, 18 Sep 2018 15:40:00 +1000 Subject: [PATCH] Merge master into wasm (#1) * fix(user_db): unwanted implicit instantiation of UserDbFormat template Fixes #188: UserDbFormat::extension not working * fix(config_compiler): "/" mistaken as path separator in merged map key (#192) Fixes #190 * fix(ConfigFileUpdate): no need to create user build if shared build is up-to-date * fix(SchemaUpdate): read compiled schema from shared build if there is no user build * 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. * chore(CMakeLists.txt): bump version to 1.3.0 * chore(release): 1.3.0 :tada: * chore(tags): adopt semver "X.Y.Z" without prefix [ci skip] * 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' * fix(thirdparty/src/leveldb): do not link to snappy library * chore(bump-version.sh): npm version script * chore(release): 1.3.1 :tada: * docs(README): replace rime/brise with rime/plum * Add ENABLE_ASAN option This enable the Address Sanitizer memory detect when turns on, make us easy to find out the memory problems. * 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. * chore(engine.cc): Google code style; more informative name * chore(REAME.md): require boost>=1.48, for boost::locale [ci skip] * fix(config_compiler): support creating list in-place by __patch and __merge * fix(CMakeLists.txt): do not link binaries when building static library * refactor(editor): extract helper class key_binding_processor * refactor(navigator): key bindings * fix(config_compiler): ambiguous operator overload with cmake option ENABLE_LOGGING=OFF Fixes #211 * feat(language): shared user dictionary per language (Closes #184) (#214) * fix(table_translator): enable encoding uniquified commit history * feat: always_show_comments option (#220) * feat: add always_show_comments option * fix: address feedback Line wraps --- .npmrc | 2 +- CHANGELOG.md | 36 +++++ CMakeLists.txt | 23 ++- README.md | 7 +- bump-version.sh | 12 ++ data/test/config_merge_test.yaml | 8 + package.json | 4 +- src/rime/common.h | 2 + src/rime/config/config_compiler.cc | 59 ++++--- src/rime/config/config_compiler.h | 6 +- src/rime/config/config_compiler_impl.h | 6 +- src/rime/config/config_data.cc | 43 +++-- src/rime/dict/level_db.cc | 8 +- src/rime/dict/user_db.cc | 13 +- src/rime/dict/user_db.h | 17 +- src/rime/dict/user_dictionary.cc | 13 +- src/rime/dict/user_dictionary.h | 2 +- src/rime/engine.cc | 3 +- src/rime/gear/editor.cc | 80 ++-------- src/rime/gear/editor.h | 9 +- src/rime/gear/key_binding_processor.h | 46 ++++++ src/rime/gear/key_binding_processor_impl.h | 91 +++++++++++ src/rime/gear/memory.cc | 13 +- src/rime/gear/memory.h | 10 +- src/rime/gear/navigator.cc | 123 +++++++++------ src/rime/gear/navigator.h | 23 ++- src/rime/gear/poet.h | 8 +- src/rime/gear/script_translator.cc | 6 +- src/rime/gear/script_translator.h | 2 + src/rime/gear/table_translator.cc | 19 +-- src/rime/gear/table_translator.h | 14 +- src/rime/gear/translator_commons.h | 8 +- src/rime/language.cc | 18 +++ src/rime/language.h | 34 ++++ src/rime/lever/deployment_tasks.cc | 155 ++++++++++--------- src/rime/lever/user_dict_manager.cc | 9 +- test/config_compiler_test.cc | 6 + thirdparty/src/leveldb/build_detect_platform | 6 + 38 files changed, 611 insertions(+), 333 deletions(-) create mode 100755 bump-version.sh create mode 100644 src/rime/gear/key_binding_processor.h create mode 100644 src/rime/gear/key_binding_processor_impl.h create mode 100644 src/rime/language.cc create mode 100644 src/rime/language.h diff --git a/.npmrc b/.npmrc index 75451031e..ed67c5cc0 100644 --- a/.npmrc +++ b/.npmrc @@ -1,2 +1,2 @@ -tag-version-prefix="rime-" +tag-version-prefix="" message="chore(release): %s :tada:" diff --git a/CHANGELOG.md b/CHANGELOG.md index 526f3f86b..ff00f18e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,39 @@ + +## [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) + + +### 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 semver 'X.Y.Z' + + +### 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/CMakeLists.txt b/CMakeLists.txt index 48836af1e..b66850f31 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.1) set(rime_soversion 1) add_definitions(-DRIME_VERSION="${rime_version}") @@ -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}) @@ -186,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() diff --git a/README.md b/README.md index 9c5a8cadd..b97d0d31d 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 @@ -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 diff --git a/bump-version.sh b/bump-version.sh new file mode 100755 index 000000000..0585d8e74 --- /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/data/test/config_merge_test.yaml b/data/test/config_merge_test.yaml index 6a660131f..3c570a46b 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/package.json b/package.json index ee072e0b9..32cf6c66f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "librime", - "version": "1.2.10", + "version": "1.3.1", "description": "Rime Input Method Engine", "main": "index.js", "directories": { @@ -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", diff --git a/src/rime/common.h b/src/rime/common.h index d3b9d0232..56ae277fa 100644 --- a/src/rime/common.h +++ b/src/rime/common.h @@ -37,6 +37,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/config/config_compiler.cc b/src/rime/config/config_compiler.cc index 8af3de032..053ab948f 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; @@ -97,8 +105,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; @@ -155,33 +168,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_compiler.h b/src/rime/config/config_compiler.h index a08fd0169..a6cdd45c0 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 00c1df7ee..bd4cf701c 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; diff --git a/src/rime/config/config_data.cc b/src/rime/config/config_data.cc index 980c1042d..43783f147 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) { diff --git a/src/rime/dict/level_db.cc b/src/rime/dict/level_db.cc index 31e41b7df..21c106611 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 32a8a8691..d97b30457 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 168f6af96..a3e1047fd 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/dict/user_dictionary.cc b/src/rime/dict/user_dictionary.cc index 078d3d984..2c7b9869a 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 67f1427fb..4601a22f6 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/engine.cc b/src/rime/engine.cc index 420abd012..5d839f4ed 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 reached_caret_pos = (seg.end >= ctx->caret_pos()); ctx->composition().Forward(); - if (seg.end >= ctx->caret_pos()) { + if (reached_caret_pos) { // finished converting current segment // move caret to the end of input ctx->set_caret_pos(ctx->input().length()); diff --git a/src/rime/gear/editor.cc b/src/rime/gear/editor.cc index 021394d2c..27e0e276b 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 dcaa0577d..72a00a7ea 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 000000000..1df8a71dc --- /dev/null +++ b/src/rime/gear/key_binding_processor.h @@ -0,0 +1,46 @@ +// +// Copyright RIME Developers +// Distributed under the BSD License +// +#ifndef RIME_KEY_BINDING_PROCESSOR_H_ +#define RIME_KEY_BINDING_PROCESSOR_H_ + +#include +#include +#include +#include +#include + +namespace rime { + +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 000000000..e4b89471e --- /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 diff --git a/src/rime/gear/memory.cc b/src/rime/gear/memory.cc index 644bfd7bb..bde9a4785 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 5ec8145f7..0b065491f 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/navigator.cc b/src/rime/gear/navigator.cc index 17e0d3acf..13d499b87 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 39d109f48..f85194e19 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_; diff --git a/src/rime/gear/poet.h b/src/rime/gear/poet.h index 55f0c36b8..df27da549 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/script_translator.cc b/src/rime/gear/script_translator.cc index 9853e508b..8a168c478 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 0a9c6d6e4..2eab5cf55 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 diff --git a/src/rime/gear/table_translator.cc b/src/rime/gear/table_translator.cc index 6f6d0c892..86a83b5ea 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) @@ -346,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 diff --git a/src/rime/gear/table_translator.h b/src/rime/gear/table_translator.h index 6b5767310..cd4b01dbe 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 566a522d2..3367bf41e 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 000000000..5626499f8 --- /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 000000000..dbc8c0a2b --- /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_ diff --git a/src/rime/lever/deployment_tasks.cc b/src/rime/lever/deployment_tasks.cc index f5fcbdecc..d6e04c34d 100644 --- a/src/rime/lever/deployment_tasks.cc +++ b/src/rime/lever/deployment_tasks.cc @@ -198,9 +198,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; @@ -266,55 +265,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 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 TrashCustomizedCopy(const fs::path& shared_copy, - const fs::path& user_copy, - const string& version_key, - const fs::path& trash) { +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) { @@ -366,11 +376,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(); - LOG(INFO) << "preparing dictionary '" << dict_name << "'. before compile\n"; - + 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.\n"; return false; @@ -402,20 +412,26 @@ 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()) { - 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; } } @@ -429,10 +445,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; } @@ -538,13 +554,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; @@ -602,12 +613,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; diff --git a/src/rime/lever/user_dict_manager.cc b/src/rime/lever/user_dict_manager.cc index 515521f24..2a23f049d 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; diff --git a/test/config_compiler_test.cc b/test/config_compiler_test.cc index fbd905c6f..0c83c1fd7 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")); diff --git a/thirdparty/src/leveldb/build_detect_platform b/thirdparty/src/leveldb/build_detect_platform index bb76c4f22..8f7f0309d 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 <