-
Notifications
You must be signed in to change notification settings - Fork 15.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Spellchecker Async Implementation #14032
Conversation
0d5849d
to
f492da3
Compare
@@ -79,19 +85,6 @@ SpellCheckClient::~SpellCheckClient() { | |||
context_.Reset(); | |||
} | |||
|
|||
void SpellCheckClient::CheckSpelling( | |||
const blink::WebString& text, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is only called by blink when creating the context menu. Clients use electron APIs to create context menus, so we don't need to override this function.
@@ -188,15 +188,14 @@ void WebFrame::DetachGuest(int id) { | |||
|
|||
void WebFrame::SetSpellCheckProvider(mate::Arguments* args, | |||
const std::string& language, | |||
bool auto_spell_correct_turned_on, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found that we weren't doing anything with this parameter in the API, so getting rid of it and also removing from the API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a breaking change? Our app currently has this code which seems like it will now fail:
webFrame.setSpellCheckProvider(locale, true, provider);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just confirmed that it is, see electron-userland/electron-spellchecker#144 this should be noted as breaking in the release notes, it's currently a "Feature"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Never mind, I see this has been raised elsewhere #17915
The linter step is failing to create the typescript definitions. Looks like I'll have to update https://github.com/electron/electron-typescript-definitions/blob/eaa478d08a30cf7c721360f478aedf81c7dd2843/test-smoke/electron/test/renderer.ts#L63. |
@nitsakh Breaking changes are hard on that generator. Best bet would be to update those smoke tests on a branch, then make the reference to the module in our package.json point at that branch. Then when we merge this PR we'll do a major release of the generator |
* `spellCheck` Function. | ||
* `words` String[] | ||
* `callback` Function | ||
* `misspeltWords` String[] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
curious if callback accepts Array<boolean>
instead of returning word itself. i.e,
spellcheck: (words, callback) => {
// do some async
callback(words.map(isMisspelled));
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently it doesn't, but I can make changes to change the API to accept Array<boolean>
if we want.
What would be the benefit of doing that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(just my 5c) it makes js callback doesn't need to maintain list of words to be returned but just apply fn then return it directly - i.e I could do similar like below in provider:
// this is fn signature runs spellcheck in async and returns result
const isMisspelled: async (word) => boolean;
spellcheck: (words, callback) =>
Observable.from(words)
.mergeMap((w) => isMisspelled(w))
.toArray().subscribe(callback);
when provider required to return array of misspelled words, provider fn need to re-map from result of spellcheck to construct list of misspelled words to be returned.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also sort of alignment (still it's breaking change), previously provider returns boolean for single words, now returns array of boolean for corresponding words.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds reasonable. However, to do that, we will have to maintain the list of all words on the c++ side and then map the returned array to those to get the locations in the text to return back to blink. I was just trying to avoid running through all the words by using a map. So, it's definitely doable but I'm not sure how important it is.
Also, APIwise returning misspelt words seems better than an array of booleans. But that's just me. 😃
I'd like to see if others have any opinions about this. I'm okay going either way!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/cc @juturu
734f310
to
2692ec1
Compare
36927d8
to
fe4edc9
Compare
SpellcheckRequest(const base::string16& text, | ||
blink::WebTextCheckingCompletion* completion) | ||
: text_(text), completion_(completion) { | ||
DCHECK(completion); | ||
} | ||
~SpellcheckRequest() {} | ||
|
||
base::string16 text() { return text_; } | ||
base::string16& text() { return text_; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we're returning a reference, should be a const reference.
Also (not new to this PR) the method should be const.
So const base::string16& text() const { return text_; }
word_map[word].push_back(result); | ||
} else { | ||
// For a contraction, we want check the spellings of each individual | ||
// part, but mark the entire word incorrect if any part is misspelt |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(opinion) let's use 'misspelled' everywhere instead of 'misspelt'. The former is what the existing API uses and has about 25x more Google hits than the latter does
@@ -155,62 +148,93 @@ void SpellCheckClient::SpellCheckText( | |||
base::string16 word; | |||
int word_start; | |||
int word_length; | |||
std::vector<base::string16> words; | |||
auto& word_map = pending_request_param_->wordmap(); | |||
for (auto status = | |||
text_iterator_.GetNextWord(&word, &word_start, &word_length); | |||
status != SpellcheckWordIterator::IS_END_OF_TEXT; | |||
status = text_iterator_.GetNextWord(&word, &word_start, &word_length)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could replace word_start
and word_length
with a blink::WebTextCheckingResult that we can use below pre-populated:
blink::WebTextCheckingResult result;
std::vector<base::string16> words;
... GetNextWord(&word, &result.location, &result.length)
@@ -155,62 +148,93 @@ void SpellCheckClient::SpellCheckText( | |||
base::string16 word; | |||
int word_start; | |||
int word_length; | |||
std::vector<base::string16> words; | |||
auto& word_map = pending_request_param_->wordmap(); | |||
for (auto status = | |||
text_iterator_.GetNextWord(&word, &word_start, &word_length); | |||
status != SpellcheckWordIterator::IS_END_OF_TEXT; | |||
status = text_iterator_.GetNextWord(&word, &word_start, &word_length)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(opinion)
Those two calls to GetNextWord() in the loop structure are kind of cumbersome. This would be less repetitive:
for (;;) {
const auto status = text_iterator_.GetNextWord(...);
if (status == SpellcheckWordIterator::IS_END_OF_TEXT)
break;
if (status == SpellcheckWordIterator::IS_SKIPPABLE)
continue;
// For a contraction, we want check the spellings of each individual | ||
// part, but mark the entire word incorrect if any part is misspelt | ||
// Hence, we use the same word_start and word_length values for every | ||
// part of the contraction. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
^ This is a good idea!
@@ -155,62 +148,93 @@ void SpellCheckClient::SpellCheckText( | |||
base::string16 word; | |||
int word_start; | |||
int word_length; | |||
std::vector<base::string16> words; | |||
auto& word_map = pending_request_param_->wordmap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this every be populated from a previous run -- do we need to .clear()
this out?
I think the answer is "no" but am not positive
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, we don't need to clear it. Ideally, we shouldn't receive another request until the first one is complete, i.e. we call DidFinishCheckingText
on blink, which happens at the end of this function.
@@ -5,6 +5,7 @@ | |||
#ifndef ATOM_RENDERER_API_ATOM_API_SPELL_CHECK_CLIENT_H_ | |||
#define ATOM_RENDERER_API_ATOM_API_SPELL_CHECK_CLIENT_H_ | |||
|
|||
#include <map> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no std::map in this header, so this shouldn't be #included here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated, was leftover from my previous test implementation.
// words in the contraction. | ||
bool IsContraction(const SpellCheckScope& scope, | ||
const base::string16& word, | ||
std::vector<base::string16>* contraction_words); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason contraction_words
is a pointer instead of a reference here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, changed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ohh, just saw. The linter tells to make this a pointer.
Is this a non-const reference? If so, make const or use a pointer: std::vector<base::string16>& contraction_words [runtime/references] [2]
for (const auto& word : misspelt_words) { | ||
auto iter = word_map.find(word); | ||
if (iter != word_map.end()) { | ||
auto& words = iter->second; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(minor) 'words' is a confusing variable name here because they're ranges / results. I know 'results' is already taken but could something else be used here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a comment.
@nitsakh there's a bit more review changes to be done here but then i think we can finally get this in 🎉 |
fe4edc9
to
e6956b3
Compare
Thanks a lot for the review comments @ckerr ! 🙇 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good!
Release Notes Persisted
|
Starting with Electron 5.0.0, the function `webFrame.setSpellCheckProvider` has a different signature, in order to support asynchronous spell checkers. For more information on this change in Electron, see electron/electron#14032. This diff updates `SpellCheckHandler` to use the new interface if running on Electron 5.0.0 and above. However, it still checks the spelling synchronously like before. If running on Electron versions below 5.0.0, the `SpellCheckHandler` still works. Closes electron-userland#144.
Starting with Electron 5.0.0, the function `webFrame.setSpellCheckProvider` has a different signature, in order to support asynchronous spell checkers. For more information on this change in Electron, see electron/electron#14032. This diff updates `SpellCheckHandler` to use the new interface if running on Electron 5.0.0 and above. However, it still checks the spelling synchronously like before. If running on Electron versions below 5.0.0, the `SpellCheckHandler` still works. Closes electron-userland#144.
This PR makes the spellchecker provider function async. This enables the client to process the incoming spellcheck words and respond through a callback once the checking is done.
The API, which initially accepted only one word at a time, now takes in an array of words to be checked. This reduces the cross language calls for spellchecking the text and behaves in a true asynchronous way, that blink expects.
More comments inline.
/cc @kwonoj
Checklist
npm test
passesNotes: Updated SpellCheck API to support asynchronous results.