From a6a4423a68c1018451a490d378f7f5ad62b3f5fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=85=E6=88=8E=E6=B0=8F?= Date: Thu, 16 Jan 2025 10:38:38 +0800 Subject: [PATCH] feat(punctuator): auto-convert digit separator fixes #972 use ascii punctuation ,.:' after numbers. they are auto-committed if followed by digit. or commit manualy with space key. double strike the key to access the original binding. --- src/rime/gear/punctuator.cc | 86 +++++++++++++++++++++++++++++-------- src/rime/gear/punctuator.h | 3 +- 2 files changed, 70 insertions(+), 19 deletions(-) diff --git a/src/rime/gear/punctuator.cc b/src/rime/gear/punctuator.cc index a391b85159..ed439c3d7a 100644 --- a/src/rime/gear/punctuator.cc +++ b/src/rime/gear/punctuator.cc @@ -58,6 +58,30 @@ static bool punctuation_is_translated(Context* ctx) { return cand && cand->type() == "punct"; } +inline static bool is_digit_separator(char ch) { + return ch == '.' || ch == ':' || ch == ',' || ch == '\''; +} + +inline static bool ends_with_digit(const string& text) { + auto len = text.length(); + return len > 0 && isdigit(text[len - 1]); +} + +// recognizes patterns like 3.14 12:30 1,000 1'000 +static bool is_after_number(Context* ctx) { + const CommitHistory& history = ctx->commit_history(); + if (history.empty()) { + return false; + } + const CommitRecord& cr = history.back(); + return ends_with_digit(cr.text) & (cr.type == "thru" || cr.type == "raw"); +} + +static bool is_after_digit_separator(Context* ctx) { + const auto& comp = ctx->composition(); + return !comp.empty() && comp.back().HasTag("punct_number"); +} + ProcessResult Punctuator::ProcessKeyEvent(const KeyEvent& key_event) { if (key_event.release() || key_event.ctrl() || key_event.alt() || key_event.super()) @@ -72,29 +96,48 @@ ProcessResult Punctuator::ProcessKeyEvent(const KeyEvent& key_event) { if (!use_space_ && ch == XK_space && ctx->IsComposing()) { return kNoop; } - if (ch == '.' || ch == ':') { // 3.14, 12:30 - const CommitHistory& history(ctx->commit_history()); - if (!history.empty()) { - const CommitRecord& cr(history.back()); - if (cr.type == "thru" && cr.text.length() == 1 && isdigit(cr.text[0])) { - return kRejected; - } - } + if (isdigit(ch) && is_after_digit_separator(ctx)) { + ctx->PushInput(ch) && ctx->Commit(); + return kAccepted; } + // sync with full_shape option config_.LoadConfig(engine_); - string punct_key(1, ch); - auto punct_definition = config_.GetPunctDefinition(punct_key); + string key(1, ch); + auto punct_definition = config_.GetPunctDefinition(key); if (!punct_definition) return kNoop; - DLOG(INFO) << "punct key: '" << punct_key << "'"; - if (!AlternatePunct(punct_key, punct_definition)) { - ctx->PushInput(ch) && punctuation_is_translated(ctx) && - (ConfirmUniquePunct(punct_definition) || - AutoCommitPunct(punct_definition) || PairPunct(punct_definition)); + DLOG(INFO) << "punct key: '" << key << "'"; + if (AlternatePunct(key, punct_definition)) { + return kAccepted; + } + if (ToggleNumberMode(key) || ctx->PushInput(ch)) { + if (punctuation_is_translated(ctx)) { + ConfirmUniquePunct(punct_definition) || + AutoCommitPunct(punct_definition) || PairPunct(punct_definition); + } } return kAccepted; } +bool Punctuator::ToggleNumberMode(const string& key) { + Context* ctx = engine_->context(); + if (ctx->input() == key) { + Composition& comp = ctx->composition(); + if (!comp.empty()) { + Segment& segment(comp.back()); + if (segment.HasTag("punct_number")) { + segment.tags.erase("punct_number"); + segment.tags.insert("punct"); + segment.status = Segment::kVoid; + DLOG(INFO) << "exit number mode, key = " << key; + ctx->update_notifier()(ctx); + return true; + } + } + } + return false; +} + bool Punctuator::AlternatePunct(const string& key, const an& definition) { if (!As(definition)) @@ -170,16 +213,22 @@ bool PunctSegmentor::Proceed(Segmentation* segmentation) { char ch = input[k]; if (ch < 0x20 || ch >= 0x7f) return true; + // sync with full_shape option config_.LoadConfig(engine_); - string punct_key(1, ch); - auto punct_definition = config_.GetPunctDefinition(punct_key); + string key(1, ch); + auto punct_definition = config_.GetPunctDefinition(key); if (!punct_definition) return true; { Segment segment(k, k + 1); DLOG(INFO) << "add a punctuation segment [" << segment.start << ", " << segment.end << ")"; - segment.tags.insert("punct"); + if (k == 0 && is_digit_separator(ch) && + is_after_number(engine_->context())) { + segment.tags.insert("punct_number"); + } else { + segment.tags.insert("punct"); + } segmentation->AddSegment(segment); } return false; // exclusive @@ -235,6 +284,7 @@ an PunctTranslator::Query(const string& input, const Segment& segment) { if (!segment.HasTag("punct")) return nullptr; + // sync with full_shape option config_.LoadConfig(engine_); auto definition = config_.GetPunctDefinition(input); if (!definition) diff --git a/src/rime/gear/punctuator.h b/src/rime/gear/punctuator.h index 836ec3aabb..beb4d8dfac 100644 --- a/src/rime/gear/punctuator.h +++ b/src/rime/gear/punctuator.h @@ -35,8 +35,9 @@ class Punctuator : public Processor { virtual ProcessResult ProcessKeyEvent(const KeyEvent& key_event); protected: - bool ConfirmUniquePunct(const an& definition); + bool ToggleNumberMode(const string& key); bool AlternatePunct(const string& key, const an& definition); + bool ConfirmUniquePunct(const an& definition); bool AutoCommitPunct(const an& definition); bool PairPunct(const an& definition);