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();
   }

Reply via email to