You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			181 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			JavaScript
		
	
			
		
		
	
	
			181 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			JavaScript
		
	
| /* 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';
 | |
| 
 | |
|     window.log.info(
 | |
|       'Detected Linux. Setting up spell check with locale',
 | |
|       locale,
 | |
|       'and dictionary location',
 | |
|       location
 | |
|     );
 | |
|     spellchecker.setDictionary(locale, location);
 | |
|   } else {
 | |
|     window.log.info(
 | |
|       '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;
 | |
| 
 | |
|     window.log.info(
 | |
|       'Detected Windows 7 or below. Setting up spell-check with locale',
 | |
|       locale,
 | |
|       'and dictionary location',
 | |
|       location
 | |
|     );
 | |
|     spellchecker.setDictionary(locale, location);
 | |
|   } else {
 | |
|     window.log.info(
 | |
|       '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
 | |
|   window.log.info(
 | |
|     '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);
 | |
|   },
 | |
| };
 | |
| 
 | |
| const dummyChecker = {
 | |
|   spellCheck() {
 | |
|     return true;
 | |
|   },
 | |
|   isMisspelled() {
 | |
|     return false;
 | |
|   },
 | |
|   getSuggestions() {
 | |
|     return [];
 | |
|   },
 | |
|   add() {
 | |
|     // nothing
 | |
|   },
 | |
| };
 | |
| 
 | |
| window.spellChecker = simpleChecker;
 | |
| window.disableSpellCheck = () => {
 | |
|   window.removeEventListener('contextmenu', spellCheckHandler);
 | |
|   webFrame.setSpellCheckProvider('en-US', false, dummyChecker);
 | |
| };
 | |
| 
 | |
| window.enableSpellCheck = () => {
 | |
|   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', spellCheckHandler);
 | |
| };
 | |
| 
 | |
| const spellCheckHandler = 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);
 | |
| };
 |