Skip to content
This repository has been archived by the owner on Dec 15, 2022. It is now read-only.

Commit

Permalink
Merge pull request #115 from dmoonfire/optional-system-checker
Browse files Browse the repository at this point in the history
Add a new method, `setSpellcheckerType`, to pick spellchecker selection.
  • Loading branch information
Nathan Sobo authored Jul 29, 2019
2 parents 81367ae + a9f58e5 commit 55ae514
Show file tree
Hide file tree
Showing 11 changed files with 621 additions and 485 deletions.
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,42 @@ When using Hunspell, this will not modify the .dic file; new words must be added
`word` - String word to add.

Returns nothing.

### new Spellchecker()

In addition to the above functions that are used on a default instance, a new instance of SpellChecker can be instantiated with the use of the `new` operator. The same methods are available with the instance but the dictionary and underlying API can be changed independently from the default instance.

```javascript
const checker = new SpellChecker.Spellchecker()
```

#### SpellChecker.Spellchecker.setSpellcheckerType(type)

Overrides the library selection for checking. Without this, the checker will use [Hunspell](http://hunspell.github.io/) on Linux, the [Spell Checking API](https://docs.microsoft.com/en-us/windows/desktop/intl/spell-checker-api) for Windows, and [NSSpellChecker](https://developer.apple.com/documentation/appkit/nsspellchecker) on Macs.

If the environment variable `SPELLCHECKER_PREFER_HUNSPELL` is set to any value, the library will fallback to always using the Hunspell implementation.

This is the same behavior as calling `setSpellcheckerType` with the `USE_SYSTEM_DEFAULTS` constant:

```coffeescript
checker = new SpellChecker.Spellchecker
checker.setSpellcheckerType SpellChecker.USE_SYSTEM_DEFAULTS
```

To always use the system API and not fallback to Hunspell regardless of the environment variable, use the `ALWAYS_USE_SYSTEM` constant:

```coffeescript
checker = new SpellChecker.Spellchecker
checker.setSpellcheckerType SpellChecker.ALWAYS_USE_SYSTEM
```

Likewise, Hunspell can be forced with the `ALWAYS_USE_HUNSPELL` constant.

```javascript
const checker = new SpellChecker.Spellchecker();
checker.setSpellcheckerType(SpellChecker.ALWAYS_USE_SYSTEM);
```

On Linux, Hunspell is always used regardless of the setting. This method must also be called before any spelling is done otherwise it will throw an error.

This returns nothing.
5 changes: 4 additions & 1 deletion lib/spellchecker.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,5 +103,8 @@ module.exports = {
getAvailableDictionaries: getAvailableDictionaries,
getCorrectionsForMisspelling: getCorrectionsForMisspelling,
getDictionaryPath: getDictionaryPath,
Spellchecker: Spellchecker
Spellchecker: Spellchecker,
USE_SYSTEM_DEFAULTS: 0,
ALWAYS_USE_SYSTEM: 1,
ALWAYS_USE_HUNSPELL: 2,
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"test": "jasmine-focused --captureExceptions --coffee spec/"
},
"devDependencies": {
"jasmine-focused": "1.x"
"jasmine-focused": "^1.0.7"
},
"dependencies": {
"any-promise": "^1.3.0",
Expand Down
942 changes: 477 additions & 465 deletions spec/spellchecker-spec.coffee

Large diffs are not rendered by default.

81 changes: 75 additions & 6 deletions src/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,49 @@ class Spellchecker : public Nan::ObjectWrap {
info.GetReturnValue().Set(info.This());
}

static NAN_METHOD(SetDictionary) {
static NAN_METHOD(SetSpellcheckerType) {
// Pull out the handle to the spellchecker instance.
Nan::HandleScope scope;

if (info.Length() < 1) {
return Nan::ThrowError("Bad argument");
return Nan::ThrowError("Bad argument: missing mode");
}

Spellchecker* that = Nan::ObjectWrap::Unwrap<Spellchecker>(info.Holder());

// If we already have an implementation, then we want to complain because
// we can't handle reinitializing the dictionary paths.
if (that->impl) {
return Nan::ThrowError("Cannot call SetSpellcheckerType after the dictionary has been configured or used");
}

// Make sure we have a sane value for our enumeration.
int modeNumber = info[0]->Int32Value(Nan::GetCurrentContext()).ToChecked();
int spellcheckerType = USE_SYSTEM_DEFAULTS;

switch (modeNumber)
{
case 0:
break;
case 1:
spellcheckerType = ALWAYS_USE_SYSTEM;
break;
case 2:
spellcheckerType = ALWAYS_USE_HUNSPELL;
break;
default:
return Nan::ThrowError("Bad argument: SetSpellcheckerType must be given 0, 1, or 2 as a parameter");
}

// Create a new one with the appropriate checker type.
that->impl = SpellcheckerFactory::CreateSpellchecker(spellcheckerType);
}

static NAN_METHOD(SetDictionary) {
Nan::HandleScope scope;

if (info.Length() < 2) {
return Nan::ThrowError("Bad arguments");
}

Spellchecker* that = Nan::ObjectWrap::Unwrap<Spellchecker>(info.Holder());
Expand All @@ -36,6 +74,9 @@ class Spellchecker : public Nan::ObjectWrap {
directory = *Nan::Utf8String(info[1]);
}

// Make sure we have the implementation loaded.
Spellchecker::EnsureLoadedImplementation(that);

bool result = that->impl->SetDictionary(language, directory);
info.GetReturnValue().Set(Nan::New(result));
}
Expand All @@ -49,6 +90,9 @@ class Spellchecker : public Nan::ObjectWrap {
Spellchecker* that = Nan::ObjectWrap::Unwrap<Spellchecker>(info.Holder());
std::string word = *Nan::Utf8String(info[0]);

// Make sure we have the implementation loaded.
Spellchecker::EnsureLoadedImplementation(that);

info.GetReturnValue().Set(Nan::New(that->impl->IsMisspelled(word)));
}

Expand Down Expand Up @@ -78,6 +122,10 @@ class Spellchecker : public Nan::ObjectWrap {
reinterpret_cast<uint16_t *>(text.data()));

Spellchecker* that = Nan::ObjectWrap::Unwrap<Spellchecker>(info.Holder());

// Make sure we have the implementation loaded.
Spellchecker::EnsureLoadedImplementation(that);

std::vector<MisspelledRange> misspelled_ranges = that->impl->CheckSpelling(text.data(), text.size());

std::vector<MisspelledRange>::const_iterator iter = misspelled_ranges.begin();
Expand Down Expand Up @@ -114,6 +162,9 @@ class Spellchecker : public Nan::ObjectWrap {

Spellchecker* that = Nan::ObjectWrap::Unwrap<Spellchecker>(info.Holder());

// Make sure we have the implementation loaded.
Spellchecker::EnsureLoadedImplementation(that);

CheckSpellingWorker* worker = new CheckSpellingWorker(std::move(corpus), that->impl, callback);
Nan::AsyncQueueWorker(worker);
}
Expand All @@ -125,8 +176,11 @@ class Spellchecker : public Nan::ObjectWrap {
}

Spellchecker* that = Nan::ObjectWrap::Unwrap<Spellchecker>(info.Holder());
std::string word = *Nan::Utf8String(info[0]);

// Make sure we have the implementation loaded.
Spellchecker::EnsureLoadedImplementation(that);

std::string word = *Nan::Utf8String(info[0]);
that->impl->Add(word);
return;
}
Expand All @@ -138,18 +192,23 @@ class Spellchecker : public Nan::ObjectWrap {
}

Spellchecker* that = Nan::ObjectWrap::Unwrap<Spellchecker>(info.Holder());
std::string word = *Nan::Utf8String(info[0]);

// Make sure we have the implementation loaded.
Spellchecker::EnsureLoadedImplementation(that);

std::string word = *Nan::Utf8String(info[0]);
that->impl->Remove(word);
return;
}


static NAN_METHOD(GetAvailableDictionaries) {
Nan::HandleScope scope;

Spellchecker* that = Nan::ObjectWrap::Unwrap<Spellchecker>(info.Holder());

// Make sure we have the implementation loaded.
Spellchecker::EnsureLoadedImplementation(that);

std::string path = ".";
if (info.Length() > 0) {
std::string path = *Nan::Utf8String(info[0]);
Expand All @@ -175,6 +234,9 @@ class Spellchecker : public Nan::ObjectWrap {

Spellchecker* that = Nan::ObjectWrap::Unwrap<Spellchecker>(info.Holder());

// Make sure we have the implementation loaded.
Spellchecker::EnsureLoadedImplementation(that);

std::string word = *Nan::Utf8String(info[0]);
std::vector<std::string> corrections =
that->impl->GetCorrectionsForMisspelling(word);
Expand All @@ -191,21 +253,28 @@ class Spellchecker : public Nan::ObjectWrap {
}

Spellchecker() {
impl = SpellcheckerFactory::CreateSpellchecker();
impl = NULL;
}

// actual destructor
virtual ~Spellchecker() {
delete impl;
}

static void EnsureLoadedImplementation(Spellchecker *that) {
if (!that->impl) {
that->impl = SpellcheckerFactory::CreateSpellchecker(USE_SYSTEM_DEFAULTS);
}
}

public:
static void Init(Local<Object> exports) {
Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(Spellchecker::New);

tpl->SetClassName(Nan::New<String>("Spellchecker").ToLocalChecked());
tpl->InstanceTemplate()->SetInternalFieldCount(1);

Nan::SetPrototypeMethod(tpl, "setSpellcheckerType", Spellchecker::SetSpellcheckerType);
Nan::SetPrototypeMethod(tpl, "setDictionary", Spellchecker::SetDictionary);
Nan::SetPrototypeMethod(tpl, "getAvailableDictionaries", Spellchecker::GetAvailableDictionaries);
Nan::SetPrototypeMethod(tpl, "getCorrectionsForMisspelling", Spellchecker::GetCorrectionsForMisspelling);
Expand Down
6 changes: 5 additions & 1 deletion src/spellchecker.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@

namespace spellchecker {

const int USE_SYSTEM_DEFAULTS = 0;
const int ALWAYS_USE_SYSTEM = 1;
const int ALWAYS_USE_HUNSPELL = 2;

struct MisspelledRange {
size_t start;
size_t end;
Expand Down Expand Up @@ -66,7 +70,7 @@ class SpellcheckerImplementation {

class SpellcheckerFactory {
public:
static SpellcheckerImplementation* CreateSpellchecker();
static SpellcheckerImplementation* CreateSpellchecker(int spellcheckerType);
};

inline std::vector<MisspelledRange> SpellcheckerThreadView::CheckSpelling(const uint16_t *text, size_t length)
Expand Down
2 changes: 1 addition & 1 deletion src/spellchecker_hunspell.cc
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ std::vector<MisspelledRange> HunspellSpellchecker::CheckSpelling(const uint16_t
// way, we need to make sure our iswalpha works on UTF-8 strings. We picked a
// generic locale because we don't pass the locale in. Sadly, "C.utf8" doesn't
// work so we assume that US English is available everywhere.
setlocale(LC_CTYPE, "en_US.utf8");
setlocale(LC_CTYPE, "en_US.UTF-8");

// Go through the UTF-16 characters and look for breaks.
for (size_t word_start = 0, i = 0; i < utf16_length; i++) {
Expand Down
2 changes: 1 addition & 1 deletion src/spellchecker_linux.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace spellchecker {

SpellcheckerImplementation* SpellcheckerFactory::CreateSpellchecker() {
SpellcheckerImplementation* SpellcheckerFactory::CreateSpellchecker(int spellcheckerType) {
return new HunspellSpellchecker();
}

Expand Down
10 changes: 6 additions & 4 deletions src/spellchecker_mac.mm
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,14 @@
}
}

SpellcheckerImplementation* SpellcheckerFactory::CreateSpellchecker() {
if (getenv("SPELLCHECKER_PREFER_HUNSPELL")) {
return new HunspellSpellchecker();
SpellcheckerImplementation* SpellcheckerFactory::CreateSpellchecker(int spellcheckerType) {
bool preferHunspell = getenv("SPELLCHECKER_PREFER_HUNSPELL") && spellcheckerType != ALWAYS_USE_SYSTEM;

if (spellcheckerType != ALWAYS_USE_HUNSPELL && !preferHunspell) {
return new MacSpellchecker();
}

return new MacSpellchecker();
return new HunspellSpellchecker();
}

} // namespace spellchecker
16 changes: 11 additions & 5 deletions src/spellchecker_win.cc
Original file line number Diff line number Diff line change
Expand Up @@ -401,13 +401,19 @@ uv_mutex_t &WindowsSpellchecker::GetGlobalTableMutex()
return this->gTableMutex;
}

SpellcheckerImplementation* SpellcheckerFactory::CreateSpellchecker() {
WindowsSpellchecker* ret = new WindowsSpellchecker();
if (ret->IsSupported() && getenv("SPELLCHECKER_PREFER_HUNSPELL") == NULL) {
return ret;
SpellcheckerImplementation* SpellcheckerFactory::CreateSpellchecker(int spellcheckerType) {
bool preferHunspell = getenv("SPELLCHECKER_PREFER_HUNSPELL") && spellcheckerType != ALWAYS_USE_SYSTEM;

if (spellcheckerType != ALWAYS_USE_HUNSPELL && !preferHunspell) {
WindowsSpellchecker* ret = new WindowsSpellchecker();

if (ret->IsSupported()) {
return ret;
}

delete ret;
}

delete ret;
return new HunspellSpellchecker();
}

Expand Down
1 change: 1 addition & 0 deletions src/transcoder_posix.cc
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Transcoder *NewUTF16ToUTF8Transcoder() {

Transcoder *NewTranscoder8to8(const char *from_encoding, const char *to_encoding) {
iconv_t conversion = iconv_open(to_encoding, from_encoding);

if (conversion == (iconv_t)-1) {
return NULL;
}
Expand Down

0 comments on commit 55ae514

Please sign in to comment.