text: // Use strict mode (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode)
  'use strict';
  
  // Requires
  var Typo = require('typo-js');
  
  // Create function
  function CodeMirrorSpellChecker(options) {
      // Initialize
      options = options || {};
  
      // Verify
      if (typeof options.codeMirrorInstance !== 'function' || typeof options.codeMirrorInstance.defineMode !== 'function') {
          console.log('CodeMirror Spell Checker: You must provide an instance of CodeMirror via the option `codeMirrorInstance`');
          return;
      }
  
      // Because some browsers don't support this functionality yet
      if (!String.prototype.includes) {
          String.prototype.includes = function () {
              'use strict';
              return String.prototype.indexOf.apply(this, arguments) !== -1;
          };
      }
  
      // Define the new mode
      options.codeMirrorInstance.defineMode('spell-checker', function (config) {
          // Load AFF/DIC data
          if (!CodeMirrorSpellChecker.aff_loading) {
              CodeMirrorSpellChecker.aff_loading = true;
              var xhr_aff = new XMLHttpRequest();
              xhr_aff.open('GET', 'https://cdn.jsdelivr.net/codemirror.spell-checker/latest/en_US.aff', true);
              xhr_aff.onload = function () {
                  if (xhr_aff.readyState === 4 && xhr_aff.status === 200) {
                      CodeMirrorSpellChecker.aff_data = xhr_aff.responseText;
                      CodeMirrorSpellChecker.num_loaded++;
  
                      if (CodeMirrorSpellChecker.num_loaded == 2) {
                          CodeMirrorSpellChecker.typo = new Typo('en_US', CodeMirrorSpellChecker.aff_data, CodeMirrorSpellChecker.dic_data, {
                              platform: 'any'
                          });
                      }
                  }
              };
              xhr_aff.send(null);
          }
  
          if (!CodeMirrorSpellChecker.dic_loading) {
              CodeMirrorSpellChecker.dic_loading = true;
              var xhr_dic = new XMLHttpRequest();
              xhr_dic.open('GET', 'https://cdn.jsdelivr.net/codemirror.spell-checker/latest/en_US.dic', true);
              xhr_dic.onload = function () {
                  if (xhr_dic.readyState === 4 && xhr_dic.status === 200) {
                      CodeMirrorSpellChecker.dic_data = xhr_dic.responseText;
                      CodeMirrorSpellChecker.num_loaded++;
  
                      if (CodeMirrorSpellChecker.num_loaded == 2) {
                          CodeMirrorSpellChecker.typo = new Typo('en_US', CodeMirrorSpellChecker.aff_data, CodeMirrorSpellChecker.dic_data, {
                              platform: 'any'
                          });
                      }
                  }
              };
              xhr_dic.send(null);
          }
  
          // Define what separates a word
          var rx_word = /^[^!\"#$%&()*+,\-./:;<=>?@\[\\\]^_`{|}~\s]+/;
          var rx_link = /(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9]\.[^\s]{2,})/;
  
          var rx_ignore_num = /^[0-9]+$/;
  
          var customWords = [];
  
          if (options.customWords && options.customWords instanceof Array) {
              customWords = options.customWords;
          }
  
          // Create the overlay and such
          var overlay = {
              token: function token(stream) {
                  var word = stream.match(rx_word, true);
                  var hasLink = stream.string.match(rx_link);
                  var link = hasLink && hasLink[0];
  
                  var ignore = link && link.match(word);
  
                  if (word && !ignore) {
                      word = word[0]; // regex match body
                      if (!word.match(rx_ignore_num) && CodeMirrorSpellChecker.typo && !CodeMirrorSpellChecker.typo.check(word) && ! ~customWords.indexOf(word)) return 'spell-error'; // CSS class: cm-spell-error
                  } else {
                          stream.next(); // skip non-word character
                      }
  
                  return null;
              }
          };
  
          var mode = options.codeMirrorInstance.getMode(config, config.backdrop || 'text/plain');
  
          return options.codeMirrorInstance.overlayMode(mode, overlay, true);
      });
  }
  
  // Initialize data globally to reduce memory consumption
  CodeMirrorSpellChecker.num_loaded = 0;
  CodeMirrorSpellChecker.aff_loading = false;
  CodeMirrorSpellChecker.dic_loading = false;
  CodeMirrorSpellChecker.aff_data = '';
  CodeMirrorSpellChecker.dic_data = '';
  CodeMirrorSpellChecker.typo;
  
  // Export
  module.exports = CodeMirrorSpellChecker;