Hello @soso ,
I was able to reproduce your three results with a minimal read-only text service, and the outcome points to the host, not to your TSF integration.
What I tested (Windows 11 x64)
A minimal TIP that only advises ITfThreadMgrEventSink and, for the focused document manager, ITfTextEditSink. In OnEndEdit it logs ITfEditRecord::GetTextAndPropertyUpdates(TF_GTP_INCL_TEXT), the selection, the 12 characters before the caret and ITfContext::GetStatus. No SetText, no hooks, no clipboard. Initial text こんにちは。 with the caret after 。, then /mail typed through the normal keyboard input path.
Results:
- Standard Unicode
EDIT: sink attach succeeded, but the context reportsTF_STATUS.dwStaticFlags = TS_SS_TRANSITORY. The text was inserted correctly, andOnEndEditfired zero times during/mail. -
RICHEDIT50Wwith the default edit style (EM_GETEDITSTYLEreturns 0): same transitory context, zeroOnEndEdit. -
RICHEDIT50WafterEM_SETEDITSTYLE(SES_USECTF, SES_USECTF): regular context (not transitory), fiveOnEndEditnotifications carrying/mailat ACP 6..10, and the text before the caret reconstructsこんにちは。/mail. - Packaged Notepad (editor window class
RichEditD2DPT): regular context, five notifications, identical to the previous item.
So the same RichEdit engine and the same TIP go from "nothing observable" to "every character observable" by flipping one host-side flag. That rules out a missing piece in your text service.
Why this happens
A text service never talks to the control directly; everything goes through the TSF manager (Architecture). For the manager to know about an edit made by the application itself (direct typing handled by the control), the host has to implement a text store (ITextStoreACP) and call ITextStoreACPSink::OnTextChange; the advise sink "receives notifications when the text store is modified by something other than the manager, such as user input to the application" (Text Stores, OnTextChange). Only then does the manager surface the change to text services as ITfTextEditSink::OnEndEdit with a read-only cookie (OnEndEdit).
- The classic
EDITcontrol has no TSF text store. TSF reaches it through the IMM32 compatibility layer, which gives your TIP a transitory context (TS_SS_TRANSITORY, "expected to have a short usage cycle") that only holds composition text. DirectWM_CHARinput is inserted by the control itself and is never reported to TSF, which is why your sink setup succeeds but no committed-text notification arrives. - RichEdit does have TSF support, but it is off by default:
SES_USECTF"Turns on TSF support. (default: 0)" (EM_GETEDITSTYLE, EM_SETEDITSTYLE). Without it, RichEdit behaves likeEDITfrom a TIP's point of view. Notepad's editor is RichEdit with TSF enabled, which matches what you saw.
Your questions
- Is it expected? Yes, this is host-dependent by design, and it is also a limitation of the observation approach rather than a gap in your integration. TSF does not guarantee
OnEndEditfor direct edits in a host that does not expose a text store, and neitherEDITnor a default RichEdit does. - Supported architecture across Windows edit controls: within TSF there is none for TSF-unaware hosts, because the data simply does not reach the manager. What I can suggest: in hosts you control, use RichEdit with
SES_USECTFor implementITextStoreACP; in your TIP, checkITfContext::GetStatusforTS_SS_TRANSITORYto detect a host where the document is not observable and degrade gracefully. The only TSF-level data available in such hosts is keystrokes viaITfKeyEventSink, which requires your TIP to be the active keyboard TIP. For read-only observation in third-party apps you could also evaluate UI Automation text change events, but I have not verified that path. - Coexisting with Microsoft IME: only one keyboard-category TIP is active per language, so a keyboard TIP cannot run alongside Microsoft IME. This does not change the result above though: whether or not your service is the active keyboard TIP, a host without a text store still reports nothing. In my test the observer TIP was the active keyboard TIP for en-US while Microsoft Pinyin stayed active for zh-CN, and the host results were exactly as listed.
One more thing, the code blocks in your question render empty on my side (initial text, typed sequence, observations, RichEdit class name). If any detail differs from what I assumed above, please re-add them and I will take another look.
Hope these information help. If you found my response helpful or informative, I would greatly appreciate it if you could follow this guide for your confirmation.
Thank you.