Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package librime for openSUSE:Factory checked in at 2026-08-14 22:07:33 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/librime (Old) and /work/SRC/openSUSE:Factory/.librime.new.1258 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "librime" Fri Aug 14 22:07:33 2026 rev:40 rq:1371059 version:1.17.0 Changes: -------- --- /work/SRC/openSUSE:Factory/librime/librime.changes 2026-02-09 15:34:47.795259760 +0100 +++ /work/SRC/openSUSE:Factory/.librime.new.1258/librime.changes 2026-08-14 22:07:52.906972616 +0200 @@ -1,0 +2,11 @@ +Thu Aug 13 14:32:01 UTC 2026 - Hillwood Yang <[email protected]> + +- Update version to 1.17.0 + * syllabifier: trim leading delimiters + * navigator: Allows jumping to before the delimiter. + * poet: make multiple sentences + * script_translator: allow multiple sentence candidates + * Fix bugs +- Add librime-fix-gcc14-cpp20-compat.patch, fix C++20 compilation errors + +------------------------------------------------------------------- Old: ---- librime-1.16.1.tar.gz New: ---- librime-1.17.0.tar.gz librime-fix-gcc14-cpp20-compat.patch ----------(New B)---------- New: * Fix bugs - Add librime-fix-gcc14-cpp20-compat.patch, fix C++20 compilation errors ----------(New E)---------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ librime.spec ++++++ --- /var/tmp/diff_new_pack.EYywc5/_old 2026-08-14 22:07:53.605998067 +0200 +++ /var/tmp/diff_new_pack.EYywc5/_new 2026-08-14 22:07:53.607998140 +0200 @@ -17,15 +17,17 @@ Name: librime -Version: 1.16.1 +Version: 1.17.0 Release: 0 Summary: Rime Input Method Engine License: BSD-3-Clause Group: System/I18n/Chinese URL: https://github.com/rime/librime Source: https://github.com/rime/%{name}/archive/refs/tags/%{version}.tar.gz#/%{name}-%{version}.tar.gz -#PATCH-FIX-OPENSUSE librime-boost166.patch [email protected] -- leap's gcc7 has no <filesystem> +# PATCH-FIX-OPENSUSE librime-boost166.patch [email protected] -- leap's gcc7 has no <filesystem> Patch0: librime-boost166.patch +# PATCH-FIX-UPSTREAM librime-fix-gcc14-cpp20-compat.patch [email protected] -- Fix C++20 compilation errors +Patch1: librime-fix-gcc14-cpp20-compat.patch BuildRequires: capnproto >= 0.7.0 BuildRequires: cmake >= 3.1.0 BuildRequires: gcc-c++ @@ -101,6 +103,7 @@ %if 0%{?suse_version} == 1500 %patch -P 0 -p 1 %endif +%patch -P 1 -p 1 %build %cmake -DCMAKE_BUILD_TYPE=Release \ ++++++ librime-1.16.1.tar.gz -> librime-1.17.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/librime-1.16.1/.github/workflows/commit-ci.yml new/librime-1.17.0/.github/workflows/commit-ci.yml --- old/librime-1.16.1/.github/workflows/commit-ci.yml 2026-01-20 01:20:26.000000000 +0100 +++ new/librime-1.17.0/.github/workflows/commit-ci.yml 2026-06-05 13:32:44.000000000 +0200 @@ -30,10 +30,14 @@ macos: needs: lint uses: ./.github/workflows/macos-build.yml + with: + rime_plugins: hchunhui/librime-lua lotem/librime-octagram rime/librime-predict windows: needs: lint uses: ./.github/workflows/windows-build.yml + with: + rime_plugins: hchunhui/librime-lua lotem/librime-octagram rime/librime-predict docker: needs: lint diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/librime-1.16.1/CHANGELOG.md new/librime-1.17.0/CHANGELOG.md --- old/librime-1.16.1/CHANGELOG.md 2026-01-20 01:20:26.000000000 +0100 +++ new/librime-1.17.0/CHANGELOG.md 2026-06-05 13:32:44.000000000 +0200 @@ -1,3 +1,21 @@ +<a name="1.17.0"></a> +## 1.17.0 (2026-06-05) + +### Bug 修復 | Bug Fixes +- excluded words cause crashes (#1152) (4218af0) +- forward/rewind 步長視未選定編碼分節數而定 (3a0bafa) +- commit ci should also build with plugins (#1160) (422ad2d) +- space after digit separators shouldn't be translated (aa32d48) +- 不過濾漢字以外的符號 (1300e56) + +### 主要功能更新 | Major Updates +- trim leading delimiters (#1147) (4ff27f8) +- 允許跳轉到分隔符之前 (#1142) (2b7ccf2) +- make multiple sentences (d32cd32) +- allow multiple sentence candidates (9422ca7) + +**Full Changelog**: https://github.com/rime/squirrel/compare/1.16.1...1.17.0 + <a name="1.16.1"></a> ## 1.16.1 (2026-01-20) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/librime-1.16.1/CMakeLists.txt new/librime-1.17.0/CMakeLists.txt --- old/librime-1.16.1/CMakeLists.txt 2026-01-20 01:20:26.000000000 +0100 +++ new/librime-1.17.0/CMakeLists.txt 2026-06-05 13:32:44.000000000 +0200 @@ -5,7 +5,7 @@ project(rime) set(CMAKE_CXX_STANDARD 17) -set(rime_version 1.16.1) +set(rime_version 1.17.0) set(rime_soversion 1) add_definitions(-DRIME_VERSION="${rime_version}") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/librime-1.16.1/src/rime/algo/syllabifier.cc new/librime-1.17.0/src/rime/algo/syllabifier.cc --- old/librime-1.16.1/src/rime/algo/syllabifier.cc 2026-01-20 01:20:26.000000000 +0100 +++ new/librime-1.17.0/src/rime/algo/syllabifier.cc 2026-06-05 13:32:44.000000000 +0200 @@ -53,12 +53,19 @@ if (current_pos > farthest) farthest = current_pos; - DLOG(INFO) << "current_pos: " << current_pos; + + // consume leading delimiters + size_t begin_pos = current_pos; + while (begin_pos < input.length() && + delimiters_.find(input[begin_pos]) != string::npos) + ++begin_pos; + DLOG(INFO) << "current_pos: " << current_pos + << ", begin_pos: " << begin_pos; // see where we can go by advancing a syllable vector<Prism::Match> matches; set<SyllableId> exact_match_syllables; - auto current_input = input.substr(current_pos); + auto current_input = input.substr(begin_pos); prism.CommonPrefixSearch(current_input, &matches); if (corrector_) { for (auto& m : matches) { @@ -78,12 +85,13 @@ } } + size_t leading_gap = begin_pos - current_pos; if (!matches.empty()) { auto& end_vertices(graph->edges[current_pos]); for (const auto& m : matches) { if (m.length == 0) continue; - size_t end_pos = current_pos + m.length; + size_t end_pos = current_pos + leading_gap + m.length; // consume trailing delimiters while (end_pos < input.length() && delimiters_.find(input[end_pos]) != string::npos) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/librime-1.16.1/src/rime/common.h new/librime-1.17.0/src/rime/common.h --- old/librime-1.16.1/src/rime/common.h 2026-01-20 01:20:26.000000000 +0100 +++ new/librime-1.17.0/src/rime/common.h 2026-06-05 13:32:44.000000000 +0200 @@ -18,6 +18,7 @@ #include <string> #include <utility> #include <vector> +#include <deque> #define BOOST_BIND_NO_PLACEHOLDERS #include <boost/signals2/connection.hpp> #include <boost/signals2/signal.hpp> @@ -37,6 +38,7 @@ namespace rime { +using std::deque; using std::function; using std::list; using std::make_pair; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/librime-1.16.1/src/rime/gear/abc_segmentor.cc new/librime-1.17.0/src/rime/gear/abc_segmentor.cc --- old/librime-1.16.1/src/rime/gear/abc_segmentor.cc 2026-01-20 01:20:26.000000000 +0100 +++ new/librime-1.17.0/src/rime/gear/abc_segmentor.cc 2026-06-05 13:32:44.000000000 +0200 @@ -44,7 +44,7 @@ bool expecting_an_initial = true; for (; k < input.length(); ++k) { bool is_letter = alphabet_.find(input[k]) != string::npos; - bool is_delimiter = (k != j) && (delimiter_.find(input[k]) != string::npos); + bool is_delimiter = (k != 0) && (delimiter_.find(input[k]) != string::npos); if (!is_letter && !is_delimiter) break; bool is_initial = initials_.find(input[k]) != string::npos; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/librime-1.16.1/src/rime/gear/charset_filter.cc new/librime-1.17.0/src/rime/gear/charset_filter.cc --- old/librime-1.16.1/src/rime/gear/charset_filter.cc 2026-01-20 01:20:26.000000000 +0100 +++ new/librime-1.17.0/src/rime/gear/charset_filter.cc 2026-06-05 13:32:44.000000000 +0200 @@ -26,9 +26,9 @@ (ch >= 0x31350 && ch <= 0x323AF) || // CJK Unified Ideographs Extension H (ch >= 0x2EBF0 && ch <= 0x2EE5F) || // CJK Unified Ideographs Extension I (ch >= 0x323B0 && ch <= 0x3347F) || // CJK Unified Ideographs Extension J - (ch >= 0x3300 && ch <= 0x33FF) || // CJK Compatibility - (ch >= 0xFE30 && ch <= 0xFE4F) || // CJK Compatibility Forms - (ch >= 0xF900 && ch <= 0xFAFF) || // CJK Compatibility Ideographs + // (ch >= 0x3300 && ch <= 0x33FF) || // CJK Compatibility + // (ch >= 0xFE30 && ch <= 0xFE4F) || // CJK Compatibility Forms + (ch >= 0xF900 && ch <= 0xFAFF) || // CJK Compatibility Ideographs (ch >= 0x2F800 && ch <= 0x2FA1F)) // CJK Compatibility Ideographs Supplement return true; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/librime-1.16.1/src/rime/gear/navigator.cc new/librime-1.17.0/src/rime/gear/navigator.cc --- old/librime-1.16.1/src/rime/gear/navigator.cc 2026-01-20 01:20:26.000000000 +0100 +++ new/librime-1.17.0/src/rime/gear/navigator.cc 2026-06-05 13:32:44.000000000 +0200 @@ -66,6 +66,16 @@ Config* config = engine_->schema()->config(); LoadConfig(config, "navigator", Horizontal); LoadConfig(config, "navigator/vertical", Vertical); + config->GetString("speller/delimiter", &delimiters_); + string syllable_jump_position; + if (config->GetString("navigator/syllable_jump_position", + &syllable_jump_position)) { + if (syllable_jump_position == "before_delimiter") { + syllable_jump_position_ = BeforeDelimiter; + } else { + syllable_jump_position_ = AfterDelimiter; + } + } select_connection_ = engine_->context()->select_notifier().connect( [this](Context* ctx) { OnSelect(ctx); }); @@ -119,10 +129,11 @@ bool Navigator::Rewind(Context* ctx) { BeginMove(ctx); - // take a jump leftwards when there are multiple spans, + size_t confirmed_pos = ctx->composition().GetConfirmedPosition(); + // take a jump leftwards when there are multiple non-confirmed spans, // but not from the middle of a span. - if (spans_.Count() > 1 && spans_.HasVertex(ctx->caret_pos())) { - size_t confirmed_pos = ctx->composition().GetConfirmedPosition(); + if (spans_.Count(confirmed_pos, spans_.end()) > 1 && + spans_.HasVertex(ctx->caret_pos())) { JumpLeft(ctx, confirmed_pos, true); } else { MoveLeft(ctx); @@ -132,8 +143,10 @@ bool Navigator::Forward(Context* ctx) { BeginMove(ctx); - if (spans_.Count() > 1 && spans_.HasVertex(ctx->caret_pos())) { - size_t confirmed_pos = ctx->composition().GetConfirmedPosition(); + size_t confirmed_pos = ctx->composition().GetConfirmedPosition(); + // when half-selected, count the rest spans. + if (spans_.Count(confirmed_pos, spans_.end()) > 1 && + spans_.HasVertex(ctx->caret_pos())) { JumpRight(ctx, confirmed_pos, true); } else { MoveRight(ctx); @@ -199,11 +212,12 @@ size_t caret_pos = ctx->caret_pos(); size_t end_of_translation = spans_.end(); size_t end_of_input = ctx->input().length(); + size_t span_start_pos = ToSpanStart(caret_pos); size_t new_pos = // 跳過未翻譯的輸入碼 - (caret_pos > end_of_translation) ? end_of_translation + (span_start_pos > end_of_translation) ? end_of_translation // 迴轉? - : (loop && caret_pos <= start_pos) + : (loop && span_start_pos <= start_pos) ? // 若未翻譯完則須重譯,光標迴轉到結尾 (end_of_input > end_of_translation) @@ -211,7 +225,9 @@ // 迴轉後向前找 : (std::max)(start_pos, spans_.PreviousStop(end_of_input)) // 跳至前一個切分點 - : (std::max)(start_pos, spans_.PreviousStop(caret_pos)); + : (std::max)(start_pos, spans_.PreviousStop(span_start_pos)); + if (syllable_jump_position_ == BeforeDelimiter) + new_pos = ToSyllableEnd(new_pos); if (new_pos != caret_pos) { ctx->set_caret_pos(new_pos); return true; @@ -224,13 +240,16 @@ size_t caret_pos = ctx->caret_pos(); size_t end_of_translation = spans_.end(); size_t end_of_input = ctx->input().length(); + size_t span_end_pos = ToSpanEnd(caret_pos); size_t new_pos = // 已在結尾則從頭開始向後找 - (loop && caret_pos == end_of_input) ? spans_.NextStop(start_pos) + (loop && span_end_pos == end_of_input) ? spans_.NextStop(start_pos) // 跳過未翻譯的輸入碼 - : (caret_pos >= end_of_translation) ? end_of_input - // 跳至後一個切分點 - : spans_.NextStop(caret_pos); + : (span_end_pos >= end_of_translation) ? end_of_input + // 跳至後一個切分點 + : spans_.NextStop(span_end_pos); + if (syllable_jump_position_ == BeforeDelimiter) + new_pos = ToSyllableEnd(new_pos); if (new_pos != caret_pos) { ctx->set_caret_pos(new_pos); return true; @@ -290,4 +309,53 @@ return false; } +// Considering delimiters (both leading and trailing), the span can consist of +// four positions: +// +// [ ' ' syll ' ' '] +// ^ ^ ^ ^ +// | start |leading_delim_end |trailing_delim_start +// |trailing_delim_end=end +// +// Spans only consider start and end. To jump precisely, sometimes we +// need to fix the current position to a semantic boundary. +// +// Find 'start' if pos in leading delimiters, i.e. [start, leading_delim_end]. +size_t Navigator::ToSpanStart(size_t pos) { + return SkipDelimiterBackward(pos); +} + +// Find 'trailing_delim_end' or 'end' if pos in trailing delimiters, +// i.e. [trailing_delim_start, end]. +size_t Navigator::ToSpanEnd(size_t pos) { + return SkipDelimiterForward(pos); +} + +// Find 'leading_delim_end', or the start pos of the actual syllable, +// if pos in leading delimiters, i.e. [start, leading_delim_end]. +size_t Navigator::ToSyllableStart(size_t pos) { + return SkipDelimiterForward(pos); +} + +// Find 'trailing_delim_start', or the end pos of the actual syllable, +// if pos in trailing delimiters, i.e. [trailing_delim_end, end]. +size_t Navigator::ToSyllableEnd(size_t pos) { + return SkipDelimiterBackward(pos); +} + +// Move pos backward (decreasing pos), skipping any delimiter. +size_t Navigator::SkipDelimiterBackward(size_t pos) { + while (pos > 0 && delimiters_.find(input_[pos - 1]) != string::npos) + pos--; + return pos; +} + +// Move pos forward (increasing pos), skipping any delimiter. +size_t Navigator::SkipDelimiterForward(size_t pos) { + while (pos < input_.length() - 1 && + delimiters_.find(input_[pos]) != string::npos) + pos++; + return pos; +} + } // namespace rime diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/librime-1.16.1/src/rime/gear/navigator.h new/librime-1.17.0/src/rime/gear/navigator.h --- old/librime-1.16.1/src/rime/gear/navigator.h 2026-01-20 01:20:26.000000000 +0100 +++ new/librime-1.17.0/src/rime/gear/navigator.h 2026-06-05 13:32:44.000000000 +0200 @@ -21,6 +21,10 @@ Horizontal = 0, Vertical = 1, }; + enum SyllableJumpPosition { + AfterDelimiter = 0, + BeforeDelimiter = 1, + }; explicit Navigator(const Ticket& ticket); virtual ~Navigator(); @@ -54,6 +58,16 @@ Spans spans_; connection select_connection_; + + private: + string delimiters_; + SyllableJumpPosition syllable_jump_position_ = AfterDelimiter; + size_t ToSpanStart(size_t pos); + size_t ToSpanEnd(size_t pos); + size_t ToSyllableStart(size_t pos); + size_t ToSyllableEnd(size_t pos); + size_t SkipDelimiterBackward(size_t pos); + size_t SkipDelimiterForward(size_t pos); }; } // namespace rime diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/librime-1.16.1/src/rime/gear/poet.cc new/librime-1.17.0/src/rime/gear/poet.cc --- old/librime-1.16.1/src/rime/gear/poet.cc 2026-01-20 01:20:26.000000000 +0100 +++ new/librime-1.17.0/src/rime/gear/poet.cc 2026-06-05 13:32:44.000000000 +0200 @@ -26,6 +26,7 @@ const DictEntry* entry; size_t end_pos; double weight; + size_t text_hash; // for dedup static const Line kEmpty; @@ -68,7 +69,7 @@ } }; -const Line Line::kEmpty{nullptr, nullptr, 0, 0.0}; +const Line Line::kEmpty{nullptr, nullptr, 0, 0.0, 0}; inline static Grammar* create_grammar(Config* config) { if (auto* grammar = Grammar::Require("grammar")) { @@ -251,4 +252,104 @@ graph, total_length, preceding_text); } +// Make `max_sentences` sentences using beam search and dp on word graph. +// +// There is no strategy because it unconditionally use grammar. +deque<an<Sentence>> Poet::MakeSentences(const WordGraph& graph, + size_t total_length, + const string& preceding_text, + size_t max_sentences, + double cutoff_threshold) { + size_t beam_width = + max_sentences * 3; // allow more possibilities during search + using State = std::list<Line>; + map<int, State> states; + states[0].push_back(Line::kEmpty); + for (const auto& sv : graph) { + size_t start_pos = sv.first; + if (states.find(start_pos) == states.end()) + continue; + + const auto& source_state = states[start_pos]; + for (const auto& ev : sv.second) { + size_t end_pos = ev.first; + if (start_pos == 0 && end_pos == total_length) + continue; + const DictEntryList& entries = ev.second; + bool is_rear = end_pos == total_length; + auto& target_state = states[end_pos]; + + for (const auto& source_line : source_state) { + for (const auto& entry : entries) { + const string& context = + source_line.empty() ? preceding_text : source_line.context(); + double weight = source_line.weight + + Grammar::Evaluate(context, entry->text, entry->weight, + is_rear, grammar_.get()); + size_t new_hash = source_line.text_hash; + for (char c : entry->text) { + new_hash = new_hash * 31 + c; + } + Line new_line{&source_line, entry.get(), end_pos, weight, new_hash}; + + // dedup by text hash + auto dup = std::find_if( + target_state.begin(), target_state.end(), + [&](const Line& l) { return l.text_hash == new_line.text_hash; }); + if (dup != target_state.end()) { + if (new_line.weight > dup->weight) { + target_state.erase(dup); + } else { + continue; + } + } + + // insert in descending order of weight + auto it = std::find_if( + target_state.begin(), target_state.end(), + [&](const Line& l) { return l.weight < new_line.weight; }); + target_state.insert(it, new_line); + if (target_state.size() > beam_width) + target_state.pop_back(); + } + } + } + } + + auto found = states.find(total_length); + if (found == states.end() || found->second.empty()) + return {}; + + deque<an<Sentence>> results; + double last_weight; + double acceleration = 1.0 - 1.0 / (double)max_sentences; + auto iter = found->second.begin(); + for (size_t i = 0; iter != found->second.end() && i < max_sentences; + ++i, ++iter) { + const auto& candidate = *iter; + double cur_weight = candidate.weight; + if (i > 0) { + // idea: if the current sentence is, on average, not too rare when + // compared to last sentence, we should consider it too + if (fabs(cur_weight - last_weight) / fabs(last_weight) > + cutoff_threshold) { + break; + } + // but don't deviate too far from the first weight by accelerating + // the cutoff threshold. cutoff_threshold becomes + // ~0.36*cutoff_threshold after N candidates are added. + cutoff_threshold *= acceleration; + } + last_weight = cur_weight; + auto sentence = New<Sentence>(language_); + for (const auto* c : candidate.components()) { + if (!c->entry) + continue; + sentence->Extend(*c->entry, c->end_pos, c->weight); + } + results.emplace_back(sentence); + } + return results; +} + } // namespace rime diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/librime-1.16.1/src/rime/gear/poet.h new/librime-1.17.0/src/rime/gear/poet.h --- old/librime-1.16.1/src/rime/gear/poet.h 2026-01-20 01:20:26.000000000 +0100 +++ new/librime-1.17.0/src/rime/gear/poet.h 2026-06-05 13:32:44.000000000 +0200 @@ -39,6 +39,11 @@ an<Sentence> MakeSentence(const WordGraph& graph, size_t total_length, const string& preceding_text); + deque<an<Sentence>> MakeSentences(const WordGraph& graph, + size_t total_length, + const string& preceding_text, + size_t count, + double cutoff_threshold); template <class TranslatorT> an<Translation> ContextualWeighted(an<Translation> translation, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/librime-1.16.1/src/rime/gear/punctuator.cc new/librime-1.17.0/src/rime/gear/punctuator.cc --- old/librime-1.16.1/src/rime/gear/punctuator.cc 2026-01-20 01:20:26.000000000 +0100 +++ new/librime-1.17.0/src/rime/gear/punctuator.cc 2026-06-05 13:32:44.000000000 +0200 @@ -103,7 +103,12 @@ return kNoop; } if ((isdigit(ch) || ch == XK_space) && is_after_digit_separator(ctx)) { - ctx->PushInput(ch) && ctx->Commit(); + // pushing ch directly may cause ch to be translated + auto& comp = ctx->composition(); + string s = ctx->input().substr(comp[0].start, comp[0].end) + char(ch); + ctx->AbortComposition(); + ctx->PushInput(s); + ctx->Commit(); return kAccepted; } if (!use_space_ && ch == XK_space && ctx->IsComposing()) { @@ -253,14 +258,18 @@ return true; { Segment segment(k, k + 1); - DLOG(INFO) << "add a punctuation segment [" << segment.start << ", " - << segment.end << ")"; if (k == 0 && config_.is_digit_separator(ch) && is_after_number(engine_->context())) { + // space to be committed directly + if (input.length() > k + 1 && input[k + 1] == XK_space) { + segment.end += 1; + } segment.tags.insert("punct_number"); } else { segment.tags.insert("punct"); } + DLOG(INFO) << "add a punctuation segment [" << segment.start << ", " + << segment.end << ")"; segmentation->AddSegment(segment); } return false; // exclusive diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/librime-1.16.1/src/rime/gear/script_translator.cc new/librime-1.17.0/src/rime/gear/script_translator.cc --- old/librime-1.16.1/src/rime/gear/script_translator.cc 2026-01-20 01:20:26.000000000 +0100 +++ new/librime-1.17.0/src/rime/gear/script_translator.cc 2026-06-05 13:32:44.000000000 +0200 @@ -115,14 +115,18 @@ Poet* poet, const string& input, size_t start, - size_t end_of_input) + size_t end_of_input, + int max_sentences, + double sentence_cutoff_threshold) : translator_(translator), poet_(poet), start_(start), end_of_input_(end_of_input), syllabifier_( New<ScriptSyllabifier>(translator, corrector, input, start)), - enable_correction_(corrector) { + enable_correction_(corrector), + max_sentences_(max_sentences), + sentence_cutoff_threshold_(sentence_cutoff_threshold) { set_exhausted(true); } bool Evaluate(Dictionary* dict, UserDictionary* user_dict); @@ -136,7 +140,11 @@ template <class QueryResult> void EnrollEntries(map<int, DictEntryList>& entries_by_end_pos, const an<QueryResult>& query_result); + WordGraph PrepareForMakingSentence(Dictionary* dict, + UserDictionary* user_dict); an<Sentence> MakeSentence(Dictionary* dict, UserDictionary* user_dict); + deque<an<Sentence>> MakeSentences(Dictionary* dict, + UserDictionary* user_dict); ScriptTranslator* translator_; Poet* poet_; @@ -146,7 +154,7 @@ an<DictEntryCollector> phrase_; an<UserDictEntryCollector> user_phrase_; - an<Sentence> sentence_; + deque<an<Sentence>> sentences_; an<Phrase> candidate_ = nullptr; size_t candidate_index_ = 0; @@ -163,6 +171,8 @@ const size_t max_corrections_ = 4; size_t correction_count_ = 0; + int max_sentences_ = 1; + double sentence_cutoff_threshold_ = 0.1; bool enable_correction_; }; @@ -210,8 +220,9 @@ size_t end_of_input = engine_->context()->input().length(); // the translator should survive translations it creates - auto result = New<ScriptTranslation>(this, corrector_.get(), poet_.get(), - input, segment.start, end_of_input); + auto result = New<ScriptTranslation>( + this, corrector_.get(), poet_.get(), input, segment.start, end_of_input, + max_sentences_, sentence_cutoff_threshold_); if (!result || !result->Evaluate( dict_.get(), enable_user_dict ? user_dict_.get() : NULL)) { return nullptr; @@ -484,7 +495,15 @@ // make sentences when there is no exact-matching phrase candidate if (has_at_least_two_syllables && !has_reliable_phrase && !has_reliable_user_phrase) { - sentence_ = MakeSentence(dict, user_dict); + if (max_sentences_ > 1) + sentences_ = MakeSentences(dict, user_dict); + else if (max_sentences_) { + auto sentence = MakeSentence(dict, user_dict); + if (sentence) + sentences_ = {sentence}; + else + sentences_.clear(); + } } return !CheckEmpty(); @@ -501,7 +520,8 @@ case kUninitialized: break; case kSentence: - sentence_.reset(); + if (!sentences_.empty()) + sentences_.pop_front(); break; case kUserPhrase: { UserDictEntryIterator& uter(user_phrase_iter_->second); @@ -569,14 +589,15 @@ } bool ScriptTranslation::PrepareCandidate() { +iter_incremented: if (exhausted()) { candidate_source_ = kUninitialized; candidate_ = nullptr; return false; } - if (sentence_) { + if (!sentences_.empty()) { candidate_source_ = kSentence; - candidate_ = sentence_; + candidate_ = sentences_[0]; return true; } const size_t full_code_length = end_of_input_ - start_; @@ -628,6 +649,10 @@ return true; } else if (phrase_code_length > 0) { DictEntryIterator& iter = phrase_iter_->second; + if (iter.exhausted()) { + ++phrase_iter_; + goto iter_incremented; + } const auto& entry = iter.Peek(); DLOG(INFO) << "phrase '" << entry->text << "', code length: " << phrase_code_length; @@ -670,8 +695,9 @@ } } -an<Sentence> ScriptTranslation::MakeSentence(Dictionary* dict, - UserDictionary* user_dict) { +WordGraph ScriptTranslation::PrepareForMakingSentence( + Dictionary* dict, + UserDictionary* user_dict) { const int kMaxSyllablesForUserPhraseQuery = 5; const auto& syllable_graph = syllabifier_->syllable_graph(); WordGraph graph; @@ -686,6 +712,29 @@ EnrollEntries(same_start_pos, dict->Lookup(syllable_graph, x.first, &translator_->blacklist())); } + return graph; +} + +deque<an<Sentence>> ScriptTranslation::MakeSentences( + Dictionary* dict, + UserDictionary* user_dict) { + const auto& syllable_graph = syllabifier_->syllable_graph(); + WordGraph graph = PrepareForMakingSentence(dict, user_dict); + auto sentences = + poet_->MakeSentences(graph, syllable_graph.interpreted_length, + translator_->GetPrecedingText(start_), + max_sentences_, sentence_cutoff_threshold_); + for (auto& sentence : sentences) { + sentence->Offset(start_); + sentence->set_syllabifier(syllabifier_); + } + return sentences; +} + +an<Sentence> ScriptTranslation::MakeSentence(Dictionary* dict, + UserDictionary* user_dict) { + const auto& syllable_graph = syllabifier_->syllable_graph(); + WordGraph graph = PrepareForMakingSentence(dict, user_dict); if (auto sentence = poet_->MakeSentence(graph, syllable_graph.interpreted_length, translator_->GetPrecedingText(start_))) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/librime-1.16.1/src/rime/gear/translator_commons.cc new/librime-1.17.0/src/rime/gear/translator_commons.cc --- old/librime-1.16.1/src/rime/gear/translator_commons.cc 2026-01-20 01:20:26.000000000 +0100 +++ new/librime-1.17.0/src/rime/gear/translator_commons.cc 2026-06-05 13:32:44.000000000 +0200 @@ -125,12 +125,18 @@ config->GetBool(ticket.name_space + "/strict_spelling", &strict_spelling_); config->GetDouble(ticket.name_space + "/initial_quality", &initial_quality_); + config->GetInt(ticket.name_space + "/max_sentences", &max_sentences_); + max_sentences_ = std::min(std::max(1, max_sentences_), 100); + config->GetDouble(ticket.name_space + "/sentence_cutoff_threshold", + &sentence_cutoff_threshold_); + preedit_formatter_.Load( config->GetList(ticket.name_space + "/preedit_format")); comment_formatter_.Load( config->GetList(ticket.name_space + "/comment_format")); user_dict_disabling_patterns_.Load( config->GetList(ticket.name_space + "/disable_user_dict_for_patterns")); + string tag; if (config->GetString(ticket.name_space + "/tag", &tag)) { // replace the first tag, and understand /tags as extra tags diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/librime-1.16.1/src/rime/gear/translator_commons.h new/librime-1.17.0/src/rime/gear/translator_commons.h --- old/librime-1.16.1/src/rime/gear/translator_commons.h 2026-01-20 01:20:26.000000000 +0100 +++ new/librime-1.17.0/src/rime/gear/translator_commons.h 2026-06-05 13:32:44.000000000 +0200 @@ -176,6 +176,8 @@ bool enable_completion_ = true; bool strict_spelling_ = false; double initial_quality_ = 0.; + int max_sentences_ = 1; + double sentence_cutoff_threshold_ = 0.1; Projection preedit_formatter_; Projection comment_formatter_; Patterns user_dict_disabling_patterns_; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/librime-1.16.1/test/syllabifier_test.cc new/librime-1.17.0/test/syllabifier_test.cc --- old/librime-1.16.1/test/syllabifier_test.cc 2026-01-20 01:20:26.000000000 +0100 +++ new/librime-1.17.0/test/syllabifier_test.cc 2026-06-05 13:32:44.000000000 +0200 @@ -160,3 +160,54 @@ ASSERT_FALSE(NULL == g.indices[0][syllable_id_["chan"]][0]); EXPECT_EQ(4, g.indices[0][syllable_id_["chan"]][0]->end_pos); } + +TEST_F(RimeSyllabifierTest, TrimLeadingDelimiters) { + rime::Syllabifier s(" '"); + rime::SyllableGraph g; + const rime::string input("''a"); + s.BuildSyllableGraph(input, *prism_, &g); + EXPECT_EQ(input.length(), g.input_length); + EXPECT_EQ(input.length(), g.interpreted_length); + EXPECT_EQ(2, g.vertices.size()); + ASSERT_FALSE(g.vertices.end() == g.vertices.find(3)); + EXPECT_EQ(rime::kNormalSpelling, g.vertices[1]); + rime::SpellingMap& sp(g.edges[0][3]); + EXPECT_EQ(1, sp.size()); + ASSERT_FALSE(sp.end() == sp.find(syllable_id_["a"])); + EXPECT_EQ(rime::kNormalSpelling, sp[0].type); + EXPECT_EQ(0.0, sp[0].credibility); +} + +TEST_F(RimeSyllabifierTest, TrimTrailingDelimiters) { + rime::Syllabifier s(" '"); + rime::SyllableGraph g; + const rime::string input("a''"); + s.BuildSyllableGraph(input, *prism_, &g); + EXPECT_EQ(input.length(), g.input_length); + EXPECT_EQ(input.length(), g.interpreted_length); + EXPECT_EQ(2, g.vertices.size()); + ASSERT_FALSE(g.vertices.end() == g.vertices.find(3)); + EXPECT_EQ(rime::kNormalSpelling, g.vertices[1]); + rime::SpellingMap& sp(g.edges[0][3]); + EXPECT_EQ(1, sp.size()); + ASSERT_FALSE(sp.end() == sp.find(syllable_id_["a"])); + EXPECT_EQ(rime::kNormalSpelling, sp[0].type); + EXPECT_EQ(0.0, sp[0].credibility); +} + +TEST_F(RimeSyllabifierTest, TrimBothLeadingAndTrailingDelimiters) { + rime::Syllabifier s(" '"); + rime::SyllableGraph g; + const rime::string input("''a''"); + s.BuildSyllableGraph(input, *prism_, &g); + EXPECT_EQ(input.length(), g.input_length); + EXPECT_EQ(input.length(), g.interpreted_length); + EXPECT_EQ(2, g.vertices.size()); + ASSERT_FALSE(g.vertices.end() == g.vertices.find(5)); + EXPECT_EQ(rime::kNormalSpelling, g.vertices[1]); + rime::SpellingMap& sp(g.edges[0][5]); + EXPECT_EQ(1, sp.size()); + ASSERT_FALSE(sp.end() == sp.find(syllable_id_["a"])); + EXPECT_EQ(rime::kNormalSpelling, sp[0].type); + EXPECT_EQ(0.0, sp[0].credibility); +} ++++++ librime-fix-gcc14-cpp20-compat.patch ++++++ diff -Nur librime-1.17.0/src/rime/common.h librime-1.17.0-new/src/rime/common.h --- librime-1.17.0/src/rime/common.h 2026-06-05 19:32:44.000000000 +0800 +++ librime-1.17.0-new/src/rime/common.h 2026-08-13 22:07:04.169978790 +0800 @@ -130,7 +130,7 @@ } #ifdef RIME_ENABLE_LOGGING friend std::ostream& operator<<(std::ostream& os, const path& p) { - return os << p.u8string(); + return os << p.string(); } #endif }; diff -Nur librime-1.17.0/src/rime/dict/vocabulary.h librime-1.17.0-new/src/rime/dict/vocabulary.h --- librime-1.17.0/src/rime/dict/vocabulary.h 2026-06-05 19:32:44.000000000 +0800 +++ librime-1.17.0-new/src/rime/dict/vocabulary.h 2026-08-13 22:24:34.889708508 +0800 @@ -57,7 +57,13 @@ int matching_code_size = 0; DictEntry() = default; - ShortDictEntry ToShort() const { return {text, code, weight}; } + ShortDictEntry ToShort() const { + ShortDictEntry entry; + entry.text = text; + entry.code = code; + entry.weight = weight; + return entry; + } bool IsExactMatch() const { return matching_code_size == 0 || matching_code_size == code.size(); }
