|  |  |  | /* global require, process, _ */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* eslint-disable strict */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const electron = require('electron'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const osLocale = require('os-locale'); | 
					
						
							|  |  |  | const os = require('os'); | 
					
						
							|  |  |  | const semver = require('semver'); | 
					
						
							|  |  |  | const spellchecker = require('spellchecker'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const { remote, webFrame } = electron; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // `remote.require` since `Menu` is a main-process module.
 | 
					
						
							|  |  |  | const buildEditorContextMenu = remote.require('electron-editor-context-menu'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const EN_VARIANT = /^en/; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Prevent the spellchecker from showing contractions as errors.
 | 
					
						
							|  |  |  | const ENGLISH_SKIP_WORDS = [ | 
					
						
							|  |  |  |   'ain', | 
					
						
							|  |  |  |   'couldn', | 
					
						
							|  |  |  |   'didn', | 
					
						
							|  |  |  |   'doesn', | 
					
						
							|  |  |  |   'hadn', | 
					
						
							|  |  |  |   'hasn', | 
					
						
							|  |  |  |   'mightn', | 
					
						
							|  |  |  |   'mustn', | 
					
						
							|  |  |  |   'needn', | 
					
						
							|  |  |  |   'oughtn', | 
					
						
							|  |  |  |   'shan', | 
					
						
							|  |  |  |   'shouldn', | 
					
						
							|  |  |  |   'wasn', | 
					
						
							|  |  |  |   'weren', | 
					
						
							|  |  |  |   '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
 | 
					
						
							|  |  |  |     const location = process.env.HUNSPELL_DICTIONARIES || '/usr/share/hunspell'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     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') { | 
					
						
							|  |  |  |     const location = process.env.HUNSPELL_DICTIONARIES; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     console.log( | 
					
						
							|  |  |  |       'Detected Windows 7 or below. Setting up spell-check with locale', | 
					
						
							|  |  |  |       locale, | 
					
						
							|  |  |  |       'and dictionary location', | 
					
						
							|  |  |  |       location | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     spellchecker.setDictionary(locale, location); | 
					
						
							|  |  |  |   } else { | 
					
						
							|  |  |  |     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.
 | 
					
						
							|  |  |  | const 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); | 
					
						
							|  |  |  | } 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); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const simpleChecker = { | 
					
						
							|  |  |  |   spellCheck(text) { | 
					
						
							|  |  |  |     return !this.isMisspelled(text); | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  |   isMisspelled(text) { | 
					
						
							|  |  |  |     const 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(text) { | 
					
						
							|  |  |  |     return spellchecker.getCorrectionsForMisspelling(text); | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  |   add(text) { | 
					
						
							|  |  |  |     spellchecker.add(text); | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | window.spellChecker = simpleChecker; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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', e => { | 
					
						
							|  |  |  |   // Only show the context menu in text editors.
 | 
					
						
							|  |  |  |   if (!e.target.closest('textarea, input, [contenteditable="true"]')) { | 
					
						
							|  |  |  |     return; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const selectedText = window.getSelection().toString(); | 
					
						
							|  |  |  |   const isMisspelled = selectedText && simpleChecker.isMisspelled(selectedText); | 
					
						
							|  |  |  |   const spellingSuggestions = | 
					
						
							|  |  |  |     isMisspelled && simpleChecker.getSuggestions(selectedText).slice(0, 5); | 
					
						
							|  |  |  |   const menu = buildEditorContextMenu({ | 
					
						
							|  |  |  |     isMisspelled, | 
					
						
							|  |  |  |     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(() => { | 
					
						
							|  |  |  |     menu.popup(remote.getCurrentWindow()); | 
					
						
							|  |  |  |   }, 30); | 
					
						
							|  |  |  | }); |