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 <