ckeditor / ckeditor5

Powerful rich text editor framework with a modular architecture, modern integrations, and features like collaborative editing.
https://ckeditor.com/ckeditor-5
Other
9.53k stars 3.7k forks source link

[Windows] Text suggestion doesn't replace the whole word #13583

Closed Czhang0727 closed 1 year ago

Czhang0727 commented 1 year ago

📝 Steps to reproduce

  1. Enable text suggestions in Windows 10/11 (https://support.microsoft.com/en-us/windows/enable-text-suggestions-in-windows-0bf313ca-c992-4173-aa5f-8341d3953498).
  2. Go to https://ckeditor.com/docs/ckeditor5/latest/examples/builds/classic-editor.html.
  3. Type "thi".
  4. Select "this" from the auto-complete box.

Current result

The text replaces only the last character from the word, instead of the whole word.


Original message

Currently we see windows text suggestion has issue on CKEditor for "all suggestions will get inserted instead of replace"

Please find feature flag here:
Settings > Time & Language > Typing > Show text suggestions when typing on the physical keyboard.

Please note this issue currently only reported on Ckeditor5.

What is the expected behavior of the proposed feature?
The text will get replaced by suggestions.


If you'd like to see this feature implemented, add a 👍 reaction to this post.

Mgsy commented 1 year ago

Hi @Czhang0727, thank you for the report. I've edited your post to summarize steps required to reproduce the problem.

Below I'm sharing debugging notes from the Microsoft team:

Events fired after replacing "al" -> "also"

keyboard_listener

Potential source of the problem

Reinmar commented 1 year ago

Some context: The deleteContent(|Backward|Forward) events spec is unclear and in mooost cases we didn't need to use targetRange because our internal calculations of what should be removed was sufficient. It's also actually tricky to calculate what needs to be removed – depending on the direction it's a code point or character and emojis have very custom handling.

Unfortunately, we cannot blindly use targetRange in all cases:

Bonus: We are aware of an issue with Grammarly where accepting a suggestion to remove an unnecessary word doesn't delete that word. Potentially, it's the same scenario.

The idea is to try to come up with a heuristic that uses the target range provided by the browser when it's related to spell-checking scenarios but use our logic in other cases.

Witoso commented 1 year ago

After a quick investigation, looks like Grammarly uses a different event :keydown (key:"Delete").

Checked with beforeinput test with a phrase Leaving your comfort zone might lead you to such beautiful sceneries like this one.. The word such is suggested for deletion by Grammarly.

Let's not focus on it right now, and fix deleteContentBackward scenario.

niegowski commented 1 year ago

This got even more complicated. We would like to delete a single code point on the backspace key press, and the whole character on the delete key press. Also no matter the direction the whole emoji should be removed.

2023-03-15 15 14 57

In the above screencast, you can see that behavior of the backspace key press after a combined symbol (for example or ) in the plain contenteditable differs from what the browser reports in beforeInput.targetRanges. We should detect what is inside the target range and decide whether we want to use it or use model logic to determine the range of deletion.

There was a workaround for Android that would detect simple ranges but it would not handle combined symbols properly (as described above).

I propose a heuristic that would detect if the target range is covering more than one character (combined symbols, and emojis count as a single character, also elements like image or BR count as a character).

Doubts:

This is still a work in progress but looks promising: https://github.com/ckeditor/ckeditor5/pull/13685

Reinmar commented 1 year ago

@niegowski I tried to re-edit your comment as some bits of it were unclear to me. Please check out the diff whether I understood all correctly.

Also, two comments:

niegowski commented 1 year ago
  • What if the range ends or starts inside a widget?

  • We don't know whether every browser and possible external integrations provide correct target ranges.

  • The target range may be incorrect in cases like merging blocks or next to block non-editable objects.

The target range actually ends/starts inside a widget, crosses non-editable elements, etc. But this has no visible effect because delete event handlers use bubbling event propagation or document selection to handle delete actions other than triggering the delete command. However, this seems fragile and might break easily especially since the bubbling starts from the target range.

  • The heuristic must be bulletproofed. We cannot expect that target ranges will follow any particular rules as they may be provided by a browser (and change with its new versions) or external tools (e.g. spellchecks). So let's not assume anything about those target ranges.

I was thinking about how to bulletproof those ranges. I realized that we need to fix those ranges before the delete event is fired (because the bubbling event could be intercepted by some handlers - for example, the widget could stop the event and handle it differently than the delete command). But the delete event is fired by the observer that does not have access to the model and that range could be fixed only on the model level. I realized that we already fire beforeinput event by another observer and the delete observer is acting on that beforeinput event. So my proposal is to listen to beforeinput event fired by InputObserver and fix the targetRanges according to the model and model schema rules. We already have a selection post-fixer so we could reuse that logic to fix target ranges and update event if fixing is needed. (this is already implemented in the PR: https://github.com/ckeditor/ckeditor5/pull/13685/commits/f6c1b6ba669cedb55539dec1a7fc13024db3a674)

There is also another option. We could listen to the delete event in the $capture phase and fix the target range at that stage but this would not fix possible issues with other beforeinput ranges (insertText, enter, etc)

Reinmar commented 1 year ago

So my proposal is to listen to beforeinput event fired by InputObserver and fix the targetRanges according to the model and model schema rules. We already have a selection post-fixer so we could reuse that logic to fix target ranges and update event if fixing is needed.
(this is already implemented in the PR: f6c1b6b)

You mean a round-trip from a view range -> model range -> fix model range (same as the current selection postifxer) -> view range?

This sounds interesting.

Two thoughts:

niegowski commented 1 year ago

You mean a round-trip from a view range -> model range -> fix model range (same as the current selection postifxer) -> view range?

exactly, but view range update only if model range required any fixing

  • Since we start with a weird view range (e.g. crossing some cE boundaries), I wonder how will the fixed range look like. I guess sometimes it can  be severely distorted when compared to the target range that we got. Do you think it may affect e.g. deleting in some cases? Since we'll be using the target range when it's covering more than one character, it will drive how the backspace works. Does it mean that we'd give the browser (distorted by our post-fixer) complete control on that action?

I think it should not affect what is deleted. The fixed range is more reasonable because it includes the whole object and not just a part of it.

  • Does it mean that we'd give the browser (distorted by our post-fixer) complete control on that action?

Nope, in special cases we listen to delete bubbling event that starts bubbling from the target range and we intercept it for example on widgets or next to those.

  • I wonder what's the performance implication. Test case: long paragraph with a lot of inline formatting and keeping the backspace pressed.

I'm not sure, shall I do some performance tests?

As I noted here:

There is also another option. We could listen to the delete event in the $capture phase and fix the target range at that stage but this would not fix possible issues with other beforeinput ranges (insertText, enter, etc)

The current proposal is fixing all target ranges in beforeInput events so also could affect typing but seems more reasonable to fix it there too so that typing over a non-collapsed selection would make sure that the range is valid.

Reinmar commented 1 year ago

The current proposal is fixing all target ranges in beforeInput events so also could affect typing but seems more reasonable to fix it there too so that typing over a non-collapsed selection would make sure that the range is valid.

I forgot to comment on that. I like this option much more than the idea of targeting only the delete event. The latter sounds more like a hack.

I think it should not affect what is deleted. The fixed range is more reasonable because it includes the whole object and not just a part of it.

I don't know the exact algorithm that we implement in the post-fixer. Do we always extend the range? I recall issues navigating up and down with shift+arrow keys due to that post-fixer so that made me a bit worried here.

I'm not sure, shall I do some performance tests?

I think so.

  • Does it mean that we'd give the browser (distorted by our post-fixer) complete control on that action?

Nope, in special cases we listen to delete bubbling event that starts bubbling from the target range and we intercept it for example on widgets or next to those.

I think I need to test the PoC myself because I forgot too much of the execution flow for these cases :D

niegowski commented 1 year ago

I'm not sure, shall I do some performance tests?

I think so.

I did some performance tests (using the performance tab in Chrome dev tools and by measuring the time of a single beforeinput action and continuous action of holding a key and checking the time it took to delete a paragraph with mixed inline elements) and I can't see any performance issues.

niegowski commented 1 year ago

I think it should not affect what is deleted. The fixed range is more reasonable because it includes the whole object and not just a part of it.

I don't know the exact algorithm that we implement in the post-fixer. Do we always extend the range? I recall issues navigating up and down with shift+arrow keys due to that post-fixer so that made me a bit worried here.

Selection post-fixer flow:

niegowski commented 1 year ago

Do we always extend the range? I recall issues navigating up and down with shift+arrow keys due to that post-fixer so that made me a bit worried here.

I think it should not cause any issues because the current implementation (on master) is using the document selection that is already fixed by the selection post-fixer so it looks like acting on the same range.

I recall issues navigating up and down with shift+arrow keys due to that post-fixer so that made me a bit worried here.

Is this what you are mentioning?

2023-04-04 14 59 34

Looks like the selection post-fixer would need some user intentions to do it better, now it does not know if the user intention is to expand or to shrink the selection.

Reinmar commented 1 year ago

I think it should not cause any issues because the current implementation (on master) is using the document selection that is already fixed by the selection post-fixer so it looks like acting on the same range.

My point was: does the outcome of the post-fixer make sense for "we want to delete that range" in the context of where Backspace was pressed. But I suppose this needs to be tested manually.

I don't question here the post-fixer itself. Nor whether the range will be "correct" (whatever it means). But the integration of all these pieces when handling Backspace.

pkhodo commented 1 year ago

I found this page by asking ChatGPT about this problem. I thought to add that this is a windows wide issue. E.g. in Microsoft Teams I type predi - and I choose "predict" - it will add the suggestion next to what I already typed, which is, as you can see - a problem. It looks like this is what we see in action in this issue and it itself is not a ckeditor issue. Unless.... MS Teams uses CKeditor :-)

image

edited: added links to Microsoft answers.

Reinmar commented 1 year ago

Unless.... MS Teams uses CKeditor :-)

😉

soheilamiri commented 4 months ago

i have the same problem wit my windows on any app. OS: 10.0.22635

princejosephfelix commented 3 months ago

I also have the same problem in my windows 11 pc on any application, even with notepad, all MS Office applications, save file dialog etc.