From dbc316eeee10c658ce057c7a74817c1974552bb9 Mon Sep 17 00:00:00 2001 From: manusa Date: Wed, 1 Jan 2020 12:34:09 +0100 Subject: [PATCH 1/4] wip: Dictionaries load blocking the UI --- README.md | 4 +- package-lock.json | 131 ++++++++++++++++++ package.json | 23 ++- src/constants/index.js | 1 + src/main/index.js | 3 + src/spell-check/browser-spell-check.js | 17 ++- src/spell-check/index.js | 88 ++++++++---- .../load-dictionary.renderer/index.html | 13 ++ .../load-dictionary.worker.js | 23 +++ src/spell-check/load-dictionary.worker.js | 7 + 10 files changed, 275 insertions(+), 35 deletions(-) create mode 100644 src/spell-check/load-dictionary.renderer/index.html create mode 100644 src/spell-check/load-dictionary.renderer/load-dictionary.worker.js create mode 100644 src/spell-check/load-dictionary.worker.js diff --git a/README.md b/README.md index 94c6088f..2ab66960 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,8 @@ spell checking for free. ## Acknowledgements - [Electron](https://electronjs.org/) +- [Bulma](https://github.com/jgthms/bulma) - [Chrome tabs](https://github.com/adamschwartz/chrome-tabs#readme) - [Draggabilly](https://github.com/desandro/draggabilly) -- [Simple spell checker](https://github.com/jfmdev/simple-spellchecker) -- [Bulma](https://github.com/jgthms/bulma) - [Font Awesome Free](https://github.com/FortAwesome/Font-Awesome) +- [nspell](https://github.com/wooorm/nspell) diff --git a/package-lock.json b/package-lock.json index 0ef928d4..de7f75cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1523,6 +1523,101 @@ "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", "optional": true }, + "dictionary-ca": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/dictionary-ca/-/dictionary-ca-2.1.1.tgz", + "integrity": "sha512-rSIwQqMrPAV1VgzG+tHQ7KrGRPf3qj/yuzBnkf1u8YnC7L6A8s8CRBqxiCQc/oH3QXg+cwhhRkxh4t0Kcu6K7A==" + }, + "dictionary-ca-valencia": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/dictionary-ca-valencia/-/dictionary-ca-valencia-2.1.1.tgz", + "integrity": "sha512-k0YIKKyXAVo8ijCQwOq9Sd0DzffZTcC/xm5uYiWgBxUVDeSSecLGl8XAdDtZFUiAwellX8ZEY1oBFWVZ5CRdTQ==" + }, + "dictionary-de": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dictionary-de/-/dictionary-de-2.0.2.tgz", + "integrity": "sha512-NIhhHUw2pAk/zkHt9kKe1CDTwJNTGcPYAMQuyEGdHSrShAGSx52egqc4n/xG2Xfy77DI1Yt7b1gUbeBR6mQqlA==" + }, + "dictionary-en-gb": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/dictionary-en-gb/-/dictionary-en-gb-2.1.1.tgz", + "integrity": "sha512-G9jAkpXtSpkdllzojfcVAWy0KBCR/MzyYHA9p2So+fJXAN2yrR0LXacdcRweswiV3aNnaf+krTk0M4I+d5RKMw==" + }, + "dictionary-en-us": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/dictionary-en-us/-/dictionary-en-us-2.1.1.tgz", + "integrity": "sha512-3182Q3ede8TEm6FthZ2u8FppaayB+D43wy5cnOSweZwg9vSmmUCjCA3zxmMpZLw0nIAILGLR0j+06o2l9aaCzA==" + }, + "dictionary-es": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/dictionary-es/-/dictionary-es-1.2.3.tgz", + "integrity": "sha512-G4Tj+xHXWY0P+J3i2LTvrZMpmA9teYrtsDPYbZIsWN1JEFK9f66kT+5PwLlEkkGby7kTf5d+g1Sc7hX74IkyCw==" + }, + "dictionary-eu": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/dictionary-eu/-/dictionary-eu-3.1.1.tgz", + "integrity": "sha512-KB3rJdU7cpJoYysqLyQVl6LyNJS6MsjDIW8NfIS1mIyBbQPttaV2cV2CJshxXlD8ZzvzBtF7Iv1jr2Ea93NnLQ==" + }, + "dictionary-fr": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/dictionary-fr/-/dictionary-fr-2.3.2.tgz", + "integrity": "sha512-j1e3qIkoLsGnsV/Vn74ILh/1U7+tO1K6z70ZVk/rrGatYMRmlvT3HgSQ6W/jEyXKkSE2EeeDwPZpT6U6WiTJJw==" + }, + "dictionary-it": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/dictionary-it/-/dictionary-it-1.3.1.tgz", + "integrity": "sha512-EE6jy0F7hJewKWFQ0ITx9afHjIscP52+uKWtxH6yfRI3aCFzZJC1dhC1F1uplVTrTgSb6sbGmM7jzydY1gIDxg==" + }, + "dictionary-ka": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dictionary-ka/-/dictionary-ka-1.0.0.tgz", + "integrity": "sha512-NLftElBRlPPpVbF7aqQRf91qOOEuniwJvWuO3oR1qq4Wuxu6WAcdnld+xYCeAJjZlIkHGctkqTVzsTmu10UOpw==" + }, + "dictionary-lt": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dictionary-lt/-/dictionary-lt-1.0.1.tgz", + "integrity": "sha512-iW1n/f7i7culXxKM0XASyKp4VW0lRIu2fdcHjIGUaydXIwHT4OxqWruQSfYabqh4e4MKV/2F6bUvOek9nIeO2w==" + }, + "dictionary-nl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/dictionary-nl/-/dictionary-nl-1.3.2.tgz", + "integrity": "sha512-UpQ/S2UNvNcXqcgKtltyJIoWeZouUap80fQqsp250JVOmvadxXM1/UG2pvglU//F9PZCSAynFKjYh1JnbDjUNA==" + }, + "dictionary-pl": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/dictionary-pl/-/dictionary-pl-1.2.2.tgz", + "integrity": "sha512-qvYuL1I2Mtw69JRWu+FvBvb3fKZ6bXfOn9TiO2nDlogJqjTMmhVxB4KX0UQjQtKOA47nCPNdEzXuvFjzgGIHoA==" + }, + "dictionary-pt": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/dictionary-pt/-/dictionary-pt-1.3.1.tgz", + "integrity": "sha512-mczX359Cg9ywhjdgcRbRkCZq0QYynsaH47+6uix0YZEpxWeMAmVsavb0M4gv7VEP33SFBXWYsBV6DFViLUKUMw==" + }, + "dictionary-pt-br": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/dictionary-pt-br/-/dictionary-pt-br-1.2.2.tgz", + "integrity": "sha512-cnyjBtybWGyKeh7RTNlpohqVrGfrquSvIRS+Ntn0+xwigD/mdalekIeqlDbbhGn6Y++wOrAAGw6L9mlruxNGgQ==" + }, + "dictionary-ru": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/dictionary-ru/-/dictionary-ru-1.2.2.tgz", + "integrity": "sha512-IBAH6Qi/xjiVMViCiwdvmx05vRz/Y7I/UyScGkhzqeqwX3O5NdZtyM2jXru1AjF8uhAR5fhrpj87oVvkWGgziQ==" + }, + "dictionary-sv": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dictionary-sv/-/dictionary-sv-2.0.2.tgz", + "integrity": "sha512-rvilg7iJGpLzfoH4AToVGhKBJflFYKcbJKKd4qMWDuZPH22L7rXcidcJ/kuREkzvL7Va56drARRgEOVhWjSbRw==" + }, + "dictionary-tr": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/dictionary-tr/-/dictionary-tr-1.3.1.tgz", + "integrity": "sha512-6cWBXpkUfMOSqi0XtwdfECLeRNdA9B/3IhPsVvWLPZngGcL2Ms/yGkovEi1iPKmrmUN/fVZTRdIo60g75tLmkw==" + }, + "dictionary-uk": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/dictionary-uk/-/dictionary-uk-2.1.1.tgz", + "integrity": "sha512-GK7W8ZF3FRwqwpRy/mhwbVarR7/EJyaUI1/dlLNYXdeKlhR/Ead0gZxz8KN/8QQQZZrhI6OWTAkTASnStKDKKw==" + }, "diff-sequences": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz", @@ -1699,6 +1794,14 @@ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz", "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==" }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "requires": { + "prr": "~1.0.1" + } + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -4642,6 +4745,21 @@ "path-key": "^2.0.0" } }, + "nspell": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/nspell/-/nspell-2.1.2.tgz", + "integrity": "sha512-j79L4A5aJSiswMUJ/6BW8+7ytgBUVce5BJX6xq8LtvKQpU96CMB340/yMeREH9U+gB/8dk4ctTn62DLiPMAXsA==", + "requires": { + "is-buffer": "^2.0.0" + }, + "dependencies": { + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==" + } + } + }, "nwsapi": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", @@ -5020,6 +5138,11 @@ "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", "optional": true }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" + }, "psl": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.6.0.tgz", @@ -6395,6 +6518,14 @@ "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", "dev": true }, + "worker-farm": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", + "requires": { + "errno": "~0.1.7" + } + }, "wrap-ansi": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", diff --git a/package.json b/package.json index 81fc2b41..f308cf5e 100644 --- a/package.json +++ b/package.json @@ -52,8 +52,29 @@ "@fortawesome/fontawesome-free": "^5.12.0", "bulma": "^0.8.0", "chrome-tabs": "^5.4.0", + "dictionary-ca": "^2.1.1", + "dictionary-ca-valencia": "^2.1.1", + "dictionary-de": "^2.0.2", + "dictionary-en-gb": "^2.1.1", + "dictionary-en-us": "^2.1.1", + "dictionary-es": "^1.2.3", + "dictionary-eu": "^3.1.1", + "dictionary-fr": "^2.3.2", + "dictionary-it": "^1.3.1", + "dictionary-ka": "^1.0.0", + "dictionary-lt": "^1.0.1", + "dictionary-nl": "^1.3.2", + "dictionary-pl": "^1.2.2", + "dictionary-pt": "^1.3.1", + "dictionary-pt-br": "^1.2.2", + "dictionary-ru": "^1.2.2", + "dictionary-sv": "^2.0.2", + "dictionary-tr": "^1.3.1", + "dictionary-uk": "^2.1.1", "draggabilly": "^2.2.0", "electron": "^7.1.6", - "simple-spellchecker": "^1.0.0" + "nspell": "^2.1.2", + "simple-spellchecker": "^1.0.0", + "worker-farm": "^1.7.0" } } diff --git a/src/constants/index.js b/src/constants/index.js index 902177b2..69c3c66b 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -1,6 +1,7 @@ const APP_EVENTS = { activateTab: 'activateTab', addTabs: 'addTabs', + dictionariesLoaded: 'dictionariesLoaded', settingsCancel: 'settingsCancel', settingsOpenDialog: 'settingsOpenDialog', settingsSave: 'settingsSave', diff --git a/src/main/index.js b/src/main/index.js index 65db6dec..4860e563 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -2,6 +2,7 @@ const {BrowserWindow, ipcMain: ipc} = require('electron'); const {APP_EVENTS} = require('../constants'); const {TABS_CONTAINER_HEIGHT, initTabContainer} = require('../chrome-tabs'); const {loadSettings, updateSettings, updateTabs, openSettingsDialog} = require('../settings'); +const {loadDictionaries} = require('../spell-check'); const tabManager = require('../tab-manager'); const webPreferences = { @@ -57,6 +58,7 @@ const closeSettings = () => { const saveSettings = (event, {tabs, dictionaries}) => { updateSettings({enabledDictionaries: [...dictionaries]}); updateTabs(tabs); + loadDictionaries(); const currentBrowserView = mainWindow.getBrowserView(); mainWindow.removeBrowserView(currentBrowserView); tabManager.removeAll(); @@ -71,6 +73,7 @@ const initSettingsListener = () => { }; const init = () => { + loadDictionaries(); const {width = 800, height = 600} = loadSettings(); mainWindow = new BrowserWindow({ width, height, resizable: true, maximizable: false, webPreferences diff --git a/src/spell-check/browser-spell-check.js b/src/spell-check/browser-spell-check.js index 02d0179b..8d5c93d5 100644 --- a/src/spell-check/browser-spell-check.js +++ b/src/spell-check/browser-spell-check.js @@ -1,15 +1,20 @@ -const activeDictionaries = require('./').getActiveDictionariesSnapshot(); +/* eslint-disable no-undef */ +const {remote} = require('electron'); + +const getActiveDictionaries = () => remote.getGlobal('dictionaries').activeDictionaries; const isMisspelled = word => - activeDictionaries.every(dictionary => !dictionary.spellCheck(word)); + getActiveDictionaries().every(dictionary => !dictionary.correct(word)); const initSpellChecker = webFrame => { webFrame.setSpellCheckProvider(navigator.language, { spellCheck (words, callback) { - setTimeout(() => { - const misspelled = words.filter(isMisspelled); - callback(misspelled); - }, 0); + if (getActiveDictionaries().length > 0) { + setTimeout(() => { + const misspelled = words.filter(isMisspelled); + callback(misspelled); + }, 0); + } } }); }; diff --git a/src/spell-check/index.js b/src/spell-check/index.js index 6e20e77b..5dcf1069 100644 --- a/src/spell-check/index.js +++ b/src/spell-check/index.js @@ -1,39 +1,78 @@ -const simpleSpellChecker = require('simple-spellchecker'); +const {BrowserWindow, ipcMain: ipc} = require('electron'); +const nspell = require('nspell'); const {loadSettings} = require('../settings'); const AVAILABLE_DICTIONARIES = [ - 'de-DE', - 'en-GB', - 'en-US', - 'es-ES', - 'es-MX', - 'fr-FR', - 'it-IT', - 'lt-LT', - 'nl-NL', - 'pl-PL', - 'pt-BR', - 'sv-SE' + 'ca', // + 'ca-valencia', // + 'de', + 'en-gb', + 'en-us', + 'es', + 'eu', // + 'fr', + 'it', + 'ka', + 'lt', + 'nl', + 'pl', + 'pt', + 'pt-br', + 'ru', + 'sv', + 'tr', + 'uk' ]; -const activeDictionaries = []; +let fakeRendererWorker; + +global.dictionaries = { + activeDictionaries: [] +}; const getEnabledDictionaries = () => loadSettings().enabledDictionaries; const loadDictionaries = () => { - getEnabledDictionaries().forEach(dictionaryKey => - simpleSpellChecker.getDictionary(dictionaryKey, (err, loadedDictionary) => { - activeDictionaries.push(loadedDictionary); - }) - ); + if (fakeRendererWorker) { + fakeRendererWorker.destroy(); + } + global.dictionaries.activeDictionaries = []; + fakeRendererWorker = new BrowserWindow({ + show: false, + webPreferences: {nodeIntegration: true} + }); + fakeRendererWorker.loadURL(`file://${__dirname}/load-dictionary.renderer/index.html`); + ipc.handle('dictionaryLoaded', async (event, {dictionary}) => { + global.dictionaries.activeDictionaries = [...global.dictionaries.activeDictionaries, nspell(dictionary)]; + }); + // ipc.on('dictionaryLoaded', (event, {nspellDictionary, dictionary}) => { + // // global.dictionaries.activeDictionaries = [...global.dictionaries.activeDictionaries, nspellDictionary]; + // setTimeout( + // () => global.dictionaries.activeDictionaries = [...global.dictionaries.activeDictionaries, nspell(dictionary)], 0); + // }); + fakeRendererWorker.webContents.openDevTools(); + // const loadDictionaryWorker = workerFarm(require.resolve('./load-dictionary.worker.js')); + // global.dictionaries.activeDictionaries.length = 0; + // getEnabledDictionaries() + // .filter(dictionaryKey => AVAILABLE_DICTIONARIES.includes(dictionaryKey)) + // .forEach(dictionaryKey => { + // setTimeout(() => { + // require(`dictionary-${dictionaryKey}`)((err, dict) => { + // global.dictionaries.push(nspell(dict)); + // }); + // }, 0); + // // loadDictionaryWorker(dictionaryKey, (aff, dic) => { + // // // global.activeDictionaries.push(nspell(Buffer.from(aff.data), Buffer.from(dic.data))); + // // }); + // }); }; const getSuggestions = word => { const ret = new Set(); - activeDictionaries.map(dictionary => dictionary.checkAndSuggest(word)) - .flatMap(({misspelled, suggestions}) => (misspelled ? suggestions : [])) + global.dictionaries.activeDictionaries.map(dictionary => dictionary.suggest(word)) + .flatMap(suggestions => suggestions) .forEach(suggestion => ret.add(suggestion)); - return Array.from(ret.values()); + return Array.from(ret.values()).sort().slice(0, 10); }; const contextMenuHandler = (event, {misspelledWord}, webContents) => { @@ -52,9 +91,6 @@ const contextMenuHandler = (event, {misspelledWord}, webContents) => { return ret; }; -const getActiveDictionariesSnapshot = () => activeDictionaries; - -loadDictionaries(); module.exports = { - AVAILABLE_DICTIONARIES, contextMenuHandler, getEnabledDictionaries, getActiveDictionariesSnapshot + AVAILABLE_DICTIONARIES, contextMenuHandler, getEnabledDictionaries, loadDictionaries }; diff --git a/src/spell-check/load-dictionary.renderer/index.html b/src/spell-check/load-dictionary.renderer/index.html new file mode 100644 index 00000000..8bf82447 --- /dev/null +++ b/src/spell-check/load-dictionary.renderer/index.html @@ -0,0 +1,13 @@ + + + + + This is a Fake Renderer to perform background tasks + + + + + diff --git a/src/spell-check/load-dictionary.renderer/load-dictionary.worker.js b/src/spell-check/load-dictionary.renderer/load-dictionary.worker.js new file mode 100644 index 00000000..1bc988dc --- /dev/null +++ b/src/spell-check/load-dictionary.renderer/load-dictionary.worker.js @@ -0,0 +1,23 @@ +const {remote, ipcRenderer} = require('electron'); +// const nspell = require('nspell'); +const {loadSettings} = require('../../settings'); + +const getDictionaries = () => remote.getGlobal('dictionaries'); +const load = () => { + getDictionaries().activeDictionaries = []; + loadSettings().enabledDictionaries + .forEach(dictionaryKey => { + const dictionary = require(`dictionary-${dictionaryKey}`); + if (dictionary) { + dictionary((err, dict) => { + // ipcRenderer.send('dictionaryLoaded', {nspellDictionary: nspell(dict)}); + const nspell = require('nspell'); + // ipcRenderer.send('dictionaryLoaded', {dictionary: dict}); + ipcRenderer.invoke('dictionaryLoaded', {dictionary: dict}); + // getDictionaries().activeDictionaries = [...getDictionaries().activeDictionaries, nspell(dict)]; + }); + } + }); +}; + +module.exports = load; diff --git a/src/spell-check/load-dictionary.worker.js b/src/spell-check/load-dictionary.worker.js new file mode 100644 index 00000000..1fabb4af --- /dev/null +++ b/src/spell-check/load-dictionary.worker.js @@ -0,0 +1,7 @@ +const loadDictionaryWorker = (dictionaryKey, callback) => { + require(`dictionary-${dictionaryKey}`)((err, dict) => { + callback(dict.aff, dict.dic); + }); +}; + +module.exports = loadDictionaryWorker; From 2b3f500bc2fac73e69e38eda25394cb5c2eb6815 Mon Sep 17 00:00:00 2001 From: manusa Date: Wed, 1 Jan 2020 16:27:43 +0100 Subject: [PATCH 2/4] wip: Async works --- src/constants/index.js | 2 +- src/spell-check/browser-spell-check.js | 18 ++---- .../dictionary.renderer/dictionary.worker.js | 37 ++++++++++++ .../index.html | 4 +- src/spell-check/index.js | 60 +++++-------------- .../load-dictionary.worker.js | 23 ------- src/tab-manager/index.js | 7 ++- 7 files changed, 63 insertions(+), 88 deletions(-) create mode 100644 src/spell-check/dictionary.renderer/dictionary.worker.js rename src/spell-check/{load-dictionary.renderer => dictionary.renderer}/index.html (73%) delete mode 100644 src/spell-check/load-dictionary.renderer/load-dictionary.worker.js diff --git a/src/constants/index.js b/src/constants/index.js index 69c3c66b..16cc2677 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -1,7 +1,7 @@ const APP_EVENTS = { activateTab: 'activateTab', addTabs: 'addTabs', - dictionariesLoaded: 'dictionariesLoaded', + dictionaryGetMispelled: 'dictionaryGetMispelled', settingsCancel: 'settingsCancel', settingsOpenDialog: 'settingsOpenDialog', settingsSave: 'settingsSave', diff --git a/src/spell-check/browser-spell-check.js b/src/spell-check/browser-spell-check.js index 8d5c93d5..567b4b4e 100644 --- a/src/spell-check/browser-spell-check.js +++ b/src/spell-check/browser-spell-check.js @@ -1,20 +1,12 @@ /* eslint-disable no-undef */ -const {remote} = require('electron'); - -const getActiveDictionaries = () => remote.getGlobal('dictionaries').activeDictionaries; - -const isMisspelled = word => - getActiveDictionaries().every(dictionary => !dictionary.correct(word)); +const {ipcRenderer} = require('electron'); const initSpellChecker = webFrame => { webFrame.setSpellCheckProvider(navigator.language, { - spellCheck (words, callback) { - if (getActiveDictionaries().length > 0) { - setTimeout(() => { - const misspelled = words.filter(isMisspelled); - callback(misspelled); - }, 0); - } + async spellCheck (words, callback) { + const misspelled = await ipcRenderer.invoke(APP_EVENTS.dictionaryGetMispelled, words); + // const misspelled = await words.filter(async word => await window.isMisspelled(word)); + callback(misspelled); } }); }; diff --git a/src/spell-check/dictionary.renderer/dictionary.worker.js b/src/spell-check/dictionary.renderer/dictionary.worker.js new file mode 100644 index 00000000..1216a52e --- /dev/null +++ b/src/spell-check/dictionary.renderer/dictionary.worker.js @@ -0,0 +1,37 @@ +const nspell = require('nspell'); +const {loadSettings} = require('../../settings'); + +const dictionaries = []; + +const isMisspelled = word => + dictionaries.every(dictionary => !dictionary.correct(word)); + +window.getMisspelled = words => { + if (dictionaries.length === 0) { + return []; + } + return words.filter(isMisspelled); +}; + +window.getSuggestions = word => { + const ret = new Set(); + dictionaries.map(dictionary => dictionary.suggest(word)) + .flatMap(suggestions => suggestions) + .forEach(suggestion => ret.add(suggestion)); + return Array.from(ret.values()).sort().slice(0, 10); +}; + +window.reloadDictionaries = () => { + dictionaries.length = 0; + const {enabledDictionaries} = loadSettings(); + enabledDictionaries + .forEach(dictionaryKey => { + const dictionary = require(`dictionary-${dictionaryKey}`); + if (dictionary) { + dictionary((err, dict) => { + dictionaries.push(nspell(dict)); + }); + } + }); +}; + diff --git a/src/spell-check/load-dictionary.renderer/index.html b/src/spell-check/dictionary.renderer/index.html similarity index 73% rename from src/spell-check/load-dictionary.renderer/index.html rename to src/spell-check/dictionary.renderer/index.html index 8bf82447..97f6706d 100644 --- a/src/spell-check/load-dictionary.renderer/index.html +++ b/src/spell-check/dictionary.renderer/index.html @@ -6,8 +6,8 @@ diff --git a/src/spell-check/index.js b/src/spell-check/index.js index 5dcf1069..63158797 100644 --- a/src/spell-check/index.js +++ b/src/spell-check/index.js @@ -1,5 +1,5 @@ -const {BrowserWindow, ipcMain: ipc} = require('electron'); -const nspell = require('nspell'); +const {BrowserWindow, ipcMain} = require('electron'); +const {APP_EVENTS} = require('../constants'); const {loadSettings} = require('../settings'); const AVAILABLE_DICTIONARIES = [ @@ -26,60 +26,25 @@ const AVAILABLE_DICTIONARIES = [ let fakeRendererWorker; -global.dictionaries = { - activeDictionaries: [] -}; - const getEnabledDictionaries = () => loadSettings().enabledDictionaries; const loadDictionaries = () => { - if (fakeRendererWorker) { - fakeRendererWorker.destroy(); + if (!fakeRendererWorker) { + fakeRendererWorker = new BrowserWindow({ + show: false, + webPreferences: {nodeIntegration: true} + }); } - global.dictionaries.activeDictionaries = []; - fakeRendererWorker = new BrowserWindow({ - show: false, - webPreferences: {nodeIntegration: true} - }); - fakeRendererWorker.loadURL(`file://${__dirname}/load-dictionary.renderer/index.html`); - ipc.handle('dictionaryLoaded', async (event, {dictionary}) => { - global.dictionaries.activeDictionaries = [...global.dictionaries.activeDictionaries, nspell(dictionary)]; - }); - // ipc.on('dictionaryLoaded', (event, {nspellDictionary, dictionary}) => { - // // global.dictionaries.activeDictionaries = [...global.dictionaries.activeDictionaries, nspellDictionary]; - // setTimeout( - // () => global.dictionaries.activeDictionaries = [...global.dictionaries.activeDictionaries, nspell(dictionary)], 0); - // }); + fakeRendererWorker.loadURL(`file://${__dirname}/dictionary.renderer/index.html`); fakeRendererWorker.webContents.openDevTools(); - // const loadDictionaryWorker = workerFarm(require.resolve('./load-dictionary.worker.js')); - // global.dictionaries.activeDictionaries.length = 0; - // getEnabledDictionaries() - // .filter(dictionaryKey => AVAILABLE_DICTIONARIES.includes(dictionaryKey)) - // .forEach(dictionaryKey => { - // setTimeout(() => { - // require(`dictionary-${dictionaryKey}`)((err, dict) => { - // global.dictionaries.push(nspell(dict)); - // }); - // }, 0); - // // loadDictionaryWorker(dictionaryKey, (aff, dic) => { - // // // global.activeDictionaries.push(nspell(Buffer.from(aff.data), Buffer.from(dic.data))); - // // }); - // }); }; -const getSuggestions = word => { - const ret = new Set(); - global.dictionaries.activeDictionaries.map(dictionary => dictionary.suggest(word)) - .flatMap(suggestions => suggestions) - .forEach(suggestion => ret.add(suggestion)); - return Array.from(ret.values()).sort().slice(0, 10); -}; - -const contextMenuHandler = (event, {misspelledWord}, webContents) => { +const contextMenuHandler = async (event, {misspelledWord}, webContents) => { const {MenuItem} = require('electron'); const ret = []; if (misspelledWord && misspelledWord.length > 0) { - getSuggestions(misspelledWord).forEach(suggestion => + const suggestions = await fakeRendererWorker.webContents.executeJavaScript(`getSuggestions('${misspelledWord}')`); + suggestions.forEach(suggestion => ret.push(new MenuItem({ label: suggestion, click: () => { @@ -91,6 +56,9 @@ const contextMenuHandler = (event, {misspelledWord}, webContents) => { return ret; }; +ipcMain.handle(APP_EVENTS.dictionaryGetMispelled, async (event, words) => + await fakeRendererWorker.webContents.executeJavaScript(`getMisspelled(${JSON.stringify(words)})`)); + module.exports = { AVAILABLE_DICTIONARIES, contextMenuHandler, getEnabledDictionaries, loadDictionaries }; diff --git a/src/spell-check/load-dictionary.renderer/load-dictionary.worker.js b/src/spell-check/load-dictionary.renderer/load-dictionary.worker.js deleted file mode 100644 index 1bc988dc..00000000 --- a/src/spell-check/load-dictionary.renderer/load-dictionary.worker.js +++ /dev/null @@ -1,23 +0,0 @@ -const {remote, ipcRenderer} = require('electron'); -// const nspell = require('nspell'); -const {loadSettings} = require('../../settings'); - -const getDictionaries = () => remote.getGlobal('dictionaries'); -const load = () => { - getDictionaries().activeDictionaries = []; - loadSettings().enabledDictionaries - .forEach(dictionaryKey => { - const dictionary = require(`dictionary-${dictionaryKey}`); - if (dictionary) { - dictionary((err, dict) => { - // ipcRenderer.send('dictionaryLoaded', {nspellDictionary: nspell(dict)}); - const nspell = require('nspell'); - // ipcRenderer.send('dictionaryLoaded', {dictionary: dict}); - ipcRenderer.invoke('dictionaryLoaded', {dictionary: dict}); - // getDictionaries().activeDictionaries = [...getDictionaries().activeDictionaries, nspell(dict)]; - }); - } - }); -}; - -module.exports = load; diff --git a/src/tab-manager/index.js b/src/tab-manager/index.js index e20dc88c..6c3047a0 100644 --- a/src/tab-manager/index.js +++ b/src/tab-manager/index.js @@ -34,16 +34,17 @@ const handlePageFaviconUpdated = (browserView, ipcSender, tabId) => async (e, fa } }; -const handleContextMenu = browserView => (event, params) => { +const handleContextMenu = browserView => async (event, params) => { const {webContents} = browserView; const menu = new Menu(); - const spellingSuggestions = contextMenuHandler(event, params, webContents); + const {x, y} = params; + menu.popup({x, y}); + const spellingSuggestions = await contextMenuHandler(event, params, webContents); if (spellingSuggestions.length > 0) { spellingSuggestions.forEach(mi => menu.append(mi)); menu.append(new MenuItem({type: 'separator'})); } menu.append(new MenuItem({label: 'DevTools', click: () => webContents.openDevTools()})); - const {x, y} = params; menu.popup({x, y}); }; From 6ad8263356a58d72892f601e19c1e68ceebd1fb6 Mon Sep 17 00:00:00 2001 From: manusa Date: Wed, 1 Jan 2020 17:13:55 +0100 Subject: [PATCH 3/4] wip: Cleared malfunctioning dictionaries --- src/constants/index.js | 3 ++- src/main/__tests__/index.test.js | 1 + src/main/index.js | 3 ++- .../dictionary.renderer/dictionary.worker.js | 7 +++++- src/spell-check/index.js | 25 ++++++++++--------- .../browser-spell-check.js | 3 +-- src/tab-manager/index.js | 4 +-- src/tab-manager/preload.js | 2 +- 8 files changed, 28 insertions(+), 20 deletions(-) rename src/{spell-check => tab-manager}/browser-spell-check.js (74%) diff --git a/src/constants/index.js b/src/constants/index.js index 16cc2677..7c18ce92 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -1,7 +1,8 @@ const APP_EVENTS = { activateTab: 'activateTab', addTabs: 'addTabs', - dictionaryGetMispelled: 'dictionaryGetMispelled', + dictionaryLoaded: 'dictionaryLoaded', + dictionaryGetMisspelled: 'dictionaryGetMisspelled', settingsCancel: 'settingsCancel', settingsOpenDialog: 'settingsOpenDialog', settingsSave: 'settingsSave', diff --git a/src/main/__tests__/index.test.js b/src/main/__tests__/index.test.js index 2ddbbec8..278d43af 100644 --- a/src/main/__tests__/index.test.js +++ b/src/main/__tests__/index.test.js @@ -30,6 +30,7 @@ describe('Main module test suite', () => { updateSettings: jest.fn() })); settingsModule = require('../../settings'); + jest.mock('../../spell-check'); jest.mock('../../tab-manager', () => ({})); main = require('../'); }); diff --git a/src/main/index.js b/src/main/index.js index 4860e563..68722f7d 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -1,4 +1,4 @@ -const {BrowserWindow, ipcMain: ipc} = require('electron'); +const {BrowserWindow, app, ipcMain: ipc} = require('electron'); const {APP_EVENTS} = require('../constants'); const {TABS_CONTAINER_HEIGHT, initTabContainer} = require('../chrome-tabs'); const {loadSettings, updateSettings, updateTabs, openSettingsDialog} = require('../settings'); @@ -83,6 +83,7 @@ const init = () => { const [currentWidth, currentHeight] = mainWindow.getSize(); updateSettings({width: currentWidth, height: currentHeight}); }); + mainWindow.on('closed', () => app.quit()); initTabListener(); initSettingsListener(); tabContainer = initTabContainer(mainWindow); diff --git a/src/spell-check/dictionary.renderer/dictionary.worker.js b/src/spell-check/dictionary.renderer/dictionary.worker.js index 1216a52e..78f0bab0 100644 --- a/src/spell-check/dictionary.renderer/dictionary.worker.js +++ b/src/spell-check/dictionary.renderer/dictionary.worker.js @@ -26,7 +26,12 @@ window.reloadDictionaries = () => { const {enabledDictionaries} = loadSettings(); enabledDictionaries .forEach(dictionaryKey => { - const dictionary = require(`dictionary-${dictionaryKey}`); + let dictionary; + try { + dictionary = require(`dictionary-${dictionaryKey}`); + } catch (error) { + // Error is ignored + } if (dictionary) { dictionary((err, dict) => { dictionaries.push(nspell(dict)); diff --git a/src/spell-check/index.js b/src/spell-check/index.js index 63158797..3a25547a 100644 --- a/src/spell-check/index.js +++ b/src/spell-check/index.js @@ -3,29 +3,32 @@ const {APP_EVENTS} = require('../constants'); const {loadSettings} = require('../settings'); const AVAILABLE_DICTIONARIES = [ - 'ca', // - 'ca-valencia', // + // 'ca', // + // 'ca-valencia', // 'de', 'en-gb', 'en-us', 'es', - 'eu', // + // 'eu', // 'fr', - 'it', + // 'it', // 'ka', - 'lt', + // 'lt', // 'nl', - 'pl', - 'pt', - 'pt-br', + // 'pl', // + // 'pt', // + // 'pt-br', // 'ru', 'sv', 'tr', - 'uk' + // 'uk' // ]; let fakeRendererWorker; +const handleGetMisspelled = async (event, words) => + await fakeRendererWorker.webContents.executeJavaScript(`getMisspelled(${JSON.stringify(words)})`); + const getEnabledDictionaries = () => loadSettings().enabledDictionaries; const loadDictionaries = () => { @@ -34,9 +37,9 @@ const loadDictionaries = () => { show: false, webPreferences: {nodeIntegration: true} }); + ipcMain.handle(APP_EVENTS.dictionaryGetMisspelled, handleGetMisspelled); } fakeRendererWorker.loadURL(`file://${__dirname}/dictionary.renderer/index.html`); - fakeRendererWorker.webContents.openDevTools(); }; const contextMenuHandler = async (event, {misspelledWord}, webContents) => { @@ -56,8 +59,6 @@ const contextMenuHandler = async (event, {misspelledWord}, webContents) => { return ret; }; -ipcMain.handle(APP_EVENTS.dictionaryGetMispelled, async (event, words) => - await fakeRendererWorker.webContents.executeJavaScript(`getMisspelled(${JSON.stringify(words)})`)); module.exports = { AVAILABLE_DICTIONARIES, contextMenuHandler, getEnabledDictionaries, loadDictionaries diff --git a/src/spell-check/browser-spell-check.js b/src/tab-manager/browser-spell-check.js similarity index 74% rename from src/spell-check/browser-spell-check.js rename to src/tab-manager/browser-spell-check.js index 567b4b4e..cf2ee2af 100644 --- a/src/spell-check/browser-spell-check.js +++ b/src/tab-manager/browser-spell-check.js @@ -4,8 +4,7 @@ const {ipcRenderer} = require('electron'); const initSpellChecker = webFrame => { webFrame.setSpellCheckProvider(navigator.language, { async spellCheck (words, callback) { - const misspelled = await ipcRenderer.invoke(APP_EVENTS.dictionaryGetMispelled, words); - // const misspelled = await words.filter(async word => await window.isMisspelled(word)); + const misspelled = await ipcRenderer.invoke(APP_EVENTS.dictionaryGetMisspelled, words); callback(misspelled); } }); diff --git a/src/tab-manager/index.js b/src/tab-manager/index.js index 6c3047a0..001c3279 100644 --- a/src/tab-manager/index.js +++ b/src/tab-manager/index.js @@ -37,14 +37,14 @@ const handlePageFaviconUpdated = (browserView, ipcSender, tabId) => async (e, fa const handleContextMenu = browserView => async (event, params) => { const {webContents} = browserView; const menu = new Menu(); - const {x, y} = params; - menu.popup({x, y}); + const spellingSuggestions = await contextMenuHandler(event, params, webContents); if (spellingSuggestions.length > 0) { spellingSuggestions.forEach(mi => menu.append(mi)); menu.append(new MenuItem({type: 'separator'})); } menu.append(new MenuItem({label: 'DevTools', click: () => webContents.openDevTools()})); + const {x, y} = params; menu.popup({x, y}); }; diff --git a/src/tab-manager/preload.js b/src/tab-manager/preload.js index 93d4a2da..d2711d3a 100644 --- a/src/tab-manager/preload.js +++ b/src/tab-manager/preload.js @@ -1,5 +1,5 @@ require('../main/preload'); const {webFrame} = require('electron'); -const {initSpellChecker} = require('../spell-check/browser-spell-check'); +const {initSpellChecker} = require('./browser-spell-check'); initSpellChecker(webFrame); From 5c8dd00a5f628b3a00f657ca66644344d7e532d3 Mon Sep 17 00:00:00 2001 From: manusa Date: Wed, 1 Jan 2020 17:39:20 +0100 Subject: [PATCH 4/4] feat: Spell-checker in async non-blocking thread, support for many languages --- README.md | 3 +- package-lock.json | 101 +++++------------- package.json | 4 +- src/constants/index.js | 1 - src/spell-check/__tests__/index.test.js | 19 ++++ src/spell-check/dictionary.renderer/README.md | 4 + .../dictionary.renderer/dictionary.worker.js | 10 +- src/spell-check/index.js | 18 ++-- src/spell-check/load-dictionary.worker.js | 7 -- 9 files changed, 66 insertions(+), 101 deletions(-) create mode 100644 src/spell-check/dictionary.renderer/README.md delete mode 100644 src/spell-check/load-dictionary.worker.js diff --git a/README.md b/README.md index 2ab66960..96eca499 100644 --- a/README.md +++ b/README.md @@ -52,4 +52,5 @@ spell checking for free. - [Chrome tabs](https://github.com/adamschwartz/chrome-tabs#readme) - [Draggabilly](https://github.com/desandro/draggabilly) - [Font Awesome Free](https://github.com/FortAwesome/Font-Awesome) -- [nspell](https://github.com/wooorm/nspell) +- [Nodehun](https://github.com/Wulf/nodehun/) +- [Woorm's dictionary repo](https://github.com/wooorm/dictionaries) diff --git a/package-lock.json b/package-lock.json index de7f75cc..de9d523f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -581,11 +581,6 @@ "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", "dev": true }, - "adm-zip": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.13.tgz", - "integrity": "sha512-fERNJX8sOXfel6qCBCMPvZLzENBEhZTzKqg6vrOW5pvoEaQuJhRU4ndTAh6lHOxn1I6jnz2NHra56ZODM751uw==" - }, "ajv": { "version": "6.10.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", @@ -806,7 +801,8 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true }, "base": { "version": "0.11.2", @@ -878,11 +874,6 @@ "tweetnacl": "^0.14.3" } }, - "binarysearch": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/binarysearch/-/binarysearch-0.2.4.tgz", - "integrity": "sha1-Ru8+A/1FKekyhmLmjkAyjnoL8qw=" - }, "bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -909,6 +900,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1268,7 +1260,8 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true }, "concat-stream": { "version": "1.6.2", @@ -1379,11 +1372,6 @@ "integrity": "sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs=", "dev": true }, - "damerau-levenshtein": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.5.tgz", - "integrity": "sha512-CBCRqFnpu715iPmw1KrdOrzRqbdFwQTwAWyyyYS42+iAgHCuXZ+/TdMgQkUENPomxEz9z1BEzuQU2Xw0kUuAgA==" - }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -1794,14 +1782,6 @@ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz", "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==" }, - "errno": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", - "requires": { - "prr": "~1.0.1" - } - }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -2416,7 +2396,8 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true }, "fsevents": { "version": "1.2.11", @@ -3188,6 +3169,7 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -3437,6 +3419,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -3673,11 +3656,6 @@ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" - }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -4566,6 +4544,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -4659,6 +4638,11 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "node-addon-api": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.0.tgz", + "integrity": "sha512-ASCL5U13as7HhOExbT6OlWJJUV/lLzL2voOSP1UVehpRD8FbSrSDjfScK/KwAvVTI5AS6r4VwbOMlIqtvRidnA==" + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -4692,6 +4676,14 @@ } } }, + "nodehun": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/nodehun/-/nodehun-3.0.1.tgz", + "integrity": "sha512-JiS+6ky3uPAQ9xDzb1nq54shrTzGZJmB1J6sZoevzrp2ENy2tXqMpfV+9axNq5KsDTnX5s7rG+Jeyt81/CputA==", + "requires": { + "node-addon-api": "*" + } + }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -5000,7 +4992,8 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true }, "path-key": { "version": "2.0.1", @@ -5138,11 +5131,6 @@ "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", "optional": true }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" - }, "psl": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.6.0.tgz", @@ -5404,6 +5392,7 @@ "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, "requires": { "glob": "^7.1.3" } @@ -5581,36 +5570,6 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true }, - "simple-spellchecker": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/simple-spellchecker/-/simple-spellchecker-1.0.0.tgz", - "integrity": "sha512-gOQdOyXV/SoQJ2MfNj2eEbumnDIMWa6ET01xvK+fbHfZMjMC08ZNGNqa6WZmtoLxzK0l3IRkxALRaOo9Pg0jPg==", - "requires": { - "adm-zip": "^0.4.13", - "binarysearch": "^0.2.4", - "damerau-levenshtein": "^1.0.5", - "strip-bom": "^2.0.0", - "tmp": "^0.1.0" - }, - "dependencies": { - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "requires": { - "is-utf8": "^0.2.0" - } - }, - "tmp": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", - "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==", - "requires": { - "rimraf": "^2.6.3" - } - } - } - }, "sisteransi": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.4.tgz", @@ -6518,14 +6477,6 @@ "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", "dev": true }, - "worker-farm": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", - "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", - "requires": { - "errno": "~0.1.7" - } - }, "wrap-ansi": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", diff --git a/package.json b/package.json index f308cf5e..52ce586e 100644 --- a/package.json +++ b/package.json @@ -73,8 +73,6 @@ "dictionary-uk": "^2.1.1", "draggabilly": "^2.2.0", "electron": "^7.1.6", - "nspell": "^2.1.2", - "simple-spellchecker": "^1.0.0", - "worker-farm": "^1.7.0" + "nodehun": "^3.0.1" } } diff --git a/src/constants/index.js b/src/constants/index.js index 7c18ce92..78a99e8b 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -1,7 +1,6 @@ const APP_EVENTS = { activateTab: 'activateTab', addTabs: 'addTabs', - dictionaryLoaded: 'dictionaryLoaded', dictionaryGetMisspelled: 'dictionaryGetMisspelled', settingsCancel: 'settingsCancel', settingsOpenDialog: 'settingsOpenDialog', diff --git a/src/spell-check/__tests__/index.test.js b/src/spell-check/__tests__/index.test.js index efa86427..0a71c82c 100644 --- a/src/spell-check/__tests__/index.test.js +++ b/src/spell-check/__tests__/index.test.js @@ -1,11 +1,23 @@ describe('Spell-check module test suite', () => { + let mockBrowserWindow; + let mockIpc; let mockSettings; let spellCheck; beforeEach(() => { + mockBrowserWindow = { + loadURL: jest.fn() + }; + mockIpc = { + handle: jest.fn() + }; mockSettings = { enabledDictionaries: [] }; jest.resetModules(); + jest.mock('electron', () => ({ + BrowserWindow: jest.fn(() => mockBrowserWindow), + ipcMain: mockIpc + })); jest.mock('../../settings', () => ({ loadSettings: jest.fn(() => mockSettings) })); @@ -19,4 +31,11 @@ describe('Spell-check module test suite', () => { // Then expect(result).toEqual(['13-37']); }); + test('loadDictionaries', () => { + // When + spellCheck.loadDictionaries(); + // Then + expect(mockBrowserWindow.loadURL).toHaveBeenCalledWith(expect.stringMatching(/\/dictionary.renderer\/index.html$/)); + expect(mockIpc.handle).toHaveBeenCalledWith('dictionaryGetMisspelled', expect.any(Function)); + }); }); diff --git a/src/spell-check/dictionary.renderer/README.md b/src/spell-check/dictionary.renderer/README.md new file mode 100644 index 00000000..2cfd2539 --- /dev/null +++ b/src/spell-check/dictionary.renderer/README.md @@ -0,0 +1,4 @@ +Fake BrowserWindow that runs long processes (such as loading dictionaries) +in an Electron renderer thread. + +Communication with the thread happens via events and remote JavaScript execution. diff --git a/src/spell-check/dictionary.renderer/dictionary.worker.js b/src/spell-check/dictionary.renderer/dictionary.worker.js index 78f0bab0..6e0d12da 100644 --- a/src/spell-check/dictionary.renderer/dictionary.worker.js +++ b/src/spell-check/dictionary.renderer/dictionary.worker.js @@ -1,10 +1,10 @@ -const nspell = require('nspell'); +const Nodehun = require('nodehun'); const {loadSettings} = require('../../settings'); const dictionaries = []; const isMisspelled = word => - dictionaries.every(dictionary => !dictionary.correct(word)); + dictionaries.every(dictionary => !dictionary.spellSync(word)); window.getMisspelled = words => { if (dictionaries.length === 0) { @@ -15,7 +15,7 @@ window.getMisspelled = words => { window.getSuggestions = word => { const ret = new Set(); - dictionaries.map(dictionary => dictionary.suggest(word)) + dictionaries.map(dictionary => dictionary.suggestSync(word)) .flatMap(suggestions => suggestions) .forEach(suggestion => ret.add(suggestion)); return Array.from(ret.values()).sort().slice(0, 10); @@ -33,8 +33,8 @@ window.reloadDictionaries = () => { // Error is ignored } if (dictionary) { - dictionary((err, dict) => { - dictionaries.push(nspell(dict)); + dictionary((err, {aff, dic}) => { + dictionaries.push(new Nodehun(aff, dic)); }); } }); diff --git a/src/spell-check/index.js b/src/spell-check/index.js index 3a25547a..e9a4c69e 100644 --- a/src/spell-check/index.js +++ b/src/spell-check/index.js @@ -3,25 +3,25 @@ const {APP_EVENTS} = require('../constants'); const {loadSettings} = require('../settings'); const AVAILABLE_DICTIONARIES = [ - // 'ca', // - // 'ca-valencia', // + 'ca', + 'ca-valencia', 'de', 'en-gb', 'en-us', 'es', - // 'eu', // + 'eu', 'fr', - // 'it', // + 'it', 'ka', - // 'lt', // + 'lt', 'nl', - // 'pl', // - // 'pt', // - // 'pt-br', // + 'pl', + 'pt', + 'pt-br', 'ru', 'sv', 'tr', - // 'uk' // + 'uk' ]; let fakeRendererWorker; diff --git a/src/spell-check/load-dictionary.worker.js b/src/spell-check/load-dictionary.worker.js deleted file mode 100644 index 1fabb4af..00000000 --- a/src/spell-check/load-dictionary.worker.js +++ /dev/null @@ -1,7 +0,0 @@ -const loadDictionaryWorker = (dictionaryKey, callback) => { - require(`dictionary-${dictionaryKey}`)((err, dict) => { - callback(dict.aff, dict.dic); - }); -}; - -module.exports = loadDictionaryWorker;