From 56a9128f71be51085265090d4b08d1f197ab9730 Mon Sep 17 00:00:00 2001 From: Xinkai Chen Date: Fri, 20 Mar 2026 23:19:45 +0800 Subject: [PATCH 1/3] fix: delayed state-reset on fcitx side should be unconditional. choir: rename cleared_ to ended_ for better clarity. --- DictypeFcitx/src/DictypeFcitx.cpp | 2 +- DictypeFcitx/src/DictypeState.cpp | 32 ++++++++++++++++++------------- DictypeFcitx/src/DictypeState.h | 10 ++++++++-- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/DictypeFcitx/src/DictypeFcitx.cpp b/DictypeFcitx/src/DictypeFcitx.cpp index 9d0cd17..280e1bb 100644 --- a/DictypeFcitx/src/DictypeFcitx.cpp +++ b/DictypeFcitx/src/DictypeFcitx.cpp @@ -300,7 +300,7 @@ void DictypeFcitx::trigger_(const fcitx::KeyEvent& keyEvent, that2->updateUI_(); that2->closeUI_(); - that2->state_.clear(); + that2->state_.end(); that2->running_.store(false, std::memory_order_release); }); diff --git a/DictypeFcitx/src/DictypeState.cpp b/DictypeFcitx/src/DictypeState.cpp index d5565fd..54a20cc 100644 --- a/DictypeFcitx/src/DictypeState.cpp +++ b/DictypeFcitx/src/DictypeState.cpp @@ -10,31 +10,33 @@ DictypeState::DictypeState() = default; -void DictypeState::clear() { +void DictypeState::end() { latestCommittableBeginTime_ = -1; if (!texts_.empty()) { DICTYPE_WARN() << "uncommitted texts: " << getUncommittedText(); texts_.clear(); } - cleared_ = true; + ended_ = true; } bool DictypeState::newSession(fcitx::InputContext* inputContext) { - if (!cleared_) { - DICTYPE_WARN() << "Previous session is not cleared."; + if (!ended_) { + DICTYPE_WARN() << "Previous session is not ended."; return false; } - if (stage_ == DictypeStage::Errored) { - stage_ = DictypeStage::Closed; - errorMsg_.clear(); - inputContext_.unwatch(); - } if (inputContext == nullptr) { - inputContext_.unwatch(); - } else { - inputContext_ = inputContext->watch(); + DICTYPE_WARN() << "inputContext is nullptr"; + return false; } - cleared_ = false; + + // Clear states from the last session + stage_ = DictypeStage::Closed; + errorMsg_.clear(); + inputContext_.unwatch(); + + // Set up for the new session + inputContext_ = inputContext->watch(); + ended_ = false; return true; } @@ -56,6 +58,10 @@ void DictypeState::setConnecting() { } void DictypeState::setText(const Dictype::TranscribeResponse& response) { + if (ended_) { + DICTYPE_WARN() << "Previous session is not ended."; + return; + } if (!(stage_ == DictypeStage::Connecting || stage_ == DictypeStage::Transcribing || stage_ == DictypeStage::Stopping)) { diff --git a/DictypeFcitx/src/DictypeState.h b/DictypeFcitx/src/DictypeState.h index bcafc2c..c4ef6b2 100644 --- a/DictypeFcitx/src/DictypeState.h +++ b/DictypeFcitx/src/DictypeState.h @@ -25,7 +25,7 @@ class DictypeState final : public fcitx::InputContextProperty { public: explicit DictypeState(); - void clear(); + void end(); bool newSession(fcitx::InputContext* inputContext); void stop(); @@ -52,6 +52,12 @@ class DictypeState final : public fcitx::InputContextProperty { std::map texts_; std::string errorMsg_; fcitx::TrackableObjectReference inputContext_; - bool cleared_ = true; + + /** + * A flag tied to the lifetime of a session. + * + * Only live sessions can accept state mutations. + */ + bool ended_ = true; DictypeStage stage_{DictypeStage::Closed}; }; From 44346fede677a7feba4cbf481bc9e1488b065994 Mon Sep 17 00:00:00 2001 From: Xinkai Chen Date: Fri, 20 Mar 2026 23:37:09 +0800 Subject: [PATCH 2/3] fix test --- DictypeFcitx/src/DictypeState.test.cpp | 33 +++++++++++++++++++++----- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/DictypeFcitx/src/DictypeState.test.cpp b/DictypeFcitx/src/DictypeState.test.cpp index bb5fd7a..99de26a 100644 --- a/DictypeFcitx/src/DictypeState.test.cpp +++ b/DictypeFcitx/src/DictypeState.test.cpp @@ -3,6 +3,8 @@ #include +#include + #include "DictypeState.h" #include "dictype.grpc.pb.h" @@ -27,10 +29,12 @@ TEST(DictypeStateTest, DefaultStateIsClosed) { TEST(DictypeStateTest, NewSessionClearsStateAndData) { DictypeState state; + const fcitx::InputContextManager icm {}; + state.newSession(icm.dummyInputContext()); state.setText(MakeResponse(1, "hello ", true)); - state.clear(); + state.end(); EXPECT_EQ(state.getStage(), DictypeStage::Closed); EXPECT_EQ(state.getErrorMsg(), ""); @@ -40,14 +44,19 @@ TEST(DictypeStateTest, NewSessionClearsStateAndData) { TEST(DictypeStateTest, NewSessionRequiresPreviousClear) { DictypeState state; - EXPECT_TRUE(state.newSession(nullptr)); + const fcitx::InputContextManager icm {}; + + EXPECT_TRUE(state.newSession(icm.dummyInputContext())); EXPECT_FALSE(state.newSession(nullptr)); - state.clear(); - EXPECT_TRUE(state.newSession(nullptr)); + state.end(); + EXPECT_TRUE(state.newSession(icm.dummyInputContext())); } TEST(DictypeStateTest, StopTransitionsOnlyFromConnectingOrTranscribing) { DictypeState state; + const fcitx::InputContextManager icm {}; + state.newSession(icm.dummyInputContext()); + state.stop(); EXPECT_EQ(state.getStage(), DictypeStage::Closed); @@ -62,6 +71,9 @@ TEST(DictypeStateTest, StopTransitionsOnlyFromConnectingOrTranscribing) { TEST(DictypeStateTest, SetWordIgnoredUnlessConnectingOrTranscribing) { DictypeState state; + const fcitx::InputContextManager icm {}; + state.newSession(icm.dummyInputContext()); + state.setText(MakeResponse(1, "hello ")); EXPECT_EQ(state.getStage(), DictypeStage::Closed); EXPECT_EQ(state.getUncommittedText(), ""); @@ -71,6 +83,9 @@ TEST(DictypeStateTest, SetWordIgnoredUnlessConnectingOrTranscribing) { TEST(DictypeStateTest, SetWordTransitionsToTranscribingAndTracksCommitBoundaries) { DictypeState state; + const fcitx::InputContextManager icm {}; + state.newSession(icm.dummyInputContext()); + state.setConnecting(); state.setText(MakeResponse(0, "hello ", false)); EXPECT_EQ(state.getStage(), DictypeStage::Transcribing); @@ -92,6 +107,9 @@ TEST(DictypeStateTest, TEST(DictypeStateTest, SetErrorStoresFirstErrorAndLocksStage) { DictypeState state; + const fcitx::InputContextManager icm {}; + state.newSession(icm.dummyInputContext()); + state.setError("boom"); EXPECT_EQ(state.getStage(), DictypeStage::Errored); EXPECT_EQ(state.getErrorMsg(), "boom"); @@ -103,15 +121,18 @@ TEST(DictypeStateTest, SetErrorStoresFirstErrorAndLocksStage) { TEST(DictypeStateTest, RetainErrorMessageUntilNewSession) { DictypeState state; + const fcitx::InputContextManager icm {}; + state.newSession(icm.dummyInputContext()); + state.setError("boom"); EXPECT_EQ(state.getStage(), DictypeStage::Errored); EXPECT_EQ(state.getErrorMsg(), "boom"); - state.clear(); + state.end(); EXPECT_EQ(state.getStage(), DictypeStage::Errored); EXPECT_EQ(state.getErrorMsg(), "boom"); - state.newSession(nullptr); + state.newSession(icm.dummyInputContext()); EXPECT_EQ(state.getStage(), DictypeStage::Closed); EXPECT_EQ(state.getErrorMsg(), ""); } From c5d56853b13659eedb86a56c12813d9dcb88234c Mon Sep 17 00:00:00 2001 From: Xinkai Chen Date: Fri, 20 Mar 2026 23:42:17 +0800 Subject: [PATCH 3/3] clang format --- DictypeFcitx/src/DictypeState.test.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/DictypeFcitx/src/DictypeState.test.cpp b/DictypeFcitx/src/DictypeState.test.cpp index 99de26a..1de28c2 100644 --- a/DictypeFcitx/src/DictypeState.test.cpp +++ b/DictypeFcitx/src/DictypeState.test.cpp @@ -29,7 +29,7 @@ TEST(DictypeStateTest, DefaultStateIsClosed) { TEST(DictypeStateTest, NewSessionClearsStateAndData) { DictypeState state; - const fcitx::InputContextManager icm {}; + const fcitx::InputContextManager icm{}; state.newSession(icm.dummyInputContext()); state.setText(MakeResponse(1, "hello ", true)); @@ -44,7 +44,7 @@ TEST(DictypeStateTest, NewSessionClearsStateAndData) { TEST(DictypeStateTest, NewSessionRequiresPreviousClear) { DictypeState state; - const fcitx::InputContextManager icm {}; + const fcitx::InputContextManager icm{}; EXPECT_TRUE(state.newSession(icm.dummyInputContext())); EXPECT_FALSE(state.newSession(nullptr)); @@ -54,7 +54,7 @@ TEST(DictypeStateTest, NewSessionRequiresPreviousClear) { TEST(DictypeStateTest, StopTransitionsOnlyFromConnectingOrTranscribing) { DictypeState state; - const fcitx::InputContextManager icm {}; + const fcitx::InputContextManager icm{}; state.newSession(icm.dummyInputContext()); state.stop(); @@ -71,7 +71,7 @@ TEST(DictypeStateTest, StopTransitionsOnlyFromConnectingOrTranscribing) { TEST(DictypeStateTest, SetWordIgnoredUnlessConnectingOrTranscribing) { DictypeState state; - const fcitx::InputContextManager icm {}; + const fcitx::InputContextManager icm{}; state.newSession(icm.dummyInputContext()); state.setText(MakeResponse(1, "hello ")); @@ -83,7 +83,7 @@ TEST(DictypeStateTest, SetWordIgnoredUnlessConnectingOrTranscribing) { TEST(DictypeStateTest, SetWordTransitionsToTranscribingAndTracksCommitBoundaries) { DictypeState state; - const fcitx::InputContextManager icm {}; + const fcitx::InputContextManager icm{}; state.newSession(icm.dummyInputContext()); state.setConnecting(); @@ -107,7 +107,7 @@ TEST(DictypeStateTest, TEST(DictypeStateTest, SetErrorStoresFirstErrorAndLocksStage) { DictypeState state; - const fcitx::InputContextManager icm {}; + const fcitx::InputContextManager icm{}; state.newSession(icm.dummyInputContext()); state.setError("boom"); @@ -121,7 +121,7 @@ TEST(DictypeStateTest, SetErrorStoresFirstErrorAndLocksStage) { TEST(DictypeStateTest, RetainErrorMessageUntilNewSession) { DictypeState state; - const fcitx::InputContextManager icm {}; + const fcitx::InputContextManager icm{}; state.newSession(icm.dummyInputContext()); state.setError("boom");