spellchecker

This commit is contained in:
Zira project 2019-12-29 17:50:28 +05:00
parent 2c48c2f992
commit d87e68cad6
25 changed files with 488 additions and 44 deletions

View File

@ -64,7 +64,10 @@ SOURCES += \
src/helpdialog.cpp \
src/popup.cpp \
src/gitbrowser.cpp \
src/annotation.cpp
src/annotation.cpp \
src/spellcheckerinterface.cpp \
src/plugininterface.cpp \
src/spellwords.cpp
HEADERS += \
include/mainwindow.h \
@ -106,7 +109,10 @@ HEADERS += \
include/helpdialog.h \
include/popup.h \
include/gitbrowser.h \
include/annotation.h
include/annotation.h \
include/spellcheckerinterface.h \
include/plugininterface.h \
include/spellwords.h
FORMS += \
ui/mainwindow.ui \
@ -123,4 +129,5 @@ RESOURCES += \
qrc/syntax.qrc \
qrc/image.qrc \
qrc/help.qrc \
qrc/style.qrc
qrc/style.qrc \
qrc/spell.qrc

View File

@ -11,12 +11,14 @@
#include <QRegularExpression>
#include <QLabel>
#include <QHash>
#include "spellcheckerinterface.h"
#include "settings.h"
#include "highlight.h"
#include "completepopup.h"
#include "highlightwords.h"
#include "completewords.h"
#include "helpwords.h"
#include "spellwords.h"
#include "tooltip.h"
#include "parsephp.h"
#include "parsejs.h"
@ -27,7 +29,7 @@ class Editor : public QTextEdit
{
Q_OBJECT
public:
Editor(Settings * settings, HighlightWords * highlightWords, CompleteWords * completeWords, HelpWords * helpWords, QWidget * parent = nullptr);
Editor(SpellCheckerInterface * spellChecker, Settings * settings, HighlightWords * highlightWords, CompleteWords * completeWords, HelpWords * helpWords, SpellWords * spellWords, QWidget * parent = nullptr);
~Editor() override;
void init();
void lineNumberAreaPaintEvent(QPaintEvent *event);
@ -155,6 +157,9 @@ protected:
QString findNextWordNonSpaceAtCursor(QTextCursor & curs, std::string mode);
QString completeClassNamePHPAtCursor(QTextCursor & curs, QString prevWord, QString nsName);
void scrollToMiddle(QTextCursor cursor, int line);
void initSpellChecker();
void suggestWords(QStringList words, int cursorTextPos);
bool isKnownWord(QString word);
public slots:
void save(QString name = "");
void back();
@ -192,10 +197,13 @@ private slots:
void duplicateLine();
void deleteLine();
void reloadRequested();
void spellCheck(bool suggest = true, bool forceRehighlight = true);
private:
SpellCheckerInterface * spellChecker;
CompleteWords * CW;
HighlightWords * HW;
HelpWords * HPW;
SpellWords * SW;
int tabIndex;
std::string tabWidthStr;
std::string tabTypeStr;
@ -283,6 +291,7 @@ private:
QRegularExpression functionWordExpr;
QRegularExpression classNameExpr;
QRegularExpression colorExpr;
QRegularExpression spellWordExpr;
ParsePHP parserPHP;
ParsePHP::ParseResult parseResultPHP;
@ -311,6 +320,7 @@ private:
bool focused;
bool cursorPositionChangeLocked;
bool scrollBarValueChangeLocked;
bool textChangeLocked;
bool modified;
int lastModifiedMsec;
bool warningDisplayed;
@ -341,6 +351,10 @@ private:
int gitAnnotationLastLineNumber;
bool annotationsEnabled;
int parseResultChangedDelay;
bool spellCheckerEnabled;
bool spellLocked;
QVector<int> spellBlocksQueue;
int spellCheckInitBlockNumber;
signals:
void ready(int index);
void statusBarText(int index, QString text);

View File

@ -14,7 +14,7 @@ class EditorTabs : public QObject
{
Q_OBJECT
public:
EditorTabs(QTabWidget * widget, Settings * settings, HighlightWords * highlightWords, CompleteWords * completeWords, HelpWords * helpWords);
EditorTabs(SpellCheckerInterface * spellChecker, QTabWidget * widget, Settings * settings, HighlightWords * highlightWords, CompleteWords * completeWords, HelpWords * helpWords, SpellWords * spellWords);
void createTab(QString filepath, bool initHighlight = true);
bool closeWindowAllowed();
Editor * getActiveEditor();
@ -36,11 +36,13 @@ protected:
void fileBrowserFolderRenamed(QString oldpath, QString newpath);
private:
Editor * editor;
SpellCheckerInterface * spellChecker;
QTabWidget * tabWidget;
Settings * settings;
HighlightWords * highlightWords;
CompleteWords * completeWords;
HelpWords * helpWords;
SpellWords * spellWords;
bool blockSig;
signals:

View File

@ -68,6 +68,7 @@ public:
void setHighlightVarsMode(bool varsMode);
void setFirstRunMode(bool runMode);
bool isDirty();
std::unordered_map<std::string, int> unusedVars;
std::unordered_map<std::string, int>::iterator unusedVarsIterator;
protected:
@ -117,7 +118,7 @@ protected:
void updateState(const QChar & c, int pos, int & pState);
void openBlockDataLists();
void closeBlockDataLists(int textSize);
void highlightUnderline();
void highlightSpell();
private:
QTextDocument * doc;
QVector<QTextCharFormat> formatChanges;
@ -156,6 +157,8 @@ private:
QVector<QString> specialWords;
QVector<int> specialWordsPos;
QColor spellColor;
bool enabled;
QTextBlock cBlock;
HighlightData * blockData;
@ -262,8 +265,6 @@ private:
int parensJS;
int parensPHP;
bool cssMediaScope;
int underlineStart;
int underlineEnd;
QString nsNamePHP;
QList<int> nsScopeChainPHP;
QString nsChainPHP;

View File

@ -54,8 +54,6 @@ public:
QVector<int> stateStarts;
QVector<int> stateEnds;
QVector<int> stateIds;
int underlineStart;
int underlineEnd;
bool hasMarkPoint;
QString nsNamePHP;
QList<int> nsScopeChainPHP;
@ -124,6 +122,8 @@ public:
QString keywordPHPprevStringPrevChar;
QString keywordJSprevString;
QString keywordJSprevStringPrevChar;
QVector<int> spellStarts;
QVector<int> spellLengths;
bool wantUpdate;
};

View File

@ -14,6 +14,7 @@
#include "settings.h"
#include "highlightwords.h"
#include "completewords.h"
#include "spellwords.h"
#include "parserworker.h"
#include "filebrowser.h"
#include "editortabs.h"
@ -22,6 +23,7 @@
#include "helpwords.h"
#include "project.h"
#include "git.h"
#include "spellcheckerinterface.h"
#include "quickaccess.h"
#include "popup.h"
#include "types.h"
@ -73,6 +75,8 @@ protected:
void runServersCommand(QString command, QString pwd, QString description);
void compileSass(QString src, QString dst);
void applyThemeColors();
QObject * loadPlugin(QString name);
bool loadSpellChecker();
public slots:
void setStatusBarText(QString text);
void editorShowLine(int line);
@ -201,6 +205,7 @@ private:
HighlightWords * highlightWords;
CompleteWords * completeWords;
HelpWords * helpWords;
SpellWords * spellWords;
QProgressBar * progressBar;
ParserWorker * parserWorker;
QThread parserThread;
@ -210,6 +215,7 @@ private:
EditorTabs * editorTabs;
Project * project;
Git * git;
SpellCheckerInterface * spellChecker;
QString outputMsgErrorTpl;
QString outputMsgWarningTpl;
int outputMsgCount;

16
include/plugininterface.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef PLUGININTERFACE_H
#define PLUGININTERFACE_H
#include <QObject>
extern const QString PLUGINS_DIR;
class PluginInterface : public QObject
{
public:
PluginInterface(QObject *parent = nullptr);
virtual ~PluginInterface();
virtual QString getDir() = 0;
};
#endif // PLUGININTERFACE_H

View File

@ -0,0 +1,20 @@
#ifndef SPELLCHECKERINTERFACE_H
#define SPELLCHECKERINTERFACE_H
#include "plugininterface.h"
extern const QString SPELLCHECKER_PLUGIN_NAME;
class SpellCheckerInterface : public PluginInterface
{
public:
SpellCheckerInterface(QObject *parent = nullptr);
virtual ~SpellCheckerInterface();
virtual bool check(QString & word) = 0;
virtual QStringList suggest(QString & word) = 0;
};
#define SpellCheckerInterface_iid "com.github.ziracms.editor.SpellCheckerInterface"
Q_DECLARE_INTERFACE(SpellCheckerInterface, SpellCheckerInterface_iid)
#endif // SPELLCHECKERINTERFACE_H

22
include/spellwords.h Normal file
View File

@ -0,0 +1,22 @@
#ifndef SPELLWORDS_H
#define SPELLWORDS_H
#include <QObject>
#include <unordered_map>
class SpellWords : public QObject
{
Q_OBJECT
public:
explicit SpellWords();
void reload();
void reset();
std::unordered_map<std::string, std::string> words;
std::unordered_map<std::string, std::string>::iterator wordsIterator;
protected:
void loadWords();
public slots:
void load();
};
#endif // SPELLWORDS_H

18
qrc/resources/spell/words Normal file
View File

@ -0,0 +1,18 @@
https
localhost
www
github
destructor
sendmail
ascii
filesystem
gif
png
jpeg
txt
php
htaccess
js
css
cms
ziracms

5
qrc/spell.qrc Normal file
View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/spell">
<file alias="words">resources/spell/words</file>
</qresource>
</RCC>

View File

@ -11,7 +11,7 @@
#include <QTextStream>
#include <QTimer>
const int LOAD_DELAY = 250;
const int LOAD_DELAY = 250; // should not be less then PROJECT_LOAD_DELAY
CompleteWords::CompleteWords(HighlightWords * hWords)
{

View File

@ -42,6 +42,9 @@ const std::string LF = "lf";
const int INTERVAL_SCROLL_HIGHLIGHT_MILLISECONDS = 500;
const int INTERVAL_TEXT_CHANGED_MILLISECONDS = 200;
const int INTERVAL_CURSOR_POS_CHANGED_MILLISECONDS = 200;
const int INTERVAL_SPELL_CHECK_MILLISECONDS = 500;
const int SPELLCHECKER_INIT_BLOCKS_COUNT = 10;
const int TOOLTIP_OFFSET = 20;
const int TOOLTIP_SCREEN_MARGIN = 10;
@ -63,7 +66,7 @@ const QString TOOLTIP_DELIMITER = "[:OR:]";
const QString TOOLTIP_PAGER_TPL = " | <b>[<u>%1</u>/%2]</b>";
const QString TOOLTIP_COLOR_TPL = "<span style=\"background:%1;\">&nbsp;&nbsp;&nbsp;</span>";
Editor::Editor(Settings * settings, HighlightWords * highlightWords, CompleteWords * completeWords, HelpWords * helpWords, QWidget * parent) : QTextEdit(parent)
Editor::Editor(SpellCheckerInterface * spellChecker, Settings * settings, HighlightWords * highlightWords, CompleteWords * completeWords, HelpWords * helpWords, SpellWords * spellWords, QWidget * parent) : QTextEdit(parent), spellChecker(spellChecker)
{
setMinimumSize(0, 0);
setMaximumSize(16777215, 16777215);
@ -222,6 +225,7 @@ Editor::Editor(Settings * settings, HighlightWords * highlightWords, CompleteWor
functionWordExpr = QRegularExpression("(?:^|[^a-zA-Z0-9_\\$]+)function(?:[^a-zA-Z0-9_]+|$)");
classNameExpr = QRegularExpression("([a-zA-Z0-9_\\\\]+)[\\s]*[(]");
colorExpr = QRegularExpression("^[#](?:[a-fA-F0-9][a-fA-F0-9][a-fA-F0-9])(?:[a-fA-F0-9][a-fA-F0-9][a-fA-F0-9])?(?:[a-fA-F0-9][a-fA-F0-9])?$");
spellWordExpr = QRegularExpression("([\\p{L}0-9_'\\$]+)");
// some features is enabled only in experimental mode
experimentalMode = false;
@ -354,6 +358,7 @@ Editor::Editor(Settings * settings, HighlightWords * highlightWords, CompleteWor
CW = completeWords;
HW = highlightWords;
HPW = helpWords;
SW = spellWords;
parsePHPEnabled = false;
std::string parsePHPEnabledStr = settings->get("parser_enable_parse_php");
@ -379,6 +384,10 @@ Editor::Editor(Settings * settings, HighlightWords * highlightWords, CompleteWor
if (parseResultChangedDelayMS >= 1000) parseResultChangedDelay = parseResultChangedDelayMS;
else parseResultChangedDelay = 5000;
spellCheckerEnabled = false;
std::string spellCheckerEnabledStr = settings->get("spellchecker_enabled");
if (spellCheckerEnabledStr == "yes") spellCheckerEnabled = true;
// cursor is not set to default sometimes
horizontalScrollBar()->setCursor(Qt::ArrowCursor);
verticalScrollBar()->setCursor(Qt::ArrowCursor);
@ -434,6 +443,7 @@ void Editor::reset()
is_ready = false;
cursorPositionChangeLocked = false;
scrollBarValueChangeLocked = false;
textChangeLocked = false;
modeOnKeyPress = "";
lastKeyPressed = -1;
tooltipSavedText = "";
@ -459,6 +469,10 @@ void Editor::reset()
gitAnnotationLastLineNumber = -1;
gitAnnotations.clear();
gitDiffLines.clear();
spellLocked = false;
spellBlocksQueue.clear();
errorsExtraSelections.clear();
spellCheckInitBlockNumber = 0;
}
void Editor::highlightProgressChanged(int percent)
@ -540,6 +554,26 @@ void Editor::initHighlighter()
cursorPositionChangedDelayed();
emit ready(tabIndex);
if (highlight->getModeType() == MODE_MIXED) highlightUnusedVars(false);
initSpellChecker();
}
void Editor::initSpellChecker()
{
if (!spellCheckerEnabled || spellChecker == nullptr) return;
int totalBlocks = document()->blockCount();
QString progressStr = tr("Spell check")+": ";
while(spellCheckInitBlockNumber < totalBlocks) {
for (int i=0; i<SPELLCHECKER_INIT_BLOCKS_COUNT; i++) {
spellBlocksQueue.append(i+spellCheckInitBlockNumber);
}
spellCheckInitBlockNumber += SPELLCHECKER_INIT_BLOCKS_COUNT;
spellCheck(false, false);
int percent = (spellCheckInitBlockNumber * 100) / totalBlocks;
if (percent > 100) percent = 100;
emit statusBarText(tabIndex, progressStr+Helper::intToStr(percent)+"%");
QCoreApplication::processEvents();
}
emit statusBarText(tabIndex, "");
}
std::string Editor::getModeType()
@ -1066,7 +1100,13 @@ bool Editor::event(QEvent *e)
}
} else {
clearTextHoverFormat();
//hideTooltip();
if (prevText.size() > 0 && nextText.size() > 0) {
QString nonAlphaText = prevText+nextText;
QRegularExpressionMatch m = colorExpr.match(nonAlphaText);
if (m.capturedStart()==0 && QColor::isValidColor(nonAlphaText)) {
showTooltip(& curs, TOOLTIP_COLOR_TPL.arg(nonAlphaText)+" "+nonAlphaText);
}
}
}
return true;
}
@ -1129,6 +1169,19 @@ void Editor::highlightErrorLine(int line)
highlightExtras();
}
void Editor::suggestWords(QStringList words, int cursorTextPos)
{
completePopup->clearItems();
for (QString word : words) {
completePopup->addItem(word, word);
if (completePopup->count() >= completePopup->limit()) break;
}
if (completePopup->count()>0) {
completePopup->setTextStartPos(cursorTextPos);
showCompletePopup();
}
}
void Editor::updateSizes()
{
updateViewportMargins();
@ -2087,9 +2140,10 @@ void Editor::textChanged()
if (!is_ready || isReadOnly()) return;
Qt::KeyboardModifiers modifiers = QApplication::queryKeyboardModifiers();
bool ctrl = modifiers & Qt::ControlModifier;
if (lastKeyPressed != Qt::Key_Backspace && lastKeyPressed != Qt::Key_Delete && lastKeyPressed != Qt::Key_Return && lastKeyPressed != Qt::Key_Tab && lastKeyPressed != Qt::Key_Space && !ctrl) {
if (!textChangeLocked && lastKeyPressed != Qt::Key_Backspace && lastKeyPressed != Qt::Key_Delete && lastKeyPressed != Qt::Key_Return && lastKeyPressed != Qt::Key_Tab && lastKeyPressed != Qt::Key_Space && !ctrl) {
textChangeLocked = true;
QTimer::singleShot(INTERVAL_TEXT_CHANGED_MILLISECONDS, this, SLOT(textChangedDelayed()));
} else {
} else if (!textChangeLocked) {
hideCompletePopup();
}
bool _modified = modified;
@ -2107,10 +2161,20 @@ void Editor::textChanged()
parseLocked = true;
QTimer::singleShot(parseResultChangedDelay, this, SLOT(parseResultChanged()));
}
// spell check
if (spellBlocksQueue.size() == 0 || (spellBlocksQueue.last() != textCursor().block().blockNumber())) {
spellBlocksQueue.append(textCursor().block().blockNumber());
}
if (!spellLocked && spellCheckerEnabled && spellChecker != nullptr) {
spellLocked = true;
QTimer::singleShot(INTERVAL_SPELL_CHECK_MILLISECONDS, this, SLOT(spellCheck()));
}
}
void Editor::textChangedDelayed()
{
textChangeLocked = false;
// complete popup
QTextCursor curs = textCursor();
if (curs.selectedText().size()!=0) return;
@ -2118,16 +2182,20 @@ void Editor::textChangedDelayed()
QString blockText = block.text();
int total = blockText.size();
int pos = curs.positionInBlock();
if (highlight->isStateOpen(&block, pos)) {
hideCompletePopup();
return;
}
// if (highlight->isStateOpen(&block, pos)) {
// hideCompletePopup();
// return;
// }
std::string mode = highlight->findModeAtCursor(&block, pos);
int state = highlight->findStateAtCursor(&block, pos);
if (mode != MODE_HTML && state != STATE_NONE) return;
if (mode == MODE_HTML && state != STATE_TAG) return;
if (mode == MODE_UNKNOWN) return;
QChar prevChar = '\0', nextChar = '\0';
if (pos > 0 && curs.selectedText().size()==0) prevChar = blockText[pos - 1];
if (pos < total && curs.selectedText().size()==0) nextChar = blockText[pos];
// text till cursor
QString cursorText = "", prevText = "";
std::string mode = highlight->findModeAtCursor(&block, pos);
QChar cursorTextPrevChar = '\0';
int cursorTextPos = pos;
for (int i=pos; i>0; i--) {
@ -2158,6 +2226,136 @@ void Editor::textChangedDelayed()
}
}
bool Editor::isKnownWord(QString word)
{
bool known = false;
SW->wordsIterator = SW->words.find(word.toLower().toStdString());
if (SW->wordsIterator != SW->words.end()) {
known = true;
}
if (!known) {
HW->phpwordsIterator = HW->phpwords.find(word.toLower().toStdString());
if (HW->phpwordsIterator != HW->phpwords.end()) {
known = true;
}
}
if (!known) {
HW->phpwordsCSIterator = HW->phpwordsCS.find(word.toStdString());
if (HW->phpwordsCSIterator != HW->phpwordsCS.end()) {
known = true;
}
}
if (!known) {
HW->jswordsCSIterator = HW->jswordsCS.find(word.toStdString());
if (HW->jswordsCSIterator != HW->jswordsCS.end()) {
known = true;
}
}
if (!known) {
HW->csswordsIterator = HW->csswords.find(word.toLower().toStdString());
if (HW->csswordsIterator != HW->csswords.end()) {
known = true;
}
}
if (!known) {
HW->htmlwordsIterator = HW->htmlwords.find(word.toLower().toStdString());
if (HW->htmlwordsIterator != HW->htmlwords.end()) {
known = true;
}
}
return known;
}
void Editor::spellCheck(bool suggest, bool forceRehighlight)
{
spellLocked = false;
if (!spellCheckerEnabled || spellChecker == nullptr) return;
QTextCursor cursor = textCursor();
int pos = cursor.positionInBlock();
QString blockText = cursor.block().text();
// text till cursor
QString cursorText = "";
int cursorTextPos = pos;
if (suggest) {
for (int i=pos; i>0; i--) {
QChar c = blockText[i-1];
if (c.isLetter() || c=="_") cursorText = c + cursorText;
else break;
cursorTextPos = i-1;
}
}
QRegularExpressionMatch m;
for (int i=0; i<spellBlocksQueue.size(); i++) {
bool misspelled = false;
int blockNumber = spellBlocksQueue.at(i);
if (blockNumber == cursor.block().blockNumber() + 1) {
cursor.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor);
} else {
cursor.movePosition(QTextCursor::Start);
if (blockNumber > 0) cursor.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor, blockNumber);
}
QTextBlock block = cursor.block();
if (block.blockNumber() != spellBlocksQueue.at(i)) continue;
HighlightData * blockData = dynamic_cast<HighlightData *>(block.userData());
if (blockData == nullptr) continue;
blockData->spellStarts.clear();
blockData->spellLengths.clear();
QString blockText = cursor.block().text();
if (blockText.trimmed().size() == 0) continue;
int offset = 0;
do {
m = spellWordExpr.match(blockText, offset);
if (m.capturedStart() >= 0) {
offset = m.capturedStart() + m.capturedLength();
int start = m.capturedStart(1);
int length = m.capturedLength(1);
QString word = blockText.mid(start, length);
bool hasLetter = false;
for (int i=0; i<word.size(); i++) {
QChar c = word[i];
if (c.isLetter()) hasLetter = true;
}
if (!hasLetter) continue;
if (word.size() > 0 && word[0] == "$") continue;
if (word.size()>1 && word[0]=='\'' && word[word.size()-1]=='\'') {
word = word.mid(1,word.size()-2);
start += 1;
length -= 2;
}
if (word.size() < 2) continue;
std::string mode = highlight->findModeAtCursor(&block, start);
int state = highlight->findStateAtCursor(&block, start);
//if ((mode == MODE_PHP || mode == MODE_JS || mode == MODE_CSS) && state == STATE_NONE) continue;
if (mode == MODE_PHP && state != STATE_COMMENT_ML_PHP) continue;
if (mode == MODE_JS && state != STATE_COMMENT_ML_JS) continue;
if (mode == MODE_CSS && state != STATE_COMMENT_ML_CSS) continue;
if (mode == MODE_HTML && state != STATE_NONE) continue;
bool doSuggest = suggest;
if (word.size() < 4) doSuggest = false;
if (mode != MODE_HTML && mode != MODE_UNKNOWN) doSuggest = false;
if (lastKeyPressed == Qt::Key_Backspace || lastKeyPressed == Qt::Key_Delete) doSuggest = false;
if (!isKnownWord(word) && !spellChecker->check(word)) {
misspelled = true;
blockData->spellStarts.append(start);
blockData->spellLengths.append(length);
if (doSuggest && word == cursorText) {
hideCompletePopup();
QStringList suggestions = spellChecker->suggest(word);
suggestWords(suggestions, cursorTextPos);
}
}
}
} while(m.capturedStart() >= 0);
block.setUserData(blockData);
if (forceRehighlight || misspelled) {
blockSignals(true);
highlight->rehighlightBlock(block);
blockSignals(false);
}
}
spellBlocksQueue.clear();
}
QChar Editor::findPrevCharNonSpaceAtCursos(QTextCursor & curs)
{
QChar prevChar = '\0';
@ -3410,6 +3608,7 @@ void Editor::completePopupSelected(QString text, QString data)
QString blockText = block.text();
int total = blockText.size();
std::string mode = highlight->findModeAtCursor(& block, pos);
int state = highlight->findStateAtCursor(& block, pos);
QChar nextChar = '\0';
if (pos < total) nextChar = blockText[pos];
if (cursorTextPos >= 0 && cursorTextPos <= pos) {
@ -3496,7 +3695,11 @@ void Editor::completePopupSelected(QString text, QString data)
if (cursorTextPos < pos) {
curs.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor, pos - cursorTextPos);
}
blockSignals(true);
bool blockS = true;
if (mode != MODE_HTML && state != STATE_NONE) blockS = false;
if (mode == MODE_HTML && state != STATE_TAG) blockS = false;
if (mode == MODE_UNKNOWN) blockS = false;
if (blockS) blockSignals(true);
curs.beginEditBlock();
curs.insertText(text);
// show tooltip
@ -3523,7 +3726,7 @@ void Editor::completePopupSelected(QString text, QString data)
tooltipOrigText = origText + " " + data;
}
curs.endEditBlock();
blockSignals(false);
if (blockS) blockSignals(false);
setTextCursor(curs);
if (tooltipText.size() > 0) {
showTooltip(& curs, tooltipOrigText);

View File

@ -14,12 +14,14 @@
#include <QFileDialog>
#include <QShortcut>
EditorTabs::EditorTabs(QTabWidget * widget, Settings * settings, HighlightWords * highlightWords, CompleteWords * completeWords, HelpWords * helpWords):
EditorTabs::EditorTabs(SpellCheckerInterface * spellChecker, QTabWidget * widget, Settings * settings, HighlightWords * highlightWords, CompleteWords * completeWords, HelpWords * helpWords, SpellWords * spellWords):
spellChecker(spellChecker),
tabWidget(widget),
settings(settings),
highlightWords(highlightWords),
completeWords(completeWords),
helpWords(helpWords)
helpWords(helpWords),
spellWords(spellWords)
{
editor = nullptr;
blockSig = false;
@ -67,7 +69,7 @@ QString EditorTabs::getTabNameFromPath(QString filepath)
void EditorTabs::createTab(QString filepath, bool initHighlight)
{
EditorTab * tab = new EditorTab();
editor = new Editor(settings, highlightWords, completeWords, helpWords);
editor = new Editor(spellChecker, settings, highlightWords, completeWords, helpWords, spellWords);
tab->setEditor(editor);
QString tabName = getTabNameFromPath(filepath);

View File

@ -10,7 +10,7 @@
#include <QTextStream>
#include <QTimer>
const int LOAD_DELAY = 250;
const int LOAD_DELAY = 250; // should not be less then PROJECT_LOAD_DELAY
HelpWords::HelpWords()
{

View File

@ -78,6 +78,9 @@ Highlight::Highlight(Settings * settings, HighlightWords * hWords, QTextDocument
modeTypes[ext.trimmed().toStdString()] = MODE_HTML;
}
std::string spellColorStr = settings->get("editor_line_warning_color");
spellColor = QColor(QString::fromStdString(spellColorStr));
enabled = false;
modeType = MODE_UNKNOWN;
block_state = 0;
@ -242,8 +245,6 @@ void Highlight::reset()
specialCharsPos.clear();
specialWords.clear();
specialWordsPos.clear();
underlineStart = -1;
underlineEnd = -1;
nsNamePHP = "";
nsChainPHP = "";
nsScopeChainPHP.clear();
@ -1590,12 +1591,18 @@ void Highlight::restoreState() {
}
}
void Highlight::highlightUnderline()
void Highlight::highlightSpell()
{
if (underlineStart >= 0 && underlineEnd >= 0 && underlineEnd > underlineStart) {
QTextCharFormat uFormat = format(underlineStart);
uFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline);
highlightString(underlineStart, underlineEnd - underlineStart, uFormat);
if (blockData != nullptr && blockData->spellStarts.size() == blockData->spellLengths.size()) {
for (int i=0; i<blockData->spellStarts.size(); i++) {
int start = blockData->spellStarts.at(i);
int length = blockData->spellLengths.at(i);
if (start < 0 || length <= 0) continue;
QTextCharFormat uFormat = format(start);
uFormat.setUnderlineStyle(QTextCharFormat::WaveUnderline);
uFormat.setUnderlineColor(spellColor);
highlightString(start, length, uFormat);
}
}
}
@ -3112,7 +3119,7 @@ void Highlight::highlightBlock(QTextBlock & block, bool markDirty)
bool Highlight::parseBlock(const QString & text)
{
if (!enabled || modeType == MODE_UNKNOWN) return false;
if (!enabled) return false;
reset();
if (modeType != MODE_MIXED) {
@ -3192,8 +3199,6 @@ bool Highlight::parseBlock(const QString & text)
_keywordJSScoped = blockData->keywordJSScoped;
_exprEscStringJS = blockData->exprEscStringJS;
_stringEscVariableJS = blockData->stringEscVariableJS;
underlineStart = blockData->underlineStart;
underlineEnd = blockData->underlineEnd;
_hasMarkPoint = blockData->hasMarkPoint;
_nsNamePHP = blockData->nsNamePHP;
_nsChainPHP = blockData->nsChainPHP;
@ -3268,8 +3273,8 @@ bool Highlight::parseBlock(const QString & text)
updateState(c, i, pState);
}
// draw underline
highlightUnderline();
// draw spell underline
highlightSpell();
// close data lists
closeBlockDataLists(text.size());

View File

@ -53,8 +53,6 @@ void HighlightData::reset()
stateStarts.clear();
stateEnds.clear();
stateIds.clear();
underlineStart = -1;
underlineEnd = -1;
hasMarkPoint = false;
nsNamePHP = "";
nsChainPHP = "";

View File

@ -10,7 +10,7 @@
#include <QTextStream>
#include <QTimer>
const int LOAD_DELAY = 250;
const int LOAD_DELAY = 250; // should not be less then PROJECT_LOAD_DELAY
HighlightWords::HighlightWords(Settings * settings)
{

View File

@ -21,6 +21,7 @@
#include <QInputDialog>
#include <QTextStream>
#include <QDesktopServices>
#include <QPluginLoader>
#include "editortab.h"
#include "searchdialog.h"
#include "servers.h"
@ -79,10 +80,15 @@ MainWindow::MainWindow(QWidget *parent) :
restoreGeometry(windowSettings.value("main_window_geometry").toByteArray());
restoreState(windowSettings.value("main_window_state").toByteArray());
// plugins
spellChecker = nullptr;
loadSpellChecker();
// load words
highlightWords = new HighlightWords(settings);
completeWords = new CompleteWords(highlightWords);
helpWords = new HelpWords();
spellWords = new SpellWords();
// statusbar progress
progressBar = new QProgressBar;
@ -92,7 +98,7 @@ MainWindow::MainWindow(QWidget *parent) :
statusBar()->addPermanentWidget(progressBar);
// editor tabs
editorTabs = new EditorTabs(ui->tabWidget, settings, highlightWords, completeWords, helpWords);
editorTabs = new EditorTabs(spellChecker, ui->tabWidget, settings, highlightWords, completeWords, helpWords, spellWords);
connect(editorTabs, SIGNAL(statusBarText(QString)), this, SLOT(setStatusBarText(QString)));
connect(editorTabs, SIGNAL(progressChange(int)), this, SLOT(progressChanged(int)));
connect(editorTabs, SIGNAL(editorFilenameChanged(QString)), this, SLOT(editorFilenameChanged(QString)));
@ -392,6 +398,7 @@ MainWindow::~MainWindow()
delete highlightWords;
delete completeWords;
delete helpWords;
delete spellWords;
delete ui;
}
@ -1912,3 +1919,26 @@ void MainWindow::applyThemeColors()
if (style.size() > 0) setStyleSheet(style);
}
QObject * MainWindow::loadPlugin(QString name)
{
QDir pluginsDir(QCoreApplication::applicationDirPath());
if (!pluginsDir.cd(PLUGINS_DIR + "/" + name)) return nullptr;
QPluginLoader pluginLoader(pluginsDir.absoluteFilePath("lib"+name+".so"));
QObject *plugin = pluginLoader.instance();
if (!plugin) return nullptr;
return plugin;
}
bool MainWindow::loadSpellChecker()
{
QObject * plugin = loadPlugin(SPELLCHECKER_PLUGIN_NAME);
if (plugin == nullptr) return false;
spellChecker = qobject_cast<SpellCheckerInterface *>(plugin);
if (!spellChecker) {
spellChecker = nullptr;
delete plugin;
return false;
}
return true;
}

13
src/plugininterface.cpp Normal file
View File

@ -0,0 +1,13 @@
#include "plugininterface.h"
const QString PLUGINS_DIR = "plugins";
PluginInterface::PluginInterface(QObject *parent) : QObject (parent)
{
}
PluginInterface::~PluginInterface()
{
}

View File

@ -84,6 +84,7 @@ Settings::Settings(QObject * parent) : QObject(parent)
{"shortcut_duplicate_line", "Ctrl+D"},
{"shortcut_delete_line", "Ctrl+Alt+D"},
{"experimental_mode_enabled", "yes"},
{"spellchecker_enabled", "yes"},
{"color_scheme", "light"},
{"theme", "system"},
{"custom_themes_path", ""}

View File

@ -72,6 +72,7 @@ SettingsDialog::SettingsDialog(Settings * settings, QWidget * parent):
ui->editorParseIntervalSpinBox->setValue(std::stoi(settings->get("editor_parse_interval")) / 1000);
if (settings->get("parser_enable_git") == CHECKED_YES) ui->gitCheckbox->setChecked(true);
if (settings->get("parser_enable_servers") == CHECKED_YES) ui->serversCheckbox->setChecked(true);
if (settings->get("spellchecker_enabled") == CHECKED_YES) ui->spellCheckerCheckbox->setChecked(true);
ui->phpPathLineEdit->setText(QString::fromStdString(settings->get("parser_php_path")));
ui->phpcsPathLineEdit->setText(QString::fromStdString(settings->get("parser_phpcs_path")));
ui->gitPathLineEdit->setText(QString::fromStdString(settings->get("parser_git_path")));
@ -316,6 +317,9 @@ std::unordered_map<std::string, std::string> SettingsDialog::getData()
if (ui->serversCheckbox->isChecked()) dataMap["parser_enable_servers"] = CHECKED_YES;
else dataMap["parser_enable_servers"] = CHECKED_NO;
if (ui->spellCheckerCheckbox->isChecked()) dataMap["spellchecker_enabled"] = CHECKED_YES;
else dataMap["spellchecker_enabled"] = CHECKED_NO;
QString phpPathStr = ui->phpPathLineEdit->text();
QString phpcsPathStr = ui->phpcsPathLineEdit->text();
QString gitPathStr = ui->gitPathLineEdit->text();

View File

@ -0,0 +1,13 @@
#include "spellcheckerinterface.h"
const QString SPELLCHECKER_PLUGIN_NAME = "SpellChecker";
SpellCheckerInterface::SpellCheckerInterface(QObject *parent) : PluginInterface (parent)
{
}
SpellCheckerInterface::~SpellCheckerInterface()
{
}

44
src/spellwords.cpp Normal file
View File

@ -0,0 +1,44 @@
#include "spellwords.h"
#include <QString>
#include <QFile>
#include <QTextStream>
#include <QTimer>
const int LOAD_DELAY = 250; // should not be less then PROJECT_LOAD_DELAY
SpellWords::SpellWords()
{
QTimer::singleShot(LOAD_DELAY, this, SLOT(load()));
}
void SpellWords::load()
{
loadWords();
}
void SpellWords::reload()
{
reset();
load();
}
void SpellWords::reset()
{
words.clear();
}
void SpellWords::loadWords()
{
QString k;
// spell words
QFile pf(":/spell/words");
pf.open(QIODevice::ReadOnly);
QTextStream pin(&pf);
while (!pin.atEnd()) {
k = pin.readLine();
if (k == "") continue;
words[k.toStdString()] = k.toStdString();
}
pf.close();
}

View File

@ -89,6 +89,26 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="spellCheckerLayout">
<property name="leftMargin">
<number>156</number>
</property>
<property name="topMargin">
<number>10</number>
</property>
<property name="bottomMargin">
<number>10</number>
</property>
<item>
<widget class="QCheckBox" name="spellCheckerCheckbox">
<property name="text">
<string>Enable spell checker (plugin required)</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="experimentalModeLayout">
<property name="leftMargin">
@ -1238,10 +1258,10 @@
<item>
<layout class="QHBoxLayout" name="phpmanualPathLayout">
<property name="topMargin">
<number>20</number>
<number>10</number>
</property>
<property name="bottomMargin">
<number>20</number>
<number>10</number>
</property>
<item>
<widget class="QLabel" name="phpmanualLabel">