spellchecker
This commit is contained in:
parent
2c48c2f992
commit
d87e68cad6
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,18 @@
|
|||
https
|
||||
localhost
|
||||
www
|
||||
github
|
||||
destructor
|
||||
sendmail
|
||||
ascii
|
||||
filesystem
|
||||
gif
|
||||
png
|
||||
jpeg
|
||||
txt
|
||||
php
|
||||
htaccess
|
||||
js
|
||||
css
|
||||
cms
|
||||
ziracms
|
|
@ -0,0 +1,5 @@
|
|||
<RCC>
|
||||
<qresource prefix="/spell">
|
||||
<file alias="words">resources/spell/words</file>
|
||||
</qresource>
|
||||
</RCC>
|
|
@ -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)
|
||||
{
|
||||
|
|
225
src/editor.cpp
225
src/editor.cpp
|
@ -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;\"> </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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -53,8 +53,6 @@ void HighlightData::reset()
|
|||
stateStarts.clear();
|
||||
stateEnds.clear();
|
||||
stateIds.clear();
|
||||
underlineStart = -1;
|
||||
underlineEnd = -1;
|
||||
hasMarkPoint = false;
|
||||
nsNamePHP = "";
|
||||
nsChainPHP = "";
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
#include "plugininterface.h"
|
||||
|
||||
const QString PLUGINS_DIR = "plugins";
|
||||
|
||||
PluginInterface::PluginInterface(QObject *parent) : QObject (parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
PluginInterface::~PluginInterface()
|
||||
{
|
||||
|
||||
}
|
|
@ -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", ""}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
#include "spellcheckerinterface.h"
|
||||
|
||||
const QString SPELLCHECKER_PLUGIN_NAME = "SpellChecker";
|
||||
|
||||
SpellCheckerInterface::SpellCheckerInterface(QObject *parent) : PluginInterface (parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
SpellCheckerInterface::~SpellCheckerInterface()
|
||||
{
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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">
|
||||
|
|
Loading…
Reference in New Issue