<commit_before>import {Disposable, Observable, Scheduler, SerialDisposable, Subject} from 'rx';
import {Spellchecker} from '@paulcbetts/spellchecker';
import {getInstalledKeyboardLanguages} from 'keyboard-layout';
import pify from 'pify';
import {spawn} from 'spawn-rx';

import DictionarySync from './dictionary-sync';
import {normalizeLanguageCode} from './utility';

const d = require('debug')('electron-spellchecker:spell-check-handler');
let cld = null;

const validLangCode = /[a-z]{2}[_][A-Z]{2}/;

function fromEventCapture(element, name) {
  return Observable.create((subj) => {
    const handler = function(...args) {
      if (args.length > 1) {
        subj.onNext(args);
      } else {
        subj.onNext(args[0] || true);
      }
    };

    element.addEventListener(name, handler, true);
    return Disposable.create(() => element.removeEventListener(name, handler, true));
  });
}

export default class SpellCheckHandler {
  constructor(dictionarySync=null) {
    this.dictionarySync = dictionarySync || new DictionarySync();
    this.switchToLanguage = new Subject();
    this.currentSpellchecker = null;
    this.currentSpellcheckerLanguage = null;
    
    this.disp = new SerialDisposable();
    
    if (process.platform === 'darwin') {
      // NB: OS X does automatic language detection, we're gonna trust it
      this.currentSpellchecker = new Spellchecker();
      return;
    }
    
    this.switchToLanguage
      .flatMap((lang) => 
        this.dictionarySync.loadDictionaryForLanguage(lang)
          .map((dict) => ({ language: lang, dictionary: dict }))
          .catch((e) => {
            d(`Failed to load dictionary ${lang}: ${e.message}`);
            return Observable.just(null);
          }))
      .where((x) => x !== null)
      .observeOn(Scheduler.default)
      .subscribe((ld) => {
        this.currentSpellchecker = new Spellchecker();
        this.currentSpellchecker.setLanguage(ld.language, ld.dictionary);
      });
  }
  
  attachToInput(inputText=null) {
    cld = cld || pify(require('cld'));
    
    let input = inputText || addEventListener(document.body, 'input')
      .flatMap((e) => {
        if (!e.target || !e.target.value) return Observable.empty();
        return Observable.just(e.target.value);
      });
      
    let disp = input
      .flatMap((text) => 
        Observable.fromPromise(cld.listen(text))
          .catch(() => Observable.empty()))
      .subscribe((lang) => console.log(`Language is ${lang}`));
      
    this.disp.setDisposable(disp);
    return disp;
  }
  
  getLikelyLocaleForLanguage(language) {
    //if (this.likely)
  }
  
  static async buildLikelyLocaleTable() {
    let localeList = [];
    
    if (process.platform === 'linux') {
      let locales = await spawn('locale', ['-a'])
        .catch(() => Observable.just(null))
        .reduce((acc,x) => { acc.push(...x.split('\n')); return acc; }, [])
        .toPromise();
        
      d(`Raw Locale list: ${JSON.stringify(locales)}`);
        
      localeList = locales.reduce((acc, x) => {
        let m = x.match(validLangCode);
        if (!m) return acc;
        
        acc.push(m[0]);
        return acc;
      }, []);
    }
    
    if (process.platform === 'win32') {
      localeList = getInstalledKeyboardLanguages();
    }
      
    d(`Filtered Locale list: ${JSON.stringify(localeList)}`);
    
    // Some distros like Ubuntu make locale -a useless by dumping
    // every possible locale for the language into the list :-/
    let counts = localeList.reduce((acc,x) => {
      let k = x.substring(0,2);
      acc[k] = acc[k] || [];
      acc[k].push(x);
      
      return acc;
    }, {});
    
    d(`Counts: ${JSON.stringify(counts)}`);
    
    let ret = Object.keys(counts).reduce((acc, x) => {
      if (counts[x].length > 1) return acc;
      
      d(`Setting ${x}`);
      acc[x] = normalizeLanguageCode(counts[x][0]);
      
      return acc;
    }, {});
    
    // NB: LANG has a Special Place In Our Hearts
    if (process.platform === 'linux' && process.env.LANG) {
      let m = process.env.LANG.match(validLangCode);
      if (!m) return ret;<commit_msg>On OS X, use the installed dictionary list as our hint list<commit_after>import {Disposable, Observable, Scheduler, SerialDisposable, Subject} from 'rx';
import {Spellchecker} from '@paulcbetts/spellchecker';
import {getInstalledKeyboardLanguages} from 'keyboard-layout';
import pify from 'pify';
import {spawn} from 'spawn-rx';

import DictionarySync from './dictionary-sync';
import {normalizeLanguageCode} from './utility';

const d = require('debug')('electron-spellchecker:spell-check-handler');
let cld = null;
let fallbackLocaleTable = null;

const validLangCode = /[a-z]{2}[_][A-Z]{2}/;

function fromEventCapture(element, name) {
  return Observable.create((subj) => {
    const handler = function(...args) {
      if (args.length > 1) {
        subj.onNext(args);
      } else {
        subj.onNext(args[0] || true);
      }
    };

    element.addEventListener(name, handler, true);
    return Disposable.create(() => element.removeEventListener(name, handler, true));
  });
}

export default class SpellCheckHandler {
  constructor(dictionarySync=null) {
    this.dictionarySync = dictionarySync || new DictionarySync();
    this.switchToLanguage = new Subject();
    this.currentSpellchecker = null;
    this.currentSpellcheckerLanguage = null;
    
    this.disp = new SerialDisposable();
    
    if (process.platform === 'darwin') {
      // NB: OS X does automatic language detection, we're gonna trust it
      this.currentSpellchecker = new Spellchecker();
      return;
    }
    
    this.switchToLanguage
      .flatMap((lang) => 
        this.dictionarySync.loadDictionaryForLanguage(lang)
          .map((dict) => ({ language: lang, dictionary: dict }))
          .catch((e) => {
            d(`Failed to load dictionary ${lang}: ${e.message}`);
            return Observable.just(null);
          }))
      .where((x) => x !== null)
      .observeOn(Scheduler.default)
      .subscribe((ld) => {
        this.currentSpellchecker = new Spellchecker();
        this.currentSpellchecker.setLanguage(ld.language, ld.dictionary);
      });
  }
  
  attachToInput(inputText=null) {
    cld = cld || pify(require('cld'));
    
    let input = inputText || addEventListener(document.body, 'input')
      .flatMap((e) => {
        if (!e.target || !e.target.value) return Observable.empty();
        return Observable.just(e.target.value);
      });
      
    let disp = input
      .flatMap((text) => 
        Observable.fromPromise(cld.listen(text))
          .catch(() => Observable.empty()))
      .subscribe((lang) => console.log(`Language is ${lang}`));
      
    this.disp.setDisposable(disp);
    return disp;
  }
  
  getLikelyLocaleForLanguage(language) {
    //if (this.likely)
  }
  
  static async buildLikelyLocaleTable() {
    let localeList = [];
    
    if (process.platform === 'linux') {
      let locales = await spawn('locale', ['-a'])
        .catch(() => Observable.just(null))
        .reduce((acc,x) => { acc.push(...x.split('\n')); return acc; }, [])
        .toPromise();
        
      d(`Raw Locale list: ${JSON.stringify(locales)}`);
        
      localeList = locales.reduce((acc, x) => {
        let m = x.match(validLangCode);
        if (!m) return acc;
        
        acc.push(m[0]);
        return acc;
      }, []);
    }
    
    if (process.platform === 'win32') {
      localeList = getInstalledKeyboardLanguages();
    }

    if (process.platform === 'darwin') {
      fallbackLocaleTable = fallbackLocaleTable || require('./fallback-locales');

      // NB: OS X will return lists that are half just a language, half
      // language + locale, like ['en', 'pt_BR', 'ko']
      localeList = this.spellchecker.getAvailableDictionaries()
        .map((x => {
          if (x.length === 2) return fallbackLocaleTable[x];
          return normalizeLanguageCode(x);
        }))
    }
      
    d(`Filtered Locale list: ${JSON.stringify(localeList)}`);
    
    // Some distros like Ubuntu make locale -a useless by dumping
    // every possible locale for the language into the list :-/
    let counts = localeList.reduce((acc,x) => {
      let k = x.substring(0,2);
      acc[k] = acc[k] || [];
      acc[k].push(x);
      
      return acc;
    }, {});
    
    d(`Counts: ${JSON.stringify(counts)}`);
    
    let ret = Object.keys(counts).reduce((acc, x) => {
      if (counts[x].length > 1) return acc;
      
      d(`Setting ${x}`);
      acc[x] = normalizeLanguageCode(counts[x][0]);
      
      return acc;
    }, {});
    
    // NB: LANG has a Special Place In Our Hearts
    if (process.platform === 'linux' && process.env.LANG) {
      let m = process.env.LANG.match(validLangCode);
      if (!m) return ret;
{'fraction_non_alphanumeric': 0.15633850685397077, 'fraction_numerical': 0.0026056417808995127, 'mean_word_length': 2.629934210526316, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 40, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7361120', 'n_tokens_mistral': 2750, 'n_tokens_neox': 2724, 'n_words': 600}