CodeMirror с проверкой орфографии
Я хотел бы использовать функциональность CodeMirror (например, linenumbering, wrapping, search и т.д.) для обычного текста без особая потребность в подсветке кода, но вместо этого с проверкой орфографии Google Chrome или другим обычным языком (особенно английским) проверка орфографии активирована (мне не нужно, чтобы она работала в других браузерах). Как я могу это сделать? Можно ли написать дополнение к текстовому режиму, позволяющее проверять орфографию?
Ответы
Ответ 1
Я действительно интегрировал typo.js с CodeMirror при кодировании NoTex.ch; вы можете посмотреть на него здесь CodeMirror.rest.js; Мне нужен способ проверки проверки разметки reStructuredText, и поскольку я использую превосходные возможности подсветки синтаксиса CodeMirror, это было довольно просто сделать.
Вы можете проверить код по предоставленной ссылке, но я подведу итог, что я сделал:
-
Инициализировать библиотеку typo.js; см. также блог автора/документацию:
var typo = new Typo ("en_US", AFF_DATA, DIC_DATA, {
platform: 'any'
});
-
Определите регулярное выражение для разделителей слов:
var rx_word = "!\"#$%&()*+,-./:;<=>[email protected][\\\\\\]^_`{|}~";
-
Определите режим наложения для 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);
});
-
Использовать наложение с помощью CodeMirror; см. руководство пользователя, чтобы выяснить, как именно вы это делаете. Я сделал это в своем коде, чтобы вы могли проверить его там, но я рекомендую руководство пользователя.
-
Определите класс CSS:
.CodeMirror .cm-spell-error {
background: url(images/red-wavy-underline.gif) bottom repeat-x;
}
Этот подход отлично подходит для немецкого, английского и испанского языков. С французским словарем typo.js, похоже, есть некоторые (акцент) проблемы и языки, такие как иврит, венгерский и итальянский языки - где число аффиксов длинное или словарь довольно обширен - он не работает действительно, так как typo.js при его текущей реализации слишком много памяти и слишком медленно.
С немецким (и испанским) typo.js может блокировать виртуальную машину JavaScript на несколько сотен миллисекунд (но только во время инициализации!), поэтому вам может потребоваться рассмотреть фоновые потоки с веб-рабочими HTML5 (см. CodeMirror.typo.worker. js для примера). Кроме того, typo.js, похоже, не поддерживает Unicode (из-за ограничений JavaScript): по крайней мере, мне не удалось заставить его работать с нелатинскими языками, такими как русский, греческий, хинди и т.д.
Я не реорганизовал описанное решение в прекрасный отдельный проект, кроме (теперь довольно большого) NoTex.ch, но я мог бы сделать это довольно скоро; до этого вы должны исправить свое решение на основе приведенного выше описания или намеченного кода. Надеюсь, это поможет.
Ответ 2
Это рабочая версия ответа hsk81. Он использует режим наложения кода CodeMirror и ищет любое слово внутри кавычек, html-тегов и т.д. У него есть образец typo.check, который следует заменить чем-то вроде Typo.js. Он подчеркивает неизвестные слова красной квадратной линией.
Это было протестировано с использованием IPython %% html cell.
<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>
Ответ 3
CodeMirror не основан на текстовом поле HTML, поэтому не может использовать встроенную проверку орфографии
Вы можете реализовать собственную проверку орфографии для CodeMirror с чем-то вроде typo.js
Я не верю, что кто-то еще это сделал.
Ответ 4
Некоторое время назад я написал короткую кнопку проверки правописания. Нужно переписать, чтобы быть честным, но я был очень новым для JavaScript. Но принципы все там.
https://github.com/jameswestgate/SpellAsYouType
Ответ 5
Я создал проверку орфографии с предложениями/исправлениями опечаток:
https://gist.github.com/kofifus/4b2f79cadc871a29439d919692099406
demo: 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>⌛</option>'; // hourglass
} else {
items.forEach(s => options += '<option value="' + s + '">' + s + '</option>');
options+='<option value="##ignoreall##">ignore 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;
}
Надеюсь, это поможет!