Skip to content
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

Merged
merged 8 commits into from
Oct 18, 2018
Merged

feat: Spellchecker Async Implementation #14032

merged 8 commits into from
Oct 18, 2018

Conversation

nitsakh
Copy link
Contributor

@nitsakh nitsakh commented Aug 12, 2018

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
  • PR description included and stakeholders cc'd
  • npm test passes
  • tests are changed or added
  • relevant documentation is changed or added
  • PR title follows semantic commit guidelines

Notes: Updated SpellCheck API to support asynchronous results.

@nitsakh nitsakh requested review from a team August 12, 2018 01:30
@@ -79,19 +85,6 @@ SpellCheckClient::~SpellCheckClient() {
context_.Reset();
}

void SpellCheckClient::CheckSpelling(
const blink::WebString& text,
Copy link
Contributor Author

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,
Copy link
Contributor Author

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.

Copy link
Contributor

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);

Copy link
Contributor

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"

Copy link
Contributor

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

@nitsakh
Copy link
Contributor Author

nitsakh commented Aug 12, 2018

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.
What's the best way to do that without breaking builds for others?

@nitsakh nitsakh requested a review from kwonoj August 12, 2018 02:26
@MarshallOfSound
Copy link
Member

@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[]
Copy link
Contributor

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));
}

Copy link
Contributor Author

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?

Copy link
Contributor

@kwonoj kwonoj Aug 12, 2018

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/cc @juturu

@juturu juturu requested a review from john-hern August 13, 2018 22:19
@nitsakh nitsakh force-pushed the spellcheck-async branch 2 times, most recently from 734f310 to 2692ec1 Compare September 23, 2018 22:59
@nitsakh nitsakh force-pushed the spellcheck-async branch 2 times, most recently from 36927d8 to fe4edc9 Compare October 3, 2018 15:22
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_; }
Copy link
Member

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
Copy link
Member

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)) {
Copy link
Member

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)) {
Copy link
Member

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.
Copy link
Member

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();
Copy link
Member

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

Copy link
Contributor Author

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>
Copy link
Member

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

Copy link
Contributor Author

@nitsakh nitsakh Oct 15, 2018

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);
Copy link
Member

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, changed.

Copy link
Contributor Author

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;
Copy link
Member

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a comment.

@codebytere
Copy link
Member

@nitsakh there's a bit more review changes to be done here but then i think we can finally get this in 🎉

@nitsakh
Copy link
Contributor Author

nitsakh commented Oct 15, 2018

Thanks a lot for the review comments @ckerr ! 🙇

Copy link
Member

@codebytere codebytere left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm!

Copy link
Member

@ckerr ckerr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

@ckerr ckerr changed the title feat:Spellchecker Async Implementation feat: Spellchecker Async Implementation Oct 18, 2018
@ckerr ckerr merged commit a9ca152 into master Oct 18, 2018
@release-clerk
Copy link

release-clerk bot commented Oct 18, 2018

Release Notes Persisted

Updated SpellCheck API to support asynchronous results.

@ckerr ckerr deleted the spellcheck-async branch October 18, 2018 16:20
mlalkaka added a commit to mlalkaka/electron-spellchecker that referenced this pull request Jun 11, 2019
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.
mlalkaka added a commit to mlalkaka/electron-spellchecker that referenced this pull request Jun 11, 2019
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants