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

Commit 55ae514

Browse files
author
Nathan Sobo
authored
Merge pull request #115 from dmoonfire/optional-system-checker
Add a new method, `setSpellcheckerType`, to pick spellchecker selection.
2 parents 81367ae + a9f58e5 commit 55ae514

File tree

11 files changed

+621
-485
lines changed

11 files changed

+621
-485
lines changed

README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,42 @@ When using Hunspell, this will not modify the .dic file; new words must be added
5454
`word` - String word to add.
5555

5656
Returns nothing.
57+
58+
### new Spellchecker()
59+
60+
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.
61+
62+
```javascript
63+
const checker = new SpellChecker.Spellchecker()
64+
```
65+
66+
#### SpellChecker.Spellchecker.setSpellcheckerType(type)
67+
68+
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.
69+
70+
If the environment variable `SPELLCHECKER_PREFER_HUNSPELL` is set to any value, the library will fallback to always using the Hunspell implementation.
71+
72+
This is the same behavior as calling `setSpellcheckerType` with the `USE_SYSTEM_DEFAULTS` constant:
73+
74+
```coffeescript
75+
checker = new SpellChecker.Spellchecker
76+
checker.setSpellcheckerType SpellChecker.USE_SYSTEM_DEFAULTS
77+
```
78+
79+
To always use the system API and not fallback to Hunspell regardless of the environment variable, use the `ALWAYS_USE_SYSTEM` constant:
80+
81+
```coffeescript
82+
checker = new SpellChecker.Spellchecker
83+
checker.setSpellcheckerType SpellChecker.ALWAYS_USE_SYSTEM
84+
```
85+
86+
Likewise, Hunspell can be forced with the `ALWAYS_USE_HUNSPELL` constant.
87+
88+
```javascript
89+
const checker = new SpellChecker.Spellchecker();
90+
checker.setSpellcheckerType(SpellChecker.ALWAYS_USE_SYSTEM);
91+
```
92+
93+
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.
94+
95+
This returns nothing.

lib/spellchecker.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,5 +103,8 @@ module.exports = {
103103
getAvailableDictionaries: getAvailableDictionaries,
104104
getCorrectionsForMisspelling: getCorrectionsForMisspelling,
105105
getDictionaryPath: getDictionaryPath,
106-
Spellchecker: Spellchecker
106+
Spellchecker: Spellchecker,
107+
USE_SYSTEM_DEFAULTS: 0,
108+
ALWAYS_USE_SYSTEM: 1,
109+
ALWAYS_USE_HUNSPELL: 2,
107110
};

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"test": "jasmine-focused --captureExceptions --coffee spec/"
1717
},
1818
"devDependencies": {
19-
"jasmine-focused": "1.x"
19+
"jasmine-focused": "^1.0.7"
2020
},
2121
"dependencies": {
2222
"any-promise": "^1.3.0",

spec/spellchecker-spec.coffee

Lines changed: 477 additions & 465 deletions
Large diffs are not rendered by default.

src/main.cc

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,49 @@ class Spellchecker : public Nan::ObjectWrap {
2121
info.GetReturnValue().Set(info.This());
2222
}
2323

24-
static NAN_METHOD(SetDictionary) {
24+
static NAN_METHOD(SetSpellcheckerType) {
25+
// Pull out the handle to the spellchecker instance.
2526
Nan::HandleScope scope;
2627

2728
if (info.Length() < 1) {
28-
return Nan::ThrowError("Bad argument");
29+
return Nan::ThrowError("Bad argument: missing mode");
30+
}
31+
32+
Spellchecker* that = Nan::ObjectWrap::Unwrap<Spellchecker>(info.Holder());
33+
34+
// If we already have an implementation, then we want to complain because
35+
// we can't handle reinitializing the dictionary paths.
36+
if (that->impl) {
37+
return Nan::ThrowError("Cannot call SetSpellcheckerType after the dictionary has been configured or used");
38+
}
39+
40+
// Make sure we have a sane value for our enumeration.
41+
int modeNumber = info[0]->Int32Value(Nan::GetCurrentContext()).ToChecked();
42+
int spellcheckerType = USE_SYSTEM_DEFAULTS;
43+
44+
switch (modeNumber)
45+
{
46+
case 0:
47+
break;
48+
case 1:
49+
spellcheckerType = ALWAYS_USE_SYSTEM;
50+
break;
51+
case 2:
52+
spellcheckerType = ALWAYS_USE_HUNSPELL;
53+
break;
54+
default:
55+
return Nan::ThrowError("Bad argument: SetSpellcheckerType must be given 0, 1, or 2 as a parameter");
56+
}
57+
58+
// Create a new one with the appropriate checker type.
59+
that->impl = SpellcheckerFactory::CreateSpellchecker(spellcheckerType);
60+
}
61+
62+
static NAN_METHOD(SetDictionary) {
63+
Nan::HandleScope scope;
64+
65+
if (info.Length() < 2) {
66+
return Nan::ThrowError("Bad arguments");
2967
}
3068

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

77+
// Make sure we have the implementation loaded.
78+
Spellchecker::EnsureLoadedImplementation(that);
79+
3980
bool result = that->impl->SetDictionary(language, directory);
4081
info.GetReturnValue().Set(Nan::New(result));
4182
}
@@ -49,6 +90,9 @@ class Spellchecker : public Nan::ObjectWrap {
4990
Spellchecker* that = Nan::ObjectWrap::Unwrap<Spellchecker>(info.Holder());
5091
std::string word = *Nan::Utf8String(info[0]);
5192

93+
// Make sure we have the implementation loaded.
94+
Spellchecker::EnsureLoadedImplementation(that);
95+
5296
info.GetReturnValue().Set(Nan::New(that->impl->IsMisspelled(word)));
5397
}
5498

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

80124
Spellchecker* that = Nan::ObjectWrap::Unwrap<Spellchecker>(info.Holder());
125+
126+
// Make sure we have the implementation loaded.
127+
Spellchecker::EnsureLoadedImplementation(that);
128+
81129
std::vector<MisspelledRange> misspelled_ranges = that->impl->CheckSpelling(text.data(), text.size());
82130

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

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

165+
// Make sure we have the implementation loaded.
166+
Spellchecker::EnsureLoadedImplementation(that);
167+
117168
CheckSpellingWorker* worker = new CheckSpellingWorker(std::move(corpus), that->impl, callback);
118169
Nan::AsyncQueueWorker(worker);
119170
}
@@ -125,8 +176,11 @@ class Spellchecker : public Nan::ObjectWrap {
125176
}
126177

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

180+
// Make sure we have the implementation loaded.
181+
Spellchecker::EnsureLoadedImplementation(that);
182+
183+
std::string word = *Nan::Utf8String(info[0]);
130184
that->impl->Add(word);
131185
return;
132186
}
@@ -138,18 +192,23 @@ class Spellchecker : public Nan::ObjectWrap {
138192
}
139193

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

196+
// Make sure we have the implementation loaded.
197+
Spellchecker::EnsureLoadedImplementation(that);
198+
199+
std::string word = *Nan::Utf8String(info[0]);
143200
that->impl->Remove(word);
144201
return;
145202
}
146203

147-
148204
static NAN_METHOD(GetAvailableDictionaries) {
149205
Nan::HandleScope scope;
150206

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

209+
// Make sure we have the implementation loaded.
210+
Spellchecker::EnsureLoadedImplementation(that);
211+
153212
std::string path = ".";
154213
if (info.Length() > 0) {
155214
std::string path = *Nan::Utf8String(info[0]);
@@ -175,6 +234,9 @@ class Spellchecker : public Nan::ObjectWrap {
175234

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

237+
// Make sure we have the implementation loaded.
238+
Spellchecker::EnsureLoadedImplementation(that);
239+
178240
std::string word = *Nan::Utf8String(info[0]);
179241
std::vector<std::string> corrections =
180242
that->impl->GetCorrectionsForMisspelling(word);
@@ -191,21 +253,28 @@ class Spellchecker : public Nan::ObjectWrap {
191253
}
192254

193255
Spellchecker() {
194-
impl = SpellcheckerFactory::CreateSpellchecker();
256+
impl = NULL;
195257
}
196258

197259
// actual destructor
198260
virtual ~Spellchecker() {
199261
delete impl;
200262
}
201263

264+
static void EnsureLoadedImplementation(Spellchecker *that) {
265+
if (!that->impl) {
266+
that->impl = SpellcheckerFactory::CreateSpellchecker(USE_SYSTEM_DEFAULTS);
267+
}
268+
}
269+
202270
public:
203271
static void Init(Local<Object> exports) {
204272
Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(Spellchecker::New);
205273

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

277+
Nan::SetPrototypeMethod(tpl, "setSpellcheckerType", Spellchecker::SetSpellcheckerType);
209278
Nan::SetPrototypeMethod(tpl, "setDictionary", Spellchecker::SetDictionary);
210279
Nan::SetPrototypeMethod(tpl, "getAvailableDictionaries", Spellchecker::GetAvailableDictionaries);
211280
Nan::SetPrototypeMethod(tpl, "getCorrectionsForMisspelling", Spellchecker::GetCorrectionsForMisspelling);

src/spellchecker.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88

99
namespace spellchecker {
1010

11+
const int USE_SYSTEM_DEFAULTS = 0;
12+
const int ALWAYS_USE_SYSTEM = 1;
13+
const int ALWAYS_USE_HUNSPELL = 2;
14+
1115
struct MisspelledRange {
1216
size_t start;
1317
size_t end;
@@ -66,7 +70,7 @@ class SpellcheckerImplementation {
6670

6771
class SpellcheckerFactory {
6872
public:
69-
static SpellcheckerImplementation* CreateSpellchecker();
73+
static SpellcheckerImplementation* CreateSpellchecker(int spellcheckerType);
7074
};
7175

7276
inline std::vector<MisspelledRange> SpellcheckerThreadView::CheckSpelling(const uint16_t *text, size_t length)

src/spellchecker_hunspell.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ std::vector<MisspelledRange> HunspellSpellchecker::CheckSpelling(const uint16_t
125125
// way, we need to make sure our iswalpha works on UTF-8 strings. We picked a
126126
// generic locale because we don't pass the locale in. Sadly, "C.utf8" doesn't
127127
// work so we assume that US English is available everywhere.
128-
setlocale(LC_CTYPE, "en_US.utf8");
128+
setlocale(LC_CTYPE, "en_US.UTF-8");
129129

130130
// Go through the UTF-16 characters and look for breaks.
131131
for (size_t word_start = 0, i = 0; i < utf16_length; i++) {

src/spellchecker_linux.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace spellchecker {
55

6-
SpellcheckerImplementation* SpellcheckerFactory::CreateSpellchecker() {
6+
SpellcheckerImplementation* SpellcheckerFactory::CreateSpellchecker(int spellcheckerType) {
77
return new HunspellSpellchecker();
88
}
99

src/spellchecker_mac.mm

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,14 @@
154154
}
155155
}
156156

157-
SpellcheckerImplementation* SpellcheckerFactory::CreateSpellchecker() {
158-
if (getenv("SPELLCHECKER_PREFER_HUNSPELL")) {
159-
return new HunspellSpellchecker();
157+
SpellcheckerImplementation* SpellcheckerFactory::CreateSpellchecker(int spellcheckerType) {
158+
bool preferHunspell = getenv("SPELLCHECKER_PREFER_HUNSPELL") && spellcheckerType != ALWAYS_USE_SYSTEM;
159+
160+
if (spellcheckerType != ALWAYS_USE_HUNSPELL && !preferHunspell) {
161+
return new MacSpellchecker();
160162
}
161163

162-
return new MacSpellchecker();
164+
return new HunspellSpellchecker();
163165
}
164166

165167
} // namespace spellchecker

src/spellchecker_win.cc

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -401,13 +401,19 @@ uv_mutex_t &WindowsSpellchecker::GetGlobalTableMutex()
401401
return this->gTableMutex;
402402
}
403403

404-
SpellcheckerImplementation* SpellcheckerFactory::CreateSpellchecker() {
405-
WindowsSpellchecker* ret = new WindowsSpellchecker();
406-
if (ret->IsSupported() && getenv("SPELLCHECKER_PREFER_HUNSPELL") == NULL) {
407-
return ret;
404+
SpellcheckerImplementation* SpellcheckerFactory::CreateSpellchecker(int spellcheckerType) {
405+
bool preferHunspell = getenv("SPELLCHECKER_PREFER_HUNSPELL") && spellcheckerType != ALWAYS_USE_SYSTEM;
406+
407+
if (spellcheckerType != ALWAYS_USE_HUNSPELL && !preferHunspell) {
408+
WindowsSpellchecker* ret = new WindowsSpellchecker();
409+
410+
if (ret->IsSupported()) {
411+
return ret;
412+
}
413+
414+
delete ret;
408415
}
409416

410-
delete ret;
411417
return new HunspellSpellchecker();
412418
}
413419

0 commit comments

Comments
 (0)