signal-desktop/js/spell_check.js

153 lines
4.7 KiB
JavaScript
Raw Normal View History

2018-04-27 21:25:04 +00:00
(function() {
var electron = require('electron');
var remote = electron.remote;
var app = remote.app;
var webFrame = electron.webFrame;
var path = require('path');
var osLocale = require('os-locale');
var os = require('os');
var semver = require('semver');
var spellchecker = require('spellchecker');
// `remote.require` since `Menu` is a main-process module.
var buildEditorContextMenu = remote.require('electron-editor-context-menu');
var EN_VARIANT = /^en/;
// Prevent the spellchecker from showing contractions as errors.
var ENGLISH_SKIP_WORDS = [
'ain',
'couldn',
'didn',
'doesn',
'hadn',
'hasn',
'mightn',
'mustn',
'needn',
'oughtn',
'shan',
'shouldn',
'wasn',
'weren',
2018-04-27 21:25:04 +00:00
'wouldn',
];
function setupLinux(locale) {
if (process.env.HUNSPELL_DICTIONARIES || locale !== 'en_US') {
// apt-get install hunspell-<locale> can be run for easy access to other dictionaries
var location = process.env.HUNSPELL_DICTIONARIES || '/usr/share/hunspell';
2018-04-27 21:25:04 +00:00
console.log(
'Detected Linux. Setting up spell check with locale',
locale,
'and dictionary location',
location
);
spellchecker.setDictionary(locale, location);
} else {
console.log('Detected Linux. Using default en_US spell check dictionary');
}
}
function setupWin7AndEarlier(locale) {
if (process.env.HUNSPELL_DICTIONARIES || locale !== 'en_US') {
var location = process.env.HUNSPELL_DICTIONARIES;
2018-04-27 21:25:04 +00:00
console.log(
'Detected Windows 7 or below. Setting up spell-check with locale',
locale,
'and dictionary location',
location
);
spellchecker.setDictionary(locale, location);
} else {
2018-04-27 21:25:04 +00:00
console.log(
'Detected Windows 7 or below. Using default en_US spell check dictionary'
);
}
}
// We load locale this way and not via app.getLocale() because this call returns
// 'es_ES' and not just 'es.' And hunspell requires the fully-qualified locale.
var locale = osLocale.sync().replace('-', '_');
// The LANG environment variable is how node spellchecker finds its default language:
// https://github.com/atom/node-spellchecker/blob/59d2d5eee5785c4b34e9669cd5d987181d17c098/lib/spellchecker.js#L29
if (!process.env.LANG) {
process.env.LANG = locale;
}
if (process.platform === 'linux') {
setupLinux(locale);
2018-04-27 21:25:04 +00:00
} else if (
process.platform === 'windows' &&
semver.lt(os.release(), '8.0.0')
) {
setupWin7AndEarlier(locale);
} else {
// OSX and Windows 8+ have OS-level spellcheck APIs
console.log('Using OS-level spell check API with locale', process.env.LANG);
}
2018-04-27 21:25:04 +00:00
var simpleChecker = (window.spellChecker = {
spellCheck: function(text) {
return !this.isMisspelled(text);
},
isMisspelled: function(text) {
var misspelled = spellchecker.isMisspelled(text);
// The idea is to make this as fast as possible. For the many, many calls which
// don't result in the red squiggly, we minimize the number of checks.
if (!misspelled) {
return false;
}
// Only if we think we've found an error do we check the locale and skip list.
if (locale.match(EN_VARIANT) && _.contains(ENGLISH_SKIP_WORDS, text)) {
return false;
}
return true;
},
getSuggestions: function(text) {
return spellchecker.getCorrectionsForMisspelling(text);
},
add: function(text) {
spellchecker.add(text);
2018-04-27 21:25:04 +00:00
},
});
webFrame.setSpellCheckProvider(
'en-US',
// Not sure what this parameter (`autoCorrectWord`) does: https://github.com/atom/electron/issues/4371
// The documentation for `webFrame.setSpellCheckProvider` passes `true` so we do too.
true,
simpleChecker
);
window.addEventListener('contextmenu', function(e) {
// Only show the context menu in text editors.
if (!e.target.closest('textarea, input, [contenteditable="true"]')) {
return;
}
var selectedText = window.getSelection().toString();
var isMisspelled = selectedText && simpleChecker.isMisspelled(selectedText);
2018-04-27 21:25:04 +00:00
var spellingSuggestions =
isMisspelled && simpleChecker.getSuggestions(selectedText).slice(0, 5);
var menu = buildEditorContextMenu({
isMisspelled: isMisspelled,
spellingSuggestions: spellingSuggestions,
});
// The 'contextmenu' event is emitted after 'selectionchange' has fired but possibly before the
// visible selection has changed. Try to wait to show the menu until after that, otherwise the
// visible selection will update after the menu dismisses and look weird.
setTimeout(function() {
menu.popup(remote.getCurrentWindow());
}, 30);
});
})();