2012-09-09 54 views
15

我希望爲純文本使用CodeMirror的功能(例如,編號,包裝,搜索等),而不需要代碼高亮顯示,而是使用Google Chrome拼寫檢查器或其他自然語言(特別是英文)拼寫檢查激活(我不需要在其他瀏覽器上工作)。我怎樣才能做到這一點?是否可以寫一個純文本模式插件來啓用拼寫檢查?帶拼寫檢查器的CodeMirror

回答

26

我實際上CodeMirror集成typo.js而編碼NoTex.ch;你可以在這裏看看它CodeMirror.rest.js;我需要一種方法來檢查標記拼寫,並且由於我使用了CodeMirror出色的語法高亮功能,因此非常簡單。

您可以在提供的鏈接檢查代碼,但我會總結一下,我做了什麼:

  1. 初始化typo.js庫;又見作者的博客/文件:

    var typo = new Typo ("en_US", AFF_DATA, DIC_DATA, { 
        platform: 'any' 
    }); 
    
  2. 定義正則表達式爲你的單詞分隔符:

    var rx_word = "!\"#$%&()*+,-./:;<=>[email protected][\\\\\\]^_`{|}~"; 
    
  3. 定義的覆蓋模式CodeMirror:

    CodeMirror.defineMode ("myoverlay", function (config, parserConfig) { 
        var overlay = { 
         token: function (stream, state) { 
    
          if (stream.match (rx_word) && 
           typo && !typo.check (stream.current())) 
    
           return "spell-error"; //CSS class: cm-spell-error 
    
          while (stream.next() != null) { 
           if (stream.match (rx_word, false)) return null; 
          } 
    
          return null; 
         } 
        }; 
    
        var mode = CodeMirror.getMode (
         config, parserConfig.backdrop || "text/x-myoverlay" 
        ); 
    
        return CodeMirror.overlayMode (mode, overlay); 
    }); 
    
  4. 使用與CodeMirror疊加;請參閱用戶手冊以瞭解您如何完成此操作。我已經在我的代碼中完成了它,所以你也可以在那裏查看它,但我推薦用戶手冊。

  5. 定義CSS類:

    .CodeMirror .cm-spell-error { 
        background: url(images/red-wavy-underline.gif) bottom repeat-x; 
    } 
    

這種做法的偉大工程的德語,英語和西班牙語。用法語字典typo.js似乎有一些(口音)問題,以及像希伯來語,匈牙利語和意大利語這樣的語言 - 詞綴數很多或字典相當廣泛 - 它不能真正起作用,因爲在目前的實現中,typo.js使用的內存太多,速度太慢。

德國(和西班牙語)typo.js可以阻止JavaScript的VM幾百毫秒(但只能在初始化過程中!),所以你可能要考慮與HTML5網頁工人後臺線程(見CodeMirror。 typo.worker。例如js)。進一步typo.js似乎不支持Unicode(由於JavaScript限制):至少,我沒有設法讓它與俄文,希臘文,印地文等非拉丁語言一起工作。

我'除了(現在相當大)NoTex.ch之外,我沒有將所描述的解決方案重構成一個很好的獨立項目,但我很快就可以做到這一點;直到那時你必須根據上面的描述或暗示的代碼修補你自己的解決方案。我希望這有幫助。

+0

太好了。我還沒有完全嘗試過,但我相信它。 – sawa

+3

這太棒了!您可能還想使用新增的「addOverlay」功能(http://codemirror.net/doc/manual.html#addOverlay),該功能提供了一種更有效且侵害性更小的添加實用程序疊加層的方式。 – Marijn

+1

在這個答案中的鏈接不再解決,被移動的文件,它已經被殺死了? –

1

CodeMirror不是基於一個HTML textarea的,所以你can't use the built-in spell check

你可以用一些實施CodeMirror自己的拼寫檢查類似typo.js

我不相信任何人還沒有這樣做。

+0

好的,忘掉我以前的評論。你提出的建議看起來更一般,我會研究它。 – sawa

+0

CodeMirror面向代碼,而不是散文。所以,如果你不需要語法突出顯示,它可能不是正確的工具。 你可能想看看[MarkItUp(http://markitup.jaysalvat.com/home/),其中有一個[概念拼寫檢查的證明(http://spellchecker.jquery.badsyntax.co.uk/ markitup.html) – Doug

+1

您的回答有助於尋找正確的方向,但hsk81提供了更詳細的答案,因此我將對勾移至該答案。謝謝。 – sawa

3

這是hsk81的答案的工作版本。它使用CodeMirror的覆蓋模式,並在引號,html標籤等內查找任何單詞。它有一個樣本typo.check,應該用Typo.js替換。它用紅色的波浪線突出了未知的單詞。

這是使用IPython的%% html單元格進行測試的。

<style> 
.CodeMirror .cm-spell-error { 
    background: url("https://raw.githubusercontent.com/jwulf/typojs-project/master/public/images/red-wavy-underline.gif") bottom repeat-x; 
} 
</style> 

<h2>Overlay Parser Demo</h2> 
<form><textarea id="code" name="code"> 
</textarea></form> 

<script> 
var typo = { check: function(current) { 
       var dictionary = {"apple": 1, "banana":1, "can't":1, "this":1, "that":1, "the":1}; 
       return current.toLowerCase() in dictionary; 
      } 
} 

CodeMirror.defineMode("spell-check", function(config, parserConfig) { 
    var rx_word = new RegExp("[^\!\"\#\$\%\&\(\)\*\+\,\-\.\/\:\;\<\=\>\?\@\[\\\]\^\_\`\{\|\}\~\ ]"); 
    var spellOverlay = { 
     token: function (stream, state) { 
      var ch; 
      if (stream.match(rx_word)) { 
      while ((ch = stream.peek()) != null) { 
        if (!ch.match(rx_word)) { 
        break; 
        } 
        stream.next(); 
      } 
      if (!typo.check(stream.current())) 
       return "spell-error"; 
      return null; 
      } 
      while (stream.next() != null && !stream.match(rx_word, false)) {} 
      return null; 
     } 
    }; 

    return CodeMirror.overlayMode(CodeMirror.getMode(config, parserConfig.backdrop || "text/html"), spellOverlay); 
}); 

var editor = CodeMirror.fromTextArea(document.getElementById("code"), {mode: "spell-check"}); 
</script> 
1

我創建了錯別字建議/更正拼寫檢查:

https://gist.github.com/kofifus/4b2f79cadc871a29439d919692099406

演示:https://plnkr.co/edit/0y1wCHXx3k3mZaHFOpHT

下面是代碼的相關部分:

首先,我許下諾言加載詞典。我用typo.js的字典,加載可能需要一段時間,如果他們沒有當地舉辦的,所以最好是儘快啓動加載作爲登錄之前,你的開始/ CM初始化等:

function loadTypo() { 
    // hosting the dicts on your local domain will give much faster results 
    const affDict='https://rawgit.com/ropensci/hunspell/master/inst/dict/en_US.aff'; 
    const dicDict='https://rawgit.com/ropensci/hunspell/master/inst/dict/en_US.dic'; 

    return new Promise(function(resolve, reject) { 
     var xhr_aff = new XMLHttpRequest(); 
     xhr_aff.open('GET', affDict, true); 
     xhr_aff.onload = function() { 
      if (xhr_aff.readyState === 4 && xhr_aff.status === 200) { 
       //console.log('aff loaded'); 
       var xhr_dic = new XMLHttpRequest(); 
       xhr_dic.open('GET', dicDict, true); 
       xhr_dic.onload = function() { 
        if (xhr_dic.readyState === 4 && xhr_dic.status === 200) { 
         //console.log('dic loaded'); 
         resolve(new Typo('en_US', xhr_aff.responseText, xhr_dic.responseText, { platform: 'any' })); 
        } else { 
         console.log('failed loading aff'); 
         reject(); 
        } 
       }; 
       //console.log('loading dic'); 
       xhr_dic.send(null); 
      } else { 
       console.log('failed loading aff'); 
       reject(); 
      } 
     }; 
     //console.log('loading aff'); 
     xhr_aff.send(null); 
    }); 
} 

其次,我添加一個覆蓋檢測和標記錯別字這樣的:

cm.spellcheckOverlay={ 
    token: function(stream) { 
     var ch = stream.peek(); 
     var word = ""; 

     if (rx_word.includes(ch) || ch==='\uE000' || ch==='\uE001') { 
      stream.next(); 
      return null; 
     } 

     while ((ch = stream.peek()) && !rx_word.includes(ch)) { 
      word += ch; 
      stream.next(); 
     } 

     if (! /[a-z]/i.test(word)) return null; // no letters 
     if (startSpellCheck.ignoreDict[word]) return null; 
     if (!typo.check(word)) return "spell-error"; // CSS class: cm-spell-error 
    } 
} 
cm.addOverlay(cm.spellcheckOverlay); 

三我用一個列表框中顯示的建議和修復錯誤:

function getSuggestionBox(typo) { 
    function sboxShow(cm, sbox, items, x, y) { 
     let selwidget=sbox.children[0]; 

     let options=''; 
     if (items==='hourglass') { 
      options='<option>&#8987;</option>'; // hourglass 
     } else { 
      items.forEach(s => options += '<option value="' + s + '">' + s + '</option>'); 
      options+='<option value="##ignoreall##">ignore&nbsp;all</option>'; 
     } 
     selwidget.innerHTML=options; 
     selwidget.disabled=(items==='hourglass'); 
     selwidget.size = selwidget.length; 
     selwidget.value=-1; 

     // position widget inside cm 
     let cmrect=cm.getWrapperElement().getBoundingClientRect(); 
     sbox.style.left=x+'px'; 
     sbox.style.top=(y-sbox.offsetHeight/2)+'px'; 
     let widgetRect = sbox.getBoundingClientRect(); 
     if (widgetRect.top<cmrect.top) sbox.style.top=(cmrect.top+2)+'px'; 
     if (widgetRect.right>cmrect.right) sbox.style.left=(cmrect.right-widgetRect.width-2)+'px'; 
     if (widgetRect.bottom>cmrect.bottom) sbox.style.top=(cmrect.bottom-widgetRect.height-2)+'px'; 
    } 

    function sboxHide(sbox) { 
     sbox.style.top=sbox.style.left='-1000px'; 
    } 

    // create suggestions widget 
    let sbox=document.getElementById('suggestBox'); 
    if (!sbox) { 
     sbox=document.createElement('div'); 
     sbox.style.zIndex=100000; 
     sbox.id='suggestBox'; 
     sbox.style.position='fixed'; 
     sboxHide(sbox); 

     let selwidget=document.createElement('select'); 
     selwidget.multiple='yes'; 
     sbox.appendChild(selwidget); 

     sbox.suggest=((cm, e) => { // e is the event from cm contextmenu event 
      if (!e.target.classList.contains('cm-spell-error')) return false; // not on typo 

      let token=e.target.innerText; 
      if (!token) return false; // sanity 

      // save cm instance, token, token coordinates in sbox 
      sbox.codeMirror=cm; 
      sbox.token=token; 
      let tokenRect = e.target.getBoundingClientRect(); 
      let start=cm.coordsChar({left: tokenRect.left+1, top: tokenRect.top+1}); 
      let end=cm.coordsChar({left: tokenRect.right-1, top: tokenRect.top+1}); 
      sbox.cmpos={ line: start.line, start: start.ch, end: end.ch}; 

      // show hourglass 
      sboxShow(cm, sbox, 'hourglass', e.pageX, e.pageY); 

      // let the ui refresh with the hourglass & show suggestions 
      setTimeout(() => { 
       sboxShow(cm, sbox, typo.suggest(token), e.pageX, e.pageY); // typo.suggest takes a while 
      }, 100); 

      e.preventDefault(); 
      return false; 
     }); 

     sbox.onmouseleave=(e => { 
      sboxHide(sbox) 
     }); 

     selwidget.onchange=(e => { 
      sboxHide(sbox) 
      let cm=sbox.codeMirror, correction=e.target.value; 
      if (correction=='##ignoreall##') { 
       startSpellCheck.ignoreDict[sbox.token]=true; 
       cm.setOption('maxHighlightLength', (--cm.options.maxHighlightLength) +1); // ugly hack to rerun overlays 
      } else { 
       cm.replaceRange(correction, { line: sbox.cmpos.line, ch: sbox.cmpos.start}, { line: sbox.cmpos.line, ch: sbox.cmpos.end}); 
       cm.focus(); 
       cm.setCursor({line: sbox.cmpos.line, ch: sbox.cmpos.start+correction.length}); 
      } 
     }); 

     document.body.appendChild(sbox); 
    } 

    return sbox; 
} 

希望這有助於!