From b48ad8f17a35b36fc675b6f3f6ba5211836f015a Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 17:46:30 +0000 Subject: [PATCH 1/2] Add more zoom shortcuts and a way to reset the zoom in the code editors QScintilla only binds Ctrl++ and Ctrl+- to zooming. On keyboards where the plus sign needs Shift (Ctrl+Shift+= on a US layout, Cmd+Shift+; on a Japanese Mac) the Shift modifier is sent along, so that binding never matches and there was no keyboard way to zoom in at all. Ctrl+= did not work either. There was also no way back to the normal size except opening a new tab. Add Ctrl+Shift++ and Ctrl+= as additional ways to zoom in, and Ctrl+Shift+0 to reset the zoom. Ctrl+0 would be the usual shortcut for the latter, but it is already taken by View > Window Layout > Reset Window Layout in the main window, so the editors must not claim it. The shortcuts are widget shortcuts of ExtendedScintilla, so they also apply to the Edit Cell editor and don't clash between multiple editors. Add test-sql-editor, which creates editors offscreen and checks the zoom keys by sending key events, including that Ctrl+0 is left alone. Related: #2929 --- src/ExtendedScintilla.cpp | 11 +++ src/tests/CMakeLists.txt | 58 ++++++++++++++++ src/tests/SqlEditorTestSupport.cpp | 12 ++++ src/tests/TestSqlEditor.cpp | 108 +++++++++++++++++++++++++++++ src/tests/TestSqlEditor.h | 28 ++++++++ 5 files changed, 217 insertions(+) create mode 100644 src/tests/SqlEditorTestSupport.cpp create mode 100644 src/tests/TestSqlEditor.cpp create mode 100644 src/tests/TestSqlEditor.h diff --git a/src/ExtendedScintilla.cpp b/src/ExtendedScintilla.cpp index 882b1590ab..dfec42390c 100644 --- a/src/ExtendedScintilla.cpp +++ b/src/ExtendedScintilla.cpp @@ -78,6 +78,17 @@ ExtendedScintilla::ExtendedScintilla(QWidget* parent) : QShortcut* shortcutPrint = new QShortcut(QKeySequence(tr("Ctrl+P")), this, nullptr, nullptr, Qt::WidgetShortcut); connect(shortcutPrint, &QShortcut::activated, this, &ExtendedScintilla::openPrintDialog); + // QScintilla only binds Ctrl++ and Ctrl+- to zoom. On keyboards where the plus sign needs the Shift key (Ctrl+Shift+= on + // a US layout, Ctrl+Shift+; on a Japanese one) the Shift modifier is sent too, so that binding never matches and there + // was no way to zoom in. Add the usual alternatives as well as a way back to the normal size. Ctrl+0 would be the + // obvious key for the latter but it is already used by the main window to reset the window layout. + QShortcut* shortcutZoomInShift = new QShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_Plus), this, nullptr, nullptr, Qt::WidgetShortcut); + connect(shortcutZoomInShift, &QShortcut::activated, this, [this]() { zoomIn(); }); + QShortcut* shortcutZoomInEqual = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_Equal), this, nullptr, nullptr, Qt::WidgetShortcut); + connect(shortcutZoomInEqual, &QShortcut::activated, this, [this]() { zoomIn(); }); + QShortcut* shortcutZoomReset = new QShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_0), this, nullptr, nullptr, Qt::WidgetShortcut); + connect(shortcutZoomReset, &QShortcut::activated, this, [this]() { zoomTo(0); }); + // Prepare for adding the find/replace option to the QScintilla context menu setContextMenuPolicy(Qt::CustomContextMenu); connect(this, &ExtendedScintilla::customContextMenuRequested, this, &ExtendedScintilla::showContextMenu); diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 75ce742838..923e6b4b2b 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -123,3 +123,61 @@ set(TESTCACHE_HDR add_executable(test-cache ${TESTCACHE_HDR} ${TESTCACHE_SRC}) target_link_libraries(test-cache ${QT_MAJOR}::Test) add_test(NAME test-cache COMMAND $) + +# test sql editor + +set(TESTSQLEDITOR_SRC + TestSqlEditor.cpp + SqlEditorTestSupport.cpp + ../sqltextedit.cpp + ../ExtendedScintilla.cpp + ../FindReplaceDialog.cpp + ../SqlUiLexer.cpp + ../Settings.cpp + ../sql/sqlitetypes.cpp + ../sql/ObjectIdentifier.cpp + ../sql/Query.cpp + ../sql/parser/ParserDriver.cpp + ../sql/parser/sqlite3_lexer.cpp + ../sql/parser/sqlite3_parser.cpp +) + +set(TESTSQLEDITOR_HDR + TestSqlEditor.h + ../sqltextedit.h + ../ExtendedScintilla.h + ../FindReplaceDialog.h + ../SqlUiLexer.h + ../Settings.h + ../sql/sqlitetypes.h + ../sql/Query.h + ../sql/ObjectIdentifier.h + ../sql/parser/ParserDriver.h + ../sql/parser/sqlite3_lexer.h + ../sql/parser/sqlite3_location.h + ../sql/parser/sqlite3_parser.hpp +) + +add_executable(test-sql-editor ${TESTSQLEDITOR_HDR} ${TESTSQLEDITOR_SRC} ../FindReplaceDialog.ui) +if(QT_MAJOR STREQUAL "Qt6") + set_target_properties(test-sql-editor PROPERTIES AUTOUIC_OPTIONS "--connections=string") +endif() +target_link_libraries(test-sql-editor ${QT_MAJOR}::Test ${QT_MAJOR}::Widgets ${QT_MAJOR}::PrintSupport QScintilla::QScintilla ${LIBSQLITE_NAME} ${QT5_COMPAT}) +add_test(NAME test-sql-editor COMMAND $) +if(WIN32) + set(_se_extra_paths "") + foreach(_p IN LISTS CMAKE_PREFIX_PATH) + list(APPEND _se_extra_paths "${_p}" "${_p}/bin") + # OpenSSL 3 Universal installer layout: bin/x86, bin/x64 + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + list(APPEND _se_extra_paths "${_p}/bin/x64") + else() + list(APPEND _se_extra_paths "${_p}/bin/x86") + endif() + endforeach() + # SQLite and SQLCipher are built as DLLs in the CMake prefix by the Windows CI jobs. + string(REPLACE ";" "\\;" TESTSQLEDITOR_PATH "${_se_extra_paths};$ENV{PATH}") + set_tests_properties(test-sql-editor PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen;PATH=${TESTSQLEDITOR_PATH}") +else() + set_tests_properties(test-sql-editor PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen") +endif() diff --git a/src/tests/SqlEditorTestSupport.cpp b/src/tests/SqlEditorTestSupport.cpp new file mode 100644 index 0000000000..a06860b602 --- /dev/null +++ b/src/tests/SqlEditorTestSupport.cpp @@ -0,0 +1,12 @@ +#include "../sqlitedb.h" + +namespace sqlb +{ + +// The same wrapper sqlitedb.cpp provides. It is repeated here so the editor tests do not have to link the whole database code. +QString escapeIdentifier(const QString& id) +{ + return QString::fromStdString(escapeIdentifier(id.toStdString())); +} + +} diff --git a/src/tests/TestSqlEditor.cpp b/src/tests/TestSqlEditor.cpp new file mode 100644 index 0000000000..84a0c8abff --- /dev/null +++ b/src/tests/TestSqlEditor.cpp @@ -0,0 +1,108 @@ +#include "TestSqlEditor.h" + +#include "../Settings.h" +#include "../sqltextedit.h" + +#include +#include +#include + +QTEST_MAIN(TestSqlEditor) + +namespace +{ + +// Zoom level of the editor in points relative to its normal font size +int zoomLevel(SqlTextEdit& edit) +{ + return static_cast(edit.SendScintilla(QsciScintillaBase::SCI_GETZOOM)); +} + +// Shortcuts constrained to the widget only fire when the editor has the focus in the active window +bool showWithFocus(SqlTextEdit& edit) +{ + edit.resize(400, 300); + edit.show(); + if(!QTest::qWaitForWindowActive(&edit)) + return false; + edit.setFocus(); + return edit.hasFocus(); +} + +} + +void TestSqlEditor::initTestCase() +{ + // Never read or write the settings of the real user + QVERIFY(settingsDir.isValid()); + Settings::setUserSettingsFile(settingsDir.filePath("settings.ini")); + + lexerOwner = new SqlTextEdit(); +} + +void TestSqlEditor::cleanupTestCase() +{ + delete lexerOwner; + lexerOwner = nullptr; +} + +void TestSqlEditor::zoomKeys_data() +{ + QTest::addColumn("key"); + QTest::addColumn("modifiers"); + QTest::addColumn("startZoom"); + QTest::addColumn("expectedZoom"); + + const int ctrl = Qt::ControlModifier; + const int shift = Qt::ShiftModifier; + const int keypad = Qt::KeypadModifier; + + // The bindings QScintilla provides + QTest::newRow("Ctrl++ zooms in") << int(Qt::Key_Plus) << ctrl << 0 << 1; + QTest::newRow("Ctrl+- zooms out") << int(Qt::Key_Minus) << ctrl << 0 << -1; + QTest::newRow("Ctrl+keypad+ zooms in") << int(Qt::Key_Plus) << (ctrl | keypad) << 0 << 1; + QTest::newRow("Ctrl+keypad- zooms out") << int(Qt::Key_Minus) << (ctrl | keypad) << 0 << -1; + + // On keyboards where + needs Shift (US: Shift+=, Japanese: Shift+;) the plain Ctrl++ never arrives + QTest::newRow("Ctrl+Shift++ zooms in") << int(Qt::Key_Plus) << (ctrl | shift) << 0 << 1; + QTest::newRow("Ctrl+= zooms in") << int(Qt::Key_Equal) << ctrl << 0 << 1; + + // Back to the normal size + QTest::newRow("Ctrl+Shift+0 resets from bigger") << int(Qt::Key_0) << (ctrl | shift) << 4 << 0; + QTest::newRow("Ctrl+Shift+0 resets from smaller") << int(Qt::Key_0) << (ctrl | shift) << -3 << 0; + QTest::newRow("Ctrl+Shift+0 keeps the normal size") << int(Qt::Key_0) << (ctrl | shift) << 0 << 0; +} + +void TestSqlEditor::zoomKeys() +{ + QFETCH(int, key); + QFETCH(int, modifiers); + QFETCH(int, startZoom); + QFETCH(int, expectedZoom); + + SqlTextEdit edit; + QVERIFY(showWithFocus(edit)); + edit.zoomTo(startZoom); + QCOMPARE(zoomLevel(edit), startZoom); + + QTest::keyClick(&edit, static_cast(key), static_cast(modifiers)); + + QCOMPARE(zoomLevel(edit), expectedZoom); +} + +void TestSqlEditor::ctrlZeroStaysWithTheApplication() +{ + // Ctrl+0 is the shortcut of View > Window Layout > Reset Window Layout in the main window. + // The editor must not claim it, otherwise that shortcut would stop working while typing SQL. + SqlTextEdit edit; + QVERIFY(showWithFocus(edit)); + + QKeyEvent override(QEvent::ShortcutOverride, Qt::Key_0, Qt::ControlModifier); + override.ignore(); + QApplication::sendEvent(&edit, &override); + QVERIFY(!override.isAccepted()); + + edit.zoomTo(3); + QTest::keyClick(&edit, Qt::Key_0, Qt::ControlModifier); + QCOMPARE(zoomLevel(edit), 3); +} diff --git a/src/tests/TestSqlEditor.h b/src/tests/TestSqlEditor.h new file mode 100644 index 0000000000..7a6e715c0f --- /dev/null +++ b/src/tests/TestSqlEditor.h @@ -0,0 +1,28 @@ +#ifndef TESTSQLEDITOR_H +#define TESTSQLEDITOR_H + +#include +#include + +class SqlTextEdit; + +class TestSqlEditor : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + + void zoomKeys_data(); + void zoomKeys(); + void ctrlZeroStaysWithTheApplication(); + +private: + QTemporaryDir settingsDir; + + // The static SQL lexer is owned by the first SqlTextEdit that is created, so this one must outlive all the others + SqlTextEdit* lexerOwner; +}; + +#endif // TESTSQLEDITOR_H From b8c12b1bddc8abbfbdcc00f44eb7c0a8f665e0c8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 17:52:01 +0000 Subject: [PATCH 2/2] Add an option to comment lines by clicking their number in the SQL editors Commenting out a line to switch between alternative conditions is a common thing to do while writing queries. That works with Ctrl+/ or the toolbar button, but a click on the line number would be quicker. By default such a click selects the whole line, and the same goes for dragging over several line numbers or Shift+click to extend the selection, so changing that for everybody would take something away. Add the option "Toggle comment on line number click" to the SQL tab of the preferences, which is off by default, so nothing changes unless it is turned on. A sensitive margin takes over all clicks on it, so the margin is only made sensitive in editors where there is something to comment. Read-only editors, like the SQL log, and the editor for the content of a cell, which does not hold SQL code, keep selecting the line. To do this, split the part of toggleBlockComment that works on a range of lines into toggleLineComments, which toggleBlockComment still uses after working out the lines from the selection. Add tests to test-sql-editor for toggleBlockComment, toggleLineComments and clicking on the line numbers. Related: #2996 --- src/PreferencesDialog.cpp | 2 + src/PreferencesDialog.ui | 21 ++++ src/Settings.cpp | 4 + src/docktextedit.cpp | 3 + src/sqltextedit.cpp | 38 ++++++- src/sqltextedit.h | 12 +++ src/tests/CMakeLists.txt | 2 + src/tests/TestSqlEditor.cpp | 204 ++++++++++++++++++++++++++++++++++++ src/tests/TestSqlEditor.h | 13 +++ 9 files changed, 295 insertions(+), 4 deletions(-) diff --git a/src/PreferencesDialog.cpp b/src/PreferencesDialog.cpp index a488e5600f..afafed7b6d 100644 --- a/src/PreferencesDialog.cpp +++ b/src/PreferencesDialog.cpp @@ -167,6 +167,7 @@ void PreferencesDialog::loadSettings() ui->checkErrorIndicators->setChecked(Settings::getValue("editor", "error_indicators").toBool()); ui->checkHorizontalTiling->setChecked(Settings::getValue("editor", "horizontal_tiling").toBool()); ui->checkCloseButtonOnTabs->setChecked(Settings::getValue("editor", "close_button_on_tabs").toBool()); + ui->checkToggleCommentOnMarginClick->setChecked(Settings::getValue("editor", "toggle_comment_on_margin_click").toBool()); ui->listExtensions->addItems(Settings::getValue("extensions", "list").toStringList()); for (int i=0;ilistBuiltinExtensions->count();++i) @@ -244,6 +245,7 @@ void PreferencesDialog::saveSettings(bool accept) Settings::setValue("editor", "error_indicators", ui->checkErrorIndicators->isChecked()); Settings::setValue("editor", "horizontal_tiling", ui->checkHorizontalTiling->isChecked()); Settings::setValue("editor", "close_button_on_tabs", ui->checkCloseButtonOnTabs->isChecked()); + Settings::setValue("editor", "toggle_comment_on_margin_click", ui->checkToggleCommentOnMarginClick->isChecked()); QStringList extList; for(int i=0;ilistExtensions->count();++i) diff --git a/src/PreferencesDialog.ui b/src/PreferencesDialog.ui index 38743fb034..904f3b61d0 100644 --- a/src/PreferencesDialog.ui +++ b/src/PreferencesDialog.ui @@ -1880,6 +1880,26 @@ Can be set to 0 for disabling completion. + + + + Toggle comment on line number click + + + checkToggleCommentOnMarginClick + + + + + + + If enabled, clicking a line number in the SQL editors comments or uncomments that line instead of selecting it. The Un/comment block command and its keyboard shortcut can always be used. + + + enabled + + + @@ -2086,6 +2106,7 @@ Can be set to 0 for disabling completion. checkErrorIndicators checkHorizontalTiling checkCloseButtonOnTabs + checkToggleCommentOnMarginClick listExtensions buttonAddExtension buttonRemoveExtension diff --git a/src/Settings.cpp b/src/Settings.cpp index 0cf6aa52eb..9aa9c97785 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -410,6 +410,10 @@ QVariant Settings::getDefaultValue(const std::string& group, const std::string& if(group == "editor" && name == "horizontal_tiling") return false; + // editor/toggle_comment_on_margin_click? + if(group == "editor" && name == "toggle_comment_on_margin_click") + return false; + // editor/splitter1_sizes? if(group == "editor" && name == "splitter1_sizes") return QVariant(); diff --git a/src/docktextedit.cpp b/src/docktextedit.cpp index 8c4ade71a7..089b6acafe 100644 --- a/src/docktextedit.cpp +++ b/src/docktextedit.cpp @@ -18,6 +18,9 @@ DockTextEdit::DockTextEdit(QWidget* parent) : if(xmlLexer == nullptr) xmlLexer = new QsciLexerXML(this); + // The content of a cell is not SQL code, so don't comment lines by clicking on their number + commentOnMarginClickApplies = false; + // Set plain text as default setLanguage(PlainText); diff --git a/src/sqltextedit.cpp b/src/sqltextedit.cpp index 93f8efd2ce..075f78c73a 100644 --- a/src/sqltextedit.cpp +++ b/src/sqltextedit.cpp @@ -12,7 +12,8 @@ SqlUiLexer* SqlTextEdit::sqlLexer = nullptr; SqlTextEdit::SqlTextEdit(QWidget* parent) : - ExtendedScintilla(parent) + ExtendedScintilla(parent), + commentOnMarginClickApplies(true) { // Create lexer object if not done yet if(sqlLexer == nullptr) @@ -53,6 +54,12 @@ SqlTextEdit::SqlTextEdit(QWidget* parent) : QShortcut* shortcutToggleComment = new QShortcut(QKeySequence(tr("Ctrl+/")), this, nullptr, nullptr, Qt::WidgetShortcut); connect(shortcutToggleComment, &QShortcut::activated, this, &SqlTextEdit::toggleBlockComment); + // Margin 0 is the one with the line numbers. It only emits this signal when it is sensitive to clicks, see updateMarginSensitivity. + connect(this, &SqlTextEdit::marginClicked, this, [this](int margin, int line, Qt::KeyboardModifiers) { + if(margin == 0) + toggleLineComments(line, line); + }); + // Do rest of initialisation reloadSettings(); } @@ -71,6 +78,7 @@ void SqlTextEdit::reloadSettings() } // Set wrap lines setWrapMode(static_cast(Settings::getValue("editor", "wrap_lines").toInt())); + updateMarginSensitivity(); ExtendedScintilla::reloadSettings(); @@ -100,6 +108,18 @@ void SqlTextEdit::reloadSettings() setupSyntaxHighlightingFormat(sqlLexer, "identifier", QsciLexerSQL::QuotedIdentifier); } +void SqlTextEdit::setReadOnly(bool ro) +{ + ExtendedScintilla::setReadOnly(ro); + updateMarginSensitivity(); +} + +void SqlTextEdit::updateMarginSensitivity() +{ + // A click on a line number selects that line. When asked for, comment or uncomment the line instead. A sensitive margin + // takes over all the clicks on it, so only do this if there is something to comment. It is off by default. + setMarginSensitivity(0, commentOnMarginClickApplies && !isReadOnly() && Settings::getValue("editor", "toggle_comment_on_margin_click").toBool()); +} void SqlTextEdit::toggleBlockComment() { @@ -121,13 +141,23 @@ void SqlTextEdit::toggleBlockComment() getSelection(&lineFrom, &indexFrom, &lineTo, &indexTo); - bool uncomment = text(lineFrom).contains(QRegularExpression("^[ \t]*--")); - // If the selection ends before the first character of a line, don't // take this line into account for un/commenting. if (indexTo==0) lineTo--; + toggleLineComments(lineFrom, lineTo); +} + +void SqlTextEdit::toggleLineComments(int lineFrom, int lineTo) +{ + lineFrom = qMax(lineFrom, 0); + lineTo = qMin(lineTo, lines() - 1); + if (lineFrom > lineTo) + return; + + bool uncomment = text(lineFrom).contains(QRegularExpression("^[ \t]*--")); + beginUndoAction(); // Iterate over the selected lines, get line text, make @@ -141,7 +171,7 @@ void SqlTextEdit::toggleBlockComment() else lineText.replace(QRegularExpression("^"), "-- "); - indexTo = text(line).length(); + int indexTo = text(line).length(); if (lineText.endsWith("\r\n")) indexTo--; diff --git a/src/sqltextedit.h b/src/sqltextedit.h index ffcd6cf889..12d711a188 100644 --- a/src/sqltextedit.h +++ b/src/sqltextedit.h @@ -18,10 +18,22 @@ class SqlTextEdit : public ExtendedScintilla static SqlUiLexer* sqlLexer; + // Override parent setReadOnly + void setReadOnly(bool ro) override; + public slots: void reloadSettings(); void toggleBlockComment(); + // Comment or uncomment the lines from lineFrom to lineTo (both included) as a single undo action. + // The first of them decides whether all of them get commented or uncommented. + void toggleLineComments(int lineFrom, int lineTo); + +protected: + // Whether the setting to comment a line by clicking its number applies to this editor. Subclasses which don't hold SQL code turn it off. + bool commentOnMarginClickApplies; +private: + void updateMarginSensitivity(); }; #endif diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 923e6b4b2b..b98d5ec0c5 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -130,6 +130,7 @@ set(TESTSQLEDITOR_SRC TestSqlEditor.cpp SqlEditorTestSupport.cpp ../sqltextedit.cpp + ../docktextedit.cpp ../ExtendedScintilla.cpp ../FindReplaceDialog.cpp ../SqlUiLexer.cpp @@ -145,6 +146,7 @@ set(TESTSQLEDITOR_SRC set(TESTSQLEDITOR_HDR TestSqlEditor.h ../sqltextedit.h + ../docktextedit.h ../ExtendedScintilla.h ../FindReplaceDialog.h ../SqlUiLexer.h diff --git a/src/tests/TestSqlEditor.cpp b/src/tests/TestSqlEditor.cpp index 84a0c8abff..3c2509541e 100644 --- a/src/tests/TestSqlEditor.cpp +++ b/src/tests/TestSqlEditor.cpp @@ -1,6 +1,7 @@ #include "TestSqlEditor.h" #include "../Settings.h" +#include "../docktextedit.h" #include "../sqltextedit.h" #include @@ -29,6 +30,21 @@ bool showWithFocus(SqlTextEdit& edit) return edit.hasFocus(); } +// A point in the line number margin on the given line +QPoint marginPoint(SqlTextEdit& edit, int line) +{ + const long lineStart = edit.SendScintilla(QsciScintillaBase::SCI_POSITIONFROMLINE, static_cast(line)); + const int top = static_cast(edit.SendScintilla(QsciScintillaBase::SCI_POINTYFROMPOSITION, 0UL, lineStart)); + const int height = static_cast(edit.SendScintilla(QsciScintillaBase::SCI_TEXTHEIGHT, static_cast(line))); + return QPoint(edit.marginWidth(0) / 2, top + height / 2); +} + +void enableMarginClick(SqlTextEdit& edit, bool enable) +{ + Settings::setValue("editor", "toggle_comment_on_margin_click", enable, /* save_to_disk */ false); + edit.reloadSettings(); +} + } void TestSqlEditor::initTestCase() @@ -40,6 +56,11 @@ void TestSqlEditor::initTestCase() lexerOwner = new SqlTextEdit(); } +void TestSqlEditor::cleanup() +{ + Settings::setValue("editor", "toggle_comment_on_margin_click", false, /* save_to_disk */ false); +} + void TestSqlEditor::cleanupTestCase() { delete lexerOwner; @@ -106,3 +127,186 @@ void TestSqlEditor::ctrlZeroStaysWithTheApplication() QTest::keyClick(&edit, Qt::Key_0, Qt::ControlModifier); QCOMPARE(zoomLevel(edit), 3); } + +void TestSqlEditor::marginClickIsOffByDefault() +{ + // This must run before any other test changes the setting + QVERIFY(!Settings::getValue("editor", "toggle_comment_on_margin_click").toBool()); +} + +void TestSqlEditor::toggleBlockCommentUsesSelection_data() +{ + QTest::addColumn("text"); + QTest::addColumn("lineFrom"); + QTest::addColumn("indexFrom"); + QTest::addColumn("lineTo"); + QTest::addColumn("indexTo"); + QTest::addColumn("expected"); + + // No selection: the line of the cursor (this is the position the cursor is moved to) + QTest::newRow("cursor line") << "a\nb\nc" << 1 << 1 << 1 << 1 << "a\n-- b\nc"; + // A selection ending at the start of a line doesn't include that line + QTest::newRow("selection up to the start of a line") << "a\nb\nc\nd" << 0 << 0 << 2 << 0 << "-- a\n-- b\nc\nd"; + QTest::newRow("selection inside lines") << "a\nb\nc\nd" << 1 << 1 << 2 << 1 << "a\n-- b\n-- c\nd"; +} + +void TestSqlEditor::toggleBlockCommentUsesSelection() +{ + QFETCH(QString, text); + QFETCH(int, lineFrom); + QFETCH(int, indexFrom); + QFETCH(int, lineTo); + QFETCH(int, indexTo); + QFETCH(QString, expected); + + SqlTextEdit edit; + edit.setText(text); + if(lineFrom == lineTo && indexFrom == indexTo) + edit.setCursorPosition(lineFrom, indexFrom); + else + edit.setSelection(lineFrom, indexFrom, lineTo, indexTo); + + edit.toggleBlockComment(); + + QCOMPARE(edit.text(), expected); +} + +void TestSqlEditor::toggleLineComments_data() +{ + QTest::addColumn("text"); + QTest::addColumn("lineFrom"); + QTest::addColumn("lineTo"); + QTest::addColumn("expected"); + + QTest::newRow("comment one line") << "a\nb\nc" << 1 << 1 << "a\n-- b\nc"; + QTest::newRow("uncomment one line") << "a\n-- b\nc" << 1 << 1 << "a\nb\nc"; + QTest::newRow("uncomment without space") << "a\n--b\nc" << 1 << 1 << "a\nb\nc"; + QTest::newRow("uncomment keeps indentation") << "a\n -- b\nc" << 1 << 1 << "a\n b\nc"; + QTest::newRow("comment a range") << "a\nb\nc\nd" << 1 << 2 << "a\n-- b\n-- c\nd"; + QTest::newRow("uncomment a range") << "-- a\n-- b\n-- c" << 0 << 1 << "a\nb\n-- c"; + QTest::newRow("first line decides: comment") << "a\n-- b" << 0 << 1 << "-- a\n-- -- b"; + QTest::newRow("first line decides: uncomment") << "-- a\nb\n-- c" << 0 << 2 << "a\nb\nc"; + QTest::newRow("last line without line break") << "a\nb" << 1 << 1 << "a\n-- b"; + QTest::newRow("Windows line breaks") << "a\r\nb\r\nc" << 0 << 1 << "-- a\r\n-- b\r\nc"; + QTest::newRow("whole text") << "a\nb" << 0 << 1 << "-- a\n-- b"; + QTest::newRow("last line past the end") << "a\nb" << 1 << 7 << "a\n-- b"; + QTest::newRow("first line before the start") << "a\nb" << -3 << 0 << "-- a\nb"; + QTest::newRow("empty range") << "a\nb" << 1 << 0 << "a\nb"; + QTest::newRow("range outside the text") << "a\nb" << 5 << 6 << "a\nb"; +} + +void TestSqlEditor::toggleLineComments() +{ + QFETCH(QString, text); + QFETCH(int, lineFrom); + QFETCH(int, lineTo); + QFETCH(QString, expected); + + SqlTextEdit edit; + edit.setText(text); + + edit.toggleLineComments(lineFrom, lineTo); + + QCOMPARE(edit.text(), expected); +} + +void TestSqlEditor::toggleLineCommentsIsOneUndoStep() +{ + SqlTextEdit edit; + edit.setText("a\nb\nc"); + + edit.toggleLineComments(0, 2); + QCOMPARE(edit.text(), QString("-- a\n-- b\n-- c")); + + edit.undo(); + QCOMPARE(edit.text(), QString("a\nb\nc")); +} + +void TestSqlEditor::marginClickSelectsLineByDefault() +{ + // What a click on a line number does today: it selects that line and doesn't touch the text + SqlTextEdit edit; + edit.setText("a\nb\nc"); + QVERIFY(showWithFocus(edit)); + + QTest::mouseClick(edit.viewport(), Qt::LeftButton, Qt::NoModifier, marginPoint(edit, 1)); + + QCOMPARE(edit.text(), QString("a\nb\nc")); + QVERIFY(edit.hasSelectedText()); + QVERIFY(edit.selectedText().startsWith("b")); +} + +void TestSqlEditor::marginClickTogglesComment() +{ + SqlTextEdit edit; + edit.setText("a\nb\nc"); + QVERIFY(showWithFocus(edit)); + enableMarginClick(edit, true); + + QTest::mouseClick(edit.viewport(), Qt::LeftButton, Qt::NoModifier, marginPoint(edit, 1)); + QCOMPARE(edit.text(), QString("a\n-- b\nc")); + + QTest::mouseClick(edit.viewport(), Qt::LeftButton, Qt::NoModifier, marginPoint(edit, 1)); + QCOMPARE(edit.text(), QString("a\nb\nc")); + + // Other lines are not affected + QTest::mouseClick(edit.viewport(), Qt::LeftButton, Qt::NoModifier, marginPoint(edit, 2)); + QCOMPARE(edit.text(), QString("a\nb\n-- c")); + + // Turning it off again brings the old behaviour back + enableMarginClick(edit, false); + QTest::mouseClick(edit.viewport(), Qt::LeftButton, Qt::NoModifier, marginPoint(edit, 0)); + QCOMPARE(edit.text(), QString("a\nb\n-- c")); + QVERIFY(edit.hasSelectedText()); +} + +void TestSqlEditor::marginClickOutsideMarginDoesNotToggle() +{ + SqlTextEdit edit; + edit.setText("a\nb\nc"); + QVERIFY(showWithFocus(edit)); + enableMarginClick(edit, true); + + QPoint inText = marginPoint(edit, 1); + inText.setX(edit.marginWidth(0) + edit.marginWidth(1) + edit.marginWidth(2) + 40); + QTest::mouseClick(edit.viewport(), Qt::LeftButton, Qt::NoModifier, inText); + + QCOMPARE(edit.text(), QString("a\nb\nc")); +} + +void TestSqlEditor::marginClickSelectsLineInReadOnlyEditor() +{ + // Nothing to comment in an editor that can't be changed, so clicking the line numbers must keep selecting the line there + SqlTextEdit edit; + edit.setText("a\nb\nc"); + edit.setReadOnly(true); + QVERIFY(showWithFocus(edit)); + enableMarginClick(edit, true); + + QTest::mouseClick(edit.viewport(), Qt::LeftButton, Qt::NoModifier, marginPoint(edit, 1)); + + QCOMPARE(edit.text(), QString("a\nb\nc")); + QVERIFY(edit.hasSelectedText()); + QVERIFY(edit.selectedText().startsWith("b")); + + // ... but works as soon as the editor can be changed + edit.setReadOnly(false); + QTest::mouseClick(edit.viewport(), Qt::LeftButton, Qt::NoModifier, marginPoint(edit, 1)); + + QCOMPARE(edit.text(), QString("a\n-- b\nc")); +} + +void TestSqlEditor::marginClickSelectsLineInCellEditor() +{ + // The editor for the cell content is based on SqlTextEdit but doesn't hold SQL code + DockTextEdit edit; + edit.setText("a\nb\nc"); + QVERIFY(showWithFocus(edit)); + enableMarginClick(edit, true); + + QTest::mouseClick(edit.viewport(), Qt::LeftButton, Qt::NoModifier, marginPoint(edit, 1)); + + QCOMPARE(edit.text(), QString("a\nb\nc")); + QVERIFY(edit.hasSelectedText()); + QVERIFY(edit.selectedText().startsWith("b")); +} diff --git a/src/tests/TestSqlEditor.h b/src/tests/TestSqlEditor.h index 7a6e715c0f..c2c7dabe6f 100644 --- a/src/tests/TestSqlEditor.h +++ b/src/tests/TestSqlEditor.h @@ -13,11 +13,24 @@ class TestSqlEditor : public QObject private slots: void initTestCase(); void cleanupTestCase(); + void cleanup(); void zoomKeys_data(); void zoomKeys(); void ctrlZeroStaysWithTheApplication(); + void marginClickIsOffByDefault(); + void toggleBlockCommentUsesSelection_data(); + void toggleBlockCommentUsesSelection(); + void toggleLineComments_data(); + void toggleLineComments(); + void toggleLineCommentsIsOneUndoStep(); + void marginClickSelectsLineByDefault(); + void marginClickTogglesComment(); + void marginClickOutsideMarginDoesNotToggle(); + void marginClickSelectsLineInReadOnlyEditor(); + void marginClickSelectsLineInCellEditor(); + private: QTemporaryDir settingsDir;