Files
msime-windows/windows/tests/temporary_japanese_input.cpp
6072ae7784 fix(windows): 修复临时日语 R 模式无法输入长音符 (#1000)
* fix(windows): 修复临时日语 R 模式无法输入长音符

* fix(server,windows): 临时日语策略每键只算一次,并去掉多余的帧

- Server 在 HandleImeKey 开头按本次按键所属客户端算一次策略,不再每个谓词都查活动客户端、加锁、构造 payload 再解析回来。
- 普通激活只补发 TemporaryJapanesePolicyChanged;整套输入模式状态(含语言栏刷新)仍只在 FocusRestored 时发送。
- 配置重载时输入方案变了由 ApplyConfiguredInputScheme 统一广播,不再重复广播策略。
- TSF 的 WaitForProtocolReady 不再清零策略:worker 管道先于 Main 注册,可能已收到本次连接的策略;断开时已有清零路径。

---------

Co-authored-by: ranly <ran618ly210805@qq.com>
Co-authored-by: fanlusky <1730976608@qq.com>
2026-10-16 22:30:28 +08:00

134 lines
5.9 KiB
C++

// Exercise the production TSF key result and shared editing rules without a TIP or host doubles.
#define NOMINMAX
#include "../src/Key/TemporaryJapaneseKeyPolicy.h"
#include <iostream>
#include <stdexcept>
#include <string>
#define CHECK(x) \
do \
{ \
if (!(x)) \
throw std::runtime_error(#x); \
} while (false)
namespace
{
using namespace FanyImeTemporaryJapanese;
_KEYSTROKE_STATE Classify(const std::wstring &raw, WCHAR ch, bool full = false, Policy policy = Allowed,
UINT modifiers = 0)
{
const UINT key = ch == L'-' || ch == L'_' ? VK_OEM_MINUS : VK_OEM_PLUS;
return ClassifyJapaneseKey(key, ch, modifiers, full, policy, raw.data(), raw.size(), !raw.empty());
}
void Classification()
{
for (Policy policy : {Policy(0), Policy(1), Policy(2), Policy(3)})
for (bool full : {false, true})
for (const std::wstring raw : {L"", L"R", L"Rko", L"rko", L"ni"})
{
const bool expected = full || (policy == Allowed && !raw.empty() && raw.front() == L'R');
const auto state = Classify(raw, L'-', full, policy);
CHECK((state.Function == FUNCTION_INPUT) == expected);
CHECK(state.Category == (expected ? CATEGORY_COMPOSING : CATEGORY_NONE));
// Repeated key tests must leave the buffer and policy untouched.
CHECK(Classify(raw, L'-', full, policy).Function == state.Function);
for (WCHAR punctuation : {L'_', L'=', L'+'})
{
const auto symbol = Classify(raw, punctuation, full, policy);
CHECK((symbol.Function == FUNCTION_PUNCTUATION) == (expected && !raw.empty()));
}
for (UINT modifiers : {2u, 4u, 6u})
CHECK(Classify(raw, L'-', full, policy, modifiers).Function == FUNCTION_NONE);
}
// Fresh compositions intentionally ignore the previous session's R prefix.
CHECK(Classify(L"", L'-').Function == FUNCTION_NONE);
CHECK(Classify(L"", L'-', true).Function == FUNCTION_INPUT);
CHECK(Classify(L"Rko-hi--", L'-').Function == FUNCTION_INPUT);
CHECK(ClassifyJapaneseKey(VK_SUBTRACT, L'-', 0, false, Allowed, L"Rko", 3, true).Function == FUNCTION_NONE);
CHECK(ClassifyJapaneseKey(VK_NEXT, 0, 0, false, Allowed, L"Rko", 3, true).Function == FUNCTION_NONE);
CHECK(ClassifyJapaneseKey(VK_OEM_4, L'[', 0, false, Allowed, L"Rko", 3, true).Function == FUNCTION_NONE);
}
void Editing()
{
std::wstring raw;
std::size_t caret = 0;
// Supply each queued buffer to the same classifier as the real TSF buffer.
for (WCHAR ch : std::wstring(L"Rko-hi-"))
{
if (ch == L'-')
CHECK(Classify(raw, ch).Function == FUNCTION_INPUT);
caret = ClampCaret(false, Allowed, raw.data(), raw.size(), caret);
raw.insert(caret++, 1, ch);
}
CHECK(raw == L"Rko-hi-");
CHECK(CanBackspace(false, Allowed, raw.data(), raw.size(), caret));
raw.erase(--caret, 1);
CHECK(raw == L"Rko-hi");
--caret;
CHECK(Classify(raw, L'-').Function == FUNCTION_INPUT);
caret = ClampCaret(false, Allowed, raw.data(), raw.size(), caret);
raw.insert(caret++, 1, L'-');
CHECK(raw == L"Rko-h-i");
CHECK(ClampCaret(false, Allowed, raw.data(), raw.size(), 0) == 1);
CHECK(!CanMoveCaret(false, Allowed, raw.data(), raw.size(), 0));
CHECK(CanMoveCaret(false, Allowed, raw.data(), raw.size(), 1));
CHECK(!CanMoveCaret(false, Allowed, raw.data(), raw.size(), -1));
CHECK(!CanMoveCaret(false, Allowed, raw.data(), raw.size(), raw.size() + 1));
CHECK(!CanBackspace(false, Allowed, raw.data(), raw.size(), 1));
CHECK(CanBackspace(false, Allowed, L"R", 1, 1));
CHECK(!CanBackspace(false, Allowed, L"R", 1, 0));
CHECK(ClampCaret(false, Allowed, L"", 0, 10) == 0);
CHECK(ClampCaret(false, Allowed, L"Rko", 3, 10) == 3);
CHECK(CanMoveCaret(true, Allowed, L"ko", 2, 0));
CHECK(CanBackspace(true, Allowed, L"ko", 2, 1));
CHECK(CanMoveCaret(false, 0, L"Rko", 3, 0));
}
void PolicyChanges()
{
Policy policy = Allowed;
CHECK(Classify(L"Rko", L'-', false, policy).Function == FUNCTION_INPUT);
policy = ToggleEnglish(policy);
CHECK(Classify(L"Rko", L'-', false, policy).Function == FUNCTION_NONE);
policy = ToggleEnglish(policy);
CHECK(Classify(L"Rko", L'-', false, policy).Function == FUNCTION_INPUT);
policy = LeaveEnglish(ToggleEnglish(policy));
CHECK(policy == Allowed);
policy = 0; // The IPC owner resets policy before reconnect negotiation.
CHECK(Classify(L"Rko", L'-', false, policy).Function == FUNCTION_NONE);
for (WCHAR ch : {L'0', L'1', L'2', L'3', L'4', L'/', L'\0'})
{
WCHAR frame[]{ch, L'\0'};
CHECK(IsValidPolicyFrame(frame, 2) == (ch >= L'0' && ch <= L'3'));
if (IsValidPolicyFrame(frame, 2))
CHECK(ToPayload(FromPayload(ch)) == ch);
frame[1] = L'x';
CHECK(!IsValidPolicyFrame(frame, 2));
CHECK(!IsValidPolicyFrame(frame, 1));
}
CHECK(!IsValidPolicyFrame(static_cast<const WCHAR *>(nullptr), 2));
policy = FromPayload(L'1');
CHECK(Classify(L"Rko", L'-', false, policy).Function == FUNCTION_INPUT);
}
} // namespace
int main()
{
try
{
Classification();
Editing();
PolicyChanges();
std::cout << "temporary Japanese key classification and editing passed\n";
}
catch (const std::exception &ex)
{
std::cerr << ex.what() << '\n';
return 1;
}
}