diff options
Diffstat (limited to 'SQLiteStudio3/guiSQLiteStudio/sqleditor.cpp')
| -rw-r--r-- | SQLiteStudio3/guiSQLiteStudio/sqleditor.cpp | 280 |
1 files changed, 236 insertions, 44 deletions
diff --git a/SQLiteStudio3/guiSQLiteStudio/sqleditor.cpp b/SQLiteStudio3/guiSQLiteStudio/sqleditor.cpp index 50189d4..d1f1a47 100644 --- a/SQLiteStudio3/guiSQLiteStudio/sqleditor.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/sqleditor.cpp @@ -1,8 +1,10 @@ #include "sqleditor.h" -#include "log.h" +#include "common/mouseshortcut.h" +#include "sqlitesyntaxhighlighter.h" +#include "db/db.h" #include "uiconfig.h" #include "uiutils.h" -#include "services/config.h" +#include "services/codesnippetmanager.h" #include "iconmanager.h" #include "completer/completerwindow.h" #include "completionhelper.h" @@ -16,12 +18,13 @@ #include "dbobjectdialogs.h" #include "searchtextlocator.h" #include "services/codeformatter.h" -#include "sqlitestudio.h" #include "style.h" #include "dbtree/dbtreeitem.h" #include "dbtree/dbtree.h" #include "dbtree/dbtreemodel.h" +#include "dbtree/dbtreeview.h" #include "common/lazytrigger.h" +#include "common/extaction.h" #include <QAction> #include <QMenu> #include <QTimer> @@ -36,6 +39,28 @@ CFG_KEYS_DEFINE(SqlEditor) +QHash<SqlEditor::Action, QAction*> SqlEditor::staticActions; +bool SqlEditor::wrapWords = false; + +void SqlEditor::createStaticActions() +{ + staticActions[WORD_WRAP] = new ExtAction(tr("Wrap words", "sql editor"), MainWindow::getInstance()); + + staticActions[WORD_WRAP]->setCheckable(true); + staticActions[WORD_WRAP]->setChecked(wrapWords); + connect(staticActions[WORD_WRAP], &QAction::toggled, [=](bool value) + { + wrapWords = value; + CFG_UI.General.SqlEditorWrapWords.set(value); + }); +} + +void SqlEditor::staticInit() +{ + wrapWords = CFG_UI.General.SqlEditorWrapWords.get(); + createStaticActions(); +} + SqlEditor::SqlEditor(QWidget *parent) : QPlainTextEdit(parent) { @@ -44,8 +69,8 @@ SqlEditor::SqlEditor(QWidget *parent) : SqlEditor::~SqlEditor() { - if (objectsInNamedDbFuture.isRunning()) - objectsInNamedDbFuture.waitForFinished(); + if (objectsInNamedDbWatcher->isRunning()) + objectsInNamedDbWatcher->waitForFinished(); if (queryParser) { @@ -57,20 +82,25 @@ SqlEditor::~SqlEditor() void SqlEditor::init() { highlighter = new SqliteSyntaxHighlighter(document()); - setFont(CFG_UI.Fonts.SqlEditor.get()); initActions(); setupMenu(); + objectsInNamedDbWatcher = new QFutureWatcher<QHash<QString,QStringList>>(this); + connect(objectsInNamedDbWatcher, SIGNAL(finished()), this, SLOT(scheduleQueryParserForSchemaRefresh())); + textLocator = new SearchTextLocator(document(), this); connect(textLocator, SIGNAL(found(int,int)), this, SLOT(found(int,int))); connect(textLocator, SIGNAL(reachedEnd()), this, SLOT(reachedEnd())); + connect(textLocator, SIGNAL(newCursorPositionAfterAllReplaced(int)), this, SLOT(moveCursorTo(int))); lineNumberArea = new LineNumberArea(this); + changeFont(CFG_UI.Fonts.SqlEditor.get()); connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth())); connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateLineNumberArea(QRect,int))); connect(this, SIGNAL(textChanged()), this, SLOT(checkContentSize())); connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(cursorMoved())); + MouseShortcut::forWheel(Qt::ControlModifier, this, SLOT(fontSizeChangeRequested(int)), viewport()); updateLineNumberAreaWidth(); highlightCurrentCursorContext(); @@ -97,6 +127,7 @@ void SqlEditor::init() connect(this, &QWidget::customContextMenuRequested, this, &SqlEditor::customContextMenuRequested); connect(CFG_UI.Fonts.SqlEditor, SIGNAL(changed(QVariant)), this, SLOT(changeFont(QVariant))); connect(CFG, SIGNAL(massSaveCommitted()), this, SLOT(configModified())); + connect(STYLE, SIGNAL(paletteChanged()), this, SLOT(colorsConfigChanged())); } void SqlEditor::removeErrorMarkers() @@ -143,6 +174,8 @@ void SqlEditor::createActions() createAction(FIND_PREV, tr("Find previous", "sql editor"), this, SLOT(findPrevious()), this); createAction(REPLACE, ICONS.SEARCH_AND_REPLACE, tr("Replace", "sql editor"), this, SLOT(replace()), this); createAction(TOGGLE_COMMENT, tr("Toggle comment", "sql editor"), this, SLOT(toggleComment()), this); + createAction(INCR_FONT_SIZE, tr("Increase font size", "sql editor"), this, SLOT(incrFontSize()), this); + createAction(DECR_FONT_SIZE, tr("Decrease font size", "sql editor"), this, SLOT(decrFontSize()), this); actionMap[CUT]->setEnabled(false); actionMap[COPY]->setEnabled(false); @@ -153,12 +186,14 @@ void SqlEditor::createActions() connect(this, &QPlainTextEdit::undoAvailable, this, &SqlEditor::updateUndoAction); connect(this, &QPlainTextEdit::redoAvailable, this, &SqlEditor::updateRedoAction); connect(this, &QPlainTextEdit::copyAvailable, this, &SqlEditor::updateCopyAction); + + connect(CFG_UI.General.SqlEditorWrapWords, SIGNAL(changed(QVariant)), this, SLOT(wordWrappingChanged(QVariant))); } void SqlEditor::setupDefShortcuts() { setShortcutContext({CUT, COPY, PASTE, DELETE, SELECT_ALL, UNDO, REDO, COMPLETE, FORMAT_SQL, SAVE_SQL_FILE, OPEN_SQL_FILE, - DELETE_LINE}, Qt::WidgetWithChildrenShortcut); + DELETE_LINE, INCR_FONT_SIZE, DECR_FONT_SIZE}, Qt::WidgetWithChildrenShortcut); BIND_SHORTCUTS(SqlEditor, Action); } @@ -167,6 +202,7 @@ void SqlEditor::setupMenu() { contextMenu = new QMenu(this); contextMenu->addAction(actionMap[FORMAT_SQL]); + contextMenu->addAction(staticActions[WORD_WRAP]); contextMenu->addSeparator(); contextMenu->addAction(actionMap[SAVE_SQL_FILE]); contextMenu->addAction(actionMap[OPEN_SQL_FILE]); @@ -194,7 +230,7 @@ void SqlEditor::setDb(Db* value) { db = value; refreshValidObjects(); - scheduleQueryParser(true); + scheduleQueryParser(true, true); } void SqlEditor::setAutoCompletion(bool enabled) @@ -243,10 +279,13 @@ bool SqlEditor::handleValidObjectContextMenu(const QPoint& pos) void SqlEditor::saveToFile(const QString &fileName) { + if (!openSaveActionsEnabled) + return; + QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { - notifyError(tr("Could not open file '%1' for writing: %2").arg(fileName).arg(file.errorString())); + notifyError(tr("Could not open file '%1' for writing: %2").arg(fileName, file.errorString())); return; } @@ -271,7 +310,34 @@ void SqlEditor::toggleLineCommentForLine(const QTextBlock& block) } else cur.insertText("--"); +} +bool SqlEditor::getAlwaysEnforceErrorsChecking() const +{ + return alwaysEnforceErrorsChecking; +} + +void SqlEditor::setAlwaysEnforceErrorsChecking(bool newAlwaysEnforceErrorsChecking) +{ + alwaysEnforceErrorsChecking = newAlwaysEnforceErrorsChecking; +} + +bool SqlEditor::getHighlightingSyntax() const +{ + return highlightingSyntax; +} + +void SqlEditor::setOpenSaveActionsEnabled(bool value) +{ + openSaveActionsEnabled = value; + if (value) + { + noConfigShortcutActions.remove(SAVE_SQL_FILE); + noConfigShortcutActions.remove(SAVE_AS_SQL_FILE); + noConfigShortcutActions.remove(OPEN_SQL_FILE); + } + else + noConfigShortcutActions << SAVE_SQL_FILE << SAVE_AS_SQL_FILE << OPEN_SQL_FILE; } void SqlEditor::updateUndoAction(bool enabled) @@ -487,6 +553,12 @@ void SqlEditor::updateCompleterPosition() void SqlEditor::completeSelected() { + if (completer->getMode() == CompleterWindow::SNIPPETS) + { + insertPlainText(CODESNIPPETS->getCodeByName(completer->getSnippetName())); + return; + } + deletePreviousChars(completer->getNumberOfCharsToRemove()); ExpectedTokenPtr token = completer->getSelected(); @@ -505,7 +577,7 @@ void SqlEditor::completeSelected() void SqlEditor::checkForAutoCompletion() { - if (!db || !autoCompletion || deletionKeyPressed || !richFeaturesEnabled) + if (!db || !autoCompletion || deletionKeyPressed || !richFeaturesEnabled || !CFG_CORE.CodeAssistant.AutoTrigger.get()) return; Lexer lexer; @@ -529,21 +601,25 @@ void SqlEditor::refreshValidObjects() if (!db || !db->isValid()) return; - objectsInNamedDbFuture = QtConcurrent::run([this]() + Db* dbClone = db->clone(); + QFuture<QHash<QString,QStringList>> objectsInNamedDbFuture = QtConcurrent::run([dbClone]() { - QMutexLocker lock(&objectsInNamedDbMutex); - objectsInNamedDb.clear(); - - SchemaResolver resolver(db); + dbClone->openQuiet(); + QHash<QString,QStringList> objectsByDbName; + SchemaResolver resolver(dbClone); QSet<QString> databases = resolver.getDatabases(); databases << "main"; QStringList objects; - for (const QString& dbName : databases) + for (const QString& dbName : qAsConst(databases)) { objects = resolver.getAllObjects(dbName); - objectsInNamedDb[dbName] << objects; + objectsByDbName[dbName] << objects; } + dbClone->closeQuiet(); + delete dbClone; + return objectsByDbName; }); + objectsInNamedDbWatcher->setFuture(objectsInNamedDbFuture); } void SqlEditor::setObjectLinks(bool enabled) @@ -551,7 +627,7 @@ void SqlEditor::setObjectLinks(bool enabled) objectLinksEnabled = enabled; setMouseTracking(enabled); highlighter->setObjectLinksEnabled(enabled); - highlighter->rehighlight(); + highlightSyntax(); if (enabled) handleValidObjectCursor(mapFromGlobal(QCursor::pos())); @@ -574,7 +650,7 @@ void SqlEditor::clearDbObjects() void SqlEditor::lineNumberAreaPaintEvent(QPaintEvent* event) { QPainter painter(lineNumberArea); - painter.fillRect(event->rect(), STYLE->extendedPalette().editorLineBase()); + painter.fillRect(event->rect(), STYLE->extendedPalette().editorLineNumberBase()); QTextBlock block = firstVisibleBlock(); int blockNumber = block.blockNumber(); int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top(); @@ -649,9 +725,32 @@ void SqlEditor::highlightParenthesis(QList<QTextEdit::ExtraSelection>& selection markMatchedParenthesis(thePar->position, matchedPar->position, selections); } -void SqlEditor::highlightCurrentCursorContext() +void SqlEditor::highlightCurrentQuery(QList<QTextEdit::ExtraSelection>& selections) +{ + QTextCursor cursor = textCursor(); + int curPos = cursor.position(); + QString contents = cursor.document()->toPlainText(); + QPair<int,int> boundries = getQueryBoundriesForPosition(contents, curPos, true); + if (boundries.second < 0) + return; + + QTextEdit::ExtraSelection selection; + selection.format.setBackground(STYLE->extendedPalette().editorCurrentQueryBase()); + + cursor.setPosition(boundries.first); + cursor.setPosition(boundries.second, QTextCursor::KeepAnchor); + selection.cursor = cursor; + selections.append(selection); +} + +void SqlEditor::highlightCurrentCursorContext(bool delayedCall) { QList<QTextEdit::ExtraSelection> selections; + if (delayedCall) + highlightCurrentQuery(selections); + else if (currentQueryTimer) + currentQueryTimer->start(); + highlightCurrentLine(selections); highlightParenthesis(selections); setExtraSelections(selections); @@ -661,8 +760,8 @@ void SqlEditor::markMatchedParenthesis(int pos1, int pos2, QList<QTextEdit::Extr { QTextEdit::ExtraSelection selection; - selection.format.setBackground(style()->standardPalette().windowText()); - selection.format.setForeground(style()->standardPalette().window()); + selection.format.setBackground(Cfg::getSyntaxParenthesisBg()); + selection.format.setForeground(Cfg::getSyntaxParenthesisFg()); QTextCursor cursor = textCursor(); @@ -829,7 +928,7 @@ void SqlEditor::completerRightPressed() void SqlEditor::parseContents() { - if (!richFeaturesEnabled) + if (!richFeaturesEnabled && !alwaysEnforceErrorsChecking) return; QString sql = toPlainText(); @@ -841,13 +940,20 @@ void SqlEditor::parseContents() sql = virtualSqlExpression.arg(sql); } + queryParser->parse(sql); if (richFeaturesEnabled) - { - queryParser->parse(sql); checkForValidObjects(); - checkForSyntaxErrors(); - highlighter->rehighlight(); - } + + checkForSyntaxErrors(); + + if (richFeaturesEnabled) + highlightSyntax(); +} + +void SqlEditor::scheduleQueryParserForSchemaRefresh() +{ + objectsInNamedDb = objectsInNamedDbWatcher->future().result(); + scheduleQueryParser(true, true); } void SqlEditor::checkForSyntaxErrors() @@ -858,9 +964,9 @@ void SqlEditor::checkForSyntaxErrors() // Marking invalid tokens, like in "SELECT * from test] t" - the "]" token is invalid. // Such tokens don't cause parser to fail. - for (SqliteQueryPtr query : queryParser->getQueries()) + for (const SqliteQueryPtr& query : queryParser->getQueries()) { - for (TokenPtr token : query->tokens) + for (TokenPtr& token : query->tokens) { if (token->type == Token::INVALID) markErrorAt(token->start, token->end, true); @@ -886,16 +992,15 @@ void SqlEditor::checkForValidObjects() if (!db || !db->isValid()) return; - QMutexLocker lock(&objectsInNamedDbMutex); QList<SqliteStatement::FullObject> fullObjects; QString dbName; - for (SqliteQueryPtr query : queryParser->getQueries()) + for (const SqliteQueryPtr& query : queryParser->getQueries()) { fullObjects = query->getContextFullObjects(); - for (const SqliteStatement::FullObject& fullObj : fullObjects) + for (SqliteStatement::FullObject& fullObj : fullObjects) { dbName = fullObj.database ? stripObjName(fullObj.database->value) : "main"; - if (!objectsInNamedDb.contains(dbName)) + if (!objectsInNamedDb.contains(dbName, Qt::CaseInsensitive)) continue; if (fullObj.type == SqliteStatement::FullObject::DATABASE) @@ -905,7 +1010,7 @@ void SqlEditor::checkForValidObjects() continue; } - if (!objectsInNamedDb[dbName].contains(stripObjName(fullObj.object->value))) + if (!objectsInNamedDb[dbName].contains(stripObjName(fullObj.object->value), Qt::CaseInsensitive)) continue; // Valid object name @@ -914,7 +1019,7 @@ void SqlEditor::checkForValidObjects() } } -void SqlEditor::scheduleQueryParser(bool force) +void SqlEditor::scheduleQueryParser(bool force, bool skipCompleter) { if (!document()->isModified() && !force) return; @@ -923,7 +1028,8 @@ void SqlEditor::scheduleQueryParser(bool force) document()->setModified(false); queryParserTrigger->schedule(); - autoCompleteTrigger->schedule(); + if (!skipCompleter) + autoCompleteTrigger->schedule(); } int SqlEditor::sqlIndex(int idx) @@ -968,7 +1074,14 @@ QString SqlEditor::getSelectedText() const void SqlEditor::openObject(const QString& database, const QString& name) { DbObjectDialogs dialogs(db); - dialogs.editObject(database, name); + dialogs.editObject(DbObjectDialogs::Type::UNKNOWN, database, name); +} + +void SqlEditor::highlightSyntax() +{ + highlightingSyntax = true; + highlighter->rehighlight(); + highlightingSyntax = false; } void SqlEditor::updateLineNumberAreaWidth() @@ -987,7 +1100,6 @@ void SqlEditor::highlightCurrentLine(QList<QTextEdit::ExtraSelection>& selection if (!isReadOnly() && isEnabled()) { QTextEdit::ExtraSelection selection; - selection.format.setBackground(STYLE->extendedPalette().editorLineBase()); selection.format.setProperty(QTextFormat::FullWidthSelection, true); selection.cursor = textCursor(); @@ -1059,6 +1171,9 @@ void SqlEditor::saveToFile() void SqlEditor::saveAsToFile() { + if (!openSaveActionsEnabled) + return; + QString dir = getFileDialogInitPath(); QString fName = QFileDialog::getSaveFileName(this, tr("Save to file"), dir); if (fName.isNull()) @@ -1071,6 +1186,9 @@ void SqlEditor::saveAsToFile() void SqlEditor::loadFromFile() { + if (!openSaveActionsEnabled) + return; + QString dir = getFileDialogInitPath(); QString filters = tr("SQL scripts (*.sql);;All files (*)"); QString fName = QFileDialog::getOpenFileName(this, tr("Open file"), dir, filters); @@ -1083,7 +1201,7 @@ void SqlEditor::loadFromFile() QString sql = readFileContents(fName, &err); if (sql.isNull() && !err.isNull()) { - notifyError(tr("Could not open file '%1' for reading: %2").arg(fName).arg(err)); + notifyError(tr("Could not open file '%1' for reading: %2").arg(fName, err)); return; } @@ -1293,13 +1411,14 @@ void SqlEditor::reachedEnd() void SqlEditor::changeFont(const QVariant& font) { - setFont(font.value<QFont>()); + auto f = font.value<QFont>(); + setFont(f); + lineNumberArea->setFont(f); } void SqlEditor::configModified() { - highlighter->rehighlight(); - highlightCurrentCursorContext(); + colorsConfigChanged(); } void SqlEditor::toggleComment() @@ -1387,6 +1506,51 @@ void SqlEditor::toggleComment() setTextCursor(cur); } +void SqlEditor::wordWrappingChanged(const QVariant& value) +{ + setLineWrapMode(value.toBool() ? QPlainTextEdit::WidgetWidth : QPlainTextEdit::NoWrap); +} + +void SqlEditor::currentCursorContextDelayedHighlight() +{ + highlightCurrentCursorContext(true); +} + +void SqlEditor::fontSizeChangeRequested(int delta) +{ + changeFontSize(delta >= 0 ? 1 : -1); +} + +void SqlEditor::incrFontSize() +{ + changeFontSize(1); +} + +void SqlEditor::decrFontSize() +{ + changeFontSize(-1); +} + +void SqlEditor::moveCursorTo(int pos) +{ + QTextCursor cur = textCursor(); + cur.setPosition(pos); + setTextCursor(cur); +} + +void SqlEditor::changeFontSize(int factor) +{ + auto f = font(); + f.setPointSize(f.pointSize() + factor); + CFG_UI.Fonts.SqlEditor.set(f); +} + +void SqlEditor::colorsConfigChanged() +{ + highlightSyntax(); + highlightCurrentCursorContext(); +} + void SqlEditor::keyPressEvent(QKeyEvent* e) { switch (e->key()) @@ -1563,6 +1727,21 @@ QToolBar* SqlEditor::getToolBar(int toolbar) const return nullptr; } +void SqlEditor::setCurrentQueryHighlighting(bool enabled) +{ + if (enabled && !currentQueryTimer) + { + currentQueryTimer = new QTimer(this); + currentQueryTimer->setInterval(300); + currentQueryTimer->setSingleShot(true); + connect(currentQueryTimer, SIGNAL(timeout()), this, SLOT(currentCursorContextDelayedHighlight())); + } + else if (!enabled && currentQueryTimer) + { + safe_delete(currentQueryTimer); + } +} + QString SqlEditor::getVirtualSqlExpression() const { return virtualSqlExpression; @@ -1600,7 +1779,7 @@ const SqlEditor::DbObject* SqlEditor::getValidObjectForPosition(const QPoint& po const SqlEditor::DbObject* SqlEditor::getValidObjectForPosition(int position, bool movedLeft) { - for (const DbObject& obj : validDbObjects) + for (DbObject& obj : validDbObjects) { if ((!movedLeft && position > obj.from && position-1 <= obj.to) || (movedLeft && position >= obj.from && position <= obj.to)) @@ -1641,3 +1820,16 @@ void SqlEditor::changeEvent(QEvent* e) QPlainTextEdit::changeEvent(e); } + +void SqlEditor::showEvent(QShowEvent* event) +{ + UNUSED(event); + setLineWrapMode(wrapWords ? QPlainTextEdit::WidgetWidth : QPlainTextEdit::NoWrap); +} + +void SqlEditor::dropEvent(QDropEvent* e) +{ + QPlainTextEdit::dropEvent(e); + if (MAINWINDOW->getDbTree()->getModel()->hasDbTreeItem(e->mimeData())) + e->ignore(); +} |
