diff --git a/autotests/src/katedocument_test.cpp b/autotests/src/katedocument_test.cpp index 4be9d7c3..849041fb 100644 --- a/autotests/src/katedocument_test.cpp +++ b/autotests/src/katedocument_test.cpp @@ -1,683 +1,683 @@ /* This file is part of the KDE libraries Copyright (C) 2010-2018 Dominik Haumann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "katedocument_test.h" #include "moc_katedocument_test.cpp" #include #include #include #include #include #include #include #include ///TODO: is there a FindValgrind cmake command we could use to /// define this automatically? // comment this out and run the test case with: // valgrind --tool=callgrind --instr-atstart=no ./katedocument_test testSetTextPerformance // or similar // // #define USE_VALGRIND #ifdef USE_VALGRIND #include #endif using namespace KTextEditor; QTEST_MAIN(KateDocumentTest) class MovingRangeInvalidator : public QObject { Q_OBJECT public: explicit MovingRangeInvalidator(QObject *parent = nullptr) : QObject(parent) { } void addRange(MovingRange *range) { m_ranges << range; } QList ranges() const { return m_ranges; } public Q_SLOTS: void aboutToInvalidateMovingInterfaceContent() { qDeleteAll(m_ranges); m_ranges.clear(); } private: QList m_ranges; }; KateDocumentTest::KateDocumentTest() : QObject() { } KateDocumentTest::~KateDocumentTest() { } void KateDocumentTest::initTestCase() { KTextEditor::EditorPrivate::enableUnitTestMode(); } // tests: // KTextEditor::DocumentPrivate::insertText with word wrap enabled. It is checked whether the // text is correctly wrapped and whether the moving cursors maintain the correct // position. // see also: http://bugs.kde.org/show_bug.cgi?id=168534 void KateDocumentTest::testWordWrap() { KTextEditor::DocumentPrivate doc(false, false); doc.setWordWrap(true); doc.setWordWrapAt(80); const QString content = QLatin1String(".........1.........2.........3.........4.........5.........6 ........7 ........8"); // space after 7 is now kept // else we kill indentation... const QString firstWrap = QLatin1String(".........1.........2.........3.........4.........5.........6 ........7 \n....x....8"); // space after 6 is now kept // else we kill indentation... const QString secondWrap = QLatin1String(".........1.........2.........3.........4.........5.........6 \n....ooooooooooo....7 ....x....8"); doc.setText(content); MovingCursor *c = doc.newMovingCursor(Cursor(0, 75), MovingCursor::MoveOnInsert); QCOMPARE(doc.text(), content); QCOMPARE(c->toCursor(), Cursor(0, 75)); // type a character at (0, 75) doc.insertText(c->toCursor(), QLatin1String("x")); QCOMPARE(doc.text(), firstWrap); QCOMPARE(c->toCursor(), Cursor(1, 5)); // set cursor to (0, 65) and type "ooooooooooo" c->setPosition(Cursor(0, 65)); doc.insertText(c->toCursor(), QLatin1String("ooooooooooo")); QCOMPARE(doc.text(), secondWrap); QCOMPARE(c->toCursor(), Cursor(1, 15)); } void KateDocumentTest::testWrapParagraph() { // Each paragraph must be kept as an own but re-wrapped nicely KTextEditor::DocumentPrivate doc(false, false); doc.setWordWrapAt(30); // Keep needed test data small const QString before = QLatin1String("aaaaa a aaaa\naaaaa aaa aa aaaa aaaa \naaaa a aaa aaaaaaa a aaaa\n\nxxxxx x\nxxxx xxxxx\nxxx xx xxxx \nxxxx xxxx x xxx xxxxxxx x xxxx"); const QString after = QLatin1String("aaaaa a aaaa aaaaa aaa aa aaaa \naaaa aaaa a aaa aaaaaaa a aaaa\n\nxxxxx x xxxx xxxxx xxx xx xxxx \nxxxx xxxx x xxx xxxxxxx x xxxx"); doc.setWordWrap(false); // First we try with disabled hard wrap doc.setText(before); doc.wrapParagraph(0, doc.lines() - 1); QCOMPARE(doc.text(), after); doc.setWordWrap(true); // Test again with enabled hard wrap, that had cause trouble due to twice wrapping doc.setText(before); doc.wrapParagraph(0, doc.lines() - 1); QCOMPARE(doc.text(), after); } void KateDocumentTest::testReplaceQStringList() { KTextEditor::DocumentPrivate doc(false, false); doc.setWordWrap(false); doc.setText(QLatin1String("asdf\n" "foo\n" "foo\n" "bar\n")); doc.replaceText(Range(1, 0, 3, 0), { "new", "text", "" }, false); QCOMPARE(doc.text(), QLatin1String("asdf\n" "new\n" "text\n" "bar\n")); } void KateDocumentTest::testMovingInterfaceSignals() { KTextEditor::DocumentPrivate *doc = new KTextEditor::DocumentPrivate; QSignalSpy aboutToDeleteSpy(doc, SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*))); QSignalSpy aboutToInvalidateSpy(doc, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*))); QCOMPARE(doc->revision(), qint64(0)); QCOMPARE(aboutToInvalidateSpy.count(), 0); QCOMPARE(aboutToDeleteSpy.count(), 0); QTemporaryFile f; f.open(); doc->openUrl(QUrl::fromLocalFile(f.fileName())); QCOMPARE(doc->revision(), qint64(0)); //TODO: gets emitted once in closeFile and once in openFile - is that OK? QCOMPARE(aboutToInvalidateSpy.count(), 2); QCOMPARE(aboutToDeleteSpy.count(), 0); doc->documentReload(); QCOMPARE(doc->revision(), qint64(0)); QCOMPARE(aboutToInvalidateSpy.count(), 4); //TODO: gets emitted once in closeFile and once in openFile - is that OK? QCOMPARE(aboutToDeleteSpy.count(), 0); delete doc; QCOMPARE(aboutToInvalidateSpy.count(), 4); QCOMPARE(aboutToDeleteSpy.count(), 1); } void KateDocumentTest::testSetTextPerformance() { const int lines = 150; const int columns = 80; const int rangeLength = 4; const int rangeGap = 1; Q_ASSERT(columns % (rangeLength + rangeGap) == 0); KTextEditor::DocumentPrivate doc; MovingRangeInvalidator invalidator; connect(&doc, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), &invalidator, SLOT(aboutToInvalidateMovingInterfaceContent())); QString text; QVector ranges; ranges.reserve(lines * columns / (rangeLength + rangeGap)); const QString line = QString().fill('a', columns); for (int l = 0; l < lines; ++l) { text.append(line); text.append('\n'); for (int c = 0; c < columns; c += rangeLength + rangeGap) { ranges << Range(l, c, l, c + rangeLength); } } // replace QBENCHMARK { // init doc.setText(text); foreach (const Range &range, ranges) { invalidator.addRange(doc.newMovingRange(range)); } QCOMPARE(invalidator.ranges().size(), ranges.size()); #ifdef USE_VALGRIND CALLGRIND_START_INSTRUMENTATION #endif doc.setText(text); #ifdef USE_VALGRIND CALLGRIND_STOP_INSTRUMENTATION #endif QCOMPARE(doc.text(), text); QVERIFY(invalidator.ranges().isEmpty()); } } void KateDocumentTest::testRemoveTextPerformance() { const int lines = 5000; const int columns = 80; KTextEditor::DocumentPrivate doc; QString text; const QString line = QString().fill('a', columns); for (int l = 0; l < lines; ++l) { text.append(line); text.append('\n'); } doc.setText(text); // replace QBENCHMARK_ONCE { #ifdef USE_VALGRIND CALLGRIND_START_INSTRUMENTATION #endif doc.editStart(); doc.removeText(doc.documentRange()); doc.editEnd(); #ifdef USE_VALGRIND CALLGRIND_STOP_INSTRUMENTATION #endif } } void KateDocumentTest::testForgivingApiUsage() { KTextEditor::DocumentPrivate doc; QVERIFY(doc.isEmpty()); QVERIFY(doc.replaceText(Range(0, 0, 100, 100), "asdf")); QCOMPARE(doc.text(), QString("asdf")); QCOMPARE(doc.lines(), 1); QVERIFY(doc.replaceText(Range(2, 5, 2, 100), "asdf")); QCOMPARE(doc.lines(), 3); QCOMPARE(doc.text(), QLatin1String("asdf\n\n asdf")); QVERIFY(doc.removeText(Range(0, 0, 1000, 1000))); QVERIFY(doc.removeText(Range(0, 0, 0, 100))); QVERIFY(doc.isEmpty()); doc.insertText(Cursor(100, 0), "foobar"); QCOMPARE(doc.line(100), QString("foobar")); doc.setText("nY\nnYY\n"); QVERIFY(doc.removeText(Range(0, 0, 0, 1000))); } void KateDocumentTest::testAutoBrackets() { KTextEditor::DocumentPrivate doc; auto view = static_cast(doc.createView(nullptr)); auto reset = [&]() { doc.setText(""); view->setCursorPosition(Cursor(0, 0)); }; auto typeText = [&](const QString &text) { for (int i = 0; i < text.size(); ++i) { doc.typeChars(view, text.at(i)); } }; doc.setHighlightingMode ("Normal"); // Just to be sure - view->config()->setAutoBrackets(true); + view->config()->setValue(KateViewConfig::AutoBrackets, true); QString testInput; testInput = ("("); typeText(testInput); QCOMPARE(doc.text(), "()"); reset(); testInput = ("\""); typeText(testInput); QCOMPARE(doc.text(), "\"\""); reset(); testInput = ("'"); typeText(testInput); QCOMPARE(doc.text(), "'"); // In Normal mode there is only one quote to expect // // Switch over to some other mode // doc.setHighlightingMode ("C++"); reset(); typeText(testInput); QCOMPARE(doc.text(), "''"); // Now it must be two reset(); testInput = "('')"; typeText(testInput); // Known bad behaviour in case of nested brackets QCOMPARE(doc.text(), testInput); reset(); testInput = ("foo \"bar\" haz"); typeText(testInput); QCOMPARE(doc.text(), testInput); // Simulate afterwards to add quotes, bug 405089 doc.setText("foo \"bar"); typeText("\" haz"); QCOMPARE(doc.text(), testInput); // Let's check to add brackets to a selection... view->setBlockSelection(false); doc.setText("012xxx678"); view->setSelection(Range(0, 3, 0, 6)); typeText("["); QCOMPARE(doc.text(), "012[xxx]678"); QCOMPARE(view->selectionRange(), Range(0, 4, 0, 7)); // ...over multiple lines.. doc.setText("012xxx678\n012xxx678"); view->setSelection(Range(0, 3, 1, 6)); typeText("["); QCOMPARE(doc.text(), "012[xxx678\n012xxx]678"); QCOMPARE(view->selectionRange(), Range(0, 4, 1, 6)); // ..once again in in block mode with increased complexity.. view->setBlockSelection(true); doc.setText("012xxx678\n012xxx678"); view->setSelection(Range(0, 3, 1, 6)); typeText("[("); QCOMPARE(doc.text(), "012[(xxx)]678\n012[(xxx)]678"); QCOMPARE(view->selectionRange(), Range(0, 5, 1, 8)); // ..and the same with right->left selection view->setBlockSelection(true); doc.setText("012xxx678\n012xxx678"); view->setSelection(Range(0, 6, 1, 3)); typeText("[("); QCOMPARE(doc.text(), "012[(xxx)]678\n012[(xxx)]678"); QCOMPARE(view->selectionRange(), Range(0, 8, 1, 5)); } void KateDocumentTest::testReplaceTabs() { KTextEditor::DocumentPrivate doc; auto view = static_cast(doc.createView(nullptr)); auto reset = [&]() { doc.setText(" Hi!"); view->setCursorPosition(Cursor(0, 0)); }; doc.setHighlightingMode ("C++"); doc.config()->setTabWidth(4); doc.config()->setIndentationMode("cppstyle"); // no replace tabs, no indent pasted text doc.setConfigValue("replace-tabs", false); doc.setConfigValue("indent-pasted-text", false); reset(); doc.insertText(Cursor(0, 0), "\t"); QCOMPARE(doc.text(), QStringLiteral("\t Hi!")); reset(); doc.typeChars(view, "\t"); QCOMPARE(doc.text(), QStringLiteral("\t Hi!")); reset(); doc.paste(view, "some\ncode\n 3\nhi\n"); QCOMPARE(doc.text(), QStringLiteral("some\ncode\n 3\nhi\n Hi!")); // now, enable replace tabs doc.setConfigValue("replace-tabs", true); reset(); doc.insertText(Cursor(0, 0), "\t"); // calling insertText does not replace tabs QCOMPARE(doc.text(), QStringLiteral("\t Hi!")); reset(); doc.typeChars(view, "\t"); // typing text replaces tabs QCOMPARE(doc.text(), QStringLiteral(" Hi!")); reset(); doc.paste(view, "\t"); // pasting text does not with indent-pasted off QCOMPARE(doc.text(), QStringLiteral("\t Hi!")); doc.setConfigValue("indent-pasted-text", true); doc.setText("int main() {\n \n}"); view->setCursorPosition(Cursor(1, 4)); doc.paste(view, "\tHi"); // ... and it still does not with indent-pasted on. // This behaviour is up to discussion. QCOMPARE(doc.text(), QStringLiteral("int main() {\n Hi\n}")); reset(); doc.paste(view, "some\ncode\n 3\nhi"); QCOMPARE(doc.text(), QStringLiteral("some\ncode\n3\nhi Hi!")); } /** * Provides slots to check data sent in specific signals. Slot names are derived from corresponding test names. */ class SignalHandler : public QObject { Q_OBJECT public Q_SLOTS: void slotMultipleLinesRemoved(KTextEditor::Document *, const KTextEditor::Range &, const QString &oldText) { QCOMPARE(oldText, QString("line2\nline3\n")); } void slotNewlineInserted(KTextEditor::Document *, const KTextEditor::Range &range) { QCOMPARE(range, Range(Cursor(1, 4), Cursor(2, 0))); } }; void KateDocumentTest::testRemoveMultipleLines() { KTextEditor::DocumentPrivate doc; doc.setText("line1\n" "line2\n" "line3\n" "line4\n"); SignalHandler handler; connect(&doc, &KTextEditor::DocumentPrivate::textRemoved, &handler, &SignalHandler::slotMultipleLinesRemoved); doc.removeText(Range(1, 0, 3, 0)); } void KateDocumentTest::testInsertNewline() { KTextEditor::DocumentPrivate doc; doc.setText("this is line\n" "this is line2\n"); SignalHandler handler; connect(&doc, SIGNAL(textInserted(KTextEditor::Document*,KTextEditor::Range)), &handler, SLOT(slotNewlineInserted(KTextEditor::Document*,KTextEditor::Range))); doc.editWrapLine(1, 4); } void KateDocumentTest::testInsertAfterEOF() { KTextEditor::DocumentPrivate doc; doc.setText("line0\n" "line1"); const QString input = QLatin1String("line3\n" "line4"); const QString expected = QLatin1String("line0\n" "line1\n" "\n" "line3\n" "line4"); doc.insertText(KTextEditor::Cursor(3, 0), input); QCOMPARE(doc.text(), expected); } // we have two different ways of creating the checksum: // in KateFileLoader and KTextEditor::DocumentPrivate::createDigest. Make // sure, these two implementations result in the same checksum. void KateDocumentTest::testDigest() { // Git hash of test file (git hash-object data/md5checksum.txt): const QByteArray gitHash = "696e6d35a5d9cc28d16e56bdcb2d2a88126b814e"; // QCryptographicHash is used, therefore we need fromHex here const QByteArray fileDigest = QByteArray::fromHex(gitHash); // make sure, Kate::TextBuffer and KTextEditor::DocumentPrivate::createDigest() equal KTextEditor::DocumentPrivate doc; doc.openUrl(QUrl::fromLocalFile(QLatin1String(TEST_DATA_DIR"md5checksum.txt"))); const QByteArray bufferDigest(doc.checksum()); QVERIFY(doc.createDigest()); const QByteArray docDigest(doc.checksum()); QCOMPARE(bufferDigest, fileDigest); QCOMPARE(docDigest, fileDigest); } void KateDocumentTest::testModelines() { // honor document variable indent-width { KTextEditor::DocumentPrivate doc; QCOMPARE(doc.config()->indentationWidth(), 4); doc.readVariableLine(QStringLiteral("kate: indent-width 3;")); QCOMPARE(doc.config()->indentationWidth(), 3); } // honor document variable indent-width with * wildcard { KTextEditor::DocumentPrivate doc; QCOMPARE(doc.config()->indentationWidth(), 4); doc.readVariableLine(QStringLiteral("kate-wildcard(*): indent-width 3;")); QCOMPARE(doc.config()->indentationWidth(), 3); } // ignore document variable indent-width, since the wildcard does not match { KTextEditor::DocumentPrivate doc; QCOMPARE(doc.config()->indentationWidth(), 4); doc.readVariableLine(QStringLiteral("kate-wildcard(*.txt): indent-width 3;")); QCOMPARE(doc.config()->indentationWidth(), 4); } // document variable indent-width, since the wildcard does not match { KTextEditor::DocumentPrivate doc; doc.openUrl(QUrl::fromLocalFile(QLatin1String(TEST_DATA_DIR"modelines.txt"))); QVERIFY(!doc.isEmpty()); // ignore wrong wildcard QCOMPARE(doc.config()->indentationWidth(), 4); doc.readVariableLine(QStringLiteral("kate-wildcard(*.bar): indent-width 3;")); QCOMPARE(doc.config()->indentationWidth(), 4); // read correct wildcard QCOMPARE(doc.config()->indentationWidth(), 4); doc.readVariableLine(QStringLiteral("kate-wildcard(*.txt): indent-width 5;")); QCOMPARE(doc.config()->indentationWidth(), 5); // honor correct wildcard QCOMPARE(doc.config()->indentationWidth(), 5); doc.readVariableLine(QStringLiteral("kate-wildcard(*.foo;*.txt;*.bar): indent-width 6;")); QCOMPARE(doc.config()->indentationWidth(), 6); // ignore incorrect mimetype QCOMPARE(doc.config()->indentationWidth(), 6); doc.readVariableLine(QStringLiteral("kate-mimetype(text/unknown): indent-width 7;")); QCOMPARE(doc.config()->indentationWidth(), 6); // honor correct mimetype QCOMPARE(doc.config()->indentationWidth(), 6); doc.readVariableLine(QStringLiteral("kate-mimetype(text/plain): indent-width 8;")); QCOMPARE(doc.config()->indentationWidth(), 8); // honor correct mimetype QCOMPARE(doc.config()->indentationWidth(), 8); doc.readVariableLine(QStringLiteral("kate-mimetype(text/foo;text/plain;text/bar): indent-width 9;")); QCOMPARE(doc.config()->indentationWidth(), 9); } } void KateDocumentTest::testDefStyleNum() { KTextEditor::DocumentPrivate doc; doc.setText("foo\nbar\nasdf"); QCOMPARE(doc.defStyleNum(0, 0), 0); } void KateDocumentTest::testTypeCharsWithSurrogateAndNewLine() { KTextEditor::DocumentPrivate doc; auto view = static_cast(doc.createView(nullptr)); const uint surrogateUcs4String[] = { 0x1f346, '\n', 0x1f346, 0 }; const auto surrogateString = QString::fromUcs4(surrogateUcs4String); doc.typeChars(view, surrogateString); QCOMPARE(doc.text(), surrogateString); } void KateDocumentTest::testRemoveComposedCharacters() { KTextEditor::DocumentPrivate doc; auto view = static_cast(doc.createView(nullptr)); - view->config()->setBackspaceRemoveComposed(true); + view->config()->setValue(KateViewConfig::BackspaceRemoveComposedCharacters, true); doc.setText(QString::fromUtf8("व्यक्तियों")); doc.del(view, Cursor(0, 0)); QCOMPARE(doc.text(), QString::fromUtf8(("क्तियों"))); doc.backspace(view, Cursor(0, 7)); QCOMPARE(doc.text(), QString::fromUtf8(("क्ति"))); } void KateDocumentTest::testAutoReload() { QTemporaryFile file("AutoReloadTestFile"); file.open(); QTextStream stream(&file); stream << "Hello"; stream.flush(); KTextEditor::DocumentPrivate doc; auto view = static_cast(doc.createView(nullptr)); QVERIFY(doc.openUrl(QUrl::fromLocalFile(file.fileName()))); QCOMPARE(doc.text(), "Hello"); // The cursor should be and stay in the last line... QCOMPARE(view->cursorPosition().line(), doc.documentEnd().line()); doc.autoReloadToggled(true); // Some magic value. You can wait as long as you like after write to file, // without to wait before it doesn't work here. It mostly fails with 500, // it tend to work with 600, but is not guarantied to work even with 800 QTest::qWait(1000); stream << "\nTest"; stream.flush(); // Hardcoded delay m_modOnHdTimer in DocumentPrivate // + min val with which it looks working reliable here QTest::qWait(200 + 800); QCOMPARE(doc.text(), "Hello\nTest"); // ...stay in the last line after reload! QCOMPARE(view->cursorPosition().line(), doc.documentEnd().line()); // Now we have more then one line, set the cursor to some line != last line... Cursor c(0, 3); view->setCursorPosition(c); stream << "\nWorld!"; stream.flush(); file.close(); QTest::qWait(200 + 800); QCOMPARE(doc.text(), "Hello\nTest\nWorld!"); // ...and ensure we have not move around QCOMPARE(view->cursorPosition(), c); } #include "katedocument_test.moc" diff --git a/autotests/src/kateview_test.cpp b/autotests/src/kateview_test.cpp index 1b8d71eb..aed5c5f6 100644 --- a/autotests/src/kateview_test.cpp +++ b/autotests/src/kateview_test.cpp @@ -1,532 +1,532 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kateview_test.h" #include "moc_kateview_test.cpp" #include #include #include #include #include #include #include #include #include #define testNewRow() (QTest::newRow(QString("line %1").arg(__LINE__).toLatin1().data())) using namespace KTextEditor; QTEST_MAIN(KateViewTest) KateViewTest::KateViewTest() : QObject() { KTextEditor::EditorPrivate::enableUnitTestMode(); } KateViewTest::~KateViewTest() { } void KateViewTest::testCoordinatesToCursor() { KTextEditor::DocumentPrivate doc(false, false); doc.setText("Hi World!\nHi\n"); KTextEditor::View* view1 = static_cast(doc.createView(nullptr)); view1->resize(400, 300); view1->show(); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(0, 2))), KTextEditor::Cursor(0, 2)); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(1, 1))), KTextEditor::Cursor(1, 1)); // behind end of line should give an invalid cursor QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(1, 5))), KTextEditor::Cursor::invalid()); QCOMPARE(view1->cursorToCoordinate(KTextEditor::Cursor(3, 1)), QPoint(-1, -1)); // check consistency between cursorToCoordinate(view->cursorPosition() and cursorPositionCoordinates() // random position view1->setCursorPosition(KTextEditor::Cursor(0, 3)); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(view1->cursorPosition())), KTextEditor::Cursor(0, 3)); QCOMPARE(view1->coordinatesToCursor(view1->cursorPositionCoordinates()), KTextEditor::Cursor(0, 3)); // end of line view1->setCursorPosition(KTextEditor::Cursor(0, 9)); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(0, 9))), KTextEditor::Cursor(0, 9)); QCOMPARE(view1->coordinatesToCursor(view1->cursorPositionCoordinates()), KTextEditor::Cursor(0, 9)); // empty line view1->setCursorPosition(KTextEditor::Cursor(2, 0)); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(2, 0))), KTextEditor::Cursor(2, 0)); QCOMPARE(view1->coordinatesToCursor(view1->cursorPositionCoordinates()), KTextEditor::Cursor(2, 0)); // same test again, but with message widget on top visible KTextEditor::Message *message = new KTextEditor::Message("Jo World!", KTextEditor::Message::Information); doc.postMessage(message); // wait 500ms until show animation is finished, so the message widget is visible QTest::qWait(500); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(0, 2))), KTextEditor::Cursor(0, 2)); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(1, 1))), KTextEditor::Cursor(1, 1)); // behind end of line should give an invalid cursor QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(1, 5))), KTextEditor::Cursor::invalid()); QCOMPARE(view1->cursorToCoordinate(KTextEditor::Cursor(3, 1)), QPoint(-1, -1)); } void KateViewTest::testCursorToCoordinates() { KTextEditor::DocumentPrivate doc(false, false); doc.setText("int a;"); KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); view->config()->setDynWordWrap(true); view->show(); // don't crash, see https://bugs.kde.org/show_bug.cgi?id=337863 view->cursorToCoordinate(Cursor(0, 0)); view->cursorToCoordinate(Cursor(1, 0)); view->cursorToCoordinate(Cursor(-1, 0)); } void KateViewTest::testReloadMultipleViews() { QTemporaryFile file("XXXXXX.cpp"); file.open(); QTextStream stream(&file); const QString line = "const char* foo = \"asdf\"\n"; for (int i = 0; i < 200; ++i) { stream << line; } stream << flush; file.close(); KTextEditor::DocumentPrivate doc; QVERIFY(doc.openUrl(QUrl::fromLocalFile(file.fileName()))); QCOMPARE(doc.highlightingMode(), QString("C++")); KTextEditor::ViewPrivate *view1 = new KTextEditor::ViewPrivate(&doc, nullptr); KTextEditor::ViewPrivate *view2 = new KTextEditor::ViewPrivate(&doc, nullptr); view1->show(); view2->show(); QCOMPARE(doc.views().count(), 2); QVERIFY(doc.documentReload()); } void KateViewTest::testTabCursorOnReload() { // testcase for https://bugs.kde.org/show_bug.cgi?id=258480 QTemporaryFile file("XXXXXX.cpp"); file.open(); QTextStream stream(&file); stream << "\tfoo\n"; file.close(); KTextEditor::DocumentPrivate doc; QVERIFY(doc.openUrl(QUrl::fromLocalFile(file.fileName()))); KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); const KTextEditor::Cursor cursor(0, 4); view->setCursorPosition(cursor); QCOMPARE(view->cursorPosition(), cursor); QVERIFY(doc.documentReload()); QCOMPARE(view->cursorPosition(), cursor); } void KateViewTest::testLowerCaseBlockSelection() { // testcase for https://bugs.kde.org/show_bug.cgi?id=258480 KTextEditor::DocumentPrivate doc; doc.setText("nY\nnYY\n"); KTextEditor::ViewPrivate *view1 = new KTextEditor::ViewPrivate(&doc, nullptr); view1->setBlockSelection(true); view1->setSelection(Range(0, 1, 1, 3)); view1->lowercase(); QCOMPARE(doc.text(), QString("ny\nnyy\n")); } namespace { QWidget *findViewInternal(KTextEditor::View* view) { foreach (QObject* child, view->children()) { if (child->metaObject()->className() == QByteArrayLiteral("KateViewInternal")) { return qobject_cast(child); } } return nullptr; } } void KateViewTest::testSelection() { // see also: https://bugs.kde.org/show_bug.cgi?id=277422 // wrong behavior before: // Open file with text // click at end of some line (A) and drag to right, i.e. without selecting anything // click somewhere else (B) // shift click to another place (C) // => expected: selection from B to C // => actual: selection from A to C QTemporaryFile file("XXXXXX.txt"); file.open(); QTextStream stream(&file); stream << "A\n" << "B\n" << "C"; stream << flush; file.close(); KTextEditor::DocumentPrivate doc; QVERIFY(doc.openUrl(QUrl::fromLocalFile(file.fileName()))); KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); view->resize(100, 200); view->show(); QObject *internalView = findViewInternal(view); QVERIFY(internalView); const QPoint afterA = view->cursorToCoordinate(Cursor(0, 1)); const QPoint afterB = view->cursorToCoordinate(Cursor(1, 1)); const QPoint afterC = view->cursorToCoordinate(Cursor(2, 1)); // click after A QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonPress, afterA, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonRelease, afterA, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCOMPARE(view->cursorPosition(), Cursor(0, 1)); // drag to right QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonPress, afterA, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseMove, afterA + QPoint(50, 0), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonRelease, afterA + QPoint(50, 0), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCOMPARE(view->cursorPosition(), Cursor(0, 1)); QVERIFY(!view->selection()); // click after C QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonPress, afterC, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonRelease, afterC, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCOMPARE(view->cursorPosition(), Cursor(2, 1)); // shift+click after B QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonPress, afterB, Qt::LeftButton, Qt::LeftButton, Qt::ShiftModifier)); QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonRelease, afterB, Qt::LeftButton, Qt::LeftButton, Qt::ShiftModifier)); QCOMPARE(view->cursorPosition(), Cursor(1, 1)); QCOMPARE(view->selectionRange(), Range(1, 1, 2, 1)); } void KateViewTest::testDeselectByArrowKeys_data() { QTest::addColumn("text"); testNewRow() << "foobarhaz"; testNewRow() << "كلسشمن يتبكسب"; // We all win, translates Google } void KateViewTest::testDeselectByArrowKeys() { QFETCH(QString, text); KTextEditor::DocumentPrivate doc; doc.setText(text); KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); KTextEditor::Cursor cur1(0, 3); // Start of bar: foo|barhaz KTextEditor::Cursor cur2(0, 6); // End of bar: foobar|haz KTextEditor::Cursor curDelta(0, 1); Range range(cur1, cur2); // Select "bar" // RTL drives me nuts! KTextEditor::Cursor help; if (text.isRightToLeft()) { help = cur1; cur1 = cur2; cur2 = help; } view->setSelection(range); view->setCursorPositionInternal(cur1); view->cursorLeft(); QCOMPARE(view->cursorPosition(), cur1); // Be at begin: foo|barhaz QCOMPARE(view->selection(), false); view->setSelection(range); view->setCursorPositionInternal(cur1); view->cursorRight(); QCOMPARE(view->cursorPosition(), cur2); // Be at end: foobar|haz QCOMPARE(view->selection(), false); - view->config()->setPersistentSelection(true); + view->config()->setValue(KateViewConfig::PersistentSelection, true); view->setSelection(range); view->setCursorPositionInternal(cur1); view->cursorLeft(); // RTL drives me nuts! help = text.isRightToLeft() ? (cur1 + curDelta): (cur1 - curDelta); QCOMPARE(view->cursorPosition(), help); // Be one left: fo|obarhaz QCOMPARE(view->selection(), true); view->setSelection(range); view->setCursorPositionInternal(cur1); view->cursorRight(); // RTL drives me nuts! help = text.isRightToLeft() ? (cur1 - curDelta): (cur1 + curDelta); QCOMPARE(view->cursorPosition(), help); // Be one right: foob|arhaz QCOMPARE(view->selection(), true); } void KateViewTest::testKillline() { KTextEditor::DocumentPrivate doc; doc.insertLines(0, { "foo", "bar", "baz" }); KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); view->setCursorPositionInternal(KTextEditor::Cursor(1, 2)); view->killLine(); QCOMPARE(doc.text(), QLatin1String("foo\nbaz\n")); doc.clear(); QVERIFY(doc.isEmpty()); doc.insertLines(0, { "foo", "bar", "baz", "xxx" }); view->setCursorPositionInternal(KTextEditor::Cursor(1, 2)); view->shiftDown(); view->killLine(); QCOMPARE(doc.text(), QLatin1String("foo\nxxx\n")); } void KateViewTest::testScrollPastEndOfDocument() { // bug 306745 KTextEditor::DocumentPrivate doc; doc.setText(QStringLiteral("0000000000\n" "1111111111\n" "2222222222\n" "3333333333\n" "4444444444")); QCOMPARE(doc.lines(), 5); KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); view->setCursorPosition({ 3, 5 }); view->resize(400, 300); view->show(); // enable "[x] Scroll past end of document" - view->config()->setScrollPastEnd(true); + view->config()->setValue(KateViewConfig::ScrollPastEnd, true); QCOMPARE(view->config()->scrollPastEnd(), true); // disable dynamic word wrap view->config()->setDynWordWrap(false); QCOMPARE(view->config()->dynWordWrap(), false); view->scrollDown(); view->scrollDown(); view->scrollDown(); // at this point, only lines 3333333333 and 4444444444 are visible. view->down(); QCOMPARE(view->cursorPosition(), KTextEditor::Cursor(4, 5)); // verify, that only lines 3333333333 and 4444444444 are still visible. QCOMPARE(view->firstDisplayedLineInternal(KTextEditor::View::RealLine), 3); } void KateViewTest::testFoldFirstLine() { QTemporaryFile file("XXXXXX.cpp"); file.open(); QTextStream stream(&file); stream << "/**\n" << " * foo\n" << " */\n" << "\n" << "int main() {}\n"; file.close(); KTextEditor::DocumentPrivate doc; QVERIFY(doc.openUrl(QUrl::fromLocalFile(file.fileName()))); QCOMPARE(doc.highlightingMode(), QString("C++")); KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); - view->config()->setFoldFirstLine(false); + view->config()->setValue(KateViewConfig::FoldFirstLine, false); view->setCursorPosition({4, 0}); // initially, nothing is folded QVERIFY(view->textFolding().isLineVisible(1)); // now change the config, and expect the header to be folded - view->config()->setFoldFirstLine(true); + view->config()->setValue(KateViewConfig::FoldFirstLine, true); qint64 foldedRangeId = 0; QVERIFY(!view->textFolding().isLineVisible(1, &foldedRangeId)); // now unfold the range QVERIFY(view->textFolding().unfoldRange(foldedRangeId)); QVERIFY(view->textFolding().isLineVisible(1)); // and save the file, we do not expect the folding to change then doc.setModified(true); doc.saveFile(); QVERIFY(view->textFolding().isLineVisible(1)); // now reload the document, nothing should change doc.setModified(false); QVERIFY(doc.documentReload()); QVERIFY(view->textFolding().isLineVisible(1)); } // test for bug https://bugs.kde.org/374163 void KateViewTest::testDragAndDrop() { KTextEditor::DocumentPrivate doc(false, false); doc.setText("line0\n" "line1\n" "line2\n" "\n" "line4"); KTextEditor::View* view = static_cast(doc.createView(nullptr)); view->show(); view->resize(400, 300); QWidget *internalView = findViewInternal(view); QVERIFY(internalView); // select "line1\n" view->setSelection(Range(1, 0, 2, 0)); QCOMPARE(view->selectionRange(), Range(1, 0, 2, 0)); QVERIFY(QTest::qWaitForWindowExposed(view)); QTest::qWait(0); // For whatever reason needed const QPoint startDragPos = internalView->mapFrom(view, view->cursorToCoordinate(KTextEditor::Cursor(1, 2))); const QPoint endDragPos = internalView->mapFrom(view, view->cursorToCoordinate(KTextEditor::Cursor(3, 0))); const QPoint gStartDragPos = internalView->mapToGlobal(startDragPos); const QPoint gEndDragPos = internalView->mapToGlobal(endDragPos); // now drag and drop selected text to Cursor(3, 0) QMouseEvent pressEvent(QEvent::MouseButtonPress, startDragPos, gStartDragPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QCoreApplication::sendEvent(internalView, &pressEvent); // ugly workaround: Drag & Drop has own blocking event queue. Therefore, we need a single-shot timer to // break out of the blocking event queue, see (*) QTimer::singleShot(50, [&](){ QMouseEvent moveEvent(QEvent::MouseMove, endDragPos + QPoint(5, 0), gEndDragPos + QPoint(5, 0), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QMouseEvent releaseEvent(QEvent::MouseButtonRelease, endDragPos, gEndDragPos, Qt::LeftButton, Qt::NoButton, Qt::NoModifier); QCoreApplication::sendEvent(internalView, &moveEvent); QCoreApplication::sendEvent(internalView, &releaseEvent); }); // (*) this somehow blocks... QMouseEvent moveEvent1(QEvent::MouseMove, endDragPos + QPoint(10, 0), gEndDragPos + QPoint(10, 0), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QCoreApplication::sendEvent(internalView, &moveEvent1); QTest::qWait(100); // final tests of dragged text QCOMPARE(doc.text(), QString("line0\n" "line2\n" "line1\n" "\n" "line4")); QCOMPARE(view->cursorPosition(), KTextEditor::Cursor(3, 0)); QCOMPARE(view->selectionRange(), Range(2, 0, 3, 0)); } // test for bug https://bugs.kde.org/402594 void KateViewTest::testGotoMatchingBracket() { KTextEditor::DocumentPrivate doc(false, false); doc.setText("foo(bar)baz"); // 0123456789 KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); const KTextEditor::Cursor cursor1(0, 3); // Starting point on open ( const KTextEditor::Cursor cursor2(0, 8); // Insert Mode differ slightly from... const KTextEditor::Cursor cursor3(0, 7); // Overwrite Mode doc.config()->setOvr(false); // Insert Mode view->setCursorPosition(cursor1); view->toMatchingBracket(); QCOMPARE(view->cursorPosition(), cursor2); view->toMatchingBracket(); QCOMPARE(view->cursorPosition(), cursor1); // Currently has it in Insert Mode also to work when the cursor is placed inside the parentheses view->setCursorPosition(cursor1 + KTextEditor::Cursor(0, 1)); view->toMatchingBracket(); QCOMPARE(view->cursorPosition(), cursor2); view->setCursorPosition(cursor2 + KTextEditor::Cursor(0, -1)); view->toMatchingBracket(); QCOMPARE(view->cursorPosition(), cursor1); doc.config()->setOvr(true);// Overwrite Mode view->setCursorPosition(cursor1); view->toMatchingBracket(); QCOMPARE(view->cursorPosition(), cursor3); view->toMatchingBracket(); QCOMPARE(view->cursorPosition(), cursor1); } // kate: indent-mode cstyle; indent-width 4; replace-tabs on; diff --git a/autotests/src/testutils.cpp b/autotests/src/testutils.cpp index 8689de99..efbafd3a 100644 --- a/autotests/src/testutils.cpp +++ b/autotests/src/testutils.cpp @@ -1,315 +1,315 @@ /** * This file is part of the KDE project * * Copyright (C) 2001,2003 Peter Kelly (pmk@post.com) * Copyright (C) 2003,2004 Stephan Kulow (coolo@kde.org) * Copyright (C) 2004 Dirk Mueller ( mueller@kde.org ) * Copyright 2006, 2007 Leo Savernik (l.savernik@aon.at) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ //BEGIN Includes #include "testutils.h" #include "kateview.h" #include "kateconfig.h" #include "katedocument.h" #include "katescripthelpers.h" #include "ktexteditor/cursor.h" #include "ktexteditor/range.h" #include #include #include #include //END Includes //BEGIN TestScriptEnv TestScriptEnv::TestScriptEnv(KTextEditor::DocumentPrivate *part, bool &cflag) : m_engine(nullptr), m_viewObj(nullptr), m_docObj(nullptr), m_output(nullptr) { m_engine = new QJSEngine(this); // export read & require function and add the require guard object QJSValue functions = m_engine->newQObject(new Kate::ScriptHelper(m_engine)); m_engine->globalObject().setProperty(QStringLiteral("functions"), functions); m_engine->globalObject().setProperty(QStringLiteral("read"), functions.property(QStringLiteral("read"))); m_engine->globalObject().setProperty(QStringLiteral("require"), functions.property(QStringLiteral("require"))); m_engine->globalObject().setProperty(QStringLiteral("require_guard"), m_engine->newObject()); // export debug function m_engine->globalObject().setProperty(QStringLiteral("debug"), functions.property(QStringLiteral("debug"))); // export translation functions m_engine->globalObject().setProperty(QStringLiteral("i18n"), functions.property(QStringLiteral("_i18n"))); m_engine->globalObject().setProperty(QStringLiteral("i18nc"), functions.property(QStringLiteral("_i18nc"))); m_engine->globalObject().setProperty(QStringLiteral("i18np"), functions.property(QStringLiteral("_i18np"))); m_engine->globalObject().setProperty(QStringLiteral("i18ncp"), functions.property(QStringLiteral("_i18ncp"))); KTextEditor::ViewPrivate *view = qobject_cast(part->widget()); m_viewObj = new KateViewObject(m_engine, view); QJSValue sv = m_engine->newQObject(m_viewObj); m_engine->globalObject().setProperty(QStringLiteral("view"), sv); m_engine->globalObject().setProperty(QStringLiteral("v"), sv); m_docObj = new KateDocumentObject(m_engine, view->doc()); QJSValue sd = m_engine->newQObject(m_docObj); m_engine->globalObject().setProperty(QStringLiteral("document"), sd); m_engine->globalObject().setProperty(QStringLiteral("d"), sd); m_output = new OutputObject(view, cflag); QJSValue so = m_engine->newQObject(m_output); m_engine->globalObject().setProperty(QStringLiteral("output"), so); m_engine->globalObject().setProperty(QStringLiteral("out"), so); m_engine->globalObject().setProperty(QStringLiteral("o"), so); } TestScriptEnv::~TestScriptEnv() { // delete explicitly, as the parent is the KTE::Document kpart, which is // reused for all tests. Hence, we explicitly have to delete the bindings. delete m_output; m_output = nullptr; delete m_docObj; m_docObj = nullptr; delete m_viewObj; m_viewObj = nullptr; // delete this too, although this should also be automagically be freed delete m_engine; m_engine = nullptr; // kDebug() << "deleted"; } //END TestScriptEnv //BEGIN KateViewObject KateViewObject::KateViewObject(QJSEngine *engine, KTextEditor::ViewPrivate *view) : KateScriptView(engine) { setView(view); } KateViewObject::~KateViewObject() { // kDebug() << "deleted"; } // Implements a function that calls an edit function repeatedly as specified by // its first parameter (once if not specified). #define REP_CALL(func) \ void KateViewObject::func(int cnt) { \ while (cnt--) { view()->func(); } \ } REP_CALL(keyReturn) REP_CALL(backspace) REP_CALL(deleteWordLeft) REP_CALL(keyDelete) REP_CALL(deleteWordRight) REP_CALL(transpose) REP_CALL(cursorLeft) REP_CALL(shiftCursorLeft) REP_CALL(cursorRight) REP_CALL(shiftCursorRight) REP_CALL(wordLeft) REP_CALL(shiftWordLeft) REP_CALL(wordRight) REP_CALL(shiftWordRight) REP_CALL(home) REP_CALL(shiftHome) REP_CALL(end) REP_CALL(shiftEnd) REP_CALL(up) REP_CALL(shiftUp) REP_CALL(down) REP_CALL(shiftDown) REP_CALL(scrollUp) REP_CALL(scrollDown) REP_CALL(topOfView) REP_CALL(shiftTopOfView) REP_CALL(bottomOfView) REP_CALL(shiftBottomOfView) REP_CALL(pageUp) REP_CALL(shiftPageUp) REP_CALL(pageDown) REP_CALL(shiftPageDown) REP_CALL(top) REP_CALL(shiftTop) REP_CALL(bottom) REP_CALL(shiftBottom) REP_CALL(toMatchingBracket) REP_CALL(shiftToMatchingBracket) #undef REP_CALL bool KateViewObject::type(const QString &str) { return view()->doc()->typeChars(view(), str); } void KateViewObject::setAutoBrackets(bool enable) { - view()->config()->setAutoBrackets(enable); + view()->config()->setValue(KateViewConfig::AutoBrackets, enable); } #define ALIAS(alias, func) \ void KateViewObject::alias(int cnt) { \ func(cnt); \ } ALIAS(enter, keyReturn) ALIAS(cursorPrev, cursorLeft) ALIAS(left, cursorLeft) ALIAS(prev, cursorLeft) ALIAS(shiftCursorPrev, shiftCursorLeft) ALIAS(shiftLeft, shiftCursorLeft) ALIAS(shiftPrev, shiftCursorLeft) ALIAS(cursorNext, cursorRight) ALIAS(right, cursorRight) ALIAS(next, cursorRight) ALIAS(shiftCursorNext, shiftCursorRight) ALIAS(shiftRight, shiftCursorRight) ALIAS(shiftNext, shiftCursorRight) ALIAS(wordPrev, wordLeft) ALIAS(shiftWordPrev, shiftWordLeft) ALIAS(wordNext, wordRight) ALIAS(shiftWordNext, shiftWordRight) #undef ALIAS //END KateViewObject //BEGIN KateDocumentObject KateDocumentObject::KateDocumentObject(QJSEngine *engine, KTextEditor::DocumentPrivate *doc) : KateScriptDocument(engine) { setDocument(doc); } KateDocumentObject::~KateDocumentObject() { // kDebug() << "deleted"; } //END KateDocumentObject //BEGIN OutputObject OutputObject::OutputObject(KTextEditor::ViewPrivate *v, bool &cflag) : view(v), cflag(cflag) { } OutputObject::~OutputObject() { // kDebug() << "deleted"; } void OutputObject::output(bool cp, bool ln) { QString str; // FIXME: This is not available with QtQml, but not sure if we need it // for (int i = 0; i < context()->argumentCount(); ++i) { // QJSValue arg = context()->argument(i); // str += arg.toString(); // } if (cp) { KTextEditor::Cursor c = view->cursorPosition(); str += QLatin1Char('(') + QString::number(c.line()) + QLatin1Char(',') + QString::number(c.column()) + QLatin1Char(')'); } if (ln) { str += QLatin1Char('\n'); } view->insertText(str); cflag = true; } void OutputObject::write() { output(false, false); } void OutputObject::writeln() { output(false, true); } void OutputObject::writeLn() { output(false, true); } void OutputObject::print() { output(false, false); } void OutputObject::println() { output(false, true); } void OutputObject::printLn() { output(false, true); } void OutputObject::writeCursorPosition() { output(true, false); } void OutputObject::writeCursorPositionln() { output(true, true); } void OutputObject::cursorPosition() { output(true, false); } void OutputObject::cursorPositionln() { output(true, true); } void OutputObject::cursorPositionLn() { output(true, true); } void OutputObject::pos() { output(true, false); } void OutputObject::posln() { output(true, true); } void OutputObject::posLn() { output(true, true); } //END OutputObject diff --git a/autotests/src/vimode/base.cpp b/autotests/src/vimode/base.cpp index 70219d34..91583618 100644 --- a/autotests/src/vimode/base.cpp +++ b/autotests/src/vimode/base.cpp @@ -1,337 +1,337 @@ /* * This file is part of the KDE libraries * * Copyright (C) 2014 Miquel Sabaté Solà * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include "base.h" #include "vimode/macros.h" #include "vimode/mappings.h" #include "vimode/globalstate.h" using namespace KateVi; using namespace KTextEditor; //BEGIN: BaseTest BaseTest::BaseTest() { kate_view = nullptr; kate_document = nullptr; mainWindow = new QMainWindow; auto centralWidget = new QWidget(); mainWindowLayout = new QVBoxLayout(centralWidget); centralWidget->setLayout(mainWindowLayout); mainWindow->setCentralWidget(centralWidget); mainWindow->resize(640, 480); m_codesToModifiers.insert("ctrl", Qt::ControlModifier); m_codesToModifiers.insert("alt", Qt::AltModifier); m_codesToModifiers.insert("meta", Qt::MetaModifier); m_codesToModifiers.insert("keypad", Qt::KeypadModifier); m_codesToSpecialKeys.insert("backspace", Qt::Key_Backspace); m_codesToSpecialKeys.insert("esc", Qt::Key_Escape); m_codesToSpecialKeys.insert("return", Qt::Key_Return); m_codesToSpecialKeys.insert("enter", Qt::Key_Enter); m_codesToSpecialKeys.insert("left", Qt::Key_Left); m_codesToSpecialKeys.insert("right", Qt::Key_Right); m_codesToSpecialKeys.insert("up", Qt::Key_Up); m_codesToSpecialKeys.insert("down", Qt::Key_Down); m_codesToSpecialKeys.insert("home", Qt::Key_Home); m_codesToSpecialKeys.insert("end", Qt::Key_End); m_codesToSpecialKeys.insert("delete", Qt::Key_Delete); m_codesToSpecialKeys.insert("insert", Qt::Key_Insert); m_codesToSpecialKeys.insert("pageup", Qt::Key_PageUp); m_codesToSpecialKeys.insert("pagedown", Qt::Key_PageDown); } BaseTest::~BaseTest() { delete kate_document; } void BaseTest::waitForCompletionWidgetToActivate(KTextEditor::ViewPrivate *kate_view) { const QDateTime start = QDateTime::currentDateTime(); while (start.msecsTo(QDateTime::currentDateTime()) < 1000) { if (kate_view->isCompletionActive()) { break; } QApplication::processEvents(); } QVERIFY(kate_view->isCompletionActive()); } void BaseTest::init() { delete kate_view; delete kate_document; kate_document = new KTextEditor::DocumentPrivate(); // fixed indentation options kate_document->config()->setTabWidth(8); kate_document->config()->setIndentationWidth(2); kate_document->config()->setReplaceTabsDyn(false); kate_view = new KTextEditor::ViewPrivate(kate_document, mainWindow); mainWindowLayout->addWidget(kate_view); kate_view->setInputMode(View::ViInputMode); Q_ASSERT(kate_view->currentInputMode()->viewInputMode() == KTextEditor::View::ViInputMode); vi_input_mode = dynamic_cast(kate_view->currentInputMode()); vi_input_mode_manager = vi_input_mode->viInputModeManager(); Q_ASSERT(vi_input_mode_manager); vi_global = vi_input_mode->globalState(); Q_ASSERT(vi_global); kate_document->config()->setShowSpaces(KateDocumentConfig::Trailing); // Flush out some issues in the KateRenderer when rendering spaces. - kate_view->config()->setScrollBarMiniMap(false); - kate_view->config()->setScrollBarPreview(false); + kate_view->config()->setValue(KateViewConfig::ShowScrollBarMiniMap, false); + kate_view->config()->setValue(KateViewConfig::ShowScrollBarPreview, false); connect(kate_document, &KTextEditor::DocumentPrivate::textInserted, this, &BaseTest::textInserted); connect(kate_document, &KTextEditor::DocumentPrivate::textRemoved, this, &BaseTest::textRemoved); } void BaseTest::TestPressKey(const QString &str) { if (m_firstBatchOfKeypressesForTest) { qDebug() << "\n\n>>> running command " << str << " on text " << kate_document->text(); } else { qDebug() << "\n>>> running further keypresses " << str << " on text " << kate_document->text(); } m_firstBatchOfKeypressesForTest = false; for (int i = 0; i < str.length(); i++) { Qt::KeyboardModifiers keyboard_modifier = Qt::NoModifier; QString key; int keyCode = -1; // Looking for keyboard modifiers if (str[i] == QChar('\\')) { int endOfModifier = -1; Qt::KeyboardModifier parsedModifier = parseCodedModifier(str, i, &endOfModifier); int endOfSpecialKey = -1; Qt::Key parsedSpecialKey = parseCodedSpecialKey(str, i, &endOfSpecialKey); if (parsedModifier != Qt::NoModifier) { keyboard_modifier = parsedModifier; // Move to the character after the '-' in the modifier. i = endOfModifier + 1; // Is this a modifier plus special key? int endOfSpecialKeyAfterModifier = -1; const Qt::Key parsedCodedSpecialKeyAfterModifier = parseCodedSpecialKey(str, i, &endOfSpecialKeyAfterModifier); if (parsedCodedSpecialKeyAfterModifier != Qt::Key_unknown) { key = QString(parsedCodedSpecialKeyAfterModifier); keyCode = parsedCodedSpecialKeyAfterModifier; i = endOfSpecialKeyAfterModifier; } } else if (parsedSpecialKey != Qt::Key_unknown) { key = QString(parsedSpecialKey); keyCode = parsedSpecialKey; i = endOfSpecialKey; } else if (str.mid(i, 2) == QString("\\:")) { int start_cmd = i + 2; for (i += 2; true; i++) { if (str.at(i) == '\\') { if (i + 1 < str.length() && str.at(i + 1) == '\\') { // A backslash within a command; skip. i += 2; } else { // End of command. break; } } } const QString commandToExecute = str.mid(start_cmd, i - start_cmd).replace("\\\\", "\\"); qDebug() << "Executing command directly from ViModeTest:\n" << commandToExecute; vi_input_mode->viModeEmulatedCommandBar()->executeCommand(commandToExecute); // We've handled the command; go back round the loop, avoiding sending // the closing \ to vi_input_mode_manager. continue; } else if (str.mid(i, 2) == QString("\\\\")) { key = QString("\\"); keyCode = Qt::Key_Backslash; i++; } else { Q_ASSERT(false); //Do not use "\" in tests except for modifiers, command mode (\\:) and literal backslashes "\\\\") } } if (keyCode == -1) { key = str[i]; keyCode = key[0].unicode(); // Kate Vim mode's internals identifier e.g. CTRL-C by Qt::Key_C plus the control modifier, // so we need to translate e.g. 'c' to Key_C. if (key[0].isLetter()) { if (key[0].toLower() == key[0]) { keyCode = keyCode - 'a' + Qt::Key_A; } else { keyCode = keyCode - 'A' + Qt::Key_A; keyboard_modifier |= Qt::ShiftModifier; } } } QKeyEvent *key_event = new QKeyEvent(QEvent::KeyPress, keyCode, keyboard_modifier, key); // Attempt to simulate how Qt usually sends events - typically, we want to send them // to kate_view->focusProxy() (which is a KateViewInternal). QWidget *destWidget = nullptr; if (QApplication::activePopupWidget()) { // According to the docs, the activePopupWidget, if present, takes all events. destWidget = QApplication::activePopupWidget(); } else if (QApplication::focusWidget()) { if (QApplication::focusWidget()->focusProxy()) { destWidget = QApplication::focusWidget()->focusProxy(); } else { destWidget = QApplication::focusWidget(); } } else { destWidget = kate_view->focusProxy(); } QApplication::postEvent(destWidget, key_event); QApplication::sendPostedEvents(); } } void BaseTest::BeginTest(const QString &original) { vi_input_mode_manager->viEnterNormalMode(); vi_input_mode->reset(); vi_input_mode_manager = vi_input_mode->viInputModeManager(); kate_document->setText(original); kate_document->undoManager()->clearUndo(); kate_document->undoManager()->clearRedo(); kate_view->setCursorPosition(Cursor(0, 0)); m_firstBatchOfKeypressesForTest = true; } void BaseTest::FinishTest_(int line, const char *file, const QString &expected, Expectation expectation, const QString &failureReason) { if (expectation == ShouldFail) { if (!QTest::qExpectFail("", failureReason.toLocal8Bit().constData(), QTest::Continue, file, line)) { return; } qDebug() << "Actual text:\n\t" << kate_document->text() << "\nShould be (for this test to pass):\n\t" << expected; } if (!QTest::qCompare(kate_document->text(), expected, "kate_document->text()", "expected_text", file, line)) { return; } Q_ASSERT(!emulatedCommandBarTextEdit()->isVisible() && "Make sure you close the command bar before the end of a test!"); } void BaseTest::DoTest_(int line, const char *file, const QString &original, const QString &command, const QString &expected, Expectation expectation, const QString &failureReason) { BeginTest(original); TestPressKey(command); FinishTest_(line, file, expected, expectation, failureReason); } Qt::KeyboardModifier BaseTest::parseCodedModifier(const QString &string, int startPos, int *destEndOfCodedModifier) { foreach (const QString &modifierCode, m_codesToModifiers.keys()) { // The "+2" is from the leading '\' and the trailing '-' if (string.mid(startPos, modifierCode.length() + 2) == QString("\\") + modifierCode + "-") { if (destEndOfCodedModifier) { // destEndOfCodeModifier lies on the trailing '-'. *destEndOfCodedModifier = startPos + modifierCode.length() + 1; Q_ASSERT(string[*destEndOfCodedModifier] == '-'); } return m_codesToModifiers.value(modifierCode); } } return Qt::NoModifier; } Qt::Key BaseTest::parseCodedSpecialKey(const QString &string, int startPos, int *destEndOfCodedKey) { foreach (const QString &specialKeyCode, m_codesToSpecialKeys.keys()) { // "+1" is for the leading '\'. if (string.mid(startPos, specialKeyCode.length() + 1) == QString("\\") + specialKeyCode) { if (destEndOfCodedKey) { *destEndOfCodedKey = startPos + specialKeyCode.length(); } return m_codesToSpecialKeys.value(specialKeyCode); } } return Qt::Key_unknown; } KateVi::EmulatedCommandBar * BaseTest::emulatedCommandBar() { KateVi::EmulatedCommandBar *emulatedCommandBar = vi_input_mode->viModeEmulatedCommandBar(); Q_ASSERT(emulatedCommandBar); return emulatedCommandBar; } QLineEdit * BaseTest::emulatedCommandBarTextEdit() { QLineEdit *emulatedCommandBarText = emulatedCommandBar()->findChild("commandtext"); Q_ASSERT(emulatedCommandBarText); return emulatedCommandBarText; } void BaseTest::ensureKateViewVisible() { mainWindow->show(); kate_view->show(); QApplication::setActiveWindow(mainWindow); kate_view->setFocus(); const QDateTime startTime = QDateTime::currentDateTime(); while (startTime.msecsTo(QDateTime::currentDateTime()) < 3000 && !mainWindow->isActiveWindow()) { QApplication::processEvents(); } QVERIFY(kate_view->isVisible()); QVERIFY(mainWindow->isActiveWindow()); } void BaseTest::clearAllMappings() { vi_global->mappings()->clear(Mappings::NormalModeMapping); vi_global->mappings()->clear(Mappings::VisualModeMapping); vi_global->mappings()->clear(Mappings::InsertModeMapping); vi_global->mappings()->clear(Mappings::CommandModeMapping); } void BaseTest::clearAllMacros() { vi_global->macros()->clear(); } void BaseTest::textInserted(Document *document, KTextEditor::Range range) { m_docChanges.append(DocChange(DocChange::TextInserted, range, document->text(range))); } void BaseTest::textRemoved(Document *document, KTextEditor::Range range) { Q_UNUSED(document); m_docChanges.append(DocChange(DocChange::TextRemoved, range)); } //END: BaseTest diff --git a/autotests/src/vimode/completion.cpp b/autotests/src/vimode/completion.cpp index 30471ecc..4bb682df 100644 --- a/autotests/src/vimode/completion.cpp +++ b/autotests/src/vimode/completion.cpp @@ -1,610 +1,610 @@ /* * This file is part of the KDE libraries * * Copyright (C) 2014 Miquel Sabaté Solà * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include "completion.h" #include "fakecodecompletiontestmodel.h" #include "vimode/mappings.h" #include "vimode/globalstate.h" using namespace KTextEditor; using KateVi::Mappings; QTEST_MAIN(CompletionTest) //BEGIN: VimCodeCompletionTestModel VimCodeCompletionTestModel::VimCodeCompletionTestModel(KTextEditor::View *parent) : KTextEditor::CodeCompletionModel(parent) { setRowCount(3); cc()->setAutomaticInvocationEnabled(true); // It would add additional items and we don't want that in tests cc()->unregisterCompletionModel(KTextEditor::EditorPrivate::self()->wordCompletionModel()); cc()->registerCompletionModel(this); } QVariant VimCodeCompletionTestModel::data(const QModelIndex &index, int role) const { // Order is important, here, as the completion widget seems to do its own sorting. const char *completions[] = { "completion1", "completion2", "completion3" }; if (role == Qt::DisplayRole) { if (index.column() == Name) { return QString(completions[index.row()]); } } return QVariant(); } CodeCompletionInterface * VimCodeCompletionTestModel::cc() const { return dynamic_cast(const_cast(QObject::parent())); } //END: VimCodeCompletionTestModel //BEGIN: CodeCompletionInterface FailTestOnInvocationModel::FailTestOnInvocationModel(KTextEditor::View *parent) : KTextEditor::CodeCompletionModel(parent) { setRowCount(3); cc()->setAutomaticInvocationEnabled(true); // It would add additional items and we don't want that in tests. cc()->unregisterCompletionModel(EditorPrivate::self()->wordCompletionModel()); cc()->registerCompletionModel(this); } QVariant FailTestOnInvocationModel::data(const QModelIndex &index, int role) const { Q_UNUSED(index); Q_UNUSED(role); failTest(); return QVariant(); } void FailTestOnInvocationModel::failTest() const { QFAIL("Shouldn't be invoking me!"); } CodeCompletionInterface * FailTestOnInvocationModel::cc() const { return dynamic_cast(const_cast(QObject::parent())); } //END: CodeCompletionInterface //BEGIN: CompletionTest void CompletionTest::FakeCodeCompletionTests() { // Test that FakeCodeCompletionTestModel behaves similar to the code-completion in e.g. KDevelop. const bool oldStealKeys = KateViewConfig::global()->viInputModeStealKeys(); - KateViewConfig::global()->setViInputModeStealKeys(true); // For Ctrl-P, Ctrl-N etc + KateViewConfig::global()->setValue(KateViewConfig::ViInputModeStealKeys, true); // For Ctrl-P, Ctrl-N etc ensureKateViewVisible(); // KTextEditor::ViewPrivate needs to be visible for the completion widget. FakeCodeCompletionTestModel *fakeCodeCompletionModel = new FakeCodeCompletionTestModel(kate_view); kate_view->registerCompletionModel(fakeCodeCompletionModel); fakeCodeCompletionModel->setCompletions({ "completionA", "completionB", "completionC" }); DoTest("", "i\\ctrl-p\\enter", "completionC"); DoTest("", "i\\ctrl-p\\ctrl-p\\enter", "completionB"); DoTest("", "i\\ctrl-p\\ctrl-p\\ctrl-p\\enter", "completionA"); DoTest("", "i\\ctrl-p\\ctrl-p\\ctrl-p\\ctrl-p\\enter", "completionC"); // If no word before cursor, don't delete any text. BeginTest(""); clearTrackedDocumentChanges(); TestPressKey("i\\ctrl- \\enter"); QCOMPARE(m_docChanges.length(), 1); FinishTest("completionA"); // Apparently, we must delete the word before the cursor upon completion // (even if we replace it with identical text!) BeginTest("compl"); TestPressKey("ea"); clearTrackedDocumentChanges(); TestPressKey("\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 2); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextRemoved); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 0), Cursor(0, 5))); QCOMPARE(m_docChanges[1].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[1].changeRange(), Range(Cursor(0, 0), Cursor(0, 11))); QCOMPARE(m_docChanges[1].newText(), QString("completionA")); FinishTest("completionA"); // A "word" is currently alphanumeric, plus underscore. fakeCodeCompletionModel->setCompletions({ "w_123completion" }); BeginTest("(w_123"); TestPressKey("ea"); clearTrackedDocumentChanges(); TestPressKey("\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 2); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextRemoved); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 1), Cursor(0, 6))); QCOMPARE(m_docChanges[1].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[1].changeRange(), Range(Cursor(0, 1), Cursor(0, 16))); QCOMPARE(m_docChanges[1].newText(), QString("w_123completion")); FinishTest("(w_123completion"); // "Removing tail on complete" is apparently done in three stages: // delete word up to the cursor; insert new word; then delete remainder. fakeCodeCompletionModel->setRemoveTailOnComplete(true); BeginTest("(w_123comp"); TestPressKey("6li"); clearTrackedDocumentChanges(); TestPressKey("\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 3); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextRemoved); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 1), Cursor(0, 6))); QCOMPARE(m_docChanges[1].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[1].changeRange(), Range(Cursor(0, 1), Cursor(0, 16))); QCOMPARE(m_docChanges[1].newText(), QString("w_123completion")); QCOMPARE(m_docChanges[2].changeType(), DocChange::TextRemoved); QCOMPARE(m_docChanges[2].changeRange(), Range(Cursor(0, 16), Cursor(0, 20))); FinishTest("(w_123completion"); // If we don't remove tail, just delete up to the cursor and insert. fakeCodeCompletionModel->setRemoveTailOnComplete(false); BeginTest("(w_123comp"); TestPressKey("6li"); clearTrackedDocumentChanges(); TestPressKey("\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 2); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextRemoved); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 1), Cursor(0, 6))); QCOMPARE(m_docChanges[1].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[1].changeRange(), Range(Cursor(0, 1), Cursor(0, 16))); QCOMPARE(m_docChanges[1].newText(), QString("w_123completion")); FinishTest("(w_123completioncomp"); // If no opening bracket after the cursor, a function taking no arguments // is added as "function()", and the cursor placed after the closing ")". // The addition of "function()" is done in two steps: first "function", then "()". BeginTest("object->"); fakeCodeCompletionModel->setCompletions({ "functionCall()" }); fakeCodeCompletionModel->setRemoveTailOnComplete(true); clearTrackedDocumentChanges(); TestPressKey("$a\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 2); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 8), Cursor(0, 20))); QCOMPARE(m_docChanges[0].newText(), QString("functionCall")); QCOMPARE(m_docChanges[1].changeRange(), Range(Cursor(0, 20), Cursor(0, 22))); QCOMPARE(m_docChanges[1].newText(), QString("()")); TestPressKey("X"); FinishTest("object->functionCall()X"); // If no opening bracket after the cursor, a function taking at least one argument // is added as "function()", and the cursor placed after the opening "(". // The addition of "function()" is done in two steps: first "function", then "()". qDebug() << "Fleep"; BeginTest("object->"); fakeCodeCompletionModel->setCompletions({ "functionCall(...)" }); fakeCodeCompletionModel->setRemoveTailOnComplete(true); clearTrackedDocumentChanges(); TestPressKey("$a\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 2); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 8), Cursor(0, 20))); QCOMPARE(m_docChanges[0].newText(), QString("functionCall")); QCOMPARE(m_docChanges[1].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[1].changeRange(), Range(Cursor(0, 20), Cursor(0, 22))); QCOMPARE(m_docChanges[1].newText(), QString("()")); TestPressKey("X"); FinishTest("object->functionCall(X)"); // If there is an opening bracket after the cursor, we merge the function call // with that. // Even if the function takes no arguments, we still place the cursor after the opening bracket, // in contrast to the case where there is no opening bracket after the cursor. // No brackets are added. No removals occur if there is no word before the cursor. BeginTest("object->("); fakeCodeCompletionModel->setCompletions({ "functionCall()" }); fakeCodeCompletionModel->setRemoveTailOnComplete(true); clearTrackedDocumentChanges(); TestPressKey("f(i\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 1); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 8), Cursor(0, 20))); QCOMPARE(m_docChanges[0].newText(), QString("functionCall")); TestPressKey("X"); FinishTest("object->functionCall(X"); // There can't be any non-whitespace between cursor position and opening bracket, though! BeginTest("object->|( ("); fakeCodeCompletionModel->setCompletions({ "functionCall()" }); fakeCodeCompletionModel->setRemoveTailOnComplete(true); clearTrackedDocumentChanges(); TestPressKey("f>a\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 2); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 8), Cursor(0, 20))); QCOMPARE(m_docChanges[0].newText(), QString("functionCall")); QCOMPARE(m_docChanges[1].changeRange(), Range(Cursor(0, 20), Cursor(0, 22))); QCOMPARE(m_docChanges[1].newText(), QString("()")); TestPressKey("X"); FinishTest("object->functionCall()X|( ("); // Whitespace before the bracket is fine, though. BeginTest("object-> (<-Cursor here!"); fakeCodeCompletionModel->setCompletions({ "functionCall()" }); fakeCodeCompletionModel->setRemoveTailOnComplete(true); clearTrackedDocumentChanges(); TestPressKey("f>a\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 1); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 8), Cursor(0, 20))); QCOMPARE(m_docChanges[0].newText(), QString("functionCall")); TestPressKey("X"); FinishTest("object->functionCall (X<-Cursor here!"); // Be careful with positioning the cursor if we delete leading text! BeginTest("object-> (<-Cursor here!"); fakeCodeCompletionModel->setCompletions({ "functionCall()" }); fakeCodeCompletionModel->setRemoveTailOnComplete(true); clearTrackedDocumentChanges(); TestPressKey("f>afunct"); clearTrackedDocumentChanges(); TestPressKey("\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 2); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextRemoved); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 8), Cursor(0, 13))); QCOMPARE(m_docChanges[1].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[1].changeRange(), Range(Cursor(0, 8), Cursor(0, 20))); QCOMPARE(m_docChanges[1].newText(), QString("functionCall")); TestPressKey("X"); FinishTest("object->functionCall (X<-Cursor here!"); // If we're removing tail on complete, it's whether there is a suitable opening // bracket *after* the word (not the cursor) that's important. BeginTest("object->function (<-Cursor here!"); fakeCodeCompletionModel->setCompletions({ "functionCall()" }); fakeCodeCompletionModel->setRemoveTailOnComplete(true); clearTrackedDocumentChanges(); TestPressKey("12li"); // Start inserting before the "t" in "function" clearTrackedDocumentChanges(); TestPressKey("\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 3); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextRemoved); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 8), Cursor(0, 12))); QCOMPARE(m_docChanges[1].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[1].changeRange(), Range(Cursor(0, 8), Cursor(0, 20))); QCOMPARE(m_docChanges[1].newText(), QString("functionCall")); QCOMPARE(m_docChanges[2].changeType(), DocChange::TextRemoved); qDebug() << "m_docChanges[2].changeRange(): " << m_docChanges[2].changeRange(); QCOMPARE(m_docChanges[2].changeRange(), Range(Cursor(0, 20), Cursor(0, 24))); TestPressKey("X"); FinishTest("object->functionCall (X<-Cursor here!"); // Repeat of bracket-merging stuff, this time for functions that take at least one argument. BeginTest("object->("); fakeCodeCompletionModel->setCompletions({ "functionCall(...)" }); fakeCodeCompletionModel->setRemoveTailOnComplete(true); clearTrackedDocumentChanges(); TestPressKey("f(i\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 1); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextInserted); qDebug() << "Range: " << m_docChanges[0].changeRange(); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 8), Cursor(0, 20))); QCOMPARE(m_docChanges[0].newText(), QString("functionCall")); TestPressKey("X"); FinishTest("object->functionCall(X"); // There can't be any non-whitespace between cursor position and opening bracket, though! BeginTest("object->|( ("); fakeCodeCompletionModel->setCompletions({ "functionCall(...)" }); fakeCodeCompletionModel->setRemoveTailOnComplete(true); clearTrackedDocumentChanges(); TestPressKey("f>a\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 2); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 8), Cursor(0, 20))); QCOMPARE(m_docChanges[0].newText(), QString("functionCall")); QCOMPARE(m_docChanges[1].changeRange(), Range(Cursor(0, 20), Cursor(0, 22))); QCOMPARE(m_docChanges[1].newText(), QString("()")); TestPressKey("X"); FinishTest("object->functionCall(X)|( ("); // Whitespace before the bracket is fine, though. BeginTest("object-> (<-Cursor here!"); qDebug() << "NooooO"; fakeCodeCompletionModel->setCompletions({ "functionCall(...)" }); fakeCodeCompletionModel->setRemoveTailOnComplete(true); clearTrackedDocumentChanges(); TestPressKey("f>a\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 1); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 8), Cursor(0, 20))); QCOMPARE(m_docChanges[0].newText(), QString("functionCall")); TestPressKey("X"); FinishTest("object->functionCall (X<-Cursor here!"); // Be careful with positioning the cursor if we delete leading text! BeginTest("object-> (<-Cursor here!"); fakeCodeCompletionModel->setCompletions({ "functionCall(...)" }); fakeCodeCompletionModel->setRemoveTailOnComplete(true); clearTrackedDocumentChanges(); TestPressKey("f>afunct"); clearTrackedDocumentChanges(); TestPressKey("\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 2); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextRemoved); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 8), Cursor(0, 13))); QCOMPARE(m_docChanges[1].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[1].changeRange(), Range(Cursor(0, 8), Cursor(0, 20))); QCOMPARE(m_docChanges[1].newText(), QString("functionCall")); TestPressKey("X"); FinishTest("object->functionCall (X<-Cursor here!"); // If we're removing tail on complete, it's whether there is a suitable opening // bracket *after* the word (not the cursor) that's important. BeginTest("object->function (<-Cursor here!"); fakeCodeCompletionModel->setCompletions({ "functionCall(...)" }); fakeCodeCompletionModel->setRemoveTailOnComplete(true); clearTrackedDocumentChanges(); TestPressKey("12li"); // Start inserting before the "t" in "function" clearTrackedDocumentChanges(); TestPressKey("\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 3); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextRemoved); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 8), Cursor(0, 12))); QCOMPARE(m_docChanges[1].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[1].changeRange(), Range(Cursor(0, 8), Cursor(0, 20))); QCOMPARE(m_docChanges[1].newText(), QString("functionCall")); QCOMPARE(m_docChanges[2].changeType(), DocChange::TextRemoved); qDebug() << "m_docChanges[2].changeRange(): " << m_docChanges[2].changeRange(); QCOMPARE(m_docChanges[2].changeRange(), Range(Cursor(0, 20), Cursor(0, 24))); TestPressKey("X"); FinishTest("object->functionCall (X<-Cursor here!"); // Deal with function completions which add a ";". BeginTest(""); fakeCodeCompletionModel->setCompletions({ "functionCall();" }); clearTrackedDocumentChanges(); TestPressKey("ifun"); clearTrackedDocumentChanges(); TestPressKey("\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 3); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextRemoved); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 0), Cursor(0, 3))); QCOMPARE(m_docChanges[1].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[1].changeRange(), Range(Cursor(0, 0), Cursor(0, 12))); QCOMPARE(m_docChanges[1].newText(), QString("functionCall")); QCOMPARE(m_docChanges[2].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[2].changeRange(), Range(Cursor(0, 12), Cursor(0, 15))); QCOMPARE(m_docChanges[2].newText(), QString("();")); FinishTest("functionCall();"); BeginTest(""); fakeCodeCompletionModel->setCompletions({ "functionCall();" }); TestPressKey("ifun\\ctrl- \\enterX"); FinishTest("functionCall();X"); BeginTest(""); fakeCodeCompletionModel->setCompletions({ "functionCall(...);" }); clearTrackedDocumentChanges(); TestPressKey("ifun"); clearTrackedDocumentChanges(); TestPressKey("\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 3); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextRemoved); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 0), Cursor(0, 3))); QCOMPARE(m_docChanges[1].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[1].changeRange(), Range(Cursor(0, 0), Cursor(0, 12))); QCOMPARE(m_docChanges[1].newText(), QString("functionCall")); QCOMPARE(m_docChanges[2].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[2].changeRange(), Range(Cursor(0, 12), Cursor(0, 15))); QCOMPARE(m_docChanges[2].newText(), QString("();")); FinishTest("functionCall();"); BeginTest(""); fakeCodeCompletionModel->setCompletions({ "functionCall(...);" }); TestPressKey("ifun\\ctrl- \\enterX"); FinishTest("functionCall(X);"); // Completions ending with ";" do not participate in bracket merging. BeginTest("(<-old bracket"); fakeCodeCompletionModel->setCompletions({ "functionCall();" }); TestPressKey("ifun\\ctrl- \\enterX"); FinishTest("functionCall();X(<-old bracket"); BeginTest("(<-old bracket"); fakeCodeCompletionModel->setCompletions({ "functionCall(...);" }); TestPressKey("ifun\\ctrl- \\enterX"); FinishTest("functionCall(X);(<-old bracket"); - KateViewConfig::global()->setViInputModeStealKeys(oldStealKeys); + KateViewConfig::global()->setValue(KateViewConfig::ViInputModeStealKeys, oldStealKeys); kate_view->hide(); mainWindow->hide(); kate_view->unregisterCompletionModel(fakeCodeCompletionModel); delete fakeCodeCompletionModel; } void CompletionTest::CompletionTests() { const bool oldRemoveTailOnCompletion = KateViewConfig::global()->wordCompletionRemoveTail(); // For these tests, assume we don't swallow the tail on completion. - KateViewConfig::global()->setWordCompletionRemoveTail(false); + KateViewConfig::global()->setValue(KateViewConfig::WordCompletionRemoveTail, false); - KateViewConfig::global()->setViInputModeStealKeys(true); // For Ctrl-P, Ctrl-N etc + KateViewConfig::global()->setValue(KateViewConfig::ViInputModeStealKeys, true); // For Ctrl-P, Ctrl-N etc ensureKateViewVisible(); // KTextEditor::ViewPrivate needs to be visible for the completion widget. VimCodeCompletionTestModel *testModel = new VimCodeCompletionTestModel(kate_view); BeginTest(""); TestPressKey("i\\ctrl-p"); waitForCompletionWidgetToActivate(); TestPressKey("\\return"); FinishTest("completion3"); BeginTest(""); TestPressKey("i\\ctrl- "); waitForCompletionWidgetToActivate(); TestPressKey("\\return"); FinishTest("completion1"); BeginTest(""); TestPressKey("i\\ctrl-n"); waitForCompletionWidgetToActivate(); TestPressKey("\\return"); FinishTest("completion1"); // Test wraps around from top to bottom. BeginTest(""); TestPressKey("i\\ctrl- \\ctrl-p"); waitForCompletionWidgetToActivate(); TestPressKey("\\return"); FinishTest("completion3"); // Test wraps around from bottom to top. BeginTest(""); TestPressKey("i\\ctrl- \\ctrl-n\\ctrl-n\\ctrl-n"); waitForCompletionWidgetToActivate(); TestPressKey("\\return"); FinishTest("completion1"); // Test does not re-invoke completion when doing a "." repeat. BeginTest(""); TestPressKey("i\\ctrl- "); waitForCompletionWidgetToActivate(); TestPressKey("\\return\\ctrl-c"); kate_view->unregisterCompletionModel(testModel); FailTestOnInvocationModel *failsTestOnInvocation = new FailTestOnInvocationModel(kate_view); TestPressKey("gg."); FinishTest("completion1completion1"); kate_view->unregisterCompletionModel(failsTestOnInvocation); kate_view->registerCompletionModel(testModel); // Test that the full completion is repeated when repeat an insert that uses completion, // where the completion list was not manually invoked. BeginTest(""); TestPressKey("i"); // Simulate "automatic" invoking of completion. kate_view->completionWidget()->userInvokedCompletion(); waitForCompletionWidgetToActivate(); TestPressKey("\\return\\ctrl-cgg."); FinishTest("completion1completion1"); clearAllMappings(); // Make sure the "Enter"/ "Return" used when invoking completions is not swallowed before being // passed to the key mapper. kate_view->registerCompletionModel(testModel); vi_global->mappings()->add(Mappings::InsertModeMapping, "cb", "mapped-shouldntbehere", Mappings::Recursive); BeginTest(""); TestPressKey("ic"); kate_view->userInvokedCompletion(); waitForCompletionWidgetToActivate(); QVERIFY(kate_view->completionWidget()->isCompletionActive()); TestPressKey("\\enterb"); FinishTest("completion1b"); BeginTest(""); TestPressKey("ic"); kate_view->userInvokedCompletion(); waitForCompletionWidgetToActivate(); QVERIFY(kate_view->completionWidget()->isCompletionActive()); TestPressKey("\\returnb"); FinishTest("completion1b"); // Make sure the completion widget is dismissed on ESC, ctrl-c and ctrl-[. BeginTest(""); TestPressKey("ic"); kate_view->userInvokedCompletion(); waitForCompletionWidgetToActivate(); QVERIFY(kate_view->completionWidget()->isCompletionActive()); TestPressKey("\\esc"); QVERIFY(!kate_view->completionWidget()->isCompletionActive()); FinishTest("c"); BeginTest(""); TestPressKey("ic"); kate_view->userInvokedCompletion(); waitForCompletionWidgetToActivate(); QVERIFY(kate_view->completionWidget()->isCompletionActive()); TestPressKey("\\ctrl-c"); QVERIFY(!kate_view->completionWidget()->isCompletionActive()); FinishTest("c"); BeginTest(""); TestPressKey("ic"); kate_view->userInvokedCompletion(); waitForCompletionWidgetToActivate(); QVERIFY(kate_view->completionWidget()->isCompletionActive()); TestPressKey("\\ctrl-["); QVERIFY(!kate_view->completionWidget()->isCompletionActive()); FinishTest("c"); kate_view->unregisterCompletionModel(testModel); // Check that the repeat-last-change handles Completions in the same way as Macros do // i.e. fairly intelligently :) FakeCodeCompletionTestModel *fakeCodeCompletionModel = new FakeCodeCompletionTestModel(kate_view); fakeCodeCompletionModel->setRemoveTailOnComplete(true); - KateViewConfig::global()->setWordCompletionRemoveTail(true); + KateViewConfig::global()->setValue(KateViewConfig::WordCompletionRemoveTail, true); kate_view->registerCompletionModel(fakeCodeCompletionModel); clearAllMacros(); BeginTest("funct\nnoa\ncomtail\ncomtail"); fakeCodeCompletionModel->setCompletions({ "completionA", "functionwithargs(...)", "noargfunction()" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); // Record 'a'. TestPressKey("i\\right\\right\\right\\right\\right\\ctrl- \\enterfirstArg"); // Function with args. TestPressKey("\\home\\down\\right\\right\\right\\ctrl- \\enter"); // Function no args. fakeCodeCompletionModel->setRemoveTailOnComplete(true); - KateViewConfig::global()->setWordCompletionRemoveTail(true); + KateViewConfig::global()->setValue(KateViewConfig::WordCompletionRemoveTail, true); TestPressKey("\\home\\down\\right\\right\\right\\ctrl- \\enter"); // Cut off tail. fakeCodeCompletionModel->setRemoveTailOnComplete(false); - KateViewConfig::global()->setWordCompletionRemoveTail(false); + KateViewConfig::global()->setValue(KateViewConfig::WordCompletionRemoveTail, false); TestPressKey("\\home\\down\\right\\right\\right\\ctrl- \\enter\\ctrl-c"); // Don't cut off tail. fakeCodeCompletionModel->setRemoveTailOnComplete(true); - KateViewConfig::global()->setWordCompletionRemoveTail(true); + KateViewConfig::global()->setValue(KateViewConfig::WordCompletionRemoveTail, true); // Replay. fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("funct\nnoa\ncomtail\ncomtail"); TestPressKey("gg."); FinishTest("functionwithargs(firstArg)\nnoargfunction()\ncompletionA\ncompletionAtail"); // Clear our log of completions for each change. BeginTest(""); fakeCodeCompletionModel->setCompletions({ "completionA" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("ciw\\ctrl- \\enter\\ctrl-c"); fakeCodeCompletionModel->setCompletions({ "completionB" }); TestPressKey("ciw\\ctrl- \\enter\\ctrl-c"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("."); FinishTest("completionB"); kate_view->unregisterCompletionModel(fakeCodeCompletionModel); delete fakeCodeCompletionModel; - KateViewConfig::global()->setWordCompletionRemoveTail(oldRemoveTailOnCompletion); + KateViewConfig::global()->setValue(KateViewConfig::WordCompletionRemoveTail, oldRemoveTailOnCompletion); // Hide the kate_view for subsequent tests. kate_view->hide(); mainWindow->hide(); } void CompletionTest::waitForCompletionWidgetToActivate() { BaseTest::waitForCompletionWidgetToActivate(kate_view); } void CompletionTest::clearTrackedDocumentChanges() { m_docChanges.clear(); } //END: CompletionTest diff --git a/autotests/src/vimode/emulatedcommandbarsetupandteardown.cpp b/autotests/src/vimode/emulatedcommandbarsetupandteardown.cpp index 88488b06..a2831305 100644 --- a/autotests/src/vimode/emulatedcommandbarsetupandteardown.cpp +++ b/autotests/src/vimode/emulatedcommandbarsetupandteardown.cpp @@ -1,88 +1,88 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2013-2016 Simon St James * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "emulatedcommandbarsetupandteardown.h" #include #include #include #include #include #include #include //BEGIN: WindowKeepActive WindowKeepActive::WindowKeepActive(QMainWindow *mainWindow) : m_mainWindow(mainWindow) { /* There's nothing to do here. */ } bool WindowKeepActive::eventFilter(QObject *object, QEvent *event) { Q_UNUSED(object); if (event->type() == QEvent::WindowDeactivate) { // With some combinations of Qt and Xvfb, invoking/ dismissing a popup // will deactivate the m_mainWindow, preventing it from receiving shortcuts. // If we detect this, set it back to being the active window again. event->ignore(); QApplication::setActiveWindow(m_mainWindow); return true; } return false; } //END: WindowKeepActive //BEGIN: EmulatedCommandBarSetUpAndTearDown EmulatedCommandBarSetUpAndTearDown::EmulatedCommandBarSetUpAndTearDown(KateViInputMode *inputMode, KTextEditor::ViewPrivate *view, QMainWindow *window) : m_view(view), m_window(window), m_windowKeepActive(window), m_viInputMode(inputMode) { m_window->show(); m_view->show(); QApplication::setActiveWindow(m_window); m_view->setFocus(); while (QApplication::hasPendingEvents()) { QApplication::processEvents(); } - KateViewConfig::global()->setViInputModeStealKeys(true); + KateViewConfig::global()->setValue(KateViewConfig::ViInputModeStealKeys, true); m_window->installEventFilter(&m_windowKeepActive); } EmulatedCommandBarSetUpAndTearDown::~EmulatedCommandBarSetUpAndTearDown() { m_window->removeEventFilter(&m_windowKeepActive); // Use invokeMethod to avoid having to export KateViewBar for testing. QMetaObject::invokeMethod(m_viInputMode->viModeEmulatedCommandBar(), "hideMe"); m_view->hide(); m_window->hide(); - KateViewConfig::global()->setViInputModeStealKeys(false); + KateViewConfig::global()->setValue(KateViewConfig::ViInputModeStealKeys, false); while (QApplication::hasPendingEvents()) { QApplication::processEvents(); } } //END: EmulatedCommandBarSetUpAndTearDown diff --git a/autotests/src/vimode/keys.cpp b/autotests/src/vimode/keys.cpp index 9ef73a12..b3bb213b 100644 --- a/autotests/src/vimode/keys.cpp +++ b/autotests/src/vimode/keys.cpp @@ -1,1586 +1,1586 @@ /* * This file is part of the KDE libraries * * Copyright (C) 2014 Miquel Sabaté Solà * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include "keys.h" #include "emulatedcommandbarsetupandteardown.h" #include "fakecodecompletiontestmodel.h" #include "vimode/mappings.h" #include "vimode/globalstate.h" using namespace KTextEditor; using KateVi::Mappings; using KateVi::KeyParser; QTEST_MAIN(KeysTest) //BEGIN: KeysTest void KeysTest::MappingTests() { // QVERIFY(false); const int mappingTimeoutMSOverride = QString::fromLocal8Bit(qgetenv("KATE_VIMODE_TEST_MAPPINGTIMEOUTMS")).toInt(); const int mappingTimeoutMS = (mappingTimeoutMSOverride > 0) ? mappingTimeoutMSOverride : 2000; - KateViewConfig::global()->setViInputModeStealKeys(true); // For tests involving e.g. + KateViewConfig::global()->setValue(KateViewConfig::ViInputModeStealKeys, true); // For tests involving e.g. { // Check storage and retrieval of mapping recursion. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'", "ihello", Mappings::Recursive); QVERIFY(vi_global->mappings()->isRecursive(Mappings::NormalModeMapping, "'")); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "ihello", Mappings::NonRecursive); QVERIFY(!vi_global->mappings()->isRecursive(Mappings::NormalModeMapping, "a")); } clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'", "ihello^aworld", Mappings::Recursive); DoTest("", "'", "hworldello"); // Ensure that the non-mapping logged keypresses are cleared before we execute a mapping vi_global->mappings()->add(Mappings::NormalModeMapping, "'a", "rO", Mappings::Recursive); DoTest("X", "'a", "O"); { // Check that '123 is mapped after the timeout, given that we also have mappings that // extend it (e.g. '1234, '12345, etc) and which it itself extends ('1, '12, etc). clearAllMappings(); BeginTest(""); vi_input_mode_manager->keyMapper()->setMappingTimeout(mappingTimeoutMS);; QString consectiveDigits; for (int i = 1; i < 9; i++) { consectiveDigits += QString::number(i); vi_global->mappings()->add(Mappings::NormalModeMapping, '\'' + consectiveDigits, "iMapped from " + consectiveDigits + "", Mappings::Recursive); } TestPressKey("'123"); QCOMPARE(kate_document->text(), QString()); // Shouldn't add anything until after the timeout! QTest::qWait(2 * mappingTimeoutMS); FinishTest("Mapped from 123"); } // Mappings are not "counted": any count entered applies to the first command/ motion in the mapped sequence, // and is not used to replay the entire mapped sequence times in a row. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'downmapping", "j", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "'testmapping", "ifooibar", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "'testmotionmapping", "lj", Mappings::Recursive); DoTest("AAAA\nXXXX\nXXXX\nXXXX\nXXXX\nBBBB\nCCCC\nDDDD", "jd3'downmapping", "AAAA\nBBBB\nCCCC\nDDDD"); DoTest("", "5'testmapping", "foofoofoofoofobaro"); DoTest("XXXX\nXXXX\nXXXX\nXXXX", "3'testmotionmappingrO", "XXXX\nXXXO\nXXXX\nXXXX"); // Regression test for a weird mistake I made: *completely* remove all counting for the // first command in the sequence; don't just set it to 1! If it is set to 1, then "%" // will mean "go to position 1 percent of the way through the document" rather than // go to matching item. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "gl", "%", Mappings::Recursive); DoTest("0\n1\n2\n3\n4\n5\nfoo bar(xyz) baz", "jjjjjjwdgl", "0\n1\n2\n3\n4\n5\nfoo baz"); // Test that countable mappings work even when triggered by timeouts. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'testmapping", "ljrO", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "'testmappingdummy", "dummy", Mappings::Recursive); BeginTest("XXXX\nXXXX\nXXXX\nXXXX"); vi_input_mode_manager->keyMapper()->setMappingTimeout(mappingTimeoutMS);; TestPressKey("3'testmapping"); QTest::qWait(2 * mappingTimeoutMS); FinishTest("XXXX\nXXXO\nXXXX\nXXXX"); // Test that telescoping mappings don't interfere with built-in commands. Assumes that gp // is implemented and working. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "gdummy", "idummy", Mappings::Recursive); DoTest("hello", "yiwgpx", "hhellollo"); // Test that we can map a sequence of keys that extends a built-in command and use // that sequence without the built-in command firing. // Once again, assumes that gp is implemented and working. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "gpa", "idummy", Mappings::Recursive); DoTest("hello", "yiwgpa", "dummyhello"); // Test that we can map a sequence of keys that extends a built-in command and still // have the original built-in command fire if we timeout after entering that command. // Once again, assumes that gp is implemented and working. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "gpa", "idummy", Mappings::Recursive); BeginTest("hello"); vi_input_mode_manager->keyMapper()->setMappingTimeout(mappingTimeoutMS);; TestPressKey("yiwgp"); QTest::qWait(2 * mappingTimeoutMS); TestPressKey("x"); FinishTest("hhellollo"); // Test that something that starts off as a partial mapping following a command // (the "g" in the first "dg" is a partial mapping of "gj"), when extended to something // that is definitely not a mapping ("gg"), results in the full command being executed ("dgg"). clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "gj", "aj", Mappings::Recursive); DoTest("foo\nbar\nxyz", "jjdgg", ""); // Make sure that a mapped sequence of commands is merged into a single undo-able edit. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'a", "ofooofooofoo", Mappings::Recursive); DoTest("bar", "'au", "bar"); // Make sure that a counted mapping is merged into a single undoable edit. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'a", "ofoo", Mappings::Recursive); DoTest("bar", "5'au", "bar"); // Some test setup for non-recursive mapping g -> gj (cf: bug:314415) // Firstly: work out the expected result of gj (this might be fragile as default settings // change, etc.). We use BeginTest & FinishTest for the setup and teardown etc, but this is // not an actual test - it's just computing the expected result of the real test! const QString multiVirtualLineText = "foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo"; ensureKateViewVisible(); // Needs to be visible in order for virtual lines to make sense. KateViewConfig::global()->setDynWordWrap(true); BeginTest(multiVirtualLineText); TestPressKey("gjrX"); const QString expectedAfterVirtualLineDownAndChange = kate_document->text(); Q_ASSERT_X(expectedAfterVirtualLineDownAndChange.contains("X") && !expectedAfterVirtualLineDownAndChange.startsWith('X'), "setting up j->gj testcase data", "gj doesn't seem to have worked correctly!"); FinishTest(expectedAfterVirtualLineDownAndChange); // Test that non-recursive mappings are not expanded. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "j", "gj", Mappings::NonRecursive); DoTest(multiVirtualLineText, "jrX", expectedAfterVirtualLineDownAndChange); KateViewConfig::global()->setDynWordWrap(false); // Test that recursive mappings are expanded. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "X", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "X", "rx", Mappings::Recursive); DoTest("foo", "la", "fxo"); // Test that the flag that stops mappings being expanded is reset after the mapping has been executed. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "j", "gj", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "X", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "X", "rx", Mappings::Recursive); DoTest("foo", "jla", "fxo"); // Even if we start with a recursive mapping, as soon as we hit one that is not recursive, we should stop // expanding. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "X", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "X", "r.", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "i", "a", Mappings::Recursive); DoTest("foo", "li", "oo"); // Regression test: Using a mapping may trigger a call to updateSelection(), which can change the mode // from VisualLineMode to plain VisualMode. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "gA", "%", Mappings::NonRecursive); DoTest("xyz\nfoo\n{\nbar\n}", "jVjgAdgglP", "foo\n{\nbar\n}\nxyz"); // Piggy back on the previous test with a regression test for issue where, if gA is mapped to %, vgly // will yank one more character than it should. DoTest("foo(bar)X", "vgAyp", "ffoo(bar)oo(bar)X"); // Make sure that a successful mapping does not break the "if we select stuff externally in Normal mode, // we should switch to Visual Mode" thing. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "gA", "%", Mappings::NonRecursive); BeginTest("foo bar xyz()"); TestPressKey("gAr."); kate_view->setSelection(Range(0, 1, 0, 4)); // Actually selects "oo " (i.e. without the "b"). TestPressKey("d"); FinishTest("fbar xyz(."); // Regression tests for BUG:260655 clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "f", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "d", "i", Mappings::NonRecursive); DoTest("foo dar", "adr.", "foo .ar"); clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "F", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "d", "i", Mappings::NonRecursive); DoTest("foo dar", "$adr.", "foo .ar"); clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "t", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "d", "i", Mappings::NonRecursive); DoTest("foo dar", "adr.", "foo.dar"); clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "T", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "d", "i", Mappings::NonRecursive); DoTest("foo dar", "$adr.", "foo d.r"); clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "r", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "d", "i", Mappings::NonRecursive); DoTest("foo dar", "ad", "doo dar"); // Feel free to map the keypress after that, though. DoTest("foo dar", "addber\\esc", "berdoo dar"); // Also, be careful about how we interpret "waiting for find char/ replace char" DoTest("foo dar", "ffas", "soo dar"); // Ignore raw "Ctrl", "Shift", "Meta" and "Alt" keys, which will almost certainly end up being pressed as // we try to trigger mappings that contain these keys. clearAllMappings(); { // Ctrl. vi_global->mappings()->add(Mappings::NormalModeMapping, "", "ictrl", Mappings::NonRecursive); BeginTest(""); QKeyEvent *ctrlKeyDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Control, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), ctrlKeyDown); QApplication::sendPostedEvents(); TestPressKey("\\ctrl-a"); ctrlKeyDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Control, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), ctrlKeyDown); QApplication::sendPostedEvents(); TestPressKey("\\ctrl-b"); FinishTest("ctrl"); } { // Shift. vi_global->mappings()->add(Mappings::NormalModeMapping, "C", "ishift", Mappings::NonRecursive); BeginTest(""); QKeyEvent *ctrlKeyDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Control, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), ctrlKeyDown); QApplication::sendPostedEvents(); TestPressKey("\\ctrl-a"); QKeyEvent *shiftKeyDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Shift, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), shiftKeyDown); QApplication::sendPostedEvents(); TestPressKey("C"); FinishTest("shift"); } { // Alt. vi_global->mappings()->add(Mappings::NormalModeMapping, "", "ialt", Mappings::NonRecursive); BeginTest(""); QKeyEvent *ctrlKeyDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Control, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), ctrlKeyDown); QApplication::sendPostedEvents(); TestPressKey("\\ctrl-a"); QKeyEvent *altKeyDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Alt, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), altKeyDown); QApplication::sendPostedEvents(); TestPressKey("\\alt-b"); FinishTest("alt"); } { // Meta. vi_global->mappings()->add(Mappings::NormalModeMapping, "", "imeta", Mappings::NonRecursive); BeginTest(""); QKeyEvent *ctrlKeyDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Control, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), ctrlKeyDown); QApplication::sendPostedEvents(); TestPressKey("\\ctrl-a"); QKeyEvent *metaKeyDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Meta, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), metaKeyDown); QApplication::sendPostedEvents(); TestPressKey("\\meta-b"); FinishTest("meta"); } { // Can have mappings in Visual mode, distinct from Normal mode.. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "a", "3l", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "inose", Mappings::NonRecursive); DoTest("0123456", "lvad", "056"); // The recursion in Visual Mode is distinct from that of Normal mode. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "b", "", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::VisualModeMapping, "a", "b", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "b", Mappings::Recursive); DoTest("XXX\nXXX", "lvajd", "XXX"); clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "b", "", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::VisualModeMapping, "a", "b", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "b", Mappings::NonRecursive); DoTest("XXX\nXXX", "lvajd", "XXX\nXXX"); // A Visual mode mapping applies to all Visual modes (line, block, etc). clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "a", "2j", Mappings::NonRecursive); DoTest("123\n456\n789", "lvad", "19"); DoTest("123\n456\n789", "l\\ctrl-vad", "13\n46\n79"); DoTest("123\n456\n789", "lVad", ""); // Same for recursion. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "b", "2j", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::VisualModeMapping, "a", "b", Mappings::Recursive); DoTest("123\n456\n789", "lvad", "19"); DoTest("123\n456\n789", "l\\ctrl-vad", "13\n46\n79"); DoTest("123\n456\n789", "lVad", ""); // Can clear Visual mode mappings. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "h", "l", Mappings::Recursive); vi_global->mappings()->clear(Mappings::VisualModeMapping); DoTest("123\n456\n789", "lvhd", "3\n456\n789"); DoTest("123\n456\n789", "l\\ctrl-vhd", "3\n456\n789"); DoTest("123\n456\n789", "lVhd", "456\n789"); vi_global->mappings()->add(Mappings::VisualModeMapping, "h", "l", Mappings::Recursive); vi_global->mappings()->clear(Mappings::VisualModeMapping); DoTest("123\n456\n789", "lvhd", "3\n456\n789"); DoTest("123\n456\n789", "l\\ctrl-vhd", "3\n456\n789"); DoTest("123\n456\n789", "lVhd", "456\n789"); vi_global->mappings()->add(Mappings::VisualModeMapping, "h", "l", Mappings::Recursive); vi_global->mappings()->clear(Mappings::VisualModeMapping); DoTest("123\n456\n789", "lvhd", "3\n456\n789"); DoTest("123\n456\n789", "l\\ctrl-vhd", "3\n456\n789"); DoTest("123\n456\n789", "lVhd", "456\n789"); } { // Can have mappings in Insert mode. clearAllMappings(); vi_global->mappings()->add(Mappings::InsertModeMapping, "a", "xyz", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "inose", Mappings::NonRecursive); DoTest("foo", "ia\\esc", "xyzfoo"); // Recursion for Insert mode. clearAllMappings(); vi_global->mappings()->add(Mappings::InsertModeMapping, "b", "c", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::InsertModeMapping, "a", "b", Mappings::NonRecursive); DoTest("", "ia\\esc", "b"); clearAllMappings(); vi_global->mappings()->add(Mappings::InsertModeMapping, "b", "c", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::InsertModeMapping, "a", "b", Mappings::Recursive); DoTest("", "ia\\esc", "c"); clearAllMappings(); // Clear mappings for Insert mode. vi_global->mappings()->add(Mappings::InsertModeMapping, "a", "b", Mappings::NonRecursive); vi_global->mappings()->clear(Mappings::InsertModeMapping); DoTest("", "ia\\esc", "a"); } { EmulatedCommandBarSetUpAndTearDown vimStyleCommandBarTestsSetUpAndTearDown(vi_input_mode, kate_view, mainWindow); // Can have mappings in Emulated Command Bar. clearAllMappings(); vi_global->mappings()->add(Mappings::CommandModeMapping, "a", "xyz", Mappings::NonRecursive); DoTest(" a xyz", "/a\\enterrX", " a Xyz"); // Use mappings from Normal mode as soon as we exit command bar via Enter. vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "ixyz", Mappings::NonRecursive); DoTest(" a xyz", "/a\\entera", " a xyzxyz"); // Multiple mappings. vi_global->mappings()->add(Mappings::CommandModeMapping, "b", "123", Mappings::NonRecursive); DoTest(" xyz123", "/ab\\enterrX", " Xyz123"); // Recursive mappings. vi_global->mappings()->add(Mappings::CommandModeMapping, "b", "a", Mappings::Recursive); DoTest(" xyz", "/b\\enterrX", " Xyz"); // Can clear all. vi_global->mappings()->clear(Mappings::CommandModeMapping); DoTest(" ab xyz xyz123", "/ab\\enterrX", " Xb xyz xyz123"); } // Test that not *both* of the mapping and the mapped keys are logged for repetition via "." clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "ixyz", "iabc", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "gl", "%", Mappings::NonRecursive); DoTest("", "ixyz\\esc.", "ababcc"); DoTest("foo()X\nbarxyz()Y", "cglbaz\\escggj.", "bazX\nbazY"); // Regression test for a crash when executing a mapping that switches to Normal mode. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "h", "d", Mappings::Recursive); DoTest("foo", "vlh", "o"); { // Test that we can set/ unset mappings from the command-line. clearAllMappings(); DoTest("", "\\:nn foo ibar\\foo", "bar"); // "nn" is not recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "l", "iabc", Mappings::NonRecursive); DoTest("xxx", "\\:nn foo l\\foorX", "xXx"); // "no" is not recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "l", "iabc", Mappings::NonRecursive); DoTest("xxx", "\\:no foo l\\foorX", "xXx"); // "noremap" is not recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "l", "iabc", Mappings::NonRecursive); DoTest("xxx", "\\:noremap foo l\\foorX", "xXx"); // "nm" is recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "l", "iabc", Mappings::NonRecursive); DoTest("xxx", "\\:nm foo l\\foorX", "abXxxx"); // "nmap" is recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "l", "iabc", Mappings::NonRecursive); DoTest("xxx", "\\:nmap foo l\\foorX", "abXxxx"); // Unfortunately, "map" is a reserved word :/ clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "l", "iabc", Mappings::NonRecursive); DoTest("xxx", "\\:map foo l\\foorX", "abXxxx", ShouldFail, "'map' is reserved for other stuff in Kate command line"); // nunmap works in normal mode. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "w", "ciwabc", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "b", "ciwxyz", Mappings::NonRecursive); DoTest(" 123 456 789", "\\:nunmap b\\WWwbrX", " 123 Xbc 789"); // nmap and nunmap whose "from" is a complex encoded expression. clearAllMappings(); BeginTest("123"); TestPressKey("\\:nmap ciwxyz\\"); TestPressKey("\\ctrl-9"); FinishTest("xyz"); BeginTest("123"); TestPressKey("\\:nunmap \\"); TestPressKey("\\ctrl-9"); FinishTest("123"); // vmap works in Visual mode and is recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "l", "d", Mappings::NonRecursive); DoTest("abco", "\\:vmap foo l\\v\\rightfoogU", "co"); // vmap does not work in Normal mode. clearAllMappings(); DoTest("xxx", "\\:vmap foo l\\foorX", "xxx\nrX"); // vm works in Visual mode and is recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "l", "d", Mappings::NonRecursive); DoTest("abco", "\\:vm foo l\\v\\rightfoogU", "co"); // vn works in Visual mode and is not recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "l", "d", Mappings::NonRecursive); DoTest("abco", "\\:vn foo l\\v\\rightfoogU", "ABCo"); // vnoremap works in Visual mode and is not recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "l", "d", Mappings::NonRecursive); DoTest("abco", "\\:vnoremap foo l\\v\\rightfoogU", "ABCo"); // vunmap works in Visual Mode. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "l", "w", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::VisualModeMapping, "gU", "2b", Mappings::NonRecursive); DoTest("foo bar xyz", "\\:vunmap gU\\wvlgUd", "foo BAR Xyz"); // imap works in Insert mode and is recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::InsertModeMapping, "l", "d", Mappings::NonRecursive); DoTest("", "\\:imap foo l\\ifoo\\esc", "d"); // im works in Insert mode and is recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::InsertModeMapping, "l", "d", Mappings::NonRecursive); DoTest("", "\\:im foo l\\ifoo\\esc", "d"); // ino works in Insert mode and is not recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::InsertModeMapping, "l", "d", Mappings::NonRecursive); DoTest("", "\\:ino foo l\\ifoo\\esc", "l"); // inoremap works in Insert mode and is not recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::InsertModeMapping, "l", "d", Mappings::NonRecursive); DoTest("", "\\:inoremap foo l\\ifoo\\esc", "l"); // iunmap works in Insert mode. clearAllMappings(); vi_global->mappings()->add(Mappings::InsertModeMapping, "l", "d", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::InsertModeMapping, "m", "e", Mappings::NonRecursive); DoTest("", "\\:iunmap l\\ilm\\esc", "le"); { EmulatedCommandBarSetUpAndTearDown vimStyleCommandBarTestsSetUpAndTearDown(vi_input_mode, kate_view, mainWindow); // cmap works in emulated command bar and is recursive. // NOTE: need to do the cmap call using the direct execution (i.e. \\:cmap blah blah\\), *not* using // the emulated command bar (:cmap blah blah\\enter), as this will be subject to mappings, which // can interfere with the tests! clearAllMappings(); vi_global->mappings()->add(Mappings::CommandModeMapping, "l", "d", Mappings::NonRecursive); DoTest(" l d foo", "\\:cmap foo l\\/foo\\enterrX", " l X foo"); // cm works in emulated command bar and is recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::CommandModeMapping, "l", "d", Mappings::NonRecursive); DoTest(" l d foo", "\\:cm foo l\\/foo\\enterrX", " l X foo"); // cnoremap works in emulated command bar and is recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::CommandModeMapping, "l", "d", Mappings::NonRecursive); DoTest(" l d foo", "\\:cnoremap foo l\\/foo\\enterrX", " X d foo"); // cno works in emulated command bar and is recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::CommandModeMapping, "l", "d", Mappings::NonRecursive); DoTest(" l d foo", "\\:cno foo l\\/foo\\enterrX", " X d foo"); // cunmap works in emulated command bar. clearAllMappings(); vi_global->mappings()->add(Mappings::CommandModeMapping, "l", "d", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::CommandModeMapping, "m", "e", Mappings::NonRecursive); DoTest(" de le", "\\:cunmap l\\/lm\\enterrX", " de Xe"); } // Can use to signify a space. clearAllMappings(); DoTest("", "\\:nn h iab\\h ", " a b"); } // More recursion tests - don't lose characters from a Recursive mapping if it looks like they might // be part of a different mapping (but end up not being so). // (Here, the leading "i" in "irecursive" could be part of the mapping "ihello"). clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'", "ihello", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "ihello", "irecursive", Mappings::Recursive); DoTest("", "'", "recursive"); // Capslock in insert mode is not handled by Vim nor by KateViewInternal, and ends up // being sent to KateViInputModeManager::handleKeypress twice (it could be argued that this is // incorrect behaviour on the part of KateViewInternal), which can cause infinite // recursion if we are not careful about identifying replayed rejected keypresses. BeginTest("foo bar"); TestPressKey("i"); QKeyEvent *capslockKeyPress = new QKeyEvent(QEvent::KeyPress, Qt::Key_CapsLock, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), capslockKeyPress); QApplication::sendPostedEvents(); FinishTest("foo bar"); // Mapping the u and the U commands to other keys. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "t", "u", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "r", "U", Mappings::Recursive); DoTest("", "ihello\\esct", ""); DoTest("", "ihello\\esctr", "hello"); // clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "l", "", Mappings::Recursive); DoTest("Hello", "lrr", "rello"); clearAllMappings(); vi_global->mappings()->add(Mappings::InsertModeMapping, "l", "", Mappings::Recursive); DoTest("Hello", "sl\\esc", "ello"); clearAllMappings(); vi_global->mappings()->add(Mappings::InsertModeMapping, "l", "abc", Mappings::Recursive); DoTest("Hello", "sl\\esc", "abcello"); // Clear mappings for subsequent tests. clearAllMappings(); } void KeysTest::LeaderTests() { // Clean slate. - KateViewConfig::global()->setViInputModeStealKeys(true); + KateViewConfig::global()->setValue(KateViewConfig::ViInputModeStealKeys, true); clearAllMappings(); // By default the backslash character is the leader. The default leader // is picked from the config. If we don't want to mess this from other // tests, it's better if we mock the config. const QString viTestKConfigFileName = QStringLiteral("vimodetest-leader-katevimoderc"); KConfig viTestKConfig(viTestKConfigFileName); vi_global->mappings()->setLeader(QChar()); vi_global->readConfig(&viTestKConfig); vi_global->mappings()->add(Mappings::NormalModeMapping, "i", "ii", Mappings::Recursive); DoTest("", "\\\\i", "i"); // We can change the leader and it will work. clearAllMappings(); vi_global->readConfig(&viTestKConfig); vi_global->mappings()->setLeader(QChar::fromLatin1(',')); vi_global->mappings()->add(Mappings::NormalModeMapping, "i", "ii", Mappings::Recursive); DoTest("", ",i", "i"); // Mixing up the with its value. clearAllMappings(); vi_global->readConfig(&viTestKConfig); vi_global->mappings()->setLeader(QChar::fromLatin1(',')); vi_global->mappings()->add(Mappings::NormalModeMapping, ",", "ii", Mappings::Recursive); DoTest("", ",,", "i"); vi_global->mappings()->add(Mappings::NormalModeMapping, ",", "ii", Mappings::Recursive); DoTest("", ",,", "i"); // It doesn't work outside normal mode. clearAllMappings(); vi_global->readConfig(&viTestKConfig); vi_global->mappings()->setLeader(QChar::fromLatin1(',')); vi_global->mappings()->add(Mappings::InsertModeMapping, "i", "ii", Mappings::Recursive); DoTest("", "i,ii", ",ii"); // Clear mappings for subsequent tests. clearAllMappings(); } void KeysTest::ParsingTests() { // BUG #298726 const QChar char_o_diaeresis(246); // Test that we can correctly translate finnish key ö QKeyEvent *k = new QKeyEvent(QEvent::KeyPress, 214, Qt::NoModifier, 47, 246, 16400, char_o_diaeresis); QCOMPARE(KeyParser::self()->KeyEventToQChar(*k), QChar(246)); // Test that it can be used in mappings clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, char_o_diaeresis, "ifoo", Mappings::Recursive); DoTest("hello", QString("ll%1bar").arg(char_o_diaeresis), "hefoobarllo"); // Test that is parsed like QCOMPARE(KeyParser::self()->vi2qt("cr"), int(Qt::Key_Enter)); const QString &enter = KeyParser::self()->encodeKeySequence(QLatin1String("")); QCOMPARE(KeyParser::self()->decodeKeySequence(enter), QLatin1String("")); } void KeysTest::AltGr() { QKeyEvent *altGrDown; QKeyEvent *altGrUp; // Test Alt-gr still works - this isn't quite how things work in "real-life": in real-life, something like // Alt-gr+7 would be a "{", but I don't think this can be reproduced without sending raw X11 // keypresses to Qt, so just duplicate the keypress events we would receive if we pressed // Alt-gr+7 (that is: Alt-gr down; "{"; Alt-gr up). // Ensure we have auto brackets off, or the test will fail - kate_view->config()->setAutoBrackets(false); + kate_view->config()->setValue(KateViewConfig::AutoBrackets, false); BeginTest(""); TestPressKey("i"); altGrDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_AltGr, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), altGrDown); QApplication::sendPostedEvents(); // Not really Alt-gr and 7, but this is the key event that is reported by Qt if we press that. QKeyEvent *altGrAnd7 = new QKeyEvent(QEvent::KeyPress, Qt::Key_BraceLeft, Qt::GroupSwitchModifier, "{"); QApplication::postEvent(kate_view->focusProxy(), altGrAnd7); QApplication::sendPostedEvents(); altGrUp = new QKeyEvent(QEvent::KeyRelease, Qt::Key_AltGr, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), altGrUp); QApplication::sendPostedEvents(); TestPressKey("\\ctrl-c"); FinishTest("{"); // French Bepo keyabord AltGr + Shift + s = Ù = Unicode(0x00D9); const QString ugrave = QString(QChar(0x00D9)); BeginTest(""); TestPressKey("i"); altGrDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_AltGr, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), altGrDown); QApplication::sendPostedEvents(); altGrDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Shift, Qt::ShiftModifier | Qt::GroupSwitchModifier); QApplication::postEvent(kate_view->focusProxy(), altGrDown); QApplication::sendPostedEvents(); QKeyEvent *altGrAndUGrave = new QKeyEvent(QEvent::KeyPress, Qt::Key_Ugrave, Qt::ShiftModifier | Qt::GroupSwitchModifier, ugrave); qDebug() << QString("%1").arg(altGrAndUGrave->modifiers(), 10, 16); QApplication::postEvent(kate_view->focusProxy(), altGrAndUGrave); QApplication::sendPostedEvents(); altGrUp = new QKeyEvent(QEvent::KeyRelease, Qt::Key_AltGr, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), altGrUp); QApplication::sendPostedEvents(); FinishTest(ugrave); } void KeysTest::MacroTests() { // Update the status on qa. const QString macroIsRecordingStatus = QLatin1String("(") + i18n("recording") + QLatin1String(")"); clearAllMacros(); BeginTest(""); QVERIFY(!kate_view->viewModeHuman().contains(macroIsRecordingStatus)); TestPressKey("qa"); QVERIFY(kate_view->viewModeHuman().contains(macroIsRecordingStatus)); TestPressKey("q"); QVERIFY(!kate_view->viewModeHuman().contains(macroIsRecordingStatus)); FinishTest(""); // The closing "q" is not treated as the beginning of a new "begin recording macro" command. clearAllMacros(); BeginTest("foo"); TestPressKey("qaqa"); QVERIFY(!kate_view->viewModeHuman().contains(macroIsRecordingStatus)); TestPressKey("xyz\\esc"); FinishTest("fxyzoo"); // Record and playback a single keypress into macro register "a". clearAllMacros(); DoTest("foo bar", "qawqgg@arX", "foo Xar"); // Two macros - make sure the old one is cleared. clearAllMacros(); DoTest("123 foo bar xyz", "qawqqabqggww@arX", "123 Xoo bar xyz"); // Update the status on qb. clearAllMacros(); BeginTest(""); QVERIFY(!kate_view->viewModeHuman().contains(macroIsRecordingStatus)); TestPressKey("qb"); QVERIFY(kate_view->viewModeHuman().contains(macroIsRecordingStatus)); TestPressKey("q"); QVERIFY(!kate_view->viewModeHuman().contains(macroIsRecordingStatus)); FinishTest(""); // Record and playback a single keypress into macro register "b". clearAllMacros(); DoTest("foo bar", "qbwqgg@brX", "foo Xar"); // More complex macros. clearAllMacros(); DoTest("foo", "qcrXql@c", "XXo"); // Re-recording a macro should only clear that macro. clearAllMacros(); DoTest("foo 123", "qaraqqbrbqqbrBqw@a", "Boo a23"); // Empty macro clears it. clearAllMacros(); DoTest("", "qaixyz\\ctrl-cqqaq@a", "xyz"); // Hold two macros in memory simultanenously so both can be played. clearAllMacros(); DoTest("foo 123", "qaraqqbrbqw@al@b", "boo ab3"); // Do more complex things, including switching modes and using ctrl codes. clearAllMacros(); DoTest("foo bar", "qainose\\ctrl-c~qw@a", "nosEfoo nosEbar"); clearAllMacros(); DoTest("foo bar", "qayiwinose\\ctrl-r0\\ctrl-c~qw@a", "nosefoOfoo nosebaRbar"); clearAllMacros(); DoTest("foo bar", "qavldqw@a", "o r"); // Make sure we can use "q" in insert mode while recording a macro. clearAllMacros(); DoTest("foo bar", "qaiqueequeg\\ctrl-cqw@a", "queequegfoo queequegbar"); // Can invoke a macro in Visual Mode. clearAllMacros(); DoTest("foo bar", "qa~qvlll@a", "FOO Bar"); // Invoking a macro in Visual Mode does not exit Visual Mode. clearAllMacros(); DoTest("foo bar", "qallqggv@a~", "FOO bar");; // Can record & macros in Visual Mode for playback in Normal Mode. clearAllMacros(); DoTest("foo bar", "vqblq\\ctrl-c@b~", "foO bar"); // Recording a macro in Visual Mode does not exit Visual Mode. clearAllMacros(); DoTest("foo bar", "vqblql~", "FOO bar"); // Recognize correctly numbered registers clearAllMacros(); DoTest("foo", "q1iX\\escq@1", "XXfoo"); { // Ensure that we can call emulated command bar searches, and that we don't record // synthetic keypresses. EmulatedCommandBarSetUpAndTearDown vimStyleCommandBarTestsSetUpAndTearDown(vi_input_mode, kate_view, mainWindow); clearAllMacros(); DoTest("foo bar\nblank line", "qa/bar\\enterqgg@arX", "foo Xar\nblank line"); // More complex searching stuff. clearAllMacros(); DoTest("foo 123foo123\nbar 123bar123", "qayiw/\\ctrl-r0\\enterrXqggj@a", "foo 123Xoo123\nbar 123Xar123"); } // Expand mappings, but don't do *both* original keypresses and executed keypresses. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'", "ihello", Mappings::Recursive); clearAllMacros(); DoTest("", "qa'q@a", "hellhelloo"); // Actually, just do the mapped keypresses, not the executed mappings (like Vim). clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'", "ihello", Mappings::Recursive); clearAllMacros(); BeginTest(""); TestPressKey("qa'q"); vi_global->mappings()->add(Mappings::NormalModeMapping, "'", "igoodbye", Mappings::Recursive); TestPressKey("@a"); FinishTest("hellgoodbyeo"); // Clear the "stop recording macro keypresses because we're executing a mapping" when the mapping has finished // executing. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'", "ihello", Mappings::Recursive); clearAllMacros(); DoTest("", "qa'ixyz\\ctrl-cq@a", "hellxyhellxyzozo"); // ... make sure that *all* mappings have finished, though: take into account recursion. clearAllMappings(); clearAllMacros(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'", "ihello", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "ihello", "irecursive", Mappings::Recursive); DoTest("", "qa'q@a", "recursivrecursivee"); clearAllMappings(); clearAllMacros(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'", "ihelloixyz", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "ihello", "irecursive", Mappings::Recursive); DoTest("", "qa'q@a", "recursivxyrecursivxyzeze"); clearAllMappings(); clearAllMacros(); // Don't save the trailing "q" with macros, and also test that we can call one macro from another, // without one of the macros being repeated. DoTest("", "qaixyz\\ctrl-cqqb@aq@b", "xyxyxyzzz"); clearAllMappings(); clearAllMacros(); // More stringent test that macros called from another macro aren't repeated - requires more nesting // of macros ('a' calls 'b' calls 'c'). DoTest("", "qciC\\ctrl-cq" "qb@ciB\\ctrl-cq" "qa@biA\\ctrl-cq" "dd@a", "ABC"); // Don't crash if we invoke a non-existent macro. clearAllMacros(); DoTest("", "@x", ""); // Make macros "counted". clearAllMacros(); DoTest("XXXX\nXXXX\nXXXX\nXXXX", "qarOljq3@a", "OXXX\nXOXX\nXXOX\nXXXO"); // A macro can be undone with one undo. clearAllMacros(); DoTest("foo bar", "qaciwxyz\\ctrl-ci123\\ctrl-cqw@au", "xy123z bar"); // As can a counted macro. clearAllMacros(); DoTest("XXXX\nXXXX\nXXXX\nXXXX", "qarOljq3@au", "OXXX\nXXXX\nXXXX\nXXXX"); { EmulatedCommandBarSetUpAndTearDown vimStyleCommandBarTestsSetUpAndTearDown(vi_input_mode, kate_view, mainWindow); // Make sure we can macro-ise an interactive sed replace. clearAllMacros(); DoTest("foo foo foo foo\nfoo foo foo foo", "qa:s/foo/bar/gc\\enteryynyAdone\\escqggj@a", "bar bar foo bardone\nbar bar foo bardone"); // Make sure the closing "q" in the interactive sed replace isn't mistaken for a macro's closing "q". clearAllMacros(); DoTest("foo foo foo foo\nfoo foo foo foo", "qa:s/foo/bar/gc\\enteryyqAdone\\escqggj@a", "bar bar foo foodone\nbar bar foo foodone"); clearAllMacros(); DoTest("foo foo foo foo\nfoo foo foo foo", "qa:s/foo/bar/gc\\enteryyqqAdone\\escggj@aAdone\\esc", "bar bar foo foodone\nbar bar foo foodone"); } clearAllMappings(); clearAllMacros(); // Expand mapping in an executed macro, if the invocation of the macro "@a" is a prefix of a mapping M, and // M ends up not being triggered. vi_global->mappings()->add(Mappings::NormalModeMapping, "@aaaa", "idummy", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "S", "ixyz", Mappings::Recursive); DoTest("", "qaSq@abrX", "Xyxyzz"); clearAllMappings(); // Can't play old version of macro while recording new version. clearAllMacros(); DoTest("", "qaiaaa\\ctrl-cqqa@aq", "aaa"); // Can't play the macro while recording it. clearAllMacros(); DoTest("", "qaiaaa\\ctrl-c@aq", "aaa"); // "@@" plays back macro "a" if "a" was the last macro we played back. clearAllMacros(); DoTest("", "qaia\\ctrl-cq@adiw@@", "a"); // "@@" plays back macro "b" if "b" was the last macro we played back. clearAllMacros(); DoTest("", "qbib\\ctrl-cq@bdiw@@", "b"); // "@@" does nothing if no macro was previously played. clearAllMacros(); DoTest("", "qaia\\ctrl-cq@@", "a"); // Nitpick: "@@" replays the last played back macro, even if that macro had not been defined // when it was first played back. clearAllMacros(); DoTest("", "@aqaia\\ctrl-cq@@", "aa"); // "@@" is counted. clearAllMacros(); DoTest("", "qaia\\ctrl-cq@adiw5@@", "aaaaa"); // Test that we can save and restore a single macro. const QString viTestKConfigFileName = "vimodetest-katevimoderc"; { clearAllMacros(); KConfig viTestKConfig(viTestKConfigFileName); BeginTest(""); TestPressKey("qaia\\ctrl-cq"); vi_global->writeConfig(&viTestKConfig); viTestKConfig.sync(); // Overwrite macro "a", and clear the document. TestPressKey("qaidummy\\ctrl-cqdd"); vi_global->readConfig(&viTestKConfig); TestPressKey("@a"); FinishTest("a"); } { // Test that we can save and restore several macros. clearAllMacros(); const QString viTestKConfigFileName = "vimodetest-katevimoderc"; KConfig viTestKConfig(viTestKConfigFileName); BeginTest(""); TestPressKey("qaia\\ctrl-cqqbib\\ctrl-cq"); vi_global->writeConfig(&viTestKConfig); viTestKConfig.sync(); // Overwrite macros "a" & "b", and clear the document. TestPressKey("qaidummy\\ctrl-cqqbidummy\\ctrl-cqdd"); vi_global->readConfig(&viTestKConfig); TestPressKey("@a@b"); FinishTest("ba"); } // Ensure that we don't crash when a "repeat change" occurs in a macro we execute. clearAllMacros(); DoTest("", "qqixyz\\ctrl-c.q@qdd", ""); // Don't record both the "." *and* the last-change keypresses when recording a macro; // just record the "." clearAllMacros(); DoTest("", "ixyz\\ctrl-cqq.qddi123\\ctrl-c@q", "121233"); // Test dealing with auto-completion. FakeCodeCompletionTestModel *fakeCodeCompletionModel = new FakeCodeCompletionTestModel(kate_view); kate_view->registerCompletionModel(fakeCodeCompletionModel); // Completion tests require a visible kate_view. ensureKateViewVisible(); // Want Vim mode to intercept ctrl-p, ctrl-n shortcuts, etc. const bool oldStealKeys = KateViewConfig::global()->viInputModeStealKeys(); - KateViewConfig::global()->setViInputModeStealKeys(true); + KateViewConfig::global()->setValue(KateViewConfig::ViInputModeStealKeys, true); // Don't invoke completion via ctrl-space when replaying a macro. clearAllMacros(); fakeCodeCompletionModel->setCompletions({ "completionA", "completionB", "completionC" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); BeginTest(""); TestPressKey("qqico\\ctrl- \\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("@q"); FinishTest("ccoo"); // Don't invoke completion via ctrl-p when replaying a macro. clearAllMacros(); fakeCodeCompletionModel->setCompletions({ "completionA", "completionB", "completionC" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); BeginTest(""); TestPressKey("qqico\\ctrl-p\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("@q"); FinishTest("ccoo"); // Don't invoke completion via ctrl-n when replaying a macro. clearAllMacros(); fakeCodeCompletionModel->setCompletions({ "completionA", "completionB", "completionC" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); BeginTest(""); TestPressKey("qqico\\ctrl-n\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("@q"); FinishTest("ccoo"); // An "enter" in insert mode when no completion is activated (so, a newline) // is treated as a newline when replayed as a macro, even if completion is // active when the "enter" is replayed. clearAllMacros(); fakeCodeCompletionModel->setCompletions(QStringList()); // Prevent any completions. fakeCodeCompletionModel->setFailTestOnInvocation(false); fakeCodeCompletionModel->clearWasInvoked(); BeginTest(""); TestPressKey("qqicompl\\enterX\\ctrl-cqdddd"); QVERIFY(!fakeCodeCompletionModel->wasInvoked()); // Error in test setup! fakeCodeCompletionModel->setCompletions({ "completionA", "completionB", "completionC" }); fakeCodeCompletionModel->forceInvocationIfDocTextIs("compl"); fakeCodeCompletionModel->clearWasInvoked(); TestPressKey("@q"); QVERIFY(fakeCodeCompletionModel->wasInvoked()); // Error in test setup! fakeCodeCompletionModel->doNotForceInvocation(); FinishTest("compl\nX"); // Same for "return". clearAllMacros(); fakeCodeCompletionModel->setCompletions(QStringList()); // Prevent any completions. fakeCodeCompletionModel->setFailTestOnInvocation(false); fakeCodeCompletionModel->clearWasInvoked(); BeginTest(""); TestPressKey("qqicompl\\returnX\\ctrl-cqdddd"); QVERIFY(!fakeCodeCompletionModel->wasInvoked()); // Error in test setup! fakeCodeCompletionModel->setCompletions({ "completionA", "completionB", "completionC" }); fakeCodeCompletionModel->forceInvocationIfDocTextIs("compl"); fakeCodeCompletionModel->clearWasInvoked(); TestPressKey("@q"); QVERIFY(fakeCodeCompletionModel->wasInvoked()); // Error in test setup! fakeCodeCompletionModel->doNotForceInvocation(); FinishTest("compl\nX"); // If we do a plain-text completion in a macro, this should be repeated when we replay it. clearAllMacros(); BeginTest(""); fakeCodeCompletionModel->setCompletions({ "completionA", "completionB", "completionC" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqicompl\\ctrl- \\enter\\ctrl-cq"); kate_document->clear(); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("@q"); FinishTest("completionA"); // Should replace only the current word when we repeat the completion. clearAllMacros(); BeginTest("compl"); fakeCodeCompletionModel->setCompletions({ "completionA", "completionB", "completionC" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqfla\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("(compl)"); TestPressKey("gg@q"); FinishTest("(completionA)"); // Tail-clearing completions should be undoable with one undo. clearAllMacros(); BeginTest("compl"); fakeCodeCompletionModel->setCompletions({ "completionA", "completionB", "completionC" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqfla\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("(compl)"); TestPressKey("gg@qu"); FinishTest("(compl)"); // Should be able to store multiple completions. clearAllMacros(); BeginTest(""); fakeCodeCompletionModel->setCompletions({ "completionA", "completionB", "completionC" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqicom\\ctrl-p\\enter com\\ctrl-p\\ctrl-p\\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("dd@q"); FinishTest("completionC completionB"); // Clear the completions for a macro when we start recording. clearAllMacros(); BeginTest(""); fakeCodeCompletionModel->setCompletions({ "completionOrig" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqicom\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setCompletions({ "completionSecond" }); TestPressKey("ddqqicom\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("dd@q"); FinishTest("completionSecond"); // Completions are per macro. clearAllMacros(); BeginTest(""); fakeCodeCompletionModel->setCompletions({ "completionA" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qaicom\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setCompletions({ "completionB" }); TestPressKey("ddqbicom\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("dd@aA\\enter\\ctrl-c@b"); FinishTest("completionA\ncompletionB"); // Make sure completions work with recursive macros. clearAllMacros(); BeginTest(""); fakeCodeCompletionModel->setCompletions({ "completionA1", "completionA2" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); // Record 'a', which calls the (non-yet-existent) macro 'b'. TestPressKey("qaicom\\ctrl- \\enter\\ctrl-cA\\enter\\ctrl-c@bA\\enter\\ctrl-cicom\\ctrl- \\ctrl-p\\enter\\ctrl-cq"); // Clear document and record 'b'. fakeCodeCompletionModel->setCompletions({ "completionB" }); TestPressKey("ggdGqbicom\\ctrl- \\enter\\ctrl-cq"); TestPressKey("dd@a"); FinishTest("completionA1\ncompletionB\ncompletionA2"); // Test that non-tail-removing completions are respected. // Note that there is no way (in general) to determine if a completion was // non-tail-removing, so we explicitly set the config to false. const bool oldRemoveTailOnCompletion = KateViewConfig::global()->wordCompletionRemoveTail(); - KateViewConfig::global()->setWordCompletionRemoveTail(false); + KateViewConfig::global()->setValue(KateViewConfig::WordCompletionRemoveTail, false); const bool oldReplaceTabsDyn = kate_document->config()->replaceTabsDyn(); kate_document->config()->setReplaceTabsDyn(false); fakeCodeCompletionModel->setRemoveTailOnComplete(false); clearAllMacros(); BeginTest("compTail"); fakeCodeCompletionModel->setCompletions({ "completionA", "completionB", "completionC" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqfTi\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("compTail"); TestPressKey("gg@q"); FinishTest("completionATail"); // A "word" consists of letters & numbers, plus "_". clearAllMacros(); BeginTest("(123_compTail"); fakeCodeCompletionModel->setCompletions({ "123_completionA" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqfTi\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("(123_compTail"); TestPressKey("gg@q"); FinishTest("(123_completionATail"); // Correctly remove word if we are set to remove tail. - KateViewConfig::global()->setWordCompletionRemoveTail(true); + KateViewConfig::global()->setValue(KateViewConfig::WordCompletionRemoveTail, true); clearAllMacros(); BeginTest("(123_compTail)"); fakeCodeCompletionModel->setCompletions({ "123_completionA" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); fakeCodeCompletionModel->setRemoveTailOnComplete(true); TestPressKey("qqfTi\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("(123_compTail)"); TestPressKey("gg@q"); FinishTest("(123_completionA)"); // Again, a "word" consists of letters & numbers & underscores. clearAllMacros(); BeginTest("(123_compTail_456)"); fakeCodeCompletionModel->setCompletions({ "123_completionA" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); fakeCodeCompletionModel->setRemoveTailOnComplete(true); TestPressKey("qqfTi\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("(123_compTail_456)"); TestPressKey("gg@q"); FinishTest("(123_completionA)"); // Actually, let whether the tail is swallowed or not depend on the value when the // completion occurred, not when we replay it. clearAllMacros(); BeginTest("(123_compTail_456)"); fakeCodeCompletionModel->setCompletions({ "123_completionA" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); fakeCodeCompletionModel->setRemoveTailOnComplete(true); - KateViewConfig::global()->setWordCompletionRemoveTail(true); + KateViewConfig::global()->setValue(KateViewConfig::WordCompletionRemoveTail, true); TestPressKey("qqfTi\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); - KateViewConfig::global()->setWordCompletionRemoveTail(false); + KateViewConfig::global()->setValue(KateViewConfig::WordCompletionRemoveTail, false); kate_document->setText("(123_compTail_456)"); TestPressKey("gg@q"); FinishTest("(123_completionA)"); clearAllMacros(); BeginTest("(123_compTail_456)"); fakeCodeCompletionModel->setCompletions({ "123_completionA" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); fakeCodeCompletionModel->setRemoveTailOnComplete(false); - KateViewConfig::global()->setWordCompletionRemoveTail(false); + KateViewConfig::global()->setValue(KateViewConfig::WordCompletionRemoveTail, false); TestPressKey("qqfTi\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); - KateViewConfig::global()->setWordCompletionRemoveTail(true); + KateViewConfig::global()->setValue(KateViewConfig::WordCompletionRemoveTail, true); kate_document->setText("(123_compTail_456)"); TestPressKey("gg@q"); FinishTest("(123_completionATail_456)"); // Can have remove-tail *and* non-remove-tail completions in one macro. clearAllMacros(); BeginTest("(123_compTail_456)\n(123_compTail_456)"); fakeCodeCompletionModel->setCompletions({ "123_completionA" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); fakeCodeCompletionModel->setRemoveTailOnComplete(true); - KateViewConfig::global()->setWordCompletionRemoveTail(true); + KateViewConfig::global()->setValue(KateViewConfig::WordCompletionRemoveTail, true); TestPressKey("qqfTi\\ctrl- \\enter\\ctrl-c"); fakeCodeCompletionModel->setRemoveTailOnComplete(false); - KateViewConfig::global()->setWordCompletionRemoveTail(false); + KateViewConfig::global()->setValue(KateViewConfig::WordCompletionRemoveTail, false); TestPressKey("j^fTi\\ctrl- \\enter\\ctrl-cq"); kate_document->setText("(123_compTail_456)\n(123_compTail_456)"); TestPressKey("gg@q"); FinishTest("(123_completionA)\n(123_completionATail_456)"); // Can repeat plain-text completions when there is no word to the left of the cursor. clearAllMacros(); BeginTest(""); fakeCodeCompletionModel->setCompletions({ "123_completionA" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqi\\ctrl- \\enter\\ctrl-cq"); kate_document->clear(); TestPressKey("gg@q"); FinishTest("123_completionA"); // Shouldn't swallow the letter under the cursor if we're not swallowing tails. clearAllMacros(); BeginTest(""); fakeCodeCompletionModel->setCompletions({ "123_completionA" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); fakeCodeCompletionModel->setRemoveTailOnComplete(false); - KateViewConfig::global()->setWordCompletionRemoveTail(false); + KateViewConfig::global()->setValue(KateViewConfig::WordCompletionRemoveTail, false); TestPressKey("qqi\\ctrl- \\enter\\ctrl-cq"); kate_document->setText("oldwordshouldbeuntouched"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("123_completionAoldwordshouldbeuntouched"); // ... but do if we are swallowing tails. clearAllMacros(); BeginTest(""); fakeCodeCompletionModel->setCompletions({ "123_completionA" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); fakeCodeCompletionModel->setRemoveTailOnComplete(true); - KateViewConfig::global()->setWordCompletionRemoveTail(true); + KateViewConfig::global()->setValue(KateViewConfig::WordCompletionRemoveTail, true); TestPressKey("qqi\\ctrl- \\enter\\ctrl-cq"); kate_document->setText("oldwordshouldbedeleted"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("123_completionA"); // Completion of functions. // Currently, not removing the tail on function completion is not supported. fakeCodeCompletionModel->setRemoveTailOnComplete(true); - KateViewConfig::global()->setWordCompletionRemoveTail(true); + KateViewConfig::global()->setValue(KateViewConfig::WordCompletionRemoveTail, true); // A completed, no argument function "function()" is repeated correctly. BeginTest(""); fakeCodeCompletionModel->setCompletions({ "function()" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifunc\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("dd@q"); FinishTest("function()"); // Cursor is placed after the closing bracket when completion a no-arg function. BeginTest(""); fakeCodeCompletionModel->setCompletions({ "function()" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifunc\\ctrl- \\enter.something();\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("dd@q"); FinishTest("function().something();"); // A function taking some arguments, repeated where there is no opening bracket to // merge with, is repeated as "function()"). BeginTest(""); fakeCodeCompletionModel->setCompletions({ "function(...)" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifunc\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("dd@q"); FinishTest("function()"); // A function taking some arguments, repeated where there is no opening bracket to // merge with, places the cursor after the opening bracket. BeginTest(""); fakeCodeCompletionModel->setCompletions({ "function(...)" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifunc\\ctrl- \\enterfirstArg\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("dd@q"); FinishTest("function(firstArg)"); // A function taking some arguments, recorded where there was an opening bracket to merge // with but repeated where there is no such bracket, is repeated as "function()" and the // cursor placed appropriately. BeginTest("(<-Mergeable opening bracket)"); fakeCodeCompletionModel->setCompletions({ "function(...)" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifunc\\ctrl- \\enterfirstArg\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("dd@q"); FinishTest("function(firstArg)"); // A function taking some arguments, recorded where there was no opening bracket to merge // with but repeated where there is such a bracket, is repeated as "function" and the // cursor moved to after the merged opening bracket. BeginTest(""); fakeCodeCompletionModel->setCompletions({ "function(...)" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifunc\\ctrl- \\enterfirstArg\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("(<-firstArg goes here)"); TestPressKey("gg@q"); FinishTest("function(firstArg<-firstArg goes here)"); // A function taking some arguments, recorded where there was an opening bracket to merge // with and repeated where there is also such a bracket, is repeated as "function" and the // cursor moved to after the merged opening bracket. BeginTest("(<-mergeablebracket)"); fakeCodeCompletionModel->setCompletions({ "function(...)" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifunc\\ctrl- \\enterfirstArg\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("(<-firstArg goes here)"); TestPressKey("gg@q"); FinishTest("function(firstArg<-firstArg goes here)"); // The mergeable bracket can be separated by whitespace; the cursor is still placed after the // opening bracket. BeginTest(""); fakeCodeCompletionModel->setCompletions({ "function(...)" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifunc\\ctrl- \\enterfirstArg\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText(" \t (<-firstArg goes here)"); TestPressKey("gg@q"); FinishTest("function \t (firstArg<-firstArg goes here)"); // Whitespace only, though! BeginTest(""); fakeCodeCompletionModel->setCompletions({ "function(...)" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifunc\\ctrl- \\enterfirstArg\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("| \t ()"); TestPressKey("gg@q"); FinishTest("function(firstArg)| \t ()"); // The opening bracket can actually be after the current word (with optional whitespace). // Note that this wouldn't be the case if we weren't swallowing tails when completion functions, // but this is not currently supported. BeginTest("function"); fakeCodeCompletionModel->setCompletions({ "function(...)" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqfta\\ctrl- \\enterfirstArg\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("functxyz (<-firstArg goes here)"); TestPressKey("gg@q"); FinishTest("function (firstArg<-firstArg goes here)"); // Regression test for weird issue with replaying completions when the character to the left of the cursor // is not a word char. BeginTest(""); fakeCodeCompletionModel->setCompletions({ "completionA" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqciw\\ctrl- \\enter\\ctrl-cq"); TestPressKey("ddi.xyz\\enter123\\enter456\\ctrl-cggl"); // Position cursor just after the "." TestPressKey("@q"); FinishTest(".completionA\n123\n456"); BeginTest(""); fakeCodeCompletionModel->setCompletions({ "completionA" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqciw\\ctrl- \\enter\\ctrl-cq"); TestPressKey("ddi.xyz.abc\\enter123\\enter456\\ctrl-cggl"); // Position cursor just after the "." TestPressKey("@q"); FinishTest(".completionA.abc\n123\n456"); // Functions taking no arguments are never bracket-merged. BeginTest(""); fakeCodeCompletionModel->setCompletions({ "function()" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifunc\\ctrl- \\enter.something();\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("(<-don't merge this bracket)"); TestPressKey("gg@q"); FinishTest("function().something();(<-don't merge this bracket)"); // Not-removing-tail when completing functions is not currently supported, // so ignore the "do-not-remove-tail" settings when we try this. BeginTest("funct"); fakeCodeCompletionModel->setCompletions({ "function(...)" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); - KateViewConfig::global()->setWordCompletionRemoveTail(false); + KateViewConfig::global()->setValue(KateViewConfig::WordCompletionRemoveTail, false); TestPressKey("qqfta\\ctrl- \\enterfirstArg\\ctrl-cq"); kate_document->setText("functxyz"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("function(firstArg)"); BeginTest("funct"); fakeCodeCompletionModel->setCompletions({ "function()" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); - KateViewConfig::global()->setWordCompletionRemoveTail(false); + KateViewConfig::global()->setValue(KateViewConfig::WordCompletionRemoveTail, false); TestPressKey("qqfta\\ctrl- \\enter\\ctrl-cq"); kate_document->setText("functxyz"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("function()"); - KateViewConfig::global()->setWordCompletionRemoveTail(true); + KateViewConfig::global()->setValue(KateViewConfig::WordCompletionRemoveTail, true); // Deal with cases where completion ends with ";". BeginTest(""); fakeCodeCompletionModel->setCompletions({ "function();" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifun\\ctrl- \\enter\\ctrl-cq"); kate_document->clear(); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("function();"); BeginTest(""); fakeCodeCompletionModel->setCompletions({ "function();" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifun\\ctrl- \\enterX\\ctrl-cq"); kate_document->clear(); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("function();X"); BeginTest(""); fakeCodeCompletionModel->setCompletions({ "function(...);" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifun\\ctrl- \\enter\\ctrl-cq"); kate_document->clear(); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("function();"); BeginTest(""); fakeCodeCompletionModel->setCompletions({ "function(...);" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifun\\ctrl- \\enterX\\ctrl-cq"); kate_document->clear(); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("function(X);"); // Tests for completions ending in ";" where bracket merging should happen on replay. // NB: bracket merging when recording is impossible with completions that end in ";". BeginTest(""); fakeCodeCompletionModel->setCompletions({ "function(...);" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifun\\ctrl- \\enter\\ctrl-cq"); kate_document->setText("(<-mergeable bracket"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("function(<-mergeable bracket"); BeginTest(""); fakeCodeCompletionModel->setCompletions({ "function(...);" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifun\\ctrl- \\enterX\\ctrl-cq"); kate_document->setText("(<-mergeable bracket"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("function(X<-mergeable bracket"); // Don't merge no arg functions. BeginTest(""); fakeCodeCompletionModel->setCompletions({ "function();" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifun\\ctrl- \\enterX\\ctrl-cq"); kate_document->setText("(<-mergeable bracket"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("function();X(<-mergeable bracket"); { const QString viTestKConfigFileName = "vimodetest-katevimoderc"; KConfig viTestKConfig(viTestKConfigFileName); // Test loading and saving of macro completions. clearAllMacros(); BeginTest("funct\nnoa\ncomtail\ncomtail\ncom"); fakeCodeCompletionModel->setCompletions({ "completionA", "functionwithargs(...)", "noargfunction()" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); // Record 'a'. TestPressKey("qafta\\ctrl- \\enterfirstArg\\ctrl-c"); // Function with args. TestPressKey("\\enterea\\ctrl- \\enter\\ctrl-c"); // Function no args. fakeCodeCompletionModel->setRemoveTailOnComplete(true); - KateViewConfig::global()->setWordCompletionRemoveTail(true); + KateViewConfig::global()->setValue(KateViewConfig::WordCompletionRemoveTail, true); TestPressKey("\\enterfti\\ctrl- \\enter\\ctrl-c"); // Cut off tail. fakeCodeCompletionModel->setRemoveTailOnComplete(false); - KateViewConfig::global()->setWordCompletionRemoveTail(false); + KateViewConfig::global()->setValue(KateViewConfig::WordCompletionRemoveTail, false); TestPressKey("\\enterfti\\ctrl- \\enter\\ctrl-cq"); // Don't cut off tail. fakeCodeCompletionModel->setRemoveTailOnComplete(true); - KateViewConfig::global()->setWordCompletionRemoveTail(true); + KateViewConfig::global()->setValue(KateViewConfig::WordCompletionRemoveTail, true); // Record 'b'. fakeCodeCompletionModel->setCompletions({ "completionB", "semicolonfunctionnoargs();", "semicolonfunctionwithargs(...);" }); TestPressKey("\\enterqbea\\ctrl- \\enter\\ctrl-cosemicolonfunctionw\\ctrl- \\enterX\\ctrl-cosemicolonfunctionn\\ctrl- \\enterX\\ctrl-cq"); // Save. vi_global->writeConfig(&viTestKConfig); viTestKConfig.sync(); // Overwrite 'a' and 'b' and their completions. fakeCodeCompletionModel->setCompletions({ "blah1" }); kate_document->setText(""); TestPressKey("ggqaiblah\\ctrl- \\enter\\ctrl-cq"); TestPressKey("ddqbiblah\\ctrl- \\enter\\ctrl-cq"); // Reload. vi_global->readConfig(&viTestKConfig); // Replay reloaded. fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("funct\nnoa\ncomtail\ncomtail\ncom"); TestPressKey("gg@a\\enter@b"); FinishTest("functionwithargs(firstArg)\nnoargfunction()\ncompletionA\ncompletionAtail\ncompletionB\nsemicolonfunctionwithargs(X);\nsemicolonfunctionnoargs();X"); } // Check that undo/redo operations work properly with macros. { clearAllMacros(); BeginTest(""); TestPressKey("ihello\\ctrl-cqauq"); TestPressKey("@a\\enter"); FinishTest(""); } { clearAllMacros(); BeginTest(""); TestPressKey("ihello\\ctrl-cui.bye\\ctrl-cu"); TestPressKey("qa\\ctrl-r\\enterq"); TestPressKey("@a\\enter"); FinishTest(".bye"); } // When replaying a last change in the process of replaying a macro, take the next completion // event from the last change completions log, rather than the macro completions log. // Ensure that the last change completions log is kept up to date even while we're replaying the macro. if (false) { // FIXME: test currently fails in newer Qt >= 5.11, but works with Qt 5.10 clearAllMacros(); BeginTest(""); fakeCodeCompletionModel->setCompletions({ "completionMacro", "completionRepeatLastChange" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqicompletionM\\ctrl- \\enter\\ctrl-c"); TestPressKey("a completionRep\\ctrl- \\enter\\ctrl-c"); TestPressKey(".q"); qDebug() << "text: " << kate_document->text(); kate_document->clear(); TestPressKey("gg@q"); FinishTest("completionMacro completionRepeatLastChange completionRepeatLastChange"); } - KateViewConfig::global()->setWordCompletionRemoveTail(oldRemoveTailOnCompletion); + KateViewConfig::global()->setValue(KateViewConfig::WordCompletionRemoveTail, oldRemoveTailOnCompletion); kate_document->config()->setReplaceTabsDyn(oldReplaceTabsDyn); kate_view->unregisterCompletionModel(fakeCodeCompletionModel); delete fakeCodeCompletionModel; fakeCodeCompletionModel = nullptr; // Hide the kate_view for subsequent tests. kate_view->hide(); mainWindow->hide(); - KateViewConfig::global()->setViInputModeStealKeys(oldStealKeys); + KateViewConfig::global()->setValue(KateViewConfig::ViInputModeStealKeys, oldStealKeys); } void KeysTest::MarkTests() { // Difference between ` and ' DoTest(" a\n b", "jmak'aii", " a\n ib"); DoTest(" a\n b", "jmak`aii", " a\ni b"); // Last edit markers. DoTest("foo", "ean\\escgg`.r.", "foo."); DoTest("foo", "ean\\escgg`[r[", "foo["); DoTest("foo", "ean\\escgg`]r]", "foo]"); DoTest("foo bar", "ean\\escgg`]r]", "foon]bar"); DoTest("", "ibar\\escgg`.r.", "ba."); DoTest("", "ibar\\escgggUiw`.r.", ".AR"); DoTest("", "2ibar\\escgg`]r]", "barba]"); DoTest("", "2ibar\\escgg`[r[", "[arbar"); DoTest("", "3ibar\\escgg`.r.", "barbar.ar"); // Vim is weird. DoTest("", "abar\\esc.gg`]r]", "barba]"); DoTest("foo bar", "wgUiwgg`]r]", "foo BA]"); DoTest("foo bar", "wgUiwgg`.r.", "foo .AR"); DoTest("foo bar", "gUiwgg`]r.", "FO. bar"); DoTest("foo bar", "wdiwgg`[r[", "foo["); DoTest("foo bar", "wdiwgg`]r]", "foo]"); DoTest("foo bar", "wdiwgg`.r.", "foo."); DoTest("foo bar", "wciwnose\\escgg`.r.", "foo nos."); DoTest("foo bar", "wciwnose\\escgg`[r[", "foo [ose"); DoTest("foo bar", "wciwnose\\escgg`]r]", "foo nos]"); DoTest("foo", "~ibar\\escgg`[r[", "F[aroo"); DoTest("foo bar", "lragg`.r.", "f.o bar"); DoTest("foo bar", "lragg`[r[", "f[o bar"); DoTest("foo bar", "lragg`]r]", "f]o bar"); DoTest("", "ifoo\\ctrl-hbar\\esc`[r[", "[obar"); DoTest("", "ifoo\\ctrl-wbar\\esc`[r[", "[ar"); DoTest("", "if\\ctrl-hbar\\esc`[r[", "[ar"); DoTest("", "5ofoo\\escgg`[r[", "\n[oo\nfoo\nfoo\nfoo\nfoo"); DoTest("", "5ofoo\\escgg`]r]", "\nfoo\nfoo\nfoo\nfoo\nfo]"); DoTest("", "5ofoo\\escgg`.r.", "\nfoo\nfoo\nfoo\nfoo\n.oo"); DoTest("foo", "yyp`[r[", "foo\n[oo"); DoTest("xyz\nfoo", "ja\\returnbar\\esc`[r[", "xyz\n[\nbaroo"); DoTest("foo", "lrayypgg`[r[", "fao\n[ao"); DoTest("foo", "l~u`[r[", "[oo"); DoTest("foo", "l~u`.r.", ".oo"); DoTest("foo", "l~u`]r]", "]oo"); DoTest("foo", "lia\\escu`[r[", "[oo"); DoTest("foo", "lia\\escu`.r.", ".oo"); DoTest("foo", "lia\\escu`]r]", "]oo"); DoTest("foo", "l~u~`[r[", "f[o"); DoTest("foo\nbar\nxyz", "jyypu`[r[", "foo\nbar\n[yz"); DoTest("foo\nbar\nxyz", "jyypu`.r.", "foo\nbar\n.yz"); DoTest("foo\nbar\nxyz", "jyypu`]r]", "foo\nbar\n]yz"); DoTest("foo\nbar\nxyz\n123", "jdju`[r[", "foo\n[ar\nxyz\n123"); DoTest("foo\nbar\nxyz\n123", "jdju`.r.", "foo\n.ar\nxyz\n123"); DoTest("foo\nbar\nxyz\n123", "jdju`]r]", "foo\nbar\n]yz\n123"); DoTest("foo\nbar\nxyz\n123", "jVj~u\\esc`[r[", "foo\n[ar\nxyz\n123", ShouldFail, "Vim is weird."); } //END: KeysTest diff --git a/autotests/src/vimode/view.cpp b/autotests/src/vimode/view.cpp index c2237a19..ed778eb2 100644 --- a/autotests/src/vimode/view.cpp +++ b/autotests/src/vimode/view.cpp @@ -1,416 +1,416 @@ /* * This file is part of the KDE libraries * * Copyright (C) 2014 Miquel Sabaté Solà * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include "view.h" using namespace KTextEditor; QTEST_MAIN(ViewTest) void ViewTest::yankHighlightingTests() { const QColor yankHighlightColour = kate_view->renderer()->config()->savedLineColor(); BeginTest("foo bar xyz"); const QList rangesInitial = rangesOnFirstLine(); Q_ASSERT(rangesInitial.isEmpty() && "Assumptions about ranges are wrong - this test is invalid and may need updating!"); TestPressKey("wyiw"); { const QList rangesAfterYank = rangesOnFirstLine(); QCOMPARE(rangesAfterYank.size(), rangesInitial.size() + 1); QCOMPARE(rangesAfterYank.first()->attribute()->background().color(), yankHighlightColour); QCOMPARE(rangesAfterYank.first()->start().line(), 0); QCOMPARE(rangesAfterYank.first()->start().column(), 4); QCOMPARE(rangesAfterYank.first()->end().line(), 0); QCOMPARE(rangesAfterYank.first()->end().column(), 7); } FinishTest("foo bar xyz"); BeginTest("foom bar xyz"); TestPressKey("wY"); { const QList rangesAfterYank = rangesOnFirstLine(); QCOMPARE(rangesAfterYank.size(), rangesInitial.size() + 1); QCOMPARE(rangesAfterYank.first()->attribute()->background().color(), yankHighlightColour); QCOMPARE(rangesAfterYank.first()->start().line(), 0); QCOMPARE(rangesAfterYank.first()->start().column(), 5); QCOMPARE(rangesAfterYank.first()->end().line(), 0); QCOMPARE(rangesAfterYank.first()->end().column(), 12); } FinishTest("foom bar xyz"); // Unhighlight on keypress. DoTest("foo bar xyz", "yiww", "foo bar xyz"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size()); // Update colour on config change. DoTest("foo bar xyz", "yiw", "foo bar xyz"); const QColor newYankHighlightColour = QColor(255, 0, 0); kate_view->renderer()->config()->setSavedLineColor(newYankHighlightColour); QCOMPARE(rangesOnFirstLine().first()->attribute()->background().color(), newYankHighlightColour); // Visual Mode. DoTest("foo", "viwy", "foo"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size() + 1); // Unhighlight on keypress in Visual Mode DoTest("foo", "viwyw", "foo"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size()); // Add a yank highlight and directly (i.e. without using Vim commands, // which would clear the highlight) delete all text; if this deletes the yank highlight behind our back // and we don't respond correctly to this, it will be double-deleted by KateViNormalMode. // Currently, this seems like it doesn't occur, but better safe than sorry :) BeginTest("foo bar xyz"); TestPressKey("yiw"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size() + 1); kate_document->documentReload(); kate_document->clear(); vi_input_mode->reset(); vi_input_mode_manager = vi_input_mode->viInputModeManager(); FinishTest(""); } void ViewTest::visualLineUpDownTests() { // Need to ensure we have dynamic wrap, a fixed width font, and a decent size kate_view. ensureKateViewVisible(); const QFont oldFont = kate_view->renderer()->config()->font(); QFont fixedWidthFont("Courier"); fixedWidthFont.setStyleHint(QFont::TypeWriter); Q_ASSERT_X(QFontInfo(fixedWidthFont).fixedPitch(), "setting up visual line up down tests", "Need a fixed pitch font!"); kate_view->renderer()->config()->setFont(fixedWidthFont); const bool oldDynWordWrap = KateViewConfig::global()->dynWordWrap(); KateViewConfig::global()->setDynWordWrap(true); const bool oldReplaceTabsDyn = kate_document->config()->replaceTabsDyn(); kate_document->config()->setReplaceTabsDyn(false); const int oldTabWidth = kate_document->config()->tabWidth(); const int tabWidth = 5; kate_document->config()->setTabWidth(tabWidth); - KateViewConfig::global()->setShowScrollbars(0); + KateViewConfig::global()->setValue(KateViewConfig::ShowScrollbars, KateViewConfig::ScrollbarMode::AlwaysOn); // Compute the maximum width of text before line-wrapping sets it. int textWrappingLength = 1; while (true) { QString text = QString("X").repeated(textWrappingLength) + ' ' + 'O'; const int posOfO = text.length() - 1; kate_document->setText(text); if (kate_view->cursorToCoordinate(Cursor(0, posOfO)).y() != kate_view->cursorToCoordinate(Cursor(0, 0)).y()) { textWrappingLength++; // Number of x's, plus space. break; } textWrappingLength++; } const QString fillsLineAndEndsOnSpace = QString("X").repeated(textWrappingLength - 1) + ' '; // Create a QString consisting of enough concatenated fillsLineAndEndsOnSpace to completely // fill the viewport of the kate View. QString fillsView = fillsLineAndEndsOnSpace; while (true) { kate_document->setText(fillsView); const QString visibleText = kate_document->text(kate_view->visibleRange()); if (fillsView.length() > visibleText.length() * 2) { // Overkill. break; } fillsView += fillsLineAndEndsOnSpace; } const int numVisibleLinesToFillView = fillsView.length() / fillsLineAndEndsOnSpace.length(); { // gk/ gj when there is only one line. DoTest("foo", "lgkr.", "f.o"); DoTest("foo", "lgjr.", "f.o"); } { // gk when sticky bit is set to the end. const QString originalText = fillsLineAndEndsOnSpace.repeated(2); QString expectedText = originalText; kate_document->setText(originalText); Q_ASSERT(expectedText[textWrappingLength - 1] == ' '); expectedText[textWrappingLength - 1] = '.'; DoTest(originalText, "$gkr.", expectedText); } { // Regression test: more than fill the view up, go to end, and do gk on wrapped text (used to crash). // First work out the text that will fill up the view. QString expectedText = fillsView; Q_ASSERT(expectedText[expectedText.length() - textWrappingLength - 1] == ' '); expectedText[expectedText.length() - textWrappingLength - 1] = '.'; DoTest(fillsView, "$gkr.", expectedText); } { // Jump down a few lines all in one go, where we have some variable length lines to navigate. const int numVisualLinesOnLine[] = { 3, 5, 2, 3 }; const int numLines = sizeof(numVisualLinesOnLine) / sizeof(int); const int startVisualLine = 2; const int numberLinesToGoDownInOneGo = 10; int totalVisualLines = 0; for (int i = 0; i < numLines; i++) { totalVisualLines += numVisualLinesOnLine[i]; } QString startText; for (int i = 0; i < numLines; i++) { QString thisLine = fillsLineAndEndsOnSpace.repeated(numVisualLinesOnLine[i]); // Replace trailing space with carriage return. thisLine.chop(1); thisLine.append('\n'); startText += thisLine; } QString expectedText = startText; expectedText[((startVisualLine - 1) + numberLinesToGoDownInOneGo) * fillsLineAndEndsOnSpace.length()] = '.'; Q_ASSERT(numberLinesToGoDownInOneGo + startVisualLine < totalVisualLines); Q_ASSERT(numberLinesToGoDownInOneGo + startVisualLine < numVisibleLinesToFillView); DoTest(startText, QString("gj").repeated(startVisualLine - 1) + QString::number(numberLinesToGoDownInOneGo) + "gjr.", expectedText); // Now go up a few lines. const int numLinesToGoBackUp = 7; expectedText = startText; expectedText[((startVisualLine - 1) + numberLinesToGoDownInOneGo - numLinesToGoBackUp) * fillsLineAndEndsOnSpace.length()] = '.'; DoTest(startText, QString("gj").repeated(startVisualLine - 1) + QString::number(numberLinesToGoDownInOneGo) + "gj" + QString::number(numLinesToGoBackUp) + "gkr.", expectedText); } { // Move down enough lines in one go to disappear off the view. // About half-a-viewport past the end of the current viewport. const int numberLinesToGoDown = numVisibleLinesToFillView * 3 / 2; const int visualColumnNumber = 7; Q_ASSERT(fillsLineAndEndsOnSpace.length() > visualColumnNumber); QString expectedText = fillsView.repeated(2); Q_ASSERT(expectedText[expectedText.length() - textWrappingLength - 1] == ' '); expectedText[visualColumnNumber + fillsLineAndEndsOnSpace.length() * numberLinesToGoDown] = '.'; DoTest(fillsView.repeated(2), QString("l").repeated(visualColumnNumber) + QString::number(numberLinesToGoDown) + "gjr.", expectedText); } { // Deal with dynamic wrapping and indented blocks - continuations of a line are "invisibly" idented by // the same amount as the beginning of the line, and we have to subtract this indentation. const QString unindentedFirstLine = "stickyhelper\n"; const int numIndentationSpaces = 5; Q_ASSERT(textWrappingLength > numIndentationSpaces * 2 /* keep some wriggle room */); const QString indentedFillsLineEndsOnSpace = QString(" ").repeated(numIndentationSpaces) + QString("X").repeated(textWrappingLength - 1 - numIndentationSpaces) + ' '; DoTest(unindentedFirstLine + indentedFillsLineEndsOnSpace + "LINE3", QString("l").repeated(numIndentationSpaces) + "jgjr.", unindentedFirstLine + indentedFillsLineEndsOnSpace + ".INE3"); // The first, non-wrapped portion of the line is not invisibly indented, though, so ensure we don't mess that up. QString expectedSecondLine = indentedFillsLineEndsOnSpace; expectedSecondLine[numIndentationSpaces] = '.'; DoTest(unindentedFirstLine + indentedFillsLineEndsOnSpace + "LINE3", QString("l").repeated(numIndentationSpaces) + "jgjgkr.", unindentedFirstLine + expectedSecondLine + "LINE3"); } { // Take into account any invisible indentation when setting the sticky column. const int numIndentationSpaces = 5; Q_ASSERT(textWrappingLength > numIndentationSpaces * 2 /* keep some wriggle room */); const QString indentedFillsLineEndsOnSpace = QString(" ").repeated(numIndentationSpaces) + QString("X").repeated(textWrappingLength - 1 - numIndentationSpaces) + ' '; const int posInSecondWrappedLineToChange = 3; QString expectedText = indentedFillsLineEndsOnSpace + fillsLineAndEndsOnSpace; expectedText[textWrappingLength + posInSecondWrappedLineToChange] = '.'; DoTest(indentedFillsLineEndsOnSpace + fillsLineAndEndsOnSpace, QString::number(textWrappingLength + posInSecondWrappedLineToChange) + "lgkgjr.", expectedText); // Make sure we can do this more than once (i.e. clear any flags that need clearing). DoTest(indentedFillsLineEndsOnSpace + fillsLineAndEndsOnSpace, QString::number(textWrappingLength + posInSecondWrappedLineToChange) + "lgkgjr.", expectedText); } { // Take into account any invisible indentation when setting the sticky column as above, but use tabs. const QString indentedFillsLineEndsOnSpace = QString("\t") + QString("X").repeated(textWrappingLength - 1 - tabWidth) + ' '; const int posInSecondWrappedLineToChange = 3; QString expectedText = indentedFillsLineEndsOnSpace + fillsLineAndEndsOnSpace; expectedText[textWrappingLength - tabWidth + posInSecondWrappedLineToChange] = '.'; DoTest(indentedFillsLineEndsOnSpace + fillsLineAndEndsOnSpace, QString("fXf ") + QString::number(posInSecondWrappedLineToChange) + "lgkgjr.", expectedText); } { // Deal with the fact that j/ k may set a sticky column that is impossible to adhere to in visual mode because // it is too high. // Here, we have one dummy line and one wrapped line. We start from the beginning of the wrapped line and // move right until we wrap and end up at posInWrappedLineToChange one the second line of the wrapped line. // We then move up and down with j and k to set the sticky column to a value to large to adhere to in a // visual line, and try to move a visual line up. const QString dummyLineForUseWithK("dummylineforusewithk\n"); QString startText = dummyLineForUseWithK + fillsLineAndEndsOnSpace.repeated(2); const int posInWrappedLineToChange = 3; QString expectedText = startText; expectedText[dummyLineForUseWithK.length() + posInWrappedLineToChange] = '.'; DoTest(startText, 'j' + QString::number(textWrappingLength + posInWrappedLineToChange) + "lkjgkr.", expectedText); } { // Ensure gj works in Visual mode. Q_ASSERT(fillsLineAndEndsOnSpace.toLower() != fillsLineAndEndsOnSpace); QString expectedText = fillsLineAndEndsOnSpace.toLower() + fillsLineAndEndsOnSpace; expectedText[textWrappingLength] = expectedText[textWrappingLength].toLower(); DoTest(fillsLineAndEndsOnSpace.repeated(2), "vgjgu", expectedText); } { // Ensure gk works in Visual mode. Q_ASSERT(fillsLineAndEndsOnSpace.toLower() != fillsLineAndEndsOnSpace); DoTest(fillsLineAndEndsOnSpace.repeated(2), "$vgkgu", fillsLineAndEndsOnSpace + fillsLineAndEndsOnSpace.toLower()); } { // Some tests for how well we handle things with real tabs. QString beginsWithTabFillsLineEndsOnSpace = "\t"; while (beginsWithTabFillsLineEndsOnSpace.length() + (tabWidth - 1) < textWrappingLength - 1) { beginsWithTabFillsLineEndsOnSpace += 'X'; } beginsWithTabFillsLineEndsOnSpace += ' '; const QString unindentedFirstLine = "stockyhelper\n"; const int posOnThirdLineToChange = 3; QString expectedThirdLine = fillsLineAndEndsOnSpace; expectedThirdLine[posOnThirdLineToChange] = '.'; DoTest(unindentedFirstLine + beginsWithTabFillsLineEndsOnSpace + fillsLineAndEndsOnSpace, QString("l").repeated(tabWidth + posOnThirdLineToChange) + "gjgjr.", unindentedFirstLine + beginsWithTabFillsLineEndsOnSpace + expectedThirdLine); // As above, but go down twice and return to the middle line. const int posOnSecondLineToChange = 2; QString expectedSecondLine = beginsWithTabFillsLineEndsOnSpace; expectedSecondLine[posOnSecondLineToChange + 1 /* "+1" as we're not counting the leading tab as a pos */] = '.'; DoTest(unindentedFirstLine + beginsWithTabFillsLineEndsOnSpace + fillsLineAndEndsOnSpace, QString("l").repeated(tabWidth + posOnSecondLineToChange) + "gjgjgkr.", unindentedFirstLine + expectedSecondLine + fillsLineAndEndsOnSpace); } // Restore back to how we were before. kate_view->renderer()->config()->setFont(oldFont); KateViewConfig::global()->setDynWordWrap(oldDynWordWrap); kate_document->config()->setReplaceTabsDyn(oldReplaceTabsDyn); kate_document->config()->setTabWidth(oldTabWidth); } void ViewTest::ScrollViewTests() { QSKIP("This is too unstable in Jenkins", SkipAll); // First of all, we have to initialize some sizes and fonts. ensureKateViewVisible(); const QFont oldFont = kate_view->renderer()->config()->font(); QFont fixedWidthFont("Monospace"); fixedWidthFont.setStyleHint(QFont::TypeWriter); fixedWidthFont.setPixelSize(14); Q_ASSERT_X(QFontInfo(fixedWidthFont).fixedPitch(), "setting up ScrollViewTests", "Need a fixed pitch font!"); kate_view->renderer()->config()->setFont(fixedWidthFont); // Generating our text here. QString text; for (int i = 0; i < 20; i++) { text += " aaaaaaaaaaaaaaaa\n"; } // TODO: fix the visibleRange's tests. // zz BeginTest(text); TestPressKey("10l9jzz"); QCOMPARE(kate_view->cursorPosition().line(), 9); QCOMPARE(kate_view->cursorPosition().column(), 10); QCOMPARE(kate_view->visibleRange(), Range(4, 0, 13, 20)); FinishTest(text); // z. BeginTest(text); TestPressKey("10l9jz."); QCOMPARE(kate_view->cursorPosition().line(), 9); QCOMPARE(kate_view->cursorPosition().column(), 4); QCOMPARE(kate_view->visibleRange(), Range(4, 0, 13, 20)); FinishTest(text); // zt BeginTest(text); TestPressKey("10l9jzt"); QCOMPARE(kate_view->cursorPosition().line(), 9); QCOMPARE(kate_view->cursorPosition().column(), 10); QCOMPARE(kate_view->visibleRange(), Range(9, 0, 18, 20)); FinishTest(text); // z BeginTest(text); TestPressKey("10l9jz\\return"); QCOMPARE(kate_view->cursorPosition().line(), 9); QCOMPARE(kate_view->cursorPosition().column(), 4); QCOMPARE(kate_view->visibleRange(), Range(9, 0, 18, 20)); FinishTest(text); // zb BeginTest(text); TestPressKey("10l9jzb"); QCOMPARE(kate_view->cursorPosition().line(), 9); QCOMPARE(kate_view->cursorPosition().column(), 10); QCOMPARE(kate_view->visibleRange(), Range(0, 0, 9, 20)); FinishTest(text); // z- BeginTest(text); TestPressKey("10l9jz-"); QCOMPARE(kate_view->cursorPosition().line(), 9); QCOMPARE(kate_view->cursorPosition().column(), 4); QCOMPARE(kate_view->visibleRange(), Range(0, 0, 9, 20)); FinishTest(text); // Restore back to how we were before. kate_view->renderer()->config()->setFont(oldFont); } void ViewTest::clipboardTests_data() { QTest::addColumn("text"); QTest::addColumn("commands"); QTest::addColumn("clipboard"); QTest::newRow("yank") << "yyfoo\nbar" << "yy" << "yyfoo\n"; QTest::newRow("delete") << "ddfoo\nbar" << "dd" << "ddfoo\n"; QTest::newRow("yank empty line") << "\nbar" << "yy" << QString(); QTest::newRow("delete word") << "word foo" << "dw" << "word "; QTest::newRow("delete onechar word") << "w foo" << "dw" << "w "; QTest::newRow("delete onechar") << "word foo" << "dc" << QString(); QTest::newRow("delete empty lines") << " \t\n\n \nfoo" << "d3d" << QString(); } void ViewTest::clipboardTests() { QFETCH(QString, text); QFETCH(QString, commands); QFETCH(QString, clipboard); QApplication::clipboard()->clear(); BeginTest(text); TestPressKey(commands); QCOMPARE(QApplication::clipboard()->text(), clipboard); } QList ViewTest::rangesOnFirstLine() { return kate_document->buffer().rangesForLine(0, kate_view, true); } diff --git a/src/dialogs/katedialogs.cpp b/src/dialogs/katedialogs.cpp index 10a56767..853e8acc 100644 --- a/src/dialogs/katedialogs.cpp +++ b/src/dialogs/katedialogs.cpp @@ -1,1402 +1,1399 @@ /* This file is part of the KDE libraries Copyright (C) 2002, 2003 Anders Lund Copyright (C) 2003 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2006 Dominik Haumann Copyright (C) 2007 Mirko Stocker Copyright (C) 2009 Michel Ludwig Copyright (C) 2009 Erlend Hamberg Based on work of: Copyright (C) 1999 Jochen Wilhelmy This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ //BEGIN Includes #include "katedialogs.h" #include #include #include "kateautoindent.h" #include "katebuffer.h" #include "kateconfig.h" #include "katedocument.h" #include "kateglobal.h" #include "kateschema.h" #include "katemodeconfigpage.h" #include "kateview.h" #include "spellcheck/spellcheck.h" // auto generated ui files #include "ui_textareaappearanceconfigwidget.h" #include "ui_bordersappearanceconfigwidget.h" #include "ui_navigationconfigwidget.h" #include "ui_editconfigwidget.h" #include "ui_indentationconfigwidget.h" #include "ui_completionconfigtab.h" #include "ui_opensaveconfigwidget.h" #include "ui_opensaveconfigadvwidget.h" #include "ui_spellcheckconfigwidget.h" #include #include #include #include #include #include #include "katepartdebug.h" #include "kateabstractinputmodefactory.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //END //BEGIN KateIndentConfigTab KateIndentConfigTab::KateIndentConfigTab(QWidget *parent) : KateConfigPage(parent) { // This will let us have more separation between this page and // the QTabWidget edge (ereslibre) QVBoxLayout *layout = new QVBoxLayout; QWidget *newWidget = new QWidget(this); ui = new Ui::IndentationConfigWidget(); ui->setupUi(newWidget); ui->cmbMode->addItems(KateAutoIndent::listModes()); ui->label->setTextInteractionFlags(Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard); connect(ui->label, SIGNAL(linkActivated(QString)), this, SLOT(showWhatsThis(QString))); // What's This? help can be found in the ui file reload(); // // after initial reload, connect the stuff for the changed () signal // connect(ui->cmbMode, SIGNAL(activated(int)), this, SLOT(slotChanged())); connect(ui->rbIndentWithTabs, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->rbIndentWithSpaces, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->rbIndentMixed, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->rbIndentWithTabs, SIGNAL(toggled(bool)), ui->sbIndentWidth, SLOT(setDisabled(bool))); connect(ui->chkKeepExtraSpaces, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->chkIndentPaste, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->chkBackspaceUnindents, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->sbTabWidth, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); connect(ui->sbIndentWidth, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); connect(ui->rbTabAdvances, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->rbTabIndents, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->rbTabSmart, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); layout->addWidget(newWidget); setLayout(layout); } KateIndentConfigTab::~KateIndentConfigTab() { delete ui; } void KateIndentConfigTab::slotChanged() { if (ui->rbIndentWithTabs->isChecked()) { ui->sbIndentWidth->setValue(ui->sbTabWidth->value()); } KateConfigPage::slotChanged(); } void KateIndentConfigTab::showWhatsThis(const QString &text) { QWhatsThis::showText(QCursor::pos(), text); } void KateIndentConfigTab::apply() { // nothing changed, no need to apply stuff if (!hasChanged()) { return; } m_changed = false; KateDocumentConfig::global()->configStart(); KateDocumentConfig::global()->setKeepExtraSpaces(ui->chkKeepExtraSpaces->isChecked()); KateDocumentConfig::global()->setBackspaceIndents(ui->chkBackspaceUnindents->isChecked()); KateDocumentConfig::global()->setIndentPastedText(ui->chkIndentPaste->isChecked()); KateDocumentConfig::global()->setIndentationWidth(ui->sbIndentWidth->value()); KateDocumentConfig::global()->setIndentationMode(KateAutoIndent::modeName(ui->cmbMode->currentIndex())); KateDocumentConfig::global()->setTabWidth(ui->sbTabWidth->value()); KateDocumentConfig::global()->setReplaceTabsDyn(ui->rbIndentWithSpaces->isChecked()); if (ui->rbTabAdvances->isChecked()) { KateDocumentConfig::global()->setTabHandling(KateDocumentConfig::tabInsertsTab); } else if (ui->rbTabIndents->isChecked()) { KateDocumentConfig::global()->setTabHandling(KateDocumentConfig::tabIndents); } else { KateDocumentConfig::global()->setTabHandling(KateDocumentConfig::tabSmart); } KateDocumentConfig::global()->configEnd(); } void KateIndentConfigTab::reload() { ui->sbTabWidth->setSuffix(ki18np(" character", " characters")); ui->sbTabWidth->setValue(KateDocumentConfig::global()->tabWidth()); ui->sbIndentWidth->setSuffix(ki18np(" character", " characters")); ui->sbIndentWidth->setValue(KateDocumentConfig::global()->indentationWidth()); ui->chkKeepExtraSpaces->setChecked(KateDocumentConfig::global()->keepExtraSpaces()); ui->chkIndentPaste->setChecked(KateDocumentConfig::global()->indentPastedText()); ui->chkBackspaceUnindents->setChecked(KateDocumentConfig::global()->backspaceIndents()); ui->rbTabAdvances->setChecked(KateDocumentConfig::global()->tabHandling() == KateDocumentConfig::tabInsertsTab); ui->rbTabIndents->setChecked(KateDocumentConfig::global()->tabHandling() == KateDocumentConfig::tabIndents); ui->rbTabSmart->setChecked(KateDocumentConfig::global()->tabHandling() == KateDocumentConfig::tabSmart); ui->cmbMode->setCurrentIndex(KateAutoIndent::modeNumber(KateDocumentConfig::global()->indentationMode())); if (KateDocumentConfig::global()->replaceTabsDyn()) { ui->rbIndentWithSpaces->setChecked(true); } else { if (KateDocumentConfig::global()->indentationWidth() == KateDocumentConfig::global()->tabWidth()) { ui->rbIndentWithTabs->setChecked(true); } else { ui->rbIndentMixed->setChecked(true); } } ui->sbIndentWidth->setEnabled(!ui->rbIndentWithTabs->isChecked()); } QString KateIndentConfigTab::name() const { return i18n("Indentation"); } //END KateIndentConfigTab //BEGIN KateCompletionConfigTab KateCompletionConfigTab::KateCompletionConfigTab(QWidget *parent) : KateConfigPage(parent) { // This will let us have more separation between this page and // the QTabWidget edge (ereslibre) QVBoxLayout *layout = new QVBoxLayout; QWidget *newWidget = new QWidget(this); ui = new Ui::CompletionConfigTab(); ui->setupUi(newWidget); // What's This? help can be found in the ui file reload(); // // after initial reload, connect the stuff for the changed () signal // connect(ui->chkAutoCompletionEnabled, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->gbWordCompletion, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->gbKeywordCompletion, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->minimalWordLength, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); connect(ui->removeTail, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); layout->addWidget(newWidget); setLayout(layout); } KateCompletionConfigTab::~KateCompletionConfigTab() { delete ui; } void KateCompletionConfigTab::showWhatsThis(const QString &text) { QWhatsThis::showText(QCursor::pos(), text); } void KateCompletionConfigTab::apply() { // nothing changed, no need to apply stuff if (!hasChanged()) { return; } m_changed = false; KateViewConfig::global()->configStart(); - KateViewConfig::global()->setAutomaticCompletionInvocation(ui->chkAutoCompletionEnabled->isChecked()); - KateViewConfig::global()->setWordCompletion(ui->gbWordCompletion->isChecked()); - KateViewConfig::global()->setWordCompletionMinimalWordLength(ui->minimalWordLength->value()); - KateViewConfig::global()->setWordCompletionRemoveTail(ui->removeTail->isChecked()); - KateViewConfig::global()->setKeywordCompletion(ui->gbKeywordCompletion->isChecked()); + + KateViewConfig::global()->setValue(KateViewConfig::AutomaticCompletionInvocation, ui->chkAutoCompletionEnabled->isChecked()); + KateViewConfig::global()->setValue(KateViewConfig::KeywordCompletion, ui->gbKeywordCompletion->isChecked()); + KateViewConfig::global()->setValue(KateViewConfig::WordCompletion, ui->gbWordCompletion->isChecked()); + KateViewConfig::global()->setValue(KateViewConfig::WordCompletionMinimalWordLength, ui->minimalWordLength->value()); + KateViewConfig::global()->setValue(KateViewConfig::WordCompletionRemoveTail, ui->removeTail->isChecked()); + KateViewConfig::global()->configEnd(); } void KateCompletionConfigTab::reload() { ui->chkAutoCompletionEnabled->setChecked(KateViewConfig::global()->automaticCompletionInvocation()); ui->gbWordCompletion->setChecked(KateViewConfig::global()->wordCompletion()); ui->minimalWordLength->setValue(KateViewConfig::global()->wordCompletionMinimalWordLength()); ui->gbKeywordCompletion->setChecked(KateViewConfig::global()->keywordCompletion()); ui->removeTail->setChecked(KateViewConfig::global()->wordCompletionRemoveTail()); } QString KateCompletionConfigTab::name() const { return i18n("Auto Completion"); } //END KateCompletionConfigTab //BEGIN KateSpellCheckConfigTab KateSpellCheckConfigTab::KateSpellCheckConfigTab(QWidget *parent) : KateConfigPage(parent) { // This will let us have more separation between this page and // the QTabWidget edge (ereslibre) QVBoxLayout *layout = new QVBoxLayout; QWidget *newWidget = new QWidget(this); ui = new Ui::SpellCheckConfigWidget(); ui->setupUi(newWidget); // What's This? help can be found in the ui file reload(); // // after initial reload, connect the stuff for the changed () signal m_sonnetConfigWidget = new Sonnet::ConfigWidget(this); connect(m_sonnetConfigWidget, SIGNAL(configChanged()), this, SLOT(slotChanged())); layout->addWidget(m_sonnetConfigWidget); layout->addWidget(newWidget); setLayout(layout); } KateSpellCheckConfigTab::~KateSpellCheckConfigTab() { delete ui; } void KateSpellCheckConfigTab::showWhatsThis(const QString &text) { QWhatsThis::showText(QCursor::pos(), text); } void KateSpellCheckConfigTab::apply() { if (!hasChanged()) { // nothing changed, no need to apply stuff return; } m_changed = false; // WARNING: this is slightly hackish, but it's currently the only way to // do it, see also the KTextEdit class KateDocumentConfig::global()->configStart(); m_sonnetConfigWidget->save(); QSettings settings(QStringLiteral("KDE"), QStringLiteral("Sonnet")); KateDocumentConfig::global()->setOnTheFlySpellCheck(settings.value(QStringLiteral("checkerEnabledByDefault"), false).toBool()); KateDocumentConfig::global()->configEnd(); foreach (KTextEditor::DocumentPrivate *doc, KTextEditor::EditorPrivate::self()->kateDocuments()) { doc->refreshOnTheFlyCheck(); } } void KateSpellCheckConfigTab::reload() { // does nothing } QString KateSpellCheckConfigTab::name() const { return i18n("Spellcheck"); } //END KateSpellCheckConfigTab //BEGIN KateNavigationConfigTab KateNavigationConfigTab::KateNavigationConfigTab(QWidget *parent) : KateConfigPage(parent) { // This will let us having more separation between this page and // the QTabWidget edge (ereslibre) QVBoxLayout *layout = new QVBoxLayout; QWidget *newWidget = new QWidget(this); ui = new Ui::NavigationConfigWidget(); ui->setupUi(newWidget); // What's This? Help is in the ui-files reload(); // // after initial reload, connect the stuff for the changed () signal // connect(ui->cbTextSelectionMode, SIGNAL(currentIndexChanged(int)), this, SLOT(slotChanged())); connect(ui->chkSmartHome, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->chkPagingMovesCursor, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->sbAutoCenterCursor, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); connect(ui->chkScrollPastEnd, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->chkBackspaceRemoveComposed, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); layout->addWidget(newWidget); setLayout(layout); } KateNavigationConfigTab::~KateNavigationConfigTab() { delete ui; } void KateNavigationConfigTab::apply() { // nothing changed, no need to apply stuff if (!hasChanged()) { return; } m_changed = false; KateViewConfig::global()->configStart(); KateDocumentConfig::global()->configStart(); - KateDocumentConfig::global()->setSmartHome(ui->chkSmartHome->isChecked()); - - KateViewConfig::global()->setAutoCenterLines(qMax(0, ui->sbAutoCenterCursor->value())); KateDocumentConfig::global()->setPageUpDownMovesCursor(ui->chkPagingMovesCursor->isChecked()); + KateDocumentConfig::global()->setSmartHome(ui->chkSmartHome->isChecked()); - KateViewConfig::global()->setPersistentSelection(ui->cbTextSelectionMode->currentIndex() == 1); - - KateViewConfig::global()->setScrollPastEnd(ui->chkScrollPastEnd->isChecked()); - - KateViewConfig::global()->setBackspaceRemoveComposed(ui->chkBackspaceRemoveComposed->isChecked()); + KateViewConfig::global()->setValue(KateViewConfig::AutoCenterLines, ui->sbAutoCenterCursor->value()); + KateViewConfig::global()->setValue(KateViewConfig::BackspaceRemoveComposedCharacters, ui->chkBackspaceRemoveComposed->isChecked()); + KateViewConfig::global()->setValue(KateViewConfig::PersistentSelection, ui->cbTextSelectionMode->currentIndex() == 1); + KateViewConfig::global()->setValue(KateViewConfig::ScrollPastEnd, ui->chkScrollPastEnd->isChecked()); KateDocumentConfig::global()->configEnd(); KateViewConfig::global()->configEnd(); } void KateNavigationConfigTab::reload() { ui->cbTextSelectionMode->setCurrentIndex(KateViewConfig::global()->persistentSelection() ? 1 : 0); ui->chkSmartHome->setChecked(KateDocumentConfig::global()->smartHome()); ui->chkPagingMovesCursor->setChecked(KateDocumentConfig::global()->pageUpDownMovesCursor()); ui->sbAutoCenterCursor->setValue(KateViewConfig::global()->autoCenterLines()); ui->chkScrollPastEnd->setChecked(KateViewConfig::global()->scrollPastEnd()); ui->chkBackspaceRemoveComposed->setChecked(KateViewConfig::global()->backspaceRemoveComposed()); } QString KateNavigationConfigTab::name() const { return i18n("Text Navigation"); } //END KateNavigationConfigTab //BEGIN KateEditGeneralConfigTab KateEditGeneralConfigTab::KateEditGeneralConfigTab(QWidget *parent) : KateConfigPage(parent) { QVBoxLayout *layout = new QVBoxLayout; QWidget *newWidget = new QWidget(this); ui = new Ui::EditConfigWidget(); ui->setupUi(newWidget); QList inputModes = KTextEditor::EditorPrivate::self()->inputModeFactories(); Q_FOREACH(KateAbstractInputModeFactory *fact, inputModes) { ui->cmbInputMode->addItem(fact->name(), static_cast(fact->inputMode())); } reload(); connect(ui->chkStaticWordWrap, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->chkShowStaticWordWrapMarker, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->sbWordWrap, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); connect(ui->chkAutoBrackets, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->chkSmartCopyCut, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->chkMousePasteAtCursorPosition, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->cmbInputMode, SIGNAL(currentIndexChanged(int)), this, SLOT(slotChanged())); // "What's this?" help is in the ui-file layout->addWidget(newWidget); setLayout(layout); } KateEditGeneralConfigTab::~KateEditGeneralConfigTab() { delete ui; } void KateEditGeneralConfigTab::apply() { // nothing changed, no need to apply stuff if (!hasChanged()) { return; } m_changed = false; KateViewConfig::global()->configStart(); KateDocumentConfig::global()->configStart(); KateDocumentConfig::global()->setWordWrapAt(ui->sbWordWrap->value()); KateDocumentConfig::global()->setWordWrap(ui->chkStaticWordWrap->isChecked()); KateRendererConfig::global()->setWordWrapMarker(ui->chkShowStaticWordWrapMarker->isChecked()); - KateViewConfig::global()->setAutoBrackets(ui->chkAutoBrackets->isChecked()); - KateViewConfig::global()->setSmartCopyCut(ui->chkSmartCopyCut->isChecked()); - KateViewConfig::global()->setMousePasteAtCursorPosition(ui->chkMousePasteAtCursorPosition->isChecked()); - - KateViewConfig::global()->setInputModeRaw(ui->cmbInputMode->currentData().toInt()); + KateViewConfig::global()->setValue(KateViewConfig::AutoBrackets, ui->chkAutoBrackets->isChecked()); + KateViewConfig::global()->setValue(KateViewConfig::InputMode, ui->cmbInputMode->currentData().toInt()); + KateViewConfig::global()->setValue(KateViewConfig::MousePasteAtCursorPosition, ui->chkMousePasteAtCursorPosition->isChecked()); + KateViewConfig::global()->setValue(KateViewConfig::SmartCopyCut, ui->chkSmartCopyCut->isChecked()); KateDocumentConfig::global()->configEnd(); KateViewConfig::global()->configEnd(); } void KateEditGeneralConfigTab::reload() { ui->chkStaticWordWrap->setChecked(KateDocumentConfig::global()->wordWrap()); ui->chkShowStaticWordWrapMarker->setChecked(KateRendererConfig::global()->wordWrapMarker()); ui->sbWordWrap->setSuffix(ki18ncp("Wrap words at (value is at 20 or larger)", " character", " characters")); ui->sbWordWrap->setValue(KateDocumentConfig::global()->wordWrapAt()); ui->chkAutoBrackets->setChecked(KateViewConfig::global()->autoBrackets()); ui->chkSmartCopyCut->setChecked(KateViewConfig::global()->smartCopyCut()); ui->chkMousePasteAtCursorPosition->setChecked(KateViewConfig::global()->mousePasteAtCursorPosition()); const int id = static_cast(KateViewConfig::global()->inputMode()); ui->cmbInputMode->setCurrentIndex(ui->cmbInputMode->findData(id)); } QString KateEditGeneralConfigTab::name() const { return i18n("General"); } //END KateEditGeneralConfigTab //BEGIN KateEditConfigTab KateEditConfigTab::KateEditConfigTab(QWidget *parent) : KateConfigPage(parent) , editConfigTab(new KateEditGeneralConfigTab(this)) , navigationConfigTab(new KateNavigationConfigTab(this)) , indentConfigTab(new KateIndentConfigTab(this)) , completionConfigTab(new KateCompletionConfigTab(this)) , spellCheckConfigTab(new KateSpellCheckConfigTab(this)) { QVBoxLayout *layout = new QVBoxLayout; layout->setContentsMargins(0, 0, 0, 0); QTabWidget *tabWidget = new QTabWidget(this); // add all tabs tabWidget->insertTab(0, editConfigTab, editConfigTab->name()); tabWidget->insertTab(1, navigationConfigTab, navigationConfigTab->name()); tabWidget->insertTab(2, indentConfigTab, indentConfigTab->name()); tabWidget->insertTab(3, completionConfigTab, completionConfigTab->name()); tabWidget->insertTab(4, spellCheckConfigTab, spellCheckConfigTab->name()); connect(editConfigTab, SIGNAL(changed()), this, SLOT(slotChanged())); connect(navigationConfigTab, SIGNAL(changed()), this, SLOT(slotChanged())); connect(indentConfigTab, SIGNAL(changed()), this, SLOT(slotChanged())); connect(completionConfigTab, SIGNAL(changed()), this, SLOT(slotChanged())); connect(spellCheckConfigTab, SIGNAL(changed()), this, SLOT(slotChanged())); int i = tabWidget->count(); Q_FOREACH(KateAbstractInputModeFactory *factory, KTextEditor::EditorPrivate::self()->inputModeFactories()) { KateConfigPage *tab = factory->createConfigPage(this); if (tab) { m_inputModeConfigTabs << tab; tabWidget->insertTab(i, tab, tab->name()); connect(tab, SIGNAL(changed()), this, SLOT(slotChanged())); i++; } } layout->addWidget(tabWidget); setLayout(layout); } KateEditConfigTab::~KateEditConfigTab() { qDeleteAll(m_inputModeConfigTabs); } void KateEditConfigTab::apply() { // try to update the rest of tabs editConfigTab->apply(); navigationConfigTab->apply(); indentConfigTab->apply(); completionConfigTab->apply(); spellCheckConfigTab->apply(); Q_FOREACH(KateConfigPage *tab, m_inputModeConfigTabs) { tab->apply(); } } void KateEditConfigTab::reload() { editConfigTab->reload(); navigationConfigTab->reload(); indentConfigTab->reload(); completionConfigTab->reload(); spellCheckConfigTab->reload(); Q_FOREACH(KateConfigPage *tab, m_inputModeConfigTabs) { tab->reload(); } } void KateEditConfigTab::reset() { editConfigTab->reset(); navigationConfigTab->reset(); indentConfigTab->reset(); completionConfigTab->reset(); spellCheckConfigTab->reset(); Q_FOREACH(KateConfigPage *tab, m_inputModeConfigTabs) { tab->reset(); } } void KateEditConfigTab::defaults() { editConfigTab->defaults(); navigationConfigTab->defaults(); indentConfigTab->defaults(); completionConfigTab->defaults(); spellCheckConfigTab->defaults(); Q_FOREACH(KateConfigPage *tab, m_inputModeConfigTabs) { tab->defaults(); } } QString KateEditConfigTab::name() const { return i18n("Editing"); } QString KateEditConfigTab::fullName() const { return i18n("Editing Options"); } QIcon KateEditConfigTab::icon() const { return QIcon::fromTheme(QStringLiteral("accessories-text-editor")); } //END KateEditConfigTab //BEGIN KateViewDefaultsConfig KateViewDefaultsConfig::KateViewDefaultsConfig(QWidget *parent) : KateConfigPage(parent) , textareaUi(new Ui::TextareaAppearanceConfigWidget()) , bordersUi(new Ui::BordersAppearanceConfigWidget()) { QLayout *layout = new QVBoxLayout(this); QTabWidget *tabWidget = new QTabWidget(this); layout->addWidget(tabWidget); layout->setContentsMargins(0, 0, 0, 0); QWidget *textareaTab = new QWidget(tabWidget); textareaUi->setupUi(textareaTab); tabWidget->addTab(textareaTab, i18n("General")); QWidget *bordersTab = new QWidget(tabWidget); bordersUi->setupUi(bordersTab); tabWidget->addTab(bordersTab, i18n("Borders")); textareaUi->cmbDynamicWordWrapIndicator->addItem(i18n("Off")); textareaUi->cmbDynamicWordWrapIndicator->addItem(i18n("Follow Line Numbers")); textareaUi->cmbDynamicWordWrapIndicator->addItem(i18n("Always On")); // What's This? help is in the ui-file reload(); // // after initial reload, connect the stuff for the changed () signal // connect(textareaUi->gbWordWrap, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(textareaUi->chkDynWrapAtStaticMarker, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(textareaUi->cmbDynamicWordWrapIndicator, SIGNAL(activated(int)), this, SLOT(slotChanged())); connect(textareaUi->sbDynamicWordWrapDepth, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); connect(textareaUi->chkShowTabs, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(textareaUi->spacesComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &KateViewDefaultsConfig::slotChanged); connect(textareaUi->chkShowIndentationLines, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(textareaUi->sliSetMarkerSize, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); connect(textareaUi->chkShowWholeBracketExpression, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(textareaUi->chkAnimateBracketMatching, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(textareaUi->chkFoldFirstLine, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(textareaUi->chkShowWordCount, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(textareaUi->chkShowLineCount, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->chkIconBorder, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->chkScrollbarMarks, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->chkScrollbarPreview, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->chkScrollbarMiniMap, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->chkScrollbarMiniMapAll, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); bordersUi->chkScrollbarMiniMapAll->hide(); // this is temporary until the feature is done connect(bordersUi->spBoxMiniMapWidth, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); connect(bordersUi->chkLineNumbers, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->chkShowLineModification, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->chkShowFoldingMarkers, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->chkShowFoldingPreview, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->rbSortBookmarksByPosition, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->rbSortBookmarksByCreation, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->cmbShowScrollbars, SIGNAL(activated(int)), this, SLOT(slotChanged())); } KateViewDefaultsConfig::~KateViewDefaultsConfig() { delete bordersUi; delete textareaUi; } void KateViewDefaultsConfig::apply() { // nothing changed, no need to apply stuff if (!hasChanged()) { return; } m_changed = false; KateViewConfig::global()->configStart(); KateRendererConfig::global()->configStart(); KateViewConfig::global()->setDynWordWrap(textareaUi->gbWordWrap->isChecked()); - KateViewConfig::global()->setDynWrapAtStaticMarker(textareaUi->chkDynWrapAtStaticMarker->isChecked()); - KateViewConfig::global()->setDynWordWrapIndicators(textareaUi->cmbDynamicWordWrapIndicator->currentIndex()); - KateViewConfig::global()->setDynWordWrapAlignIndent(textareaUi->sbDynamicWordWrapDepth->value()); + KateViewConfig::global()->setValue(KateViewConfig::DynWrapAtStaticMarker, textareaUi->chkDynWrapAtStaticMarker->isChecked()); + KateViewConfig::global()->setValue(KateViewConfig::DynWordWrapIndicators, textareaUi->cmbDynamicWordWrapIndicator->currentIndex()); + KateViewConfig::global()->setValue(KateViewConfig::DynWordWrapAlignIndent, textareaUi->sbDynamicWordWrapDepth->value()); KateDocumentConfig::global()->setShowTabs(textareaUi->chkShowTabs->isChecked()); KateDocumentConfig::global()->setShowSpaces(KateDocumentConfig::WhitespaceRendering(textareaUi->spacesComboBox->currentIndex())); KateDocumentConfig::global()->setMarkerSize(textareaUi->sliSetMarkerSize->value()); - KateViewConfig::global()->setLineNumbers(bordersUi->chkLineNumbers->isChecked()); - KateViewConfig::global()->setIconBar(bordersUi->chkIconBorder->isChecked()); - KateViewConfig::global()->setScrollBarMarks(bordersUi->chkScrollbarMarks->isChecked()); - KateViewConfig::global()->setScrollBarPreview(bordersUi->chkScrollbarPreview->isChecked()); - KateViewConfig::global()->setScrollBarMiniMap(bordersUi->chkScrollbarMiniMap->isChecked()); - KateViewConfig::global()->setScrollBarMiniMapAll(bordersUi->chkScrollbarMiniMapAll->isChecked()); - KateViewConfig::global()->setScrollBarMiniMapWidth(bordersUi->spBoxMiniMapWidth->value()); - KateViewConfig::global()->setFoldingBar(bordersUi->chkShowFoldingMarkers->isChecked()); - KateViewConfig::global()->setFoldingPreview(bordersUi->chkShowFoldingPreview->isChecked()); - KateViewConfig::global()->setLineModification(bordersUi->chkShowLineModification->isChecked()); - KateViewConfig::global()->setShowScrollbars(bordersUi->cmbShowScrollbars->currentIndex()); - - KateViewConfig::global()->setBookmarkSort(bordersUi->rbSortBookmarksByPosition->isChecked() ? 0 : 1); + KateViewConfig::global()->setValue(KateViewConfig::ShowLineNumbers, bordersUi->chkLineNumbers->isChecked()); + KateViewConfig::global()->setValue(KateViewConfig::ShowIconBar, bordersUi->chkIconBorder->isChecked()); + KateViewConfig::global()->setValue(KateViewConfig::ShowScrollBarMarks, bordersUi->chkScrollbarMarks->isChecked()); + KateViewConfig::global()->setValue(KateViewConfig::ShowScrollBarPreview, bordersUi->chkScrollbarPreview->isChecked()); + KateViewConfig::global()->setValue(KateViewConfig::ShowScrollBarMiniMap, bordersUi->chkScrollbarMiniMap->isChecked()); + KateViewConfig::global()->setValue(KateViewConfig::ShowScrollBarMiniMapAll, bordersUi->chkScrollbarMiniMapAll->isChecked()); + KateViewConfig::global()->setValue(KateViewConfig::ScrollBarMiniMapWidth, bordersUi->spBoxMiniMapWidth->value()); + KateViewConfig::global()->setValue(KateViewConfig::ShowFoldingBar, bordersUi->chkShowFoldingMarkers->isChecked()); + KateViewConfig::global()->setValue(KateViewConfig::ShowFoldingPreview, bordersUi->chkShowFoldingPreview->isChecked()); + KateViewConfig::global()->setValue(KateViewConfig::ShowLineModification, bordersUi->chkShowLineModification->isChecked()); + KateViewConfig::global()->setValue(KateViewConfig::ShowScrollbars, bordersUi->cmbShowScrollbars->currentIndex()); + KateViewConfig::global()->setValue(KateViewConfig::BookmarkSorting, bordersUi->rbSortBookmarksByPosition->isChecked() ? 0 : 1); KateRendererConfig::global()->setShowIndentationLines(textareaUi->chkShowIndentationLines->isChecked()); KateRendererConfig::global()->setShowWholeBracketExpression(textareaUi->chkShowWholeBracketExpression->isChecked()); KateRendererConfig::global()->setAnimateBracketMatching(textareaUi->chkAnimateBracketMatching->isChecked()); - KateViewConfig::global()->setFoldFirstLine(textareaUi->chkFoldFirstLine->isChecked()); + KateViewConfig::global()->setValue(KateViewConfig::FoldFirstLine, textareaUi->chkFoldFirstLine->isChecked()); KateViewConfig::global()->setShowWordCount(textareaUi->chkShowWordCount->isChecked()); - KateViewConfig::global()->setShowLineCount(textareaUi->chkShowLineCount->isChecked()); + KateViewConfig::global()->setValue(KateViewConfig::ShowLineCount, textareaUi->chkShowLineCount->isChecked()); KateRendererConfig::global()->configEnd(); KateViewConfig::global()->configEnd(); } void KateViewDefaultsConfig::reload() { textareaUi->gbWordWrap->setChecked(KateViewConfig::global()->dynWordWrap()); textareaUi->chkDynWrapAtStaticMarker->setChecked(KateViewConfig::global()->dynWrapAtStaticMarker()); textareaUi->cmbDynamicWordWrapIndicator->setCurrentIndex(KateViewConfig::global()->dynWordWrapIndicators()); textareaUi->sbDynamicWordWrapDepth->setValue(KateViewConfig::global()->dynWordWrapAlignIndent()); textareaUi->chkShowTabs->setChecked(KateDocumentConfig::global()->showTabs()); textareaUi->spacesComboBox->setCurrentIndex(KateDocumentConfig::global()->showSpaces()); textareaUi->sliSetMarkerSize->setValue(KateDocumentConfig::global()->markerSize()); bordersUi->chkLineNumbers->setChecked(KateViewConfig::global()->lineNumbers()); bordersUi->chkIconBorder->setChecked(KateViewConfig::global()->iconBar()); bordersUi->chkScrollbarMarks->setChecked(KateViewConfig::global()->scrollBarMarks()); bordersUi->chkScrollbarPreview->setChecked(KateViewConfig::global()->scrollBarPreview()); bordersUi->chkScrollbarMiniMap->setChecked(KateViewConfig::global()->scrollBarMiniMap()); bordersUi->chkScrollbarMiniMapAll->setChecked(KateViewConfig::global()->scrollBarMiniMapAll()); bordersUi->spBoxMiniMapWidth->setValue(KateViewConfig::global()->scrollBarMiniMapWidth()); bordersUi->chkShowFoldingMarkers->setChecked(KateViewConfig::global()->foldingBar()); bordersUi->chkShowFoldingPreview->setChecked(KateViewConfig::global()->foldingPreview()); bordersUi->chkShowLineModification->setChecked(KateViewConfig::global()->lineModification()); bordersUi->rbSortBookmarksByPosition->setChecked(KateViewConfig::global()->bookmarkSort() == 0); bordersUi->rbSortBookmarksByCreation->setChecked(KateViewConfig::global()->bookmarkSort() == 1); bordersUi->cmbShowScrollbars->setCurrentIndex(KateViewConfig::global()->showScrollbars()); textareaUi->chkShowIndentationLines->setChecked(KateRendererConfig::global()->showIndentationLines()); textareaUi->chkShowWholeBracketExpression->setChecked(KateRendererConfig::global()->showWholeBracketExpression()); textareaUi->chkAnimateBracketMatching->setChecked(KateRendererConfig::global()->animateBracketMatching()); textareaUi->chkFoldFirstLine->setChecked(KateViewConfig::global()->foldFirstLine()); textareaUi->chkShowWordCount->setChecked(KateViewConfig::global()->showWordCount()); textareaUi->chkShowLineCount->setChecked(KateViewConfig::global()->showLineCount()); } void KateViewDefaultsConfig::reset() { ; } void KateViewDefaultsConfig::defaults() { ; } QString KateViewDefaultsConfig::name() const { return i18n("Appearance"); } QString KateViewDefaultsConfig::fullName() const { return i18n("Appearance"); } QIcon KateViewDefaultsConfig::icon() const { return QIcon::fromTheme(QStringLiteral("preferences-desktop-theme")); } //END KateViewDefaultsConfig //BEGIN KateSaveConfigTab KateSaveConfigTab::KateSaveConfigTab(QWidget *parent) : KateConfigPage(parent) , modeConfigPage(new ModeConfigPage(this)) { // FIXME: Is really needed to move all this code below to another class, // since it is another tab itself on the config dialog. This means we should // initialize, add and work with as we do with modeConfigPage (ereslibre) QVBoxLayout *layout = new QVBoxLayout; layout->setContentsMargins(0, 0, 0, 0); QTabWidget *tabWidget = new QTabWidget(this); QWidget *tmpWidget = new QWidget(tabWidget); QVBoxLayout *internalLayout = new QVBoxLayout; QWidget *newWidget = new QWidget(tabWidget); ui = new Ui::OpenSaveConfigWidget(); ui->setupUi(newWidget); QWidget *tmpWidget2 = new QWidget(tabWidget); QVBoxLayout *internalLayout2 = new QVBoxLayout; QWidget *newWidget2 = new QWidget(tabWidget); uiadv = new Ui::OpenSaveConfigAdvWidget(); uiadv->setupUi(newWidget2); // What's this help is added in ui/opensaveconfigwidget.ui reload(); // // after initial reload, connect the stuff for the changed () signal // connect(ui->cmbEncoding, SIGNAL(activated(int)), this, SLOT(slotChanged())); connect(ui->cmbEncodingDetection, SIGNAL(activated(int)), this, SLOT(slotChanged())); connect(ui->cmbEncodingFallback, SIGNAL(activated(int)), this, SLOT(slotChanged())); connect(ui->cmbEOL, SIGNAL(activated(int)), this, SLOT(slotChanged())); connect(ui->chkDetectEOL, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->chkEnableBOM, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->lineLengthLimit, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); connect(ui->cbRemoveTrailingSpaces, SIGNAL(currentIndexChanged(int)), this, SLOT(slotChanged())); connect(ui->chkNewLineAtEof, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(uiadv->chkBackupLocalFiles, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(uiadv->chkBackupRemoteFiles, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(uiadv->edtBackupPrefix, SIGNAL(textChanged(QString)), this, SLOT(slotChanged())); connect(uiadv->edtBackupSuffix, SIGNAL(textChanged(QString)), this, SLOT(slotChanged())); connect(uiadv->cmbSwapFileMode, SIGNAL(currentIndexChanged(int)), this, SLOT(slotChanged())); connect(uiadv->cmbSwapFileMode, SIGNAL(currentIndexChanged(int)), this, SLOT(swapFileModeChanged(int))); connect(uiadv->kurlSwapDirectory, SIGNAL(textChanged(QString)), this, SLOT(slotChanged())); connect(uiadv->spbSwapFileSync, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); internalLayout->addWidget(newWidget); tmpWidget->setLayout(internalLayout); internalLayout2->addWidget(newWidget2); tmpWidget2->setLayout(internalLayout2); // add all tabs tabWidget->insertTab(0, tmpWidget, i18n("General")); tabWidget->insertTab(1, tmpWidget2, i18n("Advanced")); tabWidget->insertTab(2, modeConfigPage, modeConfigPage->name()); connect(modeConfigPage, SIGNAL(changed()), this, SLOT(slotChanged())); layout->addWidget(tabWidget); setLayout(layout); } KateSaveConfigTab::~KateSaveConfigTab() { delete ui; } void KateSaveConfigTab::swapFileModeChanged(int idx) { const KateDocumentConfig::SwapFileMode mode = static_cast(idx); switch (mode) { case KateDocumentConfig::DisableSwapFile: uiadv->lblSwapDirectory->setEnabled(false); uiadv->kurlSwapDirectory->setEnabled(false); uiadv->lblSwapFileSync->setEnabled(false); uiadv->spbSwapFileSync->setEnabled(false); break; case KateDocumentConfig::EnableSwapFile: uiadv->lblSwapDirectory->setEnabled(false); uiadv->kurlSwapDirectory->setEnabled(false); uiadv->lblSwapFileSync->setEnabled(true); uiadv->spbSwapFileSync->setEnabled(true); break; case KateDocumentConfig::SwapFilePresetDirectory: uiadv->lblSwapDirectory->setEnabled(true); uiadv->kurlSwapDirectory->setEnabled(true); uiadv->lblSwapFileSync->setEnabled(true); uiadv->spbSwapFileSync->setEnabled(true); break; } } void KateSaveConfigTab::apply() { modeConfigPage->apply(); // nothing changed, no need to apply stuff if (!hasChanged()) { return; } m_changed = false; KateGlobalConfig::global()->configStart(); KateDocumentConfig::global()->configStart(); if (uiadv->edtBackupSuffix->text().isEmpty() && uiadv->edtBackupPrefix->text().isEmpty()) { KMessageBox::information( this, i18n("You did not provide a backup suffix or prefix. Using default suffix: '~'"), i18n("No Backup Suffix or Prefix") ); uiadv->edtBackupSuffix->setText(QStringLiteral("~")); } KateDocumentConfig::global()->setBackupOnSaveLocal(uiadv->chkBackupLocalFiles->isChecked()); KateDocumentConfig::global()->setBackupOnSaveRemote(uiadv->chkBackupRemoteFiles->isChecked()); KateDocumentConfig::global()->setBackupPrefix(uiadv->edtBackupPrefix->text()); KateDocumentConfig::global()->setBackupSuffix(uiadv->edtBackupSuffix->text()); KateDocumentConfig::global()->setSwapFileMode(uiadv->cmbSwapFileMode->currentIndex()); KateDocumentConfig::global()->setSwapDirectory(uiadv->kurlSwapDirectory->url().toLocalFile()); KateDocumentConfig::global()->setSwapSyncInterval(uiadv->spbSwapFileSync->value()); KateDocumentConfig::global()->setRemoveSpaces(ui->cbRemoveTrailingSpaces->currentIndex()); KateDocumentConfig::global()->setNewLineAtEof(ui->chkNewLineAtEof->isChecked()); // set both standard and fallback encoding KateDocumentConfig::global()->setEncoding(KCharsets::charsets()->encodingForName(ui->cmbEncoding->currentText())); KateGlobalConfig::global()->setProberType((KEncodingProber::ProberType)ui->cmbEncodingDetection->currentIndex()); KateGlobalConfig::global()->setFallbackEncoding(KCharsets::charsets()->encodingForName(ui->cmbEncodingFallback->currentText())); KateDocumentConfig::global()->setEol(ui->cmbEOL->currentIndex()); KateDocumentConfig::global()->setAllowEolDetection(ui->chkDetectEOL->isChecked()); KateDocumentConfig::global()->setBom(ui->chkEnableBOM->isChecked()); KateDocumentConfig::global()->setLineLengthLimit(ui->lineLengthLimit->value()); KateDocumentConfig::global()->configEnd(); KateGlobalConfig::global()->configEnd(); } void KateSaveConfigTab::reload() { modeConfigPage->reload(); // encodings ui->cmbEncoding->clear(); ui->cmbEncodingFallback->clear(); QStringList encodings(KCharsets::charsets()->descriptiveEncodingNames()); int insert = 0; for (int i = 0; i < encodings.count(); i++) { bool found = false; QTextCodec *codecForEnc = KCharsets::charsets()->codecForName(KCharsets::charsets()->encodingForName(encodings[i]), found); if (found) { ui->cmbEncoding->addItem(encodings[i]); ui->cmbEncodingFallback->addItem(encodings[i]); if (codecForEnc == KateDocumentConfig::global()->codec()) { ui->cmbEncoding->setCurrentIndex(insert); } if (codecForEnc == KateGlobalConfig::global()->fallbackCodec()) { // adjust index for fallback config, has no default! ui->cmbEncodingFallback->setCurrentIndex(insert); } insert++; } } // encoding detection ui->cmbEncodingDetection->clear(); bool found = false; for (int i = 0; !KEncodingProber::nameForProberType((KEncodingProber::ProberType) i).isEmpty(); ++i) { ui->cmbEncodingDetection->addItem(KEncodingProber::nameForProberType((KEncodingProber::ProberType) i)); if (i == KateGlobalConfig::global()->proberType()) { ui->cmbEncodingDetection->setCurrentIndex(ui->cmbEncodingDetection->count() - 1); found = true; } } if (!found) { ui->cmbEncodingDetection->setCurrentIndex(KEncodingProber::Universal); } // eol ui->cmbEOL->setCurrentIndex(KateDocumentConfig::global()->eol()); ui->chkDetectEOL->setChecked(KateDocumentConfig::global()->allowEolDetection()); ui->chkEnableBOM->setChecked(KateDocumentConfig::global()->bom()); ui->lineLengthLimit->setValue(KateDocumentConfig::global()->lineLengthLimit()); ui->cbRemoveTrailingSpaces->setCurrentIndex(KateDocumentConfig::global()->removeSpaces()); ui->chkNewLineAtEof->setChecked(KateDocumentConfig::global()->newLineAtEof()); // other stuff uiadv->chkBackupLocalFiles->setChecked(KateDocumentConfig::global()->backupOnSaveLocal()); uiadv->chkBackupRemoteFiles->setChecked(KateDocumentConfig::global()->backupOnSaveRemote()); uiadv->edtBackupPrefix->setText(KateDocumentConfig::global()->backupPrefix()); uiadv->edtBackupSuffix->setText(KateDocumentConfig::global()->backupSuffix()); uiadv->cmbSwapFileMode->setCurrentIndex(KateDocumentConfig::global()->swapFileMode()); uiadv->kurlSwapDirectory->setUrl(QUrl::fromLocalFile(KateDocumentConfig::global()->swapDirectory())); uiadv->spbSwapFileSync->setValue(KateDocumentConfig::global()->swapSyncInterval()); swapFileModeChanged(KateDocumentConfig::global()->swapFileMode()); } void KateSaveConfigTab::reset() { modeConfigPage->reset(); } void KateSaveConfigTab::defaults() { modeConfigPage->defaults(); ui->cbRemoveTrailingSpaces->setCurrentIndex(0); uiadv->chkBackupLocalFiles->setChecked(true); uiadv->chkBackupRemoteFiles->setChecked(false); uiadv->edtBackupPrefix->setText(QString()); uiadv->edtBackupSuffix->setText(QStringLiteral("~")); uiadv->cmbSwapFileMode->setCurrentIndex(1); uiadv->kurlSwapDirectory->setDisabled(true); uiadv->lblSwapDirectory->setDisabled(true); uiadv->spbSwapFileSync->setValue(15); } QString KateSaveConfigTab::name() const { return i18n("Open/Save"); } QString KateSaveConfigTab::fullName() const { return i18n("File Opening & Saving"); } QIcon KateSaveConfigTab::icon() const { return QIcon::fromTheme(QStringLiteral("document-save")); } //END KateSaveConfigTab //BEGIN KateGotoBar KateGotoBar::KateGotoBar(KTextEditor::View *view, QWidget *parent) : KateViewBarWidget(true, parent) , m_view(view) { Q_ASSERT(m_view != nullptr); // this bar widget is pointless w/o a view QHBoxLayout *topLayout = new QHBoxLayout(centralWidget()); topLayout->setContentsMargins(0, 0, 0, 0); QToolButton *btn = new QToolButton(this); btn->setAutoRaise(true); btn->setMinimumSize(QSize(1, btn->minimumSizeHint().height())); btn->setText(i18n("&Line:")); btn->setToolTip(i18n("Go to line number from clipboard")); connect(btn, &QToolButton::clicked, this, &KateGotoBar::gotoClipboard); topLayout->addWidget(btn); m_gotoRange = new QSpinBox(this); m_gotoRange->setMinimum(1); topLayout->addWidget(m_gotoRange, 1); topLayout->setStretchFactor(m_gotoRange, 0); btn = new QToolButton(this); btn->setAutoRaise(true); btn->setMinimumSize(QSize(1, btn->minimumSizeHint().height())); btn->setText(i18n("Go to")); btn->setIcon(QIcon::fromTheme(QStringLiteral("go-jump"))); btn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); connect(btn, &QToolButton::clicked, this, &KateGotoBar::gotoLine); topLayout->addWidget(btn); btn = m_modifiedUp = new QToolButton(this); btn->setAutoRaise(true); btn->setMinimumSize(QSize(1, btn->minimumSizeHint().height())); btn->setDefaultAction(m_view->action("modified_line_up")); btn->setIcon(QIcon::fromTheme(QStringLiteral("go-up-search"))); btn->setText(QString()); btn->installEventFilter(this); topLayout->addWidget(btn); btn = m_modifiedDown = new QToolButton(this); btn->setAutoRaise(true); btn->setMinimumSize(QSize(1, btn->minimumSizeHint().height())); btn->setDefaultAction(m_view->action("modified_line_down")); btn->setIcon(QIcon::fromTheme(QStringLiteral("go-down-search"))); btn->setText(QString()); btn->installEventFilter(this); topLayout->addWidget(btn); topLayout->addStretch(); setFocusProxy(m_gotoRange); } void KateGotoBar::showEvent(QShowEvent *event) { Q_UNUSED(event) // Catch rare cases where the bar is visible while document is edited connect(m_view->document(), &KTextEditor::Document::textChanged, this, &KateGotoBar::updateData); } void KateGotoBar::closed() { disconnect(m_view->document(), &KTextEditor::Document::textChanged, this, &KateGotoBar::updateData); } bool KateGotoBar::eventFilter(QObject *object, QEvent *event) { if (object == m_modifiedUp || object == m_modifiedDown) { if (event->type() != QEvent::Wheel) { return false; } int delta = static_cast(event)->delta(); // Reset m_wheelDelta when scroll direction change if (m_wheelDelta != 0 && (m_wheelDelta < 0) != (delta < 0)) { m_wheelDelta = 0; } m_wheelDelta += delta; if (m_wheelDelta >= 120) { m_wheelDelta = 0; m_modifiedUp->click(); } else if (m_wheelDelta <= -120) { m_wheelDelta = 0; m_modifiedDown->click(); } } return false; } void KateGotoBar::gotoClipboard() { QRegularExpression rx(QStringLiteral("\\d+")); int lineNo = rx.match(QApplication::clipboard()->text(QClipboard::Selection)).captured().toInt(); if (lineNo <= m_gotoRange->maximum() && lineNo >= 1) { m_gotoRange->setValue(lineNo); gotoLine(); } else { QPointer message = new KTextEditor::Message( i18n("No valid line number found in clipboard")); message->setWordWrap(true); message->setAutoHide(2000); message->setPosition(KTextEditor::Message::BottomInView); message->setView(m_view), m_view->document()->postMessage(message); } } void KateGotoBar::updateData() { m_gotoRange->setMaximum(m_view->document()->lines()); if (!isVisible()) { m_gotoRange->setValue(m_view->cursorPosition().line() + 1); m_gotoRange->adjustSize(); // ### does not respect the range :-( } m_gotoRange->selectAll(); } void KateGotoBar::keyPressEvent(QKeyEvent *event) { int key = event->key(); if (key == Qt::Key_Return || key == Qt::Key_Enter) { gotoLine(); return; } KateViewBarWidget::keyPressEvent(event); } void KateGotoBar::gotoLine() { KTextEditor::ViewPrivate *kv = qobject_cast(m_view); if (kv && kv->selection() && !kv->config()->persistentSelection()) { kv->clearSelection(); } m_view->setCursorPosition(KTextEditor::Cursor(m_gotoRange->value() - 1, 0)); m_view->setFocus(); emit hideMe(); } //END KateGotoBar //BEGIN KateDictionaryBar KateDictionaryBar::KateDictionaryBar(KTextEditor::ViewPrivate *view, QWidget *parent) : KateViewBarWidget(true, parent) , m_view(view) { Q_ASSERT(m_view != nullptr); // this bar widget is pointless w/o a view QHBoxLayout *topLayout = new QHBoxLayout(centralWidget()); topLayout->setContentsMargins(0, 0, 0, 0); //topLayout->setSpacing(spacingHint()); m_dictionaryComboBox = new Sonnet::DictionaryComboBox(centralWidget()); connect(m_dictionaryComboBox, SIGNAL(dictionaryChanged(QString)), this, SLOT(dictionaryChanged(QString))); connect(view->doc(), SIGNAL(defaultDictionaryChanged(KTextEditor::DocumentPrivate*)), this, SLOT(updateData())); QLabel *label = new QLabel(i18n("Dictionary:"), centralWidget()); label->setBuddy(m_dictionaryComboBox); topLayout->addWidget(label); topLayout->addWidget(m_dictionaryComboBox, 1); topLayout->setStretchFactor(m_dictionaryComboBox, 0); topLayout->addStretch(); } KateDictionaryBar::~KateDictionaryBar() { } void KateDictionaryBar::updateData() { KTextEditor::DocumentPrivate *document = m_view->doc(); QString dictionary = document->defaultDictionary(); if (dictionary.isEmpty()) { dictionary = Sonnet::Speller().defaultLanguage(); } m_dictionaryComboBox->setCurrentByDictionary(dictionary); } void KateDictionaryBar::dictionaryChanged(const QString &dictionary) { const KTextEditor::Range selection = m_view->selectionRange(); if (selection.isValid() && !selection.isEmpty()) { const bool blockmode = m_view->blockSelection(); m_view->doc()->setDictionary(dictionary, selection, blockmode); } else { m_view->doc()->setDefaultDictionary(dictionary); } } //END KateGotoBar //BEGIN KateModOnHdPrompt KateModOnHdPrompt::KateModOnHdPrompt(KTextEditor::DocumentPrivate *doc, KTextEditor::ModificationInterface::ModifiedOnDiskReason modtype, const QString &reason) : QObject(doc) , m_doc(doc) , m_modtype(modtype) , m_proc(nullptr) , m_diffFile(nullptr) , m_diffAction(nullptr) { m_message = new KTextEditor::Message(reason, KTextEditor::Message::Information); m_message->setPosition(KTextEditor::Message::AboveView); m_message->setWordWrap(true); // If the file isn't deleted, present a diff button const bool onDiskDeleted = modtype == KTextEditor::ModificationInterface::OnDiskDeleted; if (!onDiskDeleted) { QAction * aAutoReload = new QAction(i18n("Enable Auto Reload"), this); aAutoReload->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); aAutoReload->setToolTip(i18n("Will never again warn about on disk changes but always reload.")); m_message->addAction(aAutoReload, false); connect(aAutoReload, &QAction::triggered, this, &KateModOnHdPrompt::autoReloadTriggered); if (!QStandardPaths::findExecutable(QStringLiteral("diff")).isEmpty()) { m_diffAction = new QAction(i18n("View &Difference"), this); m_diffAction->setToolTip(i18n("Shows a diff of the changes")); m_message->addAction(m_diffAction, false); connect(m_diffAction, SIGNAL(triggered()), this, SLOT(slotDiff())); } QAction * aReload = new QAction(i18n("&Reload"), this); aReload->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); aReload->setToolTip(i18n("Reload the file from disk. Unsaved changes will be lost.")); m_message->addAction(aReload); connect(aReload, SIGNAL(triggered()), this, SIGNAL(reloadTriggered())); } else { QAction * closeFile = new QAction(i18nc("@action:button closes the opened file", "&Close File"), this); closeFile->setIcon(QIcon::fromTheme(QStringLiteral("document-close"))); closeFile->setToolTip(i18n("Close the file, discarding its content.")); m_message->addAction(closeFile, false); connect(closeFile, &QAction::triggered, this, &KateModOnHdPrompt::closeTriggered); QAction * aSaveAs = new QAction(i18n("&Save As..."), this); aSaveAs->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as"))); aSaveAs->setToolTip(i18n("Lets you select a location and save the file again.")); m_message->addAction(aSaveAs, false); connect(aSaveAs, SIGNAL(triggered()), this, SIGNAL(saveAsTriggered())); } QAction * aIgnore = new QAction(i18n("&Ignore"), this); aIgnore->setToolTip(i18n("Ignores the changes on disk without any action.")); aIgnore->setIcon(KStandardGuiItem::overwrite().icon()); m_message->addAction(aIgnore); connect(aIgnore, SIGNAL(triggered()), this, SIGNAL(ignoreTriggered())); m_doc->postMessage(m_message); } KateModOnHdPrompt::~KateModOnHdPrompt() { delete m_proc; m_proc = nullptr; if (m_diffFile) { m_diffFile->setAutoRemove(true); delete m_diffFile; m_diffFile = nullptr; } delete m_message; } void KateModOnHdPrompt::slotDiff() { if (m_diffFile) { return; } m_diffFile = new QTemporaryFile(); m_diffFile->open(); // Start a KProcess that creates a diff m_proc = new KProcess(this); m_proc->setOutputChannelMode(KProcess::MergedChannels); *m_proc << QStringLiteral("diff") << QLatin1String("-u") << QStringLiteral("-") << m_doc->url().toLocalFile(); connect(m_proc, SIGNAL(readyRead()), this, SLOT(slotDataAvailable())); connect(m_proc, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotPDone())); // disable the diff button, to hinder the user to run it twice. m_diffAction->setEnabled(false); m_proc->start(); QTextStream ts(m_proc); int lastln = m_doc->lines() - 1; for (int l = 0; l < lastln; ++l) { ts << m_doc->line(l) << '\n'; } ts << m_doc->line(lastln); ts.flush(); m_proc->closeWriteChannel(); } void KateModOnHdPrompt::slotDataAvailable() { m_diffFile->write(m_proc->readAll()); } void KateModOnHdPrompt::slotPDone() { m_diffAction->setEnabled(true); const QProcess::ExitStatus es = m_proc->exitStatus(); delete m_proc; m_proc = nullptr; if (es != QProcess::NormalExit) { KMessageBox::sorry(nullptr, i18n("The diff command failed. Please make sure that " "diff(1) is installed and in your PATH."), i18n("Error Creating Diff")); delete m_diffFile; m_diffFile = nullptr; return; } if (m_diffFile->size() == 0) { KMessageBox::information(nullptr, i18n("The files are identical."), i18n("Diff Output")); delete m_diffFile; m_diffFile = nullptr; return; } m_diffFile->setAutoRemove(false); QUrl url = QUrl::fromLocalFile(m_diffFile->fileName()); delete m_diffFile; m_diffFile = nullptr; // KRun::runUrl should delete the file, once the client exits KRun::runUrl(url, QStringLiteral("text/x-patch"), nullptr, KRun::RunFlags(KRun::DeleteTemporaryFiles)); } //END KateModOnHdPrompt diff --git a/src/document/katedocument.cpp b/src/document/katedocument.cpp index e8db0a9d..ad3dfa0a 100644 --- a/src/document/katedocument.cpp +++ b/src/document/katedocument.cpp @@ -1,6118 +1,6109 @@ /* This file is part of the KDE libraries Copyright (C) 2001-2004 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 1999 Jochen Wilhelmy Copyright (C) 2006 Hamish Rodda Copyright (C) 2007 Mirko Stocker Copyright (C) 2009-2010 Michel Ludwig Copyright (C) 2013 Gerald Senarclens de Grancy Copyright (C) 2013 Andrey Matveyakin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-13020, USA. */ //BEGIN includes #include "config.h" #include "katedocument.h" #include "kateglobal.h" #include "katedialogs.h" #include "katehighlight.h" #include "kateview.h" #include "kateautoindent.h" #include "katetextline.h" #include "katerenderer.h" #include "kateregexp.h" #include "kateplaintextsearch.h" #include "kateregexpsearch.h" #include "kateconfig.h" #include "katemodemanager.h" #include "kateschema.h" #include "katebuffer.h" #include "kateundomanager.h" #include "spellcheck/prefixstore.h" #include "spellcheck/ontheflycheck.h" #include "spellcheck/spellcheck.h" #include "katescriptmanager.h" #include "kateswapfile.h" #include "katepartdebug.h" #include "printing/kateprinter.h" #include "kateabstractinputmode.h" #include "katetemplatehandler.h" #if EDITORCONFIG_FOUND #include "editorconfig.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if LIBGIT2_FOUND #include #include #include #endif //END includes #if 0 #define EDIT_DEBUG qCDebug(LOG_KTE) #else #define EDIT_DEBUG if (0) qCDebug(LOG_KTE) #endif template static int indexOf(const std::initializer_list & list, const E& entry) { auto it = std::find(list.begin(), list.end(), entry); return it == list.end() ? -1 : std::distance(list.begin(), it); } template static bool contains(const std::initializer_list & list, const E& entry) { return indexOf(list, entry) >= 0; } static inline QChar matchingStartBracket(const QChar c) { switch (c.toLatin1()) { case '}': return QLatin1Char('{'); case ']': return QLatin1Char('['); case ')': return QLatin1Char('('); } return QChar(); } static inline QChar matchingEndBracket(const QChar c, bool withQuotes = true) { switch (c.toLatin1()) { case '{': return QLatin1Char('}'); case '[': return QLatin1Char(']'); case '(': return QLatin1Char(')'); case '\'': return withQuotes ? QLatin1Char('\'') : QChar(); case '"': return withQuotes ? QLatin1Char('"') : QChar(); } return QChar(); } static inline QChar matchingBracket(const QChar c) { QChar bracket = matchingStartBracket(c); if (bracket.isNull()) { bracket = matchingEndBracket(c, /*withQuotes=*/false); } return bracket; } static inline bool isStartBracket(const QChar c) { return ! matchingEndBracket(c, /*withQuotes=*/false).isNull(); } static inline bool isEndBracket(const QChar c) { return ! matchingStartBracket(c).isNull(); } static inline bool isBracket(const QChar c) { return isStartBracket(c) || isEndBracket(c); } /** * normalize given url * @param url input url * @return normalized url */ static QUrl normalizeUrl (const QUrl &url) { /** * only normalize local urls */ if (url.isEmpty() || !url.isLocalFile()) return url; /** * don't normalize if not existing! * canonicalFilePath won't work! */ const QString normalizedUrl(QFileInfo(url.toLocalFile()).canonicalFilePath()); if (normalizedUrl.isEmpty()) return url; /** * else: use canonicalFilePath to normalize */ return QUrl::fromLocalFile(normalizedUrl); } //BEGIN d'tor, c'tor // // KTextEditor::DocumentPrivate Constructor // KTextEditor::DocumentPrivate::DocumentPrivate(bool bSingleViewMode, bool bReadOnly, QWidget *parentWidget, QObject *parent) : KTextEditor::Document (this, parent), m_bSingleViewMode(bSingleViewMode), m_bReadOnly(bReadOnly), m_undoManager(new KateUndoManager(this)), m_buffer(new KateBuffer(this)), m_indenter(new KateAutoIndent(this)), m_docName(QStringLiteral("need init")), m_fileType(QStringLiteral("Normal")), m_config(new KateDocumentConfig(this)) { /** * no plugins from kparts here */ setPluginLoadingMode (DoNotLoadPlugins); /** * pass on our component data, do this after plugin loading is off */ setComponentData(KTextEditor::EditorPrivate::self()->aboutData()); /** * avoid spamming plasma and other window managers with progress dialogs * we show such stuff inline in the views! */ setProgressInfoEnabled(false); // register doc at factory KTextEditor::EditorPrivate::self()->registerDocument(this); // normal hl m_buffer->setHighlight(0); // swap file m_swapfile = (config()->swapFileMode() == KateDocumentConfig::DisableSwapFile) ? nullptr : new Kate::SwapFile(this); // important, fill in the config into the indenter we use... m_indenter->updateConfig(); // some nice signals from the buffer connect(m_buffer, SIGNAL(tagLines(int,int)), this, SLOT(tagLines(int,int))); // if the user changes the highlight with the dialog, notify the doc connect(KateHlManager::self(), SIGNAL(changed()), SLOT(internalHlChanged())); // signals for mod on hd connect(KTextEditor::EditorPrivate::self()->dirWatch(), SIGNAL(dirty(QString)), this, SLOT(slotModOnHdDirty(QString))); connect(KTextEditor::EditorPrivate::self()->dirWatch(), SIGNAL(created(QString)), this, SLOT(slotModOnHdCreated(QString))); connect(KTextEditor::EditorPrivate::self()->dirWatch(), SIGNAL(deleted(QString)), this, SLOT(slotModOnHdDeleted(QString))); /** * singleshot timer to handle updates of mod on hd state delayed */ m_modOnHdTimer.setSingleShot(true); m_modOnHdTimer.setInterval(200); connect(&m_modOnHdTimer, SIGNAL(timeout()), this, SLOT(slotDelayedHandleModOnHd())); // Setup auto reload stuff m_autoReloadMode = new KToggleAction(i18n("Auto Reload Document"), this); m_autoReloadMode->setWhatsThis(i18n("Automatic reload the document when it was changed on disk")); connect(m_autoReloadMode, &KToggleAction::triggered, this, &DocumentPrivate::autoReloadToggled); // Prepare some reload amok protector... m_autoReloadThrottle.setSingleShot(true); //...but keep the value small in unit tests m_autoReloadThrottle.setInterval(KTextEditor::EditorPrivate::self()->unitTestMode() ? 50 : 3000); connect(&m_autoReloadThrottle, &QTimer::timeout, this, &DocumentPrivate::onModOnHdAutoReload); /** * load handling * this is needed to ensure we signal the user if a file ist still loading * and to disallow him to edit in that time */ connect(this, SIGNAL(started(KIO::Job*)), this, SLOT(slotStarted(KIO::Job*))); connect(this, SIGNAL(completed()), this, SLOT(slotCompleted())); connect(this, SIGNAL(canceled(QString)), this, SLOT(slotCanceled())); connect(this, SIGNAL(urlChanged(QUrl)), this, SLOT(slotUrlChanged(QUrl))); // update doc name updateDocName(); // if single view mode, like in the konqui embedding, create a default view ;) // be lazy, only create it now, if any parentWidget is given, otherwise widget() // will create it on demand... if (m_bSingleViewMode && parentWidget) { KTextEditor::View *view = (KTextEditor::View *)createView(parentWidget); insertChildClient(view); view->setContextMenu(view->defaultContextMenu()); setWidget(view); } connect(m_undoManager, SIGNAL(undoChanged()), this, SIGNAL(undoChanged())); connect(m_undoManager, SIGNAL(undoStart(KTextEditor::Document*)), this, SIGNAL(editingStarted(KTextEditor::Document*))); connect(m_undoManager, SIGNAL(undoEnd(KTextEditor::Document*)), this, SIGNAL(editingFinished(KTextEditor::Document*))); connect(m_undoManager, SIGNAL(redoStart(KTextEditor::Document*)), this, SIGNAL(editingStarted(KTextEditor::Document*))); connect(m_undoManager, SIGNAL(redoEnd(KTextEditor::Document*)), this, SIGNAL(editingFinished(KTextEditor::Document*))); connect(this, SIGNAL(sigQueryClose(bool*,bool*)), this, SLOT(slotQueryClose_save(bool*,bool*))); connect(this, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(clearEditingPosStack())); onTheFlySpellCheckingEnabled(config()->onTheFlySpellCheck()); } // // KTextEditor::DocumentPrivate Destructor // KTextEditor::DocumentPrivate::~DocumentPrivate() { // delete pending mod-on-hd message, if applicable delete m_modOnHdHandler; /** * we are about to delete cursors/ranges/... */ emit aboutToDeleteMovingInterfaceContent(this); // kill it early, it has ranges! delete m_onTheFlyChecker; m_onTheFlyChecker = nullptr; clearDictionaryRanges(); // Tell the world that we're about to close (== destruct) // Apps must receive this in a direct signal-slot connection, and prevent // any further use of interfaces once they return. emit aboutToClose(this); // remove file from dirwatch deactivateDirWatch(); // thanks for offering, KPart, but we're already self-destructing setAutoDeleteWidget(false); setAutoDeletePart(false); // clean up remaining views qDeleteAll (m_views.keys()); m_views.clear(); // cu marks for (QHash::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i) { delete i.value(); } m_marks.clear(); delete m_config; KTextEditor::EditorPrivate::self()->deregisterDocument(this); } //END void KTextEditor::DocumentPrivate::saveEditingPositions(const KTextEditor::Cursor &cursor) { if (m_editingStackPosition != m_editingStack.size() - 1) { m_editingStack.resize(m_editingStackPosition); } // try to be clever: reuse existing cursors if possible QSharedPointer mc; // we might pop last one: reuse that if (!m_editingStack.isEmpty() && cursor.line() == m_editingStack.top()->line()) { mc = m_editingStack.pop(); } // we might expire oldest one, reuse that one, if not already one there // we prefer the other one for reuse, as already on the right line aka in the right block! const int editingStackSizeLimit = 32; if (m_editingStack.size() >= editingStackSizeLimit) { if (mc) { m_editingStack.removeFirst(); } else { mc = m_editingStack.takeFirst(); } } // new cursor needed? or adjust existing one? if (mc) { mc->setPosition(cursor); } else { mc = QSharedPointer (newMovingCursor(cursor)); } // add new one as top of stack m_editingStack.push(mc); m_editingStackPosition = m_editingStack.size() - 1; } KTextEditor::Cursor KTextEditor::DocumentPrivate::lastEditingPosition(EditingPositionKind nextOrPrev, KTextEditor::Cursor currentCursor) { if (m_editingStack.isEmpty()) { return KTextEditor::Cursor::invalid(); } auto targetPos = m_editingStack.at(m_editingStackPosition)->toCursor(); if (targetPos == currentCursor) { if (nextOrPrev == Previous) { m_editingStackPosition--; } else { m_editingStackPosition++; } m_editingStackPosition = qBound(0, m_editingStackPosition, m_editingStack.size() - 1); } return m_editingStack.at(m_editingStackPosition)->toCursor(); } void KTextEditor::DocumentPrivate::clearEditingPosStack() { m_editingStack.clear(); m_editingStackPosition = -1; } // on-demand view creation QWidget *KTextEditor::DocumentPrivate::widget() { // no singleViewMode -> no widget()... if (!singleViewMode()) { return nullptr; } // does a widget exist already? use it! if (KTextEditor::Document::widget()) { return KTextEditor::Document::widget(); } // create and return one... KTextEditor::View *view = (KTextEditor::View *)createView(nullptr); insertChildClient(view); view->setContextMenu(view->defaultContextMenu()); setWidget(view); return view; } //BEGIN KTextEditor::Document stuff KTextEditor::View *KTextEditor::DocumentPrivate::createView(QWidget *parent, KTextEditor::MainWindow *mainWindow) { KTextEditor::ViewPrivate *newView = new KTextEditor::ViewPrivate(this, parent, mainWindow); if (m_fileChangedDialogsActivated) { connect(newView, SIGNAL(focusIn(KTextEditor::View*)), this, SLOT(slotModifiedOnDisk())); } emit viewCreated(this, newView); // post existing messages to the new view, if no specific view is given foreach (KTextEditor::Message *message, m_messageHash.keys()) { if (!message->view()) { newView->postMessage(message, m_messageHash[message]); } } return newView; } KTextEditor::Range KTextEditor::DocumentPrivate::rangeOnLine(KTextEditor::Range range, int line) const { const int col1 = toVirtualColumn(range.start()); const int col2 = toVirtualColumn(range.end()); return KTextEditor::Range(line, fromVirtualColumn(line, col1), line, fromVirtualColumn(line, col2)); } //BEGIN KTextEditor::EditInterface stuff bool KTextEditor::DocumentPrivate::isEditingTransactionRunning() const { return editSessionNumber > 0; } QString KTextEditor::DocumentPrivate::text() const { return m_buffer->text(); } QString KTextEditor::DocumentPrivate::text(const KTextEditor::Range &range, bool blockwise) const { if (!range.isValid()) { qCWarning(LOG_KTE) << "Text requested for invalid range" << range; return QString(); } QString s; if (range.start().line() == range.end().line()) { if (range.start().column() > range.end().column()) { return QString(); } Kate::TextLine textLine = m_buffer->plainLine(range.start().line()); if (!textLine) { return QString(); } return textLine->string(range.start().column(), range.end().column() - range.start().column()); } else { for (int i = range.start().line(); (i <= range.end().line()) && (i < m_buffer->count()); ++i) { Kate::TextLine textLine = m_buffer->plainLine(i); if (!blockwise) { if (i == range.start().line()) { s.append(textLine->string(range.start().column(), textLine->length() - range.start().column())); } else if (i == range.end().line()) { s.append(textLine->string(0, range.end().column())); } else { s.append(textLine->string()); } } else { KTextEditor::Range subRange = rangeOnLine(range, i); s.append(textLine->string(subRange.start().column(), subRange.columnWidth())); } if (i < range.end().line()) { s.append(QLatin1Char('\n')); } } } return s; } QChar KTextEditor::DocumentPrivate::characterAt(const KTextEditor::Cursor &position) const { Kate::TextLine textLine = m_buffer->plainLine(position.line()); if (!textLine) { return QChar(); } return textLine->at(position.column()); } QString KTextEditor::DocumentPrivate::wordAt(const KTextEditor::Cursor &cursor) const { return text(wordRangeAt(cursor)); } KTextEditor::Range KTextEditor::DocumentPrivate::wordRangeAt(const KTextEditor::Cursor &cursor) const { // get text line const int line = cursor.line(); Kate::TextLine textLine = m_buffer->plainLine(line); if (!textLine) { return KTextEditor::Range::invalid(); } // make sure the cursor is const int lineLenth = textLine->length(); if (cursor.column() > lineLenth) { return KTextEditor::Range::invalid(); } int start = cursor.column(); int end = start; while (start > 0 && highlight()->isInWord(textLine->at(start - 1), textLine->attribute(start - 1))) { start--; } while (end < lineLenth && highlight()->isInWord(textLine->at(end), textLine->attribute(end))) { end++; } return KTextEditor::Range(line, start, line, end); } bool KTextEditor::DocumentPrivate::isValidTextPosition(const KTextEditor::Cursor& cursor) const { const int ln = cursor.line(); const int col = cursor.column(); // cursor in document range? if (ln < 0 || col < 0 || ln >= lines() || col > lineLength(ln)) { return false; } const QString str = line(ln); Q_ASSERT(str.length() >= col); // cursor at end of line? const int len = lineLength(ln); if (col == 0 || col == len) { return true; } // cursor in the middle of a valid utf32-surrogate? return (! str.at(col).isLowSurrogate()) || (! str.at(col-1).isHighSurrogate()); } QStringList KTextEditor::DocumentPrivate::textLines(const KTextEditor::Range &range, bool blockwise) const { QStringList ret; if (!range.isValid()) { qCWarning(LOG_KTE) << "Text requested for invalid range" << range; return ret; } if (blockwise && (range.start().column() > range.end().column())) { return ret; } if (range.start().line() == range.end().line()) { Q_ASSERT(range.start() <= range.end()); Kate::TextLine textLine = m_buffer->plainLine(range.start().line()); if (!textLine) { return ret; } ret << textLine->string(range.start().column(), range.end().column() - range.start().column()); } else { for (int i = range.start().line(); (i <= range.end().line()) && (i < m_buffer->count()); ++i) { Kate::TextLine textLine = m_buffer->plainLine(i); if (!blockwise) { if (i == range.start().line()) { ret << textLine->string(range.start().column(), textLine->length() - range.start().column()); } else if (i == range.end().line()) { ret << textLine->string(0, range.end().column()); } else { ret << textLine->string(); } } else { KTextEditor::Range subRange = rangeOnLine(range, i); ret << textLine->string(subRange.start().column(), subRange.columnWidth()); } } } return ret; } QString KTextEditor::DocumentPrivate::line(int line) const { Kate::TextLine l = m_buffer->plainLine(line); if (!l) { return QString(); } return l->string(); } bool KTextEditor::DocumentPrivate::setText(const QString &s) { if (!isReadWrite()) { return false; } QList msave; foreach (KTextEditor::Mark *mark, m_marks) { msave.append(*mark); } editStart(); // delete the text clear(); // insert the new text insertText(KTextEditor::Cursor(), s); editEnd(); foreach (KTextEditor::Mark mark, msave) { setMark(mark.line, mark.type); } return true; } bool KTextEditor::DocumentPrivate::setText(const QStringList &text) { if (!isReadWrite()) { return false; } QList msave; foreach (KTextEditor::Mark *mark, m_marks) { msave.append(*mark); } editStart(); // delete the text clear(); // insert the new text insertText(KTextEditor::Cursor::start(), text); editEnd(); foreach (KTextEditor::Mark mark, msave) { setMark(mark.line, mark.type); } return true; } bool KTextEditor::DocumentPrivate::clear() { if (!isReadWrite()) { return false; } foreach (KTextEditor::ViewPrivate *view, m_views) { view->clear(); view->tagAll(); view->update(); } clearMarks(); emit aboutToInvalidateMovingInterfaceContent(this); m_buffer->invalidateRanges(); emit aboutToRemoveText(documentRange()); return editRemoveLines(0, lastLine()); } bool KTextEditor::DocumentPrivate::insertText(const KTextEditor::Cursor &position, const QString &text, bool block) { if (!isReadWrite()) { return false; } if (text.isEmpty()) { return true; } editStart(); int currentLine = position.line(); int currentLineStart = 0; const int totalLength = text.length(); int insertColumn = position.column(); // pad with empty lines, if insert position is after last line if (position.line() > lines()) { int line = lines(); while (line <= position.line()) { editInsertLine(line, QString()); line++; } } // compute expanded column for block mode int positionColumnExpanded = insertColumn; const int tabWidth = config()->tabWidth(); if (block) { if (auto l = plainKateTextLine(currentLine)) { positionColumnExpanded = l->toVirtualColumn(insertColumn, tabWidth); } } int pos = 0; for (; pos < totalLength; pos++) { const QChar &ch = text.at(pos); if (ch == QLatin1Char('\n')) { // Only perform the text insert if there is text to insert if (currentLineStart < pos) { editInsertText(currentLine, insertColumn, text.mid(currentLineStart, pos - currentLineStart)); } if (!block) { editWrapLine(currentLine, insertColumn + pos - currentLineStart); insertColumn = 0; } currentLine++; if (block) { auto l = plainKateTextLine(currentLine); if (currentLine == lastLine() + 1) { editInsertLine(currentLine, QString()); } insertColumn = positionColumnExpanded; if (l) { insertColumn = l->fromVirtualColumn(insertColumn, tabWidth); } } currentLineStart = pos + 1; } } // Only perform the text insert if there is text to insert if (currentLineStart < pos) { editInsertText(currentLine, insertColumn, text.mid(currentLineStart, pos - currentLineStart)); } editEnd(); return true; } bool KTextEditor::DocumentPrivate::insertText(const KTextEditor::Cursor &position, const QStringList &textLines, bool block) { if (!isReadWrite()) { return false; } // just reuse normal function return insertText(position, textLines.join(QStringLiteral("\n")), block); } bool KTextEditor::DocumentPrivate::removeText(const KTextEditor::Range &_range, bool block) { KTextEditor::Range range = _range; if (!isReadWrite()) { return false; } // Should now be impossible to trigger with the new Range class Q_ASSERT(range.start().line() <= range.end().line()); if (range.start().line() > lastLine()) { return false; } if (!block) { emit aboutToRemoveText(range); } editStart(); if (!block) { if (range.end().line() > lastLine()) { range.setEnd(KTextEditor::Cursor(lastLine() + 1, 0)); } if (range.onSingleLine()) { editRemoveText(range.start().line(), range.start().column(), range.columnWidth()); } else { int from = range.start().line(); int to = range.end().line(); // remove last line if (to <= lastLine()) { editRemoveText(to, 0, range.end().column()); } // editRemoveLines() will be called on first line (to remove bookmark) if (range.start().column() == 0 && from > 0) { --from; } // remove middle lines editRemoveLines(from + 1, to - 1); // remove first line if not already removed by editRemoveLines() if (range.start().column() > 0 || range.start().line() == 0) { editRemoveText(from, range.start().column(), m_buffer->plainLine(from)->length() - range.start().column()); editUnWrapLine(from); } } } // if ( ! block ) else { int startLine = qMax(0, range.start().line()); int vc1 = toVirtualColumn(range.start()); int vc2 = toVirtualColumn(range.end()); for (int line = qMin(range.end().line(), lastLine()); line >= startLine; --line) { int col1 = fromVirtualColumn(line, vc1); int col2 = fromVirtualColumn(line, vc2); editRemoveText(line, qMin(col1, col2), qAbs(col2 - col1)); } } editEnd(); return true; } bool KTextEditor::DocumentPrivate::insertLine(int l, const QString &str) { if (!isReadWrite()) { return false; } if (l < 0 || l > lines()) { return false; } return editInsertLine(l, str); } bool KTextEditor::DocumentPrivate::insertLines(int line, const QStringList &text) { if (!isReadWrite()) { return false; } if (line < 0 || line > lines()) { return false; } bool success = true; foreach (const QString &string, text) { success &= editInsertLine(line++, string); } return success; } bool KTextEditor::DocumentPrivate::removeLine(int line) { if (!isReadWrite()) { return false; } if (line < 0 || line > lastLine()) { return false; } return editRemoveLine(line); } int KTextEditor::DocumentPrivate::totalCharacters() const { int l = 0; for (int i = 0; i < m_buffer->count(); ++i) { Kate::TextLine line = m_buffer->plainLine(i); if (line) { l += line->length(); } } return l; } int KTextEditor::DocumentPrivate::lines() const { return m_buffer->count(); } int KTextEditor::DocumentPrivate::lineLength(int line) const { if (line < 0 || line > lastLine()) { return -1; } Kate::TextLine l = m_buffer->plainLine(line); if (!l) { return -1; } return l->length(); } bool KTextEditor::DocumentPrivate::isLineModified(int line) const { if (line < 0 || line >= lines()) { return false; } Kate::TextLine l = m_buffer->plainLine(line); Q_ASSERT(l); return l->markedAsModified(); } bool KTextEditor::DocumentPrivate::isLineSaved(int line) const { if (line < 0 || line >= lines()) { return false; } Kate::TextLine l = m_buffer->plainLine(line); Q_ASSERT(l); return l->markedAsSavedOnDisk(); } bool KTextEditor::DocumentPrivate::isLineTouched(int line) const { if (line < 0 || line >= lines()) { return false; } Kate::TextLine l = m_buffer->plainLine(line); Q_ASSERT(l); return l->markedAsModified() || l->markedAsSavedOnDisk(); } //END //BEGIN KTextEditor::EditInterface internal stuff // // Starts an edit session with (or without) undo, update of view disabled during session // bool KTextEditor::DocumentPrivate::editStart() { editSessionNumber++; if (editSessionNumber > 1) { return false; } editIsRunning = true; // no last change cursor at start m_editLastChangeStartCursor = KTextEditor::Cursor::invalid(); m_undoManager->editStart(); foreach (KTextEditor::ViewPrivate *view, m_views) { view->editStart(); } m_buffer->editStart(); return true; } // // End edit session and update Views // bool KTextEditor::DocumentPrivate::editEnd() { if (editSessionNumber == 0) { Q_ASSERT(0); return false; } // wrap the new/changed text, if something really changed! if (m_buffer->editChanged() && (editSessionNumber == 1)) if (m_undoManager->isActive() && config()->wordWrap()) { wrapText(m_buffer->editTagStart(), m_buffer->editTagEnd()); } editSessionNumber--; if (editSessionNumber > 0) { return false; } // end buffer edit, will trigger hl update // this will cause some possible adjustment of tagline start/end m_buffer->editEnd(); m_undoManager->editEnd(); // edit end for all views !!!!!!!!! foreach (KTextEditor::ViewPrivate *view, m_views) { view->editEnd(m_buffer->editTagStart(), m_buffer->editTagEnd(), m_buffer->editTagFrom()); } if (m_buffer->editChanged()) { setModified(true); emit textChanged(this); } // remember last change position in the stack, if any // this avoid costly updates for longer editing transactions // before we did that on textInsert/Removed if (m_editLastChangeStartCursor.isValid()) saveEditingPositions(m_editLastChangeStartCursor); editIsRunning = false; return true; } void KTextEditor::DocumentPrivate::pushEditState() { editStateStack.push(editSessionNumber); } void KTextEditor::DocumentPrivate::popEditState() { if (editStateStack.isEmpty()) { return; } int count = editStateStack.pop() - editSessionNumber; while (count < 0) { ++count; editEnd(); } while (count > 0) { --count; editStart(); } } void KTextEditor::DocumentPrivate::inputMethodStart() { m_undoManager->inputMethodStart(); } void KTextEditor::DocumentPrivate::inputMethodEnd() { m_undoManager->inputMethodEnd(); } bool KTextEditor::DocumentPrivate::wrapText(int startLine, int endLine) { if (startLine < 0 || endLine < 0) { return false; } if (!isReadWrite()) { return false; } int col = config()->wordWrapAt(); if (col == 0) { return false; } editStart(); for (int line = startLine; (line <= endLine) && (line < lines()); line++) { Kate::TextLine l = kateTextLine(line); if (!l) { break; } //qCDebug(LOG_KTE) << "try wrap line: " << line; if (l->virtualLength(m_buffer->tabWidth()) > col) { Kate::TextLine nextl = kateTextLine(line + 1); //qCDebug(LOG_KTE) << "do wrap line: " << line; int eolPosition = l->length() - 1; // take tabs into account here, too int x = 0; const QString &t = l->string(); int z2 = 0; for (; z2 < l->length(); z2++) { static const QChar tabChar(QLatin1Char('\t')); if (t.at(z2) == tabChar) { x += m_buffer->tabWidth() - (x % m_buffer->tabWidth()); } else { x++; } if (x > col) { break; } } const int colInChars = qMin(z2, l->length() - 1); int searchStart = colInChars; // If where we are wrapping is an end of line and is a space we don't // want to wrap there if (searchStart == eolPosition && t.at(searchStart).isSpace()) { searchStart--; } // Scan backwards looking for a place to break the line // We are not interested in breaking at the first char // of the line (if it is a space), but we are at the second // anders: if we can't find a space, try breaking on a word // boundary, using KateHighlight::canBreakAt(). // This could be a priority (setting) in the hl/filetype/document int z = -1; int nw = -1; // alternative position, a non word character for (z = searchStart; z >= 0; z--) { if (t.at(z).isSpace()) { break; } if ((nw < 0) && highlight()->canBreakAt(t.at(z), l->attribute(z))) { nw = z; } } if (z >= 0) { // So why don't we just remove the trailing space right away? // Well, the (view's) cursor may be directly in front of that space // (user typing text before the last word on the line), and if that // happens, the cursor would be moved to the next line, which is not // what we want (bug #106261) z++; } else { // There was no space to break at so break at a nonword character if // found, or at the wrapcolumn ( that needs be configurable ) // Don't try and add any white space for the break if ((nw >= 0) && nw < colInChars) { nw++; // break on the right side of the character } z = (nw >= 0) ? nw : colInChars; } if (nextl && !nextl->isAutoWrapped()) { editWrapLine(line, z, true); editMarkLineAutoWrapped(line + 1, true); endLine++; } else { if (nextl && (nextl->length() > 0) && !nextl->at(0).isSpace() && ((l->length() < 1) || !l->at(l->length() - 1).isSpace())) { editInsertText(line + 1, 0, QLatin1String(" ")); } bool newLineAdded = false; editWrapLine(line, z, false, &newLineAdded); editMarkLineAutoWrapped(line + 1, true); endLine++; } } } editEnd(); return true; } bool KTextEditor::DocumentPrivate::wrapParagraph(int first, int last) { if (first == last) { return wrapText(first, last); } if (first < 0 || last < first) { return false; } if (last >= lines() || first > last) { return false; } if (!isReadWrite()) { return false; } editStart(); // Because we shrink and expand lines, we need to track the working set by powerful "MovingStuff" std::unique_ptr range(newMovingRange(KTextEditor::Range(first, 0, last, 0))); std::unique_ptr curr(newMovingCursor(KTextEditor::Cursor(range->start()))); // Scan the selected range for paragraphs, whereas each empty line trigger a new paragraph for (int line = first; line <= range->end().line(); ++line) { // Is our first line a somehow filled line? if(plainKateTextLine(first)->firstChar() < 0) { // Fast forward to first non empty line ++first; curr->setPosition(curr->line() + 1, 0); continue; } // Is our current line a somehow filled line? If not, wrap the paragraph if (plainKateTextLine(line)->firstChar() < 0) { curr->setPosition(line, 0); // Set on empty line joinLines(first, line - 1); // Don't wrap twice! That may cause a bad result if (!wordWrap()) { wrapText(first, first); } first = curr->line() + 1; line = first; } } // If there was no paragraph, we need to wrap now bool needWrap = (curr->line() != range->end().line()); if (needWrap && plainKateTextLine(first)->firstChar() != -1) { joinLines(first, range->end().line()); // Don't wrap twice! That may cause a bad result if (!wordWrap()) { wrapText(first, first); } } editEnd(); return true; } bool KTextEditor::DocumentPrivate::editInsertText(int line, int col, const QString &s) { // verbose debug EDIT_DEBUG << "editInsertText" << line << col << s; if (line < 0 || col < 0) { return false; } if (!isReadWrite()) { return false; } Kate::TextLine l = kateTextLine(line); if (!l) { return false; } // nothing to do, do nothing! if (s.isEmpty()) { return true; } editStart(); QString s2 = s; int col2 = col; if (col2 > l->length()) { s2 = QString(col2 - l->length(), QLatin1Char(' ')) + s; col2 = l->length(); } m_undoManager->slotTextInserted(line, col2, s2); // remember last change cursor m_editLastChangeStartCursor = KTextEditor::Cursor(line, col2); // insert text into line m_buffer->insertText(m_editLastChangeStartCursor, s2); emit textInserted(this, KTextEditor::Range(line, col2, line, col2 + s2.length())); editEnd(); return true; } bool KTextEditor::DocumentPrivate::editRemoveText(int line, int col, int len) { // verbose debug EDIT_DEBUG << "editRemoveText" << line << col << len; if (line < 0 || col < 0 || len < 0) { return false; } if (!isReadWrite()) { return false; } Kate::TextLine l = kateTextLine(line); if (!l) { return false; } // nothing to do, do nothing! if (len == 0) { return true; } // wrong column if (col >= l->text().size()) { return false; } // don't try to remove what's not there len = qMin(len, l->text().size() - col); editStart(); QString oldText = l->string().mid(col, len); m_undoManager->slotTextRemoved(line, col, oldText); // remember last change cursor m_editLastChangeStartCursor = KTextEditor::Cursor(line, col); // remove text from line m_buffer->removeText(KTextEditor::Range(m_editLastChangeStartCursor, KTextEditor::Cursor(line, col + len))); emit textRemoved(this, KTextEditor::Range(line, col, line, col + len), oldText); editEnd(); return true; } bool KTextEditor::DocumentPrivate::editMarkLineAutoWrapped(int line, bool autowrapped) { // verbose debug EDIT_DEBUG << "editMarkLineAutoWrapped" << line << autowrapped; if (line < 0) { return false; } if (!isReadWrite()) { return false; } Kate::TextLine l = kateTextLine(line); if (!l) { return false; } editStart(); m_undoManager->slotMarkLineAutoWrapped(line, autowrapped); l->setAutoWrapped(autowrapped); editEnd(); return true; } bool KTextEditor::DocumentPrivate::editWrapLine(int line, int col, bool newLine, bool *newLineAdded) { // verbose debug EDIT_DEBUG << "editWrapLine" << line << col << newLine; if (line < 0 || col < 0) { return false; } if (!isReadWrite()) { return false; } Kate::TextLine l = kateTextLine(line); if (!l) { return false; } editStart(); Kate::TextLine nextLine = kateTextLine(line + 1); const int length = l->length(); m_undoManager->slotLineWrapped(line, col, length - col, (!nextLine || newLine)); if (!nextLine || newLine) { m_buffer->wrapLine(KTextEditor::Cursor(line, col)); QList list; for (QHash::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i) { if (i.value()->line >= line) { if ((col == 0) || (i.value()->line > line)) { list.append(i.value()); } } } for (int i = 0; i < list.size(); ++i) { m_marks.take(list.at(i)->line); } for (int i = 0; i < list.size(); ++i) { list.at(i)->line++; m_marks.insert(list.at(i)->line, list.at(i)); } if (!list.isEmpty()) { emit marksChanged(this); } // yes, we added a new line ! if (newLineAdded) { (*newLineAdded) = true; } } else { m_buffer->wrapLine(KTextEditor::Cursor(line, col)); m_buffer->unwrapLine(line + 2); // no, no new line added ! if (newLineAdded) { (*newLineAdded) = false; } } // remember last change cursor m_editLastChangeStartCursor = KTextEditor::Cursor(line, col); emit textInserted(this, KTextEditor::Range(line, col, line + 1, 0)); editEnd(); return true; } bool KTextEditor::DocumentPrivate::editUnWrapLine(int line, bool removeLine, int length) { // verbose debug EDIT_DEBUG << "editUnWrapLine" << line << removeLine << length; if (line < 0 || length < 0) { return false; } if (!isReadWrite()) { return false; } Kate::TextLine l = kateTextLine(line); Kate::TextLine nextLine = kateTextLine(line + 1); if (!l || !nextLine) { return false; } editStart(); int col = l->length(); m_undoManager->slotLineUnWrapped(line, col, length, removeLine); if (removeLine) { m_buffer->unwrapLine(line + 1); } else { m_buffer->wrapLine(KTextEditor::Cursor(line + 1, length)); m_buffer->unwrapLine(line + 1); } QList list; for (QHash::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i) { if (i.value()->line >= line + 1) { list.append(i.value()); } if (i.value()->line == line + 1) { KTextEditor::Mark *mark = m_marks.take(line); if (mark) { i.value()->type |= mark->type; } } } for (int i = 0; i < list.size(); ++i) { m_marks.take(list.at(i)->line); } for (int i = 0; i < list.size(); ++i) { list.at(i)->line--; m_marks.insert(list.at(i)->line, list.at(i)); } if (!list.isEmpty()) { emit marksChanged(this); } // remember last change cursor m_editLastChangeStartCursor = KTextEditor::Cursor(line, col); emit textRemoved(this, KTextEditor::Range(line, col, line + 1, 0), QStringLiteral("\n")); editEnd(); return true; } bool KTextEditor::DocumentPrivate::editInsertLine(int line, const QString &s) { // verbose debug EDIT_DEBUG << "editInsertLine" << line << s; if (line < 0) { return false; } if (!isReadWrite()) { return false; } if (line > lines()) { return false; } editStart(); m_undoManager->slotLineInserted(line, s); // wrap line if (line > 0) { Kate::TextLine previousLine = m_buffer->line(line - 1); m_buffer->wrapLine(KTextEditor::Cursor(line - 1, previousLine->text().size())); } else { m_buffer->wrapLine(KTextEditor::Cursor(0, 0)); } // insert text m_buffer->insertText(KTextEditor::Cursor(line, 0), s); Kate::TextLine tl = m_buffer->line(line); QList list; for (QHash::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i) { if (i.value()->line >= line) { list.append(i.value()); } } for (int i = 0; i < list.size(); ++i) { m_marks.take(list.at(i)->line); } for (int i = 0; i < list.size(); ++i) { list.at(i)->line++; m_marks.insert(list.at(i)->line, list.at(i)); } if (!list.isEmpty()) { emit marksChanged(this); } KTextEditor::Range rangeInserted(line, 0, line, tl->length()); if (line) { Kate::TextLine prevLine = plainKateTextLine(line - 1); rangeInserted.setStart(KTextEditor::Cursor(line - 1, prevLine->length())); } else { rangeInserted.setEnd(KTextEditor::Cursor(line + 1, 0)); } // remember last change cursor m_editLastChangeStartCursor = rangeInserted.start(); emit textInserted(this, rangeInserted); editEnd(); return true; } bool KTextEditor::DocumentPrivate::editRemoveLine(int line) { return editRemoveLines(line, line); } bool KTextEditor::DocumentPrivate::editRemoveLines(int from, int to) { // verbose debug EDIT_DEBUG << "editRemoveLines" << from << to; if (to < from || from < 0 || to > lastLine()) { return false; } if (!isReadWrite()) { return false; } if (lines() == 1) { return editRemoveText(0, 0, kateTextLine(0)->length()); } editStart(); QStringList oldText; /** * first remove text */ for (int line = to; line >= from; --line) { Kate::TextLine tl = m_buffer->line(line); oldText.prepend(this->line(line)); m_undoManager->slotLineRemoved(line, this->line(line)); m_buffer->removeText(KTextEditor::Range(KTextEditor::Cursor(line, 0), KTextEditor::Cursor(line, tl->text().size()))); } /** * then collapse lines */ for (int line = to; line >= from; --line) { /** * unwrap all lines, prefer to unwrap line behind, skip to wrap line 0 */ if (line + 1 < m_buffer->lines()) { m_buffer->unwrapLine(line + 1); } else if (line) { m_buffer->unwrapLine(line); } } QList rmark; QList list; foreach (KTextEditor::Mark *mark, m_marks) { int line = mark->line; if (line > to) { list << line; } else if (line >= from) { rmark << line; } } foreach (int line, rmark) { delete m_marks.take(line); } foreach (int line, list) { KTextEditor::Mark *mark = m_marks.take(line); mark->line -= to - from + 1; m_marks.insert(mark->line, mark); } if (!list.isEmpty()) { emit marksChanged(this); } KTextEditor::Range rangeRemoved(from, 0, to + 1, 0); if (to == lastLine() + to - from + 1) { rangeRemoved.setEnd(KTextEditor::Cursor(to, oldText.last().length())); if (from > 0) { Kate::TextLine prevLine = plainKateTextLine(from - 1); rangeRemoved.setStart(KTextEditor::Cursor(from - 1, prevLine->length())); } } // remember last change cursor m_editLastChangeStartCursor = rangeRemoved.start(); emit textRemoved(this, rangeRemoved, oldText.join(QStringLiteral("\n")) + QLatin1Char('\n')); editEnd(); return true; } //END //BEGIN KTextEditor::UndoInterface stuff uint KTextEditor::DocumentPrivate::undoCount() const { return m_undoManager->undoCount(); } uint KTextEditor::DocumentPrivate::redoCount() const { return m_undoManager->redoCount(); } void KTextEditor::DocumentPrivate::undo() { m_undoManager->undo(); } void KTextEditor::DocumentPrivate::redo() { m_undoManager->redo(); } //END //BEGIN KTextEditor::SearchInterface stuff QVector KTextEditor::DocumentPrivate::searchText( const KTextEditor::Range &range, const QString &pattern, const KTextEditor::SearchOptions options) const { const bool escapeSequences = options.testFlag(KTextEditor::EscapeSequences); const bool regexMode = options.testFlag(KTextEditor::Regex); const bool backwards = options.testFlag(KTextEditor::Backwards); const bool wholeWords = options.testFlag(KTextEditor::WholeWords); const Qt::CaseSensitivity caseSensitivity = options.testFlag(KTextEditor::CaseInsensitive) ? Qt::CaseInsensitive : Qt::CaseSensitive; if (regexMode) { // regexp search // escape sequences are supported by definition KateRegExpSearch searcher(this, caseSensitivity); return searcher.search(pattern, range, backwards); } if (escapeSequences) { // escaped search KatePlainTextSearch searcher(this, caseSensitivity, wholeWords); KTextEditor::Range match = searcher.search(KateRegExpSearch::escapePlaintext(pattern), range, backwards); QVector result; result.append(match); return result; } // plaintext search KatePlainTextSearch searcher(this, caseSensitivity, wholeWords); KTextEditor::Range match = searcher.search(pattern, range, backwards); QVector result; result.append(match); return result; } //END QWidget *KTextEditor::DocumentPrivate::dialogParent() { QWidget *w = widget(); if (!w) { w = activeView(); if (!w) { w = QApplication::activeWindow(); } } return w; } //BEGIN KTextEditor::HighlightingInterface stuff bool KTextEditor::DocumentPrivate::setMode(const QString &name) { updateFileType(name); return true; } KTextEditor::DefaultStyle KTextEditor::DocumentPrivate::defaultStyleAt(const KTextEditor::Cursor &position) const { // TODO, FIXME KDE5: in surrogate, use 2 bytes before if (! isValidTextPosition(position)) { return dsNormal; } int ds = const_cast(this)-> defStyleNum(position.line(), position.column()); if (ds < 0 || ds > static_cast(dsError)) { return dsNormal; } return static_cast(ds); } QString KTextEditor::DocumentPrivate::mode() const { return m_fileType; } QStringList KTextEditor::DocumentPrivate::modes() const { QStringList m; const QList &modeList = KTextEditor::EditorPrivate::self()->modeManager()->list(); foreach (KateFileType *type, modeList) { m << type->name; } return m; } bool KTextEditor::DocumentPrivate::setHighlightingMode(const QString &name) { int mode = KateHlManager::self()->nameFind(name); if (mode == -1) { return false; } m_buffer->setHighlight(mode); return true; } QString KTextEditor::DocumentPrivate::highlightingMode() const { return highlight()->name(); } QStringList KTextEditor::DocumentPrivate::highlightingModes() const { QStringList hls; for (const auto &hl : KateHlManager::self()->modeList()) { hls << hl.name(); } return hls; } QString KTextEditor::DocumentPrivate::highlightingModeSection(int index) const { return KateHlManager::self()->modeList().at(index).section(); } QString KTextEditor::DocumentPrivate::modeSection(int index) const { return KTextEditor::EditorPrivate::self()->modeManager()->list().at(index)->section; } void KTextEditor::DocumentPrivate::bufferHlChanged() { // update all views makeAttribs(false); // deactivate indenter if necessary m_indenter->checkRequiredStyle(); emit highlightingModeChanged(this); } void KTextEditor::DocumentPrivate::setDontChangeHlOnSave() { m_hlSetByUser = true; } void KTextEditor::DocumentPrivate::bomSetByUser() { m_bomSetByUser = true; } //END //BEGIN KTextEditor::SessionConfigInterface and KTextEditor::ParameterizedSessionConfigInterface stuff void KTextEditor::DocumentPrivate::readSessionConfig(const KConfigGroup &kconfig, const QSet &flags) { if (!flags.contains(QStringLiteral("SkipEncoding"))) { // get the encoding QString tmpenc = kconfig.readEntry("Encoding"); if (!tmpenc.isEmpty() && (tmpenc != encoding())) { setEncoding(tmpenc); } } if (!flags.contains(QStringLiteral("SkipUrl"))) { // restore the url QUrl url(kconfig.readEntry("URL")); // open the file if url valid if (!url.isEmpty() && url.isValid()) { openUrl(url); } else { completed(); //perhaps this should be emitted at the end of this function } } else { completed(); //perhaps this should be emitted at the end of this function } if (!flags.contains(QStringLiteral("SkipMode"))) { // restore the filetype if (kconfig.hasKey("Mode")) { updateFileType(kconfig.readEntry("Mode", fileType())); // restore if set by user, too! m_fileTypeSetByUser = kconfig.readEntry("Mode Set By User", false); } } if (!flags.contains(QStringLiteral("SkipHighlighting"))) { // restore the hl stuff if (kconfig.hasKey("Highlighting")) { const int mode = KateHlManager::self()->nameFind(kconfig.readEntry("Highlighting")); if (mode >= 0) { m_buffer->setHighlight(mode); // restore if set by user, too! see bug 332605, otherwise we loose the hl later again on save m_hlSetByUser = kconfig.readEntry("Highlighting Set By User", false); } } } // indent mode config()->setIndentationMode(kconfig.readEntry("Indentation Mode", config()->indentationMode())); // Restore Bookmarks const QList marks = kconfig.readEntry("Bookmarks", QList()); for (int i = 0; i < marks.count(); i++) { addMark(marks.at(i), KTextEditor::DocumentPrivate::markType01); } } void KTextEditor::DocumentPrivate::writeSessionConfig(KConfigGroup &kconfig, const QSet &flags) { if (this->url().isLocalFile()) { const QString path = this->url().toLocalFile(); if (path.startsWith(QDir::tempPath())) { return; // inside tmp resource, do not save } } if (!flags.contains(QStringLiteral("SkipUrl"))) { // save url kconfig.writeEntry("URL", this->url().toString()); } if (!flags.contains(QStringLiteral("SkipEncoding"))) { // save encoding kconfig.writeEntry("Encoding", encoding()); } if (!flags.contains(QStringLiteral("SkipMode"))) { // save file type kconfig.writeEntry("Mode", m_fileType); // save if set by user, too! kconfig.writeEntry("Mode Set By User", m_fileTypeSetByUser); } if (!flags.contains(QStringLiteral("SkipHighlighting"))) { // save hl kconfig.writeEntry("Highlighting", highlight()->name()); // save if set by user, too! see bug 332605, otherwise we loose the hl later again on save kconfig.writeEntry("Highlighting Set By User", m_hlSetByUser); } // indent mode kconfig.writeEntry("Indentation Mode", config()->indentationMode()); // Save Bookmarks QList marks; for (QHash::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i) if (i.value()->type & KTextEditor::MarkInterface::markType01) { marks << i.value()->line; } kconfig.writeEntry("Bookmarks", marks); } //END KTextEditor::SessionConfigInterface and KTextEditor::ParameterizedSessionConfigInterface stuff uint KTextEditor::DocumentPrivate::mark(int line) { KTextEditor::Mark *m = m_marks.value(line); if (!m) { return 0; } return m->type; } void KTextEditor::DocumentPrivate::setMark(int line, uint markType) { clearMark(line); addMark(line, markType); } void KTextEditor::DocumentPrivate::clearMark(int line) { if (line < 0 || line > lastLine()) { return; } if (!m_marks.value(line)) { return; } KTextEditor::Mark *mark = m_marks.take(line); emit markChanged(this, *mark, MarkRemoved); emit marksChanged(this); delete mark; tagLines(line, line); repaintViews(true); } void KTextEditor::DocumentPrivate::addMark(int line, uint markType) { KTextEditor::Mark *mark; if (line < 0 || line > lastLine()) { return; } if (markType == 0) { return; } if ((mark = m_marks.value(line))) { // Remove bits already set markType &= ~mark->type; if (markType == 0) { return; } // Add bits mark->type |= markType; } else { mark = new KTextEditor::Mark; mark->line = line; mark->type = markType; m_marks.insert(line, mark); } // Emit with a mark having only the types added. KTextEditor::Mark temp; temp.line = line; temp.type = markType; emit markChanged(this, temp, MarkAdded); emit marksChanged(this); tagLines(line, line); repaintViews(true); } void KTextEditor::DocumentPrivate::removeMark(int line, uint markType) { if (line < 0 || line > lastLine()) { return; } KTextEditor::Mark *mark = m_marks.value(line); if (!mark) { return; } // Remove bits not set markType &= mark->type; if (markType == 0) { return; } // Subtract bits mark->type &= ~markType; // Emit with a mark having only the types removed. KTextEditor::Mark temp; temp.line = line; temp.type = markType; emit markChanged(this, temp, MarkRemoved); if (mark->type == 0) { m_marks.remove(line); delete mark; } emit marksChanged(this); tagLines(line, line); repaintViews(true); } const QHash &KTextEditor::DocumentPrivate::marks() { return m_marks; } void KTextEditor::DocumentPrivate::requestMarkTooltip(int line, QPoint position) { KTextEditor::Mark *mark = m_marks.value(line); if (!mark) { return; } bool handled = false; emit markToolTipRequested(this, *mark, position, handled); } bool KTextEditor::DocumentPrivate::handleMarkClick(int line) { bool handled = false; KTextEditor::Mark *mark = m_marks.value(line); if (!mark) { emit markClicked(this, KTextEditor::Mark{line, 0}, handled); } else { emit markClicked(this, *mark, handled); } return handled; } bool KTextEditor::DocumentPrivate::handleMarkContextMenu(int line, QPoint position) { bool handled = false; KTextEditor::Mark *mark = m_marks.value(line); if (!mark) { emit markContextMenuRequested(this, KTextEditor::Mark{line, 0}, position, handled); } else { emit markContextMenuRequested(this, *mark, position, handled); } return handled; } void KTextEditor::DocumentPrivate::clearMarks() { while (!m_marks.isEmpty()) { QHash::iterator it = m_marks.begin(); KTextEditor::Mark mark = *it.value(); delete it.value(); m_marks.erase(it); emit markChanged(this, mark, MarkRemoved); tagLines(mark.line, mark.line); } m_marks.clear(); emit marksChanged(this); repaintViews(true); } void KTextEditor::DocumentPrivate::setMarkPixmap(MarkInterface::MarkTypes type, const QPixmap &pixmap) { m_markPixmaps.insert(type, pixmap); } void KTextEditor::DocumentPrivate::setMarkDescription(MarkInterface::MarkTypes type, const QString &description) { m_markDescriptions.insert(type, description); } QPixmap KTextEditor::DocumentPrivate::markPixmap(MarkInterface::MarkTypes type) const { return m_markPixmaps.value(type, QPixmap()); } QColor KTextEditor::DocumentPrivate::markColor(MarkInterface::MarkTypes type) const { uint reserved = (0x1 << KTextEditor::MarkInterface::reservedMarkersCount()) - 1; if ((uint)type >= (uint)markType01 && (uint)type <= reserved) { return KateRendererConfig::global()->lineMarkerColor(type); } else { return QColor(); } } QString KTextEditor::DocumentPrivate::markDescription(MarkInterface::MarkTypes type) const { return m_markDescriptions.value(type, QString()); } void KTextEditor::DocumentPrivate::setEditableMarks(uint markMask) { m_editableMarks = markMask; } uint KTextEditor::DocumentPrivate::editableMarks() const { return m_editableMarks; } //END //BEGIN KTextEditor::PrintInterface stuff bool KTextEditor::DocumentPrivate::print() { return KatePrinter::print(this); } void KTextEditor::DocumentPrivate::printPreview() { KatePrinter::printPreview(this); } //END KTextEditor::PrintInterface stuff //BEGIN KTextEditor::DocumentInfoInterface (### unfinished) QString KTextEditor::DocumentPrivate::mimeType() { /** * collect first 4k of text * only heuristic */ QByteArray buf; for (int i = 0; (i < lines()) && (buf.size() <= 4096); ++i) { buf.append(line(i).toUtf8()); buf.append('\n'); } // use path of url, too, if set if (!url().path().isEmpty()) { return QMimeDatabase().mimeTypeForFileNameAndData(url().path(), buf).name(); } // else only use the content return QMimeDatabase().mimeTypeForData(buf).name(); } //END KTextEditor::DocumentInfoInterface //BEGIN: error void KTextEditor::DocumentPrivate::showAndSetOpeningErrorAccess() { QPointer message = new KTextEditor::Message(i18n("The file %1 could not be loaded, as it was not possible to read from it.
Check if you have read access to this file.", this->url().toDisplayString(QUrl::PreferLocalFile)), KTextEditor::Message::Error); message->setWordWrap(true); QAction *tryAgainAction = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("translators: you can also translate 'Try Again' with 'Reload'", "Try Again"), nullptr); connect(tryAgainAction, SIGNAL(triggered()), SLOT(documentReload()), Qt::QueuedConnection); QAction *closeAction = new QAction(QIcon::fromTheme(QStringLiteral("window-close")), i18n("&Close"), nullptr); closeAction->setToolTip(i18n("Close message")); // add try again and close actions message->addAction(tryAgainAction); message->addAction(closeAction); // finally post message postMessage(message); // remember error m_openingError = true; m_openingErrorMessage = i18n("The file %1 could not be loaded, as it was not possible to read from it.\n\nCheck if you have read access to this file.", this->url().toDisplayString(QUrl::PreferLocalFile)); } //END: error void KTextEditor::DocumentPrivate::openWithLineLengthLimitOverride() { // raise line length limit to the next power of 2 const int longestLine = m_buffer->longestLineLoaded(); int newLimit = pow(2, ceil(log2(longestLine))); if (newLimit <= longestLine) { newLimit *= 2; } // do the raise config()->setLineLengthLimit(newLimit); // just reload m_buffer->clear(); openFile(); if (!m_openingError) { setReadWrite(true); m_readWriteStateBeforeLoading = true; } } int KTextEditor::DocumentPrivate::lineLengthLimit() const { return config()->lineLengthLimit(); } //BEGIN KParts::ReadWrite stuff bool KTextEditor::DocumentPrivate::openFile() { /** * we are about to invalidate all cursors/ranges/.. => m_buffer->openFile will do so */ emit aboutToInvalidateMovingInterfaceContent(this); // no open errors until now... m_openingError = false; m_openingErrorMessage.clear (); // add new m_file to dirwatch activateDirWatch(); // remember current encoding QString currentEncoding = encoding(); // // mime type magic to get encoding right // QString mimeType = arguments().mimeType(); int pos = mimeType.indexOf(QLatin1Char(';')); if (pos != -1 && !(m_reloading && m_userSetEncodingForNextReload)) { setEncoding(mimeType.mid(pos + 1)); } // update file type, we do this here PRE-LOAD, therefore pass file name for reading from updateFileType(KTextEditor::EditorPrivate::self()->modeManager()->fileType(this, localFilePath())); // read dir config (if possible and wanted) // do this PRE-LOAD to get encoding info! readDirConfig(); // perhaps we need to re-set again the user encoding if (m_reloading && m_userSetEncodingForNextReload && (currentEncoding != encoding())) { setEncoding(currentEncoding); } bool success = m_buffer->openFile(localFilePath(), (m_reloading && m_userSetEncodingForNextReload)); // // yeah, success // read variables // if (success) { readVariables(); } // // update views // foreach (KTextEditor::ViewPrivate *view, m_views) { // This is needed here because inserting the text moves the view's start position (it is a MovingCursor) view->setCursorPosition(KTextEditor::Cursor()); view->updateView(true); } // Inform that the text has changed (required as we're not inside the usual editStart/End stuff) emit textChanged(this); emit loaded(this); // // to houston, we are not modified // if (m_modOnHd) { m_modOnHd = false; m_modOnHdReason = OnDiskUnmodified; m_prevModOnHdReason = OnDiskUnmodified; emit modifiedOnDisk(this, m_modOnHd, m_modOnHdReason); } // // display errors // if (!success) { showAndSetOpeningErrorAccess(); } // warn: broken encoding if (m_buffer->brokenEncoding()) { // this file can't be saved again without killing it setReadWrite(false); m_readWriteStateBeforeLoading=false; QPointer message = new KTextEditor::Message(i18n("The file %1 was opened with %2 encoding but contained invalid characters.
" "It is set to read-only mode, as saving might destroy its content.
" "Either reopen the file with the correct encoding chosen or enable the read-write mode again in the tools menu to be able to edit it.", this->url().toDisplayString(QUrl::PreferLocalFile), QString::fromLatin1(m_buffer->textCodec()->name())), KTextEditor::Message::Warning); message->setWordWrap(true); postMessage(message); // remember error m_openingError = true; m_openingErrorMessage = i18n("The file %1 was opened with %2 encoding but contained invalid characters." " It is set to read-only mode, as saving might destroy its content." " Either reopen the file with the correct encoding chosen or enable the read-write mode again in the tools menu to be able to edit it.", this->url().toDisplayString(QUrl::PreferLocalFile), QString::fromLatin1(m_buffer->textCodec()->name())); } // warn: too long lines if (m_buffer->tooLongLinesWrapped()) { // this file can't be saved again without modifications setReadWrite(false); m_readWriteStateBeforeLoading = false; QPointer message = new KTextEditor::Message(i18n("The file %1 was opened and contained lines longer than the configured Line Length Limit (%2 characters).
" "The longest of those lines was %3 characters long
" "Those lines were wrapped and the document is set to read-only mode, as saving will modify its content.", this->url().toDisplayString(QUrl::PreferLocalFile), config()->lineLengthLimit(), m_buffer->longestLineLoaded()), KTextEditor::Message::Warning); QAction *increaseAndReload = new QAction(i18n("Temporarily raise limit and reload file"), message); connect(increaseAndReload, SIGNAL(triggered()), this, SLOT(openWithLineLengthLimitOverride())); message->addAction(increaseAndReload, true); message->addAction(new QAction(i18n("Close"), message), true); message->setWordWrap(true); postMessage(message); // remember error m_openingError = true; m_openingErrorMessage = i18n("The file %1 was opened and contained lines longer than the configured Line Length Limit (%2 characters).
" "The longest of those lines was %3 characters long
" "Those lines were wrapped and the document is set to read-only mode, as saving will modify its content.", this->url().toDisplayString(QUrl::PreferLocalFile), config()->lineLengthLimit(),m_buffer->longestLineLoaded()); } // // return the success // return success; } bool KTextEditor::DocumentPrivate::saveFile() { // delete pending mod-on-hd message if applicable. delete m_modOnHdHandler; // some warnings, if file was changed by the outside! if (!url().isEmpty()) { if (m_fileChangedDialogsActivated && m_modOnHd) { QString str = reasonedMOHString() + QLatin1String("\n\n"); if (!isModified()) { if (KMessageBox::warningContinueCancel(dialogParent(), str + i18n("Do you really want to save this unmodified file? You could overwrite changed data in the file on disk."), i18n("Trying to Save Unmodified File"), KGuiItem(i18n("Save Nevertheless"))) != KMessageBox::Continue) { return false; } } else { if (KMessageBox::warningContinueCancel(dialogParent(), str + i18n("Do you really want to save this file? Both your open file and the file on disk were changed. There could be some data lost."), i18n("Possible Data Loss"), KGuiItem(i18n("Save Nevertheless"))) != KMessageBox::Continue) { return false; } } } } // // can we encode it if we want to save it ? // if (!m_buffer->canEncode() && (KMessageBox::warningContinueCancel(dialogParent(), i18n("The selected encoding cannot encode every Unicode character in this document. Do you really want to save it? There could be some data lost."), i18n("Possible Data Loss"), KGuiItem(i18n("Save Nevertheless"))) != KMessageBox::Continue)) { return false; } /** * create a backup file or abort if that fails! * if no backup file wanted, this routine will just return true */ if (!createBackupFile()) return false; // update file type, pass no file path, read file type content from this document QString oldPath = m_dirWatchFile; // only update file type if path has changed so that variables are not overridden on normal save if (oldPath != localFilePath()) { updateFileType(KTextEditor::EditorPrivate::self()->modeManager()->fileType(this, QString())); if (url().isLocalFile()) { // if file is local then read dir config for new path readDirConfig(); } } // read our vars readVariables(); // remove file from dirwatch deactivateDirWatch(); // remove all trailing spaces in the document (as edit actions) // NOTE: we need this as edit actions, since otherwise the edit actions // in the swap file recovery may happen at invalid cursor positions removeTrailingSpaces(); // // try to save // if (!m_buffer->saveFile(localFilePath())) { // add m_file again to dirwatch activateDirWatch(oldPath); KMessageBox::error(dialogParent(), i18n("The document could not be saved, as it was not possible to write to %1.\nCheck that you have write access to this file or that enough disk space is available.\nThe original file may be lost or damaged. Don't quit the application until the file is successfully written.", this->url().toDisplayString(QUrl::PreferLocalFile))); return false; } // update the checksum createDigest(); // add m_file again to dirwatch activateDirWatch(); // // we are not modified // if (m_modOnHd) { m_modOnHd = false; m_modOnHdReason = OnDiskUnmodified; m_prevModOnHdReason = OnDiskUnmodified; emit modifiedOnDisk(this, m_modOnHd, m_modOnHdReason); } // (dominik) mark last undo group as not mergeable, otherwise the next // edit action might be merged and undo will never stop at the saved state m_undoManager->undoSafePoint(); m_undoManager->updateLineModifications(); // // return success // return true; } bool KTextEditor::DocumentPrivate::createBackupFile() { /** * backup for local or remote files wanted? */ const bool backupLocalFiles = config()->backupOnSaveLocal(); const bool backupRemoteFiles = config()->backupOnSaveRemote(); /** * early out, before mount check: backup wanted at all? * => if not, all fine, just return */ if (!backupLocalFiles && !backupRemoteFiles) { return true; } /** * decide if we need backup based on locality * skip that, if we always want backups, as currentMountPoints is not that fast */ QUrl u(url()); bool needBackup = backupLocalFiles && backupRemoteFiles; if (!needBackup) { bool slowOrRemoteFile = !u.isLocalFile(); if (!slowOrRemoteFile) { // could be a mounted remote filesystem (e.g. nfs, sshfs, cifs) // we have the early out above to skip this, if we want no backup, which is the default KMountPoint::Ptr mountPoint = KMountPoint::currentMountPoints().findByDevice(u.toLocalFile()); slowOrRemoteFile = (mountPoint && mountPoint->probablySlow()); } needBackup = (!slowOrRemoteFile && backupLocalFiles) || (slowOrRemoteFile && backupRemoteFiles); } /** * no backup needed? be done */ if (!needBackup) { return true; } /** * else: try to backup */ if (config()->backupPrefix().contains(QDir::separator())) { /** * replace complete path, as prefix is a path! */ u.setPath(config()->backupPrefix() + u.fileName() + config()->backupSuffix()); } else { /** * replace filename in url */ const QString fileName = u.fileName(); u = u.adjusted(QUrl::RemoveFilename); u.setPath(u.path() + config()->backupPrefix() + fileName + config()->backupSuffix()); } qCDebug(LOG_KTE) << "backup src file name: " << url(); qCDebug(LOG_KTE) << "backup dst file name: " << u; // handle the backup... bool backupSuccess = false; // local file mode, no kio if (u.isLocalFile()) { if (QFile::exists(url().toLocalFile())) { // first: check if backupFile is already there, if true, unlink it QFile backupFile(u.toLocalFile()); if (backupFile.exists()) { backupFile.remove(); } backupSuccess = QFile::copy(url().toLocalFile(), u.toLocalFile()); } else { backupSuccess = true; } } else { // remote file mode, kio // get the right permissions, start with safe default KIO::StatJob *statJob = KIO::stat(url(), KIO::StatJob::SourceSide, 2); KJobWidgets::setWindow(statJob, QApplication::activeWindow()); if (statJob->exec()) { // do a evil copy which will overwrite target if possible KFileItem item(statJob->statResult(), url()); KIO::FileCopyJob *job = KIO::file_copy(url(), u, item.permissions(), KIO::Overwrite); KJobWidgets::setWindow(job, QApplication::activeWindow()); backupSuccess = job->exec(); } else { backupSuccess = true; } } // backup has failed, ask user how to proceed if (!backupSuccess && (KMessageBox::warningContinueCancel(dialogParent() , i18n("For file %1 no backup copy could be created before saving." " If an error occurs while saving, you might lose the data of this file." " A reason could be that the media you write to is full or the directory of the file is read-only for you.", url().toDisplayString(QUrl::PreferLocalFile)) , i18n("Failed to create backup copy.") , KGuiItem(i18n("Try to Save Nevertheless")) , KStandardGuiItem::cancel(), QStringLiteral("Backup Failed Warning")) != KMessageBox::Continue)) { return false; } return true; } void KTextEditor::DocumentPrivate::readDirConfig() { if (!url().isLocalFile()) { return; } /** * first search .kateconfig upwards * with recursion guard */ QSet seenDirectories; QDir dir (QFileInfo(localFilePath()).absolutePath()); while (!seenDirectories.contains (dir.absolutePath ())) { /** * fill recursion guard */ seenDirectories.insert (dir.absolutePath ()); // try to open config file in this dir QFile f(dir.absolutePath () + QLatin1String("/.kateconfig")); if (f.open(QIODevice::ReadOnly)) { QTextStream stream(&f); uint linesRead = 0; QString line = stream.readLine(); while ((linesRead < 32) && !line.isNull()) { readVariableLine(line); line = stream.readLine(); linesRead++; } return; } /** * else: cd up, if possible or abort */ if (!dir.cdUp()) { break; } } #if EDITORCONFIG_FOUND // if there wasn’t any .kateconfig file and KTextEditor was compiled with // EditorConfig support, try to load document config from a .editorconfig // file, if such is provided EditorConfig editorConfig(this); editorConfig.parse(); #endif } void KTextEditor::DocumentPrivate::activateDirWatch(const QString &useFileName) { QString fileToUse = useFileName; if (fileToUse.isEmpty()) { fileToUse = localFilePath(); } QFileInfo fileInfo = QFileInfo(fileToUse); if (fileInfo.isSymLink()) { // Monitor the actual data and not the symlink fileToUse = fileInfo.canonicalFilePath(); } // same file as we are monitoring, return if (fileToUse == m_dirWatchFile) { return; } // remove the old watched file deactivateDirWatch(); // add new file if needed if (url().isLocalFile() && !fileToUse.isEmpty()) { KTextEditor::EditorPrivate::self()->dirWatch()->addFile(fileToUse); m_dirWatchFile = fileToUse; } } void KTextEditor::DocumentPrivate::deactivateDirWatch() { if (!m_dirWatchFile.isEmpty()) { KTextEditor::EditorPrivate::self()->dirWatch()->removeFile(m_dirWatchFile); } m_dirWatchFile.clear(); } bool KTextEditor::DocumentPrivate::openUrl(const QUrl &url) { if (!m_reloading) { // Reset filetype when opening url m_fileTypeSetByUser = false; } bool res = KTextEditor::Document::openUrl(normalizeUrl(url)); updateDocName(); return res; } bool KTextEditor::DocumentPrivate::closeUrl() { // // file mod on hd // if (!m_reloading && !url().isEmpty()) { if (m_fileChangedDialogsActivated && m_modOnHd) { // make sure to not forget pending mod-on-hd handler delete m_modOnHdHandler; QWidget *parentWidget(dialogParent()); if (!(KMessageBox::warningContinueCancel( parentWidget, reasonedMOHString() + QLatin1String("\n\n") + i18n("Do you really want to continue to close this file? Data loss may occur."), i18n("Possible Data Loss"), KGuiItem(i18n("Close Nevertheless")), KStandardGuiItem::cancel(), QStringLiteral("kate_close_modonhd_%1").arg(m_modOnHdReason)) == KMessageBox::Continue)) { /** * reset reloading */ m_reloading = false; return false; } } } // // first call the normal kparts implementation // if (!KParts::ReadWritePart::closeUrl()) { /** * reset reloading */ m_reloading = false; return false; } // Tell the world that we're about to go ahead with the close if (!m_reloading) { emit aboutToClose(this); } /** * delete all KTE::Messages */ if (!m_messageHash.isEmpty()) { QList keys = m_messageHash.keys(); foreach (KTextEditor::Message *message, keys) { delete message; } } /** * we are about to invalidate all cursors/ranges/.. => m_buffer->clear will do so */ emit aboutToInvalidateMovingInterfaceContent(this); // remove file from dirwatch deactivateDirWatch(); // // empty url + fileName // setUrl(QUrl()); setLocalFilePath(QString()); // we are not modified if (m_modOnHd) { m_modOnHd = false; m_modOnHdReason = OnDiskUnmodified; m_prevModOnHdReason = OnDiskUnmodified; emit modifiedOnDisk(this, m_modOnHd, m_modOnHdReason); } // remove all marks clearMarks(); // clear the buffer m_buffer->clear(); // clear undo/redo history m_undoManager->clearUndo(); m_undoManager->clearRedo(); // no, we are no longer modified setModified(false); // we have no longer any hl m_buffer->setHighlight(0); // update all our views foreach (KTextEditor::ViewPrivate *view, m_views) { view->clearSelection(); // fix bug #118588 view->clear(); } // purge swap file if (m_swapfile) { m_swapfile->fileClosed(); } // success return true; } bool KTextEditor::DocumentPrivate::isDataRecoveryAvailable() const { return m_swapfile && m_swapfile->shouldRecover(); } void KTextEditor::DocumentPrivate::recoverData() { if (isDataRecoveryAvailable()) { m_swapfile->recover(); } } void KTextEditor::DocumentPrivate::discardDataRecovery() { if (isDataRecoveryAvailable()) { m_swapfile->discard(); } } void KTextEditor::DocumentPrivate::setReadWrite(bool rw) { if (isReadWrite() == rw) { return; } KParts::ReadWritePart::setReadWrite(rw); foreach (KTextEditor::ViewPrivate *view, m_views) { view->slotUpdateUndo(); view->slotReadWriteChanged(); } emit readWriteChanged(this); } void KTextEditor::DocumentPrivate::setModified(bool m) { if (isModified() != m) { KParts::ReadWritePart::setModified(m); foreach (KTextEditor::ViewPrivate *view, m_views) { view->slotUpdateUndo(); } emit modifiedChanged(this); } m_undoManager->setModified(m); } //END //BEGIN Kate specific stuff ;) void KTextEditor::DocumentPrivate::makeAttribs(bool needInvalidate) { foreach (KTextEditor::ViewPrivate *view, m_views) { view->renderer()->updateAttributes(); } if (needInvalidate) { m_buffer->invalidateHighlighting(); } foreach (KTextEditor::ViewPrivate *view, m_views) { view->tagAll(); view->updateView(true); } } // the attributes of a hl have changed, update void KTextEditor::DocumentPrivate::internalHlChanged() { makeAttribs(); } void KTextEditor::DocumentPrivate::addView(KTextEditor::View *view) { Q_ASSERT (!m_views.contains(view)); m_views.insert(view, static_cast(view)); m_viewsCache.append(view); // apply the view & renderer vars from the file type if (!m_fileType.isEmpty()) { readVariableLine(KTextEditor::EditorPrivate::self()->modeManager()->fileType(m_fileType).varLine, true); } // apply the view & renderer vars from the file readVariables(true); setActiveView(view); } void KTextEditor::DocumentPrivate::removeView(KTextEditor::View *view) { Q_ASSERT (m_views.contains(view)); m_views.remove(view); m_viewsCache.removeAll(view); if (activeView() == view) { setActiveView(nullptr); } } void KTextEditor::DocumentPrivate::setActiveView(KTextEditor::View *view) { if (m_activeView == view) { return; } m_activeView = static_cast(view); } bool KTextEditor::DocumentPrivate::ownedView(KTextEditor::ViewPrivate *view) { // do we own the given view? return (m_views.contains(view)); } int KTextEditor::DocumentPrivate::toVirtualColumn(int line, int column) const { Kate::TextLine textLine = m_buffer->plainLine(line); if (textLine) { return textLine->toVirtualColumn(column, config()->tabWidth()); } else { return 0; } } int KTextEditor::DocumentPrivate::toVirtualColumn(const KTextEditor::Cursor &cursor) const { return toVirtualColumn(cursor.line(), cursor.column()); } int KTextEditor::DocumentPrivate::fromVirtualColumn(int line, int column) const { Kate::TextLine textLine = m_buffer->plainLine(line); if (textLine) { return textLine->fromVirtualColumn(column, config()->tabWidth()); } else { return 0; } } int KTextEditor::DocumentPrivate::fromVirtualColumn(const KTextEditor::Cursor &cursor) const { return fromVirtualColumn(cursor.line(), cursor.column()); } bool KTextEditor::DocumentPrivate::typeChars(KTextEditor::ViewPrivate *view, const QString &realChars) { /** * filter out non-printable chars (convert to utf-32 to support surrogate pairs) */ const auto realUcs4Chars = realChars.toUcs4(); QVector ucs4Chars; Q_FOREACH (auto c, realUcs4Chars) if (QChar::isPrint(c) || c == QChar::fromLatin1('\t') || c == QChar::fromLatin1('\n') || c == QChar::fromLatin1('\r')) { ucs4Chars.append(c); } QString chars = QString::fromUcs4(ucs4Chars.data(), ucs4Chars.size()); /** * no printable chars => nothing to insert! */ if (chars.isEmpty()) { return false; } // Check if entered closing bracket is already balanced const QChar typedChar = chars.at(0); const QChar openBracket = matchingStartBracket(typedChar); if (!openBracket.isNull()) { KTextEditor::Cursor curPos = view->cursorPosition(); if ((characterAt(curPos) == typedChar) && findMatchingBracket(curPos, 123/*Which value may best?*/).isValid()) { // Do nothing view->cursorRight(); return true; } } /** * auto bracket handling for newly inserted text * remember if we should auto add some */ QChar closingBracket; if (view->config()->autoBrackets() && chars.size() == 1) { const QChar typedChar = chars.at(0); /** * we inserted a bracket? * => remember the matching closing one */ closingBracket = matchingEndBracket(typedChar); /** * closing bracket for the autobracket we inserted earlier? */ if (m_currentAutobraceClosingChar == typedChar && m_currentAutobraceRange) { // do nothing m_currentAutobraceRange.reset(nullptr); view->cursorRight(); return true; } } editStart(); /** * special handling if we want to add auto brackets to a selection */ if (view->selection() && !closingBracket.isNull()) { std::unique_ptr selectionRange(newMovingRange(view->selectionRange())); const int startLine = qMax(0, selectionRange->start().line()); const int endLine = qMin(selectionRange->end().line(), lastLine()); const bool blockMode = view->blockSelection() && (startLine != endLine); if (blockMode) { if (selectionRange->start().column() > selectionRange->end().column()) { // Selection was done from right->left, requires special setting to ensure the new // added brackets will not be part of the selection selectionRange->setInsertBehaviors(MovingRange::ExpandLeft | MovingRange::ExpandRight); } // Add brackets to each line of the block const int startColumn = qMin(selectionRange->start().column(), selectionRange->end().column()); const int endColumn = qMax(selectionRange->start().column(), selectionRange->end().column()); const KTextEditor::Range workingRange(startLine, startColumn, endLine, endColumn); for (int line = startLine; line <= endLine; ++line) { const KTextEditor::Range r(rangeOnLine(workingRange, line)); insertText(r.end(), QString(closingBracket)); view->slotTextInserted(view, r.end(), QString(closingBracket)); insertText(r.start(), chars); view->slotTextInserted(view, r.start(), chars); } } else { // No block, just add to start & end of selection insertText(selectionRange->end(), QString(closingBracket)); view->slotTextInserted(view, selectionRange->end(), QString(closingBracket)); insertText(selectionRange->start(), chars); view->slotTextInserted(view, selectionRange->start(), chars); } // Refesh selection view->setSelection(selectionRange->toRange()); view->setCursorPosition(selectionRange->end()); editEnd(); return true; } /** * normal handling */ if (!view->config()->persistentSelection() && view->selection()) { view->removeSelectedText(); } const KTextEditor::Cursor oldCur(view->cursorPosition()); const bool multiLineBlockMode = view->blockSelection() && view->selection(); if (view->currentInputMode()->overwrite()) { // blockmode multiline selection case: remove chars in every line const KTextEditor::Range selectionRange = view->selectionRange(); const int startLine = multiLineBlockMode ? qMax(0, selectionRange.start().line()) : view->cursorPosition().line(); const int endLine = multiLineBlockMode ? qMin(selectionRange.end().line(), lastLine()) : startLine; const int virtualColumn = toVirtualColumn(multiLineBlockMode ? selectionRange.end() : view->cursorPosition()); for (int line = endLine; line >= startLine; --line) { Kate::TextLine textLine = m_buffer->plainLine(line); Q_ASSERT(textLine); const int column = fromVirtualColumn(line, virtualColumn); KTextEditor::Range r = KTextEditor::Range(KTextEditor::Cursor(line, column), qMin(chars.length(), textLine->length() - column)); // replace mode needs to know what was removed so it can be restored with backspace if (oldCur.column() < lineLength(line)) { QChar removed = characterAt(KTextEditor::Cursor(line, column)); view->currentInputMode()->overwrittenChar(removed); } removeText(r); } } chars = eventuallyReplaceTabs(view->cursorPosition(), chars); if (multiLineBlockMode) { KTextEditor::Range selectionRange = view->selectionRange(); const int startLine = qMax(0, selectionRange.start().line()); const int endLine = qMin(selectionRange.end().line(), lastLine()); const int column = toVirtualColumn(selectionRange.end()); for (int line = endLine; line >= startLine; --line) { editInsertText(line, fromVirtualColumn(line, column), chars); } int newSelectionColumn = toVirtualColumn(view->cursorPosition()); selectionRange.setRange(KTextEditor::Cursor(selectionRange.start().line(), fromVirtualColumn(selectionRange.start().line(), newSelectionColumn)) , KTextEditor::Cursor(selectionRange.end().line(), fromVirtualColumn(selectionRange.end().line(), newSelectionColumn))); view->setSelection(selectionRange); } else { insertText(view->cursorPosition(), chars); } /** * auto bracket handling for newly inserted text * we inserted a bracket? * => add the matching closing one to the view + input chars * try to preserve the cursor position */ bool skipAutobrace = closingBracket == QLatin1Char('\''); if (highlight() && skipAutobrace) { // skip adding ' in spellchecked areas, because those are text skipAutobrace = highlight()->spellCheckingRequiredForLocation(this, view->cursorPosition() - Cursor{0, 1}); } const auto cursorPos(view->cursorPosition()); if (!skipAutobrace && (closingBracket == QLatin1Char('\''))) { // skip auto quotes when these looks already balanced, bug 405089 Kate::TextLine textLine = m_buffer->plainLine(cursorPos.line()); // RegEx match quote, but not excaped quote, thanks to https://stackoverflow.com/a/11819111 const int count = textLine->text().left(cursorPos.column()).count(QRegularExpression(QStringLiteral("(?plainLine(cursorPos.line()); const int count = textLine->text().left(cursorPos.column()).count(QRegularExpression(QStringLiteral("(?document()->text({cursorPos, cursorPos + Cursor{0, 1}}).trimmed(); if (nextChar.isEmpty() || !nextChar.at(0).isLetterOrNumber()) { insertText(view->cursorPosition(), QString(closingBracket)); const auto insertedAt(view->cursorPosition()); view->setCursorPosition(cursorPos); m_currentAutobraceRange.reset(newMovingRange({cursorPos - Cursor{0, 1}, insertedAt}, KTextEditor::MovingRange::DoNotExpand)); connect(view, &View::cursorPositionChanged, this, &DocumentPrivate::checkCursorForAutobrace, Qt::UniqueConnection); // add bracket to chars inserted! needed for correct signals + indent chars.append(closingBracket); } m_currentAutobraceClosingChar = closingBracket; } // end edit session here, to have updated HL in userTypedChar! editEnd(); // trigger indentation KTextEditor::Cursor b(view->cursorPosition()); m_indenter->userTypedChar(view, b, chars.isEmpty() ? QChar() : chars.at(chars.length() - 1)); /** * inform the view about the original inserted chars */ view->slotTextInserted(view, oldCur, chars); return true; } void KTextEditor::DocumentPrivate::checkCursorForAutobrace(KTextEditor::View*, const KTextEditor::Cursor& newPos) { if ( m_currentAutobraceRange && ! m_currentAutobraceRange->toRange().contains(newPos) ) { m_currentAutobraceRange.clear(); } } void KTextEditor::DocumentPrivate::newLine(KTextEditor::ViewPrivate *v) { editStart(); if (!v->config()->persistentSelection() && v->selection()) { v->removeSelectedText(); v->clearSelection(); } // query cursor position KTextEditor::Cursor c = v->cursorPosition(); if (c.line() > lastLine()) { c.setLine(lastLine()); } if (c.line() < 0) { c.setLine(0); } int ln = c.line(); Kate::TextLine textLine = plainKateTextLine(ln); if (c.column() > textLine->length()) { c.setColumn(textLine->length()); } // first: wrap line editWrapLine(c.line(), c.column()); // end edit session here, to have updated HL in userTypedChar! editEnd(); // second: indent the new line, if needed... m_indenter->userTypedChar(v, v->cursorPosition(), QLatin1Char('\n')); } void KTextEditor::DocumentPrivate::transpose(const KTextEditor::Cursor &cursor) { Kate::TextLine textLine = m_buffer->plainLine(cursor.line()); if (!textLine || (textLine->length() < 2)) { return; } uint col = cursor.column(); if (col > 0) { col--; } if ((textLine->length() - col) < 2) { return; } uint line = cursor.line(); QString s; //clever swap code if first character on the line swap right&left //otherwise left & right s.append(textLine->at(col + 1)); s.append(textLine->at(col)); //do the swap // do it right, never ever manipulate a textline editStart(); editRemoveText(line, col, 2); editInsertText(line, col, s); editEnd(); } void KTextEditor::DocumentPrivate::backspace(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &c) { if (!view->config()->persistentSelection() && view->selection()) { if (view->blockSelection() && view->selection() && toVirtualColumn(view->selectionRange().start()) == toVirtualColumn(view->selectionRange().end())) { // Remove one character after selection line KTextEditor::Range range = view->selectionRange(); range.setStart(KTextEditor::Cursor(range.start().line(), range.start().column() - 1)); view->setSelection(range); } view->removeSelectedText(); return; } uint col = qMax(c.column(), 0); uint line = qMax(c.line(), 0); if ((col == 0) && (line == 0)) { return; } if (col > 0) { bool useNextBlock = false; if (config()->backspaceIndents()) { // backspace indents: erase to next indent position Kate::TextLine textLine = m_buffer->plainLine(line); // don't forget this check!!!! really!!!! if (!textLine) { return; } int colX = textLine->toVirtualColumn(col, config()->tabWidth()); int pos = textLine->firstChar(); if (pos > 0) { pos = textLine->toVirtualColumn(pos, config()->tabWidth()); } if (pos < 0 || pos >= (int)colX) { // only spaces on left side of cursor indent(KTextEditor::Range(line, 0, line, 0), -1); } else { useNextBlock = true; } } if (!config()->backspaceIndents() || useNextBlock) { KTextEditor::Cursor beginCursor(line, 0); KTextEditor::Cursor endCursor(line, col); if (!view->config()->backspaceRemoveComposed()) { // Normal backspace behavior beginCursor.setColumn(col - 1); // move to left of surrogate pair if (!isValidTextPosition(beginCursor)) { Q_ASSERT(col >= 2); beginCursor.setColumn(col - 2); } } else { beginCursor.setColumn(view->textLayout(c)->previousCursorPosition(c.column())); } removeText(KTextEditor::Range(beginCursor, endCursor)); // in most cases cursor is moved by removeText, but we should do it manually // for past-end-of-line cursors in block mode view->setCursorPosition(beginCursor); } } else { // col == 0: wrap to previous line if (line >= 1) { Kate::TextLine textLine = m_buffer->plainLine(line - 1); // don't forget this check!!!! really!!!! if (!textLine) { return; } if (config()->wordWrap() && textLine->endsWith(QLatin1String(" "))) { // gg: in hard wordwrap mode, backspace must also eat the trailing space removeText(KTextEditor::Range(line - 1, textLine->length() - 1, line, 0)); } else { removeText(KTextEditor::Range(line - 1, textLine->length(), line, 0)); } } } if ( m_currentAutobraceRange ) { const auto r = m_currentAutobraceRange->toRange(); if ( r.columnWidth() == 1 && view->cursorPosition() == r.start() ) { // start parenthesis removed and range length is 1, remove end as well del(view, view->cursorPosition()); m_currentAutobraceRange.clear(); } } } void KTextEditor::DocumentPrivate::del(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &c) { if (!view->config()->persistentSelection() && view->selection()) { if (view->blockSelection() && view->selection() && toVirtualColumn(view->selectionRange().start()) == toVirtualColumn(view->selectionRange().end())) { // Remove one character after selection line KTextEditor::Range range = view->selectionRange(); range.setEnd(KTextEditor::Cursor(range.end().line(), range.end().column() + 1)); view->setSelection(range); } view->removeSelectedText(); return; } if (c.column() < (int) m_buffer->plainLine(c.line())->length()) { KTextEditor::Cursor endCursor(c.line(), view->textLayout(c)->nextCursorPosition(c.column())); removeText(KTextEditor::Range(c, endCursor)); } else if (c.line() < lastLine()) { removeText(KTextEditor::Range(c.line(), c.column(), c.line() + 1, 0)); } } void KTextEditor::DocumentPrivate::paste(KTextEditor::ViewPrivate *view, const QString &text) { static const QChar newLineChar(QLatin1Char('\n')); QString s = text; if (s.isEmpty()) { return; } int lines = s.count(newLineChar); m_undoManager->undoSafePoint(); editStart(); KTextEditor::Cursor pos = view->cursorPosition(); if (!view->config()->persistentSelection() && view->selection()) { pos = view->selectionRange().start(); if (view->blockSelection()) { pos = rangeOnLine(view->selectionRange(), pos.line()).start(); if (lines == 0) { s += newLineChar; s = s.repeated(view->selectionRange().numberOfLines() + 1); s.chop(1); } } view->removeSelectedText(); } if (config()->ovr()) { QStringList pasteLines = s.split(newLineChar); if (!view->blockSelection()) { int endColumn = (pasteLines.count() == 1 ? pos.column() : 0) + pasteLines.last().length(); removeText(KTextEditor::Range(pos, pos.line() + pasteLines.count() - 1, endColumn)); } else { int maxi = qMin(pos.line() + pasteLines.count(), this->lines()); for (int i = pos.line(); i < maxi; ++i) { int pasteLength = pasteLines.at(i - pos.line()).length(); removeText(KTextEditor::Range(i, pos.column(), i, qMin(pasteLength + pos.column(), lineLength(i)))); } } } insertText(pos, s, view->blockSelection()); editEnd(); // move cursor right for block select, as the user is moved right internal // even in that case, but user expects other behavior in block selection // mode ! // just let cursor stay, that was it before I changed to moving ranges! if (view->blockSelection()) { view->setCursorPositionInternal(pos); } if (config()->indentPastedText()) { KTextEditor::Range range = KTextEditor::Range(KTextEditor::Cursor(pos.line(), 0), KTextEditor::Cursor(pos.line() + lines, 0)); m_indenter->indent(view, range); } if (!view->blockSelection()) { emit charactersSemiInteractivelyInserted(pos, s); } m_undoManager->undoSafePoint(); } void KTextEditor::DocumentPrivate::indent(KTextEditor::Range range, int change) { if (!isReadWrite()) { return; } editStart(); m_indenter->changeIndent(range, change); editEnd(); } void KTextEditor::DocumentPrivate::align(KTextEditor::ViewPrivate *view, const KTextEditor::Range &range) { m_indenter->indent(view, range); } void KTextEditor::DocumentPrivate::insertTab(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &) { if (!isReadWrite()) { return; } int lineLen = line(view->cursorPosition().line()).length(); KTextEditor::Cursor c = view->cursorPosition(); editStart(); if (!view->config()->persistentSelection() && view->selection()) { view->removeSelectedText(); } else if (view->currentInputMode()->overwrite() && c.column() < lineLen) { KTextEditor::Range r = KTextEditor::Range(view->cursorPosition(), 1); // replace mode needs to know what was removed so it can be restored with backspace QChar removed = line(view->cursorPosition().line()).at(r.start().column()); view->currentInputMode()->overwrittenChar(removed); removeText(r); } c = view->cursorPosition(); editInsertText(c.line(), c.column(), QStringLiteral("\t")); editEnd(); } /* Remove a given string at the beginning of the current line. */ bool KTextEditor::DocumentPrivate::removeStringFromBeginning(int line, const QString &str) { Kate::TextLine textline = m_buffer->plainLine(line); KTextEditor::Cursor cursor(line, 0); bool there = textline->startsWith(str); if (!there) { cursor.setColumn(textline->firstChar()); there = textline->matchesAt(cursor.column(), str); } if (there) { // Remove some chars removeText(KTextEditor::Range(cursor, str.length())); } return there; } /* Remove a given string at the end of the current line. */ bool KTextEditor::DocumentPrivate::removeStringFromEnd(int line, const QString &str) { Kate::TextLine textline = m_buffer->plainLine(line); KTextEditor::Cursor cursor(line, 0); bool there = textline->endsWith(str); if (there) { cursor.setColumn(textline->length() - str.length()); } else { cursor.setColumn(textline->lastChar() - str.length() + 1); there = textline->matchesAt(cursor.column(), str); } if (there) { // Remove some chars removeText(KTextEditor::Range(cursor, str.length())); } return there; } /* Replace tabs by spaces in the given string, if enabled. */ QString KTextEditor::DocumentPrivate::eventuallyReplaceTabs(const KTextEditor::Cursor &cursorPos, const QString &str) const { const bool replacetabs = config()->replaceTabsDyn(); if ( ! replacetabs ) { return str; } const int indentWidth = config()->indentationWidth(); static const QLatin1Char tabChar('\t'); int column = cursorPos.column(); // The result will always be at least as long as the input QString result; result.reserve(str.size()); Q_FOREACH (const QChar ch, str) { if (ch == tabChar) { // Insert only enough spaces to align to the next indentWidth column // This fixes bug #340212 int spacesToInsert = indentWidth - (column % indentWidth); result += QStringLiteral(" ").repeated(spacesToInsert); column += spacesToInsert; } else { // Just keep all other typed characters as-is result += ch; ++column; } } return result; } /* Add to the current line a comment line mark at the beginning. */ void KTextEditor::DocumentPrivate::addStartLineCommentToSingleLine(int line, int attrib) { QString commentLineMark = highlight()->getCommentSingleLineStart(attrib); int pos = -1; if (highlight()->getCommentSingleLinePosition(attrib) == KSyntaxHighlighting::CommentPosition::StartOfLine) { pos = 0; commentLineMark += QLatin1Char(' '); } else { const Kate::TextLine l = kateTextLine(line); pos = l->firstChar(); } if (pos >= 0) { insertText(KTextEditor::Cursor(line, pos), commentLineMark); } } /* Remove from the current line a comment line mark at the beginning if there is one. */ bool KTextEditor::DocumentPrivate::removeStartLineCommentFromSingleLine(int line, int attrib) { const QString shortCommentMark = highlight()->getCommentSingleLineStart(attrib); const QString longCommentMark = shortCommentMark + QLatin1Char(' '); editStart(); // Try to remove the long comment mark first bool removed = (removeStringFromBeginning(line, longCommentMark) || removeStringFromBeginning(line, shortCommentMark)); editEnd(); return removed; } /* Add to the current line a start comment mark at the beginning and a stop comment mark at the end. */ void KTextEditor::DocumentPrivate::addStartStopCommentToSingleLine(int line, int attrib) { const QString startCommentMark = highlight()->getCommentStart(attrib) + QLatin1Char(' '); const QString stopCommentMark = QLatin1Char(' ') + highlight()->getCommentEnd(attrib); editStart(); // Add the start comment mark insertText(KTextEditor::Cursor(line, 0), startCommentMark); // Go to the end of the line const int col = m_buffer->plainLine(line)->length(); // Add the stop comment mark insertText(KTextEditor::Cursor(line, col), stopCommentMark); editEnd(); } /* Remove from the current line a start comment mark at the beginning and a stop comment mark at the end. */ bool KTextEditor::DocumentPrivate::removeStartStopCommentFromSingleLine(int line, int attrib) { const QString shortStartCommentMark = highlight()->getCommentStart(attrib); const QString longStartCommentMark = shortStartCommentMark + QLatin1Char(' '); const QString shortStopCommentMark = highlight()->getCommentEnd(attrib); const QString longStopCommentMark = QLatin1Char(' ') + shortStopCommentMark; editStart(); // Try to remove the long start comment mark first const bool removedStart = (removeStringFromBeginning(line, longStartCommentMark) || removeStringFromBeginning(line, shortStartCommentMark)); // Try to remove the long stop comment mark first const bool removedStop = removedStart && (removeStringFromEnd(line, longStopCommentMark) || removeStringFromEnd(line, shortStopCommentMark)); editEnd(); return (removedStart || removedStop); } /* Add to the current selection a start comment mark at the beginning and a stop comment mark at the end. */ void KTextEditor::DocumentPrivate::addStartStopCommentToSelection(KTextEditor::ViewPrivate *view, int attrib) { const QString startComment = highlight()->getCommentStart(attrib); const QString endComment = highlight()->getCommentEnd(attrib); KTextEditor::Range range = view->selectionRange(); if ((range.end().column() == 0) && (range.end().line() > 0)) { range.setEnd(KTextEditor::Cursor(range.end().line() - 1, lineLength(range.end().line() - 1))); } editStart(); if (!view->blockSelection()) { insertText(range.end(), endComment); insertText(range.start(), startComment); } else { for (int line = range.start().line(); line <= range.end().line(); line++) { KTextEditor::Range subRange = rangeOnLine(range, line); insertText(subRange.end(), endComment); insertText(subRange.start(), startComment); } } editEnd(); // selection automatically updated (MovingRange) } /* Add to the current selection a comment line mark at the beginning of each line. */ void KTextEditor::DocumentPrivate::addStartLineCommentToSelection(KTextEditor::ViewPrivate *view, int attrib) { //const QString commentLineMark = highlight()->getCommentSingleLineStart(attrib) + QLatin1Char(' '); int sl = view->selectionRange().start().line(); int el = view->selectionRange().end().line(); // if end of selection is in column 0 in last line, omit the last line if ((view->selectionRange().end().column() == 0) && (el > 0)) { el--; } editStart(); // For each line of the selection for (int z = el; z >= sl; z--) { //insertText (z, 0, commentLineMark); addStartLineCommentToSingleLine(z, attrib); } editEnd(); // selection automatically updated (MovingRange) } bool KTextEditor::DocumentPrivate::nextNonSpaceCharPos(int &line, int &col) { for (; line < (int)m_buffer->count(); line++) { Kate::TextLine textLine = m_buffer->plainLine(line); if (!textLine) { break; } col = textLine->nextNonSpaceChar(col); if (col != -1) { return true; // Next non-space char found } col = 0; } // No non-space char found line = -1; col = -1; return false; } bool KTextEditor::DocumentPrivate::previousNonSpaceCharPos(int &line, int &col) { while (true) { Kate::TextLine textLine = m_buffer->plainLine(line); if (!textLine) { break; } col = textLine->previousNonSpaceChar(col); if (col != -1) { return true; } if (line == 0) { return false; } --line; col = textLine->length(); } // No non-space char found line = -1; col = -1; return false; } /* Remove from the selection a start comment mark at the beginning and a stop comment mark at the end. */ bool KTextEditor::DocumentPrivate::removeStartStopCommentFromSelection(KTextEditor::ViewPrivate *view, int attrib) { const QString startComment = highlight()->getCommentStart(attrib); const QString endComment = highlight()->getCommentEnd(attrib); int sl = qMax (0, view->selectionRange().start().line()); int el = qMin (view->selectionRange().end().line(), lastLine()); int sc = view->selectionRange().start().column(); int ec = view->selectionRange().end().column(); // The selection ends on the char before selectEnd if (ec != 0) { --ec; } else if (el > 0) { --el; ec = m_buffer->plainLine(el)->length() - 1; } const int startCommentLen = startComment.length(); const int endCommentLen = endComment.length(); // had this been perl or sed: s/^\s*$startComment(.+?)$endComment\s*/$2/ bool remove = nextNonSpaceCharPos(sl, sc) && m_buffer->plainLine(sl)->matchesAt(sc, startComment) && previousNonSpaceCharPos(el, ec) && ((ec - endCommentLen + 1) >= 0) && m_buffer->plainLine(el)->matchesAt(ec - endCommentLen + 1, endComment); if (remove) { editStart(); removeText(KTextEditor::Range(el, ec - endCommentLen + 1, el, ec + 1)); removeText(KTextEditor::Range(sl, sc, sl, sc + startCommentLen)); editEnd(); // selection automatically updated (MovingRange) } return remove; } bool KTextEditor::DocumentPrivate::removeStartStopCommentFromRegion(const KTextEditor::Cursor &start, const KTextEditor::Cursor &end, int attrib) { const QString startComment = highlight()->getCommentStart(attrib); const QString endComment = highlight()->getCommentEnd(attrib); const int startCommentLen = startComment.length(); const int endCommentLen = endComment.length(); const bool remove = m_buffer->plainLine(start.line())->matchesAt(start.column(), startComment) && m_buffer->plainLine(end.line())->matchesAt(end.column() - endCommentLen, endComment); if (remove) { editStart(); removeText(KTextEditor::Range(end.line(), end.column() - endCommentLen, end.line(), end.column())); removeText(KTextEditor::Range(start, startCommentLen)); editEnd(); } return remove; } /* Remove from the beginning of each line of the selection a start comment line mark. */ bool KTextEditor::DocumentPrivate::removeStartLineCommentFromSelection(KTextEditor::ViewPrivate *view, int attrib) { const QString shortCommentMark = highlight()->getCommentSingleLineStart(attrib); const QString longCommentMark = shortCommentMark + QLatin1Char(' '); int sl = view->selectionRange().start().line(); int el = view->selectionRange().end().line(); if ((view->selectionRange().end().column() == 0) && (el > 0)) { el--; } bool removed = false; editStart(); // For each line of the selection for (int z = el; z >= sl; z--) { // Try to remove the long comment mark first removed = (removeStringFromBeginning(z, longCommentMark) || removeStringFromBeginning(z, shortCommentMark) || removed); } editEnd(); // selection automatically updated (MovingRange) return removed; } /* Comment or uncomment the selection or the current line if there is no selection. */ void KTextEditor::DocumentPrivate::comment(KTextEditor::ViewPrivate *v, uint line, uint column, int change) { // skip word wrap bug #105373 const bool skipWordWrap = wordWrap(); if (skipWordWrap) { setWordWrap(false); } bool hassel = v->selection(); int c = 0; if (hassel) { c = v->selectionRange().start().column(); } int startAttrib = 0; Kate::TextLine ln = kateTextLine(line); if (c < ln->length()) { startAttrib = ln->attribute(c); } else if (!ln->attributesList().isEmpty()) { startAttrib = ln->attributesList().back().attributeValue; } bool hasStartLineCommentMark = !(highlight()->getCommentSingleLineStart(startAttrib).isEmpty()); bool hasStartStopCommentMark = (!(highlight()->getCommentStart(startAttrib).isEmpty()) && !(highlight()->getCommentEnd(startAttrib).isEmpty())); if (change > 0) { // comment if (!hassel) { if (hasStartLineCommentMark) { addStartLineCommentToSingleLine(line, startAttrib); } else if (hasStartStopCommentMark) { addStartStopCommentToSingleLine(line, startAttrib); } } else { // anders: prefer single line comment to avoid nesting probs // If the selection starts after first char in the first line // or ends before the last char of the last line, we may use // multiline comment markers. // TODO We should try to detect nesting. // - if selection ends at col 0, most likely she wanted that // line ignored const KTextEditor::Range sel = v->selectionRange(); if (hasStartStopCommentMark && (!hasStartLineCommentMark || ( (sel.start().column() > m_buffer->plainLine(sel.start().line())->firstChar()) || (sel.end().column() > 0 && sel.end().column() < (m_buffer->plainLine(sel.end().line())->length())) ))) { addStartStopCommentToSelection(v, startAttrib); } else if (hasStartLineCommentMark) { addStartLineCommentToSelection(v, startAttrib); } } } else { // uncomment bool removed = false; if (!hassel) { removed = (hasStartLineCommentMark && removeStartLineCommentFromSingleLine(line, startAttrib)) || (hasStartStopCommentMark && removeStartStopCommentFromSingleLine(line, startAttrib)); } else { // anders: this seems like it will work with above changes :) removed = (hasStartStopCommentMark && removeStartStopCommentFromSelection(v, startAttrib)) || (hasStartLineCommentMark && removeStartLineCommentFromSelection(v, startAttrib)); } // recursive call for toggle comment if (!removed && change == 0) { comment(v, line, column, 1); } } if (skipWordWrap) { setWordWrap(true); // see begin of function ::comment (bug #105373) } } void KTextEditor::DocumentPrivate::transform(KTextEditor::ViewPrivate *v, const KTextEditor::Cursor &c, KTextEditor::DocumentPrivate::TextTransform t) { if (v->selection()) { editStart(); // cache the selection and cursor, so we can be sure to restore. KTextEditor::Range selection = v->selectionRange(); KTextEditor::Range range(selection.start(), 0); while (range.start().line() <= selection.end().line()) { int start = 0; int end = lineLength(range.start().line()); if (range.start().line() == selection.start().line() || v->blockSelection()) { start = selection.start().column(); } if (range.start().line() == selection.end().line() || v->blockSelection()) { end = selection.end().column(); } if (start > end) { int swapCol = start; start = end; end = swapCol; } range.setStart(KTextEditor::Cursor(range.start().line(), start)); range.setEnd(KTextEditor::Cursor(range.end().line(), end)); QString s = text(range); QString old = s; if (t == Uppercase) { s = s.toUpper(); } else if (t == Lowercase) { s = s.toLower(); } else { // Capitalize Kate::TextLine l = m_buffer->plainLine(range.start().line()); int p(0); while (p < s.length()) { // If bol or the character before is not in a word, up this one: // 1. if both start and p is 0, upper char. // 2. if blockselect or first line, and p == 0 and start-1 is not in a word, upper // 3. if p-1 is not in a word, upper. if ((! range.start().column() && ! p) || ((range.start().line() == selection.start().line() || v->blockSelection()) && ! p && ! highlight()->isInWord(l->at(range.start().column() - 1))) || (p && ! highlight()->isInWord(s.at(p - 1))) ) { s[p] = s.at(p).toUpper(); } p++; } } if (s != old) { removeText(range); insertText(range.start(), s); } range.setBothLines(range.start().line() + 1); } editEnd(); // restore selection & cursor v->setSelection(selection); v->setCursorPosition(c); } else { // no selection editStart(); // get cursor KTextEditor::Cursor cursor = c; QString old = text(KTextEditor::Range(cursor, 1)); QString s; switch (t) { case Uppercase: s = old.toUpper(); break; case Lowercase: s = old.toLower(); break; case Capitalize: { Kate::TextLine l = m_buffer->plainLine(cursor.line()); while (cursor.column() > 0 && highlight()->isInWord(l->at(cursor.column() - 1), l->attribute(cursor.column() - 1))) { cursor.setColumn(cursor.column() - 1); } old = text(KTextEditor::Range(cursor, 1)); s = old.toUpper(); } break; default: break; } removeText(KTextEditor::Range(cursor, 1)); insertText(cursor, s); editEnd(); } } void KTextEditor::DocumentPrivate::joinLines(uint first, uint last) { // if ( first == last ) last += 1; editStart(); int line(first); while (first < last) { // Normalize the whitespace in the joined lines by making sure there's // always exactly one space between the joined lines // This cannot be done in editUnwrapLine, because we do NOT want this // behavior when deleting from the start of a line, just when explicitly // calling the join command Kate::TextLine l = kateTextLine(line); Kate::TextLine tl = kateTextLine(line + 1); if (!l || !tl) { editEnd(); return; } int pos = tl->firstChar(); if (pos >= 0) { if (pos != 0) { editRemoveText(line + 1, 0, pos); } if (!(l->length() == 0 || l->at(l->length() - 1).isSpace())) { editInsertText(line + 1, 0, QLatin1String(" ")); } } else { // Just remove the whitespace and let Kate handle the rest editRemoveText(line + 1, 0, tl->length()); } editUnWrapLine(line); first++; } editEnd(); } void KTextEditor::DocumentPrivate::tagLines(int start, int end) { foreach (KTextEditor::ViewPrivate *view, m_views) { view->tagLines(start, end, true); } } void KTextEditor::DocumentPrivate::repaintViews(bool paintOnlyDirty) { foreach (KTextEditor::ViewPrivate *view, m_views) { view->repaintText(paintOnlyDirty); } } /* Bracket matching uses the following algorithm: If in overwrite mode, match the bracket currently underneath the cursor. Otherwise, if the character to the left is a bracket, match it. Otherwise if the character to the right of the cursor is a bracket, match it. Otherwise, don't match anything. */ KTextEditor::Range KTextEditor::DocumentPrivate::findMatchingBracket(const KTextEditor::Cursor &start, int maxLines) { if (maxLines < 0) { return KTextEditor::Range::invalid(); } Kate::TextLine textLine = m_buffer->plainLine(start.line()); if (!textLine) { return KTextEditor::Range::invalid(); } KTextEditor::Range range(start, start); const QChar right = textLine->at(range.start().column()); const QChar left = textLine->at(range.start().column() - 1); QChar bracket; if (config()->ovr()) { if (isBracket(right)) { bracket = right; } else { return KTextEditor::Range::invalid(); } } else if (isBracket(right)) { bracket = right; } else if (isBracket(left)) { range.setStart(KTextEditor::Cursor(range.start().line(), range.start().column() - 1)); bracket = left; } else { return KTextEditor::Range::invalid(); } const QChar opposite = matchingBracket(bracket); if (opposite.isNull()) { return KTextEditor::Range::invalid(); } const int searchDir = isStartBracket(bracket) ? 1 : -1; uint nesting = 0; const int minLine = qMax(range.start().line() - maxLines, 0); const int maxLine = qMin(range.start().line() + maxLines, documentEnd().line()); range.setEnd(range.start()); KTextEditor::DocumentCursor cursor(this); cursor.setPosition(range.start()); int validAttr = kateTextLine(cursor.line())->attribute(cursor.column()); while (cursor.line() >= minLine && cursor.line() <= maxLine) { if (!cursor.move(searchDir)) { return KTextEditor::Range::invalid(); } Kate::TextLine textLine = kateTextLine(cursor.line()); if (textLine->attribute(cursor.column()) == validAttr) { // Check for match QChar c = textLine->at(cursor.column()); if (c == opposite) { if (nesting == 0) { if (searchDir > 0) { // forward range.setEnd(cursor.toCursor()); } else { range.setStart(cursor.toCursor()); } return range; } nesting--; } else if (c == bracket) { nesting++; } } } return KTextEditor::Range::invalid(); } // helper: remove \r and \n from visible document name (bug #170876) inline static QString removeNewLines(const QString &str) { QString tmp(str); return tmp.replace(QLatin1String("\r\n"), QLatin1String(" ")) .replace(QLatin1Char('\r'), QLatin1Char(' ')) .replace(QLatin1Char('\n'), QLatin1Char(' ')); } void KTextEditor::DocumentPrivate::updateDocName() { // if the name is set, and starts with FILENAME, it should not be changed! if (! url().isEmpty() && (m_docName == removeNewLines(url().fileName()) || m_docName.startsWith(removeNewLines(url().fileName()) + QLatin1String(" (")))) { return; } int count = -1; foreach (KTextEditor::DocumentPrivate *doc, KTextEditor::EditorPrivate::self()->kateDocuments()) { if ((doc != this) && (doc->url().fileName() == url().fileName())) if (doc->m_docNameNumber > count) { count = doc->m_docNameNumber; } } m_docNameNumber = count + 1; QString oldName = m_docName; m_docName = removeNewLines(url().fileName()); m_isUntitled = m_docName.isEmpty(); if (m_isUntitled) { m_docName = i18n("Untitled"); } if (m_docNameNumber > 0) { m_docName = QString(m_docName + QLatin1String(" (%1)")).arg(m_docNameNumber + 1); } /** * avoid to emit this, if name doesn't change! */ if (oldName != m_docName) { emit documentNameChanged(this); } } void KTextEditor::DocumentPrivate::slotModifiedOnDisk(KTextEditor::View * /*v*/) { if (url().isEmpty() || !m_modOnHd) { return; } if (!isModified() && isAutoReload()) { onModOnHdAutoReload(); return; } if (!m_fileChangedDialogsActivated || m_modOnHdHandler) { return; } // don't ask the user again and again the same thing if (m_modOnHdReason == m_prevModOnHdReason) { return; } m_prevModOnHdReason = m_modOnHdReason; m_modOnHdHandler = new KateModOnHdPrompt(this, m_modOnHdReason, reasonedMOHString()); connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::saveAsTriggered, this, &DocumentPrivate::onModOnHdSaveAs); connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::closeTriggered, this, &DocumentPrivate::onModOnHdClose); connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::reloadTriggered, this, &DocumentPrivate::onModOnHdReload); connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::autoReloadTriggered, this, &DocumentPrivate::onModOnHdAutoReload); connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::ignoreTriggered, this, &DocumentPrivate::onModOnHdIgnore); } void KTextEditor::DocumentPrivate::onModOnHdSaveAs() { m_modOnHd = false; QWidget *parentWidget(dialogParent()); const QUrl res = QFileDialog::getSaveFileUrl(parentWidget, i18n("Save File"), url(), {}, nullptr, QFileDialog::DontConfirmOverwrite); if (!res.isEmpty() && checkOverwrite(res, parentWidget)) { if (! saveAs(res)) { KMessageBox::error(parentWidget, i18n("Save failed")); m_modOnHd = true; } else { delete m_modOnHdHandler; m_prevModOnHdReason = OnDiskUnmodified; emit modifiedOnDisk(this, false, OnDiskUnmodified); } } else { // the save as dialog was canceled, we are still modified on disk m_modOnHd = true; } } void KTextEditor::DocumentPrivate::onModOnHdClose() { // avoid prompt in closeUrl() m_fileChangedDialogsActivated = false; // close the file without prompt confirmation closeUrl(); // Useful for kate only closeDocumentInApplication(); } void KTextEditor::DocumentPrivate::onModOnHdReload() { m_modOnHd = false; m_prevModOnHdReason = OnDiskUnmodified; emit modifiedOnDisk(this, false, OnDiskUnmodified); documentReload(); delete m_modOnHdHandler; } void KTextEditor::DocumentPrivate::autoReloadToggled(bool b) { m_autoReloadMode->setChecked(b); if (b) { connect(&m_modOnHdTimer, &QTimer::timeout, this, &DocumentPrivate::onModOnHdAutoReload); } else { disconnect(&m_modOnHdTimer, &QTimer::timeout, this, &DocumentPrivate::onModOnHdAutoReload); } } bool KTextEditor::DocumentPrivate::isAutoReload() { return m_autoReloadMode->isChecked(); } void KTextEditor::DocumentPrivate::delayAutoReload() { if (isAutoReload()) { m_autoReloadThrottle.start(); } } void KTextEditor::DocumentPrivate::onModOnHdAutoReload() { if (m_modOnHdHandler) { delete m_modOnHdHandler; autoReloadToggled(true); } if (!isAutoReload()) { return; } if (m_modOnHd && !m_reloading && !m_autoReloadThrottle.isActive()) { m_modOnHd = false; m_prevModOnHdReason = OnDiskUnmodified; emit modifiedOnDisk(this, false, OnDiskUnmodified); documentReload(); m_autoReloadThrottle.start(); } } void KTextEditor::DocumentPrivate::onModOnHdIgnore() { // ignore as long as m_prevModOnHdReason == m_modOnHdReason delete m_modOnHdHandler; } void KTextEditor::DocumentPrivate::setModifiedOnDisk(ModifiedOnDiskReason reason) { m_modOnHdReason = reason; m_modOnHd = (reason != OnDiskUnmodified); emit modifiedOnDisk(this, (reason != OnDiskUnmodified), reason); } class KateDocumentTmpMark { public: QString line; KTextEditor::Mark mark; }; void KTextEditor::DocumentPrivate::setModifiedOnDiskWarning(bool on) { m_fileChangedDialogsActivated = on; } bool KTextEditor::DocumentPrivate::documentReload() { if (url().isEmpty()) { return false; } // typically, the message for externally modified files is visible. Since it // does not make sense showing an additional dialog, just hide the message. delete m_modOnHdHandler; emit aboutToReload(this); QList tmp; for (QHash::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i) { KateDocumentTmpMark m; m.line = line(i.value()->line); m.mark = *i.value(); tmp.append(m); } const QString oldMode = mode(); const bool byUser = m_fileTypeSetByUser; const QString hl_mode = highlightingMode(); m_storedVariables.clear(); // save cursor positions for all views QHash cursorPositions; for (auto it = m_views.constBegin(); it != m_views.constEnd(); ++it) { auto v = it.value(); cursorPositions.insert(v, v->cursorPosition()); } m_reloading = true; KTextEditor::DocumentPrivate::openUrl(url()); // reset some flags only valid for one reload! m_userSetEncodingForNextReload = false; // restore cursor positions for all views for (auto it = m_views.constBegin(); it != m_views.constEnd(); ++it) { auto v = it.value(); setActiveView(v); v->setCursorPosition(cursorPositions.value(v)); if (v->isVisible()) { v->repaintText(false); } } for (int z = 0; z < tmp.size(); z++) { if (z < lines()) { if (line(tmp.at(z).mark.line) == tmp.at(z).line) { setMark(tmp.at(z).mark.line, tmp.at(z).mark.type); } } } if (byUser) { setMode(oldMode); } setHighlightingMode(hl_mode); emit reloaded(this); return true; } bool KTextEditor::DocumentPrivate::documentSave() { if (!url().isValid() || !isReadWrite()) { return documentSaveAs(); } return save(); } bool KTextEditor::DocumentPrivate::documentSaveAs() { const QUrl saveUrl = QFileDialog::getSaveFileUrl(dialogParent(), i18n("Save File"), url(), {}, nullptr, QFileDialog::DontConfirmOverwrite); if (saveUrl.isEmpty() || !checkOverwrite(saveUrl, dialogParent())) { return false; } return saveAs(saveUrl); } bool KTextEditor::DocumentPrivate::documentSaveAsWithEncoding(const QString &encoding) { const QUrl saveUrl = QFileDialog::getSaveFileUrl(dialogParent(), i18n("Save File"), url(), {}, nullptr, QFileDialog::DontConfirmOverwrite); if (saveUrl.isEmpty() || !checkOverwrite(saveUrl, dialogParent())) { return false; } setEncoding(encoding); return saveAs(saveUrl); } bool KTextEditor::DocumentPrivate::documentSaveCopyAs() { const QUrl saveUrl = QFileDialog::getSaveFileUrl(dialogParent(), i18n("Save Copy of File"), url(), {}, nullptr, QFileDialog::DontConfirmOverwrite); if (saveUrl.isEmpty() || !checkOverwrite(saveUrl, dialogParent())) { return false; } QTemporaryFile file; if (!file.open()) { return false; } if (!m_buffer->saveFile(file.fileName())) { KMessageBox::error(dialogParent(), i18n("The document could not be saved, as it was not possible to write to %1.\n\nCheck that you have write access to this file or that enough disk space is available.", this->url().toDisplayString(QUrl::PreferLocalFile))); return false; } // get the right permissions, start with safe default KIO::StatJob *statJob = KIO::stat(url(), KIO::StatJob::SourceSide, 2); KJobWidgets::setWindow(statJob, QApplication::activeWindow()); int permissions = -1; if (statJob->exec()) { permissions = KFileItem(statJob->statResult(), url()).permissions(); } // KIO move, important: allow overwrite, we checked above! KIO::FileCopyJob *job = KIO::file_copy(QUrl::fromLocalFile(file.fileName()), saveUrl, permissions, KIO::Overwrite); KJobWidgets::setWindow(job, QApplication::activeWindow()); return job->exec(); } void KTextEditor::DocumentPrivate::setWordWrap(bool on) { config()->setWordWrap(on); } bool KTextEditor::DocumentPrivate::wordWrap() const { return config()->wordWrap(); } void KTextEditor::DocumentPrivate::setWordWrapAt(uint col) { config()->setWordWrapAt(col); } unsigned int KTextEditor::DocumentPrivate::wordWrapAt() const { return config()->wordWrapAt(); } void KTextEditor::DocumentPrivate::setPageUpDownMovesCursor(bool on) { config()->setPageUpDownMovesCursor(on); } bool KTextEditor::DocumentPrivate::pageUpDownMovesCursor() const { return config()->pageUpDownMovesCursor(); } //END bool KTextEditor::DocumentPrivate::setEncoding(const QString &e) { return m_config->setEncoding(e); } QString KTextEditor::DocumentPrivate::encoding() const { return m_config->encoding(); } void KTextEditor::DocumentPrivate::updateConfig() { m_undoManager->updateConfig(); // switch indenter if needed and update config.... m_indenter->setMode(m_config->indentationMode()); m_indenter->updateConfig(); // set tab width there, too m_buffer->setTabWidth(config()->tabWidth()); // update all views, does tagAll and updateView... foreach (KTextEditor::ViewPrivate *view, m_views) { view->updateDocumentConfig(); } // update on-the-fly spell checking as spell checking defaults might have changes if (m_onTheFlyChecker) { m_onTheFlyChecker->updateConfig(); } emit configChanged(); } //BEGIN Variable reader // "local variable" feature by anders, 2003 /* TODO add config options (how many lines to read, on/off) add interface for plugins/apps to set/get variables add view stuff */ void KTextEditor::DocumentPrivate::readVariables(bool onlyViewAndRenderer) { if (!onlyViewAndRenderer) { m_config->configStart(); } // views! KTextEditor::ViewPrivate *v; foreach (v, m_views) { v->config()->configStart(); v->renderer()->config()->configStart(); } // read a number of lines in the top/bottom of the document for (int i = 0; i < qMin(9, lines()); ++i) { readVariableLine(line(i), onlyViewAndRenderer); } if (lines() > 10) { for (int i = qMax(10, lines() - 10); i < lines(); i++) { readVariableLine(line(i), onlyViewAndRenderer); } } if (!onlyViewAndRenderer) { m_config->configEnd(); } foreach (v, m_views) { v->config()->configEnd(); v->renderer()->config()->configEnd(); } } void KTextEditor::DocumentPrivate::readVariableLine(QString t, bool onlyViewAndRenderer) { static const QRegularExpression kvLine(QStringLiteral("kate:(.*)")); static const QRegularExpression kvLineWildcard(QStringLiteral("kate-wildcard\\((.*)\\):(.*)")); static const QRegularExpression kvLineMime(QStringLiteral("kate-mimetype\\((.*)\\):(.*)")); static const QRegularExpression kvVar(QStringLiteral("([\\w\\-]+)\\s+([^;]+)")); // simple check first, no regex // no kate inside, no vars, simple... if (!t.contains(QLatin1String("kate"))) { return; } // found vars, if any QString s; // now, try first the normal ones auto match = kvLine.match(t); if (match.hasMatch()) { s = match.captured(1); //qCDebug(LOG_KTE) << "normal variable line kate: matched: " << s; } else if ((match = kvLineWildcard.match(t)).hasMatch()) { // regex given const QStringList wildcards(match.captured(1).split(QLatin1Char(';'), QString::SkipEmptyParts)); const QString nameOfFile = url().fileName(); bool found = false; foreach (const QString &pattern, wildcards) { QRegExp wildcard(pattern, Qt::CaseSensitive, QRegExp::Wildcard); found = wildcard.exactMatch(nameOfFile); if (found) { break; } } // nothing usable found... if (!found) { return; } s = match.captured(2); //qCDebug(LOG_KTE) << "guarded variable line kate-wildcard: matched: " << s; } else if ((match = kvLineMime.match(t)).hasMatch()) { // mime-type given const QStringList types(match.captured(1).split(QLatin1Char(';'), QString::SkipEmptyParts)); // no matching type found if (!types.contains(mimeType())) { return; } s = match.captured(2); //qCDebug(LOG_KTE) << "guarded variable line kate-mimetype: matched: " << s; } else { // nothing found return; } // view variable names static const auto vvl = { QLatin1String("dynamic-word-wrap") , QLatin1String("dynamic-word-wrap-indicators") , QLatin1String("line-numbers") , QLatin1String("icon-border") , QLatin1String("folding-markers") , QLatin1String("folding-preview") , QLatin1String("bookmark-sorting") , QLatin1String("auto-center-lines") , QLatin1String("icon-bar-color") , QLatin1String("scrollbar-minimap") , QLatin1String("scrollbar-preview") // renderer , QLatin1String("background-color") , QLatin1String("selection-color") , QLatin1String("current-line-color") , QLatin1String("bracket-highlight-color") , QLatin1String("word-wrap-marker-color") , QLatin1String("font") , QLatin1String("font-size") , QLatin1String("scheme") }; int spaceIndent = -1; // for backward compatibility; see below bool replaceTabsSet = false; int startPos(0); QString var, val; while ((match = kvVar.match(s, startPos)).hasMatch()) { startPos = match.capturedEnd(0); var = match.captured(1); val = match.captured(2).trimmed(); bool state; // store booleans here int n; // store ints here // only apply view & renderer config stuff if (onlyViewAndRenderer) { if (contains(vvl, var)) { // FIXME define above setViewVariable(var, val); } } else { // BOOL SETTINGS if (var == QLatin1String("word-wrap") && checkBoolValue(val, &state)) { setWordWrap(state); // ??? FIXME CHECK } // KateConfig::configFlags // FIXME should this be optimized to only a few calls? how? else if (var == QLatin1String("backspace-indents") && checkBoolValue(val, &state)) { m_config->setBackspaceIndents(state); } else if (var == QLatin1String("indent-pasted-text") && checkBoolValue(val, &state)) { m_config->setIndentPastedText(state); } else if (var == QLatin1String("replace-tabs") && checkBoolValue(val, &state)) { m_config->setReplaceTabsDyn(state); replaceTabsSet = true; // for backward compatibility; see below } else if (var == QLatin1String("remove-trailing-space") && checkBoolValue(val, &state)) { qCWarning(LOG_KTE) << i18n("Using deprecated modeline 'remove-trailing-space'. " "Please replace with 'remove-trailing-spaces modified;', see " "https://docs.kde.org/stable5/en/applications/katepart/config-variables.html#variable-remove-trailing-spaces"); m_config->setRemoveSpaces(state ? 1 : 0); } else if (var == QLatin1String("replace-trailing-space-save") && checkBoolValue(val, &state)) { qCWarning(LOG_KTE) << i18n("Using deprecated modeline 'replace-trailing-space-save'. " "Please replace with 'remove-trailing-spaces all;', see " "https://docs.kde.org/stable5/en/applications/katepart/config-variables.html#variable-remove-trailing-spaces"); m_config->setRemoveSpaces(state ? 2 : 0); } else if (var == QLatin1String("overwrite-mode") && checkBoolValue(val, &state)) { m_config->setOvr(state); } else if (var == QLatin1String("keep-extra-spaces") && checkBoolValue(val, &state)) { m_config->setKeepExtraSpaces(state); } else if (var == QLatin1String("tab-indents") && checkBoolValue(val, &state)) { m_config->setTabIndents(state); } else if (var == QLatin1String("show-tabs") && checkBoolValue(val, &state)) { m_config->setShowTabs(state); } else if (var == QLatin1String("show-trailing-spaces") && checkBoolValue(val, &state)) { m_config->setShowSpaces(state ? KateDocumentConfig::Trailing : KateDocumentConfig::None); } else if (var == QLatin1String("space-indent") && checkBoolValue(val, &state)) { // this is for backward compatibility; see below spaceIndent = state; } else if (var == QLatin1String("smart-home") && checkBoolValue(val, &state)) { m_config->setSmartHome(state); } else if (var == QLatin1String("newline-at-eof") && checkBoolValue(val, &state)) { m_config->setNewLineAtEof(state); } // INTEGER SETTINGS else if (var == QLatin1String("tab-width") && checkIntValue(val, &n)) { m_config->setTabWidth(n); } else if (var == QLatin1String("indent-width") && checkIntValue(val, &n)) { m_config->setIndentationWidth(n); } else if (var == QLatin1String("indent-mode")) { m_config->setIndentationMode(val); } else if (var == QLatin1String("word-wrap-column") && checkIntValue(val, &n) && n > 0) { // uint, but hard word wrap at 0 will be no fun ;) m_config->setWordWrapAt(n); } // STRING SETTINGS else if (var == QLatin1String("eol") || var == QLatin1String("end-of-line")) { const auto l = { QLatin1String("unix"), QLatin1String("dos"), QLatin1String("mac") }; if ((n = indexOf(l, val.toLower())) != -1) { /** * set eol + avoid that it is overwritten by auto-detection again! * this fixes e.g. .kateconfig files with // kate: eol dos; to work, bug 365705 */ m_config->setEol(n); m_config->setAllowEolDetection(false); } } else if (var == QLatin1String("bom") || var == QLatin1String("byte-order-mark") || var == QLatin1String("byte-order-marker")) { if (checkBoolValue(val, &state)) { m_config->setBom(state); } } else if (var == QLatin1String("remove-trailing-spaces")) { val = val.toLower(); if (val == QLatin1String("1") || val == QLatin1String("modified") || val == QLatin1String("mod") || val == QLatin1String("+")) { m_config->setRemoveSpaces(1); } else if (val == QLatin1String("2") || val == QLatin1String("all") || val == QLatin1String("*")) { m_config->setRemoveSpaces(2); } else { m_config->setRemoveSpaces(0); } } else if (var == QLatin1String("syntax") || var == QLatin1String("hl")) { setHighlightingMode(val); } else if (var == QLatin1String("mode")) { setMode(val); } else if (var == QLatin1String("encoding")) { setEncoding(val); } else if (var == QLatin1String("default-dictionary")) { setDefaultDictionary(val); } else if (var == QLatin1String("automatic-spell-checking") && checkBoolValue(val, &state)) { onTheFlySpellCheckingEnabled(state); } // VIEW SETTINGS else if (contains(vvl, var)) { setViewVariable(var, val); } else { m_storedVariables.insert(var, val); } } } // Backward compatibility // If space-indent was set, but replace-tabs was not set, we assume // that the user wants to replace tabulators and set that flag. // If both were set, replace-tabs has precedence. // At this point spaceIndent is -1 if it was never set, // 0 if it was set to off, and 1 if it was set to on. // Note that if onlyViewAndRenderer was requested, spaceIndent is -1. if (!replaceTabsSet && spaceIndent >= 0) { m_config->setReplaceTabsDyn(spaceIndent > 0); } } void KTextEditor::DocumentPrivate::setViewVariable(QString var, QString val) { KTextEditor::ViewPrivate *v; bool state; int n; QColor c; foreach (v, m_views) { - if (var == QLatin1String("auto-brackets") && checkBoolValue(val, &state)) { - v->config()->setAutoBrackets(state); + // First, try the new config interface + QVariant help(val); // Special treatment to catch "on"/"off" + if (checkBoolValue(val, &state)) { + help = state; + } + if (v->config()->setValue(var, help)) { + } else if (v->renderer()->config()->setValue(var, help)) { + + // No success? Go the old way } else if (var == QLatin1String("dynamic-word-wrap") && checkBoolValue(val, &state)) { v->config()->setDynWordWrap(state); - } else if (var == QLatin1String("persistent-selection") && checkBoolValue(val, &state)) { - v->config()->setPersistentSelection(state); } else if (var == QLatin1String("block-selection") && checkBoolValue(val, &state)) { v->setBlockSelection(state); - } + //else if ( var = "dynamic-word-wrap-indicators" ) - else if (var == QLatin1String("line-numbers") && checkBoolValue(val, &state)) { - v->config()->setLineNumbers(state); - } else if (var == QLatin1String("icon-border") && checkBoolValue(val, &state)) { - v->config()->setIconBar(state); - } else if (var == QLatin1String("folding-markers") && checkBoolValue(val, &state)) { - v->config()->setFoldingBar(state); - } else if (var == QLatin1String("folding-preview") && checkBoolValue(val, &state)) { - v->config()->setFoldingPreview(state); - } else if (var == QLatin1String("auto-center-lines") && checkIntValue(val, &n)) { - v->config()->setAutoCenterLines(n); } else if (var == QLatin1String("icon-bar-color") && checkColorValue(val, c)) { v->renderer()->config()->setIconBarColor(c); - } else if (var == QLatin1String("scrollbar-minimap") && checkBoolValue(val, &state)) { - v->config()->setScrollBarMiniMap(state); - } else if (var == QLatin1String("scrollbar-preview") && checkBoolValue(val, &state)) { - v->config()->setScrollBarPreview(state); } // RENDERER else if (var == QLatin1String("background-color") && checkColorValue(val, c)) { v->renderer()->config()->setBackgroundColor(c); } else if (var == QLatin1String("selection-color") && checkColorValue(val, c)) { v->renderer()->config()->setSelectionColor(c); } else if (var == QLatin1String("current-line-color") && checkColorValue(val, c)) { v->renderer()->config()->setHighlightedLineColor(c); } else if (var == QLatin1String("bracket-highlight-color") && checkColorValue(val, c)) { v->renderer()->config()->setHighlightedBracketColor(c); } else if (var == QLatin1String("word-wrap-marker-color") && checkColorValue(val, c)) { v->renderer()->config()->setWordWrapMarkerColor(c); } else if (var == QLatin1String("font") || (checkIntValue(val, &n) && var == QLatin1String("font-size"))) { QFont _f(v->renderer()->config()->font()); if (var == QLatin1String("font")) { _f.setFamily(val); _f.setFixedPitch(QFont(val).fixedPitch()); } else { _f.setPointSize(n); } v->renderer()->config()->setFont(_f); } else if (var == QLatin1String("scheme")) { v->renderer()->config()->setSchema(val); } } } bool KTextEditor::DocumentPrivate::checkBoolValue(QString val, bool *result) { val = val.trimmed().toLower(); static const auto trueValues = { QLatin1String("1"), QLatin1String("on"), QLatin1String("true") }; if (contains(trueValues, val)) { *result = true; return true; } static const auto falseValues = { QLatin1String("0"), QLatin1String("off"), QLatin1String("false") }; if (contains(falseValues, val)) { *result = false; return true; } return false; } bool KTextEditor::DocumentPrivate::checkIntValue(QString val, int *result) { bool ret(false); *result = val.toInt(&ret); return ret; } bool KTextEditor::DocumentPrivate::checkColorValue(QString val, QColor &c) { c.setNamedColor(val); return c.isValid(); } // KTextEditor::variable QString KTextEditor::DocumentPrivate::variable(const QString &name) const { return m_storedVariables.value(name, QString()); } void KTextEditor::DocumentPrivate::setVariable(const QString &name, const QString &value) { QString s = QStringLiteral("kate: "); s.append(name); s.append(QLatin1Char(' ')); s.append(value); readVariableLine(s); } //END void KTextEditor::DocumentPrivate::slotModOnHdDirty(const QString &path) { if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != OnDiskModified)) { m_modOnHd = true; m_modOnHdReason = OnDiskModified; if (!m_modOnHdTimer.isActive()) { m_modOnHdTimer.start(); } } } void KTextEditor::DocumentPrivate::slotModOnHdCreated(const QString &path) { if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != OnDiskCreated)) { m_modOnHd = true; m_modOnHdReason = OnDiskCreated; if (!m_modOnHdTimer.isActive()) { m_modOnHdTimer.start(); } } } void KTextEditor::DocumentPrivate::slotModOnHdDeleted(const QString &path) { if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != OnDiskDeleted)) { m_modOnHd = true; m_modOnHdReason = OnDiskDeleted; if (!m_modOnHdTimer.isActive()) { m_modOnHdTimer.start(); } } } void KTextEditor::DocumentPrivate::slotDelayedHandleModOnHd() { // compare git hash with the one we have (if we have one) const QByteArray oldDigest = checksum(); if (!oldDigest.isEmpty() && !url().isEmpty() && url().isLocalFile()) { /** * if current checksum == checksum of new file => unmodified */ if (m_modOnHdReason != OnDiskDeleted && createDigest() && oldDigest == checksum()) { m_modOnHd = false; m_modOnHdReason = OnDiskUnmodified; m_prevModOnHdReason = OnDiskUnmodified; } #if LIBGIT2_FOUND /** * if still modified, try to take a look at git * skip that, if document is modified! * only do that, if the file is still there, else reload makes no sense! */ if (m_modOnHd && !isModified() && QFile::exists(url().toLocalFile())) { /** * try to discover the git repo of this file * libgit2 docs state that UTF-8 is the right encoding, even on windows * I hope that is correct! */ git_repository *repository = nullptr; const QByteArray utf8Path = url().toLocalFile().toUtf8(); if (git_repository_open_ext(&repository, utf8Path.constData(), 0, nullptr) == 0) { /** * if we have repo, convert the git hash to an OID */ git_oid oid; if (git_oid_fromstr(&oid, oldDigest.toHex().data()) == 0) { /** * finally: is there a blob for this git hash? */ git_blob *blob = nullptr; if (git_blob_lookup(&blob, repository, &oid) == 0) { /** * this hash exists still in git => just reload */ m_modOnHd = false; m_modOnHdReason = OnDiskUnmodified; m_prevModOnHdReason = OnDiskUnmodified; documentReload(); } git_blob_free(blob); } } git_repository_free(repository); } #endif } /** * emit our signal to the outside! */ emit modifiedOnDisk(this, m_modOnHd, m_modOnHdReason); } QByteArray KTextEditor::DocumentPrivate::checksum() const { return m_buffer->digest(); } bool KTextEditor::DocumentPrivate::createDigest() { QByteArray digest; if (url().isLocalFile()) { QFile f(url().toLocalFile()); if (f.open(QIODevice::ReadOnly)) { // init the hash with the git header QCryptographicHash crypto(QCryptographicHash::Sha1); const QString header = QStringLiteral("blob %1").arg(f.size()); crypto.addData(header.toLatin1() + '\0'); while (!f.atEnd()) { crypto.addData(f.read(256 * 1024)); } digest = crypto.result(); } } /** * set new digest */ m_buffer->setDigest(digest); return !digest.isEmpty(); } QString KTextEditor::DocumentPrivate::reasonedMOHString() const { // squeeze path const QString str = KStringHandler::csqueeze(url().toDisplayString(QUrl::PreferLocalFile)); switch (m_modOnHdReason) { case OnDiskModified: return i18n("The file '%1' was modified by another program.", str); break; case OnDiskCreated: return i18n("The file '%1' was created by another program.", str); break; case OnDiskDeleted: return i18n("The file '%1' was deleted by another program.", str); break; default: return QString(); } Q_UNREACHABLE(); return QString(); } void KTextEditor::DocumentPrivate::removeTrailingSpaces() { const int remove = config()->removeSpaces(); if (remove == 0) { return; } // temporarily disable static word wrap (see bug #328900) const bool wordWrapEnabled = config()->wordWrap(); if (wordWrapEnabled) { setWordWrap(false); } editStart(); for (int line = 0; line < lines(); ++line) { Kate::TextLine textline = plainKateTextLine(line); // remove trailing spaces in entire document, remove = 2 // remove trailing spaces of touched lines, remove = 1 // remove trailing spaces of lines saved on disk, remove = 1 if (remove == 2 || textline->markedAsModified() || textline->markedAsSavedOnDisk()) { const int p = textline->lastChar() + 1; const int l = textline->length() - p; if (l > 0) { editRemoveText(line, p, l); } } } editEnd(); // enable word wrap again, if it was enabled (see bug #328900) if (wordWrapEnabled) { setWordWrap(true); // see begin of this function } } void KTextEditor::DocumentPrivate::updateFileType(const QString &newType, bool user) { if (user || !m_fileTypeSetByUser) { if (!newType.isEmpty()) { // remember that we got set by user m_fileTypeSetByUser = user; m_fileType = newType; m_config->configStart(); if (!m_hlSetByUser && !KTextEditor::EditorPrivate::self()->modeManager()->fileType(newType).hl.isEmpty()) { int hl(KateHlManager::self()->nameFind(KTextEditor::EditorPrivate::self()->modeManager()->fileType(newType).hl)); if (hl >= 0) { m_buffer->setHighlight(hl); } } /** * set the indentation mode, if any in the mode... * and user did not set it before! */ if (!m_indenterSetByUser && !KTextEditor::EditorPrivate::self()->modeManager()->fileType(newType).indenter.isEmpty()) { config()->setIndentationMode(KTextEditor::EditorPrivate::self()->modeManager()->fileType(newType).indenter); } // views! KTextEditor::ViewPrivate *v; foreach (v, m_views) { v->config()->configStart(); v->renderer()->config()->configStart(); } bool bom_settings = false; if (m_bomSetByUser) { bom_settings = m_config->bom(); } readVariableLine(KTextEditor::EditorPrivate::self()->modeManager()->fileType(newType).varLine); if (m_bomSetByUser) { m_config->setBom(bom_settings); } m_config->configEnd(); foreach (v, m_views) { v->config()->configEnd(); v->renderer()->config()->configEnd(); } } } // fixme, make this better... emit modeChanged(this); } void KTextEditor::DocumentPrivate::slotQueryClose_save(bool *handled, bool *abortClosing) { *handled = true; *abortClosing = true; if (this->url().isEmpty()) { QWidget *parentWidget(dialogParent()); const QUrl res = QFileDialog::getSaveFileUrl(parentWidget, i18n("Save File"), QUrl(), {}, nullptr, QFileDialog::DontConfirmOverwrite); if (res.isEmpty() || !checkOverwrite(res, parentWidget)) { *abortClosing = true; return; } saveAs(res); *abortClosing = false; } else { save(); *abortClosing = false; } } bool KTextEditor::DocumentPrivate::checkOverwrite(QUrl u, QWidget *parent) { if (!u.isLocalFile()) { return true; } QFileInfo info(u.path()); if (!info.exists()) { return true; } return KMessageBox::Cancel != KMessageBox::warningContinueCancel(parent, i18n("A file named \"%1\" already exists. " "Are you sure you want to overwrite it?", info.fileName()), i18n("Overwrite File?"), KStandardGuiItem::overwrite(), KStandardGuiItem::cancel(), QString(), KMessageBox::Options(KMessageBox::Notify | KMessageBox::Dangerous)); } //BEGIN KTextEditor::ConfigInterface // BEGIN ConfigInterface stff QStringList KTextEditor::DocumentPrivate::configKeys() const { /** * expose all internally registered keys of the KateDocumentConfig */ return m_config->configKeys(); } QVariant KTextEditor::DocumentPrivate::configValue(const QString &key) { /** * just dispatch to internal key => value lookup */ return m_config->value(key); } void KTextEditor::DocumentPrivate::setConfigValue(const QString &key, const QVariant &value) { /** * just dispatch to internal key + value set */ m_config->setValue(key, value); } //END KTextEditor::ConfigInterface KTextEditor::Cursor KTextEditor::DocumentPrivate::documentEnd() const { return KTextEditor::Cursor(lastLine(), lineLength(lastLine())); } bool KTextEditor::DocumentPrivate::replaceText(const KTextEditor::Range &range, const QString &s, bool block) { // TODO more efficient? editStart(); bool changed = removeText(range, block); changed |= insertText(range.start(), s, block); editEnd(); return changed; } KateHighlighting *KTextEditor::DocumentPrivate::highlight() const { return m_buffer->highlight(); } Kate::TextLine KTextEditor::DocumentPrivate::kateTextLine(int i) { m_buffer->ensureHighlighted(i); return m_buffer->plainLine(i); } Kate::TextLine KTextEditor::DocumentPrivate::plainKateTextLine(int i) { return m_buffer->plainLine(i); } bool KTextEditor::DocumentPrivate::isEditRunning() const { return editIsRunning; } void KTextEditor::DocumentPrivate::setUndoMergeAllEdits(bool merge) { if (merge && m_undoMergeAllEdits) { // Don't add another undo safe point: it will override our current one, // meaning we'll need two undo's to get back there - which defeats the object! return; } m_undoManager->undoSafePoint(); m_undoManager->setAllowComplexMerge(merge); m_undoMergeAllEdits = merge; } //BEGIN KTextEditor::MovingInterface KTextEditor::MovingCursor *KTextEditor::DocumentPrivate::newMovingCursor(const KTextEditor::Cursor &position, KTextEditor::MovingCursor::InsertBehavior insertBehavior) { return new Kate::TextCursor(buffer(), position, insertBehavior); } KTextEditor::MovingRange *KTextEditor::DocumentPrivate::newMovingRange(const KTextEditor::Range &range, KTextEditor::MovingRange::InsertBehaviors insertBehaviors, KTextEditor::MovingRange::EmptyBehavior emptyBehavior) { return new Kate::TextRange(buffer(), range, insertBehaviors, emptyBehavior); } qint64 KTextEditor::DocumentPrivate::revision() const { return m_buffer->history().revision(); } qint64 KTextEditor::DocumentPrivate::lastSavedRevision() const { return m_buffer->history().lastSavedRevision(); } void KTextEditor::DocumentPrivate::lockRevision(qint64 revision) { m_buffer->history().lockRevision(revision); } void KTextEditor::DocumentPrivate::unlockRevision(qint64 revision) { m_buffer->history().unlockRevision(revision); } void KTextEditor::DocumentPrivate::transformCursor(int &line, int &column, KTextEditor::MovingCursor::InsertBehavior insertBehavior, qint64 fromRevision, qint64 toRevision) { m_buffer->history().transformCursor(line, column, insertBehavior, fromRevision, toRevision); } void KTextEditor::DocumentPrivate::transformCursor(KTextEditor::Cursor &cursor, KTextEditor::MovingCursor::InsertBehavior insertBehavior, qint64 fromRevision, qint64 toRevision) { int line = cursor.line(), column = cursor.column(); m_buffer->history().transformCursor(line, column, insertBehavior, fromRevision, toRevision); cursor.setLine(line); cursor.setColumn(column); } void KTextEditor::DocumentPrivate::transformRange(KTextEditor::Range &range, KTextEditor::MovingRange::InsertBehaviors insertBehaviors, KTextEditor::MovingRange::EmptyBehavior emptyBehavior, qint64 fromRevision, qint64 toRevision) { m_buffer->history().transformRange(range, insertBehaviors, emptyBehavior, fromRevision, toRevision); } //END //BEGIN KTextEditor::AnnotationInterface void KTextEditor::DocumentPrivate::setAnnotationModel(KTextEditor::AnnotationModel *model) { KTextEditor::AnnotationModel *oldmodel = m_annotationModel; m_annotationModel = model; emit annotationModelChanged(oldmodel, m_annotationModel); } KTextEditor::AnnotationModel *KTextEditor::DocumentPrivate::annotationModel() const { return m_annotationModel; } //END KTextEditor::AnnotationInterface //TAKEN FROM kparts.h bool KTextEditor::DocumentPrivate::queryClose() { if (!isReadWrite() || !isModified()) { return true; } QString docName = documentName(); int res = KMessageBox::warningYesNoCancel(dialogParent(), i18n("The document \"%1\" has been modified.\n" "Do you want to save your changes or discard them?", docName), i18n("Close Document"), KStandardGuiItem::save(), KStandardGuiItem::discard()); bool abortClose = false; bool handled = false; switch (res) { case KMessageBox::Yes : sigQueryClose(&handled, &abortClose); if (!handled) { if (url().isEmpty()) { QUrl url = QFileDialog::getSaveFileUrl(dialogParent()); if (url.isEmpty()) { return false; } saveAs(url); } else { save(); } } else if (abortClose) { return false; } return waitSaveComplete(); case KMessageBox::No : return true; default : // case KMessageBox::Cancel : return false; } } void KTextEditor::DocumentPrivate::slotStarted(KIO::Job *job) { /** * if we are idle before, we are now loading! */ if (m_documentState == DocumentIdle) { m_documentState = DocumentLoading; } /** * if loading: * - remember pre loading read-write mode * if remote load: * - set to read-only * - trigger possible message */ if (m_documentState == DocumentLoading) { /** * remember state */ m_readWriteStateBeforeLoading = isReadWrite(); /** * perhaps show loading message, but wait one second */ if (job) { /** * only read only if really remote file! */ setReadWrite(false); /** * perhaps some message about loading in one second! * remember job pointer, we want to be able to kill it! */ m_loadingJob = job; QTimer::singleShot(1000, this, SLOT(slotTriggerLoadingMessage())); } } } void KTextEditor::DocumentPrivate::slotCompleted() { /** * if were loading, reset back to old read-write mode before loading * and kill the possible loading message */ if (m_documentState == DocumentLoading) { setReadWrite(m_readWriteStateBeforeLoading); delete m_loadingMessage; } /** * Emit signal that we saved the document, if needed */ if (m_documentState == DocumentSaving || m_documentState == DocumentSavingAs) { emit documentSavedOrUploaded(this, m_documentState == DocumentSavingAs); } /** * back to idle mode */ m_documentState = DocumentIdle; m_reloading = false; } void KTextEditor::DocumentPrivate::slotCanceled() { /** * if were loading, reset back to old read-write mode before loading * and kill the possible loading message */ if (m_documentState == DocumentLoading) { setReadWrite(m_readWriteStateBeforeLoading); delete m_loadingMessage; showAndSetOpeningErrorAccess(); updateDocName(); } /** * back to idle mode */ m_documentState = DocumentIdle; m_reloading = false; } void KTextEditor::DocumentPrivate::slotTriggerLoadingMessage() { /** * no longer loading? * no message needed! */ if (m_documentState != DocumentLoading) { return; } /** * create message about file loading in progress */ delete m_loadingMessage; m_loadingMessage = new KTextEditor::Message(i18n("The file %2 is still loading.", url().toDisplayString(QUrl::PreferLocalFile), url().fileName())); m_loadingMessage->setPosition(KTextEditor::Message::TopInView); /** * if around job: add cancel action */ if (m_loadingJob) { QAction *cancel = new QAction(i18n("&Abort Loading"), nullptr); connect(cancel, SIGNAL(triggered()), this, SLOT(slotAbortLoading())); m_loadingMessage->addAction(cancel); } /** * really post message */ postMessage(m_loadingMessage); } void KTextEditor::DocumentPrivate::slotAbortLoading() { /** * no job, no work */ if (!m_loadingJob) { return; } /** * abort loading if any job * signal results! */ m_loadingJob->kill(KJob::EmitResult); m_loadingJob = nullptr; } void KTextEditor::DocumentPrivate::slotUrlChanged(const QUrl &url) { if (m_reloading) { // the URL is temporarily unset and then reset to the previous URL during reload // we do not want to notify the outside about this return; } Q_UNUSED(url); updateDocName(); emit documentUrlChanged(this); } bool KTextEditor::DocumentPrivate::save() { /** * no double save/load * we need to allow DocumentPreSavingAs here as state, as save is called in saveAs! */ if ((m_documentState != DocumentIdle) && (m_documentState != DocumentPreSavingAs)) { return false; } /** * if we are idle, we are now saving */ if (m_documentState == DocumentIdle) { m_documentState = DocumentSaving; } else { m_documentState = DocumentSavingAs; } /** * call back implementation for real work */ return KTextEditor::Document::save(); } bool KTextEditor::DocumentPrivate::saveAs(const QUrl &url) { /** * abort on bad URL * that is done in saveAs below, too * but we must check it here already to avoid messing up * as no signals will be send, then */ if (!url.isValid()) { return false; } /** * no double save/load */ if (m_documentState != DocumentIdle) { return false; } /** * we enter the pre save as phase */ m_documentState = DocumentPreSavingAs; /** * call base implementation for real work */ return KTextEditor::Document::saveAs(normalizeUrl(url)); } QString KTextEditor::DocumentPrivate::defaultDictionary() const { return m_defaultDictionary; } QList > KTextEditor::DocumentPrivate::dictionaryRanges() const { return m_dictionaryRanges; } void KTextEditor::DocumentPrivate::clearDictionaryRanges() { for (QList >::iterator i = m_dictionaryRanges.begin(); i != m_dictionaryRanges.end(); ++i) { delete(*i).first; } m_dictionaryRanges.clear(); if (m_onTheFlyChecker) { m_onTheFlyChecker->refreshSpellCheck(); } emit dictionaryRangesPresent(false); } void KTextEditor::DocumentPrivate::setDictionary(const QString &newDictionary, const KTextEditor::Range &range, bool blockmode) { if (blockmode) { for (int i = range.start().line(); i <= range.end().line(); ++i) { setDictionary(newDictionary, rangeOnLine(range, i)); } } else { setDictionary(newDictionary, range); } emit dictionaryRangesPresent(!m_dictionaryRanges.isEmpty()); } void KTextEditor::DocumentPrivate::setDictionary(const QString &newDictionary, const KTextEditor::Range &range) { KTextEditor::Range newDictionaryRange = range; if (!newDictionaryRange.isValid() || newDictionaryRange.isEmpty()) { return; } QList > newRanges; // all ranges is 'm_dictionaryRanges' are assumed to be mutually disjoint for (QList >::iterator i = m_dictionaryRanges.begin(); i != m_dictionaryRanges.end();) { qCDebug(LOG_KTE) << "new iteration" << newDictionaryRange; if (newDictionaryRange.isEmpty()) { break; } QPair pair = *i; QString dictionarySet = pair.second; KTextEditor::MovingRange *dictionaryRange = pair.first; qCDebug(LOG_KTE) << *dictionaryRange << dictionarySet; if (dictionaryRange->contains(newDictionaryRange) && newDictionary == dictionarySet) { qCDebug(LOG_KTE) << "dictionaryRange contains newDictionaryRange"; return; } if (newDictionaryRange.contains(*dictionaryRange)) { delete dictionaryRange; i = m_dictionaryRanges.erase(i); qCDebug(LOG_KTE) << "newDictionaryRange contains dictionaryRange"; continue; } KTextEditor::Range intersection = dictionaryRange->toRange().intersect(newDictionaryRange); if (!intersection.isEmpty() && intersection.isValid()) { if (dictionarySet == newDictionary) { // we don't have to do anything for 'intersection' // except cut off the intersection QList remainingRanges = KateSpellCheckManager::rangeDifference(newDictionaryRange, intersection); Q_ASSERT(remainingRanges.size() == 1); newDictionaryRange = remainingRanges.first(); ++i; qCDebug(LOG_KTE) << "dictionarySet == newDictionary"; continue; } QList remainingRanges = KateSpellCheckManager::rangeDifference(*dictionaryRange, intersection); for (QList::iterator j = remainingRanges.begin(); j != remainingRanges.end(); ++j) { KTextEditor::MovingRange *remainingRange = newMovingRange(*j, KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight); remainingRange->setFeedback(this); newRanges.push_back(QPair(remainingRange, dictionarySet)); } i = m_dictionaryRanges.erase(i); delete dictionaryRange; } else { ++i; } } m_dictionaryRanges += newRanges; if (!newDictionaryRange.isEmpty() && !newDictionary.isEmpty()) { // we don't add anything for the default dictionary KTextEditor::MovingRange *newDictionaryMovingRange = newMovingRange(newDictionaryRange, KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight); newDictionaryMovingRange->setFeedback(this); m_dictionaryRanges.push_back(QPair(newDictionaryMovingRange, newDictionary)); } if (m_onTheFlyChecker && !newDictionaryRange.isEmpty()) { m_onTheFlyChecker->refreshSpellCheck(newDictionaryRange); } } void KTextEditor::DocumentPrivate::setDefaultDictionary(const QString &dict) { if (m_defaultDictionary == dict) { return; } m_defaultDictionary = dict; if (m_onTheFlyChecker) { m_onTheFlyChecker->updateConfig(); refreshOnTheFlyCheck(); } emit defaultDictionaryChanged(this); } void KTextEditor::DocumentPrivate::onTheFlySpellCheckingEnabled(bool enable) { if (isOnTheFlySpellCheckingEnabled() == enable) { return; } if (enable) { Q_ASSERT(m_onTheFlyChecker == nullptr); m_onTheFlyChecker = new KateOnTheFlyChecker(this); } else { delete m_onTheFlyChecker; m_onTheFlyChecker = nullptr; } foreach (KTextEditor::ViewPrivate *view, m_views) { view->reflectOnTheFlySpellCheckStatus(enable); } } bool KTextEditor::DocumentPrivate::isOnTheFlySpellCheckingEnabled() const { return m_onTheFlyChecker != nullptr; } QString KTextEditor::DocumentPrivate::dictionaryForMisspelledRange(const KTextEditor::Range &range) const { if (!m_onTheFlyChecker) { return QString(); } else { return m_onTheFlyChecker->dictionaryForMisspelledRange(range); } } void KTextEditor::DocumentPrivate::clearMisspellingForWord(const QString &word) { if (m_onTheFlyChecker) { m_onTheFlyChecker->clearMisspellingForWord(word); } } void KTextEditor::DocumentPrivate::refreshOnTheFlyCheck(const KTextEditor::Range &range) { if (m_onTheFlyChecker) { m_onTheFlyChecker->refreshSpellCheck(range); } } void KTextEditor::DocumentPrivate::rangeInvalid(KTextEditor::MovingRange *movingRange) { deleteDictionaryRange(movingRange); } void KTextEditor::DocumentPrivate::rangeEmpty(KTextEditor::MovingRange *movingRange) { deleteDictionaryRange(movingRange); } void KTextEditor::DocumentPrivate::deleteDictionaryRange(KTextEditor::MovingRange *movingRange) { qCDebug(LOG_KTE) << "deleting" << movingRange; auto finder = [=] (const QPair& item) -> bool { return item.first == movingRange; }; auto it = std::find_if(m_dictionaryRanges.begin(), m_dictionaryRanges.end(), finder); if (it != m_dictionaryRanges.end()) { m_dictionaryRanges.erase(it); delete movingRange; } Q_ASSERT(std::find_if(m_dictionaryRanges.begin(), m_dictionaryRanges.end(), finder) == m_dictionaryRanges.end()); } bool KTextEditor::DocumentPrivate::containsCharacterEncoding(const KTextEditor::Range &range) { KateHighlighting *highlighting = highlight(); Kate::TextLine textLine; const int rangeStartLine = range.start().line(); const int rangeStartColumn = range.start().column(); const int rangeEndLine = range.end().line(); const int rangeEndColumn = range.end().column(); for (int line = range.start().line(); line <= rangeEndLine; ++line) { textLine = kateTextLine(line); int startColumn = (line == rangeStartLine) ? rangeStartColumn : 0; int endColumn = (line == rangeEndLine) ? rangeEndColumn : textLine->length(); for (int col = startColumn; col < endColumn; ++col) { int attr = textLine->attribute(col); const KatePrefixStore &prefixStore = highlighting->getCharacterEncodingsPrefixStore(attr); if (!prefixStore.findPrefix(textLine, col).isEmpty()) { return true; } } } return false; } int KTextEditor::DocumentPrivate::computePositionWrtOffsets(const OffsetList &offsetList, int pos) { int previousOffset = 0; for (OffsetList::const_iterator i = offsetList.begin(); i != offsetList.end(); ++i) { if ((*i).first > pos) { break; } previousOffset = (*i).second; } return pos + previousOffset; } QString KTextEditor::DocumentPrivate::decodeCharacters(const KTextEditor::Range &range, KTextEditor::DocumentPrivate::OffsetList &decToEncOffsetList, KTextEditor::DocumentPrivate::OffsetList &encToDecOffsetList) { QString toReturn; KTextEditor::Cursor previous = range.start(); int decToEncCurrentOffset = 0, encToDecCurrentOffset = 0; int i = 0; int newI = 0; KateHighlighting *highlighting = highlight(); Kate::TextLine textLine; const int rangeStartLine = range.start().line(); const int rangeStartColumn = range.start().column(); const int rangeEndLine = range.end().line(); const int rangeEndColumn = range.end().column(); for (int line = range.start().line(); line <= rangeEndLine; ++line) { textLine = kateTextLine(line); int startColumn = (line == rangeStartLine) ? rangeStartColumn : 0; int endColumn = (line == rangeEndLine) ? rangeEndColumn : textLine->length(); for (int col = startColumn; col < endColumn;) { int attr = textLine->attribute(col); const KatePrefixStore &prefixStore = highlighting->getCharacterEncodingsPrefixStore(attr); const QHash &characterEncodingsHash = highlighting->getCharacterEncodings(attr); QString matchingPrefix = prefixStore.findPrefix(textLine, col); if (!matchingPrefix.isEmpty()) { toReturn += text(KTextEditor::Range(previous, KTextEditor::Cursor(line, col))); const QChar &c = characterEncodingsHash.value(matchingPrefix); const bool isNullChar = c.isNull(); if (!c.isNull()) { toReturn += c; } i += matchingPrefix.length(); col += matchingPrefix.length(); previous = KTextEditor::Cursor(line, col); decToEncCurrentOffset = decToEncCurrentOffset - (isNullChar ? 0 : 1) + matchingPrefix.length(); encToDecCurrentOffset = encToDecCurrentOffset - matchingPrefix.length() + (isNullChar ? 0 : 1); newI += (isNullChar ? 0 : 1); decToEncOffsetList.push_back(QPair(newI, decToEncCurrentOffset)); encToDecOffsetList.push_back(QPair(i, encToDecCurrentOffset)); continue; } ++col; ++i; ++newI; } ++i; ++newI; } if (previous < range.end()) { toReturn += text(KTextEditor::Range(previous, range.end())); } return toReturn; } void KTextEditor::DocumentPrivate::replaceCharactersByEncoding(const KTextEditor::Range &range) { KateHighlighting *highlighting = highlight(); Kate::TextLine textLine; const int rangeStartLine = range.start().line(); const int rangeStartColumn = range.start().column(); const int rangeEndLine = range.end().line(); const int rangeEndColumn = range.end().column(); for (int line = range.start().line(); line <= rangeEndLine; ++line) { textLine = kateTextLine(line); int startColumn = (line == rangeStartLine) ? rangeStartColumn : 0; int endColumn = (line == rangeEndLine) ? rangeEndColumn : textLine->length(); for (int col = startColumn; col < endColumn;) { int attr = textLine->attribute(col); const QHash &reverseCharacterEncodingsHash = highlighting->getReverseCharacterEncodings(attr); QHash::const_iterator it = reverseCharacterEncodingsHash.find(textLine->at(col)); if (it != reverseCharacterEncodingsHash.end()) { replaceText(KTextEditor::Range(line, col, line, col + 1), *it); col += (*it).length(); continue; } ++col; } } } // // Highlighting information // KTextEditor::Attribute::Ptr KTextEditor::DocumentPrivate::attributeAt(const KTextEditor::Cursor &position) { KTextEditor::Attribute::Ptr attrib(new KTextEditor::Attribute()); KTextEditor::ViewPrivate *view = m_views.empty() ? nullptr : m_views.begin().value(); if (!view) { qCWarning(LOG_KTE) << "ATTENTION: cannot access lineAttributes() without any View (will be fixed eventually)"; return attrib; } Kate::TextLine kateLine = kateTextLine(position.line()); if (!kateLine) { return attrib; } *attrib = *view->renderer()->attribute(kateLine->attribute(position.column())); return attrib; } QStringList KTextEditor::DocumentPrivate::embeddedHighlightingModes() const { return highlight()->getEmbeddedHighlightingModes(); } QString KTextEditor::DocumentPrivate::highlightingModeAt(const KTextEditor::Cursor &position) { return highlight()->higlightingModeForLocation(this, position); } Kate::SwapFile *KTextEditor::DocumentPrivate::swapFile() { return m_swapfile; } /** * \return \c -1 if \c line or \c column invalid, otherwise one of * standard style attribute number */ int KTextEditor::DocumentPrivate::defStyleNum(int line, int column) { // Validate parameters to prevent out of range access if (line < 0 || line >= lines() || column < 0) { return -1; } // get highlighted line Kate::TextLine tl = kateTextLine(line); // make sure the textline is a valid pointer if (!tl) { return -1; } /** * either get char attribute or attribute of context still active at end of line */ int attribute = 0; if (column < tl->length()) { attribute = tl->attribute(column); } else if (column == tl->length()) { if (!tl->attributesList().isEmpty()) { attribute = tl->attributesList().back().attributeValue; } else { return -1; } } else { return -1; } return highlight()->defaultStyleForAttribute(attribute); } bool KTextEditor::DocumentPrivate::isComment(int line, int column) { const int defaultStyle = defStyleNum(line, column); return defaultStyle == KTextEditor::dsComment; } int KTextEditor::DocumentPrivate::findTouchedLine(int startLine, bool down) { const int offset = down ? 1 : -1; const int lineCount = lines(); while (startLine >= 0 && startLine < lineCount) { Kate::TextLine tl = m_buffer->plainLine(startLine); if (tl && (tl->markedAsModified() || tl->markedAsSavedOnDisk())) { return startLine; } startLine += offset; } return -1; } void KTextEditor::DocumentPrivate::setActiveTemplateHandler(KateTemplateHandler* handler) { // delete any active template handler delete m_activeTemplateHandler.data(); m_activeTemplateHandler = handler; } //BEGIN KTextEditor::MessageInterface bool KTextEditor::DocumentPrivate::postMessage(KTextEditor::Message *message) { // no message -> cancel if (!message) { return false; } // make sure the desired view belongs to this document if (message->view() && message->view()->document() != this) { qCWarning(LOG_KTE) << "trying to post a message to a view of another document:" << message->text(); return false; } message->setParent(this); message->setDocument(this); // if there are no actions, add a close action by default if widget does not auto-hide if (message->actions().count() == 0 && message->autoHide() < 0) { QAction *closeAction = new QAction(QIcon::fromTheme(QStringLiteral("window-close")), i18n("&Close"), nullptr); closeAction->setToolTip(i18n("Close message")); message->addAction(closeAction); } // make sure the message is registered even if no actions and no views exist m_messageHash[message] = QList >(); // reparent actions, as we want full control over when they are deleted foreach (QAction *action, message->actions()) { action->setParent(nullptr); m_messageHash[message].append(QSharedPointer(action)); } // post message to requested view, or to all views if (KTextEditor::ViewPrivate *view = qobject_cast(message->view())) { view->postMessage(message, m_messageHash[message]); } else { foreach (KTextEditor::ViewPrivate *view, m_views) { view->postMessage(message, m_messageHash[message]); } } // also catch if the user manually calls delete message connect(message, SIGNAL(closed(KTextEditor::Message*)), SLOT(messageDestroyed(KTextEditor::Message*))); return true; } void KTextEditor::DocumentPrivate::messageDestroyed(KTextEditor::Message *message) { // KTE:Message is already in destructor Q_ASSERT(m_messageHash.contains(message)); m_messageHash.remove(message); } //END KTextEditor::MessageInterface void KTextEditor::DocumentPrivate::closeDocumentInApplication() { KTextEditor::EditorPrivate::self()->application()->closeDocument(this); } diff --git a/src/search/katesearchbar.cpp b/src/search/katesearchbar.cpp index 4957caa4..57abc2b0 100644 --- a/src/search/katesearchbar.cpp +++ b/src/search/katesearchbar.cpp @@ -1,1740 +1,1740 @@ /* This file is part of the KDE libraries Copyright (C) 2009-2010 Bernhard Beschow Copyright (C) 2007 Sebastian Pipping Copyright (C) 2007 Matthew Woehlke Copyright (C) 2007 Thomas Friedrichsmeier This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "katesearchbar.h" #include "kateregexp.h" #include "katematch.h" #include "kateview.h" #include "katedocument.h" #include "kateundomanager.h" #include "kateconfig.h" #include "katerenderer.h" #include "kateglobal.h" #include #include #include #include "ui_searchbarincremental.h" #include "ui_searchbarpower.h" #include #include #include #include #include #include #include #include #include #include #include #include // Turn debug messages on/off here // #define FAST_DEBUG_ENABLE #ifdef FAST_DEBUG_ENABLE # define FAST_DEBUG(x) qCDebug(LOG_KTE) << x #else # define FAST_DEBUG(x) #endif using namespace KTextEditor; namespace { class AddMenuManager { private: QVector m_insertBefore; QVector m_insertAfter; QSet m_actionPointers; uint m_indexWalker; QMenu *m_menu; public: AddMenuManager(QMenu *parent, int expectedItemCount) : m_insertBefore(QVector(expectedItemCount)), m_insertAfter(QVector(expectedItemCount)), m_indexWalker(0), m_menu(nullptr) { Q_ASSERT(parent != nullptr); m_menu = parent->addMenu(i18n("Add...")); if (m_menu == nullptr) { return; } m_menu->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); } void enableMenu(bool enabled) { if (m_menu == nullptr) { return; } m_menu->setEnabled(enabled); } void addEntry(const QString &before, const QString after, const QString description, const QString &realBefore = QString(), const QString &realAfter = QString()) { if (m_menu == nullptr) { return; } QAction *const action = m_menu->addAction(before + after + QLatin1Char('\t') + description); m_insertBefore[m_indexWalker] = QString(realBefore.isEmpty() ? before : realBefore); m_insertAfter[m_indexWalker] = QString(realAfter.isEmpty() ? after : realAfter); action->setData(QVariant(m_indexWalker++)); m_actionPointers.insert(action); } void addSeparator() { if (m_menu == nullptr) { return; } m_menu->addSeparator(); } void handle(QAction *action, QLineEdit *lineEdit) { if (!m_actionPointers.contains(action)) { return; } const int cursorPos = lineEdit->cursorPosition(); const int index = action->data().toUInt(); const QString &before = m_insertBefore[index]; const QString &after = m_insertAfter[index]; lineEdit->insert(before + after); lineEdit->setCursorPosition(cursorPos + before.count()); lineEdit->setFocus(); } }; } // anon namespace KateSearchBar::KateSearchBar(bool initAsPower, KTextEditor::ViewPrivate *view, KateViewConfig *config) : KateViewBarWidget(true, view), m_view(view), m_config(config), m_layout(new QVBoxLayout()), m_widget(nullptr), m_incUi(nullptr), m_incInitCursor(view->cursorPosition()), m_powerUi(nullptr), highlightMatchAttribute(new Attribute()), highlightReplacementAttribute(new Attribute()), m_incHighlightAll(false), m_incFromCursor(true), m_incMatchCase(false), m_powerMatchCase(true), m_powerFromCursor(false), m_powerHighlightAll(false), m_powerMode(0) { connect(view, &KTextEditor::View::cursorPositionChanged, this, &KateSearchBar::updateIncInitCursor); connect(view, &KTextEditor::View::selectionChanged, this, &KateSearchBar::updateSelectionOnly); connect(this, &KateSearchBar::findOrReplaceAllFinished, this, &KateSearchBar::endFindOrReplaceAll); // init match attribute Attribute::Ptr mouseInAttribute(new Attribute()); mouseInAttribute->setFontBold(true); highlightMatchAttribute->setDynamicAttribute(Attribute::ActivateMouseIn, mouseInAttribute); Attribute::Ptr caretInAttribute(new Attribute()); caretInAttribute->setFontItalic(true); highlightMatchAttribute->setDynamicAttribute(Attribute::ActivateCaretIn, caretInAttribute); updateHighlightColors(); // Modify parent QWidget *const widget = centralWidget(); widget->setLayout(m_layout); m_layout->setContentsMargins(0, 0, 0, 0); // allow to have small size, for e.g. Kile setMinimumWidth(100); // Copy global to local config backup - const long searchFlags = m_config->searchFlags(); + const auto searchFlags = m_config->searchFlags(); m_incHighlightAll = (searchFlags & KateViewConfig::IncHighlightAll) != 0; m_incFromCursor = (searchFlags & KateViewConfig::IncFromCursor) != 0; m_incMatchCase = (searchFlags & KateViewConfig::IncMatchCase) != 0; m_powerMatchCase = (searchFlags & KateViewConfig::PowerMatchCase) != 0; m_powerFromCursor = (searchFlags & KateViewConfig::PowerFromCursor) != 0; m_powerHighlightAll = (searchFlags & KateViewConfig::PowerHighlightAll) != 0; m_powerMode = ((searchFlags & KateViewConfig::PowerModeRegularExpression) != 0) ? MODE_REGEX : (((searchFlags & KateViewConfig::PowerModeEscapeSequences) != 0) ? MODE_ESCAPE_SEQUENCES : (((searchFlags & KateViewConfig::PowerModeWholeWords) != 0) ? MODE_WHOLE_WORDS : MODE_PLAIN_TEXT)); // Load one of either dialogs if (initAsPower) { enterPowerMode(); } else { enterIncrementalMode(); } updateSelectionOnly(); } KateSearchBar::~KateSearchBar() { if (!m_cancelFindOrReplace) { // Finish/Cancel the still running job to avoid a crash endFindOrReplaceAll(); } clearHighlights(); delete m_layout; delete m_widget; delete m_incUi; delete m_powerUi; } void KateSearchBar::closed() { // remove search from the view bar, because it vertically bloats up the // stacked layout in KateViewBar. if (viewBar()) { viewBar()->removeBarWidget(this); } clearHighlights(); } void KateSearchBar::setReplacementPattern(const QString &replacementPattern) { Q_ASSERT(isPower()); if (this->replacementPattern() == replacementPattern) { return; } m_powerUi->replacement->setEditText(replacementPattern); } QString KateSearchBar::replacementPattern() const { Q_ASSERT(isPower()); return m_powerUi->replacement->currentText(); } void KateSearchBar::setSearchMode(KateSearchBar::SearchMode mode) { Q_ASSERT(isPower()); m_powerUi->searchMode->setCurrentIndex(mode); } void KateSearchBar::findNext() { const bool found = find(); if (found) { QComboBox *combo = m_powerUi != nullptr ? m_powerUi->pattern : m_incUi->pattern; // Add to search history addCurrentTextToHistory(combo); } } void KateSearchBar::findPrevious() { const bool found = find(SearchBackward); if (found) { QComboBox *combo = m_powerUi != nullptr ? m_powerUi->pattern : m_incUi->pattern; // Add to search history addCurrentTextToHistory(combo); } } void KateSearchBar::showResultMessage() { QString text; if (m_replaceMode) { text = i18ncp("short translation", "1 replacement made", "%1 replacements made", m_matchCounter); } else { text = i18ncp("short translation", "1 match found", "%1 matches found", m_matchCounter); } if (m_infoMessage) { m_infoMessage->setText(text); } else { m_infoMessage = new KTextEditor::Message(text, KTextEditor::Message::Positive); m_infoMessage->setPosition(KTextEditor::Message::BottomInView); m_infoMessage->setAutoHide(3000); // 3 seconds m_infoMessage->setView(m_view); m_view->doc()->postMessage(m_infoMessage); } } void KateSearchBar::showSearchWrappedHint(SearchDirection searchDirection) { // show message widget when wrapping const QIcon icon = (searchDirection == SearchForward) ? QIcon::fromTheme(QStringLiteral("go-down-search")) : QIcon::fromTheme(QStringLiteral("go-up-search")); if (!m_wrappedMessage || m_lastSearchDirection != searchDirection) { m_lastSearchDirection = searchDirection; m_wrappedMessage = new KTextEditor::Message(i18n("Search wrapped"), KTextEditor::Message::Information); m_wrappedMessage->setIcon(icon); m_wrappedMessage->setPosition(KTextEditor::Message::BottomInView); m_wrappedMessage->setAutoHide(2000); m_wrappedMessage->setAutoHideMode(KTextEditor::Message::Immediate); m_wrappedMessage->setView(m_view); m_view->doc()->postMessage(m_wrappedMessage); } } void KateSearchBar::highlightMatch(const Range &range) { KTextEditor::MovingRange *const highlight = m_view->doc()->newMovingRange(range, Kate::TextRange::DoNotExpand); highlight->setView(m_view); // show only in this view highlight->setAttributeOnlyForViews(true); // use z depth defined in moving ranges interface highlight->setZDepth(-10000.0); highlight->setAttribute(highlightMatchAttribute); m_hlRanges.append(highlight); } void KateSearchBar::highlightReplacement(const Range &range) { KTextEditor::MovingRange *const highlight = m_view->doc()->newMovingRange(range, Kate::TextRange::DoNotExpand); highlight->setView(m_view); // show only in this view highlight->setAttributeOnlyForViews(true); // use z depth defined in moving ranges interface highlight->setZDepth(-10000.0); highlight->setAttribute(highlightReplacementAttribute); m_hlRanges.append(highlight); } void KateSearchBar::indicateMatch(MatchResult matchResult) { QLineEdit *const lineEdit = isPower() ? m_powerUi->pattern->lineEdit() : m_incUi->pattern->lineEdit(); QPalette background(lineEdit->palette()); switch (matchResult) { case MatchFound: // FALLTHROUGH case MatchWrappedForward: case MatchWrappedBackward: // Green background for line edit KColorScheme::adjustBackground(background, KColorScheme::PositiveBackground); break; case MatchMismatch: // Red background for line edit KColorScheme::adjustBackground(background, KColorScheme::NegativeBackground); break; case MatchNothing: // Reset background of line edit background = QPalette(); break; case MatchNeutral: KColorScheme::adjustBackground(background, KColorScheme::NeutralBackground); break; } // Update status label if (m_incUi != nullptr) { QPalette foreground(m_incUi->status->palette()); switch (matchResult) { case MatchFound: // FALLTHROUGH case MatchNothing: KColorScheme::adjustForeground(foreground, KColorScheme::NormalText, QPalette::WindowText, KColorScheme::Window); m_incUi->status->clear(); break; case MatchWrappedForward: case MatchWrappedBackward: KColorScheme::adjustForeground(foreground, KColorScheme::NormalText, QPalette::WindowText, KColorScheme::Window); if (matchResult == MatchWrappedBackward) { m_incUi->status->setText(i18n("Reached top, continued from bottom")); } else { m_incUi->status->setText(i18n("Reached bottom, continued from top")); } break; case MatchMismatch: KColorScheme::adjustForeground(foreground, KColorScheme::NegativeText, QPalette::WindowText, KColorScheme::Window); m_incUi->status->setText(i18n("Not found")); break; case MatchNeutral: /* do nothing */ break; } m_incUi->status->setPalette(foreground); } lineEdit->setPalette(background); } /*static*/ void KateSearchBar::selectRange(KTextEditor::ViewPrivate *view, const KTextEditor::Range &range) { view->setCursorPositionInternal(range.end()); view->setSelection(range); } void KateSearchBar::selectRange2(const KTextEditor::Range &range) { disconnect(m_view, &KTextEditor::View::selectionChanged, this, &KateSearchBar::updateSelectionOnly); selectRange(m_view, range); connect(m_view, &KTextEditor::View::selectionChanged, this, &KateSearchBar::updateSelectionOnly); } void KateSearchBar::onIncPatternChanged(const QString &pattern) { if (!m_incUi) { return; } // clear prior highlightings (deletes info message if present) clearHighlights(); m_incUi->next->setDisabled(pattern.isEmpty()); m_incUi->prev->setDisabled(pattern.isEmpty()); KateMatch match(m_view->doc(), searchOptions()); if (!pattern.isEmpty()) { // Find, first try const Range inputRange = KTextEditor::Range(m_incInitCursor, m_view->document()->documentEnd()); match.searchText(inputRange, pattern); } const bool wrap = !match.isValid() && !pattern.isEmpty(); if (wrap) { // Find, second try const KTextEditor::Range inputRange = m_view->document()->documentRange(); match.searchText(inputRange, pattern); } const MatchResult matchResult = match.isValid() ? (wrap ? MatchWrappedForward : MatchFound) : pattern.isEmpty() ? MatchNothing : MatchMismatch; const Range selectionRange = pattern.isEmpty() ? Range(m_incInitCursor, m_incInitCursor) : match.isValid() ? match.range() : Range::invalid(); // don't update m_incInitCursor when we move the cursor disconnect(m_view, &KTextEditor::View::cursorPositionChanged, this, &KateSearchBar::updateIncInitCursor); selectRange2(selectionRange); connect(m_view, &KTextEditor::View::cursorPositionChanged, this, &KateSearchBar::updateIncInitCursor); indicateMatch(matchResult); } void KateSearchBar::setMatchCase(bool matchCase) { if (this->matchCase() == matchCase) { return; } if (isPower()) { m_powerUi->matchCase->setChecked(matchCase); } else { m_incUi->matchCase->setChecked(matchCase); } } void KateSearchBar::onMatchCaseToggled(bool /*matchCase*/) { sendConfig(); if (m_incUi != nullptr) { // Re-search with new settings const QString pattern = m_incUi->pattern->currentText(); onIncPatternChanged(pattern); } else { indicateMatch(MatchNothing); } } bool KateSearchBar::matchCase() const { return isPower() ? m_powerUi->matchCase->isChecked() : m_incUi->matchCase->isChecked(); } void KateSearchBar::fixForSingleLine(Range &range, SearchDirection searchDirection) { FAST_DEBUG("Single-line workaround checking BEFORE" << range); if (searchDirection == SearchForward) { const int line = range.start().line(); const int col = range.start().column(); const int maxColWithNewline = m_view->document()->lineLength(line) + 1; if (col == maxColWithNewline) { FAST_DEBUG("Starting on a newline" << range); const int maxLine = m_view->document()->lines() - 1; if (line < maxLine) { range.setRange(Cursor(line + 1, 0), range.end()); FAST_DEBUG("Search range fixed to " << range); } else { FAST_DEBUG("Already at last line"); range = Range::invalid(); } } } else { const int col = range.end().column(); if (col == 0) { FAST_DEBUG("Ending after a newline" << range); const int line = range.end().line(); if (line > 0) { const int maxColWithNewline = m_view->document()->lineLength(line - 1); range.setRange(range.start(), Cursor(line - 1, maxColWithNewline)); FAST_DEBUG("Search range fixed to " << range); } else { FAST_DEBUG("Already at first line"); range = Range::invalid(); } } } FAST_DEBUG("Single-line workaround checking AFTER" << range); } void KateSearchBar::onReturnPressed() { const Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers(); const bool shiftDown = (modifiers & Qt::ShiftModifier) != 0; const bool controlDown = (modifiers & Qt::ControlModifier) != 0; if (shiftDown) { // Shift down, search backwards findPrevious(); } else { // Shift up, search forwards findNext(); } if (controlDown) { emit hideMe(); } } bool KateSearchBar::findOrReplace(SearchDirection searchDirection, const QString *replacement) { // What to find? if (searchPattern().isEmpty()) { return false; // == Pattern error } // don't let selectionChanged signal mess around in this routine disconnect(m_view, &KTextEditor::View::selectionChanged, this, &KateSearchBar::updateSelectionOnly); // clear previous highlights if there are any clearHighlights(); const SearchOptions enabledOptions = searchOptions(searchDirection); // Where to find? Range inputRange; const Range selection = m_view->selection() ? m_view->selectionRange() : Range::invalid(); if (selection.isValid()) { if (selectionOnly()) { // First match in selection inputRange = selection; } else { // Next match after/before selection if a match was selected before if (searchDirection == SearchForward) { inputRange.setRange(selection.start(), m_view->document()->documentEnd()); } else { inputRange.setRange(Cursor(0, 0), selection.end()); } } } else { // No selection const Cursor cursorPos = m_view->cursorPosition(); if (searchDirection == SearchForward) { inputRange.setRange(cursorPos, m_view->document()->documentEnd()); } else { inputRange.setRange(Cursor(0, 0), cursorPos); } } FAST_DEBUG("Search range is" << inputRange); { const bool regexMode = enabledOptions.testFlag(Regex); const bool multiLinePattern = regexMode ? KateRegExp(searchPattern()).isMultiLine() : false; // Single-line pattern workaround if (regexMode && !multiLinePattern) { fixForSingleLine(inputRange, searchDirection); } } KateMatch match(m_view->doc(), enabledOptions); Range afterReplace = Range::invalid(); // Find, first try match.searchText(inputRange, searchPattern()); if (match.isValid() && match.range() == selection) { // Same match again if (replacement != nullptr) { // Selection is match -> replace KTextEditor::MovingRange *smartInputRange = m_view->doc()->newMovingRange(inputRange, KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight); afterReplace = match.replace(*replacement, m_view->blockSelection()); inputRange = *smartInputRange; delete smartInputRange; } if (!selectionOnly()) { // Find, second try after old selection if (searchDirection == SearchForward) { const Cursor start = (replacement != nullptr) ? afterReplace.end() : selection.end(); inputRange.setRange(start, inputRange.end()); } else { const Cursor end = (replacement != nullptr) ? afterReplace.start() : selection.start(); inputRange.setRange(inputRange.start(), end); } } // Single-line pattern workaround fixForSingleLine(inputRange, searchDirection); match.searchText(inputRange, searchPattern()); } bool askWrap = !match.isValid() && (!selection.isValid() || !selectionOnly()); bool wrap = false; if (askWrap) { askWrap = false; wrap = true; } if (askWrap) { QString question = searchDirection == SearchForward ? i18n("Bottom of file reached. Continue from top?") : i18n("Top of file reached. Continue from bottom?"); wrap = (KMessageBox::questionYesNo(nullptr, question, i18n("Continue search?"), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("DoNotShowAgainContinueSearchDialog")) == KMessageBox::Yes); } if (wrap) { showSearchWrappedHint(searchDirection); inputRange = m_view->document()->documentRange(); match.searchText(inputRange, searchPattern()); } if (match.isValid()) { selectRange2(match.range()); } const MatchResult matchResult = !match.isValid() ? MatchMismatch : !wrap ? MatchFound : searchDirection == SearchForward ? MatchWrappedForward : MatchWrappedBackward; indicateMatch(matchResult); // highlight replacements if applicable if (afterReplace.isValid()) { highlightReplacement(afterReplace); } // restore connection connect(m_view, &KTextEditor::View::selectionChanged, this, &KateSearchBar::updateSelectionOnly); return true; // == No pattern error } void KateSearchBar::findAll() { // clear highlightings of prior search&replace action clearHighlights(); Range inputRange = (m_view->selection() && selectionOnly()) ? m_view->selectionRange() : m_view->document()->documentRange(); beginFindAll(inputRange); } void KateSearchBar::onPowerPatternChanged(const QString & /*pattern*/) { givePatternFeedback(); indicateMatch(MatchNothing); } bool KateSearchBar::isPatternValid() const { if (searchPattern().isEmpty()) { return false; } return searchOptions().testFlag(WholeWords) ? searchPattern().trimmed() == searchPattern() : searchOptions().testFlag(Regex) ? QRegExp(searchPattern()).isValid() : true; } void KateSearchBar::givePatternFeedback() { // Enable/disable next/prev and replace next/all m_powerUi->findNext->setEnabled(isPatternValid()); m_powerUi->findPrev->setEnabled(isPatternValid()); m_powerUi->replaceNext->setEnabled(isPatternValid()); m_powerUi->replaceAll->setEnabled(isPatternValid()); m_powerUi->findAll->setEnabled(isPatternValid()); } void KateSearchBar::addCurrentTextToHistory(QComboBox *combo) { const QString text = combo->currentText(); const int index = combo->findText(text); if (index > 0) { combo->removeItem(index); } if (index != 0) { combo->insertItem(0, text); combo->setCurrentIndex(0); } // sync to application config KTextEditor::EditorPrivate::self()->saveSearchReplaceHistoryModels(); } void KateSearchBar::backupConfig(bool ofPower) { if (ofPower) { m_powerMatchCase = m_powerUi->matchCase->isChecked(); m_powerMode = m_powerUi->searchMode->currentIndex(); } else { m_incMatchCase = m_incUi->matchCase->isChecked(); } } void KateSearchBar::sendConfig() { - const long pastFlags = m_config->searchFlags(); - long futureFlags = pastFlags; + const auto pastFlags = m_config->searchFlags(); + auto futureFlags = pastFlags; if (m_powerUi != nullptr) { const bool OF_POWER = true; backupConfig(OF_POWER); // Update power search flags only - const long incFlagsOnly = pastFlags + const auto incFlagsOnly = pastFlags & (KateViewConfig::IncHighlightAll | KateViewConfig::IncFromCursor | KateViewConfig::IncMatchCase); futureFlags = incFlagsOnly | (m_powerMatchCase ? KateViewConfig::PowerMatchCase : 0) | (m_powerFromCursor ? KateViewConfig::PowerFromCursor : 0) | (m_powerHighlightAll ? KateViewConfig::PowerHighlightAll : 0) | ((m_powerMode == MODE_REGEX) ? KateViewConfig::PowerModeRegularExpression : ((m_powerMode == MODE_ESCAPE_SEQUENCES) ? KateViewConfig::PowerModeEscapeSequences : ((m_powerMode == MODE_WHOLE_WORDS) ? KateViewConfig::PowerModeWholeWords : KateViewConfig::PowerModePlainText))); } else if (m_incUi != nullptr) { const bool OF_INCREMENTAL = false; backupConfig(OF_INCREMENTAL); // Update incremental search flags only - const long powerFlagsOnly = pastFlags + const auto powerFlagsOnly = pastFlags & (KateViewConfig::PowerMatchCase | KateViewConfig::PowerFromCursor | KateViewConfig::PowerHighlightAll | KateViewConfig::PowerModeRegularExpression | KateViewConfig::PowerModeEscapeSequences | KateViewConfig::PowerModeWholeWords | KateViewConfig::PowerModePlainText); futureFlags = powerFlagsOnly | (m_incHighlightAll ? KateViewConfig::IncHighlightAll : 0) | (m_incFromCursor ? KateViewConfig::IncFromCursor : 0) | (m_incMatchCase ? KateViewConfig::IncMatchCase : 0); } // Adjust global config m_config->setSearchFlags(futureFlags); } void KateSearchBar::replaceNext() { const QString replacement = m_powerUi->replacement->currentText(); if (findOrReplace(SearchForward, &replacement)) { // Never merge replace actions with other replace actions/user actions m_view->doc()->undoManager()->undoSafePoint(); // Add to search history addCurrentTextToHistory(m_powerUi->pattern); // Add to replace history addCurrentTextToHistory(m_powerUi->replacement); } } // replacement == NULL --> Only highlight all matches // replacement != NULL --> Replace and highlight all matches void KateSearchBar::beginFindOrReplaceAll(Range inputRange, const QString &replacement, bool replaceMode/* = true*/) { // don't let selectionChanged signal mess around in this routine disconnect(m_view, &KTextEditor::View::selectionChanged, this, &KateSearchBar::updateSelectionOnly); // Cancel job when user close the document to avoid crash connect(m_view->doc(), &KTextEditor::Document::aboutToClose, this, &KateSearchBar::endFindOrReplaceAll); if (m_powerUi) { // Offer Cancel button and disable not useful buttons m_powerUi->searchCancelStacked->setCurrentIndex(m_powerUi->searchCancelStacked->indexOf(m_powerUi->cancelPage)); m_powerUi->findNext->setEnabled(false); m_powerUi->findPrev->setEnabled(false); m_powerUi->replaceNext->setEnabled(false); } m_highlightRanges.clear(); m_inputRange = inputRange; m_workingRange = m_view->doc()->newMovingRange(m_inputRange); m_replacement = replacement; m_replaceMode = replaceMode; m_matchCounter = 0; m_cancelFindOrReplace = false; // Ensure we have a GO! findOrReplaceAll(); } void KateSearchBar::findOrReplaceAll() { const SearchOptions enabledOptions = searchOptions(SearchForward); const bool regexMode = enabledOptions.testFlag(Regex); const bool multiLinePattern = regexMode ? KateRegExp(searchPattern()).isMultiLine() : false; // we highlight all ranges of a replace, up to some hard limit // e.g. if you replace 100000 things, rendering will break down otherwise ;=) const int maxHighlightings = 65536; // reuse match object to avoid massive moving range creation KateMatch match(m_view->doc(), enabledOptions); bool block = m_view->selection() && m_view->blockSelection(); int line = m_inputRange.start().line(); QTime rolex; // Watchog to suspend the work after some time rolex.start(); bool timeOut = false; bool done = false; do { if (block) { delete m_workingRange; // Never forget that! m_workingRange = m_view->doc()->newMovingRange(m_view->doc()->rangeOnLine(m_inputRange, line)); } do { match.searchText(*m_workingRange, searchPattern()); if (!match.isValid()) { done = true; break; } bool const originalMatchEmpty = match.isEmpty(); // Work with the match Range lastRange; if (m_replaceMode) { if (m_matchCounter == 0) { static_cast(m_view->document())->startEditing(); } // Replace lastRange = match.replace(m_replacement, false, ++m_matchCounter); } else { lastRange = match.range(); ++m_matchCounter; } // remember ranges if limit not reached if (m_matchCounter < maxHighlightings) { m_highlightRanges.push_back(lastRange); } else { m_highlightRanges.clear(); // TODO Info user that highlighting is disabled } // Continue after match if (lastRange.end() >= m_workingRange->end()) { done = true; break; } KTextEditor::DocumentCursor workingStart(m_view->doc(), lastRange.end()); if (originalMatchEmpty) { // Can happen for regex patterns like "^". // If we don't advance here we will loop forever... workingStart.move(1); } else if (regexMode && !multiLinePattern && workingStart.atEndOfLine()) { // single-line regexps might match the naked line end // therefore we better advance to the next line workingStart.move(1); } m_workingRange->setRange(workingStart.toCursor(), m_workingRange->end()); // Are we done? if (!m_workingRange->toRange().isValid() || workingStart.atEndOfDocument()) { done = true; break; } timeOut = rolex.elapsed() > 150; } while (!m_cancelFindOrReplace && !timeOut); } while (!m_cancelFindOrReplace && !timeOut && block && ++line <= m_inputRange.end().line()); if (done || m_cancelFindOrReplace) { emit findOrReplaceAllFinished(); } else if (timeOut) { QTimer::singleShot(0, this, &KateSearchBar::findOrReplaceAll); } showResultMessage(); } void KateSearchBar::endFindOrReplaceAll() { // Don't forget to remove our "crash protector" disconnect(m_view->doc(), &KTextEditor::Document::aboutToClose, this, &KateSearchBar::endFindOrReplaceAll); // After last match if (m_matchCounter > 0) { if (m_replaceMode) { static_cast(m_view->document())->finishEditing(); } } // Add ScrollBarMarks if (!m_highlightRanges.empty()) { KTextEditor::MarkInterface* iface = qobject_cast(m_view->document()); if (iface) { iface->setMarkDescription(KTextEditor::MarkInterface::SearchMatch, i18n("SearchHighLight")); iface->setMarkPixmap(KTextEditor::MarkInterface::SearchMatch, QIcon().pixmap(0,0)); for (const Range &r : m_highlightRanges) { iface->addMark(r.start().line(), KTextEditor::MarkInterface::SearchMatch); } } } // Add highlights if (m_replaceMode) { for (const Range &r : qAsConst(m_highlightRanges)) { highlightReplacement(r); } // Never merge replace actions with other replace actions/user actions m_view->doc()->undoManager()->undoSafePoint(); } else { for (const Range &r : qAsConst(m_highlightRanges)) { highlightMatch(r); } // indicateMatch(m_matchCounter > 0 ? MatchFound : MatchMismatch); TODO } // Clean-Up the still hold MovingRange delete m_workingRange; // restore connection connect(m_view, &KTextEditor::View::selectionChanged, this, &KateSearchBar::updateSelectionOnly); if (m_powerUi) { // Offer Find and Replace buttons and enable again useful buttons m_powerUi->searchCancelStacked->setCurrentIndex(m_powerUi->searchCancelStacked->indexOf(m_powerUi->searchPage)); m_powerUi->findNext->setEnabled(true); m_powerUi->findPrev->setEnabled(true); m_powerUi->replaceNext->setEnabled(true); // Add to search history addCurrentTextToHistory(m_powerUi->pattern); // Add to replace history addCurrentTextToHistory(m_powerUi->replacement); } m_cancelFindOrReplace = true; // Indicate we are not running } void KateSearchBar::replaceAll() { // clear prior highlightings (deletes info message if present) clearHighlights(); // What to find/replace? const QString replacement = m_powerUi->replacement->currentText(); // Where to replace? const bool selected = m_view->selection(); Range inputRange = (selected && selectionOnly()) ? m_view->selectionRange() : m_view->document()->documentRange(); beginFindOrReplaceAll(inputRange, replacement); } void KateSearchBar::setSearchPattern(const QString &searchPattern) { if (searchPattern == this->searchPattern()) { return; } if (isPower()) { m_powerUi->pattern->setEditText(searchPattern); } else { m_incUi->pattern->setEditText(searchPattern); } } QString KateSearchBar::searchPattern() const { return (m_powerUi != nullptr) ? m_powerUi->pattern->currentText() : m_incUi->pattern->currentText(); } void KateSearchBar::setSelectionOnly(bool selectionOnly) { if (this->selectionOnly() == selectionOnly) { return; } if (isPower()) { m_powerUi->selectionOnly->setChecked(selectionOnly); } } bool KateSearchBar::selectionOnly() const { return isPower() ? m_powerUi->selectionOnly->isChecked() : false; } KTextEditor::SearchOptions KateSearchBar::searchOptions(SearchDirection searchDirection) const { SearchOptions enabledOptions = KTextEditor::Default; if (!matchCase()) { enabledOptions |= CaseInsensitive; } if (searchDirection == SearchBackward) { enabledOptions |= Backwards; } if (m_powerUi != nullptr) { switch (m_powerUi->searchMode->currentIndex()) { case MODE_WHOLE_WORDS: enabledOptions |= WholeWords; break; case MODE_ESCAPE_SEQUENCES: enabledOptions |= EscapeSequences; break; case MODE_REGEX: enabledOptions |= Regex; break; case MODE_PLAIN_TEXT: // FALLTHROUGH default: break; } } return enabledOptions; } struct ParInfo { int openIndex; bool capturing; int captureNumber; // 1..9 }; QVector KateSearchBar::getCapturePatterns(const QString &pattern) const { QVector capturePatterns; capturePatterns.reserve(9); QStack parInfos; const int inputLen = pattern.length(); int input = 0; // walker index bool insideClass = false; int captureCount = 0; while (input < inputLen) { if (insideClass) { // Wait for closing, unescaped ']' if (pattern[input].unicode() == L']') { insideClass = false; } input++; } else { switch (pattern[input].unicode()) { case L'\\': // Skip this and any next character input += 2; break; case L'(': ParInfo curInfo; curInfo.openIndex = input; curInfo.capturing = (input + 1 >= inputLen) || (pattern[input + 1].unicode() != '?'); if (curInfo.capturing) { captureCount++; } curInfo.captureNumber = captureCount; parInfos.push(curInfo); input++; break; case L')': if (!parInfos.empty()) { ParInfo &top = parInfos.top(); if (top.capturing && (top.captureNumber <= 9)) { const int start = top.openIndex + 1; const int len = input - start; if (capturePatterns.size() < top.captureNumber) { capturePatterns.resize(top.captureNumber); } capturePatterns[top.captureNumber - 1] = pattern.mid(start, len); } parInfos.pop(); } input++; break; case L'[': input++; insideClass = true; break; default: input++; break; } } } return capturePatterns; } void KateSearchBar::showExtendedContextMenu(bool forPattern, const QPoint &pos) { // Make original menu QComboBox *comboBox = forPattern ? m_powerUi->pattern : m_powerUi->replacement; QMenu *const contextMenu = comboBox->lineEdit()->createStandardContextMenu(); if (contextMenu == nullptr) { return; } bool extendMenu = false; bool regexMode = false; switch (m_powerUi->searchMode->currentIndex()) { case MODE_REGEX: regexMode = true; // FALLTHROUGH case MODE_ESCAPE_SEQUENCES: extendMenu = true; break; default: break; } AddMenuManager addMenuManager(contextMenu, 37); if (!extendMenu) { addMenuManager.enableMenu(extendMenu); } else { // Build menu if (forPattern) { if (regexMode) { addMenuManager.addEntry(QStringLiteral("^"), QString(), i18n("Beginning of line")); addMenuManager.addEntry(QStringLiteral("$"), QString(), i18n("End of line")); addMenuManager.addSeparator(); addMenuManager.addEntry(QStringLiteral("."), QString(), i18n("Any single character (excluding line breaks)")); addMenuManager.addSeparator(); addMenuManager.addEntry(QStringLiteral("+"), QString(), i18n("One or more occurrences")); addMenuManager.addEntry(QStringLiteral("*"), QString(), i18n("Zero or more occurrences")); addMenuManager.addEntry(QStringLiteral("?"), QString(), i18n("Zero or one occurrences")); addMenuManager.addEntry(QStringLiteral("{a"), QStringLiteral(",b}"), i18n(" through occurrences"), QStringLiteral("{"), QStringLiteral(",}")); addMenuManager.addSeparator(); addMenuManager.addEntry(QStringLiteral("("), QStringLiteral(")"), i18n("Group, capturing")); addMenuManager.addEntry(QStringLiteral("|"), QString(), i18n("Or")); addMenuManager.addEntry(QStringLiteral("["), QStringLiteral("]"), i18n("Set of characters")); addMenuManager.addEntry(QStringLiteral("[^"), QStringLiteral("]"), i18n("Negative set of characters")); addMenuManager.addSeparator(); } } else { addMenuManager.addEntry(QStringLiteral("\\0"), QString(), i18n("Whole match reference")); addMenuManager.addSeparator(); if (regexMode) { const QString pattern = m_powerUi->pattern->currentText(); const QVector capturePatterns = getCapturePatterns(pattern); const int captureCount = capturePatterns.count(); for (int i = 1; i <= 9; i++) { const QString number = QString::number(i); const QString &captureDetails = (i <= captureCount) ? (QString::fromLatin1(" = (") + capturePatterns[i - 1].left(30)) + QLatin1String(")") : QString(); addMenuManager.addEntry(QLatin1String("\\") + number, QString(), i18n("Reference") + QLatin1Char(' ') + number + captureDetails); } addMenuManager.addSeparator(); } } addMenuManager.addEntry(QStringLiteral("\\n"), QString(), i18n("Line break")); addMenuManager.addEntry(QStringLiteral("\\t"), QString(), i18n("Tab")); if (forPattern && regexMode) { addMenuManager.addEntry(QStringLiteral("\\b"), QString(), i18n("Word boundary")); addMenuManager.addEntry(QStringLiteral("\\B"), QString(), i18n("Not word boundary")); addMenuManager.addEntry(QStringLiteral("\\d"), QString(), i18n("Digit")); addMenuManager.addEntry(QStringLiteral("\\D"), QString(), i18n("Non-digit")); addMenuManager.addEntry(QStringLiteral("\\s"), QString(), i18n("Whitespace (excluding line breaks)")); addMenuManager.addEntry(QStringLiteral("\\S"), QString(), i18n("Non-whitespace (excluding line breaks)")); addMenuManager.addEntry(QStringLiteral("\\w"), QString(), i18n("Word character (alphanumerics plus '_')")); addMenuManager.addEntry(QStringLiteral("\\W"), QString(), i18n("Non-word character")); } addMenuManager.addEntry(QStringLiteral("\\0???"), QString(), i18n("Octal character 000 to 377 (2^8-1)"), QStringLiteral("\\0")); addMenuManager.addEntry(QStringLiteral("\\x????"), QString(), i18n("Hex character 0000 to FFFF (2^16-1)"), QStringLiteral("\\x")); addMenuManager.addEntry(QStringLiteral("\\\\"), QString(), i18n("Backslash")); if (forPattern && regexMode) { addMenuManager.addSeparator(); addMenuManager.addEntry(QStringLiteral("(?:E"), QStringLiteral(")"), i18n("Group, non-capturing"), QStringLiteral("(?:")); addMenuManager.addEntry(QStringLiteral("(?=E"), QStringLiteral(")"), i18n("Lookahead"), QStringLiteral("(?=")); addMenuManager.addEntry(QStringLiteral("(?!E"), QStringLiteral(")"), i18n("Negative lookahead"), QStringLiteral("(?!")); } if (!forPattern) { addMenuManager.addSeparator(); addMenuManager.addEntry(QStringLiteral("\\L"), QString(), i18n("Begin lowercase conversion")); addMenuManager.addEntry(QStringLiteral("\\U"), QString(), i18n("Begin uppercase conversion")); addMenuManager.addEntry(QStringLiteral("\\E"), QString(), i18n("End case conversion")); addMenuManager.addEntry(QStringLiteral("\\l"), QString(), i18n("Lowercase first character conversion")); addMenuManager.addEntry(QStringLiteral("\\u"), QString(), i18n("Uppercase first character conversion")); addMenuManager.addEntry(QStringLiteral("\\#[#..]"), QString(), i18n("Replacement counter (for Replace All)"), QStringLiteral("\\#")); } } // Show menu QAction *const result = contextMenu->exec(comboBox->mapToGlobal(pos)); if (result != nullptr) { addMenuManager.handle(result, comboBox->lineEdit()); } } void KateSearchBar::onPowerModeChanged(int /*index*/) { if (m_powerUi->searchMode->currentIndex() == MODE_REGEX) { m_powerUi->matchCase->setChecked(true); } sendConfig(); indicateMatch(MatchNothing); givePatternFeedback(); } void KateSearchBar::nextMatchForSelection(KTextEditor::ViewPrivate *view, SearchDirection searchDirection) { const bool selected = view->selection(); if (selected) { const QString pattern = view->selectionText(); // How to find? SearchOptions enabledOptions(KTextEditor::Default); if (searchDirection == SearchBackward) { enabledOptions |= Backwards; } // Where to find? const Range selRange = view->selectionRange(); Range inputRange; if (searchDirection == SearchForward) { inputRange.setRange(selRange.end(), view->doc()->documentEnd()); } else { inputRange.setRange(Cursor(0, 0), selRange.start()); } // Find, first try KateMatch match(view->doc(), enabledOptions); match.searchText(inputRange, pattern); if (match.isValid()) { selectRange(view, match.range()); } else { // Find, second try showSearchWrappedHint(searchDirection); if (searchDirection == SearchForward) { inputRange.setRange(Cursor(0, 0), selRange.start()); } else { inputRange.setRange(selRange.end(), view->doc()->documentEnd()); } KateMatch match2(view->doc(), enabledOptions); match2.searchText(inputRange, pattern); if (match2.isValid()) { selectRange(view, match2.range()); } } } else { // Select current word so we can search for that the next time const Cursor cursorPos = view->cursorPosition(); KTextEditor::Range wordRange = view->document()->wordRangeAt(cursorPos); if (wordRange.isValid()) { selectRange(view, wordRange); } } } void KateSearchBar::enterPowerMode() { QString initialPattern; bool selectionOnly = false; // Guess settings from context: init pattern with current selection const bool selected = m_view->selection(); if (selected) { const Range &selection = m_view->selectionRange(); if (selection.onSingleLine()) { // ... with current selection initialPattern = m_view->selectionText(); } else { // Enable selection only selectionOnly = true; } } // If there's no new selection, we'll use the existing pattern if (initialPattern.isNull()) { // Coming from power search? const bool fromReplace = (m_powerUi != nullptr) && (m_widget->isVisible()); if (fromReplace) { QLineEdit *const patternLineEdit = m_powerUi->pattern->lineEdit(); Q_ASSERT(patternLineEdit != nullptr); patternLineEdit->selectAll(); m_powerUi->pattern->setFocus(Qt::MouseFocusReason); return; } // Coming from incremental search? const bool fromIncremental = (m_incUi != nullptr) && (m_widget->isVisible()); if (fromIncremental) { initialPattern = m_incUi->pattern->currentText(); } } // Create dialog const bool create = (m_powerUi == nullptr); if (create) { // Kill incremental widget if (m_incUi != nullptr) { // Backup current settings const bool OF_INCREMENTAL = false; backupConfig(OF_INCREMENTAL); // Kill widget delete m_incUi; m_incUi = nullptr; m_layout->removeWidget(m_widget); m_widget->deleteLater(); // I didn't get a crash here but for symmetrie to the other mutate slot^ } // Add power widget m_widget = new QWidget(this); m_powerUi = new Ui::PowerSearchBar; m_powerUi->setupUi(m_widget); m_layout->addWidget(m_widget); // Bind to shared history models m_powerUi->pattern->setDuplicatesEnabled(false); m_powerUi->pattern->setInsertPolicy(QComboBox::InsertAtTop); m_powerUi->pattern->setMaxCount(m_config->maxHistorySize()); m_powerUi->pattern->setModel(KTextEditor::EditorPrivate::self()->searchHistoryModel()); m_powerUi->pattern->lineEdit()->setClearButtonEnabled(true); m_powerUi->pattern->setCompleter(nullptr); m_powerUi->replacement->setDuplicatesEnabled(false); m_powerUi->replacement->setInsertPolicy(QComboBox::InsertAtTop); m_powerUi->replacement->setMaxCount(m_config->maxHistorySize()); m_powerUi->replacement->setModel(KTextEditor::EditorPrivate::self()->replaceHistoryModel()); m_powerUi->replacement->lineEdit()->setClearButtonEnabled(true); m_powerUi->replacement->setCompleter(nullptr); // Icons // Gnome does not seem to have all icons we want, so we use fall-back icons for those that are missing. QIcon mutateIcon = QIcon::fromTheme(QStringLiteral("games-config-options"), QIcon::fromTheme(QStringLiteral("preferences-system"))); QIcon matchCaseIcon = QIcon::fromTheme(QStringLiteral("format-text-superscript"), QIcon::fromTheme(QStringLiteral("format-text-bold"))); m_powerUi->mutate->setIcon(mutateIcon); m_powerUi->mutate->setChecked(true); m_powerUi->findNext->setIcon(QIcon::fromTheme(QStringLiteral("go-down-search"))); m_powerUi->findPrev->setIcon(QIcon::fromTheme(QStringLiteral("go-up-search"))); m_powerUi->findAll->setIcon(QIcon::fromTheme(QStringLiteral("edit-find"))); m_powerUi->matchCase->setIcon(matchCaseIcon); m_powerUi->selectionOnly->setIcon(QIcon::fromTheme(QStringLiteral("edit-select-all"))); // Focus proxy centralWidget()->setFocusProxy(m_powerUi->pattern); } m_powerUi->selectionOnly->setChecked(selectionOnly); // Restore previous settings if (create) { m_powerUi->matchCase->setChecked(m_powerMatchCase); m_powerUi->searchMode->setCurrentIndex(m_powerMode); } // force current index of -1 --> shows 1st completion entry instead of 2nd m_powerUi->pattern->setCurrentIndex(-1); m_powerUi->replacement->setCurrentIndex(-1); // Set initial search pattern QLineEdit *const patternLineEdit = m_powerUi->pattern->lineEdit(); Q_ASSERT(patternLineEdit != nullptr); patternLineEdit->setText(initialPattern); patternLineEdit->selectAll(); // Set initial replacement text QLineEdit *const replacementLineEdit = m_powerUi->replacement->lineEdit(); Q_ASSERT(replacementLineEdit != nullptr); replacementLineEdit->setText(QString()); // Propagate settings (slots are still inactive on purpose) onPowerPatternChanged(initialPattern); givePatternFeedback(); if (create) { // Slots connect(m_powerUi->mutate, SIGNAL(clicked()), this, SLOT(enterIncrementalMode())); connect(patternLineEdit, SIGNAL(textChanged(QString)), this, SLOT(onPowerPatternChanged(QString))); connect(m_powerUi->findNext, SIGNAL(clicked()), this, SLOT(findNext())); connect(m_powerUi->findPrev, SIGNAL(clicked()), this, SLOT(findPrevious())); connect(m_powerUi->replaceNext, SIGNAL(clicked()), this, SLOT(replaceNext())); connect(m_powerUi->replaceAll, SIGNAL(clicked()), this, SLOT(replaceAll())); connect(m_powerUi->searchMode, SIGNAL(currentIndexChanged(int)), this, SLOT(onPowerModeChanged(int))); connect(m_powerUi->matchCase, SIGNAL(toggled(bool)), this, SLOT(onMatchCaseToggled(bool))); connect(m_powerUi->findAll, SIGNAL(clicked()), this, SLOT(findAll())); connect(m_powerUi->cancel, &QPushButton::clicked, this, &KateSearchBar::onPowerCancelFindOrReplace); // Make [return] in pattern line edit trigger action connect(patternLineEdit, SIGNAL(returnPressed()), this, SLOT(onReturnPressed())); connect(replacementLineEdit, SIGNAL(returnPressed()), this, SLOT(replaceNext())); // Hook into line edit context menus m_powerUi->pattern->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_powerUi->pattern, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onPowerPatternContextMenuRequest(QPoint))); m_powerUi->replacement->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_powerUi->replacement, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onPowerReplacmentContextMenuRequest(QPoint))); } // Focus if (m_widget->isVisible()) { m_powerUi->pattern->setFocus(Qt::MouseFocusReason); } } void KateSearchBar::enterIncrementalMode() { QString initialPattern; // Guess settings from context: init pattern with current selection const bool selected = m_view->selection(); if (selected) { const Range &selection = m_view->selectionRange(); if (selection.onSingleLine()) { // ... with current selection initialPattern = m_view->selectionText(); } } // If there's no new selection, we'll use the existing pattern if (initialPattern.isNull()) { // Coming from incremental search? const bool fromIncremental = (m_incUi != nullptr) && (m_widget->isVisible()); if (fromIncremental) { m_incUi->pattern->lineEdit()->selectAll(); m_incUi->pattern->setFocus(Qt::MouseFocusReason); return; } // Coming from power search? const bool fromReplace = (m_powerUi != nullptr) && (m_widget->isVisible()); if (fromReplace) { initialPattern = m_powerUi->pattern->currentText(); } } // Still no search pattern? Use the word under the cursor if (initialPattern.isNull()) { const KTextEditor::Cursor cursorPosition = m_view->cursorPosition(); initialPattern = m_view->doc()->wordAt(cursorPosition); } // Create dialog const bool create = (m_incUi == nullptr); if (create) { // Kill power widget if (m_powerUi != nullptr) { // Backup current settings const bool OF_POWER = true; backupConfig(OF_POWER); // Kill widget delete m_powerUi; m_powerUi = nullptr; m_layout->removeWidget(m_widget); m_widget->deleteLater(); //deleteLater, because it's not a good idea too delete the widget and there for the button triggering this slot } // Add incremental widget m_widget = new QWidget(this); m_incUi = new Ui::IncrementalSearchBar; m_incUi->setupUi(m_widget); m_layout->addWidget(m_widget); // new QShortcut(KStandardShortcut::paste().primary(), m_incUi->pattern, SLOT(paste()), 0, Qt::WidgetWithChildrenShortcut); // if (!KStandardShortcut::paste().alternate().isEmpty()) // new QShortcut(KStandardShortcut::paste().alternate(), m_incUi->pattern, SLOT(paste()), 0, Qt::WidgetWithChildrenShortcut); // Icons // Gnome does not seem to have all icons we want, so we use fall-back icons for those that are missing. QIcon mutateIcon = QIcon::fromTheme(QStringLiteral("games-config-options"), QIcon::fromTheme(QStringLiteral("preferences-system"))); QIcon matchCaseIcon = QIcon::fromTheme(QStringLiteral("format-text-superscript"), QIcon::fromTheme(QStringLiteral("format-text-bold"))); m_incUi->mutate->setIcon(mutateIcon); m_incUi->next->setIcon(QIcon::fromTheme(QStringLiteral("go-down-search"))); m_incUi->prev->setIcon(QIcon::fromTheme(QStringLiteral("go-up-search"))); m_incUi->matchCase->setIcon(matchCaseIcon); // Ensure minimum size m_incUi->pattern->setMinimumWidth(12 * m_incUi->pattern->fontMetrics().height()); // Customize status area m_incUi->status->setTextElideMode(Qt::ElideLeft); // Focus proxy centralWidget()->setFocusProxy(m_incUi->pattern); m_incUi->pattern->setDuplicatesEnabled(false); m_incUi->pattern->setInsertPolicy(QComboBox::InsertAtTop); m_incUi->pattern->setMaxCount(m_config->maxHistorySize()); m_incUi->pattern->setModel(KTextEditor::EditorPrivate::self()->searchHistoryModel()); m_incUi->pattern->lineEdit()->setClearButtonEnabled(true); m_incUi->pattern->setCompleter(nullptr); } // Restore previous settings if (create) { m_incUi->matchCase->setChecked(m_incMatchCase); } // force current index of -1 --> shows 1st completion entry instead of 2nd m_incUi->pattern->setCurrentIndex(-1); // Set initial search pattern if (!create) { disconnect(m_incUi->pattern, SIGNAL(editTextChanged(QString)), this, SLOT(onIncPatternChanged(QString))); } m_incUi->pattern->setEditText(initialPattern); connect(m_incUi->pattern, SIGNAL(editTextChanged(QString)), this, SLOT(onIncPatternChanged(QString))); m_incUi->pattern->lineEdit()->selectAll(); // Propagate settings (slots are still inactive on purpose) if (initialPattern.isEmpty()) { // Reset edit color indicateMatch(MatchNothing); } // Enable/disable next/prev m_incUi->next->setDisabled(initialPattern.isEmpty()); m_incUi->prev->setDisabled(initialPattern.isEmpty()); if (create) { // Slots connect(m_incUi->mutate, SIGNAL(clicked()), this, SLOT(enterPowerMode())); connect(m_incUi->pattern->lineEdit(), SIGNAL(returnPressed()), this, SLOT(onReturnPressed())); connect(m_incUi->next, SIGNAL(clicked()), this, SLOT(findNext())); connect(m_incUi->prev, SIGNAL(clicked()), this, SLOT(findPrevious())); connect(m_incUi->matchCase, SIGNAL(toggled(bool)), this, SLOT(onMatchCaseToggled(bool))); } // Focus if (m_widget->isVisible()) { m_incUi->pattern->setFocus(Qt::MouseFocusReason); } } bool KateSearchBar::clearHighlights() { // Remove ScrollBarMarks KTextEditor::MarkInterface* iface = qobject_cast(m_view->document()); if (iface) { const QHash marks = iface->marks(); QHashIterator i(marks); while (i.hasNext()) { i.next(); if (i.value()->type & KTextEditor::MarkInterface::SearchMatch) { iface->removeMark(i.value()->line, KTextEditor::MarkInterface::SearchMatch); } } } if (m_infoMessage) { delete m_infoMessage; } if (m_hlRanges.isEmpty()) { return false; } qDeleteAll(m_hlRanges); m_hlRanges.clear(); return true; } void KateSearchBar::updateHighlightColors() { const QColor foregroundColor = m_view->defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color(); const QColor &searchColor = m_view->renderer()->config()->searchHighlightColor(); const QColor &replaceColor = m_view->renderer()->config()->replaceHighlightColor(); // init match attribute highlightMatchAttribute->setForeground(foregroundColor); highlightMatchAttribute->setBackground(searchColor); highlightMatchAttribute->dynamicAttribute(Attribute::ActivateMouseIn)->setBackground(searchColor); highlightMatchAttribute->dynamicAttribute(Attribute::ActivateMouseIn)->setForeground(foregroundColor); highlightMatchAttribute->dynamicAttribute(Attribute::ActivateCaretIn)->setBackground(searchColor); highlightMatchAttribute->dynamicAttribute(Attribute::ActivateCaretIn)->setForeground(foregroundColor); // init replacement attribute highlightReplacementAttribute->setBackground(replaceColor); highlightReplacementAttribute->setForeground(foregroundColor); } void KateSearchBar::showEvent(QShowEvent *event) { // Update init cursor if (m_incUi != nullptr) { m_incInitCursor = m_view->cursorPosition(); } updateSelectionOnly(); KateViewBarWidget::showEvent(event); } void KateSearchBar::updateSelectionOnly() { if (m_powerUi == nullptr) { return; } // Re-init "Selection only" checkbox if power search bar open const bool selected = m_view->selection(); bool selectionOnly = selected; if (selected) { Range const &selection = m_view->selectionRange(); selectionOnly = !selection.onSingleLine(); } m_powerUi->selectionOnly->setChecked(selectionOnly); } void KateSearchBar::updateIncInitCursor() { if (m_incUi == nullptr) { return; } // Update init cursor m_incInitCursor = m_view->cursorPosition(); } void KateSearchBar::onPowerPatternContextMenuRequest(const QPoint &pos) { const bool FOR_PATTERN = true; showExtendedContextMenu(FOR_PATTERN, pos); } void KateSearchBar::onPowerPatternContextMenuRequest() { onPowerPatternContextMenuRequest(m_powerUi->pattern->mapFromGlobal(QCursor::pos())); } void KateSearchBar::onPowerReplacmentContextMenuRequest(const QPoint &pos) { const bool FOR_REPLACEMENT = false; showExtendedContextMenu(FOR_REPLACEMENT, pos); } void KateSearchBar::onPowerReplacmentContextMenuRequest() { onPowerReplacmentContextMenuRequest(m_powerUi->replacement->mapFromGlobal(QCursor::pos())); } void KateSearchBar::onPowerCancelFindOrReplace() { m_cancelFindOrReplace = true; } bool KateSearchBar::isPower() const { return m_powerUi != nullptr; } void KateSearchBar::slotReadWriteChanged() { if (!KateSearchBar::isPower()) { return; } // perhaps disable/enable m_powerUi->replaceNext->setEnabled(m_view->doc()->isReadWrite() && isPatternValid()); m_powerUi->replaceAll->setEnabled(m_view->doc()->isReadWrite() && isPatternValid()); } diff --git a/src/utils/kateconfig.cpp b/src/utils/kateconfig.cpp index eb46ccd7..5a24fc1e 100644 --- a/src/utils/kateconfig.cpp +++ b/src/utils/kateconfig.cpp @@ -1,2470 +1,1459 @@ /* This file is part of the KDE libraries Copyright (C) 2007, 2008 Matthew Woehlke Copyright (C) 2003 Christoph Cullmann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kateconfig.h" #include "kateglobal.h" #include "katedefaultcolors.h" #include "katerenderer.h" #include "kateview.h" #include "katedocument.h" #include "kateschema.h" #include "katepartdebug.h" #include #include #include #include #include #include //BEGIN KateConfig KateConfig::KateConfig(const KateConfig *parent) : m_parent(parent) , m_configKeys(m_parent ? nullptr : new QStringList()) , m_configKeyToEntry(m_parent ? nullptr : new QHash()) { } KateConfig::~KateConfig() { } void KateConfig::addConfigEntry(ConfigEntry &&entry) { /** * shall only be called for toplevel config */ Q_ASSERT(isGlobal()); /** * there shall be no gaps in the entries * we might later want to use a vector */ Q_ASSERT(m_configEntries.size() == static_cast(entry.enumKey)); /** * add new element */ m_configEntries.emplace(entry.enumKey, entry); } void KateConfig::finalizeConfigEntries() { /** * shall only be called for toplevel config */ Q_ASSERT(isGlobal()); /** * compute list of all config keys + register map from key => config entry * * we skip entries without a command name, these config entries are not exposed ATM * */ for (const auto &entry : m_configEntries) { if (!entry.second.commandName.isEmpty()) { + Q_ASSERT_X(!m_configKeys->contains(entry.second.commandName), "finalizeConfigEntries", (QLatin1String("KEY NOT UNIQUE: ") + entry.second.commandName).toLocal8Bit().constData()); m_configKeys->append(entry.second.commandName); m_configKeyToEntry->insert(entry.second.commandName, &entry.second); } } } void KateConfig::readConfigEntries(const KConfigGroup &config) { configStart(); // read all config entries, even the ones ATM not set in this config object but known in the toplevel one for (const auto &entry : fullConfigEntries()) { setValue(entry.second.enumKey, config.readEntry(entry.second.configKey, entry.second.defaultValue)); } configEnd(); } void KateConfig::writeConfigEntries(KConfigGroup &config) const { // write all config entries, even the ones ATM not set in this config object but known in the toplevel one for (const auto &entry : fullConfigEntries()) { config.writeEntry(entry.second.configKey, value(entry.second.enumKey)); } } void KateConfig::configStart() { configSessionNumber++; if (configSessionNumber > 1) { return; } configIsRunning = true; } void KateConfig::configEnd() { if (configSessionNumber == 0) { return; } configSessionNumber--; if (configSessionNumber > 0) { return; } configIsRunning = false; updateConfig(); } QVariant KateConfig::value(const int key) const { // first: local lookup const auto it = m_configEntries.find(key); if (it != m_configEntries.end()) { return it->second.value; } // else: fallback to parent config, if any if (m_parent) { return m_parent->value(key); } // if we arrive here, the key was invalid! => programming error // for release builds, we just return invalid variant Q_ASSERT(false); return QVariant(); } bool KateConfig::setValue(const int key, const QVariant &value) { // check: is this key known at all? const auto &knownEntries = fullConfigEntries(); const auto knownIt = knownEntries.find(key); if (knownIt == knownEntries.end()) { // if we arrive here, the key was invalid! => programming error // for release builds, we just fail to set the value Q_ASSERT(false); return false; } // validator set? use it, if not accepting, abort setting if (knownIt->second.validator && !knownIt->second.validator(value)) { return false; } // check if value already there for this config auto valueIt = m_configEntries.find(key); if (valueIt != m_configEntries.end()) { // skip any work if value is equal if (valueIt->second.value == value) { return true; } // else: alter value and be done configStart(); valueIt->second.value = value; configEnd(); return true; } // if not in this hash, we must copy the known entry and adjust the value configStart(); auto res = m_configEntries.emplace(key, knownIt->second); res.first->second.value = value; configEnd(); return true; } QVariant KateConfig::value(const QString &key) const { /** * check if we know this key, if not, return invalid variant */ const auto &knownEntries = fullConfigKeyToEntry(); const auto it = knownEntries.find(key); if (it == knownEntries.end()) { return QVariant(); } /** * key known, dispatch to normal value() function with enum */ return value(it.value()->enumKey); } bool KateConfig::setValue(const QString &key, const QVariant &value) { /** * check if we know this key, if not, ignore the set */ const auto &knownEntries = fullConfigKeyToEntry(); const auto it = knownEntries.find(key); if (it == knownEntries.end()) { return false; } /** * key known, dispatch to normal setValue() function with enum */ return setValue(it.value()->enumKey, value); } //END -//BEGIN KateDocumentConfig +//BEGIN HelperFunctions KateGlobalConfig *KateGlobalConfig::s_global = nullptr; KateDocumentConfig *KateDocumentConfig::s_global = nullptr; KateViewConfig *KateViewConfig::s_global = nullptr; KateRendererConfig *KateRendererConfig::s_global = nullptr; /** * validate if an encoding is ok * @param name encoding name * @return encoding ok? */ static bool isEncodingOk(const QString &name) { bool found = false; auto codec = KCharsets::charsets()->codecForName(name, found); return found && codec; } +static bool inBounds(const int min, const QVariant &value, const int max) +{ + const int val = value.toInt(); + return (val >= min) && (val <= max); +} + +static bool isPositive(const QVariant &value) +{ + bool ok; + value.toUInt(&ok); + return ok; +} +//END + +//BEGIN KateGlobalConfig KateGlobalConfig::KateGlobalConfig() { /** * register this as our global instance */ Q_ASSERT(isGlobal()); s_global = this; /** * init all known config entries */ addConfigEntry(ConfigEntry(EncodingProberType, "Encoding Prober Type", QString(), KEncodingProber::Universal)); addConfigEntry(ConfigEntry(FallbackEncoding, "Fallback Encoding", QString(), QStringLiteral("ISO 8859-15"), [](const QVariant &value) { return isEncodingOk(value.toString()); })); /** * finalize the entries, e.g. hashs them */ finalizeConfigEntries(); /** * init with defaults from config or really hardcoded ones */ KConfigGroup cg(KTextEditor::EditorPrivate::config(), "KTextEditor Editor"); readConfig(cg); } void KateGlobalConfig::readConfig(const KConfigGroup &config) { /** * start config update group */ configStart(); /** * read generic entries */ readConfigEntries(config); /** * end config update group, might trigger updateConfig() */ configEnd(); } void KateGlobalConfig::writeConfig(KConfigGroup &config) { /** * write generic entries */ writeConfigEntries(config); } void KateGlobalConfig::updateConfig() { // write config KConfigGroup cg(KTextEditor::EditorPrivate::config(), "KTextEditor Editor"); writeConfig(cg); KTextEditor::EditorPrivate::config()->sync(); } QTextCodec *KateGlobalConfig::fallbackCodec() const { /** * query stored encoding, always fallback to ISO 8859-15 if nothing valid set */ const auto encoding = value(FallbackEncoding).toString(); if (encoding.isEmpty()) { return QTextCodec::codecForName("ISO 8859-15"); } /** * use configured encoding */ return KCharsets::charsets()->codecForName(encoding); } +//END +//BEGIN KateDocumentConfig KateDocumentConfig::KateDocumentConfig() { /** * register this as our global instance */ Q_ASSERT(isGlobal()); s_global = this; /** * init all known config entries */ addConfigEntry(ConfigEntry(TabWidth, "Tab Width", QStringLiteral("tab-width"), 4, [](const QVariant &value) { return value.toInt() >= 1; })); addConfigEntry(ConfigEntry(IndentationWidth, "Indentation Width", QStringLiteral("indent-width"), 4, [](const QVariant &value) { return value.toInt() >= 1; })); addConfigEntry(ConfigEntry(OnTheFlySpellCheck, "On-The-Fly Spellcheck", QStringLiteral("on-the-fly-spellcheck"), false)); addConfigEntry(ConfigEntry(IndentOnTextPaste, "Indent On Text Paste", QStringLiteral("indent-pasted-text"), false)); addConfigEntry(ConfigEntry(ReplaceTabsWithSpaces, "ReplaceTabsDyn", QStringLiteral("replace-tabs"), true)); addConfigEntry(ConfigEntry(BackupOnSaveLocal, "Backup Local", QStringLiteral("backup-on-save-local"), false)); addConfigEntry(ConfigEntry(BackupOnSaveRemote, "Backup Remote", QStringLiteral("backup-on-save-remote"), false)); addConfigEntry(ConfigEntry(BackupOnSavePrefix, "Backup Prefix", QStringLiteral("backup-on-save-prefix"), QString())); addConfigEntry(ConfigEntry(BackupOnSaveSuffix, "Backup Suffix", QStringLiteral("backup-on-save-suffix"), QStringLiteral("~"))); addConfigEntry(ConfigEntry(IndentationMode, "Indentation Mode", QString(), QStringLiteral("normal"))); addConfigEntry(ConfigEntry(TabHandlingMode, "Tab Handling", QString(), KateDocumentConfig::tabSmart)); addConfigEntry(ConfigEntry(StaticWordWrap, "Word Wrap", QString(), false)); addConfigEntry(ConfigEntry(StaticWordWrapColumn, "Word Wrap Column", QString(), 80, [](const QVariant &value) { return value.toInt() >= 1; })); addConfigEntry(ConfigEntry(PageUpDownMovesCursor, "PageUp/PageDown Moves Cursor", QString(), false)); addConfigEntry(ConfigEntry(SmartHome, "Smart Home", QString(), true)); addConfigEntry(ConfigEntry(ShowTabs, "Show Tabs", QString(), true)); addConfigEntry(ConfigEntry(IndentOnTab, "Indent On Tab", QString(), true)); addConfigEntry(ConfigEntry(KeepExtraSpaces, "Keep Extra Spaces", QString(), false)); addConfigEntry(ConfigEntry(BackspaceIndents, "Indent On Backspace", QString(), true)); addConfigEntry(ConfigEntry(ShowSpacesMode, "Show Spaces", QString(), KateDocumentConfig::None)); addConfigEntry(ConfigEntry(TrailingMarkerSize, "Trailing Marker Size", QString(), 1)); addConfigEntry(ConfigEntry(RemoveSpacesMode, "Remove Spaces", QString(), 0)); addConfigEntry(ConfigEntry(NewlineAtEOF, "Newline at End of File", QString(), true)); addConfigEntry(ConfigEntry(OverwriteMode, "Overwrite Mode", QString(), false)); addConfigEntry(ConfigEntry(Encoding, "Encoding", QString(), QStringLiteral("UTF-8"), [](const QVariant &value) { return isEncodingOk(value.toString()); })); addConfigEntry(ConfigEntry(EndOfLine, "End of Line", QString(), 0)); addConfigEntry(ConfigEntry(AllowEndOfLineDetection, "Allow End of Line Detection", QString(), true)); addConfigEntry(ConfigEntry(ByteOrderMark, "BOM", QString(), false)); addConfigEntry(ConfigEntry(SwapFile, "Swap File Mode", QString(), KateDocumentConfig::EnableSwapFile)); addConfigEntry(ConfigEntry(SwapFileDirectory, "Swap Directory", QString(), QString())); addConfigEntry(ConfigEntry(SwapFileSyncInterval, "Swap Sync Interval", QString(), 15)); addConfigEntry(ConfigEntry(LineLengthLimit, "Line Length Limit", QString(), 4096)); /** * finalize the entries, e.g. hashs them */ finalizeConfigEntries(); /** * init with defaults from config or really hardcoded ones */ KConfigGroup cg(KTextEditor::EditorPrivate::config(), "KTextEditor Document"); readConfig(cg); } KateDocumentConfig::KateDocumentConfig(KTextEditor::DocumentPrivate *doc) : KateConfig(s_global), m_doc(doc) { /** * per document config doesn't read stuff per default */ } void KateDocumentConfig::readConfig(const KConfigGroup &config) { /** * start config update group */ configStart(); /** * read generic entries */ readConfigEntries(config); /** * fixup sonnet config, see KateSpellCheckConfigTab::apply(), too * WARNING: this is slightly hackish, but it's currently the only way to * do it, see also the KTextEdit class */ if (isGlobal()) { const QSettings settings(QStringLiteral("KDE"), QStringLiteral("Sonnet")); setOnTheFlySpellCheck(settings.value(QStringLiteral("checkerEnabledByDefault"), false).toBool()); } /** * backwards compatibility mappings * convert stuff, old entries deleted in writeConfig */ if (const int backupFlags = config.readEntry("Backup Flags", 0)) { setBackupOnSaveLocal(backupFlags & 0x1); setBackupOnSaveRemote(backupFlags & 0x2); } /** * end config update group, might trigger updateConfig() */ configEnd(); } void KateDocumentConfig::writeConfig(KConfigGroup &config) { /** * write generic entries */ writeConfigEntries(config); /** * backwards compatibility mappings * here we remove old entries we converted on readConfig */ config.deleteEntry("Backup Flags"); } void KateDocumentConfig::updateConfig() { if (m_doc) { m_doc->updateConfig(); return; } if (isGlobal()) { for (int z = 0; z < KTextEditor::EditorPrivate::self()->kateDocuments().size(); ++z) { (KTextEditor::EditorPrivate::self()->kateDocuments())[z]->updateConfig(); } // write config KConfigGroup cg(KTextEditor::EditorPrivate::config(), "KTextEditor Document"); writeConfig(cg); KTextEditor::EditorPrivate::config()->sync(); } } QTextCodec *KateDocumentConfig::codec() const { /** * query stored encoding, always fallback to UTF-8 if nothing valid set */ const auto encoding = value(Encoding).toString(); if (encoding.isEmpty()) { return QTextCodec::codecForName("UTF-8"); } /** * use configured encoding */ return KCharsets::charsets()->codecForName(encoding); } QString KateDocumentConfig::eolString() { switch(eol()) { case KateDocumentConfig::eolDos: return QStringLiteral("\r\n"); case KateDocumentConfig::eolMac: return QStringLiteral("\r"); default: return QStringLiteral("\n"); } } //END //BEGIN KateViewConfig KateViewConfig::KateViewConfig() - : m_dynWordWrapSet(false), - m_dynWrapAtStaticMarkerSet(false), - m_dynWordWrapIndicatorsSet(false), - m_dynWordWrapAlignIndentSet(false), - m_lineNumbersSet(false), - m_scrollBarMarksSet(false), - m_scrollBarPreviewSet(false), - m_scrollBarMiniMapSet(false), - m_scrollBarMiniMapAllSet(false), - m_scrollBarMiniMapWidthSet(false), - m_showScrollbarsSet(false), - m_iconBarSet(false), - m_foldingBarSet(false), - m_foldingPreviewSet(false), - m_lineModificationSet(false), - m_bookmarkSortSet(false), - m_autoCenterLinesSet(false), - m_searchFlagsSet(false), - m_defaultMarkTypeSet(false), - m_persistentSelectionSet(false), - m_inputModeSet(false), - m_viInputModeStealKeysSet(false), - m_viRelativeLineNumbersSet(false), - m_automaticCompletionInvocationSet(false), - m_wordCompletionSet(false), - m_keywordCompletionSet(false), - m_wordCompletionMinimalWordLengthSet(false), - m_smartCopyCutSet(false), - m_mousePasteAtCursorPositionSet(false), - m_scrollPastEndSet(false), - m_allowMarkMenu(true), - m_wordCompletionRemoveTailSet(false), - m_foldFirstLineSet(false), - m_showWordCountSet(false), - m_showLineCountSet(false), - m_autoBracketsSet(false), - m_backspaceRemoveComposedSet(false) - { s_global = this; + // Init all known config entries + // NOTE: Ensure to keep the same order as listed in enum ConfigEntryTypes or it will later assert! + // addConfigEntry(ConfigEntry(, , , , [])) + addConfigEntry(ConfigEntry(AllowMarkMenu, "Allow Mark Menu", QStringLiteral("allow-mark-menu"), true)); + addConfigEntry(ConfigEntry(AutoBrackets, "Auto Brackets", QStringLiteral("auto-brackets"), false)); + addConfigEntry(ConfigEntry(AutoCenterLines, "Auto Center Lines", QStringLiteral("auto-center-lines"), 0)); + addConfigEntry(ConfigEntry(AutomaticCompletionInvocation, "Auto Completion", QString(), true)); + addConfigEntry(ConfigEntry(BackspaceRemoveComposedCharacters, "Backspace Remove Composed Characters", QString(), false)); + addConfigEntry(ConfigEntry(BookmarkSorting, "Bookmark Menu Sorting", QString(), 0)); + addConfigEntry(ConfigEntry(CharsToEncloseSelection, "Chars To Enclose Selection", QString(), QString())); + addConfigEntry(ConfigEntry(DefaultMarkType, "Default Mark Type", QStringLiteral("default-mark-type"), KTextEditor::MarkInterface::markType01, [](const QVariant &value) { return isPositive(value); })); + addConfigEntry(ConfigEntry(DynWordWrapAlignIndent, "Dynamic Word Wrap Align Indent", QString(), 80, [](const QVariant &value) { return inBounds(1, value, 100); })); + addConfigEntry(ConfigEntry(DynWordWrapIndicators, "Dynamic Word Wrap Indicators", QString(), 1, [](const QVariant &value) { return inBounds(1, value, 3); })); + addConfigEntry(ConfigEntry(DynWrapAtStaticMarker, "Dynamic Word Wrap At Static Marker", QString(), false)); + addConfigEntry(ConfigEntry(DynamicWordWrap, "Dynamic Word Wrap", QStringLiteral("dynamic-word-wrap"), true)); + addConfigEntry(ConfigEntry(FoldFirstLine, "Fold First Line", QString(), false)); + addConfigEntry(ConfigEntry(InputMode, "Input Mode", QString(), 0, [](const QVariant &value) { return isPositive(value); })); + addConfigEntry(ConfigEntry(KeywordCompletion, "Keyword Completion", QStringLiteral("keyword-completion"), true)); + addConfigEntry(ConfigEntry(MaxHistorySize, "Maximum Search History Size", QString(), 100, [](const QVariant &value) { return inBounds(0, value, 999); })); + addConfigEntry(ConfigEntry(MousePasteAtCursorPosition, "Mouse Paste At Cursor Position", QString(), false)); + addConfigEntry(ConfigEntry(PersistentSelection, "Persistent Selection", QStringLiteral("persistent-selectionq"), false)); + addConfigEntry(ConfigEntry(ScrollBarMiniMapWidth, "Scroll Bar Mini Map Width", QString(), 60, [](const QVariant &value) { return inBounds(0, value, 999); })); + addConfigEntry(ConfigEntry(ScrollPastEnd, "Scroll Past End", QString(), false)); + addConfigEntry(ConfigEntry(SearchFlags, "Search/Replace Flags", QString(), IncFromCursor | PowerMatchCase | PowerModePlainText)); + addConfigEntry(ConfigEntry(ShowFoldingBar, "Folding Bar", QStringLiteral("folding-bar"), true)); + addConfigEntry(ConfigEntry(ShowFoldingPreview, "Folding Preview", QStringLiteral("folding-preview"), true)); + addConfigEntry(ConfigEntry(ShowIconBar, "Icon Bar", QStringLiteral("icon-bar"), false)); + addConfigEntry(ConfigEntry(ShowLineCount, "Show Line Count", QString(), false)); + addConfigEntry(ConfigEntry(ShowLineModification, "Line Modification", QStringLiteral("modification-markers"), false)); + addConfigEntry(ConfigEntry(ShowLineNumbers, "Line Numbers", QStringLiteral("line-numbers"), false)); + addConfigEntry(ConfigEntry(ShowScrollBarMarks, "Scroll Bar Marks", QString(), false)); + addConfigEntry(ConfigEntry(ShowScrollBarMiniMap, "Scroll Bar MiniMap", QStringLiteral("scrollbar-minimap"), true)); + addConfigEntry(ConfigEntry(ShowScrollBarMiniMapAll, "Scroll Bar Mini Map All", QString(), true)); + addConfigEntry(ConfigEntry(ShowScrollBarPreview, "Scroll Bar Preview", QStringLiteral("scrollbar-preview"), true)); + addConfigEntry(ConfigEntry(ShowScrollbars, "Show Scrollbars", QString(), AlwaysOn, [](const QVariant &value) { return inBounds(0, value, 2); })); + addConfigEntry(ConfigEntry(ShowWordCount, "Show Word Count", QString(), false)); + addConfigEntry(ConfigEntry(SmartCopyCut, "Smart Copy Cut", QString(), false)); + addConfigEntry(ConfigEntry(ViInputModeStealKeys, "Vi Input Mode Steal Keys", QString(), false)); + addConfigEntry(ConfigEntry(ViRelativeLineNumbers, "Vi Relative Line Numbers", QString(), false)); + addConfigEntry(ConfigEntry(WordCompletion, "Word Completion", QString(), true)); + addConfigEntry(ConfigEntry(WordCompletionMinimalWordLength, "Word Completion Minimal Word Length", QString(), 3, [](const QVariant &value) { return inBounds(0, value, 99); })); + addConfigEntry(ConfigEntry(WordCompletionRemoveTail, "Word Completion Remove Tail", QString(), true)); + + // Never forget to finalize or the becomes not available + finalizeConfigEntries(); + // init with defaults from config or really hardcoded ones KConfigGroup config(KTextEditor::EditorPrivate::config(), "KTextEditor View"); readConfig(config); } KateViewConfig::KateViewConfig(KTextEditor::ViewPrivate *view) - : KateConfig(s_global), - m_searchFlags(PowerModePlainText), - m_maxHistorySize(100), - m_showWordCount(false), - m_dynWordWrapSet(false), - m_dynWrapAtStaticMarkerSet(false), - m_dynWordWrapIndicatorsSet(false), - m_dynWordWrapAlignIndentSet(false), - m_lineNumbersSet(false), - m_scrollBarMarksSet(false), - m_scrollBarPreviewSet(false), - m_scrollBarMiniMapSet(false), - m_scrollBarMiniMapAllSet(false), - m_scrollBarMiniMapWidthSet(false), - m_showScrollbarsSet(false), - m_iconBarSet(false), - m_foldingBarSet(false), - m_foldingPreviewSet(false), - m_lineModificationSet(false), - m_bookmarkSortSet(false), - m_autoCenterLinesSet(false), - m_searchFlagsSet(false), - m_defaultMarkTypeSet(false), - m_persistentSelectionSet(false), - m_inputModeSet(false), - m_viInputModeStealKeysSet(false), - m_viRelativeLineNumbersSet(false), - m_automaticCompletionInvocationSet(false), - m_wordCompletionSet(false), - m_keywordCompletionSet(false), - m_wordCompletionMinimalWordLengthSet(false), - m_smartCopyCutSet(false), - m_mousePasteAtCursorPositionSet(false), - m_scrollPastEndSet(false), - m_allowMarkMenu(true), - m_wordCompletionRemoveTailSet(false), - m_foldFirstLineSet(false), - m_showWordCountSet(false), - m_showLineCountSet(false), - m_autoBracketsSet(false), - m_backspaceRemoveComposedSet(false), - m_view(view) + : KateConfig(s_global) + , m_view(view) { } KateViewConfig::~KateViewConfig() { } -namespace -{ -const char KEY_SEARCH_REPLACE_FLAGS[] = "Search/Replace Flags"; -const char KEY_DYN_WORD_WRAP[] = "Dynamic Word Wrap"; -const char KEY_DYN_WORD_WRAP_AT_STATIC_MARKER[] = "Dynamic Word Wrap At Static Marker"; -const char KEY_DYN_WORD_WRAP_INDICATORS[] = "Dynamic Word Wrap Indicators"; -const char KEY_DYN_WORD_WRAP_ALIGN_INDENT[] = "Dynamic Word Wrap Align Indent"; -const char KEY_LINE_NUMBERS[] = "Line Numbers"; -const char KEY_SCROLL_BAR_MARKS[] = "Scroll Bar Marks"; -const char KEY_SCROLL_BAR_PREVIEW[] = "Scroll Bar Preview"; -const char KEY_SCROLL_BAR_MINI_MAP[] = "Scroll Bar MiniMap"; -const char KEY_SCROLL_BAR_MINI_MAP_ALL[] = "Scroll Bar Mini Map All"; -const char KEY_SCROLL_BAR_MINI_MAP_WIDTH[] = "Scroll Bar Mini Map Width"; -const char KEY_SHOW_SCROLLBARS[] = "Show Scrollbars"; -const char KEY_ICON_BAR[] = "Icon Bar"; -const char KEY_FOLDING_BAR[] = "Folding Bar"; -const char KEY_FOLDING_PREVIEW[] = "Folding Preview"; -const char KEY_LINE_MODIFICATION[] = "Line Modification"; -const char KEY_BOOKMARK_SORT[] = "Bookmark Menu Sorting"; -const char KEY_AUTO_CENTER_LINES[] = "Auto Center Lines"; -const char KEY_MAX_HISTORY_SIZE[] = "Maximum Search History Size"; -const char KEY_DEFAULT_MARK_TYPE[] = "Default Mark Type"; -const char KEY_ALLOW_MARK_MENU[] = "Allow Mark Menu"; -const char KEY_PERSISTENT_SELECTION[] = "Persistent Selection"; -const char KEY_INPUT_MODE[] = "Input Mode"; -const char KEY_VI_INPUT_MODE_STEAL_KEYS[] = "Vi Input Mode Steal Keys"; -const char KEY_VI_RELATIVE_LINE_NUMBERS[] = "Vi Relative Line Numbers"; -const char KEY_AUTOMATIC_COMPLETION_INVOCATION[] = "Auto Completion"; -const char KEY_WORD_COMPLETION[] = "Word Completion"; -const char KEY_KEYWORD_COMPLETION[] = "Keyword Completion"; -const char KEY_WORD_COMPLETION_MINIMAL_WORD_LENGTH[] = "Word Completion Minimal Word Length"; -const char KEY_WORD_COMPLETION_REMOVE_TAIL[] = "Word Completion Remove Tail"; -const char KEY_SMART_COPY_CUT[] = "Smart Copy Cut"; -const char KEY_MOUSE_PASTE_AT_CURSOR_POSITION[] = "Mouse Paste At Cursor Position"; -const char KEY_SCROLL_PAST_END[] = "Scroll Past End"; -const char KEY_FOLD_FIRST_LINE[] = "Fold First Line"; -const char KEY_SHOW_LINE_COUNT[] = "Show Line Count"; -const char KEY_SHOW_WORD_COUNT[] = "Show Word Count"; -const char KEY_AUTO_BRACKETS[] = "Auto Brackets"; -const char KEY_BACKSPACE_REMOVE_COMPOSED[] = "Backspace Remove Composed Characters"; -} - void KateViewConfig::readConfig(const KConfigGroup &config) { configStart(); // read generic entries readConfigEntries(config); - // default on - setDynWordWrap(config.readEntry(KEY_DYN_WORD_WRAP, true)); - setDynWrapAtStaticMarker(config.readEntry(KEY_DYN_WORD_WRAP_AT_STATIC_MARKER, false)); - setDynWordWrapIndicators(config.readEntry(KEY_DYN_WORD_WRAP_INDICATORS, 1)); - setDynWordWrapAlignIndent(config.readEntry(KEY_DYN_WORD_WRAP_ALIGN_INDENT, 80)); - - setLineNumbers(config.readEntry(KEY_LINE_NUMBERS, false)); - - setScrollBarMarks(config.readEntry(KEY_SCROLL_BAR_MARKS, false)); - - setScrollBarPreview(config.readEntry(KEY_SCROLL_BAR_PREVIEW, true)); - - setScrollBarMiniMap(config.readEntry(KEY_SCROLL_BAR_MINI_MAP, true)); - - setScrollBarMiniMapAll(config.readEntry(KEY_SCROLL_BAR_MINI_MAP_ALL, false)); - - setScrollBarMiniMapWidth(config.readEntry(KEY_SCROLL_BAR_MINI_MAP_WIDTH, 60)); - - setShowScrollbars(config.readEntry(KEY_SHOW_SCROLLBARS, static_cast(AlwaysOn))); - - setIconBar(config.readEntry(KEY_ICON_BAR, false)); - - setFoldingBar(config.readEntry(KEY_FOLDING_BAR, true)); - - setFoldingPreview(config.readEntry(KEY_FOLDING_PREVIEW, true)); - - setLineModification(config.readEntry(KEY_LINE_MODIFICATION, false)); - - setBookmarkSort(config.readEntry(KEY_BOOKMARK_SORT, 0)); - - setAutoCenterLines(config.readEntry(KEY_AUTO_CENTER_LINES, 0)); - - setSearchFlags(config.readEntry(KEY_SEARCH_REPLACE_FLAGS, - IncFromCursor | PowerMatchCase | PowerModePlainText)); - - m_maxHistorySize = config.readEntry(KEY_MAX_HISTORY_SIZE, 100); - - setDefaultMarkType(config.readEntry(KEY_DEFAULT_MARK_TYPE, int(KTextEditor::MarkInterface::markType01))); - setAllowMarkMenu(config.readEntry(KEY_ALLOW_MARK_MENU, true)); - - setPersistentSelection(config.readEntry(KEY_PERSISTENT_SELECTION, false)); - - setInputModeRaw(config.readEntry(KEY_INPUT_MODE, 0)); - setViInputModeStealKeys(config.readEntry(KEY_VI_INPUT_MODE_STEAL_KEYS, false)); - setViRelativeLineNumbers(config.readEntry(KEY_VI_RELATIVE_LINE_NUMBERS, false)); - - setAutomaticCompletionInvocation(config.readEntry(KEY_AUTOMATIC_COMPLETION_INVOCATION, true)); - setWordCompletion(config.readEntry(KEY_WORD_COMPLETION, true)); - setKeywordCompletion(config.readEntry(KEY_KEYWORD_COMPLETION, true)); - setWordCompletionMinimalWordLength(config.readEntry(KEY_WORD_COMPLETION_MINIMAL_WORD_LENGTH, 3)); - setWordCompletionRemoveTail(config.readEntry(KEY_WORD_COMPLETION_REMOVE_TAIL, true)); - setSmartCopyCut(config.readEntry(KEY_SMART_COPY_CUT, false)); - setMousePasteAtCursorPosition(config.readEntry(KEY_MOUSE_PASTE_AT_CURSOR_POSITION, false)); - setScrollPastEnd(config.readEntry(KEY_SCROLL_PAST_END, false)); - setFoldFirstLine(config.readEntry(KEY_FOLD_FIRST_LINE, false)); - setShowLineCount(config.readEntry(KEY_SHOW_LINE_COUNT, false)); - setShowWordCount(config.readEntry(KEY_SHOW_WORD_COUNT, false)); - setAutoBrackets(config.readEntry(KEY_AUTO_BRACKETS, false)); - - setBackspaceRemoveComposed(config.readEntry(KEY_BACKSPACE_REMOVE_COMPOSED, false)); - configEnd(); } void KateViewConfig::writeConfig(KConfigGroup &config) { // write generic entries writeConfigEntries(config); - - config.writeEntry(KEY_DYN_WORD_WRAP, dynWordWrap()); - config.writeEntry(KEY_DYN_WORD_WRAP_AT_STATIC_MARKER, dynWrapAtStaticMarker()); - config.writeEntry(KEY_DYN_WORD_WRAP_INDICATORS, dynWordWrapIndicators()); - config.writeEntry(KEY_DYN_WORD_WRAP_ALIGN_INDENT, dynWordWrapAlignIndent()); - - config.writeEntry(KEY_LINE_NUMBERS, lineNumbers()); - - config.writeEntry(KEY_SCROLL_BAR_MARKS, scrollBarMarks()); - - config.writeEntry(KEY_SCROLL_BAR_PREVIEW, scrollBarPreview()); - - config.writeEntry(KEY_SCROLL_BAR_MINI_MAP, scrollBarMiniMap()); - - config.writeEntry(KEY_SCROLL_BAR_MINI_MAP_ALL, scrollBarMiniMapAll()); - - config.writeEntry(KEY_SCROLL_BAR_MINI_MAP_WIDTH, scrollBarMiniMapWidth()); - - config.writeEntry(KEY_SHOW_SCROLLBARS, showScrollbars()); - - config.writeEntry(KEY_ICON_BAR, iconBar()); - - config.writeEntry(KEY_FOLDING_BAR, foldingBar()); - - config.writeEntry(KEY_FOLDING_PREVIEW, foldingPreview()); - - config.writeEntry(KEY_LINE_MODIFICATION, lineModification()); - - config.writeEntry(KEY_BOOKMARK_SORT, bookmarkSort()); - - config.writeEntry(KEY_AUTO_CENTER_LINES, autoCenterLines()); - - config.writeEntry(KEY_SEARCH_REPLACE_FLAGS, int(searchFlags())); - - config.writeEntry(KEY_MAX_HISTORY_SIZE, m_maxHistorySize); - - config.writeEntry(KEY_DEFAULT_MARK_TYPE, defaultMarkType()); - - config.writeEntry(KEY_ALLOW_MARK_MENU, allowMarkMenu()); - - config.writeEntry(KEY_PERSISTENT_SELECTION, persistentSelection()); - - config.writeEntry(KEY_AUTOMATIC_COMPLETION_INVOCATION, automaticCompletionInvocation()); - config.writeEntry(KEY_WORD_COMPLETION, wordCompletion()); - config.writeEntry(KEY_KEYWORD_COMPLETION, keywordCompletion()); - config.writeEntry(KEY_WORD_COMPLETION_MINIMAL_WORD_LENGTH, wordCompletionMinimalWordLength()); - config.writeEntry(KEY_WORD_COMPLETION_REMOVE_TAIL, wordCompletionRemoveTail()); - - config.writeEntry(KEY_SMART_COPY_CUT, smartCopyCut()); - config.writeEntry(KEY_MOUSE_PASTE_AT_CURSOR_POSITION, mousePasteAtCursorPosition()); - config.writeEntry(KEY_SCROLL_PAST_END, scrollPastEnd()); - config.writeEntry(KEY_FOLD_FIRST_LINE, foldFirstLine()); - - config.writeEntry(KEY_INPUT_MODE, static_cast(inputMode())); - config.writeEntry(KEY_VI_INPUT_MODE_STEAL_KEYS, viInputModeStealKeys()); - config.writeEntry(KEY_VI_RELATIVE_LINE_NUMBERS, viRelativeLineNumbers()); - - config.writeEntry(KEY_SHOW_LINE_COUNT, showLineCount()); - config.writeEntry(KEY_SHOW_WORD_COUNT, showWordCount()); - config.writeEntry(KEY_AUTO_BRACKETS, autoBrackets()); - - config.writeEntry(KEY_BACKSPACE_REMOVE_COMPOSED, backspaceRemoveComposed()); } void KateViewConfig::updateConfig() { if (m_view) { m_view->updateConfig(); return; } if (isGlobal()) { foreach (KTextEditor::ViewPrivate* view, KTextEditor::EditorPrivate::self()->views()) { view->updateConfig(); } // write config KConfigGroup cg(KTextEditor::EditorPrivate::config(), "KTextEditor View"); writeConfig(cg); KTextEditor::EditorPrivate::config()->sync(); } } -bool KateViewConfig::dynWordWrap() const -{ - if (m_dynWordWrapSet || isGlobal()) { - return m_dynWordWrap; - } - - return s_global->dynWordWrap(); -} - -void KateViewConfig::setDynWordWrap(bool wrap) -{ - if (m_dynWordWrapSet && m_dynWordWrap == wrap) { - return; - } - - configStart(); - - m_dynWordWrapSet = true; - m_dynWordWrap = wrap; - - configEnd(); -} - -bool KateViewConfig::dynWrapAtStaticMarker() const -{ - if (m_dynWrapAtStaticMarkerSet || isGlobal()) { - return m_dynWrapAtStaticMarker; - } - - return s_global->dynWrapAtStaticMarker(); -} - -void KateViewConfig::setDynWrapAtStaticMarker(bool on) -{ - if (m_dynWrapAtStaticMarkerSet && m_dynWrapAtStaticMarker == on) { - return; - } - - configStart(); - - m_dynWrapAtStaticMarkerSet = true; - m_dynWrapAtStaticMarker = on; - - configEnd(); -} - -int KateViewConfig::dynWordWrapIndicators() const -{ - if (m_dynWordWrapIndicatorsSet || isGlobal()) { - return m_dynWordWrapIndicators; - } - - return s_global->dynWordWrapIndicators(); -} - -void KateViewConfig::setDynWordWrapIndicators(int mode) -{ - if (m_dynWordWrapIndicatorsSet && m_dynWordWrapIndicators == mode) { - return; - } - - configStart(); - - m_dynWordWrapIndicatorsSet = true; - m_dynWordWrapIndicators = qBound(0, mode, 80); - - configEnd(); -} - -int KateViewConfig::dynWordWrapAlignIndent() const -{ - if (m_dynWordWrapAlignIndentSet || isGlobal()) { - return m_dynWordWrapAlignIndent; - } - - return s_global->dynWordWrapAlignIndent(); -} - -void KateViewConfig::setDynWordWrapAlignIndent(int indent) -{ - if (m_dynWordWrapAlignIndentSet && m_dynWordWrapAlignIndent == indent) { - return; - } - - configStart(); - - m_dynWordWrapAlignIndentSet = true; - m_dynWordWrapAlignIndent = indent; - - configEnd(); -} - -bool KateViewConfig::lineNumbers() const -{ - if (m_lineNumbersSet || isGlobal()) { - return m_lineNumbers; - } - - return s_global->lineNumbers(); -} - -void KateViewConfig::setLineNumbers(bool on) -{ - if (m_lineNumbersSet && m_lineNumbers == on) { - return; - } - - configStart(); - - m_lineNumbersSet = true; - m_lineNumbers = on; - - configEnd(); -} - -bool KateViewConfig::scrollBarMarks() const -{ - if (m_scrollBarMarksSet || isGlobal()) { - return m_scrollBarMarks; - } - - return s_global->scrollBarMarks(); -} - -void KateViewConfig::setScrollBarMarks(bool on) -{ - if (m_scrollBarMarksSet && m_scrollBarMarks == on) { - return; - } - - configStart(); - - m_scrollBarMarksSet = true; - m_scrollBarMarks = on; - - configEnd(); -} - -bool KateViewConfig::scrollBarPreview() const -{ - if (m_scrollBarPreviewSet || isGlobal()) { - return m_scrollBarPreview; - } - - return s_global->scrollBarPreview(); -} - -void KateViewConfig::setScrollBarPreview(bool on) -{ - if (m_scrollBarPreviewSet && m_scrollBarPreview == on) { - return; - } - - configStart(); - - m_scrollBarPreviewSet = true; - m_scrollBarPreview = on; - - configEnd(); -} - -bool KateViewConfig::scrollBarMiniMap() const -{ - if (m_scrollBarMiniMapSet || isGlobal()) { - return m_scrollBarMiniMap; - } - - return s_global->scrollBarMiniMap(); -} - -void KateViewConfig::setScrollBarMiniMap(bool on) -{ - if (m_scrollBarMiniMapSet && m_scrollBarMiniMap == on) { - return; - } - - configStart(); - - m_scrollBarMiniMapSet = true; - m_scrollBarMiniMap = on; - - configEnd(); -} - -bool KateViewConfig::scrollBarMiniMapAll() const -{ - if (m_scrollBarMiniMapAllSet || isGlobal()) { - return m_scrollBarMiniMapAll; - } - - return s_global->scrollBarMiniMapAll(); -} - -void KateViewConfig::setScrollBarMiniMapAll(bool on) -{ - if (m_scrollBarMiniMapAllSet && m_scrollBarMiniMapAll == on) { - return; - } - - configStart(); - - m_scrollBarMiniMapAllSet = true; - m_scrollBarMiniMapAll = on; - - configEnd(); -} - -int KateViewConfig::scrollBarMiniMapWidth() const -{ - if (m_scrollBarMiniMapWidthSet || isGlobal()) { - return m_scrollBarMiniMapWidth; - } - - return s_global->scrollBarMiniMapWidth(); -} - -void KateViewConfig::setScrollBarMiniMapWidth(int width) -{ - if (m_scrollBarMiniMapWidthSet && m_scrollBarMiniMapWidth == width) { - return; - } - - configStart(); - - m_scrollBarMiniMapWidthSet = true; - m_scrollBarMiniMapWidth = width; - - configEnd(); -} - -int KateViewConfig::showScrollbars() const -{ - if (m_showScrollbarsSet || isGlobal()) { - return m_showScrollbars; - } - - return s_global->showScrollbars(); -} - -void KateViewConfig::setShowScrollbars(int mode) -{ - if (m_showScrollbarsSet && m_showScrollbars == mode) { - return; - } - - configStart(); - - m_showScrollbarsSet = true; - m_showScrollbars = qBound(0, mode, 80); - - configEnd(); -} - -bool KateViewConfig::autoBrackets() const -{ - if (m_autoBracketsSet || isGlobal()) { - return m_autoBrackets; - } - - return s_global->autoBrackets(); -} - -void KateViewConfig::setAutoBrackets(bool on) -{ - if (m_autoBracketsSet && m_autoBrackets == on) { - return; - } - - configStart(); - - m_autoBracketsSet = true; - m_autoBrackets = on; - - configEnd(); -} - -bool KateViewConfig::iconBar() const -{ - if (m_iconBarSet || isGlobal()) { - return m_iconBar; - } - - return s_global->iconBar(); -} - -void KateViewConfig::setIconBar(bool on) -{ - if (m_iconBarSet && m_iconBar == on) { - return; - } - - configStart(); - - m_iconBarSet = true; - m_iconBar = on; - - configEnd(); -} - -bool KateViewConfig::foldingBar() const -{ - if (m_foldingBarSet || isGlobal()) { - return m_foldingBar; - } - - return s_global->foldingBar(); -} - -void KateViewConfig::setFoldingBar(bool on) -{ - if (m_foldingBarSet && m_foldingBar == on) { - return; - } - - configStart(); - - m_foldingBarSet = true; - m_foldingBar = on; - - configEnd(); -} - -bool KateViewConfig::foldingPreview() const -{ - if (m_foldingPreviewSet || isGlobal()) { - return m_foldingPreview; - } - - return s_global->foldingPreview(); -} - -void KateViewConfig::setFoldingPreview(bool on) -{ - if (m_foldingPreviewSet && m_foldingPreview == on) { - return; - } - - configStart(); - - m_foldingPreviewSet = true; - m_foldingPreview = on; - - configEnd(); -} - -bool KateViewConfig::lineModification() const -{ - if (m_lineModificationSet || isGlobal()) { - return m_lineModification; - } - - return s_global->lineModification(); -} - -void KateViewConfig::setLineModification(bool on) -{ - if (m_lineModificationSet && m_lineModification == on) { - return; - } - - configStart(); - - m_lineModificationSet = true; - m_lineModification = on; - - configEnd(); -} - -int KateViewConfig::bookmarkSort() const -{ - if (m_bookmarkSortSet || isGlobal()) { - return m_bookmarkSort; - } - - return s_global->bookmarkSort(); -} - -void KateViewConfig::setBookmarkSort(int mode) -{ - if (m_bookmarkSortSet && m_bookmarkSort == mode) { - return; - } - - configStart(); - - m_bookmarkSortSet = true; - m_bookmarkSort = mode; - - configEnd(); -} - -int KateViewConfig::autoCenterLines() const -{ - if (m_autoCenterLinesSet || isGlobal()) { - return m_autoCenterLines; - } - - return s_global->autoCenterLines(); -} - -void KateViewConfig::setAutoCenterLines(int lines) -{ - if (lines < 0) { - return; - } - - if (m_autoCenterLinesSet && m_autoCenterLines == lines) { - return; - } - - configStart(); - - m_autoCenterLinesSet = true; - m_autoCenterLines = lines; - - configEnd(); -} - -long KateViewConfig::searchFlags() const -{ - if (m_searchFlagsSet || isGlobal()) { - return m_searchFlags; - } - - return s_global->searchFlags(); -} - -void KateViewConfig::setSearchFlags(long flags) -{ - if (m_searchFlagsSet && m_searchFlags == flags) { - return; - } - - configStart(); - - m_searchFlagsSet = true; - m_searchFlags = flags; - - configEnd(); -} - -int KateViewConfig::maxHistorySize() const -{ - return m_maxHistorySize; -} - -uint KateViewConfig::defaultMarkType() const -{ - if (m_defaultMarkTypeSet || isGlobal()) { - return m_defaultMarkType; - } - - return s_global->defaultMarkType(); -} - -void KateViewConfig::setDefaultMarkType(uint type) -{ - if (m_defaultMarkTypeSet && m_defaultMarkType == type) { - return; - } - - configStart(); - - m_defaultMarkTypeSet = true; - m_defaultMarkType = type; - - configEnd(); -} - -bool KateViewConfig::allowMarkMenu() const -{ - return m_allowMarkMenu; -} - -void KateViewConfig::setAllowMarkMenu(bool allow) -{ - m_allowMarkMenu = allow; -} - -bool KateViewConfig::persistentSelection() const -{ - if (m_persistentSelectionSet || isGlobal()) { - return m_persistentSelection; - } - - return s_global->persistentSelection(); -} - -void KateViewConfig::setPersistentSelection(bool on) -{ - if (m_persistentSelectionSet && m_persistentSelection == on) { - return; - } - - configStart(); - - m_persistentSelectionSet = true; - m_persistentSelection = on; - - configEnd(); -} - -KTextEditor::View::InputMode KateViewConfig::inputMode() const -{ - if (m_inputModeSet || isGlobal()) { - return m_inputMode; - } - - return s_global->inputMode(); -} - -void KateViewConfig::setInputMode(KTextEditor::View::InputMode mode) -{ - if (m_inputModeSet && m_inputMode == mode) { - return; - } - - configStart(); - - m_inputModeSet = true; - m_inputMode = mode; - - configEnd(); -} - -void KateViewConfig::setInputModeRaw(int rawmode) -{ - setInputMode(static_cast(rawmode)); -} - -bool KateViewConfig::viInputModeStealKeys() const -{ - if (m_viInputModeStealKeysSet || isGlobal()) { - return m_viInputModeStealKeys; - } - - return s_global->viInputModeStealKeys(); -} - -void KateViewConfig::setViInputModeStealKeys(bool on) -{ - if (m_viInputModeStealKeysSet && m_viInputModeStealKeys == on) { - return; - } - - configStart(); - m_viInputModeStealKeysSet = true; - m_viInputModeStealKeys = on; - configEnd(); -} - -bool KateViewConfig::viRelativeLineNumbers() const -{ - if (m_viRelativeLineNumbersSet || isGlobal()) { - return m_viRelativeLineNumbers; - } - - return s_global->viRelativeLineNumbers(); -} - -void KateViewConfig::setViRelativeLineNumbers(bool on) -{ - if (m_viRelativeLineNumbersSet && m_viRelativeLineNumbers == on) { - return; - } - - configStart(); - m_viRelativeLineNumbersSet = true; - m_viRelativeLineNumbers = on; - configEnd(); -} - -bool KateViewConfig::automaticCompletionInvocation() const -{ - if (m_automaticCompletionInvocationSet || isGlobal()) { - return m_automaticCompletionInvocation; - } - - return s_global->automaticCompletionInvocation(); -} - -void KateViewConfig::setAutomaticCompletionInvocation(bool on) -{ - if (m_automaticCompletionInvocationSet && m_automaticCompletionInvocation == on) { - return; - } - - configStart(); - m_automaticCompletionInvocationSet = true; - m_automaticCompletionInvocation = on; - configEnd(); -} - -bool KateViewConfig::wordCompletion() const -{ - if (m_wordCompletionSet || isGlobal()) { - return m_wordCompletion; - } - - return s_global->wordCompletion(); -} - -void KateViewConfig::setWordCompletion(bool on) -{ - if (m_wordCompletionSet && m_wordCompletion == on) { - return; - } - - configStart(); - m_wordCompletionSet = true; - m_wordCompletion = on; - configEnd(); -} - -bool KateViewConfig::keywordCompletion() const -{ - if (m_keywordCompletionSet || isGlobal()) - return m_keywordCompletion; - return s_global->keywordCompletion(); -} - -void KateViewConfig::setKeywordCompletion(bool on) -{ - if (m_keywordCompletionSet && m_keywordCompletion == on) - return; - configStart(); - m_keywordCompletionSet = true; - m_keywordCompletion = on; - configEnd(); -} - -int KateViewConfig::wordCompletionMinimalWordLength() const -{ - if (m_wordCompletionMinimalWordLengthSet || isGlobal()) { - return m_wordCompletionMinimalWordLength; - } - - return s_global->wordCompletionMinimalWordLength(); -} - -void KateViewConfig::setWordCompletionMinimalWordLength(int length) -{ - if (m_wordCompletionMinimalWordLengthSet && m_wordCompletionMinimalWordLength == length) { - return; - } - - configStart(); - - m_wordCompletionMinimalWordLengthSet = true; - m_wordCompletionMinimalWordLength = length; - - configEnd(); -} - -bool KateViewConfig::wordCompletionRemoveTail() const -{ - if (m_wordCompletionRemoveTailSet || isGlobal()) { - return m_wordCompletionRemoveTail; - } - - return s_global->wordCompletionRemoveTail(); -} - -void KateViewConfig::setWordCompletionRemoveTail(bool on) -{ - if (m_wordCompletionRemoveTailSet && m_wordCompletionRemoveTail == on) { - return; - } - - configStart(); - m_wordCompletionRemoveTailSet = true; - m_wordCompletionRemoveTail = on; - configEnd(); -} - -bool KateViewConfig::smartCopyCut() const -{ - if (m_smartCopyCutSet || isGlobal()) { - return m_smartCopyCut; - } - - return s_global->smartCopyCut(); -} - -void KateViewConfig::setSmartCopyCut(bool on) -{ - if (m_smartCopyCutSet && m_smartCopyCut == on) { - return; - } - - configStart(); - - m_smartCopyCutSet = true; - m_smartCopyCut = on; - - configEnd(); -} - -bool KateViewConfig::mousePasteAtCursorPosition() const -{ - if (m_mousePasteAtCursorPositionSet|| isGlobal()) { - return m_mousePasteAtCursorPosition; - } - - return s_global->mousePasteAtCursorPosition(); -} - -void KateViewConfig::setMousePasteAtCursorPosition(bool on) -{ - if (m_mousePasteAtCursorPositionSet && m_mousePasteAtCursorPosition == on) { - return; - } - - configStart(); - - m_mousePasteAtCursorPositionSet = true; - m_mousePasteAtCursorPosition = on; - - configEnd(); -} - -bool KateViewConfig::scrollPastEnd() const -{ - if (m_scrollPastEndSet || isGlobal()) { - return m_scrollPastEnd; - } - - return s_global->scrollPastEnd(); -} - -void KateViewConfig::setScrollPastEnd(bool on) -{ - if (m_scrollPastEndSet && m_scrollPastEnd == on) { - return; - } - - configStart(); - - m_scrollPastEndSet = true; - m_scrollPastEnd = on; - - configEnd(); -} - -bool KateViewConfig::foldFirstLine() const -{ - if (m_foldFirstLineSet || isGlobal()) { - return m_foldFirstLine; - } - - return s_global->foldFirstLine(); -} - -void KateViewConfig::setFoldFirstLine(bool on) -{ - if (m_foldFirstLineSet && m_foldFirstLine == on) { - return; - } - - configStart(); - - m_foldFirstLineSet = true; - m_foldFirstLine = on; - - configEnd(); -} - -bool KateViewConfig::showWordCount() const -{ - if (m_showWordCountSet || isGlobal()) { - return m_showWordCount; - } - - return s_global->showWordCount(); -} - -void KateViewConfig::setShowWordCount(bool on) -{ - if (m_showWordCountSet && m_showWordCount == on) { - return; - } - - configStart(); - m_showWordCountSet = true; - m_showWordCount = on; - configEnd(); -} - -bool KateViewConfig::showLineCount() const -{ - if (m_showLineCountSet || isGlobal()) { - return m_showLineCount; - } - - return s_global->showLineCount(); -} - -void KateViewConfig::setShowLineCount(bool on) -{ - if (m_showLineCountSet && m_showLineCount == on) { - return; - } - - configStart(); - m_showLineCountSet = true; - m_showLineCount = on; - configEnd(); -} - -bool KateViewConfig::backspaceRemoveComposed() const -{ - if (m_backspaceRemoveComposedSet || isGlobal()) { - return m_backspaceRemoveComposed; - } - - return s_global->backspaceRemoveComposed(); -} - -void KateViewConfig::setBackspaceRemoveComposed(bool on) -{ - if (m_backspaceRemoveComposedSet && m_backspaceRemoveComposed == on) { - return; - } - - configStart(); - - m_backspaceRemoveComposedSet = true; - m_backspaceRemoveComposed = on; - - configEnd(); -} - //END //BEGIN KateRendererConfig KateRendererConfig::KateRendererConfig() : m_fontMetrics(QFont()), m_lineMarkerColor(KTextEditor::MarkInterface::reservedMarkersCount()), m_schemaSet(false), m_fontSet(false), m_wordWrapMarkerSet(false), m_showIndentationLinesSet(false), m_showWholeBracketExpressionSet(false), m_backgroundColorSet(false), m_selectionColorSet(false), m_highlightedLineColorSet(false), m_highlightedBracketColorSet(false), m_wordWrapMarkerColorSet(false), m_tabMarkerColorSet(false), m_indentationLineColorSet(false), m_iconBarColorSet(false), m_foldingColorSet(false), m_lineNumberColorSet(false), m_currentLineNumberColorSet(false), m_separatorColorSet(false), m_spellingMistakeLineColorSet(false), m_templateColorsSet(false), m_modifiedLineColorSet(false), m_savedLineColorSet(false), m_searchHighlightColorSet(false), m_replaceHighlightColorSet(false), m_lineMarkerColorSet(m_lineMarkerColor.size()) { // init bitarray m_lineMarkerColorSet.fill(true); s_global = this; // init with defaults from config or really hardcoded ones KConfigGroup config(KTextEditor::EditorPrivate::config(), "KTextEditor Renderer"); readConfig(config); } KateRendererConfig::KateRendererConfig(KateRenderer *renderer) : KateConfig(s_global), m_fontMetrics(QFont()), m_lineMarkerColor(KTextEditor::MarkInterface::reservedMarkersCount()), m_schemaSet(false), m_fontSet(false), m_wordWrapMarkerSet(false), m_showIndentationLinesSet(false), m_showWholeBracketExpressionSet(false), m_backgroundColorSet(false), m_selectionColorSet(false), m_highlightedLineColorSet(false), m_highlightedBracketColorSet(false), m_wordWrapMarkerColorSet(false), m_tabMarkerColorSet(false), m_indentationLineColorSet(false), m_iconBarColorSet(false), m_foldingColorSet(false), m_lineNumberColorSet(false), m_currentLineNumberColorSet(false), m_separatorColorSet(false), m_spellingMistakeLineColorSet(false), m_templateColorsSet(false), m_modifiedLineColorSet(false), m_savedLineColorSet(false), m_searchHighlightColorSet(false), m_replaceHighlightColorSet(false), m_lineMarkerColorSet(m_lineMarkerColor.size()), m_renderer(renderer) { // init bitarray m_lineMarkerColorSet.fill(false); } KateRendererConfig::~KateRendererConfig() { } namespace { const char KEY_SCHEMA[] = "Schema"; const char KEY_WORD_WRAP_MARKER[] = "Word Wrap Marker"; const char KEY_SHOW_INDENTATION_LINES[] = "Show Indentation Lines"; const char KEY_SHOW_WHOLE_BRACKET_EXPRESSION[] = "Show Whole Bracket Expression"; const char KEY_ANIMATE_BRACKET_MATCHING[] = "Animate Bracket Matching"; } void KateRendererConfig::readConfig(const KConfigGroup &config) { configStart(); // read generic entries readConfigEntries(config); // "Normal" Schema MUST BE THERE, see global kateschemarc setSchema(config.readEntry(KEY_SCHEMA, "Normal")); setWordWrapMarker(config.readEntry(KEY_WORD_WRAP_MARKER, false)); setShowIndentationLines(config.readEntry(KEY_SHOW_INDENTATION_LINES, false)); setShowWholeBracketExpression(config.readEntry(KEY_SHOW_WHOLE_BRACKET_EXPRESSION, false)); setAnimateBracketMatching(config.readEntry(KEY_ANIMATE_BRACKET_MATCHING, false)); configEnd(); } void KateRendererConfig::writeConfig(KConfigGroup &config) { // write generic entries writeConfigEntries(config); config.writeEntry(KEY_SCHEMA, schema()); config.writeEntry(KEY_WORD_WRAP_MARKER, wordWrapMarker()); config.writeEntry(KEY_SHOW_INDENTATION_LINES, showIndentationLines()); config.writeEntry(KEY_SHOW_WHOLE_BRACKET_EXPRESSION, showWholeBracketExpression()); config.writeEntry(KEY_ANIMATE_BRACKET_MATCHING, animateBracketMatching()); } void KateRendererConfig::updateConfig() { if (m_renderer) { m_renderer->updateConfig(); return; } if (isGlobal()) { for (int z = 0; z < KTextEditor::EditorPrivate::self()->views().size(); ++z) { (KTextEditor::EditorPrivate::self()->views())[z]->renderer()->updateConfig(); } // write config KConfigGroup cg(KTextEditor::EditorPrivate::config(), "KTextEditor Renderer"); writeConfig(cg); KTextEditor::EditorPrivate::config()->sync(); } } const QString &KateRendererConfig::schema() const { if (m_schemaSet || isGlobal()) { return m_schema; } return s_global->schema(); } void KateRendererConfig::setSchema(const QString &schema) { if (m_schemaSet && m_schema == schema) { return; } configStart(); m_schemaSet = true; m_schema = schema; setSchemaInternal(schema); configEnd(); } void KateRendererConfig::reloadSchema() { if (isGlobal()) { setSchemaInternal(m_schema); foreach (KTextEditor::ViewPrivate *view, KTextEditor::EditorPrivate::self()->views()) { view->renderer()->config()->reloadSchema(); } } else if (m_renderer && m_schemaSet) { setSchemaInternal(m_schema); } // trigger renderer/view update if (m_renderer) { m_renderer->updateConfig(); } } void KateRendererConfig::setSchemaInternal(const QString &schema) { m_schemaSet = true; m_schema = schema; KConfigGroup config = KTextEditor::EditorPrivate::self()->schemaManager()->schema(schema); // use global color instance, creation is expensive! const KateDefaultColors &colors(KTextEditor::EditorPrivate::self()->defaultColors()); m_backgroundColor = config.readEntry("Color Background", colors.color(Kate::Background)); m_backgroundColorSet = true; m_selectionColor = config.readEntry("Color Selection", colors.color(Kate::SelectionBackground)); m_selectionColorSet = true; m_highlightedLineColor = config.readEntry("Color Highlighted Line", colors.color(Kate::HighlightedLineBackground)); m_highlightedLineColorSet = true; m_highlightedBracketColor = config.readEntry("Color Highlighted Bracket", colors.color(Kate::HighlightedBracket)); m_highlightedBracketColorSet = true; m_wordWrapMarkerColor = config.readEntry("Color Word Wrap Marker", colors.color(Kate::WordWrapMarker)); m_wordWrapMarkerColorSet = true; m_tabMarkerColor = config.readEntry("Color Tab Marker", colors.color(Kate::TabMarker)); m_tabMarkerColorSet = true; m_indentationLineColor = config.readEntry("Color Indentation Line", colors.color(Kate::IndentationLine)); m_indentationLineColorSet = true; m_iconBarColor = config.readEntry("Color Icon Bar", colors.color(Kate::IconBar)); m_iconBarColorSet = true; m_foldingColor = config.readEntry("Color Code Folding", colors.color(Kate::CodeFolding)); m_foldingColorSet = true; m_lineNumberColor = config.readEntry("Color Line Number", colors.color(Kate::LineNumber)); m_lineNumberColorSet = true; m_currentLineNumberColor = config.readEntry("Color Current Line Number", colors.color(Kate::CurrentLineNumber)); m_currentLineNumberColorSet = true; m_separatorColor = config.readEntry("Color Separator", colors.color(Kate::Separator)); m_separatorColorSet = true; m_spellingMistakeLineColor = config.readEntry("Color Spelling Mistake Line", colors.color(Kate::SpellingMistakeLine)); m_spellingMistakeLineColorSet = true; m_modifiedLineColor = config.readEntry("Color Modified Lines", colors.color(Kate::ModifiedLine)); m_modifiedLineColorSet = true; m_savedLineColor = config.readEntry("Color Saved Lines", colors.color(Kate::SavedLine)); m_savedLineColorSet = true; m_searchHighlightColor = config.readEntry("Color Search Highlight", colors.color(Kate::SearchHighlight)); m_searchHighlightColorSet = true; m_replaceHighlightColor = config.readEntry("Color Replace Highlight", colors.color(Kate::ReplaceHighlight)); m_replaceHighlightColorSet = true; for (int i = Kate::FIRST_MARK; i <= Kate::LAST_MARK; i++) { QColor col = config.readEntry(QStringLiteral("Color MarkType %1").arg(i + 1), colors.mark(i)); m_lineMarkerColorSet[i] = true; m_lineMarkerColor[i] = col; } setFontWithDroppedStyleName(config.readEntry("Font", QFontDatabase::systemFont(QFontDatabase::FixedFont))); m_templateBackgroundColor = config.readEntry(QStringLiteral("Color Template Background"), colors.color(Kate::TemplateBackground)); m_templateFocusedEditablePlaceholderColor = config.readEntry(QStringLiteral("Color Template Focused Editable Placeholder"), colors.color(Kate::TemplateFocusedEditablePlaceholder)); m_templateEditablePlaceholderColor = config.readEntry(QStringLiteral("Color Template Editable Placeholder"), colors.color(Kate::TemplateEditablePlaceholder)); m_templateNotEditablePlaceholderColor = config.readEntry(QStringLiteral("Color Template Not Editable Placeholder"), colors.color(Kate::TemplateNotEditablePlaceholder)); m_templateColorsSet = true; } const QFont &KateRendererConfig::font() const { if (m_fontSet || isGlobal()) { return m_font; } return s_global->font(); } const QFontMetricsF &KateRendererConfig::fontMetrics() const { if (m_fontSet || isGlobal()) { return m_fontMetrics; } return s_global->fontMetrics(); } void KateRendererConfig::setFont(const QFont &font) { if (m_fontSet && m_font == font) { return; } configStart(); setFontWithDroppedStyleName(font); configEnd(); } void KateRendererConfig::setFontWithDroppedStyleName(const QFont &font) { /** * Drop styleName, otherwise stuff like bold/italic/... won't work as style! */ m_font = font; m_font.setStyleName(QString()); m_fontMetrics = QFontMetricsF(m_font); m_fontSet = true; } bool KateRendererConfig::wordWrapMarker() const { if (m_wordWrapMarkerSet || isGlobal()) { return m_wordWrapMarker; } return s_global->wordWrapMarker(); } void KateRendererConfig::setWordWrapMarker(bool on) { if (m_wordWrapMarkerSet && m_wordWrapMarker == on) { return; } configStart(); m_wordWrapMarkerSet = true; m_wordWrapMarker = on; configEnd(); } const QColor &KateRendererConfig::backgroundColor() const { if (m_backgroundColorSet || isGlobal()) { return m_backgroundColor; } return s_global->backgroundColor(); } void KateRendererConfig::setBackgroundColor(const QColor &col) { if (m_backgroundColorSet && m_backgroundColor == col) { return; } configStart(); m_backgroundColorSet = true; m_backgroundColor = col; configEnd(); } const QColor &KateRendererConfig::selectionColor() const { if (m_selectionColorSet || isGlobal()) { return m_selectionColor; } return s_global->selectionColor(); } void KateRendererConfig::setSelectionColor(const QColor &col) { if (m_selectionColorSet && m_selectionColor == col) { return; } configStart(); m_selectionColorSet = true; m_selectionColor = col; configEnd(); } const QColor &KateRendererConfig::highlightedLineColor() const { if (m_highlightedLineColorSet || isGlobal()) { return m_highlightedLineColor; } return s_global->highlightedLineColor(); } void KateRendererConfig::setHighlightedLineColor(const QColor &col) { if (m_highlightedLineColorSet && m_highlightedLineColor == col) { return; } configStart(); m_highlightedLineColorSet = true; m_highlightedLineColor = col; configEnd(); } const QColor &KateRendererConfig::lineMarkerColor(KTextEditor::MarkInterface::MarkTypes type) const { int index = 0; if (type > 0) { while ((type >> index++) ^ 1) {} } index -= 1; if (index < 0 || index >= KTextEditor::MarkInterface::reservedMarkersCount()) { static QColor dummy; return dummy; } if (m_lineMarkerColorSet[index] || isGlobal()) { return m_lineMarkerColor[index]; } return s_global->lineMarkerColor(type); } void KateRendererConfig::setLineMarkerColor(const QColor &col, KTextEditor::MarkInterface::MarkTypes type) { int index = static_cast(log(static_cast(type)) / log(2.0)); Q_ASSERT(index >= 0 && index < KTextEditor::MarkInterface::reservedMarkersCount()); if (m_lineMarkerColorSet[index] && m_lineMarkerColor[index] == col) { return; } configStart(); m_lineMarkerColorSet[index] = true; m_lineMarkerColor[index] = col; configEnd(); } const QColor &KateRendererConfig::highlightedBracketColor() const { if (m_highlightedBracketColorSet || isGlobal()) { return m_highlightedBracketColor; } return s_global->highlightedBracketColor(); } void KateRendererConfig::setHighlightedBracketColor(const QColor &col) { if (m_highlightedBracketColorSet && m_highlightedBracketColor == col) { return; } configStart(); m_highlightedBracketColorSet = true; m_highlightedBracketColor = col; configEnd(); } const QColor &KateRendererConfig::wordWrapMarkerColor() const { if (m_wordWrapMarkerColorSet || isGlobal()) { return m_wordWrapMarkerColor; } return s_global->wordWrapMarkerColor(); } void KateRendererConfig::setWordWrapMarkerColor(const QColor &col) { if (m_wordWrapMarkerColorSet && m_wordWrapMarkerColor == col) { return; } configStart(); m_wordWrapMarkerColorSet = true; m_wordWrapMarkerColor = col; configEnd(); } const QColor &KateRendererConfig::tabMarkerColor() const { if (m_tabMarkerColorSet || isGlobal()) { return m_tabMarkerColor; } return s_global->tabMarkerColor(); } void KateRendererConfig::setTabMarkerColor(const QColor &col) { if (m_tabMarkerColorSet && m_tabMarkerColor == col) { return; } configStart(); m_tabMarkerColorSet = true; m_tabMarkerColor = col; configEnd(); } const QColor &KateRendererConfig::indentationLineColor() const { if (m_indentationLineColorSet || isGlobal()) { return m_indentationLineColor; } return s_global->indentationLineColor(); } void KateRendererConfig::setIndentationLineColor(const QColor &col) { if (m_indentationLineColorSet && m_indentationLineColor == col) { return; } configStart(); m_indentationLineColorSet = true; m_indentationLineColor = col; configEnd(); } const QColor &KateRendererConfig::iconBarColor() const { if (m_iconBarColorSet || isGlobal()) { return m_iconBarColor; } return s_global->iconBarColor(); } void KateRendererConfig::setIconBarColor(const QColor &col) { if (m_iconBarColorSet && m_iconBarColor == col) { return; } configStart(); m_iconBarColorSet = true; m_iconBarColor = col; configEnd(); } const QColor &KateRendererConfig::foldingColor() const { if (m_foldingColorSet || isGlobal()) { return m_foldingColor; } return s_global->foldingColor(); } void KateRendererConfig::setFoldingColor(const QColor &col) { if (m_foldingColorSet && m_foldingColor == col) { return; } configStart(); m_foldingColorSet = true; m_foldingColor = col; configEnd(); } const QColor &KateRendererConfig::templateBackgroundColor() const { if (m_templateColorsSet || isGlobal()) { return m_templateBackgroundColor; } return s_global->templateBackgroundColor(); } const QColor &KateRendererConfig::templateEditablePlaceholderColor() const { if (m_templateColorsSet || isGlobal()) { return m_templateEditablePlaceholderColor; } return s_global->templateEditablePlaceholderColor(); } const QColor &KateRendererConfig::templateFocusedEditablePlaceholderColor() const { if (m_templateColorsSet || isGlobal()) { return m_templateFocusedEditablePlaceholderColor; } return s_global->templateFocusedEditablePlaceholderColor(); } const QColor &KateRendererConfig::templateNotEditablePlaceholderColor() const { if (m_templateColorsSet || isGlobal()) { return m_templateNotEditablePlaceholderColor; } return s_global->templateNotEditablePlaceholderColor(); } const QColor &KateRendererConfig::lineNumberColor() const { if (m_lineNumberColorSet || isGlobal()) { return m_lineNumberColor; } return s_global->lineNumberColor(); } void KateRendererConfig::setLineNumberColor(const QColor &col) { if (m_lineNumberColorSet && m_lineNumberColor == col) { return; } configStart(); m_lineNumberColorSet = true; m_lineNumberColor = col; configEnd(); } const QColor &KateRendererConfig::currentLineNumberColor() const { if (m_currentLineNumberColorSet || isGlobal()) { return m_currentLineNumberColor; } return s_global->currentLineNumberColor(); } void KateRendererConfig::setCurrentLineNumberColor(const QColor &col) { if (m_currentLineNumberColorSet && m_currentLineNumberColor == col) { return; } configStart(); m_currentLineNumberColorSet = true; m_currentLineNumberColor = col; configEnd(); } const QColor &KateRendererConfig::separatorColor() const { if (m_separatorColorSet || isGlobal()) { return m_separatorColor; } return s_global->separatorColor(); } void KateRendererConfig::setSeparatorColor(const QColor &col) { if (m_separatorColorSet && m_separatorColor == col) { return; } configStart(); m_separatorColorSet = true; m_separatorColor = col; configEnd(); } const QColor &KateRendererConfig::spellingMistakeLineColor() const { if (m_spellingMistakeLineColorSet || isGlobal()) { return m_spellingMistakeLineColor; } return s_global->spellingMistakeLineColor(); } void KateRendererConfig::setSpellingMistakeLineColor(const QColor &col) { if (m_spellingMistakeLineColorSet && m_spellingMistakeLineColor == col) { return; } configStart(); m_spellingMistakeLineColorSet = true; m_spellingMistakeLineColor = col; configEnd(); } const QColor &KateRendererConfig::modifiedLineColor() const { if (m_modifiedLineColorSet || isGlobal()) { return m_modifiedLineColor; } return s_global->modifiedLineColor(); } void KateRendererConfig::setModifiedLineColor(const QColor &col) { if (m_modifiedLineColorSet && m_modifiedLineColor == col) { return; } configStart(); m_modifiedLineColorSet = true; m_modifiedLineColor = col; configEnd(); } const QColor &KateRendererConfig::savedLineColor() const { if (m_savedLineColorSet || isGlobal()) { return m_savedLineColor; } return s_global->savedLineColor(); } void KateRendererConfig::setSavedLineColor(const QColor &col) { if (m_savedLineColorSet && m_savedLineColor == col) { return; } configStart(); m_savedLineColorSet = true; m_savedLineColor = col; configEnd(); } const QColor &KateRendererConfig::searchHighlightColor() const { if (m_searchHighlightColorSet || isGlobal()) { return m_searchHighlightColor; } return s_global->searchHighlightColor(); } void KateRendererConfig::setSearchHighlightColor(const QColor &col) { if (m_searchHighlightColorSet && m_searchHighlightColor == col) { return; } configStart(); m_searchHighlightColorSet = true; m_searchHighlightColor = col; configEnd(); } const QColor &KateRendererConfig::replaceHighlightColor() const { if (m_replaceHighlightColorSet || isGlobal()) { return m_replaceHighlightColor; } return s_global->replaceHighlightColor(); } void KateRendererConfig::setReplaceHighlightColor(const QColor &col) { if (m_replaceHighlightColorSet && m_replaceHighlightColor == col) { return; } configStart(); m_replaceHighlightColorSet = true; m_replaceHighlightColor = col; configEnd(); } bool KateRendererConfig::showIndentationLines() const { if (m_showIndentationLinesSet || isGlobal()) { return m_showIndentationLines; } return s_global->showIndentationLines(); } void KateRendererConfig::setShowIndentationLines(bool on) { if (m_showIndentationLinesSet && m_showIndentationLines == on) { return; } configStart(); m_showIndentationLinesSet = true; m_showIndentationLines = on; configEnd(); } bool KateRendererConfig::showWholeBracketExpression() const { if (m_showWholeBracketExpressionSet || isGlobal()) { return m_showWholeBracketExpression; } return s_global->showWholeBracketExpression(); } void KateRendererConfig::setShowWholeBracketExpression(bool on) { if (m_showWholeBracketExpressionSet && m_showWholeBracketExpression == on) { return; } configStart(); m_showWholeBracketExpressionSet = true; m_showWholeBracketExpression = on; configEnd(); } bool KateRendererConfig::animateBracketMatching() const { return s_global->m_animateBracketMatching; } void KateRendererConfig::setAnimateBracketMatching(bool on) { if (!isGlobal()) { s_global->setAnimateBracketMatching(on); } else if (on != m_animateBracketMatching) { configStart(); m_animateBracketMatching = on; configEnd(); } } //END diff --git a/src/utils/kateconfig.h b/src/utils/kateconfig.h index f4a1e352..af4b135a 100644 --- a/src/utils/kateconfig.h +++ b/src/utils/kateconfig.h @@ -1,1397 +1,1450 @@ /* This file is part of the KDE libraries Copyright (C) 2003 Christoph Cullmann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KATE_CONFIG_H #define KATE_CONFIG_H #include #include #include "ktexteditor/view.h" #include #include #include #include #include #include #include #include #include class KConfigGroup; namespace KTextEditor { class ViewPrivate; } namespace KTextEditor { class DocumentPrivate; } class KateRenderer; namespace KTextEditor { class EditorPrivate; } class KConfig; class QTextCodec; /** * Base Class for the Kate Config Classes + * Current childs are KateDocumentConfig/KateDocumentConfig/KateDocumentConfig */ class KTEXTEDITOR_EXPORT KateConfig { public: /** - * start some config changes - * this method is needed to init some kind of transaction - * for config changes, update will only be done once, at - * configEnd() call + * Start some config changes. + * This method is needed to init some kind of transaction for config changes, + * update will only be done once, at configEnd() call. */ void configStart(); /** - * end a config change transaction, update the concerned - * documents/views/renderers + * End a config change transaction, update the concerned + * KateDocumentConfig/KateDocumentConfig/KateDocumentConfig */ void configEnd(); /** - * global config? - * @return global config object? + * Is this a global config object? + * @return true when this is a global config object */ bool isGlobal() const { return !m_parent; } /** * All known config keys. * This will use the knowledge about all registered keys of the global object. * @return all known config keys */ QStringList configKeys() const { return m_parent ? m_parent->configKeys() : *m_configKeys.get(); } /** * Get a config value. * @param key config key, aka enum from KateConfig* classes * @return value for the wanted key, will assert if key is not valid */ QVariant value(const int key) const; /** * Set a config value. * Will assert if key is invalid. * Might not alter the value if given value fails validation. * @param key config key, aka enum from KateConfig* classes * @param value value to set - * @return value set? + * @return true on success */ bool setValue(const int key, const QVariant &value); /** * Get a config value for the string key. * @param key config key, aka commandName from KateConfig* classes - * @return value for the wanted key, will return invalid variant if key invalid + * @return value for the wanted key, will return invalid variant if key is not known */ QVariant value(const QString &key) const; /** * Set a config value. - * Will ignore set if key not valid - * Might not alter the value if given value fails validation. + * Will do nothing if key is not known or the given value fails validation. * @param key config key, aka commandName from KateConfig* classes * @param value value to set - * @return value set? + * @return true on success */ bool setValue(const QString &key, const QVariant &value); protected: /** * Construct a KateConfig. * @param parent parent config object, if any */ KateConfig(const KateConfig *parent = nullptr); /** * Virtual Destructor */ virtual ~KateConfig(); /** * One config entry. */ class ConfigEntry { public: /** * Construct one config entry. * @param enumId value of the enum for this config entry * @param configId value of the key for the KConfig file for this config entry * @param command command name * @param defaultVal default value * @param valid validator function, default none */ ConfigEntry(int enumId, const char *configId, QString command, QVariant defaultVal, std::function valid = nullptr) : enumKey(enumId) , configKey(configId) , commandName(command) , defaultValue(defaultVal) , value(defaultVal) , validator(valid) { } /** * Enum key for this config entry, shall be unique */ const int enumKey; /** * KConfig entry key for this config entry, shall be unique in its group * e.g. "Tab Width" */ const char * const configKey; /** * Command name as used in e.g. ConfigInterface or modeline/command line * e.g. tab-width */ const QString commandName; /** - * Default value if nothing configured + * Default value if nothing special was configured */ const QVariant defaultValue; /** - * concrete value, per default == defaultValue + * The concrete value, per default == defaultValue */ QVariant value; /** - * a validator function, only if this returns true we accept a value for the entry - * is ignored if not set + * An optional validator function, only when these returns true + * we accept a given new value. + * Is no validator set, we accept any value. */ std::function validator; }; /** - * Read all config entries from given config group + * Read all config entries from given config group. * @param config config group to read from */ void readConfigEntries(const KConfigGroup &config); /** - * Write all config entries to given config group + * Write all config entries to given config group. * @param config config group to write to */ void writeConfigEntries(KConfigGroup &config) const; /** * Register a new config entry. * Used by the sub classes to register all there known ones. * @param entry new entry to add */ void addConfigEntry(ConfigEntry &&entry); /** * Finalize the config entries. * Called by the sub classes after all entries are registered */ void finalizeConfigEntries(); /** * do the real update */ virtual void updateConfig() = 0; private: /** * Get full map of config entries, aka the m_configEntries of the top config object * @return full map with all config entries */ const std::map &fullConfigEntries () const { return m_parent ? m_parent->fullConfigEntries() : m_configEntries; } /** * Get hash of config entries, aka the m_configKeyToEntry of the top config object * @return full hash with all config entries */ const QHash &fullConfigKeyToEntry () const { return m_parent ? m_parent->fullConfigKeyToEntry() : *m_configKeyToEntry.get(); } private: /** * parent config object, if any */ const KateConfig * const m_parent = nullptr; /** * recursion depth */ uint configSessionNumber = 0; /** * is a config session running */ bool configIsRunning = false; /** * two cases: * - we have m_parent == nullptr => this contains all known config entries * - we have m_parent != nullptr => this contains all set config entries for this level of configuration * * uses a map ATM for deterministic iteration e.g. for read/writeConfig */ std::map m_configEntries; /** * All known config keys, filled only for the object with m_parent == nullptr */ std::unique_ptr m_configKeys; /** * Hash of config keys => config entry, filled only for the object with m_parent == nullptr */ std::unique_ptr> m_configKeyToEntry; }; class KTEXTEDITOR_EXPORT KateGlobalConfig : public KateConfig { private: friend class KTextEditor::EditorPrivate; /** * only used in KTextEditor::EditorPrivate for the static global fallback !!! */ KateGlobalConfig(); public: static KateGlobalConfig *global() { return s_global; } /** * Known config entries */ enum ConfigEntryTypes { /** * Encoding prober */ EncodingProberType, /** * Fallback encoding */ FallbackEncoding }; public: /** * Read config from object */ void readConfig(const KConfigGroup &config); /** * Write config to object */ void writeConfig(KConfigGroup &config); protected: void updateConfig() override; public: KEncodingProber::ProberType proberType() const { return KEncodingProber::ProberType(value(EncodingProberType).toInt()); } bool setProberType(KEncodingProber::ProberType type) { return setValue(EncodingProberType, type); } /** * Fallback codec. * Based on fallback encoding. * @return fallback codec */ QTextCodec *fallbackCodec() const; QString fallbackEncoding() const { return value(FallbackEncoding).toString(); } bool setFallbackEncoding(const QString &encoding) { return setValue(FallbackEncoding, encoding); } private: static KateGlobalConfig *s_global; }; class KTEXTEDITOR_EXPORT KateDocumentConfig : public KateConfig { private: friend class KTextEditor::EditorPrivate; KateDocumentConfig(); public: /** * Construct a DocumentConfig */ explicit KateDocumentConfig(KTextEditor::DocumentPrivate *doc); inline static KateDocumentConfig *global() { return s_global; } /** * Known config entries */ enum ConfigEntryTypes { /** * Tabulator width */ TabWidth, /** * Indentation width */ IndentationWidth, /** * On-the-fly spellcheck enabled? */ OnTheFlySpellCheck, /** * Indent pasted text? */ IndentOnTextPaste, /** * Replace tabs with spaces? */ ReplaceTabsWithSpaces, /** * Backup files for local files? */ BackupOnSaveLocal, /** * Backup files for remote files? */ BackupOnSaveRemote, /** * Prefix for backup files */ BackupOnSavePrefix, /** * Suffix for backup files */ BackupOnSaveSuffix, /** * Indentation mode, like "normal" */ IndentationMode, /** * Tab handling, like indent, insert tab, smart */ TabHandlingMode, /** * Static word wrap? */ StaticWordWrap, /** * Static word wrap column */ StaticWordWrapColumn, /** * PageUp/Down moves cursor? */ PageUpDownMovesCursor, /** * Smart Home key? */ SmartHome, /** * Show Tabs? */ ShowTabs, /** * Indent on tab? */ IndentOnTab, /** * Keep extra space? */ KeepExtraSpaces, /** * Backspace key indents? */ BackspaceIndents, /** * Show spaces mode like none, all, ... */ ShowSpacesMode, /** * Trailing Marker Size */ TrailingMarkerSize, /** * Remove spaces mode */ RemoveSpacesMode, /** * Ensure newline at end of file */ NewlineAtEOF, /** * Overwrite mode? */ OverwriteMode, /** * Encoding */ Encoding, /** * End of line mode: dos, mac, unix */ EndOfLine, /** * Allow EOL detection */ AllowEndOfLineDetection, /** * Use Byte Order Mark */ ByteOrderMark, /** * Swap file mode */ SwapFile, /** * Swap file directory */ SwapFileDirectory, /** * Swap file sync interval */ SwapFileSyncInterval, /** * Line length limit */ LineLengthLimit }; public: /** * Read config from object */ void readConfig(const KConfigGroup &config); /** * Write config to object */ void writeConfig(KConfigGroup &config); protected: void updateConfig() override; public: int tabWidth() const { return value(TabWidth).toInt(); } void setTabWidth(int tabWidth) { setValue(TabWidth, QVariant(tabWidth)); } int indentationWidth() const { return value(IndentationWidth).toInt(); } void setIndentationWidth(int indentationWidth) { setValue(IndentationWidth, QVariant(indentationWidth)); } bool onTheFlySpellCheck() const { return value(OnTheFlySpellCheck).toBool(); } void setOnTheFlySpellCheck(bool on) { setValue(OnTheFlySpellCheck, QVariant(on)); } bool indentPastedText() const { return value(IndentOnTextPaste).toBool(); } void setIndentPastedText(bool on) { setValue(IndentOnTextPaste, QVariant(on)); } bool replaceTabsDyn() const { return value(ReplaceTabsWithSpaces).toBool(); } void setReplaceTabsDyn(bool on) { setValue(ReplaceTabsWithSpaces, QVariant(on)); } bool backupOnSaveLocal() const { return value(BackupOnSaveLocal).toBool(); } void setBackupOnSaveLocal(bool on) { setValue(BackupOnSaveLocal, QVariant(on)); } bool backupOnSaveRemote() const { return value(BackupOnSaveRemote).toBool(); } void setBackupOnSaveRemote(bool on) { setValue(BackupOnSaveRemote, QVariant(on)); } QString backupPrefix() const { return value(BackupOnSavePrefix).toString(); } void setBackupPrefix(const QString &prefix) { setValue(BackupOnSavePrefix, QVariant(prefix)); } QString backupSuffix() const { return value(BackupOnSaveSuffix).toString(); } void setBackupSuffix(const QString &suffix) { setValue(BackupOnSaveSuffix, QVariant(suffix)); } QString indentationMode() const { return value(IndentationMode).toString(); } void setIndentationMode(const QString &identationMode) { setValue(IndentationMode, identationMode); } enum TabHandling { tabInsertsTab = 0, tabIndents = 1, tabSmart = 2 //!< indents in leading space, otherwise inserts tab }; enum WhitespaceRendering { None, Trailing, All }; int tabHandling() const { return value(TabHandlingMode).toInt(); } void setTabHandling(int tabHandling) { setValue(TabHandlingMode, tabHandling); } bool wordWrap() const { return value(StaticWordWrap).toBool(); } void setWordWrap(bool on) { setValue(StaticWordWrap, on); } int wordWrapAt() const { return value(StaticWordWrapColumn).toInt(); } void setWordWrapAt(int col) { setValue(StaticWordWrapColumn, col); } bool pageUpDownMovesCursor() const { return value(PageUpDownMovesCursor).toBool(); } void setPageUpDownMovesCursor(bool on) { setValue(PageUpDownMovesCursor, on); } void setKeepExtraSpaces(bool on) { setValue(KeepExtraSpaces, on); } bool keepExtraSpaces() const { return value(KeepExtraSpaces).toBool(); } void setBackspaceIndents(bool on) { setValue(BackspaceIndents, on); } bool backspaceIndents() const { return value(BackspaceIndents).toBool(); } void setSmartHome(bool on) { setValue(SmartHome, on); } bool smartHome() const { return value(SmartHome).toBool(); } void setShowTabs(bool on) { setValue(ShowTabs, on); } bool showTabs() const { return value(ShowTabs).toBool(); } void setShowSpaces(WhitespaceRendering mode) { setValue(ShowSpacesMode, mode); } WhitespaceRendering showSpaces() const { return WhitespaceRendering(value(ShowSpacesMode).toInt()); } void setMarkerSize(int markerSize) { setValue(TrailingMarkerSize, markerSize); } int markerSize() const { return value(TrailingMarkerSize).toInt(); } /** * Remove trailing spaces on save. * triState = 0: never remove trailing spaces * triState = 1: remove trailing spaces of modified lines (line modification system) * triState = 2: remove trailing spaces in entire document */ void setRemoveSpaces(int triState) { setValue(RemoveSpacesMode, triState); } int removeSpaces() const { return value(RemoveSpacesMode).toInt(); } void setNewLineAtEof(bool on) { setValue(NewlineAtEOF, on); } bool newLineAtEof() const { return value(NewlineAtEOF).toBool(); } void setOvr(bool on) { setValue(OverwriteMode, on); } bool ovr() const { return value(OverwriteMode).toBool(); } void setTabIndents(bool on) { setValue(IndentOnTab, on); } bool tabIndentsEnabled() const { return value(IndentOnTab).toBool(); } /** * Get current text codec. * Based on current set encoding * @return current text codec. */ QTextCodec *codec() const; QString encoding() const { return value(Encoding).toString(); } bool setEncoding(const QString &encoding) { return setValue(Encoding, encoding); } enum Eol { eolUnix = 0, eolDos = 1, eolMac = 2 }; int eol() const { return value(EndOfLine).toInt(); } /** * Get current end of line string. * Based on current set eol mode. * @return current end of line string */ QString eolString(); void setEol(int mode) { setValue(EndOfLine, mode); } bool bom() const { return value(ByteOrderMark).toBool(); } void setBom(bool bom) { setValue(ByteOrderMark, bom); } bool allowEolDetection() const { return value(AllowEndOfLineDetection).toBool(); } void setAllowEolDetection(bool on) { setValue(AllowEndOfLineDetection, on); } QString swapDirectory() const { return value(SwapFileDirectory).toString(); } void setSwapDirectory(const QString &directory) { setValue(SwapFileDirectory, directory); } enum SwapFileMode { DisableSwapFile = 0, EnableSwapFile, SwapFilePresetDirectory }; SwapFileMode swapFileMode() const { return SwapFileMode(value(SwapFile).toInt()); } void setSwapFileMode(int mode) { setValue(SwapFile, mode); } int swapSyncInterval() const { return value(SwapFileSyncInterval).toInt(); } void setSwapSyncInterval(int interval) { setValue(SwapFileSyncInterval, interval); } int lineLengthLimit() const { return value(LineLengthLimit).toInt(); } void setLineLengthLimit(int limit) { setValue(LineLengthLimit, limit); } private: static KateDocumentConfig *s_global; KTextEditor::DocumentPrivate *m_doc = nullptr; }; class KTEXTEDITOR_EXPORT KateViewConfig : public KateConfig { private: friend class KTextEditor::EditorPrivate; /** * only used in KTextEditor::EditorPrivate for the static global fallback !!! */ KateViewConfig(); public: /** - * Construct a DocumentConfig + * Construct a ViewConfig */ explicit KateViewConfig(KTextEditor::ViewPrivate *view); /** - * Cu DocumentConfig + * Cu ViewConfig */ ~KateViewConfig() override; inline static KateViewConfig *global() { return s_global; } + /** + * All known config keys + * Keep them sorted alphabetic for our convenience + */ + enum ConfigEntryTypes { + AllowMarkMenu, + AutoBrackets, + AutoCenterLines, + AutomaticCompletionInvocation, + BackspaceRemoveComposedCharacters, + BookmarkSorting, + CharsToEncloseSelection, + DefaultMarkType, + DynWordWrapAlignIndent, + DynWordWrapIndicators, + DynWrapAtStaticMarker, + DynamicWordWrap, + FoldFirstLine, + InputMode, + KeywordCompletion, + MaxHistorySize, + MousePasteAtCursorPosition, + PersistentSelection, + ScrollBarMiniMapWidth, + ScrollPastEnd, + SearchFlags, + ShowFoldingBar, + ShowFoldingPreview, + ShowIconBar, + ShowLineCount, + ShowLineModification, + ShowLineNumbers, + ShowScrollBarMarks, + ShowScrollBarMiniMap, + ShowScrollBarMiniMapAll, + ShowScrollBarPreview, + ShowScrollbars, + ShowWordCount, + SmartCopyCut, + ViInputModeStealKeys, + ViRelativeLineNumbers, + WordCompletion, + WordCompletionMinimalWordLength, + WordCompletionRemoveTail, + }; + public: /** * Read config from object */ void readConfig(const KConfigGroup &config); /** * Write config to object */ void writeConfig(KConfigGroup &config); protected: void updateConfig() override; public: - bool dynWordWrapSet() const + bool dynWordWrap() const + { + return value(DynamicWordWrap).toBool(); + } + void setDynWordWrap(bool on) { - return m_dynWordWrapSet; + setValue(DynamicWordWrap, on); } - bool dynWordWrap() const; - void setDynWordWrap(bool wrap); - bool dynWrapAtStaticMarker() const; - void setDynWrapAtStaticMarker(bool on); + bool dynWrapAtStaticMarker() const + { + return value(DynWrapAtStaticMarker).toBool(); + } - int dynWordWrapIndicators() const; - void setDynWordWrapIndicators(int mode); + int dynWordWrapIndicators() const + { + return value(DynWordWrapIndicators).toInt(); + } - int dynWordWrapAlignIndent() const; - void setDynWordWrapAlignIndent(int indent); + int dynWordWrapAlignIndent() const + { + return value(DynWordWrapAlignIndent).toInt(); + } - bool lineNumbers() const; - void setLineNumbers(bool on); + bool lineNumbers() const + { + return value(ShowLineNumbers).toBool(); + } - bool scrollBarMarks() const; - void setScrollBarMarks(bool on); + bool scrollBarMarks() const + { + return value(ShowScrollBarMarks).toBool(); + } - bool scrollBarPreview() const; - void setScrollBarPreview(bool on); + bool scrollBarPreview() const + { + return value(ShowScrollBarPreview).toBool(); + } - bool scrollBarMiniMap() const; - void setScrollBarMiniMap(bool on); + bool scrollBarMiniMap() const + { + return value(ShowScrollBarMiniMap).toBool(); + } - bool scrollBarMiniMapAll() const; - void setScrollBarMiniMapAll(bool on); + bool scrollBarMiniMapAll() const + { + return value(ShowScrollBarMiniMapAll).toBool(); + } - int scrollBarMiniMapWidth() const; - void setScrollBarMiniMapWidth(int width); + int scrollBarMiniMapWidth() const + { + return value(ScrollBarMiniMapWidth).toInt(); + } /* Whether to show scrollbars */ enum ScrollbarMode { AlwaysOn = 0, ShowWhenNeeded, AlwaysOff }; - int showScrollbars() const; - void setShowScrollbars(int mode); + int showScrollbars() const + { + return value(ShowScrollbars).toInt(); + } - bool iconBar() const; - void setIconBar(bool on); + bool iconBar() const + { + return value(ShowIconBar).toBool(); + } - bool foldingBar() const; - void setFoldingBar(bool on); + bool foldingBar() const + { + return value(ShowFoldingBar).toBool(); + } - bool foldingPreview() const; - void setFoldingPreview(bool on); + bool foldingPreview() const + { + return value(ShowFoldingPreview).toBool(); + } - bool lineModification() const; - void setLineModification(bool on); + bool lineModification() const + { + return value(ShowLineModification).toBool(); + } - int bookmarkSort() const; - void setBookmarkSort(int mode); + int bookmarkSort() const + { + return value(BookmarkSorting).toInt(); + } - int autoCenterLines() const; - void setAutoCenterLines(int lines); + int autoCenterLines() const + { + return value(AutoCenterLines).toInt(); + } enum SearchFlags { IncMatchCase = 1 << 0, IncHighlightAll = 1 << 1, IncFromCursor = 1 << 2, PowerMatchCase = 1 << 3, PowerHighlightAll = 1 << 4, PowerFromCursor = 1 << 5, // PowerSelectionOnly = 1 << 6, Better not save to file // Sebastian PowerModePlainText = 1 << 7, PowerModeWholeWords = 1 << 8, PowerModeEscapeSequences = 1 << 9, PowerModeRegularExpression = 1 << 10, PowerUsePlaceholders = 1 << 11 }; - long searchFlags() const; - void setSearchFlags(long flags); + uint searchFlags() const + { + return value(SearchFlags).toUInt(); + } + void setSearchFlags(uint flags) + { + setValue(SearchFlags, flags); + } - int maxHistorySize() const; + int maxHistorySize() const + { + return value(MaxHistorySize).toInt(); + } - uint defaultMarkType() const; - void setDefaultMarkType(uint type); + uint defaultMarkType() const + { + return value(DefaultMarkType).toUInt(); + } - bool allowMarkMenu() const; - void setAllowMarkMenu(bool allow); + bool allowMarkMenu() const + { + return value(AllowMarkMenu).toBool(); + } - bool persistentSelection() const; - void setPersistentSelection(bool on); + bool persistentSelection() const + { + return value(PersistentSelection).toBool(); + } - KTextEditor::View::InputMode inputMode() const; - void setInputMode(KTextEditor::View::InputMode mode); - void setInputModeRaw(int rawmode); + KTextEditor::View::InputMode inputMode() const + { + return static_cast(value(InputMode).toUInt()); + } - bool viInputModeStealKeys() const; - void setViInputModeStealKeys(bool on); + bool viInputModeStealKeys() const + { + return value(ViInputModeStealKeys).toBool(); + } - bool viRelativeLineNumbers() const; - void setViRelativeLineNumbers(bool on); + bool viRelativeLineNumbers() const + { + return value(ViRelativeLineNumbers).toBool(); + } // Do we still need the enum and related functions below? enum TextToSearch { Nowhere = 0, SelectionOnly = 1, SelectionWord = 2, WordOnly = 3, WordSelection = 4 }; - bool automaticCompletionInvocation() const; - void setAutomaticCompletionInvocation(bool on); - - bool wordCompletion() const; - void setWordCompletion(bool on); + bool automaticCompletionInvocation() const + { + return value(AutomaticCompletionInvocation).toBool(); + } - bool keywordCompletion () const; - void setKeywordCompletion (bool on); + bool wordCompletion() const + { + return value(WordCompletion).toBool(); + } - int wordCompletionMinimalWordLength() const; - void setWordCompletionMinimalWordLength(int length); + bool keywordCompletion () const + { + return value(KeywordCompletion).toBool(); + } - bool wordCompletionRemoveTail() const; - void setWordCompletionRemoveTail(bool on); + int wordCompletionMinimalWordLength() const + { + return value(WordCompletionMinimalWordLength).toInt(); + } - bool smartCopyCut() const; - void setSmartCopyCut(bool on); + bool wordCompletionRemoveTail() const + { + return value(WordCompletionRemoveTail).toBool(); + } - bool mousePasteAtCursorPosition() const; - void setMousePasteAtCursorPosition(bool on); + bool smartCopyCut() const + { + return value(SmartCopyCut).toBool(); + } - bool scrollPastEnd() const; - void setScrollPastEnd(bool on); + bool mousePasteAtCursorPosition() const + { + return value(MousePasteAtCursorPosition).toBool(); + } - bool foldFirstLine() const; - void setFoldFirstLine(bool on); + bool scrollPastEnd() const + { + return value(ScrollPastEnd).toBool(); + } - bool showWordCount() const; - void setShowWordCount(bool on); + bool foldFirstLine() const + { + return value(FoldFirstLine).toBool(); + } - bool showLineCount() const; - void setShowLineCount(bool on); + bool showWordCount() const + { + return value(ShowWordCount).toBool(); + } + void setShowWordCount(bool on) + { + setValue(ShowWordCount, on); + } - bool autoBrackets() const; - void setAutoBrackets(bool on); + bool showLineCount() const + { + return value(ShowLineCount).toBool(); + } - bool backspaceRemoveComposed() const; - void setBackspaceRemoveComposed(bool on); + bool autoBrackets() const + { + return value(AutoBrackets).toBool(); + } -private: - bool m_dynWordWrap; - bool m_dynWrapAtStaticMarker; - int m_dynWordWrapIndicators; - int m_dynWordWrapAlignIndent; - bool m_lineNumbers; - bool m_scrollBarMarks; - bool m_scrollBarPreview; - bool m_scrollBarMiniMap; - bool m_scrollBarMiniMapAll; - int m_scrollBarMiniMapWidth; - int m_showScrollbars; - bool m_iconBar; - bool m_foldingBar; - bool m_foldingPreview; - bool m_lineModification; - int m_bookmarkSort; - int m_autoCenterLines; - long m_searchFlags; - int m_maxHistorySize; - uint m_defaultMarkType; - bool m_persistentSelection; - KTextEditor::View::InputMode m_inputMode; - bool m_viInputModeStealKeys; - bool m_viRelativeLineNumbers; - bool m_automaticCompletionInvocation; - bool m_wordCompletion; - bool m_keywordCompletion; - int m_wordCompletionMinimalWordLength; - bool m_wordCompletionRemoveTail; - bool m_smartCopyCut; - bool m_mousePasteAtCursorPosition = false; - bool m_scrollPastEnd; - bool m_foldFirstLine; - bool m_showWordCount = false; - bool m_showLineCount = false; - bool m_autoBrackets; - bool m_backspaceRemoveComposed; - - bool m_dynWordWrapSet : 1; - bool m_dynWrapAtStaticMarkerSet : 1; - bool m_dynWordWrapIndicatorsSet : 1; - bool m_dynWordWrapAlignIndentSet : 1; - bool m_lineNumbersSet : 1; - bool m_scrollBarMarksSet : 1; - bool m_scrollBarPreviewSet : 1; - bool m_scrollBarMiniMapSet : 1; - bool m_scrollBarMiniMapAllSet : 1; - bool m_scrollBarMiniMapWidthSet : 1; - bool m_showScrollbarsSet : 1; - bool m_iconBarSet : 1; - bool m_foldingBarSet : 1; - bool m_foldingPreviewSet : 1; - bool m_lineModificationSet : 1; - bool m_bookmarkSortSet : 1; - bool m_autoCenterLinesSet : 1; - bool m_searchFlagsSet : 1; - bool m_defaultMarkTypeSet : 1; - bool m_persistentSelectionSet : 1; - bool m_inputModeSet : 1; - bool m_viInputModeStealKeysSet : 1; - bool m_viRelativeLineNumbersSet : 1; - bool m_automaticCompletionInvocationSet : 1; - bool m_wordCompletionSet : 1; - bool m_keywordCompletionSet : 1; - bool m_wordCompletionMinimalWordLengthSet : 1; - bool m_smartCopyCutSet : 1; - bool m_mousePasteAtCursorPositionSet : 1; - bool m_scrollPastEndSet : 1; - bool m_allowMarkMenu : 1; - bool m_wordCompletionRemoveTailSet : 1; - bool m_foldFirstLineSet : 1; - bool m_showWordCountSet : 1; - bool m_showLineCountSet : 1; - bool m_autoBracketsSet : 1; - bool m_backspaceRemoveComposedSet : 1; + bool backspaceRemoveComposed() const + { + return value(BackspaceRemoveComposedCharacters).toBool(); + } private: static KateViewConfig *s_global; KTextEditor::ViewPrivate *m_view = nullptr; }; class KTEXTEDITOR_EXPORT KateRendererConfig : public KateConfig { private: friend class KTextEditor::EditorPrivate; /** * only used in KTextEditor::EditorPrivate for the static global fallback !!! */ KateRendererConfig(); public: /** - * Construct a DocumentConfig + * Construct a RendererConfig */ explicit KateRendererConfig(KateRenderer *renderer); /** - * Cu DocumentConfig + * Cu RendererConfig */ ~KateRendererConfig() override; inline static KateRendererConfig *global() { return s_global; } public: /** * Read config from object */ void readConfig(const KConfigGroup &config); /** * Write config to object */ void writeConfig(KConfigGroup &config); protected: void updateConfig() override; public: const QString &schema() const; void setSchema(const QString &schema); /** * Reload the schema from the schema manager. * For the global instance, have all other instances reload. * Used by the schema config page to apply changes. */ void reloadSchema(); const QFont &font() const; const QFontMetricsF &fontMetrics() const; void setFont(const QFont &font); bool wordWrapMarker() const; void setWordWrapMarker(bool on); const QColor &backgroundColor() const; void setBackgroundColor(const QColor &col); const QColor &selectionColor() const; void setSelectionColor(const QColor &col); const QColor &highlightedLineColor() const; void setHighlightedLineColor(const QColor &col); const QColor &lineMarkerColor(KTextEditor::MarkInterface::MarkTypes type = KTextEditor::MarkInterface::markType01) const; // markType01 == Bookmark void setLineMarkerColor(const QColor &col, KTextEditor::MarkInterface::MarkTypes type = KTextEditor::MarkInterface::markType01); const QColor &highlightedBracketColor() const; void setHighlightedBracketColor(const QColor &col); const QColor &wordWrapMarkerColor() const; void setWordWrapMarkerColor(const QColor &col); const QColor &tabMarkerColor() const; void setTabMarkerColor(const QColor &col); const QColor &indentationLineColor() const; void setIndentationLineColor(const QColor &col); const QColor &iconBarColor() const; void setIconBarColor(const QColor &col); const QColor &foldingColor() const; void setFoldingColor(const QColor &col); // the line number color is used for the line numbers on the left bar const QColor &lineNumberColor() const; void setLineNumberColor(const QColor &col); const QColor ¤tLineNumberColor() const; void setCurrentLineNumberColor(const QColor &col); // the color of the separator between line numbers and icon bar const QColor &separatorColor() const; void setSeparatorColor(const QColor &col); const QColor &spellingMistakeLineColor() const; void setSpellingMistakeLineColor(const QColor &col); bool showIndentationLines() const; void setShowIndentationLines(bool on); bool showWholeBracketExpression() const; void setShowWholeBracketExpression(bool on); bool animateBracketMatching() const; void setAnimateBracketMatching(bool on); const QColor &templateBackgroundColor() const; const QColor &templateEditablePlaceholderColor() const; const QColor &templateFocusedEditablePlaceholderColor() const; const QColor &templateNotEditablePlaceholderColor() const; const QColor &modifiedLineColor() const; void setModifiedLineColor(const QColor &col); const QColor &savedLineColor() const; void setSavedLineColor(const QColor &col); const QColor &searchHighlightColor() const; void setSearchHighlightColor(const QColor &col); const QColor &replaceHighlightColor() const; void setReplaceHighlightColor(const QColor &col); private: /** * Read the schema properties from the config file. */ void setSchemaInternal(const QString &schema); /** * Set the font but drop style name before that. * Otherwise e.g. styles like bold/italic/... will not work */ void setFontWithDroppedStyleName(const QFont &font); QString m_schema; QFont m_font; QFontMetricsF m_fontMetrics; QColor m_backgroundColor; QColor m_selectionColor; QColor m_highlightedLineColor; QColor m_highlightedBracketColor; QColor m_wordWrapMarkerColor; QColor m_tabMarkerColor; QColor m_indentationLineColor; QColor m_iconBarColor; QColor m_foldingColor; QColor m_lineNumberColor; QColor m_currentLineNumberColor; QColor m_separatorColor; QColor m_spellingMistakeLineColor; QVector m_lineMarkerColor; QColor m_templateBackgroundColor; QColor m_templateEditablePlaceholderColor; QColor m_templateFocusedEditablePlaceholderColor; QColor m_templateNotEditablePlaceholderColor; QColor m_modifiedLineColor; QColor m_savedLineColor; QColor m_searchHighlightColor; QColor m_replaceHighlightColor; bool m_wordWrapMarker = false; bool m_showIndentationLines = false; bool m_showWholeBracketExpression = false; bool m_animateBracketMatching = false; bool m_schemaSet : 1; bool m_fontSet : 1; bool m_wordWrapMarkerSet : 1; bool m_showIndentationLinesSet : 1; bool m_showWholeBracketExpressionSet : 1; bool m_backgroundColorSet : 1; bool m_selectionColorSet : 1; bool m_highlightedLineColorSet : 1; bool m_highlightedBracketColorSet : 1; bool m_wordWrapMarkerColorSet : 1; bool m_tabMarkerColorSet : 1; bool m_indentationLineColorSet : 1; bool m_iconBarColorSet : 1; bool m_foldingColorSet : 1; bool m_lineNumberColorSet : 1; bool m_currentLineNumberColorSet : 1; bool m_separatorColorSet : 1; bool m_spellingMistakeLineColorSet : 1; bool m_templateColorsSet : 1; bool m_modifiedLineColorSet : 1; bool m_savedLineColorSet : 1; bool m_searchHighlightColorSet : 1; bool m_replaceHighlightColorSet : 1; QBitArray m_lineMarkerColorSet; private: static KateRendererConfig *s_global; KateRenderer *m_renderer = nullptr; }; #endif diff --git a/src/view/katestatusbar.cpp b/src/view/katestatusbar.cpp index da48abde..4f48f7ec 100644 --- a/src/view/katestatusbar.cpp +++ b/src/view/katestatusbar.cpp @@ -1,582 +1,582 @@ /* This file is part of the KDE and the Kate project * * Copyright (C) 2013 Dominik Haumann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "katestatusbar.h" #include "katemodemenu.h" #include "kateglobal.h" #include "katemodemanager.h" #include "katedocument.h" #include "kateconfig.h" #include "kateabstractinputmode.h" #include "wordcounter.h" #include #include #include #include #include #include #include //BEGIN menu KateStatusBarOpenUpMenu::KateStatusBarOpenUpMenu(QWidget *parent) : QMenu(parent) {} KateStatusBarOpenUpMenu::~KateStatusBarOpenUpMenu() {} void KateStatusBarOpenUpMenu::setVisible(bool visibility) { if (visibility) { QRect geo = geometry(); QPoint pos = parentWidget()->mapToGlobal(QPoint(0, 0)); geo.moveTopLeft(QPoint(pos.x(), pos.y() - geo.height())); if (geo.top() < 0) geo.moveTop(0); setGeometry(geo); } QMenu::setVisible(visibility); } //END menu //BEGIN StatusBarButton StatusBarButton::StatusBarButton(KateStatusBar *parent, const QString &text /*= QString()*/) : QPushButton(text, parent) { setFlat(true); setFocusProxy(parent->m_view); setMinimumSize(QSize(1, minimumSizeHint().height())); } StatusBarButton::~StatusBarButton() { } //END StatusBarButton KateStatusBar::KateStatusBar(KTextEditor::ViewPrivate *view) : KateViewBarWidget(false) , m_view(view) , m_modifiedStatus(-1) , m_selectionMode(-1) , m_wordCounter(nullptr) { KAcceleratorManager::setNoAccel(this); setFocusProxy(m_view); /** * just add our status bar to central widget, full sized */ QHBoxLayout *topLayout = new QHBoxLayout(centralWidget()); topLayout->setContentsMargins(0, 0, 0, 0); topLayout->setSpacing(4); /** * show modification state of the document * TODO Using a (StatusBar)Button is currently pointless but handy due to no "setIcon()" function in QLabel. * Add some useful action when button is clicked, e.g. save document or show tool-tip * or find a way to not show a "focus frame" when hovered by mouse */ m_modified = new StatusBarButton(this); topLayout->addWidget(m_modified); /** * show Line XXX, Column XXX */ m_cursorPosition = new StatusBarButton(this); topLayout->addWidget(m_cursorPosition); m_cursorPosition->setWhatsThis(i18n("Current cursor position. Click to go to a specific line.")); connect(m_cursorPosition, &StatusBarButton::clicked, m_view, &KTextEditor::ViewPrivate::gotoLine); // Separate the status line in a left and right part topLayout->addStretch(1); /** * show the current mode, like INSERT, OVERWRITE, VI + modifiers like [BLOCK] */ m_inputMode = new StatusBarButton(this); topLayout->addWidget(m_inputMode); m_inputMode->setWhatsThis(i18n("Insert mode and VI input mode indicator. Click to change the mode.")); connect(m_inputMode, &StatusBarButton::clicked, [=] { m_view->currentInputMode()->toggleInsert(); }); /** * Add dictionary button which allows user to switch dictionary of the document */ m_dictionary = new StatusBarButton(this); topLayout->addWidget(m_dictionary, 0); m_dictionary->setWhatsThis(i18n("Change dictionary")); m_dictionaryMenu = new KateStatusBarOpenUpMenu(m_dictionary); m_dictionaryMenu->addAction(m_view->action("tools_change_dictionary")); m_dictionaryMenu->addAction(m_view->action("tools_clear_dictionary_ranges")); m_dictionaryMenu->addAction(m_view->action("tools_toggle_automatic_spell_checking")); m_dictionaryMenu->addAction(m_view->action("tools_spelling_from_cursor")); m_dictionaryMenu->addAction(m_view->action("tools_spelling")); m_dictionaryMenu->addSeparator(); m_dictionaryGroup = new QActionGroup(m_dictionaryMenu); QMapIterator i(Sonnet::Speller().preferredDictionaries()); while (i.hasNext()) { i.next(); QAction *action = m_dictionaryGroup->addAction(i.key()); action->setData(i.value()); action->setToolTip(i.key()); action->setCheckable(true); m_dictionaryMenu->addAction(action); } m_dictionary->setMenu(m_dictionaryMenu); connect(m_dictionaryGroup, &QActionGroup::triggered, this, &KateStatusBar::changeDictionary); /** * allow to change indentation configuration */ m_tabsIndent = new StatusBarButton(this); topLayout->addWidget(m_tabsIndent); m_indentSettingsMenu = new KateStatusBarOpenUpMenu(m_tabsIndent); m_indentSettingsMenu->addSection(i18n("Tab Width")); m_tabGroup = new QActionGroup(this); addNumberAction(m_tabGroup, m_indentSettingsMenu, -1); addNumberAction(m_tabGroup, m_indentSettingsMenu, 8); addNumberAction(m_tabGroup, m_indentSettingsMenu, 4); addNumberAction(m_tabGroup, m_indentSettingsMenu, 2); connect(m_tabGroup, &QActionGroup::triggered, this, &KateStatusBar::slotTabGroup); m_indentSettingsMenu->addSection(i18n("Indentation Width")); m_indentGroup = new QActionGroup(this); addNumberAction(m_indentGroup, m_indentSettingsMenu, -1); addNumberAction(m_indentGroup, m_indentSettingsMenu, 8); addNumberAction(m_indentGroup, m_indentSettingsMenu, 4); addNumberAction(m_indentGroup, m_indentSettingsMenu, 2); connect(m_indentGroup, &QActionGroup::triggered, this, &KateStatusBar::slotIndentGroup); m_indentSettingsMenu->addSection(i18n("Indentation Mode")); QActionGroup *radioGroup = new QActionGroup(m_indentSettingsMenu); m_mixedAction = m_indentSettingsMenu->addAction(i18n("Tabulators && Spaces")); m_mixedAction->setCheckable(true); m_mixedAction->setActionGroup(radioGroup); m_hardAction = m_indentSettingsMenu->addAction(i18n("Tabulators")); m_hardAction->setCheckable(true); m_hardAction->setActionGroup(radioGroup); m_softAction = m_indentSettingsMenu->addAction(i18n("Spaces")); m_softAction->setCheckable(true); m_softAction->setActionGroup(radioGroup); connect(radioGroup, &QActionGroup::triggered, this, &KateStatusBar::slotIndentTabMode); m_tabsIndent->setMenu(m_indentSettingsMenu); /** * add encoding button which allows user to switch encoding of document * this will reuse the encoding action menu of the view */ m_encoding = new StatusBarButton(this); topLayout->addWidget(m_encoding); m_encoding->setMenu(m_view->encodingAction()->menu()); m_encoding->setWhatsThis(i18n("Encoding")); /** * add mode button which allows user to switch mode of document * this will reuse the mode action menu of the view */ m_mode = new StatusBarButton(this); topLayout->addWidget(m_mode); m_mode->setMenu(m_view->modeAction()->menu()); m_mode->setWhatsThis(i18n("Syntax highlighting")); // signals for the statusbar connect(m_view, &KTextEditor::View::cursorPositionChanged, this, &KateStatusBar::cursorPositionChanged); connect(m_view, &KTextEditor::View::viewModeChanged, this, &KateStatusBar::viewModeChanged); connect(m_view, &KTextEditor::View::selectionChanged, this, &KateStatusBar::selectionChanged); connect(m_view->document(), &KTextEditor::DocumentPrivate::modifiedChanged, this, &KateStatusBar::modifiedChanged); connect(m_view->doc(), &KTextEditor::DocumentPrivate::modifiedOnDisk, this, &KateStatusBar::modifiedChanged); connect(m_view->doc(), &KTextEditor::DocumentPrivate::readWriteChanged, this, &KateStatusBar::modifiedChanged); connect(m_view->doc(), &KTextEditor::DocumentPrivate::configChanged, this, &KateStatusBar::documentConfigChanged); connect(m_view->document(), &KTextEditor::DocumentPrivate::modeChanged, this, &KateStatusBar::modeChanged); connect(m_view, &KTextEditor::ViewPrivate::configChanged, this, &KateStatusBar::configChanged); connect(m_view->doc(), &KTextEditor::DocumentPrivate::defaultDictionaryChanged, this, &KateStatusBar::updateDictionary); connect(m_view->doc(), &KTextEditor::DocumentPrivate::dictionaryRangesPresent, this, &KateStatusBar::updateDictionary); connect(m_view, &KTextEditor::ViewPrivate::caretChangedRange, this, &KateStatusBar::updateDictionary); updateStatus(); toggleWordCount(KateViewConfig::global()->showWordCount()); } bool KateStatusBar::eventFilter(QObject *obj, QEvent *event) { return KateViewBarWidget::eventFilter(obj, event); } void KateStatusBar::contextMenuEvent(QContextMenuEvent *event) { // TODO Add option "Show Statusbar" and options to show/hide buttons of the status bar QMenu menu(this); if (childAt(event->pos()) == m_inputMode) { if (QAction *inputModesAction = m_view->actionCollection()->action(QStringLiteral("view_input_modes"))) { if (QMenu *inputModesMenu = inputModesAction->menu()) { const auto actions = inputModesMenu->actions(); for (int i = 0; i < actions.count(); ++i) { menu.addAction(actions.at(i)); } menu.addSeparator(); } } } QAction *showLines = menu.addAction(QStringLiteral("Show line count"), this, &KateStatusBar::toggleShowLines); showLines->setCheckable(true); showLines->setChecked(KateViewConfig::global()->showLineCount()); QAction *showWords = menu.addAction(QStringLiteral("Show word count"), this, &KateStatusBar::toggleShowWords); showWords->setCheckable(true); showWords->setChecked(KateViewConfig::global()->showWordCount()); menu.exec(event->globalPos()); } void KateStatusBar::toggleShowLines(bool checked) { - KateViewConfig::global()->setShowLineCount(checked); + KateViewConfig::global()->setValue(KateViewConfig::ShowLineCount, checked); } void KateStatusBar::toggleShowWords(bool checked) { KateViewConfig::global()->setShowWordCount(checked); } void KateStatusBar::updateStatus() { selectionChanged(); viewModeChanged(); cursorPositionChanged(); modifiedChanged(); documentConfigChanged(); modeChanged(); updateDictionary(); } void KateStatusBar::selectionChanged() { const unsigned int newSelectionMode = m_view->blockSelection(); if (newSelectionMode == m_selectionMode) { return; } // remember new mode and update info m_selectionMode = newSelectionMode; viewModeChanged(); } void KateStatusBar::viewModeChanged() { // prepend BLOCK for block selection mode QString text = m_view->viewModeHuman(); if (m_view->blockSelection()) text = i18n("[BLOCK] %1", text); m_inputMode->setText(text); } void KateStatusBar::cursorPositionChanged() { KTextEditor::Cursor position(m_view->cursorPositionVirtual()); // Update line/column label QString text; if (KateViewConfig::global()->showLineCount()) { text = i18n("Line %1 of %2, Column %3", QLocale().toString(position.line() + 1) , QLocale().toString(m_view->doc()->lines()) , QLocale().toString(position.column() + 1) ); } else { text = i18n("Line %1, Column %2", QLocale().toString(position.line() + 1) , QLocale().toString(position.column() + 1) ); } if (m_wordCounter) { text.append(QStringLiteral(", ") + m_wordCount); } m_cursorPosition->setText(text); } void KateStatusBar::updateDictionary() { QString newDict; // Check if at the current cursor position is a special dictionary in use KTextEditor::Cursor position(m_view->cursorPositionVirtual()); const QList> dictRanges = m_view->doc()->dictionaryRanges(); for (auto rangeDictPair : dictRanges) { const KTextEditor::MovingRange *range = rangeDictPair.first; if (range->contains(position) || range->end() == position) { newDict = rangeDictPair.second; break; } } // Check if the default dictionary is in use if (newDict.isEmpty()) { newDict = m_view->doc()->defaultDictionary(); if (newDict.isEmpty()) { newDict = Sonnet::Speller().defaultLanguage(); } } // Update button and menu only on a changed dictionary if (!m_dictionaryGroup->checkedAction() || (m_dictionaryGroup->checkedAction()->data().toString() != newDict) || m_dictionary->text().isEmpty()) { bool found = false; // Remove "-w_accents -variant_0" and such from dict-code to keep it small and clean m_dictionary->setText(newDict.section(QLatin1Char('-'), 0, 0)); // For maximum user clearness, change the checked menu option m_dictionaryGroup->blockSignals(true); for (auto a : m_dictionaryGroup->actions()) { if (a->data().toString() == newDict) { a->setChecked(true); found = true; break; } } if (!found) { // User has chose some other dictionary from combo box, we need to add that QString dictName = Sonnet::Speller().availableDictionaries().key(newDict); QAction *action = m_dictionaryGroup->addAction(dictName); action->setData(newDict); action->setCheckable(true); action->setChecked(true); m_dictionaryMenu->addAction(action); } m_dictionaryGroup->blockSignals(false); } } void KateStatusBar::modifiedChanged() { const bool mod = m_view->doc()->isModified(); const bool modOnHD = m_view->doc()->isModifiedOnDisc(); const bool readOnly = !m_view->doc()->isReadWrite(); /** * combine to modified status, update only if changed */ unsigned int newStatus = (unsigned int)mod | ((unsigned int)modOnHD << 1) | ((unsigned int)readOnly << 2); if (m_modifiedStatus == newStatus) return; m_modifiedStatus = newStatus; switch (m_modifiedStatus) { case 0x0: m_modified->setIcon(QIcon::fromTheme(QStringLiteral("text-plain"))); m_modified->setWhatsThis(i18n("Meaning of current icon: Document was not modified since it was loaded")); break; case 0x1: case 0x5: m_modified->setIcon(QIcon::fromTheme(QStringLiteral("document-save"))); m_modified->setWhatsThis(i18n("Meaning of current icon: Document was modified since it was loaded")); break; case 0x2: case 0x6: m_modified->setIcon(QIcon::fromTheme(QStringLiteral("dialog-warning"))); m_modified->setWhatsThis(i18n("Meaning of current icon: Document was modified or deleted by another program")); break; case 0x3: case 0x7: m_modified->setIcon(QIcon(KIconUtils::addOverlay(QIcon::fromTheme(QStringLiteral("document-save")), QIcon(QStringLiteral("emblem-important")), Qt::TopLeftCorner))); m_modified->setWhatsThis(QString()); break; default: m_modified->setIcon(QIcon::fromTheme(QStringLiteral("lock"))); m_modified->setWhatsThis(i18n("Meaning of current icon: Document is in read-only mode")); break; } } void KateStatusBar::documentConfigChanged() { m_encoding->setText(m_view->document()->encoding()); KateDocumentConfig *config = ((KTextEditor::DocumentPrivate *)m_view->document())->config(); int tabWidth = config->tabWidth(); int indentationWidth = config->indentationWidth(); bool replaceTabsDyn = config->replaceTabsDyn(); static const KLocalizedString spacesOnly = ki18n("Soft Tabs: %1"); static const KLocalizedString spacesOnlyShowTabs = ki18n("Soft Tabs: %1 (%2)"); static const KLocalizedString tabsOnly = ki18n("Tab Size: %1"); static const KLocalizedString tabSpacesMixed = ki18n("Indent/Tab: %1/%2"); if (!replaceTabsDyn) { if (tabWidth == indentationWidth) { m_tabsIndent->setText(tabsOnly.subs(tabWidth).toString()); m_tabGroup->setEnabled(false); m_hardAction->setChecked(true); } else { m_tabsIndent->setText(tabSpacesMixed.subs(indentationWidth).subs(tabWidth).toString()); m_tabGroup->setEnabled(true); m_mixedAction->setChecked(true); } } else { if (tabWidth == indentationWidth) { m_tabsIndent->setText(spacesOnly.subs(indentationWidth).toString()); m_tabGroup->setEnabled(true); m_softAction->setChecked(true); } else { m_tabsIndent->setText(spacesOnlyShowTabs.subs(indentationWidth).subs(tabWidth).toString()); m_tabGroup->setEnabled(true); m_softAction->setChecked(true); } } updateGroup(m_tabGroup, tabWidth); updateGroup(m_indentGroup, indentationWidth); } void KateStatusBar::modeChanged() { m_mode->setText(KTextEditor::EditorPrivate::self()->modeManager()->fileType(m_view->document()->mode()).nameTranslated()); } void KateStatusBar::addNumberAction(QActionGroup *group, QMenu *menu, int data) { QAction *a; if (data != -1) { a = menu->addAction(QStringLiteral("%1").arg(data)); } else { a = menu->addAction(i18n("Other...")); } a->setData(data); a->setCheckable(true); a->setActionGroup(group); } void KateStatusBar::updateGroup(QActionGroup *group, int w) { QAction *m1 = nullptr; bool found = false; //linear search should be fast enough here, no additional hash for (QAction *action : group->actions()) { int val = action->data().toInt(); if (val == -1) m1 = action; if (val == w) { found = true; action->setChecked(true); } } if (found) { m1->setText(i18n("Other...")); } else { m1->setText(i18np("Other (%1)", "Other (%1)", w)); m1->setChecked(true); } } void KateStatusBar::slotTabGroup(QAction *a) { int val = a->data().toInt(); bool ok; KateDocumentConfig *config = ((KTextEditor::DocumentPrivate *)m_view->document())->config(); if (val == -1) { val = QInputDialog::getInt(this, i18n("Tab Width"), i18n("Please specify the wanted tab width:"), config->tabWidth(), 1, 16, 1, &ok); if (!ok) val = config->tabWidth(); } config->setTabWidth(val); } void KateStatusBar::slotIndentGroup(QAction *a) { int val = a->data().toInt(); bool ok; KateDocumentConfig *config = ((KTextEditor::DocumentPrivate *)m_view->document())->config(); if (val == -1) { val = QInputDialog::getInt(this, i18n("Indentation Width"), i18n("Please specify the wanted indentation width:"), config->indentationWidth(), 1, 16, 1, &ok); if (!ok) val = config->indentationWidth(); } config->configStart(); config->setIndentationWidth(val); if (m_hardAction->isChecked()) config->setTabWidth(val); config->configEnd(); } void KateStatusBar::slotIndentTabMode(QAction *a) { KateDocumentConfig *config = ((KTextEditor::DocumentPrivate *)m_view->document())->config(); if (a == m_softAction) { config->setReplaceTabsDyn(true); } else if (a == m_mixedAction) { if (config->replaceTabsDyn()) config->setReplaceTabsDyn(false); m_tabGroup->setEnabled(true); } else if (a == m_hardAction) { if (config->replaceTabsDyn()) { config->configStart(); config->setReplaceTabsDyn(false); config->setTabWidth(config->indentationWidth()); config->configEnd(); } else { config->setTabWidth(config->indentationWidth()); } m_tabGroup->setEnabled(false); } } void KateStatusBar::toggleWordCount(bool on) { if ((m_wordCounter != nullptr) == on) { return; } if (on) { m_wordCounter = new WordCounter(m_view); connect(m_wordCounter, &WordCounter::changed, this, &KateStatusBar::wordCountChanged); } else { delete m_wordCounter; m_wordCounter = nullptr; } wordCountChanged(0, 0, 0, 0); } void KateStatusBar::wordCountChanged(int wordsInDocument, int wordsInSelection, int charsInDocument, int charsInSelection) { if (m_wordCounter) { m_wordCount = i18nc( "%1 and %3 are the selected words/chars count, %2 and %4 are the total words/chars count.", "Words %1/%2, Chars %3/%4", wordsInSelection, wordsInDocument, charsInSelection, charsInDocument ); } else { m_wordCount.clear(); } cursorPositionChanged(); } void KateStatusBar::configChanged() { toggleWordCount(m_view->config()->showWordCount()); } void KateStatusBar::changeDictionary(QAction *action) { const QString dictionary = action->data().toString(); m_dictionary->setText(dictionary); // Code stolen from KateDictionaryBar::dictionaryChanged KTextEditor::Range selection = m_view->selectionRange(); if (selection.isValid() && !selection.isEmpty()) { m_view->doc()->setDictionary(dictionary, selection); } else { m_view->doc()->setDefaultDictionary(dictionary); } } diff --git a/src/view/kateview.cpp b/src/view/kateview.cpp index 1d7271a9..b78f6731 100644 --- a/src/view/kateview.cpp +++ b/src/view/kateview.cpp @@ -1,3940 +1,3920 @@ /* This file is part of the KDE libraries Copyright (C) 2009 Michel Ludwig Copyright (C) 2007 Mirko Stocker Copyright (C) 2003 Hamish Rodda Copyright (C) 2002 John Firebaugh Copyright (C) 2001-2004 Christoph Cullmann Copyright (C) 2001-2010 Joseph Wenninger Copyright (C) 1999 Jochen Wilhelmy This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ //BEGIN includes #include "kateview.h" #include "kateviewinternal.h" #include "kateviewhelpers.h" #include "katerenderer.h" #include "katedocument.h" #include "kateundomanager.h" #include "kateglobal.h" #include "katehighlight.h" #include "katehighlightmenu.h" #include "katedialogs.h" #include "katetextline.h" #include "kateschema.h" #include "katebookmarks.h" #include "kateconfig.h" #include "katemodemenu.h" #include "kateautoindent.h" #include "katecompletionwidget.h" #include "katewordcompletion.h" #include "katekeywordcompletion.h" #include "katelayoutcache.h" #include "spellcheck/spellcheck.h" #include "spellcheck/spellcheckdialog.h" #include "spellcheck/spellingmenu.h" #include "katebuffer.h" #include "script/katescriptmanager.h" #include "script/katescriptaction.h" #include "export/exporter.h" #include "katemessagewidget.h" #include "katetemplatehandler.h" #include "katepartdebug.h" #include "printing/kateprinter.h" #include "katestatusbar.h" #include "kateabstractinputmode.h" #include "inlinenotedata.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#define VIEW_RANGE_DEBUG //END includes namespace { bool hasCommentInFirstLine(KTextEditor::DocumentPrivate* doc) { const Kate::TextLine& line = doc->kateTextLine(0); Q_ASSERT(line); return doc->isComment(0, line->firstChar()); } } void KTextEditor::ViewPrivate::blockFix(KTextEditor::Range &range) { if (range.start().column() > range.end().column()) { int tmp = range.start().column(); range.setStart(KTextEditor::Cursor(range.start().line(), range.end().column())); range.setEnd(KTextEditor::Cursor(range.end().line(), tmp)); } } KTextEditor::ViewPrivate::ViewPrivate(KTextEditor::DocumentPrivate *doc, QWidget *parent, KTextEditor::MainWindow *mainWindow) : KTextEditor::View (this, parent) , m_completionWidget(nullptr) , m_annotationModel(nullptr) , m_hasWrap(false) , m_doc(doc) , m_textFolding(doc->buffer()) , m_config(new KateViewConfig(this)) , m_renderer(new KateRenderer(doc, m_textFolding, this)) , m_viewInternal(new KateViewInternal(this)) , m_spell(new KateSpellCheckDialog(this)) , m_bookmarks(new KateBookmarks(this)) , m_topSpacer(new QSpacerItem(0,0)) , m_leftSpacer(new QSpacerItem(0,0)) , m_rightSpacer(new QSpacerItem(0,0)) , m_bottomSpacer(new QSpacerItem(0,0)) , m_startingUp(true) , m_updatingDocumentConfig(false) , m_selection(m_doc->buffer(), KTextEditor::Range::invalid(), Kate::TextRange::ExpandLeft, Kate::TextRange::AllowEmpty) , blockSelect(false) , m_bottomViewBar(nullptr) , m_gotoBar(nullptr) , m_dictionaryBar(nullptr) , m_spellingMenu(new KateSpellingMenu(this)) , m_userContextMenuSet(false) , m_delayedUpdateTriggered(false) , m_lineToUpdateMin(-1) , m_lineToUpdateMax(-1) , m_mainWindow(mainWindow ? mainWindow : KTextEditor::EditorPrivate::self()->dummyMainWindow()) // use dummy window if no window there! , m_statusBar(nullptr) , m_temporaryAutomaticInvocationDisabled(false) , m_autoFoldedFirstLine(false) { // queued connect to collapse view updates for range changes, INIT THIS EARLY ENOUGH! connect(this, SIGNAL(delayedUpdateOfView()), this, SLOT(slotDelayedUpdateOfView()), Qt::QueuedConnection); KXMLGUIClient::setComponentName(KTextEditor::EditorPrivate::self()->aboutData().componentName(), KTextEditor::EditorPrivate::self()->aboutData().displayName()); // selection if for this view only and will invalidate if becoming empty m_selection.setView(this); // use z depth defined in moving ranges interface m_selection.setZDepth(-100000.0); KTextEditor::EditorPrivate::self()->registerView(this); /** * try to let the main window, if any, create a view bar for this view */ QWidget *bottomBarParent = m_mainWindow->createViewBar(this); m_bottomViewBar = new KateViewBar(bottomBarParent != nullptr, bottomBarParent ? bottomBarParent : this, this); // ugly workaround: // Force the layout to be left-to-right even on RTL desktop, as discussed // on the mailing list. This will cause the lines and icons panel to be on // the left, even for Arabic/Hebrew/Farsi/whatever users. setLayoutDirection(Qt::LeftToRight); m_bottomViewBar->installEventFilter(m_viewInternal); // add KateMessageWidget for KTE::MessageInterface immediately above view m_messageWidgets[KTextEditor::Message::AboveView] = new KateMessageWidget(this); m_messageWidgets[KTextEditor::Message::AboveView]->hide(); // add KateMessageWidget for KTE::MessageInterface immediately above view m_messageWidgets[KTextEditor::Message::BelowView] = new KateMessageWidget(this); m_messageWidgets[KTextEditor::Message::BelowView]->hide(); // add bottom viewbar... if (bottomBarParent) { m_mainWindow->addWidgetToViewBar(this, m_bottomViewBar); } // add layout for floating message widgets to KateViewInternal m_notificationLayout = new KateMessageLayout(m_viewInternal); m_notificationLayout->setContentsMargins(20, 20, 20, 20); m_viewInternal->setLayout(m_notificationLayout); // this really is needed :) m_viewInternal->updateView(); doc->addView(this); setFocusProxy(m_viewInternal); setFocusPolicy(Qt::StrongFocus); setXMLFile(QStringLiteral("katepart5ui.rc")); setupConnections(); setupActions(); // auto word completion new KateWordCompletionView(this, actionCollection()); // update the enabled state of the undo/redo actions... slotUpdateUndo(); /** * create the status bar of this view * do this after action creation, we use some of them! */ toggleStatusBar(); m_startingUp = false; updateConfig(); slotHlChanged(); KCursor::setAutoHideCursor(m_viewInternal, true); for (auto messageWidget : m_messageWidgets) { if (messageWidget) { // user interaction (scrolling) starts notification auto-hide timer connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), messageWidget, SLOT(startAutoHideTimer())); // user interaction (cursor navigation) starts notification auto-hide timer connect(this, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), messageWidget, SLOT(startAutoHideTimer())); } } // folding restoration on reload connect(m_doc, SIGNAL(aboutToReload(KTextEditor::Document*)), SLOT(saveFoldingState())); connect(m_doc, SIGNAL(reloaded(KTextEditor::Document*)), SLOT(applyFoldingState())); connect(m_doc, &KTextEditor::DocumentPrivate::reloaded, this, &KTextEditor::ViewPrivate::slotDocumentReloaded); connect(m_doc, &KTextEditor::DocumentPrivate::aboutToReload, this, &KTextEditor::ViewPrivate::slotDocumentAboutToReload); // update highlights on scrolling and co connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), this, SLOT(createHighlights())); // clear highlights on reload connect(m_doc, SIGNAL(aboutToReload(KTextEditor::Document*)), SLOT(clearHighlights())); // setup layout setupLayout(); } KTextEditor::ViewPrivate::~ViewPrivate() { // invalidate update signal m_delayedUpdateTriggered = false; // remove from xmlgui factory, to be safe if (factory()) { factory()->removeClient(this); } // delete internal view before view bar! delete m_viewInternal; /** * remove view bar again, if needed */ m_mainWindow->deleteViewBar(this); m_bottomViewBar = nullptr; doc()->removeView(this); delete m_renderer; delete m_config; KTextEditor::EditorPrivate::self()->deregisterView(this); } void KTextEditor::ViewPrivate::toggleStatusBar() { /** * if there, delete it */ if (m_statusBar) { bottomViewBar()->removePermanentBarWidget(m_statusBar); delete m_statusBar; m_statusBar = nullptr; emit statusBarEnabledChanged(this, false); return; } /** * else: create it */ m_statusBar = new KateStatusBar(this); bottomViewBar()->addPermanentBarWidget(m_statusBar); emit statusBarEnabledChanged(this, true); } void KTextEditor::ViewPrivate::setupLayout() { // delete old layout if any if (layout()) { delete layout(); /** * need to recreate spacers because they are deleted with * their belonging layout */ m_topSpacer = new QSpacerItem(0,0); m_leftSpacer = new QSpacerItem(0,0); m_rightSpacer = new QSpacerItem(0,0); m_bottomSpacer = new QSpacerItem(0,0); } // set margins QStyleOptionFrame opt; opt.initFrom(this); opt.frameShape = QFrame::StyledPanel; opt.state |= QStyle::State_Sunken; const int margin = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, this); m_topSpacer->changeSize(0, margin, QSizePolicy::Minimum, QSizePolicy::Fixed); m_leftSpacer->changeSize(margin, 0, QSizePolicy::Fixed, QSizePolicy::Minimum); m_rightSpacer->changeSize(margin, 0, QSizePolicy::Fixed, QSizePolicy::Minimum); m_bottomSpacer->changeSize(0, margin, QSizePolicy::Minimum, QSizePolicy::Fixed); // define layout QGridLayout *layout=new QGridLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); const bool frameAroundContents = style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, &opt, this); if (frameAroundContents) { // top message widget layout->addWidget(m_messageWidgets[KTextEditor::Message::AboveView], 0, 0, 1, 5); // top spacer layout->addItem(m_topSpacer, 1, 0, 1, 4); // left spacer layout->addItem(m_leftSpacer, 2, 0, 1, 1); // left border layout->addWidget(m_viewInternal->m_leftBorder, 2, 1, 1, 1); // view layout->addWidget(m_viewInternal, 2, 2, 1, 1); // right spacer layout->addItem(m_rightSpacer, 2, 3, 1, 1); // bottom spacer layout->addItem(m_bottomSpacer, 3, 0, 1, 4); // vertical scrollbar layout->addWidget(m_viewInternal->m_lineScroll, 1, 4, 3, 1); // horizontal scrollbar layout->addWidget(m_viewInternal->m_columnScroll, 4, 0, 1, 4); // dummy layout->addWidget(m_viewInternal->m_dummy, 4, 4, 1, 1); // bottom message layout->addWidget(m_messageWidgets[KTextEditor::Message::BelowView], 5, 0, 1, 5); // bottom viewbar if (m_bottomViewBar->parentWidget() == this) { layout->addWidget(m_bottomViewBar, 6, 0, 1, 5); } // stretch layout->setColumnStretch(2, 1); layout->setRowStretch(2, 1); // adjust scrollbar background m_viewInternal->m_lineScroll->setBackgroundRole(QPalette::Window); m_viewInternal->m_lineScroll->setAutoFillBackground(false); m_viewInternal->m_columnScroll->setBackgroundRole(QPalette::Window); m_viewInternal->m_columnScroll->setAutoFillBackground(false); } else { // top message widget layout->addWidget(m_messageWidgets[KTextEditor::Message::AboveView], 0, 0, 1, 5); // top spacer layout->addItem(m_topSpacer, 1, 0, 1, 5); // left spacer layout->addItem(m_leftSpacer, 2, 0, 1, 1); // left border layout->addWidget(m_viewInternal->m_leftBorder, 2, 1, 1, 1); // view layout->addWidget(m_viewInternal, 2, 2, 1, 1); // vertical scrollbar layout->addWidget(m_viewInternal->m_lineScroll, 2, 3, 1, 1); // right spacer layout->addItem(m_rightSpacer, 2, 4, 1, 1); // horizontal scrollbar layout->addWidget(m_viewInternal->m_columnScroll, 3, 1, 1, 2); // dummy layout->addWidget(m_viewInternal->m_dummy, 3, 3, 1, 1); // bottom spacer layout->addItem(m_bottomSpacer, 4, 0, 1, 5); // bottom message layout->addWidget(m_messageWidgets[KTextEditor::Message::BelowView], 5, 0, 1, 5); // bottom viewbar if (m_bottomViewBar->parentWidget() == this) { layout->addWidget(m_bottomViewBar, 6, 0, 1, 5); } // stretch layout->setColumnStretch(2, 1); layout->setRowStretch(2, 1); // adjust scrollbar background m_viewInternal->m_lineScroll->setBackgroundRole(QPalette::Base); m_viewInternal->m_lineScroll->setAutoFillBackground(true); m_viewInternal->m_columnScroll->setBackgroundRole(QPalette::Base); m_viewInternal->m_columnScroll->setAutoFillBackground(true); } } void KTextEditor::ViewPrivate::setupConnections() { connect(m_doc, SIGNAL(undoChanged()), this, SLOT(slotUpdateUndo())); connect(m_doc, SIGNAL(highlightingModeChanged(KTextEditor::Document*)), this, SLOT(slotHlChanged())); connect(m_doc, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); connect(m_viewInternal, SIGNAL(dropEventPass(QDropEvent*)), this, SIGNAL(dropEventPass(QDropEvent*))); connect(m_doc, SIGNAL(annotationModelChanged(KTextEditor::AnnotationModel*,KTextEditor::AnnotationModel*)), m_viewInternal->m_leftBorder, SLOT(annotationModelChanged(KTextEditor::AnnotationModel*,KTextEditor::AnnotationModel*))); } void KTextEditor::ViewPrivate::goToPreviousEditingPosition() { auto c = doc()->lastEditingPosition(KTextEditor::DocumentPrivate::Previous, cursorPosition()); if(c.isValid()){ setCursorPosition(c); } } void KTextEditor::ViewPrivate::goToNextEditingPosition() { auto c = doc()->lastEditingPosition(KTextEditor::DocumentPrivate::Next, cursorPosition()); if(c.isValid()){ setCursorPosition(c); } } void KTextEditor::ViewPrivate::setupActions() { KActionCollection *ac = actionCollection(); QAction *a; m_toggleWriteLock = nullptr; m_cut = a = ac->addAction(KStandardAction::Cut, this, SLOT(cut())); a->setWhatsThis(i18n("Cut the selected text and move it to the clipboard")); m_paste = a = ac->addAction(KStandardAction::Paste, this, SLOT(paste())); a->setWhatsThis(i18n("Paste previously copied or cut clipboard contents")); m_copy = a = ac->addAction(KStandardAction::Copy, this, SLOT(copy())); a->setWhatsThis(i18n("Use this command to copy the currently selected text to the system clipboard.")); m_pasteMenu = ac->addAction(QStringLiteral("edit_paste_menu"), new KatePasteMenu(i18n("Clipboard &History"), this)); connect(KTextEditor::EditorPrivate::self(), SIGNAL(clipboardHistoryChanged()), this, SLOT(slotClipboardHistoryChanged())); if (!doc()->readOnly()) { a = ac->addAction(KStandardAction::Save, m_doc, SLOT(documentSave())); a->setWhatsThis(i18n("Save the current document")); a = m_editUndo = ac->addAction(KStandardAction::Undo, m_doc, SLOT(undo())); a->setWhatsThis(i18n("Revert the most recent editing actions")); a = m_editRedo = ac->addAction(KStandardAction::Redo, m_doc, SLOT(redo())); a->setWhatsThis(i18n("Revert the most recent undo operation")); // Tools > Scripts // stored inside scoped pointer to ensure we destruct it early enough as it does internal cleanups of other child objects m_scriptActionMenu.reset(new KateScriptActionMenu(this, i18n("&Scripts"))); ac->addAction(QStringLiteral("tools_scripts"), m_scriptActionMenu.data()); a = ac->addAction(QStringLiteral("tools_apply_wordwrap")); a->setText(i18n("Apply &Word Wrap")); a->setWhatsThis(i18n("Use this to wrap the current line, or to reformat the selected lines as paragraph, " "to fit the 'Wrap words at' setting in the configuration dialog.

" "This is a static word wrap, meaning the document is changed.")); connect(a, SIGNAL(triggered(bool)), SLOT(applyWordWrap())); a = ac->addAction(QStringLiteral("tools_cleanIndent")); a->setText(i18n("&Clean Indentation")); a->setWhatsThis(i18n("Use this to clean the indentation of a selected block of text (only tabs/only spaces).

" "You can configure whether tabs should be honored and used or replaced with spaces, in the configuration dialog.")); connect(a, SIGNAL(triggered(bool)), SLOT(cleanIndent())); a = ac->addAction(QStringLiteral("tools_align")); a->setText(i18n("&Align")); a->setWhatsThis(i18n("Use this to align the current line or block of text to its proper indent level.")); connect(a, SIGNAL(triggered(bool)), SLOT(align())); a = ac->addAction(QStringLiteral("tools_comment")); a->setText(i18n("C&omment")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_D)); a->setWhatsThis(i18n("This command comments out the current line or a selected block of text.

" "The characters for single/multiple line comments are defined within the language's highlighting.")); connect(a, SIGNAL(triggered(bool)), SLOT(comment())); a = ac->addAction(QStringLiteral("Previous Editing Line")); a->setText(i18n("Go to previous editing line")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_E)); connect(a, SIGNAL(triggered(bool)), SLOT(goToPreviousEditingPosition())); a = ac->addAction(QStringLiteral("Next Editing Line")); a->setText(i18n("Go to next editing line")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_E)); connect(a, SIGNAL(triggered(bool)), SLOT(goToNextEditingPosition())); a = ac->addAction(QStringLiteral("tools_uncomment")); a->setText(i18n("Unco&mment")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_D)); a->setWhatsThis(i18n("This command removes comments from the current line or a selected block of text.

" "The characters for single/multiple line comments are defined within the language's highlighting.")); connect(a, SIGNAL(triggered(bool)), SLOT(uncomment())); a = ac->addAction(QStringLiteral("tools_toggle_comment")); a->setText(i18n("Toggle Comment")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_Slash)); connect(a, SIGNAL(triggered(bool)), SLOT(toggleComment())); a = m_toggleWriteLock = new KToggleAction(i18n("&Read Only Mode"), this); a->setWhatsThis(i18n("Lock/unlock the document for writing")); a->setChecked(!doc()->isReadWrite()); connect(a, SIGNAL(triggered(bool)), SLOT(toggleWriteLock())); ac->addAction(QStringLiteral("tools_toggle_write_lock"), a); a = ac->addAction(QStringLiteral("tools_uppercase")); a->setIcon(QIcon::fromTheme(QStringLiteral("format-text-uppercase"))); a->setText(i18n("Uppercase")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_U)); a->setWhatsThis(i18n("Convert the selection to uppercase, or the character to the " "right of the cursor if no text is selected.")); connect(a, SIGNAL(triggered(bool)), SLOT(uppercase())); a = ac->addAction(QStringLiteral("tools_lowercase")); a->setIcon(QIcon::fromTheme(QStringLiteral("format-text-lowercase"))); a->setText(i18n("Lowercase")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_U)); a->setWhatsThis(i18n("Convert the selection to lowercase, or the character to the " "right of the cursor if no text is selected.")); connect(a, SIGNAL(triggered(bool)), SLOT(lowercase())); a = ac->addAction(QStringLiteral("tools_capitalize")); a->setIcon(QIcon::fromTheme(QStringLiteral("format-text-capitalize"))); a->setText(i18n("Capitalize")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_U)); a->setWhatsThis(i18n("Capitalize the selection, or the word under the " "cursor if no text is selected.")); connect(a, SIGNAL(triggered(bool)), SLOT(capitalize())); a = ac->addAction(QStringLiteral("tools_join_lines")); a->setText(i18n("Join Lines")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_J)); connect(a, SIGNAL(triggered(bool)), SLOT(joinLines())); a = ac->addAction(QStringLiteral("tools_invoke_code_completion")); a->setText(i18n("Invoke Code Completion")); a->setWhatsThis(i18n("Manually invoke command completion, usually by using a shortcut bound to this action.")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_Space)); connect(a, SIGNAL(triggered(bool)), SLOT(userInvokedCompletion())); } else { m_cut->setEnabled(false); m_paste->setEnabled(false); m_pasteMenu->setEnabled(false); m_editUndo = nullptr; m_editRedo = nullptr; } a = ac->addAction(KStandardAction::Print, this, SLOT(print())); a->setWhatsThis(i18n("Print the current document.")); a = ac->addAction(KStandardAction::PrintPreview, this, SLOT(printPreview())); a->setWhatsThis(i18n("Show print preview of current document")); a = ac->addAction(QStringLiteral("file_reload")); a->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); a->setText(i18n("Reloa&d")); ac->setDefaultShortcuts(a, KStandardShortcut::reload()); a->setWhatsThis(i18n("Reload the current document from disk.")); connect(a, SIGNAL(triggered(bool)), SLOT(reloadFile())); a = ac->addAction(KStandardAction::SaveAs, m_doc, SLOT(documentSaveAs())); a->setWhatsThis(i18n("Save the current document to disk, with a name of your choice.")); a = new KateViewEncodingAction(m_doc, this, i18n("Save As with Encoding..."), this, true /* special mode for save as */); a->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as"))); ac->addAction(QStringLiteral("file_save_as_with_encoding"), a); a = ac->addAction(QStringLiteral("file_save_copy_as")); a->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as"))); a->setText(i18n("Save &Copy As...")); a->setWhatsThis(i18n("Save a copy of the current document to disk.")); connect(a, SIGNAL(triggered(bool)), m_doc, SLOT(documentSaveCopyAs())); a = ac->addAction(KStandardAction::GotoLine, this, SLOT(gotoLine())); a->setWhatsThis(i18n("This command opens a dialog and lets you choose a line that you want the cursor to move to.")); a = ac->addAction(QStringLiteral("modified_line_up")); a->setText(i18n("Move to Previous Modified Line")); a->setWhatsThis(i18n("Move upwards to the previous modified line.")); connect(a, SIGNAL(triggered(bool)), SLOT(toPrevModifiedLine())); a = ac->addAction(QStringLiteral("modified_line_down")); a->setText(i18n("Move to Next Modified Line")); a->setWhatsThis(i18n("Move downwards to the next modified line.")); connect(a, SIGNAL(triggered(bool)), SLOT(toNextModifiedLine())); a = ac->addAction(QStringLiteral("set_confdlg")); a->setText(i18n("&Configure Editor...")); a->setIcon(QIcon::fromTheme(QStringLiteral("preferences-other"))); a->setWhatsThis(i18n("Configure various aspects of this editor.")); connect(a, SIGNAL(triggered(bool)), SLOT(slotConfigDialog())); m_modeAction = new KateModeMenu(i18n("&Mode"), this); ac->addAction(QStringLiteral("tools_mode"), m_modeAction); m_modeAction->setWhatsThis(i18n("Here you can choose which mode should be used for the current document. This will influence the highlighting and folding being used, for example.")); m_modeAction->updateMenu(m_doc); KateHighlightingMenu *menu = new KateHighlightingMenu(i18n("&Highlighting"), this); ac->addAction(QStringLiteral("tools_highlighting"), menu); menu->setWhatsThis(i18n("Here you can choose how the current document should be highlighted.")); menu->updateMenu(m_doc); KateViewSchemaAction *schemaMenu = new KateViewSchemaAction(i18n("&Schema"), this); ac->addAction(QStringLiteral("view_schemas"), schemaMenu); schemaMenu->updateMenu(this); // indentation menu KateViewIndentationAction *indentMenu = new KateViewIndentationAction(m_doc, i18n("&Indentation"), this); ac->addAction(QStringLiteral("tools_indentation"), indentMenu); m_selectAll = a = ac->addAction(KStandardAction::SelectAll, this, SLOT(selectAll())); a->setWhatsThis(i18n("Select the entire text of the current document.")); m_deSelect = a = ac->addAction(KStandardAction::Deselect, this, SLOT(clearSelection())); a->setWhatsThis(i18n("If you have selected something within the current document, this will no longer be selected.")); a = ac->addAction(QStringLiteral("view_inc_font_sizes")); a->setIcon(QIcon::fromTheme(QStringLiteral("zoom-in"))); a->setText(i18n("Enlarge Font")); ac->setDefaultShortcuts(a, KStandardShortcut::zoomIn()); a->setWhatsThis(i18n("This increases the display font size.")); connect(a, SIGNAL(triggered(bool)), m_viewInternal, SLOT(slotIncFontSizes())); a = ac->addAction(QStringLiteral("view_dec_font_sizes")); a->setIcon(QIcon::fromTheme(QStringLiteral("zoom-out"))); a->setText(i18n("Shrink Font")); ac->setDefaultShortcuts(a, KStandardShortcut::zoomOut()); a->setWhatsThis(i18n("This decreases the display font size.")); connect(a, SIGNAL(triggered(bool)), m_viewInternal, SLOT(slotDecFontSizes())); a = m_toggleBlockSelection = new KToggleAction(i18n("Bl&ock Selection Mode"), this); ac->addAction(QStringLiteral("set_verticalSelect"), a); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_B)); a->setWhatsThis(i18n("This command allows switching between the normal (line based) selection mode and the block selection mode.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleBlockSelection())); a = ac->addAction(QStringLiteral("switch_next_input_mode")); a->setText(i18n("Switch to Next Input Mode")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_V)); a->setWhatsThis(i18n("Switch to the next input mode.")); connect(a, SIGNAL(triggered(bool)), SLOT(cycleInputMode())); a = m_toggleInsert = new KToggleAction(i18n("Overwr&ite Mode"), this); ac->addAction(QStringLiteral("set_insert"), a); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Insert)); a->setWhatsThis(i18n("Choose whether you want the text you type to be inserted or to overwrite existing text.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleInsert())); KToggleAction *toggleAction; a = m_toggleDynWrap = toggleAction = new KToggleAction(i18n("&Dynamic Word Wrap"), this); ac->addAction(QStringLiteral("view_dynamic_word_wrap"), a); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_F10)); a->setWhatsThis(i18n("If this option is checked, the text lines will be wrapped at the view border on the screen.

" "This is only a view option, meaning the document will not changed.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleDynWordWrap())); a = m_setDynWrapIndicators = new KSelectAction(i18n("Dynamic Word Wrap Indicators"), this); ac->addAction(QStringLiteral("dynamic_word_wrap_indicators"), a); a->setWhatsThis(i18n("Choose when the Dynamic Word Wrap Indicators should be displayed")); connect(m_setDynWrapIndicators, SIGNAL(triggered(int)), this, SLOT(setDynWrapIndicators(int))); const QStringList list2{ i18n("&Off"), i18n("Follow &Line Numbers"), i18n("&Always On") }; m_setDynWrapIndicators->setItems(list2); m_setDynWrapIndicators->setEnabled(m_toggleDynWrap->isChecked()); // only synced on real change, later a = toggleAction = new KToggleAction(i18n("Static Word Wrap"), this); ac->addAction(QStringLiteral("view_static_word_wrap"), a); a->setWhatsThis(i18n("If this option is checked, the text lines will be wrapped at the column defined in the editing properties.")); connect(a, &KToggleAction::triggered, [=] { if (m_doc) { m_doc->setWordWrap(!m_doc->wordWrap()); }}); a = toggleAction = m_toggleWWMarker = new KToggleAction(i18n("Show Static &Word Wrap Marker"), this); ac->addAction(QStringLiteral("view_word_wrap_marker"), a); a->setWhatsThis(i18n("Show/hide the Word Wrap Marker, a vertical line drawn at the word " "wrap column as defined in the editing properties")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleWWMarker())); a = toggleAction = m_toggleFoldingMarkers = new KToggleAction(i18n("Show Folding &Markers"), this); ac->addAction(QStringLiteral("view_folding_markers"), a); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_F9)); a->setWhatsThis(i18n("You can choose if the codefolding marks should be shown, if codefolding is possible.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleFoldingMarkers())); a = m_toggleIconBar = toggleAction = new KToggleAction(i18n("Show &Icon Border"), this); ac->addAction(QStringLiteral("view_border"), a); a->setWhatsThis(i18n("Show/hide the icon border.

The icon border shows bookmark symbols, for instance.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleIconBorder())); a = toggleAction = m_toggleLineNumbers = new KToggleAction(i18n("Show &Line Numbers"), this); ac->addAction(QStringLiteral("view_line_numbers"), a); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_F11)); a->setWhatsThis(i18n("Show/hide the line numbers on the left hand side of the view.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleLineNumbersOn())); a = m_toggleScrollBarMarks = toggleAction = new KToggleAction(i18n("Show Scroll&bar Marks"), this); ac->addAction(QStringLiteral("view_scrollbar_marks"), a); a->setWhatsThis(i18n("Show/hide the marks on the vertical scrollbar.

The marks show bookmarks, for instance.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleScrollBarMarks())); a = m_toggleScrollBarMiniMap = toggleAction = new KToggleAction(i18n("Show Scrollbar Mini-Map"), this); ac->addAction(QStringLiteral("view_scrollbar_minimap"), a); a->setWhatsThis(i18n("Show/hide the mini-map on the vertical scrollbar.

The mini-map shows an overview of the whole document.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleScrollBarMiniMap())); a = m_doc->autoReloadToggleAction(); ac->addAction(QStringLiteral("view_auto_reload"), a); // a = m_toggleScrollBarMiniMapAll = toggleAction = new KToggleAction(i18n("Show the whole document in the Mini-Map"), this); // ac->addAction(QLatin1String("view_scrollbar_minimap_all"), a); // a->setWhatsThis(i18n("Display the whole document in the mini-map.

With this option set the whole document will be visible in the mini-map.")); // connect(a, SIGNAL(triggered(bool)), SLOT(toggleScrollBarMiniMapAll())); // connect(m_toggleScrollBarMiniMap, SIGNAL(triggered(bool)), m_toggleScrollBarMiniMapAll, SLOT(setEnabled(bool))); a = m_toggleNPSpaces = new KToggleAction(i18n("Show Non-Printable Spaces"), this); ac->addAction(QStringLiteral("view_non_printable_spaces"), a); a->setWhatsThis(i18n("Show/hide bounding box around non-printable spaces")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleNPSpaces())); a = m_switchCmdLine = ac->addAction(QStringLiteral("switch_to_cmd_line")); a->setText(i18n("Switch to Command Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_F7)); a->setWhatsThis(i18n("Show/hide the command line on the bottom of the view.")); connect(a, SIGNAL(triggered(bool)), SLOT(switchToCmdLine())); KActionMenu *am = new KActionMenu(i18n("Input Modes"), this); m_inputModeActions = new QActionGroup(am); ac->addAction(QStringLiteral("view_input_modes"), am); Q_FOREACH(KateAbstractInputMode *mode, m_viewInternal->m_inputModes) { a = new QAction(mode->viewInputModeHuman(), m_inputModeActions); am->addAction(a); a->setWhatsThis(i18n("Activate/deactivate %1", mode->viewInputModeHuman())); const InputMode im = mode->viewInputMode(); a->setData(static_cast(im)); a->setCheckable(true); if (im == m_config->inputMode()) a->setChecked(true); connect(a, SIGNAL(triggered()), SLOT(toggleInputMode())); } a = m_setEndOfLine = new KSelectAction(i18n("&End of Line"), this); ac->addAction(QStringLiteral("set_eol"), a); a->setWhatsThis(i18n("Choose which line endings should be used, when you save the document")); const QStringList list { i18nc("@item:inmenu End of Line", "&UNIX") , i18nc("@item:inmenu End of Line", "&Windows/DOS") , i18nc("@item:inmenu End of Line", "&Macintosh") }; m_setEndOfLine->setItems(list); m_setEndOfLine->setCurrentItem(doc()->config()->eol()); connect(m_setEndOfLine, SIGNAL(triggered(int)), this, SLOT(setEol(int))); a = m_addBom = new KToggleAction(i18n("Add &Byte Order Mark (BOM)"), this); m_addBom->setChecked(doc()->config()->bom()); ac->addAction(QStringLiteral("add_bom"), a); a->setWhatsThis(i18n("Enable/disable adding of byte order marks for UTF-8/UTF-16 encoded files while saving")); connect(m_addBom, SIGNAL(triggered(bool)), this, SLOT(setAddBom(bool))); // encoding menu m_encodingAction = new KateViewEncodingAction(m_doc, this, i18n("E&ncoding"), this); ac->addAction(QStringLiteral("set_encoding"), m_encodingAction); a = ac->addAction(KStandardAction::Find, this, SLOT(find())); a->setWhatsThis(i18n("Look up the first occurrence of a piece of text or regular expression.")); addAction(a); a = ac->addAction(QStringLiteral("edit_find_selected")); a->setText(i18n("Find Selected")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_H)); a->setWhatsThis(i18n("Finds next occurrence of selected text.")); connect(a, SIGNAL(triggered(bool)), SLOT(findSelectedForwards())); a = ac->addAction(QStringLiteral("edit_find_selected_backwards")); a->setText(i18n("Find Selected Backwards")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_H)); a->setWhatsThis(i18n("Finds previous occurrence of selected text.")); connect(a, SIGNAL(triggered(bool)), SLOT(findSelectedBackwards())); a = ac->addAction(KStandardAction::FindNext, this, SLOT(findNext())); a->setWhatsThis(i18n("Look up the next occurrence of the search phrase.")); addAction(a); a = ac->addAction(KStandardAction::FindPrev, QStringLiteral("edit_find_prev"), this, SLOT(findPrevious())); a->setWhatsThis(i18n("Look up the previous occurrence of the search phrase.")); addAction(a); a = ac->addAction(KStandardAction::Replace, this, SLOT(replace())); a->setWhatsThis(i18n("Look up a piece of text or regular expression and replace the result with some given text.")); m_spell->createActions(ac); m_toggleOnTheFlySpellCheck = new KToggleAction(i18n("Automatic Spell Checking"), this); m_toggleOnTheFlySpellCheck->setWhatsThis(i18n("Enable/disable automatic spell checking")); connect(m_toggleOnTheFlySpellCheck, SIGNAL(triggered(bool)), SLOT(toggleOnTheFlySpellCheck(bool))); ac->addAction(QStringLiteral("tools_toggle_automatic_spell_checking"), m_toggleOnTheFlySpellCheck); ac->setDefaultShortcut(m_toggleOnTheFlySpellCheck, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_O)); a = ac->addAction(QStringLiteral("tools_change_dictionary")); a->setText(i18n("Change Dictionary...")); a->setWhatsThis(i18n("Change the dictionary that is used for spell checking.")); connect(a, SIGNAL(triggered()), SLOT(changeDictionary())); a = ac->addAction(QStringLiteral("tools_clear_dictionary_ranges")); a->setText(i18n("Clear Dictionary Ranges")); a->setEnabled(false); a->setWhatsThis(i18n("Remove all the separate dictionary ranges that were set for spell checking.")); connect(a, SIGNAL(triggered()), m_doc, SLOT(clearDictionaryRanges())); connect(m_doc, SIGNAL(dictionaryRangesPresent(bool)), a, SLOT(setEnabled(bool))); m_copyHtmlAction = ac->addAction(QStringLiteral("edit_copy_html"), this, SLOT(exportHtmlToClipboard())); m_copyHtmlAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); m_copyHtmlAction->setText(i18n("Copy as &HTML")); m_copyHtmlAction->setWhatsThis(i18n("Use this command to copy the currently selected text as HTML to the system clipboard.")); a = ac->addAction(QStringLiteral("file_export_html"), this, SLOT(exportHtmlToFile())); a->setIcon(QIcon::fromTheme(QStringLiteral("document-export"))); a->setText(i18n("E&xport as HTML...")); a->setWhatsThis(i18n("This command allows you to export the current document" " with all highlighting information into a HTML document.")); m_spellingMenu->createActions(ac); m_bookmarks->createActions(ac); slotSelectionChanged(); //Now setup the editing actions before adding the associated //widget and setting the shortcut context setupEditActions(); setupCodeFolding(); slotClipboardHistoryChanged(); ac->addAssociatedWidget(m_viewInternal); foreach (QAction *action, ac->actions()) { action->setShortcutContext(Qt::WidgetWithChildrenShortcut); } connect(this, SIGNAL(selectionChanged(KTextEditor::View*)), this, SLOT(slotSelectionChanged())); } void KTextEditor::ViewPrivate::slotConfigDialog() { // invoke config dialog, will auto-save configuration to katepartrc KTextEditor::EditorPrivate::self()->configDialog(this); } void KTextEditor::ViewPrivate::setupEditActions() { //If you add an editing action to this //function make sure to include the line //m_editActions << a after creating the action KActionCollection *ac = actionCollection(); QAction *a = ac->addAction(QStringLiteral("word_left")); a->setText(i18n("Move Word Left")); ac->setDefaultShortcuts(a, KStandardShortcut::backwardWord()); connect(a, SIGNAL(triggered(bool)), SLOT(wordLeft())); m_editActions << a; a = ac->addAction(QStringLiteral("select_char_left")); a->setText(i18n("Select Character Left")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_Left)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftCursorLeft())); m_editActions << a; a = ac->addAction(QStringLiteral("select_word_left")); a->setText(i18n("Select Word Left")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_Left)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftWordLeft())); m_editActions << a; a = ac->addAction(QStringLiteral("word_right")); a->setText(i18n("Move Word Right")); ac->setDefaultShortcuts(a, KStandardShortcut::forwardWord()); connect(a, SIGNAL(triggered(bool)), SLOT(wordRight())); m_editActions << a; a = ac->addAction(QStringLiteral("select_char_right")); a->setText(i18n("Select Character Right")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_Right)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftCursorRight())); m_editActions << a; a = ac->addAction(QStringLiteral("select_word_right")); a->setText(i18n("Select Word Right")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_Right)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftWordRight())); m_editActions << a; a = ac->addAction(QStringLiteral("beginning_of_line")); a->setText(i18n("Move to Beginning of Line")); ac->setDefaultShortcuts(a, KStandardShortcut::beginningOfLine()); connect(a, SIGNAL(triggered(bool)), SLOT(home())); m_editActions << a; a = ac->addAction(QStringLiteral("beginning_of_document")); a->setText(i18n("Move to Beginning of Document")); ac->setDefaultShortcuts(a, KStandardShortcut::begin()); connect(a, SIGNAL(triggered(bool)), SLOT(top())); m_editActions << a; a = ac->addAction(QStringLiteral("select_beginning_of_line")); a->setText(i18n("Select to Beginning of Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_Home)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftHome())); m_editActions << a; a = ac->addAction(QStringLiteral("select_beginning_of_document")); a->setText(i18n("Select to Beginning of Document")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_Home)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftTop())); m_editActions << a; a = ac->addAction(QStringLiteral("end_of_line")); a->setText(i18n("Move to End of Line")); ac->setDefaultShortcuts(a, KStandardShortcut::endOfLine()); connect(a, SIGNAL(triggered(bool)), SLOT(end())); m_editActions << a; a = ac->addAction(QStringLiteral("end_of_document")); a->setText(i18n("Move to End of Document")); ac->setDefaultShortcuts(a, KStandardShortcut::end()); connect(a, SIGNAL(triggered(bool)), SLOT(bottom())); m_editActions << a; a = ac->addAction(QStringLiteral("select_end_of_line")); a->setText(i18n("Select to End of Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_End)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftEnd())); m_editActions << a; a = ac->addAction(QStringLiteral("select_end_of_document")); a->setText(i18n("Select to End of Document")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_End)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftBottom())); m_editActions << a; a = ac->addAction(QStringLiteral("select_line_up")); a->setText(i18n("Select to Previous Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_Up)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftUp())); m_editActions << a; a = ac->addAction(QStringLiteral("scroll_line_up")); a->setText(i18n("Scroll Line Up")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_Up)); connect(a, SIGNAL(triggered(bool)), SLOT(scrollUp())); m_editActions << a; a = ac->addAction(QStringLiteral("move_line_down")); a->setText(i18n("Move to Next Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Down)); connect(a, SIGNAL(triggered(bool)), SLOT(down())); m_editActions << a; a = ac->addAction(QStringLiteral("move_line_up")); a->setText(i18n("Move to Previous Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Up)); connect(a, SIGNAL(triggered(bool)), SLOT(up())); m_editActions << a; a = ac->addAction(QStringLiteral("move_cursor_right")); a->setText(i18n("Move Cursor Right")); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Right)); connect(a, SIGNAL(triggered(bool)), SLOT(cursorRight())); m_editActions << a; a = ac->addAction(QStringLiteral("move_cursor_left")); a->setText(i18n("Move Cursor Left")); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Left)); connect(a, SIGNAL(triggered(bool)), SLOT(cursorLeft())); m_editActions << a; a = ac->addAction(QStringLiteral("select_line_down")); a->setText(i18n("Select to Next Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_Down)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftDown())); m_editActions << a; a = ac->addAction(QStringLiteral("scroll_line_down")); a->setText(i18n("Scroll Line Down")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_Down)); connect(a, SIGNAL(triggered(bool)), SLOT(scrollDown())); m_editActions << a; a = ac->addAction(QStringLiteral("scroll_page_up")); a->setText(i18n("Scroll Page Up")); ac->setDefaultShortcuts(a, KStandardShortcut::prior()); connect(a, SIGNAL(triggered(bool)), SLOT(pageUp())); m_editActions << a; a = ac->addAction(QStringLiteral("select_page_up")); a->setText(i18n("Select Page Up")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_PageUp)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftPageUp())); m_editActions << a; a = ac->addAction(QStringLiteral("move_top_of_view")); a->setText(i18n("Move to Top of View")); ac->setDefaultShortcut(a, QKeySequence(Qt::ALT + Qt::Key_Home)); connect(a, SIGNAL(triggered(bool)), SLOT(topOfView())); m_editActions << a; a = ac->addAction(QStringLiteral("select_top_of_view")); a->setText(i18n("Select to Top of View")); ac->setDefaultShortcut(a, QKeySequence(Qt::ALT + Qt::SHIFT + Qt::Key_Home)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftTopOfView())); m_editActions << a; a = ac->addAction(QStringLiteral("scroll_page_down")); a->setText(i18n("Scroll Page Down")); ac->setDefaultShortcuts(a, KStandardShortcut::next()); connect(a, SIGNAL(triggered(bool)), SLOT(pageDown())); m_editActions << a; a = ac->addAction(QStringLiteral("select_page_down")); a->setText(i18n("Select Page Down")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_PageDown)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftPageDown())); m_editActions << a; a = ac->addAction(QStringLiteral("move_bottom_of_view")); a->setText(i18n("Move to Bottom of View")); ac->setDefaultShortcut(a, QKeySequence(Qt::ALT + Qt::Key_End)); connect(a, SIGNAL(triggered(bool)), SLOT(bottomOfView())); m_editActions << a; a = ac->addAction(QStringLiteral("select_bottom_of_view")); a->setText(i18n("Select to Bottom of View")); ac->setDefaultShortcut(a, QKeySequence(Qt::ALT + Qt::SHIFT + Qt::Key_End)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftBottomOfView())); m_editActions << a; a = ac->addAction(QStringLiteral("to_matching_bracket")); a->setText(i18n("Move to Matching Bracket")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_6)); connect(a, SIGNAL(triggered(bool)), SLOT(toMatchingBracket())); //m_editActions << a; a = ac->addAction(QStringLiteral("select_matching_bracket")); a->setText(i18n("Select to Matching Bracket")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_6)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftToMatchingBracket())); //m_editActions << a; // anders: shortcuts doing any changes should not be created in read-only mode if (!doc()->readOnly()) { a = ac->addAction(QStringLiteral("transpose_char")); a->setText(i18n("Transpose Characters")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_T)); connect(a, SIGNAL(triggered(bool)), SLOT(transpose())); m_editActions << a; a = ac->addAction(QStringLiteral("delete_line")); a->setText(i18n("Delete Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_K)); connect(a, SIGNAL(triggered(bool)), SLOT(killLine())); m_editActions << a; a = ac->addAction(QStringLiteral("delete_word_left")); a->setText(i18n("Delete Word Left")); ac->setDefaultShortcuts(a, KStandardShortcut::deleteWordBack()); connect(a, SIGNAL(triggered(bool)), SLOT(deleteWordLeft())); m_editActions << a; a = ac->addAction(QStringLiteral("delete_word_right")); a->setText(i18n("Delete Word Right")); ac->setDefaultShortcuts(a, KStandardShortcut::deleteWordForward()); connect(a, SIGNAL(triggered(bool)), SLOT(deleteWordRight())); m_editActions << a; a = ac->addAction(QStringLiteral("delete_next_character")); a->setText(i18n("Delete Next Character")); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Delete)); connect(a, SIGNAL(triggered(bool)), SLOT(keyDelete())); m_editActions << a; a = ac->addAction(QStringLiteral("backspace")); a->setText(i18n("Backspace")); QList scuts; scuts << QKeySequence(Qt::Key_Backspace) << QKeySequence(Qt::SHIFT + Qt::Key_Backspace); ac->setDefaultShortcuts(a, scuts); connect(a, SIGNAL(triggered(bool)), SLOT(backspace())); m_editActions << a; a = ac->addAction(QStringLiteral("insert_tabulator")); a->setText(i18n("Insert Tab")); connect(a, SIGNAL(triggered(bool)), SLOT(insertTab())); m_editActions << a; a = ac->addAction(QStringLiteral("smart_newline")); a->setText(i18n("Insert Smart Newline")); a->setWhatsThis(i18n("Insert newline including leading characters of the current line which are not letters or numbers.")); scuts.clear(); scuts << QKeySequence(Qt::SHIFT + Qt::Key_Return) << QKeySequence(Qt::SHIFT + Qt::Key_Enter); ac->setDefaultShortcuts(a, scuts); connect(a, SIGNAL(triggered(bool)), SLOT(smartNewline())); m_editActions << a; a = ac->addAction(QStringLiteral("tools_indent")); a->setIcon(QIcon::fromTheme(QStringLiteral("format-indent-more"))); a->setText(i18n("&Indent")); a->setWhatsThis(i18n("Use this to indent a selected block of text.

" "You can configure whether tabs should be honored and used or replaced with spaces, in the configuration dialog.")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_I)); connect(a, SIGNAL(triggered(bool)), SLOT(indent())); a = ac->addAction(QStringLiteral("tools_unindent")); a->setIcon(QIcon::fromTheme(QStringLiteral("format-indent-less"))); a->setText(i18n("&Unindent")); a->setWhatsThis(i18n("Use this to unindent a selected block of text.")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_I)); connect(a, SIGNAL(triggered(bool)), SLOT(unIndent())); } if (hasFocus()) { slotGotFocus(); } else { slotLostFocus(); } } void KTextEditor::ViewPrivate::setupCodeFolding() { KActionCollection *ac = this->actionCollection(); QAction *a; a = ac->addAction(QStringLiteral("folding_toplevel")); a->setText(i18n("Fold Toplevel Nodes")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Minus)); connect(a, SIGNAL(triggered(bool)), SLOT(slotFoldToplevelNodes())); a = ac->addAction(QLatin1String("folding_expandtoplevel")); a->setText(i18n("Unfold Toplevel Nodes")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_Plus)); connect(a, SIGNAL(triggered(bool)), SLOT(slotExpandToplevelNodes())); /*a = ac->addAction(QLatin1String("folding_expandall")); a->setText(i18n("Unfold All Nodes")); connect(a, SIGNAL(triggered(bool)), doc()->foldingTree(), SLOT(expandAll())); a = ac->addAction(QLatin1String("folding_collapse_dsComment")); a->setText(i18n("Fold Multiline Comments")); connect(a, SIGNAL(triggered(bool)), doc()->foldingTree(), SLOT(collapseAll_dsComments())); */ a = ac->addAction(QStringLiteral("folding_toggle_current")); a->setText(i18n("Toggle Current Node")); connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::slotToggleFolding); a = ac->addAction(QStringLiteral("folding_toggle_in_current")); a->setText(i18n("Toggle Contained Nodes")); connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::slotToggleFoldingsInRange); } void KTextEditor::ViewPrivate::slotFoldToplevelNodes() { for (int line = 0; line < doc()->lines(); ++line) { if (textFolding().isLineVisible(line)) { foldLine(line); } } } void KTextEditor::ViewPrivate::slotExpandToplevelNodes() { const auto topLevelRanges(textFolding().foldingRangesForParentRange()); for (const auto &range : topLevelRanges) { textFolding().unfoldRange(range.first); } } void KTextEditor::ViewPrivate::slotToggleFolding() { int line = cursorPosition().line(); bool actionDone = false; while (!actionDone && (line > -1)) { actionDone = unfoldLine(line); if (!actionDone) { actionDone = foldLine(line--).isValid(); } } } void KTextEditor::ViewPrivate::slotToggleFoldingsInRange() { int line = cursorPosition().line(); while (!toggleFoldingsInRange(line) && (line > -1)) { --line; } } KTextEditor::Range KTextEditor::ViewPrivate::foldLine(int line) { KTextEditor::Range foldingRange = doc()->buffer().computeFoldingRangeForStartLine(line); if (!foldingRange.isValid()) { return foldingRange; } // Ensure not to fold the end marker to avoid a deceptive look, but only on token based folding Kate::TextLine startTextLine = doc()->buffer().plainLine(line); if (!startTextLine->markedAsFoldingStartIndentation()) { const int adjustedLine = foldingRange.end().line() - 1; foldingRange.setEnd(KTextEditor::Cursor(adjustedLine, doc()->buffer().plainLine(adjustedLine)->length())); } // Don't try to fold a single line, which can happens due to adjustment above // FIXME Avoid to offer such a folding marker if (!foldingRange.onSingleLine()) { textFolding().newFoldingRange(foldingRange, Kate::TextFolding::Folded); } return foldingRange; } bool KTextEditor::ViewPrivate::unfoldLine(int line) { bool actionDone = false; // ask the folding info for this line, if any folds are around! // auto = QVector> auto startingRanges = textFolding().foldingRangesStartingOnLine(line); for (int i = 0; i < startingRanges.size(); ++i) { actionDone |= textFolding().unfoldRange(startingRanges[i].first); } return actionDone; } bool KTextEditor::ViewPrivate::toggleFoldingOfLine(int line) { bool actionDone = unfoldLine(line); if (!actionDone) { actionDone = foldLine(line).isValid(); } return actionDone; } bool KTextEditor::ViewPrivate::toggleFoldingsInRange(int line) { KTextEditor::Range foldingRange = doc()->buffer().computeFoldingRangeForStartLine(line); if (!foldingRange.isValid()) { // Either line is not valid or there is no start range return false; } bool actionDone = false; // Track success // Don't be too eager but obliging! Only toggle containing ranges which are // visible -> Be done when the range is folded actionDone |= unfoldLine(line); if (!actionDone) { // Unfold all in range, but not the range itself for (int ln = foldingRange.start().line() + 1; ln < foldingRange.end().line(); ++ln) { actionDone |= unfoldLine(ln); } } if (!actionDone) { // Fold all in range, but not the range itself for (int ln = foldingRange.start().line() + 1; ln < foldingRange.end().line(); ++ln) { KTextEditor::Range fr = foldLine(ln); if (fr.isValid()) { ln = fr.end().line() - 1; actionDone = true; } } } if (!actionDone) { // At this point was an unfolded range clicked which contains no "childs" // We assume the user want to fold it by the wrong button, be obliging! actionDone |= foldLine(line).isValid(); } // At this point we should be always true return actionDone; } KTextEditor::View::ViewMode KTextEditor::ViewPrivate::viewMode() const { return currentInputMode()->viewMode(); } QString KTextEditor::ViewPrivate::viewModeHuman() const { QString currentMode = currentInputMode()->viewModeHuman(); /** * append read-only if needed */ if (!doc()->isReadWrite()) { currentMode = i18n("(R/O) %1", currentMode); } /** * return full mode */ return currentMode; } KTextEditor::View::InputMode KTextEditor::ViewPrivate::viewInputMode() const { return currentInputMode()->viewInputMode(); } QString KTextEditor::ViewPrivate::viewInputModeHuman() const { return currentInputMode()->viewInputModeHuman(); } void KTextEditor::ViewPrivate::setInputMode(KTextEditor::View::InputMode mode) { if (currentInputMode()->viewInputMode() == mode) { return; } if (!m_viewInternal->m_inputModes.contains(mode)) { return; } m_viewInternal->m_currentInputMode->deactivate(); m_viewInternal->m_currentInputMode = m_viewInternal->m_inputModes[mode]; m_viewInternal->m_currentInputMode->activate(); - config()->setInputMode(mode); // TODO: this could be called from read config procedure, so it's not a good idea to set a specific view mode here + config()->setValue(KateViewConfig::InputMode, mode); // TODO: this could be called from read config procedure, so it's not a good idea to set a specific view mode here /* small duplication, but need to do this if not toggled by action */ Q_FOREACH(QAction *action, m_inputModeActions->actions()) { if (static_cast(action->data().toInt()) == mode) { action->setChecked(true); break; } } /* inform the rest of the system about the change */ emit viewInputModeChanged(this, mode); emit viewModeChanged(this, viewMode()); } void KTextEditor::ViewPrivate::slotDocumentAboutToReload() { if (doc()->isAutoReload()) { const int lastVisibleLine = m_viewInternal->endLine(); const int currentLine = cursorPosition().line(); m_gotoBottomAfterReload = (lastVisibleLine == currentLine) && (currentLine == doc()->lastLine()); if (!m_gotoBottomAfterReload) { // Ensure the view jumps not back when user scrolls around const int firstVisibleLine = 1 + lastVisibleLine - m_viewInternal->linesDisplayed(); const int newLine = qBound(firstVisibleLine, currentLine, lastVisibleLine); setCursorPositionVisual(KTextEditor::Cursor(newLine, cursorPosition().column())); } } else { m_gotoBottomAfterReload = false; } } void KTextEditor::ViewPrivate::slotDocumentReloaded() { if (m_gotoBottomAfterReload) { bottom(); } } void KTextEditor::ViewPrivate::slotGotFocus() { //qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::slotGotFocus"; currentInputMode()->gotFocus(); /** * update current view and scrollbars * it is needed for styles that implement different frame and scrollbar * rendering when focused */ update(); if (m_viewInternal->m_lineScroll->isVisible()) { m_viewInternal->m_lineScroll->update(); } if (m_viewInternal->m_columnScroll->isVisible()) { m_viewInternal->m_columnScroll->update(); } emit focusIn(this); } void KTextEditor::ViewPrivate::slotLostFocus() { //qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::slotLostFocus"; currentInputMode()->lostFocus(); /** * update current view and scrollbars * it is needed for styles that implement different frame and scrollbar * rendering when focused */ update(); if (m_viewInternal->m_lineScroll->isVisible()) { m_viewInternal->m_lineScroll->update(); } if (m_viewInternal->m_columnScroll->isVisible()) { m_viewInternal->m_columnScroll->update(); } emit focusOut(this); } void KTextEditor::ViewPrivate::setDynWrapIndicators(int mode) { - config()->setDynWordWrapIndicators(mode); + config()->setValue(KateViewConfig::DynWordWrapIndicators, mode); } bool KTextEditor::ViewPrivate::isOverwriteMode() const { return doc()->config()->ovr(); } void KTextEditor::ViewPrivate::reloadFile() { // bookmarks and cursor positions are temporarily saved by the document doc()->documentReload(); } void KTextEditor::ViewPrivate::slotReadWriteChanged() { if (m_toggleWriteLock) { m_toggleWriteLock->setChecked(! doc()->isReadWrite()); } m_cut->setEnabled(doc()->isReadWrite() && (selection() || m_config->smartCopyCut())); m_paste->setEnabled(doc()->isReadWrite()); m_pasteMenu->setEnabled(doc()->isReadWrite() && !KTextEditor::EditorPrivate::self()->clipboardHistory().isEmpty()); m_setEndOfLine->setEnabled(doc()->isReadWrite()); static const auto l = { QStringLiteral("edit_replace") , QStringLiteral("tools_spelling") , QStringLiteral("tools_indent") , QStringLiteral("tools_unindent") , QStringLiteral("tools_cleanIndent") , QStringLiteral("tools_align") , QStringLiteral("tools_comment") , QStringLiteral("tools_uncomment") , QStringLiteral("tools_toggle_comment") , QStringLiteral("tools_uppercase") , QStringLiteral("tools_lowercase") , QStringLiteral("tools_capitalize") , QStringLiteral("tools_join_lines") , QStringLiteral("tools_apply_wordwrap") , QStringLiteral("tools_spelling_from_cursor") , QStringLiteral("tools_spelling_selection") }; foreach (const auto &action, l) { QAction *a = actionCollection()->action(action); if (a) { a->setEnabled(doc()->isReadWrite()); } } slotUpdateUndo(); currentInputMode()->readWriteChanged(doc()->isReadWrite()); // => view mode changed emit viewModeChanged(this, viewMode()); emit viewInputModeChanged(this, viewInputMode()); } void KTextEditor::ViewPrivate::slotClipboardHistoryChanged() { m_pasteMenu->setEnabled(doc()->isReadWrite() && !KTextEditor::EditorPrivate::self()->clipboardHistory().isEmpty()); } void KTextEditor::ViewPrivate::slotUpdateUndo() { if (doc()->readOnly()) { return; } m_editUndo->setEnabled(doc()->isReadWrite() && doc()->undoCount() > 0); m_editRedo->setEnabled(doc()->isReadWrite() && doc()->redoCount() > 0); } bool KTextEditor::ViewPrivate::setCursorPositionInternal(const KTextEditor::Cursor &position, uint tabwidth, bool calledExternally) { Kate::TextLine l = doc()->kateTextLine(position.line()); if (!l) { return false; } QString line_str = doc()->line(position.line()); int x = 0; int z = 0; for (; z < line_str.length() && z < position.column(); z++) { if (line_str[z] == QLatin1Char('\t')) { x += tabwidth - (x % tabwidth); } else { x++; } } if (blockSelection()) if (z < position.column()) { x += position.column() - z; } m_viewInternal->updateCursor(KTextEditor::Cursor(position.line(), x), false, false, calledExternally); return true; } void KTextEditor::ViewPrivate::toggleInsert() { doc()->config()->setOvr(!doc()->config()->ovr()); m_toggleInsert->setChecked(isOverwriteMode()); emit viewModeChanged(this, viewMode()); emit viewInputModeChanged(this, viewInputMode()); } void KTextEditor::ViewPrivate::slotSaveCanceled(const QString &error) { if (!error.isEmpty()) { // happens when canceling a job KMessageBox::error(this, error); } } void KTextEditor::ViewPrivate::gotoLine() { gotoBar()->updateData(); bottomViewBar()->showBarWidget(gotoBar()); } void KTextEditor::ViewPrivate::changeDictionary() { dictionaryBar()->updateData(); bottomViewBar()->showBarWidget(dictionaryBar()); } void KTextEditor::ViewPrivate::joinLines() { int first = selectionRange().start().line(); int last = selectionRange().end().line(); //int left = doc()->line( last ).length() - doc()->selEndCol(); if (first == last) { first = cursorPosition().line(); last = first + 1; } doc()->joinLines(first, last); } void KTextEditor::ViewPrivate::readSessionConfig(const KConfigGroup &config, const QSet &flags) { Q_UNUSED(flags) // cursor position setCursorPositionInternal(KTextEditor::Cursor(config.readEntry("CursorLine", 0), config.readEntry("CursorColumn", 0))); - // restore dyn word wrap if set for this view - if (config.hasKey("Dynamic Word Wrap")) { - m_config->setDynWordWrap(config.readEntry("Dynamic Word Wrap", false)); - } + m_config->setDynWordWrap(config.readEntry("Dynamic Word Wrap", false)); // restore text folding m_savedFoldingState = QJsonDocument::fromJson(config.readEntry("TextFolding", QByteArray())); applyFoldingState(); Q_FOREACH(KateAbstractInputMode *mode, m_viewInternal->m_inputModes) { mode->readSessionConfig(config); } } void KTextEditor::ViewPrivate::writeSessionConfig(KConfigGroup &config, const QSet &flags) { Q_UNUSED(flags) // cursor position config.writeEntry("CursorLine", cursorPosition().line()); config.writeEntry("CursorColumn", cursorPosition().column()); - // save dyn word wrap if set for this view - if (m_config->dynWordWrapSet()) { - config.writeEntry("Dynamic Word Wrap", m_config->dynWordWrap()); - } + config.writeEntry("Dynamic Word Wrap", m_config->dynWordWrap()); // save text folding state saveFoldingState(); config.writeEntry("TextFolding", m_savedFoldingState.toJson(QJsonDocument::Compact)); m_savedFoldingState = QJsonDocument(); Q_FOREACH(KateAbstractInputMode *mode, m_viewInternal->m_inputModes) { mode->writeSessionConfig(config); } } int KTextEditor::ViewPrivate::getEol() const { return doc()->config()->eol(); } void KTextEditor::ViewPrivate::setEol(int eol) { if (!doc()->isReadWrite()) { return; } if (m_updatingDocumentConfig) { return; } if (eol != doc()->config()->eol()) { doc()->setModified(true); // mark modified (bug #143120) doc()->config()->setEol(eol); } } void KTextEditor::ViewPrivate::setAddBom(bool enabled) { if (!doc()->isReadWrite()) { return; } if (m_updatingDocumentConfig) { return; } doc()->config()->setBom(enabled); doc()->bomSetByUser(); } void KTextEditor::ViewPrivate::setIconBorder(bool enable) { - config()->setIconBar(enable); + config()->setValue(KateViewConfig::ShowIconBar, enable); } void KTextEditor::ViewPrivate::toggleIconBorder() { - config()->setIconBar(!config()->iconBar()); + config()->setValue(KateViewConfig::ShowIconBar, config()->iconBar()); } void KTextEditor::ViewPrivate::setLineNumbersOn(bool enable) { - config()->setLineNumbers(enable); + config()->setValue(KateViewConfig::ShowLineNumbers, enable); } void KTextEditor::ViewPrivate::toggleLineNumbersOn() { - config()->setLineNumbers(!config()->lineNumbers()); + config()->setValue(KateViewConfig::ShowLineNumbers, !config()->lineNumbers()); } void KTextEditor::ViewPrivate::setScrollBarMarks(bool enable) { - config()->setScrollBarMarks(enable); + config()->setValue(KateViewConfig::ShowScrollBarMarks, enable); } void KTextEditor::ViewPrivate::toggleScrollBarMarks() { - config()->setScrollBarMarks(!config()->scrollBarMarks()); + config()->setValue(KateViewConfig::ShowScrollBarMarks, !config()->scrollBarMarks()); } void KTextEditor::ViewPrivate::setScrollBarMiniMap(bool enable) { - config()->setScrollBarMiniMap(enable); + config()->setValue(KateViewConfig::ShowScrollBarMiniMap, enable); } void KTextEditor::ViewPrivate::toggleScrollBarMiniMap() { - config()->setScrollBarMiniMap(!config()->scrollBarMiniMap()); + config()->setValue(KateViewConfig::ShowScrollBarMiniMap, !config()->scrollBarMiniMap()); } void KTextEditor::ViewPrivate::setScrollBarMiniMapAll(bool enable) { - config()->setScrollBarMiniMapAll(enable); + config()->setValue(KateViewConfig::ShowScrollBarMiniMapAll, enable); } void KTextEditor::ViewPrivate::toggleScrollBarMiniMapAll() { - config()->setScrollBarMiniMapAll(!config()->scrollBarMiniMapAll()); + config()->setValue(KateViewConfig::ShowScrollBarMiniMapAll, !config()->scrollBarMiniMapAll()); } void KTextEditor::ViewPrivate::setScrollBarMiniMapWidth(int width) { - config()->setScrollBarMiniMapWidth(width); + config()->setValue(KateViewConfig::ScrollBarMiniMapWidth, width); } void KTextEditor::ViewPrivate::toggleDynWordWrap() { config()->setDynWordWrap(!config()->dynWordWrap()); } void KTextEditor::ViewPrivate::toggleWWMarker() { m_renderer->config()->setWordWrapMarker(!m_renderer->config()->wordWrapMarker()); } void KTextEditor::ViewPrivate::toggleNPSpaces() { m_renderer->setShowNonPrintableSpaces(!m_renderer->showNonPrintableSpaces()); m_viewInternal->update(); // force redraw } void KTextEditor::ViewPrivate::toggleWordCount(bool on) { config()->setShowWordCount(on); } void KTextEditor::ViewPrivate::setFoldingMarkersOn(bool enable) { - config()->setFoldingBar(enable); + config()->setValue(KateViewConfig::ShowFoldingBar, enable); } void KTextEditor::ViewPrivate::toggleFoldingMarkers() { - config()->setFoldingBar(!config()->foldingBar()); + config()->setValue(KateViewConfig::ShowFoldingBar, !config()->foldingBar()); } bool KTextEditor::ViewPrivate::iconBorder() { return m_viewInternal->m_leftBorder->iconBorderOn(); } bool KTextEditor::ViewPrivate::lineNumbersOn() { return m_viewInternal->m_leftBorder->lineNumbersOn(); } bool KTextEditor::ViewPrivate::scrollBarMarks() { return m_viewInternal->m_lineScroll->showMarks(); } bool KTextEditor::ViewPrivate::scrollBarMiniMap() { return m_viewInternal->m_lineScroll->showMiniMap(); } int KTextEditor::ViewPrivate::dynWrapIndicators() { return m_viewInternal->m_leftBorder->dynWrapIndicators(); } bool KTextEditor::ViewPrivate::foldingMarkersOn() { return m_viewInternal->m_leftBorder->foldingMarkersOn(); } void KTextEditor::ViewPrivate::toggleWriteLock() { doc()->setReadWrite(! doc()->isReadWrite()); } void KTextEditor::ViewPrivate::registerTextHintProvider(KTextEditor::TextHintProvider *provider) { m_viewInternal->registerTextHintProvider(provider); } void KTextEditor::ViewPrivate::unregisterTextHintProvider(KTextEditor::TextHintProvider *provider) { m_viewInternal->unregisterTextHintProvider(provider); } void KTextEditor::ViewPrivate::setTextHintDelay(int delay) { m_viewInternal->setTextHintDelay(delay); } int KTextEditor::ViewPrivate::textHintDelay() const { return m_viewInternal->textHintDelay(); } void KTextEditor::ViewPrivate::find() { currentInputMode()->find(); } void KTextEditor::ViewPrivate::findSelectedForwards() { currentInputMode()->findSelectedForwards(); } void KTextEditor::ViewPrivate::findSelectedBackwards() { currentInputMode()->findSelectedBackwards(); } void KTextEditor::ViewPrivate::replace() { currentInputMode()->findReplace(); } void KTextEditor::ViewPrivate::findNext() { currentInputMode()->findNext(); } void KTextEditor::ViewPrivate::findPrevious() { currentInputMode()->findPrevious(); } void KTextEditor::ViewPrivate::slotSelectionChanged() { m_copy->setEnabled(selection() || m_config->smartCopyCut()); m_deSelect->setEnabled(selection()); m_copyHtmlAction->setEnabled (selection()); // update highlighting of current selected word selectionChangedForHighlights (); if (doc()->readOnly()) { return; } m_cut->setEnabled(selection() || m_config->smartCopyCut()); } void KTextEditor::ViewPrivate::switchToCmdLine() { currentInputMode()->activateCommandLine(); } KateRenderer *KTextEditor::ViewPrivate::renderer() { return m_renderer; } void KTextEditor::ViewPrivate::updateConfig() { if (m_startingUp) { return; } // dyn. word wrap & markers if (m_hasWrap != config()->dynWordWrap()) { m_viewInternal->prepareForDynWrapChange(); m_hasWrap = config()->dynWordWrap(); m_viewInternal->dynWrapChanged(); m_setDynWrapIndicators->setEnabled(config()->dynWordWrap()); m_toggleDynWrap->setChecked(config()->dynWordWrap()); } m_viewInternal->m_leftBorder->setDynWrapIndicators(config()->dynWordWrapIndicators()); m_setDynWrapIndicators->setCurrentItem(config()->dynWordWrapIndicators()); // line numbers m_viewInternal->m_leftBorder->setLineNumbersOn(config()->lineNumbers()); m_toggleLineNumbers->setChecked(config()->lineNumbers()); // icon bar m_viewInternal->m_leftBorder->setIconBorderOn(config()->iconBar()); m_toggleIconBar->setChecked(config()->iconBar()); // scrollbar marks m_viewInternal->m_lineScroll->setShowMarks(config()->scrollBarMarks()); m_toggleScrollBarMarks->setChecked(config()->scrollBarMarks()); // scrollbar mini-map m_viewInternal->m_lineScroll->setShowMiniMap(config()->scrollBarMiniMap()); m_toggleScrollBarMiniMap->setChecked(config()->scrollBarMiniMap()); // scrollbar mini-map - (whole document) m_viewInternal->m_lineScroll->setMiniMapAll(config()->scrollBarMiniMapAll()); //m_toggleScrollBarMiniMapAll->setChecked( config()->scrollBarMiniMapAll() ); // scrollbar mini-map.width m_viewInternal->m_lineScroll->setMiniMapWidth(config()->scrollBarMiniMapWidth()); // misc edit m_toggleBlockSelection->setChecked(blockSelection()); m_toggleInsert->setChecked(isOverwriteMode()); updateFoldingConfig(); // bookmark m_bookmarks->setSorting((KateBookmarks::Sorting) config()->bookmarkSort()); m_viewInternal->setAutoCenterLines(config()->autoCenterLines()); Q_FOREACH(KateAbstractInputMode *input, m_viewInternal->m_inputModes) { input->updateConfig(); } setInputMode(config()->inputMode()); reflectOnTheFlySpellCheckStatus(doc()->isOnTheFlySpellCheckingEnabled()); // register/unregister word completion... bool wc = config()->wordCompletion(); if (wc != isCompletionModelRegistered(KTextEditor::EditorPrivate::self()->wordCompletionModel())) { if (wc) registerCompletionModel(KTextEditor::EditorPrivate::self()->wordCompletionModel()); else unregisterCompletionModel(KTextEditor::EditorPrivate::self()->wordCompletionModel()); } bool kc = config()->keywordCompletion(); if (kc != isCompletionModelRegistered(KTextEditor::EditorPrivate::self()->keywordCompletionModel())) { if (kc) registerCompletionModel(KTextEditor::EditorPrivate::self()->keywordCompletionModel()); else unregisterCompletionModel (KTextEditor::EditorPrivate::self()->keywordCompletionModel()); } m_cut->setEnabled(doc()->isReadWrite() && (selection() || m_config->smartCopyCut())); m_copy->setEnabled(selection() || m_config->smartCopyCut()); // if not disabled, update status bar if (m_statusBar) { m_statusBar->updateStatus(); } // now redraw... m_viewInternal->cache()->clear(); tagAll(); updateView(true); emit configChanged(); } void KTextEditor::ViewPrivate::updateDocumentConfig() { if (m_startingUp) { return; } m_updatingDocumentConfig = true; m_setEndOfLine->setCurrentItem(doc()->config()->eol()); m_addBom->setChecked(doc()->config()->bom()); m_updatingDocumentConfig = false; // maybe block selection or wrap-cursor mode changed ensureCursorColumnValid(); // first change this m_renderer->setTabWidth(doc()->config()->tabWidth()); m_renderer->setIndentWidth(doc()->config()->indentationWidth()); // now redraw... m_viewInternal->cache()->clear(); tagAll(); updateView(true); } void KTextEditor::ViewPrivate::updateRendererConfig() { if (m_startingUp) { return; } m_toggleWWMarker->setChecked(m_renderer->config()->wordWrapMarker()); m_viewInternal->updateBracketMarkAttributes(); m_viewInternal->updateBracketMarks(); // now redraw... m_viewInternal->cache()->clear(); tagAll(); m_viewInternal->updateView(true); // update the left border right, for example linenumbers m_viewInternal->m_leftBorder->updateFont(); m_viewInternal->m_leftBorder->repaint(); m_viewInternal->m_lineScroll->queuePixmapUpdate(); currentInputMode()->updateRendererConfig(); // @@ showIndentLines is not cached anymore. // m_renderer->setShowIndentLines (m_renderer->config()->showIndentationLines()); emit configChanged(); } void KTextEditor::ViewPrivate::updateFoldingConfig() { // folding bar m_viewInternal->m_leftBorder->setFoldingMarkersOn(config()->foldingBar()); m_toggleFoldingMarkers->setChecked(config()->foldingBar()); if (hasCommentInFirstLine(m_doc)) { if (config()->foldFirstLine() && !m_autoFoldedFirstLine) { foldLine(0); m_autoFoldedFirstLine = true; } else if (!config()->foldFirstLine() && m_autoFoldedFirstLine) { unfoldLine(0); m_autoFoldedFirstLine = false; } } else { m_autoFoldedFirstLine = false; } #if 0 // FIXME: FOLDING const QStringList l = { QStringLiteral("folding_toplevel") , QStringLiteral("folding_expandtoplevel") , QStringLiteral("folding_toggle_current") , QStringLiteral("folding_toggle_in_current") }; QAction *a = 0; for (int z = 0; z < l.size(); z++) if ((a = actionCollection()->action(l[z].toAscii().constData()))) { a->setEnabled(doc()->highlight() && doc()->highlight()->allowsFolding()); } #endif } void KTextEditor::ViewPrivate::ensureCursorColumnValid() { KTextEditor::Cursor c = m_viewInternal->cursorPosition(); // make sure the cursor is valid: // - in block selection mode or if wrap cursor is off, the column is arbitrary // - otherwise: it's bounded by the line length if (!blockSelection() && wrapCursor() && (!c.isValid() || c.column() > doc()->lineLength(c.line()))) { c.setColumn(doc()->kateTextLine(cursorPosition().line())->length()); setCursorPosition(c); } } //BEGIN EDIT STUFF void KTextEditor::ViewPrivate::editStart() { m_viewInternal->editStart(); } void KTextEditor::ViewPrivate::editEnd(int editTagLineStart, int editTagLineEnd, bool tagFrom) { m_viewInternal->editEnd(editTagLineStart, editTagLineEnd, tagFrom); } void KTextEditor::ViewPrivate::editSetCursor(const KTextEditor::Cursor &cursor) { m_viewInternal->editSetCursor(cursor); } //END //BEGIN TAG & CLEAR bool KTextEditor::ViewPrivate::tagLine(const KTextEditor::Cursor &virtualCursor) { return m_viewInternal->tagLine(virtualCursor); } bool KTextEditor::ViewPrivate::tagRange(const KTextEditor::Range &range, bool realLines) { return m_viewInternal->tagRange(range, realLines); } bool KTextEditor::ViewPrivate::tagLines(int start, int end, bool realLines) { return m_viewInternal->tagLines(start, end, realLines); } bool KTextEditor::ViewPrivate::tagLines(KTextEditor::Cursor start, KTextEditor::Cursor end, bool realCursors) { return m_viewInternal->tagLines(start, end, realCursors); } void KTextEditor::ViewPrivate::tagAll() { m_viewInternal->tagAll(); } void KTextEditor::ViewPrivate::clear() { m_viewInternal->clear(); } void KTextEditor::ViewPrivate::repaintText(bool paintOnlyDirty) { if (paintOnlyDirty) { m_viewInternal->updateDirty(); } else { m_viewInternal->update(); } } void KTextEditor::ViewPrivate::updateView(bool changed) { //qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::updateView"; m_viewInternal->updateView(changed); m_viewInternal->m_leftBorder->update(); } //END void KTextEditor::ViewPrivate::slotHlChanged() { KateHighlighting *hl = doc()->highlight(); bool ok(!hl->getCommentStart(0).isEmpty() || !hl->getCommentSingleLineStart(0).isEmpty()); if (actionCollection()->action(QStringLiteral("tools_comment"))) { actionCollection()->action(QStringLiteral("tools_comment"))->setEnabled(ok); } if (actionCollection()->action(QStringLiteral("tools_uncomment"))) { actionCollection()->action(QStringLiteral("tools_uncomment"))->setEnabled(ok); } if (actionCollection()->action(QStringLiteral("tools_toggle_comment"))) { actionCollection()->action(QStringLiteral("tools_toggle_comment"))->setEnabled(ok); } // show folding bar if "view defaults" says so, otherwise enable/disable only the menu entry updateFoldingConfig(); } int KTextEditor::ViewPrivate::virtualCursorColumn() const { return doc()->toVirtualColumn(m_viewInternal->cursorPosition()); } void KTextEditor::ViewPrivate::notifyMousePositionChanged(const KTextEditor::Cursor &newPosition) { emit mousePositionChanged(this, newPosition); } //BEGIN KTextEditor::SelectionInterface stuff bool KTextEditor::ViewPrivate::setSelection(const KTextEditor::Range &selection) { /** * anything to do? */ if (selection == m_selection) { return true; } /** * backup old range */ KTextEditor::Range oldSelection = m_selection; /** * set new range */ m_selection.setRange(selection.isEmpty() ? KTextEditor::Range::invalid() : selection); /** * trigger update of correct area */ tagSelection(oldSelection); repaintText(true); /** * emit holy signal */ emit selectionChanged(this); /** * be done */ return true; } bool KTextEditor::ViewPrivate::clearSelection() { return clearSelection(true); } bool KTextEditor::ViewPrivate::clearSelection(bool redraw, bool finishedChangingSelection) { /** * no selection, nothing to do... */ if (!selection()) { return false; } /** * backup old range */ KTextEditor::Range oldSelection = m_selection; /** * invalidate current selection */ m_selection.setRange(KTextEditor::Range::invalid()); /** * trigger update of correct area */ tagSelection(oldSelection); if (redraw) { repaintText(true); } /** * emit holy signal */ if (finishedChangingSelection) { emit selectionChanged(this); } /** * be done */ return true; } bool KTextEditor::ViewPrivate::selection() const { if (!wrapCursor()) { return m_selection != KTextEditor::Range::invalid(); } else { return m_selection.toRange().isValid(); } } QString KTextEditor::ViewPrivate::selectionText() const { return doc()->text(m_selection, blockSelect); } bool KTextEditor::ViewPrivate::removeSelectedText() { if (!selection()) { return false; } doc()->editStart(); // Optimization: clear selection before removing text KTextEditor::Range selection = m_selection; doc()->removeText(selection, blockSelect); // don't redraw the cleared selection - that's done in editEnd(). if (blockSelect) { int selectionColumn = qMin(doc()->toVirtualColumn(selection.start()), doc()->toVirtualColumn(selection.end())); KTextEditor::Range newSelection = selection; newSelection.setStart(KTextEditor::Cursor(newSelection.start().line(), doc()->fromVirtualColumn(newSelection.start().line(), selectionColumn))); newSelection.setEnd(KTextEditor::Cursor(newSelection.end().line(), doc()->fromVirtualColumn(newSelection.end().line(), selectionColumn))); setSelection(newSelection); setCursorPositionInternal(newSelection.start()); } else { clearSelection(false); } doc()->editEnd(); return true; } bool KTextEditor::ViewPrivate::selectAll() { setBlockSelection(false); top(); shiftBottom(); return true; } bool KTextEditor::ViewPrivate::cursorSelected(const KTextEditor::Cursor &cursor) { KTextEditor::Cursor ret = cursor; if ((!blockSelect) && (ret.column() < 0)) { ret.setColumn(0); } if (blockSelect) return cursor.line() >= m_selection.start().line() && ret.line() <= m_selection.end().line() && ret.column() >= m_selection.start().column() && ret.column() <= m_selection.end().column(); else { return m_selection.toRange().contains(cursor) || m_selection.end() == cursor; } } bool KTextEditor::ViewPrivate::lineSelected(int line) { return !blockSelect && m_selection.toRange().containsLine(line); } bool KTextEditor::ViewPrivate::lineEndSelected(const KTextEditor::Cursor &lineEndPos) { return (!blockSelect) && (lineEndPos.line() > m_selection.start().line() || (lineEndPos.line() == m_selection.start().line() && (m_selection.start().column() < lineEndPos.column() || lineEndPos.column() == -1))) && (lineEndPos.line() < m_selection.end().line() || (lineEndPos.line() == m_selection.end().line() && (lineEndPos.column() <= m_selection.end().column() && lineEndPos.column() != -1))); } bool KTextEditor::ViewPrivate::lineHasSelected(int line) { return selection() && m_selection.toRange().containsLine(line); } bool KTextEditor::ViewPrivate::lineIsSelection(int line) { return (line == m_selection.start().line() && line == m_selection.end().line()); } void KTextEditor::ViewPrivate::tagSelection(const KTextEditor::Range &oldSelection) { if (selection()) { if (oldSelection.start().line() == -1) { // We have to tag the whole lot if // 1) we have a selection, and: // a) it's new; or tagLines(m_selection, true); } else if (blockSelection() && (oldSelection.start().column() != m_selection.start().column() || oldSelection.end().column() != m_selection.end().column())) { // b) we're in block selection mode and the columns have changed tagLines(m_selection, true); tagLines(oldSelection, true); } else { if (oldSelection.start() != m_selection.start()) { if (oldSelection.start() < m_selection.start()) { tagLines(oldSelection.start(), m_selection.start(), true); } else { tagLines(m_selection.start(), oldSelection.start(), true); } } if (oldSelection.end() != m_selection.end()) { if (oldSelection.end() < m_selection.end()) { tagLines(oldSelection.end(), m_selection.end(), true); } else { tagLines(m_selection.end(), oldSelection.end(), true); } } } } else { // No more selection, clean up tagLines(oldSelection, true); } } void KTextEditor::ViewPrivate::selectWord(const KTextEditor::Cursor &cursor) { setSelection(doc()->wordRangeAt(cursor)); } void KTextEditor::ViewPrivate::selectLine(const KTextEditor::Cursor &cursor) { int line = cursor.line(); if (line + 1 >= doc()->lines()) { setSelection(KTextEditor::Range(line, 0, line, doc()->lineLength(line))); } else { setSelection(KTextEditor::Range(line, 0, line + 1, 0)); } } void KTextEditor::ViewPrivate::cut() { if (!selection() && !m_config->smartCopyCut()) { return; } copy(); if (!selection()) { selectLine(cursorPosition()); } removeSelectedText(); } void KTextEditor::ViewPrivate::copy() const { QString text; if (!selection()) { if (!m_config->smartCopyCut()) { return; } text = doc()->line(cursorPosition().line()) + QLatin1Char('\n'); m_viewInternal->moveEdge(KateViewInternal::left, false); } else { text = selectionText(); } // copy to clipboard and our history! KTextEditor::EditorPrivate::self()->copyToClipboard(text); } void KTextEditor::ViewPrivate::applyWordWrap() { int first = selectionRange().start().line(); int last = selectionRange().end().line(); if (first == last) { // Either no selection or only one line selected, wrap only the current line first = cursorPosition().line(); last = first; } doc()->wrapParagraph(first, last); } //END //BEGIN KTextEditor::BlockSelectionInterface stuff bool KTextEditor::ViewPrivate::blockSelection() const { return blockSelect; } bool KTextEditor::ViewPrivate::setBlockSelection(bool on) { if (on != blockSelect) { blockSelect = on; KTextEditor::Range oldSelection = m_selection; const bool hadSelection = clearSelection(false, false); setSelection(oldSelection); m_toggleBlockSelection->setChecked(blockSelection()); // when leaving block selection mode, if cursor is at an invalid position or past the end of the // line, move the cursor to the last column of the current line unless cursor wrapping is off ensureCursorColumnValid(); if (!hadSelection) { // emit selectionChanged() according to the KTextEditor::View api // documentation also if there is no selection around. This is needed, // as e.g. the Kate App status bar uses this signal to update the state // of the selection mode (block selection, line based selection) emit selectionChanged(this); } } return true; } bool KTextEditor::ViewPrivate::toggleBlockSelection() { m_toggleBlockSelection->setChecked(!blockSelect); return setBlockSelection(!blockSelect); } bool KTextEditor::ViewPrivate::wrapCursor() const { return !blockSelection(); } //END void KTextEditor::ViewPrivate::slotTextInserted(KTextEditor::View *view, const KTextEditor::Cursor &position, const QString &text) { emit textInserted(view, position, text); } bool KTextEditor::ViewPrivate::insertTemplateInternal(const KTextEditor::Cursor& c, const QString& templateString, const QString& script) { /** * no empty templates */ if (templateString.isEmpty()) { return false; } /** * not for read-only docs */ if (!doc()->isReadWrite()) { return false; } /** * only one handler maybe active at a time; store it in the document. * Clear it first to make sure at no time two handlers are active at once */ doc()->setActiveTemplateHandler(nullptr); doc()->setActiveTemplateHandler(new KateTemplateHandler(this, c, templateString, script, doc()->undoManager())); return true; } bool KTextEditor::ViewPrivate::tagLines(KTextEditor::Range range, bool realRange) { return tagLines(range.start(), range.end(), realRange); } void KTextEditor::ViewPrivate::deactivateEditActions() { foreach (QAction *action, m_editActions) { action->setEnabled(false); } } void KTextEditor::ViewPrivate::activateEditActions() { foreach (QAction *action, m_editActions) { action->setEnabled(true); } } bool KTextEditor::ViewPrivate::mouseTrackingEnabled() const { // FIXME support return true; } bool KTextEditor::ViewPrivate::setMouseTrackingEnabled(bool) { // FIXME support return true; } bool KTextEditor::ViewPrivate::isCompletionActive() const { return completionWidget()->isCompletionActive(); } KateCompletionWidget *KTextEditor::ViewPrivate::completionWidget() const { if (!m_completionWidget) { m_completionWidget = new KateCompletionWidget(const_cast(this)); } return m_completionWidget; } void KTextEditor::ViewPrivate::startCompletion(const KTextEditor::Range &word, KTextEditor::CodeCompletionModel *model) { completionWidget()->startCompletion(word, model); } void KTextEditor::ViewPrivate::abortCompletion() { completionWidget()->abortCompletion(); } void KTextEditor::ViewPrivate::forceCompletion() { completionWidget()->execute(); } void KTextEditor::ViewPrivate::registerCompletionModel(KTextEditor::CodeCompletionModel *model) { completionWidget()->registerCompletionModel(model); } void KTextEditor::ViewPrivate::unregisterCompletionModel(KTextEditor::CodeCompletionModel *model) { completionWidget()->unregisterCompletionModel(model); } bool KTextEditor::ViewPrivate::isCompletionModelRegistered(KTextEditor::CodeCompletionModel *model) const { return completionWidget()->isCompletionModelRegistered(model); } bool KTextEditor::ViewPrivate::isAutomaticInvocationEnabled() const { return !m_temporaryAutomaticInvocationDisabled && m_config->automaticCompletionInvocation(); } void KTextEditor::ViewPrivate::setAutomaticInvocationEnabled(bool enabled) { - config()->setAutomaticCompletionInvocation(enabled); + config()->setValue(KateViewConfig::AutomaticCompletionInvocation, enabled); } void KTextEditor::ViewPrivate::sendCompletionExecuted(const KTextEditor::Cursor &position, KTextEditor::CodeCompletionModel *model, const QModelIndex &index) { emit completionExecuted(this, position, model, index); } void KTextEditor::ViewPrivate::sendCompletionAborted() { emit completionAborted(this); } void KTextEditor::ViewPrivate::paste(const QString *textToPaste) { m_temporaryAutomaticInvocationDisabled = true; doc()->paste(this, textToPaste ? *textToPaste : QApplication::clipboard()->text(QClipboard::Clipboard)); m_temporaryAutomaticInvocationDisabled = false; } bool KTextEditor::ViewPrivate::setCursorPosition(KTextEditor::Cursor position) { return setCursorPositionInternal(position, 1, true); } KTextEditor::Cursor KTextEditor::ViewPrivate::cursorPosition() const { return m_viewInternal->cursorPosition(); } KTextEditor::Cursor KTextEditor::ViewPrivate::cursorPositionVirtual() const { return KTextEditor::Cursor(m_viewInternal->cursorPosition().line(), virtualCursorColumn()); } QPoint KTextEditor::ViewPrivate::cursorToCoordinate(const KTextEditor::Cursor &cursor) const { // map from ViewInternal to View coordinates const QPoint pt = m_viewInternal->cursorToCoordinate(cursor, true, false); return pt == QPoint(-1, -1) ? pt : m_viewInternal->mapToParent(pt); } KTextEditor::Cursor KTextEditor::ViewPrivate::coordinatesToCursor(const QPoint &coords) const { // map from View to ViewInternal coordinates return m_viewInternal->coordinatesToCursor(m_viewInternal->mapFromParent(coords), false); } QPoint KTextEditor::ViewPrivate::cursorPositionCoordinates() const { // map from ViewInternal to View coordinates const QPoint pt = m_viewInternal->cursorCoordinates(false); return pt == QPoint(-1, -1) ? pt : m_viewInternal->mapToParent(pt); } void KTextEditor::ViewPrivate::setScrollPositionInternal(KTextEditor::Cursor &cursor) { m_viewInternal->scrollPos(cursor, false, true, false); } void KTextEditor::ViewPrivate::setHorizontalScrollPositionInternal(int x) { m_viewInternal->scrollColumns(x); } KTextEditor::Cursor KTextEditor::ViewPrivate::maxScrollPositionInternal() const { return m_viewInternal->maxStartPos(true); } int KTextEditor::ViewPrivate::firstDisplayedLineInternal(LineType lineType) const { if (lineType == RealLine) { return m_textFolding.visibleLineToLine(m_viewInternal->startLine()); } else { return m_viewInternal->startLine(); } } int KTextEditor::ViewPrivate::lastDisplayedLineInternal(LineType lineType) const { if (lineType == RealLine) { return m_textFolding.visibleLineToLine(m_viewInternal->endLine()); } else { return m_viewInternal->endLine(); } } QRect KTextEditor::ViewPrivate::textAreaRectInternal() const { const auto sourceRect = m_viewInternal->rect(); const auto topLeft = m_viewInternal->mapTo(this, sourceRect.topLeft()); const auto bottomRight = m_viewInternal->mapTo(this, sourceRect.bottomRight()); return {topLeft, bottomRight}; } bool KTextEditor::ViewPrivate::setCursorPositionVisual(const KTextEditor::Cursor &position) { return setCursorPositionInternal(position, doc()->config()->tabWidth(), true); } QString KTextEditor::ViewPrivate::currentTextLine() { return doc()->line(cursorPosition().line()); } QTextLayout * KTextEditor::ViewPrivate::textLayout(int line) const { KateLineLayoutPtr thisLine = m_viewInternal->cache()->line(line); return thisLine->isValid() ? thisLine->layout() : nullptr; } QTextLayout * KTextEditor::ViewPrivate::textLayout(const KTextEditor::Cursor &pos) const { KateLineLayoutPtr thisLine = m_viewInternal->cache()->line(pos); return thisLine->isValid() ? thisLine->layout() : nullptr; } void KTextEditor::ViewPrivate::indent() { KTextEditor::Cursor c(cursorPosition().line(), 0); KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c); doc()->indent(r, 1); } void KTextEditor::ViewPrivate::unIndent() { KTextEditor::Cursor c(cursorPosition().line(), 0); KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c); doc()->indent(r, -1); } void KTextEditor::ViewPrivate::cleanIndent() { KTextEditor::Cursor c(cursorPosition().line(), 0); KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c); doc()->indent(r, 0); } void KTextEditor::ViewPrivate::align() { // no selection: align current line; selection: use selection range const int line = cursorPosition().line(); KTextEditor::Range alignRange(KTextEditor::Cursor(line, 0), KTextEditor::Cursor(line, 0)); if (selection()) { alignRange = selectionRange(); } doc()->align(this, alignRange); } void KTextEditor::ViewPrivate::comment() { m_selection.setInsertBehaviors(Kate::TextRange::ExpandLeft | Kate::TextRange::ExpandRight); doc()->comment(this, cursorPosition().line(), cursorPosition().column(), 1); m_selection.setInsertBehaviors(Kate::TextRange::ExpandRight); } void KTextEditor::ViewPrivate::uncomment() { doc()->comment(this, cursorPosition().line(), cursorPosition().column(), -1); } void KTextEditor::ViewPrivate::toggleComment() { m_selection.setInsertBehaviors(Kate::TextRange::ExpandLeft | Kate::TextRange::ExpandRight); doc()->comment(this, cursorPosition().line(), cursorPosition().column(), 0); m_selection.setInsertBehaviors(Kate::TextRange::ExpandRight); } void KTextEditor::ViewPrivate::uppercase() { doc()->transform(this, cursorPosition(), KTextEditor::DocumentPrivate::Uppercase); } void KTextEditor::ViewPrivate::killLine() { if (m_selection.isEmpty()) { doc()->removeLine(cursorPosition().line()); } else { doc()->editStart(); // cache endline, else that moves and we might delete complete document if last line is selected! for (int line = m_selection.end().line(), endLine = m_selection.start().line(); line >= endLine; line--) { doc()->removeLine(line); } doc()->editEnd(); } } void KTextEditor::ViewPrivate::lowercase() { doc()->transform(this, cursorPosition(), KTextEditor::DocumentPrivate::Lowercase); } void KTextEditor::ViewPrivate::capitalize() { doc()->editStart(); doc()->transform(this, cursorPosition(), KTextEditor::DocumentPrivate::Lowercase); doc()->transform(this, cursorPosition(), KTextEditor::DocumentPrivate::Capitalize); doc()->editEnd(); } void KTextEditor::ViewPrivate::keyReturn() { doc()->newLine(this); m_viewInternal->iconBorder()->updateForCursorLineChange(); m_viewInternal->updateView(); } void KTextEditor::ViewPrivate::smartNewline() { const KTextEditor::Cursor cursor = cursorPosition(); const int ln = cursor.line(); Kate::TextLine line = doc()->kateTextLine(ln); int col = qMin(cursor.column(), line->firstChar()); if (col != -1) { while (line->length() > col && !(line->at(col).isLetterOrNumber() || line->at(col) == QLatin1Char('_')) && col < cursor.column()) { ++col; } } else { col = line->length(); // stay indented } doc()->editStart(); doc()->editWrapLine(ln, cursor.column()); doc()->insertText(KTextEditor::Cursor(ln + 1, 0), line->string(0, col)); doc()->editEnd(); m_viewInternal->updateView(); } void KTextEditor::ViewPrivate::backspace() { doc()->backspace(this, cursorPosition()); } void KTextEditor::ViewPrivate::insertTab() { doc()->insertTab(this, cursorPosition()); } void KTextEditor::ViewPrivate::deleteWordLeft() { doc()->editStart(); m_viewInternal->wordPrev(true); KTextEditor::Range selection = selectionRange(); removeSelectedText(); doc()->editEnd(); m_viewInternal->tagRange(selection, true); m_viewInternal->updateDirty(); } void KTextEditor::ViewPrivate::keyDelete() { doc()->del(this, cursorPosition()); } void KTextEditor::ViewPrivate::deleteWordRight() { doc()->editStart(); m_viewInternal->wordNext(true); KTextEditor::Range selection = selectionRange(); removeSelectedText(); doc()->editEnd(); m_viewInternal->tagRange(selection, true); m_viewInternal->updateDirty(); } void KTextEditor::ViewPrivate::transpose() { doc()->transpose(cursorPosition()); } void KTextEditor::ViewPrivate::cursorLeft() { if (selection() && !config()->persistentSelection()) { if (currentTextLine().isRightToLeft()) { m_viewInternal->updateCursor(selectionRange().end()); setSelection(KTextEditor::Range::invalid()); } else { m_viewInternal->updateCursor(selectionRange().start()); setSelection(KTextEditor::Range::invalid()); } } else { if (currentTextLine().isRightToLeft()) { m_viewInternal->cursorNextChar(); } else { m_viewInternal->cursorPrevChar(); } } } void KTextEditor::ViewPrivate::shiftCursorLeft() { if (currentTextLine().isRightToLeft()) { m_viewInternal->cursorNextChar(true); } else { m_viewInternal->cursorPrevChar(true); } } void KTextEditor::ViewPrivate::cursorRight() { if (selection() && !config()->persistentSelection()) { if (currentTextLine().isRightToLeft()) { m_viewInternal->updateCursor(selectionRange().start()); setSelection(KTextEditor::Range::invalid()); } else { m_viewInternal->updateCursor(selectionRange().end()); setSelection(KTextEditor::Range::invalid()); } } else { if (currentTextLine().isRightToLeft()) { m_viewInternal->cursorPrevChar(); } else { m_viewInternal->cursorNextChar(); } } } void KTextEditor::ViewPrivate::shiftCursorRight() { if (currentTextLine().isRightToLeft()) { m_viewInternal->cursorPrevChar(true); } else { m_viewInternal->cursorNextChar(true); } } void KTextEditor::ViewPrivate::wordLeft() { if (currentTextLine().isRightToLeft()) { m_viewInternal->wordNext(); } else { m_viewInternal->wordPrev(); } } void KTextEditor::ViewPrivate::shiftWordLeft() { if (currentTextLine().isRightToLeft()) { m_viewInternal->wordNext(true); } else { m_viewInternal->wordPrev(true); } } void KTextEditor::ViewPrivate::wordRight() { if (currentTextLine().isRightToLeft()) { m_viewInternal->wordPrev(); } else { m_viewInternal->wordNext(); } } void KTextEditor::ViewPrivate::shiftWordRight() { if (currentTextLine().isRightToLeft()) { m_viewInternal->wordPrev(true); } else { m_viewInternal->wordNext(true); } } void KTextEditor::ViewPrivate::home() { m_viewInternal->home(); } void KTextEditor::ViewPrivate::shiftHome() { m_viewInternal->home(true); } void KTextEditor::ViewPrivate::end() { m_viewInternal->end(); } void KTextEditor::ViewPrivate::shiftEnd() { m_viewInternal->end(true); } void KTextEditor::ViewPrivate::up() { m_viewInternal->cursorUp(); } void KTextEditor::ViewPrivate::shiftUp() { m_viewInternal->cursorUp(true); } void KTextEditor::ViewPrivate::down() { m_viewInternal->cursorDown(); } void KTextEditor::ViewPrivate::shiftDown() { m_viewInternal->cursorDown(true); } void KTextEditor::ViewPrivate::scrollUp() { m_viewInternal->scrollUp(); } void KTextEditor::ViewPrivate::scrollDown() { m_viewInternal->scrollDown(); } void KTextEditor::ViewPrivate::topOfView() { m_viewInternal->topOfView(); } void KTextEditor::ViewPrivate::shiftTopOfView() { m_viewInternal->topOfView(true); } void KTextEditor::ViewPrivate::bottomOfView() { m_viewInternal->bottomOfView(); } void KTextEditor::ViewPrivate::shiftBottomOfView() { m_viewInternal->bottomOfView(true); } void KTextEditor::ViewPrivate::pageUp() { m_viewInternal->pageUp(); } void KTextEditor::ViewPrivate::shiftPageUp() { m_viewInternal->pageUp(true); } void KTextEditor::ViewPrivate::pageDown() { m_viewInternal->pageDown(); } void KTextEditor::ViewPrivate::shiftPageDown() { m_viewInternal->pageDown(true); } void KTextEditor::ViewPrivate::top() { m_viewInternal->top_home(); } void KTextEditor::ViewPrivate::shiftTop() { m_viewInternal->top_home(true); } void KTextEditor::ViewPrivate::bottom() { m_viewInternal->bottom_end(); } void KTextEditor::ViewPrivate::shiftBottom() { m_viewInternal->bottom_end(true); } void KTextEditor::ViewPrivate::toMatchingBracket() { m_viewInternal->cursorToMatchingBracket(); } void KTextEditor::ViewPrivate::shiftToMatchingBracket() { m_viewInternal->cursorToMatchingBracket(true); } void KTextEditor::ViewPrivate::toPrevModifiedLine() { const int startLine = cursorPosition().line() - 1; const int line = doc()->findTouchedLine(startLine, false); if (line >= 0) { KTextEditor::Cursor c(line, 0); m_viewInternal->updateSelection(c, false); m_viewInternal->updateCursor(c); } } void KTextEditor::ViewPrivate::toNextModifiedLine() { const int startLine = cursorPosition().line() + 1; const int line = doc()->findTouchedLine(startLine, true); if (line >= 0) { KTextEditor::Cursor c(line, 0); m_viewInternal->updateSelection(c, false); m_viewInternal->updateCursor(c); } } KTextEditor::Range KTextEditor::ViewPrivate::selectionRange() const { return m_selection; } KTextEditor::Document *KTextEditor::ViewPrivate::document() const { return m_doc; } void KTextEditor::ViewPrivate::setContextMenu(QMenu *menu) { if (m_contextMenu) { disconnect(m_contextMenu, SIGNAL(aboutToShow()), this, SLOT(aboutToShowContextMenu())); disconnect(m_contextMenu, SIGNAL(aboutToHide()), this, SLOT(aboutToHideContextMenu())); } m_contextMenu = menu; m_userContextMenuSet = true; if (m_contextMenu) { connect(m_contextMenu, SIGNAL(aboutToShow()), this, SLOT(aboutToShowContextMenu())); connect(m_contextMenu, SIGNAL(aboutToHide()), this, SLOT(aboutToHideContextMenu())); } } QMenu *KTextEditor::ViewPrivate::contextMenu() const { if (m_userContextMenuSet) { return m_contextMenu; } else { KXMLGUIClient *client = const_cast(this); while (client->parentClient()) { client = client->parentClient(); } //qCDebug(LOG_KTE) << "looking up all menu containers"; if (client->factory()) { QList conts = client->factory()->containers(QStringLiteral("menu")); foreach (QWidget *w, conts) { if (w->objectName() == QLatin1String("ktexteditor_popup")) { //perhaps optimize this block QMenu *menu = (QMenu *)w; // menu is a reusable instance shared among all views. Therefore, // disconnect the current receiver(s) from the menu show/hide signals // before connecting `this` view. This ensures that only the current // view gets a signal when the menu is about to be shown or hidden, // and not also the view(s) that previously had the menu open. disconnect(menu, SIGNAL(aboutToShow()), nullptr, nullptr); disconnect(menu, SIGNAL(aboutToHide()), nullptr, nullptr); connect(menu, SIGNAL(aboutToShow()), this, SLOT(aboutToShowContextMenu())); connect(menu, SIGNAL(aboutToHide()), this, SLOT(aboutToHideContextMenu())); return menu; } } } } return nullptr; } QMenu *KTextEditor::ViewPrivate::defaultContextMenu(QMenu *menu) const { if (!menu) { menu = new QMenu(const_cast(this)); } menu->addAction(m_editUndo); menu->addAction(m_editRedo); menu->addSeparator(); menu->addAction(m_cut); menu->addAction(m_copy); menu->addAction(m_paste); menu->addSeparator(); menu->addAction(m_selectAll); menu->addAction(m_deSelect); if (QAction *spellingSuggestions = actionCollection()->action(QStringLiteral("spelling_suggestions"))) { menu->addSeparator(); menu->addAction(spellingSuggestions); } if (QAction *bookmark = actionCollection()->action(QStringLiteral("bookmarks"))) { menu->addSeparator(); menu->addAction(bookmark); } return menu; } void KTextEditor::ViewPrivate::aboutToShowContextMenu() { QMenu *menu = qobject_cast(sender()); if (menu) { emit contextMenuAboutToShow(this, menu); } } void KTextEditor::ViewPrivate::aboutToHideContextMenu() { m_spellingMenu->setUseMouseForMisspelledRange(false); } // BEGIN ConfigInterface stff QStringList KTextEditor::ViewPrivate::configKeys() const { static const QStringList keys = { QStringLiteral("icon-bar"), QStringLiteral("line-numbers"), QStringLiteral("dynamic-word-wrap"), QStringLiteral("background-color"), QStringLiteral("selection-color"), QStringLiteral("search-highlight-color"), QStringLiteral("replace-highlight-color"), QStringLiteral("default-mark-type"), QStringLiteral("allow-mark-menu"), QStringLiteral("folding-bar"), QStringLiteral("folding-preview"), QStringLiteral("icon-border-color"), QStringLiteral("folding-marker-color"), QStringLiteral("line-number-color"), QStringLiteral("current-line-number-color"), QStringLiteral("modification-markers"), QStringLiteral("keyword-completion"), QStringLiteral("word-count"), QStringLiteral("scrollbar-minimap"), QStringLiteral("scrollbar-preview"), QStringLiteral("font") }; return keys; } QVariant KTextEditor::ViewPrivate::configValue(const QString &key) { if (key == QLatin1String("icon-bar")) { return config()->iconBar(); } else if (key == QLatin1String("line-numbers")) { return config()->lineNumbers(); } else if (key == QLatin1String("dynamic-word-wrap")) { return config()->dynWordWrap(); } else if (key == QLatin1String("background-color")) { return renderer()->config()->backgroundColor(); } else if (key == QLatin1String("selection-color")) { return renderer()->config()->selectionColor(); } else if (key == QLatin1String("search-highlight-color")) { return renderer()->config()->searchHighlightColor(); } else if (key == QLatin1String("replace-highlight-color")) { return renderer()->config()->replaceHighlightColor(); } else if (key == QLatin1String("default-mark-type")) { return config()->defaultMarkType(); } else if (key == QLatin1String("allow-mark-menu")) { return config()->allowMarkMenu(); } else if (key == QLatin1String("folding-bar")) { return config()->foldingBar(); } else if (key == QLatin1String("folding-preview")) { return config()->foldingPreview(); } else if (key == QLatin1String("icon-border-color")) { return renderer()->config()->iconBarColor(); } else if (key == QLatin1String("folding-marker-color")) { return renderer()->config()->foldingColor(); } else if (key == QLatin1String("line-number-color")) { return renderer()->config()->lineNumberColor(); } else if (key == QLatin1String("current-line-number-color")) { return renderer()->config()->currentLineNumberColor(); } else if (key == QLatin1String("modification-markers")) { return config()->lineModification(); } else if (key == QLatin1String("keyword-completion")) { return config()->keywordCompletion(); } else if (key == QLatin1String("word-count")) { return config()->showWordCount(); } else if (key == QLatin1String("scrollbar-minimap")) { return config()->scrollBarMiniMap(); } else if (key == QLatin1String("scrollbar-preview")) { return config()->scrollBarPreview(); } else if (key == QLatin1String("font")) { return renderer()->config()->font(); } // return invalid variant return QVariant(); } void KTextEditor::ViewPrivate::setConfigValue(const QString &key, const QVariant &value) { + // First, try the new config interface + if (config()->setValue(key, value)) { + return; + + } else if (renderer()->config()->setValue(key, value)) { + return; + } + + // No success? Go the old way if (value.canConvert(QVariant::Color)) { if (key == QLatin1String("background-color")) { renderer()->config()->setBackgroundColor(value.value()); } else if (key == QLatin1String("selection-color")) { renderer()->config()->setSelectionColor(value.value()); } else if (key == QLatin1String("search-highlight-color")) { renderer()->config()->setSearchHighlightColor(value.value()); } else if (key == QLatin1String("replace-highlight-color")) { renderer()->config()->setReplaceHighlightColor(value.value()); } else if (key == QLatin1String("icon-border-color")) { renderer()->config()->setIconBarColor(value.value()); } else if (key == QLatin1String("folding-marker-color")) { renderer()->config()->setFoldingColor(value.value()); } else if (key == QLatin1String("line-number-color")) { renderer()->config()->setLineNumberColor(value.value()); } else if (key == QLatin1String("current-line-number-color")) { renderer()->config()->setCurrentLineNumberColor(value.value()); } } else if (value.type() == QVariant::Bool) { // Note explicit type check above. If we used canConvert, then // values of type UInt will be trapped here. - if (key == QLatin1String("icon-bar")) { - config()->setIconBar(value.toBool()); - } else if (key == QLatin1String("line-numbers")) { - config()->setLineNumbers(value.toBool()); - } else if (key == QLatin1String("dynamic-word-wrap")) { + if (key == QLatin1String("dynamic-word-wrap")) { config()->setDynWordWrap(value.toBool()); - } else if (key == QLatin1String("allow-mark-menu")) { - config()->setAllowMarkMenu(value.toBool()); - } else if (key == QLatin1String("folding-bar")) { - config()->setFoldingBar(value.toBool()); - } else if (key == QLatin1String("folding-preview")) { - config()->setFoldingPreview(value.toBool()); - } else if (key == QLatin1String("modification-markers")) { - config()->setLineModification(value.toBool()); - } else if (key == QLatin1String("keyword-completion")) { - config()->setKeywordCompletion(value.toBool()); } else if (key == QLatin1String("word-count")) { config()->setShowWordCount(value.toBool()); - } else if (key == QLatin1String("scrollbar-minimap")) { - config()->setScrollBarMiniMap(value.toBool()); - } else if (key == QLatin1String("scrollbar-preview")) { - config()->setScrollBarPreview(value.toBool()); - } - - } else if (value.canConvert(QVariant::UInt)) { - if (key == QLatin1String("default-mark-type")) { - config()->setDefaultMarkType(value.toUInt()); } } else if (value.canConvert(QVariant::Font)) { if (key == QLatin1String("font")) { renderer()->config()->setFont(value.value()); } } } // END ConfigInterface void KTextEditor::ViewPrivate::userInvokedCompletion() { completionWidget()->userInvokedCompletion(); } KateViewBar *KTextEditor::ViewPrivate::bottomViewBar() const { return m_bottomViewBar; } KateGotoBar *KTextEditor::ViewPrivate::gotoBar() { if (!m_gotoBar) { m_gotoBar = new KateGotoBar(this); bottomViewBar()->addBarWidget(m_gotoBar); } return m_gotoBar; } KateDictionaryBar *KTextEditor::ViewPrivate::dictionaryBar() { if (!m_dictionaryBar) { m_dictionaryBar = new KateDictionaryBar(this); bottomViewBar()->addBarWidget(m_dictionaryBar); } return m_dictionaryBar; } void KTextEditor::ViewPrivate::setAnnotationModel(KTextEditor::AnnotationModel *model) { KTextEditor::AnnotationModel *oldmodel = m_annotationModel; m_annotationModel = model; m_viewInternal->m_leftBorder->annotationModelChanged(oldmodel, m_annotationModel); } KTextEditor::AnnotationModel *KTextEditor::ViewPrivate::annotationModel() const { return m_annotationModel; } void KTextEditor::ViewPrivate::setAnnotationBorderVisible(bool visible) { m_viewInternal->m_leftBorder->setAnnotationBorderOn(visible); } bool KTextEditor::ViewPrivate::isAnnotationBorderVisible() const { return m_viewInternal->m_leftBorder->annotationBorderOn(); } KTextEditor::AbstractAnnotationItemDelegate* KTextEditor::ViewPrivate::annotationItemDelegate() const { return m_viewInternal->m_leftBorder->annotationItemDelegate(); } void KTextEditor::ViewPrivate::setAnnotationItemDelegate(KTextEditor::AbstractAnnotationItemDelegate *delegate) { m_viewInternal->m_leftBorder->setAnnotationItemDelegate(delegate); } bool KTextEditor::ViewPrivate::uniformAnnotationItemSizes() const { return m_viewInternal->m_leftBorder->uniformAnnotationItemSizes(); } void KTextEditor::ViewPrivate::setAnnotationUniformItemSizes(bool enable) { m_viewInternal->m_leftBorder->setAnnotationUniformItemSizes(enable); } KTextEditor::Range KTextEditor::ViewPrivate::visibleRange() { //ensure that the view is up-to-date, otherwise 'endPos()' might fail! if (!m_viewInternal->endPos().isValid()) { m_viewInternal->updateView(); } return KTextEditor::Range(m_viewInternal->toRealCursor(m_viewInternal->startPos()), m_viewInternal->toRealCursor(m_viewInternal->endPos())); } bool KTextEditor::ViewPrivate::event(QEvent *e) { switch (e->type()) { case QEvent::StyleChange: setupLayout(); return true; default: return KTextEditor::View::event(e); } } void KTextEditor::ViewPrivate::paintEvent(QPaintEvent *e) { //base class KTextEditor::View::paintEvent(e); const QRect contentsRect = m_topSpacer->geometry()| m_bottomSpacer->geometry()| m_leftSpacer->geometry()| m_rightSpacer->geometry(); if (contentsRect.isValid()) { QStyleOptionFrame opt; opt.initFrom(this); opt.frameShape = QFrame::StyledPanel; opt.state |= QStyle::State_Sunken; // clear mouseOver and focus state // update from relevant widgets opt.state &= ~(QStyle::State_HasFocus|QStyle::State_MouseOver); const QList widgets = QList() << m_viewInternal << m_viewInternal->m_leftBorder << m_viewInternal->m_lineScroll << m_viewInternal->m_columnScroll; foreach (const QWidget *w, widgets) { if (w->hasFocus()) opt.state |= QStyle::State_HasFocus; if (w->underMouse()) opt.state |= QStyle::State_MouseOver; } // update rect opt.rect=contentsRect; // render QPainter paint(this); paint.setClipRegion(e->region()); paint.setRenderHints(QPainter::Antialiasing); style()->drawControl(QStyle::CE_ShapedFrame, &opt, &paint, this); } } void KTextEditor::ViewPrivate::toggleOnTheFlySpellCheck(bool b) { doc()->onTheFlySpellCheckingEnabled(b); } void KTextEditor::ViewPrivate::reflectOnTheFlySpellCheckStatus(bool enabled) { m_spellingMenu->setVisible(enabled); m_toggleOnTheFlySpellCheck->setChecked(enabled); } KateSpellingMenu *KTextEditor::ViewPrivate::spellingMenu() { return m_spellingMenu; } void KTextEditor::ViewPrivate::notifyAboutRangeChange(int startLine, int endLine, bool rangeWithAttribute) { #ifdef VIEW_RANGE_DEBUG // output args qCDebug(LOG_KTE) << "trigger attribute changed from" << startLine << "to" << endLine << "rangeWithAttribute" << rangeWithAttribute; #endif // first call: if (!m_delayedUpdateTriggered) { m_delayedUpdateTriggered = true; m_lineToUpdateMin = -1; m_lineToUpdateMax = -1; // only set initial line range, if range with attribute! if (rangeWithAttribute) { m_lineToUpdateMin = startLine; m_lineToUpdateMax = endLine; } // emit queued signal and be done emit delayedUpdateOfView(); return; } // ignore lines if no attribute if (!rangeWithAttribute) { return; } // update line range if (startLine != -1 && (m_lineToUpdateMin == -1 || startLine < m_lineToUpdateMin)) { m_lineToUpdateMin = startLine; } if (endLine != -1 && endLine > m_lineToUpdateMax) { m_lineToUpdateMax = endLine; } } void KTextEditor::ViewPrivate::slotDelayedUpdateOfView() { if (!m_delayedUpdateTriggered) { return; } #ifdef VIEW_RANGE_DEBUG // output args qCDebug(LOG_KTE) << "delayed attribute changed from" << m_lineToUpdateMin << "to" << m_lineToUpdateMax; #endif // update ranges in updateRangesIn(KTextEditor::Attribute::ActivateMouseIn); updateRangesIn(KTextEditor::Attribute::ActivateCaretIn); // update view, if valid line range, else only feedback update wanted anyway if (m_lineToUpdateMin != -1 && m_lineToUpdateMax != -1) { tagLines(m_lineToUpdateMin, m_lineToUpdateMax, true); updateView(true); } // reset flags m_delayedUpdateTriggered = false; m_lineToUpdateMin = -1; m_lineToUpdateMax = -1; } void KTextEditor::ViewPrivate::updateRangesIn(KTextEditor::Attribute::ActivationType activationType) { // new ranges with cursor in, default none QSet newRangesIn; // on which range set we work? QSet &oldSet = (activationType == KTextEditor::Attribute::ActivateMouseIn) ? m_rangesMouseIn : m_rangesCaretIn; // which cursor position to honor? KTextEditor::Cursor currentCursor = (activationType == KTextEditor::Attribute::ActivateMouseIn) ? m_viewInternal->mousePosition() : m_viewInternal->cursorPosition(); // first: validate the remembered ranges QSet validRanges; foreach (Kate::TextRange *range, oldSet) if (doc()->buffer().rangePointerValid(range)) { validRanges.insert(range); } // cursor valid? else no new ranges can be found if (currentCursor.isValid() && currentCursor.line() < doc()->buffer().lines()) { // now: get current ranges for the line of cursor with an attribute QList rangesForCurrentCursor = doc()->buffer().rangesForLine(currentCursor.line(), this, false); // match which ranges really fit the given cursor foreach (Kate::TextRange *range, rangesForCurrentCursor) { // range has no dynamic attribute of right type and no feedback object if ((!range->attribute() || !range->attribute()->dynamicAttribute(activationType)) && !range->feedback()) { continue; } // range doesn't contain cursor, not interesting if ((range->start().insertBehavior() == KTextEditor::MovingCursor::StayOnInsert) ? (currentCursor < range->start().toCursor()) : (currentCursor <= range->start().toCursor())) { continue; } if ((range->end().insertBehavior() == KTextEditor::MovingCursor::StayOnInsert) ? (range->end().toCursor() <= currentCursor) : (range->end().toCursor() < currentCursor)) { continue; } // range contains cursor, was it already in old set? if (validRanges.contains(range)) { // insert in new, remove from old, be done with it newRangesIn.insert(range); validRanges.remove(range); continue; } // oh, new range, trigger update and insert into new set newRangesIn.insert(range); if (range->attribute() && range->attribute()->dynamicAttribute(activationType)) { notifyAboutRangeChange(range->start().line(), range->end().line(), true); } // feedback if (range->feedback()) { if (activationType == KTextEditor::Attribute::ActivateMouseIn) { range->feedback()->mouseEnteredRange(range, this); } else { range->feedback()->caretEnteredRange(range, this); emit caretChangedRange(this); } } #ifdef VIEW_RANGE_DEBUG // found new range for activation qCDebug(LOG_KTE) << "activated new range" << range << "by" << activationType; #endif } } // now: notify for left ranges! foreach (Kate::TextRange *range, validRanges) { // range valid + right dynamic attribute, trigger update if (range->toRange().isValid() && range->attribute() && range->attribute()->dynamicAttribute(activationType)) { notifyAboutRangeChange(range->start().line(), range->end().line(), true); } // feedback if (range->feedback()) { if (activationType == KTextEditor::Attribute::ActivateMouseIn) { range->feedback()->mouseExitedRange(range, this); } else { range->feedback()->caretExitedRange(range, this); emit caretChangedRange(this); } } } // set new ranges oldSet = newRangesIn; } void KTextEditor::ViewPrivate::postMessage(KTextEditor::Message *message, QList > actions) { // just forward to KateMessageWidget :-) auto messageWidget = m_messageWidgets[message->position()]; if (!messageWidget) { // this branch is used for: TopInView, CenterInView, and BottomInView messageWidget = new KateMessageWidget(m_viewInternal, true); m_messageWidgets[message->position()] = messageWidget; m_notificationLayout->addWidget(messageWidget, message->position()); connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), messageWidget, SLOT(startAutoHideTimer())); connect(this, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), messageWidget, SLOT(startAutoHideTimer())); } messageWidget->postMessage(message, actions); } KateMessageWidget *KTextEditor::ViewPrivate::messageWidget() { return m_messageWidgets[KTextEditor::Message::TopInView]; } void KTextEditor::ViewPrivate::saveFoldingState() { m_savedFoldingState = m_textFolding.exportFoldingRanges(); } void KTextEditor::ViewPrivate::applyFoldingState() { m_textFolding.importFoldingRanges(m_savedFoldingState); m_savedFoldingState = QJsonDocument(); } void KTextEditor::ViewPrivate::exportHtmlToFile(const QString &file) { KateExporter(this).exportToFile(file); } void KTextEditor::ViewPrivate::exportHtmlToClipboard () { KateExporter(this).exportToClipboard(); } void KTextEditor::ViewPrivate::exportHtmlToFile () { const QString file = QFileDialog::getSaveFileName(this, i18n("Export File as HTML"), doc()->documentName()); if (!file.isEmpty()) { KateExporter(this).exportToFile(file); } } void KTextEditor::ViewPrivate::clearHighlights() { qDeleteAll(m_rangesForHighlights); m_rangesForHighlights.clear(); m_currentTextForHighlights.clear(); } void KTextEditor::ViewPrivate::selectionChangedForHighlights() { QString text; // if text of selection is still the same, abort if (selection() && selectionRange().onSingleLine()) { text = selectionText(); if (text == m_currentTextForHighlights) return; } // text changed: remove all highlights + create new ones // (do not call clearHighlights(), since this also resets the m_currentTextForHighlights qDeleteAll(m_rangesForHighlights); m_rangesForHighlights.clear(); // do not highlight strings with leading and trailing spaces if (!text.isEmpty() && (text.at(0).isSpace() || text.at(text.length()-1).isSpace())) return; // trigger creation of ranges for current view range m_currentTextForHighlights = text; createHighlights(); } void KTextEditor::ViewPrivate::createHighlights() { // do nothing if no text to highlight if (m_currentTextForHighlights.isEmpty()) { return; } KTextEditor::Attribute::Ptr attr(new KTextEditor::Attribute()); attr->setBackground(Qt::yellow); // set correct highlight color from Kate's color schema QColor fgColor = defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color(); QColor bgColor = renderer()->config()->searchHighlightColor(); attr->setForeground(fgColor); attr->setBackground(bgColor); KTextEditor::Cursor start(visibleRange().start()); KTextEditor::Range searchRange; /** * only add word boundary if we can find the text then * fixes $lala hl */ QString regex = QRegExp::escape (m_currentTextForHighlights); if (QRegExp (QStringLiteral("\\b%1").arg(regex)).indexIn (QStringLiteral(" %1 ").arg(m_currentTextForHighlights)) != -1) regex = QStringLiteral("\\b%1").arg(regex); if (QRegExp (QStringLiteral("%1\\b").arg(regex)).indexIn (QStringLiteral(" %1 ").arg(m_currentTextForHighlights)) != -1) regex = QStringLiteral("%1\\b").arg(regex); QVector matches; do { searchRange.setRange(start, visibleRange().end()); matches = doc()->searchText(searchRange, regex, KTextEditor::Regex); if (matches.first().isValid()) { KTextEditor::MovingRange* mr = doc()->newMovingRange(matches.first()); mr->setAttribute(attr); mr->setView(this); mr->setZDepth(-90000.0); // Set the z-depth to slightly worse than the selection mr->setAttributeOnlyForViews(true); m_rangesForHighlights.append(mr); start = matches.first().end(); } } while (matches.first().isValid()); } KateAbstractInputMode *KTextEditor::ViewPrivate::currentInputMode() const { return m_viewInternal->m_currentInputMode; } void KTextEditor::ViewPrivate::toggleInputMode() { if (QAction *a = dynamic_cast(sender())) { setInputMode(static_cast(a->data().toInt())); } } void KTextEditor::ViewPrivate::cycleInputMode() { InputMode current = currentInputMode()->viewInputMode(); InputMode to = (current == KTextEditor::View::NormalInputMode) ? KTextEditor::View::ViInputMode : KTextEditor::View::NormalInputMode; setInputMode(to); } //BEGIN KTextEditor::PrintInterface stuff bool KTextEditor::ViewPrivate::print() { return KatePrinter::print(this); } void KTextEditor::ViewPrivate::printPreview() { KatePrinter::printPreview(this); } //END //BEGIN KTextEditor::InlineNoteInterface void KTextEditor::ViewPrivate::registerInlineNoteProvider(KTextEditor::InlineNoteProvider *provider) { if (! m_inlineNoteProviders.contains(provider)) { m_inlineNoteProviders.append(provider); connect(provider, &KTextEditor::InlineNoteProvider::inlineNotesReset, this, &ViewPrivate::inlineNotesReset); connect(provider, &KTextEditor::InlineNoteProvider::inlineNotesChanged, this, &ViewPrivate::inlineNotesLineChanged); inlineNotesReset(); } } void KTextEditor::ViewPrivate::unregisterInlineNoteProvider(KTextEditor::InlineNoteProvider *provider) { const int index = m_inlineNoteProviders.indexOf(provider); if (index >= 0) { m_inlineNoteProviders.removeAt(index); provider->disconnect(this); inlineNotesReset(); } } QVarLengthArray KTextEditor::ViewPrivate::inlineNotes(int line) const { QVarLengthArray allInlineNotes; for (KTextEditor::InlineNoteProvider *provider: m_inlineNoteProviders) { int index = 0; for (auto column: provider->inlineNotes(line)) { KateInlineNoteData note = { provider, this, {line, column}, index, m_viewInternal->m_activeInlineNote.m_underMouse, m_viewInternal->renderer()->currentFont(), m_viewInternal->renderer()->lineHeight() }; allInlineNotes.append(note); index++; } } return allInlineNotes; } QRect KTextEditor::ViewPrivate::inlineNoteRect(const KateInlineNoteData& note) const { return m_viewInternal->inlineNoteRect(note); } void KTextEditor::ViewPrivate::inlineNotesReset() { m_viewInternal->m_activeInlineNote = {}; tagLines(0, doc()->lastLine(), true); } void KTextEditor::ViewPrivate::inlineNotesLineChanged(int line) { if ( line == m_viewInternal->m_activeInlineNote.m_position.line() ) { m_viewInternal->m_activeInlineNote = {}; } tagLines(line, line, true); } //END KTextEditor::InlineNoteInterface KTextEditor::Attribute::Ptr KTextEditor::ViewPrivate::defaultStyleAttribute(KTextEditor::DefaultStyle defaultStyle) const { KateRendererConfig * renderConfig = const_cast(this)->renderer()->config(); KTextEditor::Attribute::Ptr style = doc()->highlight()->attributes(renderConfig->schema()).at(defaultStyle); if (!style->hasProperty(QTextFormat::BackgroundBrush)) { // make sure the returned style has the default background color set style = new KTextEditor::Attribute(*style); style->setBackground(QBrush(renderConfig->backgroundColor())); } return style; } QList KTextEditor::ViewPrivate::lineAttributes(int line) { QList attribs; if (line < 0 || line >= doc()->lines()) return attribs; Kate::TextLine kateLine = doc()->kateTextLine(line); if (!kateLine) { return attribs; } const QVector &intAttrs = kateLine->attributesList(); for (int i = 0; i < intAttrs.size(); ++i) { if (intAttrs[i].length > 0 && intAttrs[i].attributeValue > 0) { attribs << KTextEditor::AttributeBlock( intAttrs.at(i).offset, intAttrs.at(i).length, renderer()->attribute(intAttrs.at(i).attributeValue) ); } } return attribs; } diff --git a/src/view/kateviewhelpers.cpp b/src/view/kateviewhelpers.cpp index 0d48cef3..6d8443c9 100644 --- a/src/view/kateviewhelpers.cpp +++ b/src/view/kateviewhelpers.cpp @@ -1,3095 +1,3095 @@ /* This file is part of the KDE libraries Copyright (C) 2008, 2009 Matthew Woehlke Copyright (C) 2007 Mirko Stocker Copyright (C) 2002 John Firebaugh Copyright (C) 2001 Anders Lund Copyright (C) 2001 Christoph Cullmann Copyright (C) 2011 Svyatoslav Kuzmich Copyright (C) 2012 Kåre Särs (Minimap) Copyright 2017-2018 Friedrich W. H. Kossebau This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kateviewhelpers.h" #include "katecmd.h" #include #include #include #include "kateconfig.h" #include "katedocument.h" #include #include "katerenderer.h" #include "kateannotationitemdelegate.h" #include "kateview.h" #include "kateviewinternal.h" #include "katelayoutcache.h" #include "katetextlayout.h" #include "kateglobal.h" #include "katepartdebug.h" #include "katecommandrangeexpressionparser.h" #include "kateabstractinputmode.h" #include "katetextpreview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //BEGIN KateMessageLayout KateMessageLayout::KateMessageLayout(QWidget *parent) : QLayout(parent) { } KateMessageLayout::~KateMessageLayout() { while (QLayoutItem *item = takeAt(0)) delete item; } void KateMessageLayout::addItem(QLayoutItem *item) { Q_ASSERT(false); add(item, KTextEditor::Message::CenterInView); } void KateMessageLayout::addWidget(QWidget *widget, KTextEditor::Message::MessagePosition pos) { add(new QWidgetItem(widget), pos); } int KateMessageLayout::count() const { return m_items.size(); } QLayoutItem *KateMessageLayout::itemAt(int index) const { if (index < 0 || index >= m_items.size()) return nullptr; return m_items[index]->item; } void KateMessageLayout::setGeometry(const QRect &rect) { QLayout::setGeometry(rect); const int s = spacing(); const QRect adjustedRect = rect.adjusted(s, s, -s, -s); for (auto wrapper : m_items) { QLayoutItem *item = wrapper->item; auto position = wrapper->position; if (position == KTextEditor::Message::TopInView) { const QRect r(adjustedRect.width() - item->sizeHint().width(), s, item->sizeHint().width(), item->sizeHint().height()); item->setGeometry(r); } else if (position == KTextEditor::Message::BottomInView) { const QRect r(adjustedRect.width() - item->sizeHint().width(), adjustedRect.height() - item->sizeHint().height(), item->sizeHint().width(), item->sizeHint().height()); item->setGeometry(r); } else if (position == KTextEditor::Message::CenterInView) { QRect r(0, 0, item->sizeHint().width(), item->sizeHint().height()); r.moveCenter(adjustedRect.center()); item->setGeometry(r); } else { Q_ASSERT_X(false, "setGeometry", "Only TopInView, CenterInView, and BottomInView are supported."); } } } QSize KateMessageLayout::sizeHint() const { return QSize(); } QLayoutItem *KateMessageLayout::takeAt(int index) { if (index >= 0 && index < m_items.size()) { ItemWrapper *layoutStruct = m_items.takeAt(index); return layoutStruct->item; } return nullptr; } void KateMessageLayout::add(QLayoutItem *item, KTextEditor::Message::MessagePosition pos) { m_items.push_back(new ItemWrapper(item, pos)); } //END KateMessageLayout //BEGIN KateScrollBar static const int s_lineWidth = 100; static const int s_pixelMargin = 8; static const int s_linePixelIncLimit = 6; const unsigned char KateScrollBar::characterOpacity[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // <- 15 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 0, 0, 0, 0, // <- 31 0, 125, 41, 221, 138, 195, 218, 21, 142, 142, 137, 137, 97, 87, 87, 140, // <- 47 223, 164, 183, 190, 191, 193, 214, 158, 227, 216, 103, 113, 146, 140, 146, 149, // <- 63 248, 204, 240, 174, 217, 197, 178, 205, 209, 176, 168, 211, 160, 246, 238, 218, // <- 79 195, 229, 227, 196, 167, 212, 188, 238, 197, 169, 189, 158, 21, 151, 115, 90, // <- 95 15, 192, 209, 153, 208, 187, 162, 221, 183, 149, 161, 191, 146, 203, 167, 182, // <- 111 208, 203, 139, 166, 158, 167, 157, 189, 164, 179, 156, 167, 145, 166, 109, 0, // <- 127 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // <- 143 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // <- 159 0, 125, 184, 187, 146, 201, 127, 203, 89, 194, 156, 141, 117, 87, 202, 88, // <- 175 115, 165, 118, 121, 85, 190, 236, 87, 88, 111, 151, 140, 194, 191, 203, 148, // <- 191 215, 215, 222, 224, 223, 234, 230, 192, 208, 208, 216, 217, 187, 187, 194, 195, // <- 207 228, 255, 228, 228, 235, 239, 237, 150, 255, 222, 222, 229, 232, 180, 197, 225, // <- 223 208, 208, 216, 217, 212, 230, 218, 170, 202, 202, 211, 204, 156, 156, 165, 159, // <- 239 214, 194, 197, 197, 206, 206, 201, 132, 214, 183, 183, 192, 187, 195, 227, 198 }; KateScrollBar::KateScrollBar(Qt::Orientation orientation, KateViewInternal *parent) : QScrollBar(orientation, parent->m_view) , m_middleMouseDown(false) , m_leftMouseDown(false) , m_view(parent->m_view) , m_doc(parent->doc()) , m_viewInternal(parent) , m_textPreview(nullptr) , m_showMarks(false) , m_showMiniMap(false) , m_miniMapAll(true) , m_miniMapWidth(40) , m_grooveHeight(height()) , m_linesModified(0) { connect(this, SIGNAL(valueChanged(int)), this, SLOT(sliderMaybeMoved(int))); connect(m_doc, SIGNAL(marksChanged(KTextEditor::Document*)), this, SLOT(marksChanged())); m_updateTimer.setInterval(300); m_updateTimer.setSingleShot(true); QTimer::singleShot(10, this, SLOT(updatePixmap())); // track mouse for text preview widget setMouseTracking(orientation == Qt::Vertical); // setup text preview timer m_delayTextPreviewTimer.setSingleShot(true); m_delayTextPreviewTimer.setInterval(250); connect(&m_delayTextPreviewTimer, SIGNAL(timeout()), this, SLOT(showTextPreview())); } KateScrollBar::~KateScrollBar() { delete m_textPreview; } void KateScrollBar::setShowMiniMap(bool b) { if (b && !m_showMiniMap) { connect(m_view, SIGNAL(selectionChanged(KTextEditor::View*)), &m_updateTimer, SLOT(start()), Qt::UniqueConnection); connect(m_doc, SIGNAL(textChanged(KTextEditor::Document*)), &m_updateTimer, SLOT(start()), Qt::UniqueConnection); connect(m_view, SIGNAL(delayedUpdateOfView()), &m_updateTimer, SLOT(start()), Qt::UniqueConnection); connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updatePixmap()), Qt::UniqueConnection); connect(&(m_view->textFolding()), SIGNAL(foldingRangesChanged()), &m_updateTimer, SLOT(start()), Qt::UniqueConnection); } else if (!b) { disconnect(&m_updateTimer); } m_showMiniMap = b; updateGeometry(); update(); } QSize KateScrollBar::sizeHint() const { if (m_showMiniMap) { return QSize(m_miniMapWidth, QScrollBar::sizeHint().height()); } return QScrollBar::sizeHint(); } int KateScrollBar::minimapYToStdY(int y) { // Check if the minimap fills the whole scrollbar if (m_stdGroveRect.height() == m_mapGroveRect.height()) { return y; } // check if y is on the step up/down if ((y < m_stdGroveRect.top()) || (y > m_stdGroveRect.bottom())) { return y; } if (y < m_mapGroveRect.top()) { return m_stdGroveRect.top() + 1; } if (y > m_mapGroveRect.bottom()) { return m_stdGroveRect.bottom() - 1; } // check for div/0 if (m_mapGroveRect.height() == 0) { return y; } int newY = (y - m_mapGroveRect.top()) * m_stdGroveRect.height() / m_mapGroveRect.height(); newY += m_stdGroveRect.top(); return newY; } void KateScrollBar::mousePressEvent(QMouseEvent *e) { // delete text preview hideTextPreview(); if (e->button() == Qt::MidButton) { m_middleMouseDown = true; } else if (e->button() == Qt::LeftButton) { m_leftMouseDown = true; } if (m_showMiniMap) { if (m_leftMouseDown) { // if we show the minimap left-click jumps directly to the selected position int newVal = (e->pos().y()-m_mapGroveRect.top()) / (double)m_mapGroveRect.height() * (double)(maximum()+pageStep()) - pageStep()/2; newVal = qBound(0, newVal, maximum()); setSliderPosition(newVal); } QMouseEvent eMod(QEvent::MouseButtonPress, QPoint(6, minimapYToStdY(e->pos().y())), e->button(), e->buttons(), e->modifiers()); QScrollBar::mousePressEvent(&eMod); } else { QScrollBar::mousePressEvent(e); } m_toolTipPos = e->globalPos() - QPoint(e->pos().x(), 0); const int fromLine = m_viewInternal->toRealCursor(m_viewInternal->startPos()).line() + 1; const int lastLine = m_viewInternal->toRealCursor(m_viewInternal->endPos()).line() + 1; QToolTip::showText(m_toolTipPos, i18nc("from line - to line", "
%1

%2
", fromLine, lastLine), this); redrawMarks(); } void KateScrollBar::mouseReleaseEvent(QMouseEvent *e) { if (e->button() == Qt::MidButton) { m_middleMouseDown = false; } else if (e->button() == Qt::LeftButton) { m_leftMouseDown = false; } redrawMarks(); if (m_leftMouseDown || m_middleMouseDown) { QToolTip::hideText(); } if (m_showMiniMap) { QMouseEvent eMod(QEvent::MouseButtonRelease, QPoint(e->pos().x(), minimapYToStdY(e->pos().y())), e->button(), e->buttons(), e->modifiers()); QScrollBar::mouseReleaseEvent(&eMod); } else { QScrollBar::mouseReleaseEvent(e); } } void KateScrollBar::mouseMoveEvent(QMouseEvent *e) { if (m_showMiniMap) { QMouseEvent eMod(QEvent::MouseMove, QPoint(e->pos().x(), minimapYToStdY(e->pos().y())), e->button(), e->buttons(), e->modifiers()); QScrollBar::mouseMoveEvent(&eMod); } else { QScrollBar::mouseMoveEvent(e); } if (e->buttons() & (Qt::LeftButton | Qt::MidButton)) { redrawMarks(); // current line tool tip m_toolTipPos = e->globalPos() - QPoint(e->pos().x(), 0); const int fromLine = m_viewInternal->toRealCursor(m_viewInternal->startPos()).line() + 1; const int lastLine = m_viewInternal->toRealCursor(m_viewInternal->endPos()).line() + 1; QToolTip::showText(m_toolTipPos, i18nc("from line - to line", "
%1

%2
", fromLine, lastLine), this); } showTextPreviewDelayed(); } void KateScrollBar::leaveEvent(QEvent *event) { hideTextPreview(); QAbstractSlider::leaveEvent(event); } bool KateScrollBar::eventFilter(QObject *object, QEvent *event) { Q_UNUSED(object) if (m_textPreview && event->type() == QEvent::WindowDeactivate) { // We need hide the scrollbar TextPreview widget hideTextPreview(); } return false; } void KateScrollBar::paintEvent(QPaintEvent *e) { if (m_doc->marks().size() != m_lines.size()) { recomputeMarksPositions(); } if (m_showMiniMap) { miniMapPaintEvent(e); } else { normalPaintEvent(e); } } void KateScrollBar::showTextPreviewDelayed() { if (!m_textPreview) { if (!m_delayTextPreviewTimer.isActive()) { m_delayTextPreviewTimer.start(); } } else { showTextPreview(); } } void KateScrollBar::showTextPreview() { if (orientation() != Qt::Vertical || isSliderDown() || (minimum() == maximum()) || !m_view->config()->scrollBarPreview()) { return; } // only show when main window is active (#392396) if (window() && !window()->isActiveWindow()) { return; } QRect grooveRect; if (m_showMiniMap) { // If mini-map is shown, the height of the map might not be the whole height grooveRect = m_mapGroveRect; } else { QStyleOptionSlider opt; opt.init(this); opt.subControls = QStyle::SC_None; opt.activeSubControls = QStyle::SC_None; opt.orientation = orientation(); opt.minimum = minimum(); opt.maximum = maximum(); opt.sliderPosition = sliderPosition(); opt.sliderValue = value(); opt.singleStep = singleStep(); opt.pageStep = pageStep(); grooveRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarGroove, this); } if (m_view->config()->scrollPastEnd()) { // Adjust the grove size to accommodate the added pageStep at the bottom int adjust = pageStep()*grooveRect.height() / (maximum() + pageStep() - minimum()); grooveRect.adjust(0,0,0, -adjust); } const QPoint cursorPos = mapFromGlobal(QCursor::pos()); if (grooveRect.contains(cursorPos)) { if (!m_textPreview) { m_textPreview = new KateTextPreview(m_view, this); m_textPreview->setAttribute(Qt::WA_ShowWithoutActivating); m_textPreview->setFrameStyle(QFrame::StyledPanel); // event filter to catch application WindowDeactivate event, to hide the preview window qApp->installEventFilter(this); } const qreal posInPercent = static_cast(cursorPos.y() - grooveRect.top()) / grooveRect.height(); const qreal startLine = posInPercent * m_view->textFolding().visibleLines(); m_textPreview->resize(m_view->width() / 2, m_view->height() / 5); const int xGlobal = mapToGlobal(QPoint(0, 0)).x(); const int yGlobal = qMin(mapToGlobal(QPoint(0, height())).y() - m_textPreview->height(), qMax(mapToGlobal(QPoint(0, 0)).y(), mapToGlobal(cursorPos).y() - m_textPreview->height() / 2)); m_textPreview->move(xGlobal - m_textPreview->width(), yGlobal); m_textPreview->setLine(startLine); m_textPreview->setCenterView(true); m_textPreview->setScaleFactor(0.8); m_textPreview->raise(); m_textPreview->show(); } else { hideTextPreview(); } } void KateScrollBar::hideTextPreview() { if (m_delayTextPreviewTimer.isActive()) { m_delayTextPreviewTimer.stop(); } qApp->removeEventFilter(this); delete m_textPreview; } // This function is optimized for bing called in sequence. const QColor KateScrollBar::charColor(const QVector &attributes, int &attributeIndex, const QVector &decorations, const QColor &defaultColor, int x, QChar ch) { QColor color = defaultColor; bool styleFound = false; // Query the decorations, that is, things like search highlighting, or the // KDevelop DUChain highlighting, for a color to use foreach (const QTextLayout::FormatRange &range, decorations) { if (range.start <= x && range.start + range.length > x) { // If there's a different background color set (search markers, ...) // use that, otherwise use the foreground color. if (range.format.hasProperty(QTextFormat::BackgroundBrush)) { color = range.format.background().color(); } else { color = range.format.foreground().color(); } styleFound = true; break; } } // If there's no decoration set for the current character (this will mostly be the case for // plain Kate), query the styles, that is, the default kate syntax highlighting. if (!styleFound) { // go to the block containing x while ((attributeIndex < attributes.size()) && ((attributes[attributeIndex].offset + attributes[attributeIndex].length) < x)) { ++attributeIndex; } if ((attributeIndex < attributes.size()) && (x < attributes[attributeIndex].offset + attributes[attributeIndex].length)) { color = m_view->renderer()->attribute(attributes[attributeIndex].attributeValue)->foreground().color(); } } // Query how much "blackness" the character has. // This causes for example a dot or a dash to appear less intense // than an A or similar. // This gives the pixels created a bit of structure, which makes it look more // like real text. color.setAlpha((ch.unicode() < 256) ? characterOpacity[ch.unicode()] : 222); return color; } void KateScrollBar::updatePixmap() { //QTime time; //time.start(); if (!m_showMiniMap) { // make sure no time is wasted if the option is disabled return; } // For performance reason, only every n-th line will be drawn if the widget is // sufficiently small compared to the amount of lines in the document. int docLineCount = m_view->textFolding().visibleLines(); int pixmapLineCount = docLineCount; if (m_view->config()->scrollPastEnd()) { pixmapLineCount += pageStep(); } int pixmapLinesUnscaled = pixmapLineCount; if (m_grooveHeight < 5) { m_grooveHeight = 5; } int lineDivisor = pixmapLinesUnscaled / m_grooveHeight; if (lineDivisor < 1) { lineDivisor = 1; } int charIncrement = 1; int lineIncrement = 1; if ((m_grooveHeight > 10) && (pixmapLineCount >= m_grooveHeight * 2)) { charIncrement = pixmapLineCount / m_grooveHeight; while (charIncrement > s_linePixelIncLimit) { lineIncrement++; pixmapLineCount = pixmapLinesUnscaled / lineIncrement; charIncrement = pixmapLineCount / m_grooveHeight; } pixmapLineCount /= charIncrement; } int pixmapLineWidth = s_pixelMargin + s_lineWidth / charIncrement; //qCDebug(LOG_KTE) << "l" << lineIncrement << "c" << charIncrement << "d" << lineDivisor; //qCDebug(LOG_KTE) << "pixmap" << pixmapLineCount << pixmapLineWidth << "docLines" << m_view->textFolding().visibleLines() << "height" << m_grooveHeight; const QColor backgroundColor = m_view->defaultStyleAttribute(KTextEditor::dsNormal)->background().color(); const QColor defaultTextColor = m_view->defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color(); const QColor selectionBgColor = m_view->renderer()->config()->selectionColor(); QColor modifiedLineColor = m_view->renderer()->config()->modifiedLineColor(); QColor savedLineColor = m_view->renderer()->config()->savedLineColor(); // move the modified line color away from the background color modifiedLineColor.setHsv(modifiedLineColor.hue(), 255, 255 - backgroundColor.value() / 3); savedLineColor.setHsv(savedLineColor.hue(), 100, 255 - backgroundColor.value() / 3); // increase dimensions by ratio m_pixmap = QPixmap(pixmapLineWidth * m_view->devicePixelRatioF(), pixmapLineCount * m_view->devicePixelRatioF()); m_pixmap.fill(QColor("transparent")); // The text currently selected in the document, to be drawn later. const KTextEditor::Range &selection = m_view->selectionRange(); QPainter painter; if (painter.begin(&m_pixmap)) { // init pen once, afterwards, only change it if color changes to avoid a lot of allocation for setPen painter.setPen(selectionBgColor); // Do not force updates of the highlighting if the document is very large bool simpleMode = m_doc->lines() > 7500; int pixelY = 0; int drawnLines = 0; // Iterate over all visible lines, drawing them. for (int virtualLine = 0; virtualLine < docLineCount; virtualLine += lineIncrement) { int realLineNumber = m_view->textFolding().visibleLineToLine(virtualLine); QString lineText = m_doc->line(realLineNumber); if (!simpleMode) { m_doc->buffer().ensureHighlighted(realLineNumber); } const Kate::TextLine &kateline = m_doc->plainKateTextLine(realLineNumber); const QVector &attributes = kateline->attributesList(); QVector decorations = m_view->renderer()->decorationsForLine(kateline, realLineNumber); int attributeIndex = 0; // Draw selection if it is on an empty line if (selection.contains(KTextEditor::Cursor(realLineNumber, 0)) && lineText.size() == 0) { if (selectionBgColor != painter.pen().color()) { painter.setPen(selectionBgColor); } painter.drawLine(s_pixelMargin, pixelY, s_pixelMargin + s_lineWidth - 1, pixelY); } // Iterate over the line to draw the background int selStartX = -1; int selEndX = -1; int pixelX = s_pixelMargin; // use this to control the offset of the text from the left for (int x = 0; (x < lineText.size() && x < s_lineWidth); x += charIncrement) { if (pixelX >= s_lineWidth + s_pixelMargin) { break; } // Query the selection and draw it behind the character if (selection.contains(KTextEditor::Cursor(realLineNumber, x))) { if (selStartX == -1) selStartX = pixelX; selEndX = pixelX; if (lineText.size() - 1 == x) { selEndX = s_lineWidth + s_pixelMargin-1; } } if (lineText[x] == QLatin1Char('\t')) { pixelX += qMax(4 / charIncrement, 1); // FIXME: tab width... } else { pixelX++; } } if (selStartX != -1) { if (selectionBgColor != painter.pen().color()) { painter.setPen(selectionBgColor); } painter.drawLine(selStartX, pixelY, selEndX, pixelY); } // Iterate over all the characters in the current line pixelX = s_pixelMargin; for (int x = 0; (x < lineText.size() && x < s_lineWidth); x += charIncrement) { if (pixelX >= s_lineWidth + s_pixelMargin) { break; } // draw the pixels if (lineText[x] == QLatin1Char(' ')) { pixelX++; } else if (lineText[x] == QLatin1Char('\t')) { pixelX += qMax(4 / charIncrement, 1); // FIXME: tab width... } else { const QColor newPenColor(charColor(attributes, attributeIndex, decorations, defaultTextColor, x, lineText[x])); if (newPenColor != painter.pen().color()) { painter.setPen(newPenColor); } // Actually draw the pixel with the color queried from the renderer. painter.drawPoint(pixelX, pixelY); pixelX++; } } drawnLines++; if (((drawnLines) % charIncrement) == 0) { pixelY++; } } //qCDebug(LOG_KTE) << drawnLines; // Draw line modification marker map. // Disable this if the document is really huge, // since it requires querying every line. if (m_doc->lines() < 50000) { for (int lineno = 0; lineno < docLineCount; lineno++) { int realLineNo = m_view->textFolding().visibleLineToLine(lineno); const Kate::TextLine &line = m_doc->plainKateTextLine(realLineNo); const QColor & col = line->markedAsModified() ? modifiedLineColor : savedLineColor; if (line->markedAsModified() || line->markedAsSavedOnDisk()) { painter.fillRect(2, lineno / lineDivisor, 3, 1, col); } } } // end painting painter.end(); } // set right ratio m_pixmap.setDevicePixelRatio(m_view->devicePixelRatioF()); //qCDebug(LOG_KTE) << time.elapsed(); // Redraw the scrollbar widget with the updated pixmap. update(); } void KateScrollBar::miniMapPaintEvent(QPaintEvent *e) { QScrollBar::paintEvent(e); QPainter painter(this); QStyleOptionSlider opt; opt.init(this); opt.subControls = QStyle::SC_None; opt.activeSubControls = QStyle::SC_None; opt.orientation = orientation(); opt.minimum = minimum(); opt.maximum = maximum(); opt.sliderPosition = sliderPosition(); opt.sliderValue = value(); opt.singleStep = singleStep(); opt.pageStep = pageStep(); QRect grooveRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarGroove, this); m_stdGroveRect = grooveRect; if (style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSubLine, this).height() == 0) { int alignMargin = style()->pixelMetric(QStyle::PM_FocusFrameVMargin, &opt, this); grooveRect.moveTop(alignMargin); grooveRect.setHeight(grooveRect.height() - alignMargin); } if (style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarAddLine, this).height() == 0) { int alignMargin = style()->pixelMetric(QStyle::PM_FocusFrameVMargin, &opt, this); grooveRect.setHeight(grooveRect.height() - alignMargin); } m_grooveHeight = grooveRect.height(); const int docXMargin = 1; //style()->drawControl(QStyle::CE_ScrollBarAddLine, &opt, &painter, this); //style()->drawControl(QStyle::CE_ScrollBarSubLine, &opt, &painter, this); // calculate the document size and position const int docHeight = qMin(grooveRect.height(), int(m_pixmap.height() / m_pixmap.devicePixelRatio() * 2)) - 2 * docXMargin; const int yoffset = 1; // top-aligned in stead of center-aligned (grooveRect.height() - docHeight) / 2; const QRect docRect(QPoint(grooveRect.left() + docXMargin, yoffset + grooveRect.top()), QSize(grooveRect.width() - docXMargin, docHeight)); m_mapGroveRect = docRect; // calculate the visible area int max = qMax(maximum() + 1, 1); int visibleStart = value() * docHeight / (max + pageStep()) + docRect.top() + 0.5; int visibleEnd = (value() + pageStep()) * docHeight / (max + pageStep()) + docRect.top(); QRect visibleRect = docRect; visibleRect.moveTop(visibleStart); visibleRect.setHeight(visibleEnd - visibleStart); // calculate colors const QColor backgroundColor = m_view->defaultStyleAttribute(KTextEditor::dsNormal)->background().color(); const QColor foregroundColor = m_view->defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color(); const QColor highlightColor = palette().link().color(); const int backgroundLightness = backgroundColor.lightness(); const int foregroundLightness = foregroundColor.lightness(); const int lighnessDiff = (foregroundLightness - backgroundLightness); // get a color suited for the color theme QColor darkShieldColor = palette().color(QPalette::Mid); int hue, sat, light; darkShieldColor.getHsl(&hue, &sat, &light); // apply suitable lightness darkShieldColor.setHsl(hue, sat, backgroundLightness + lighnessDiff * 0.35); // gradient for nicer results QLinearGradient gradient(0, 0, width(), 0); gradient.setColorAt(0, darkShieldColor); gradient.setColorAt(0.3, darkShieldColor.lighter(115)); gradient.setColorAt(1, darkShieldColor); QColor lightShieldColor; lightShieldColor.setHsl(hue, sat, backgroundLightness + lighnessDiff * 0.15); QColor outlineColor; outlineColor.setHsl(hue, sat, backgroundLightness + lighnessDiff * 0.5); // draw the grove background in case the document is small painter.setPen(Qt::NoPen); painter.setBrush(backgroundColor); painter.drawRect(grooveRect); // adjust the rectangles QRect sliderRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSlider, this); sliderRect.setX(docXMargin); sliderRect.setWidth(width() - docXMargin*2); if ((docHeight + 2 * docXMargin >= grooveRect.height()) && (sliderRect.height() > visibleRect.height() + 2)) { visibleRect.adjust(2, 0, -3, 0); } else { visibleRect.adjust(1, 0, -1, 2); sliderRect.setTop(visibleRect.top() - 1); sliderRect.setBottom(visibleRect.bottom() + 1); } // Smooth transform only when squeezing if (grooveRect.height() < m_pixmap.height() / m_pixmap.devicePixelRatio()) { painter.setRenderHint(QPainter::SmoothPixmapTransform); } // draw the modified lines margin QRect pixmapMarginRect(QPoint(0, 0), QSize(s_pixelMargin, m_pixmap.height() / m_pixmap.devicePixelRatio())); QRect docPixmapMarginRect(QPoint(0, docRect.top()), QSize(s_pixelMargin, docRect.height())); painter.drawPixmap(docPixmapMarginRect, m_pixmap, pixmapMarginRect); // calculate the stretch and draw the stretched lines (scrollbar marks) QRect pixmapRect(QPoint(s_pixelMargin, 0), QSize(m_pixmap.width() / m_pixmap.devicePixelRatio() - s_pixelMargin, m_pixmap.height() / m_pixmap.devicePixelRatio())); QRect docPixmapRect(QPoint(s_pixelMargin, docRect.top()), QSize(docRect.width() - s_pixelMargin, docRect.height())); painter.drawPixmap(docPixmapRect, m_pixmap, pixmapRect); // delimit the end of the document const int y = docPixmapRect.height() + grooveRect.y(); if (y+2 < grooveRect.y() + grooveRect.height()) { QColor fg(foregroundColor); fg.setAlpha(30); painter.setBrush(Qt::NoBrush); painter.setPen(QPen(fg, 1)); painter.drawLine(grooveRect.x()+1,y+2,width()-1,y+2); } // fade the invisible sections const QRect top( grooveRect.x(), grooveRect.y(), grooveRect.width(), visibleRect.y()-grooveRect.y() //Pen width ); const QRect bottom( grooveRect.x(), grooveRect.y()+visibleRect.y()+visibleRect.height()-grooveRect.y(), //Pen width grooveRect.width(), grooveRect.height() - (visibleRect.y()-grooveRect.y())-visibleRect.height() ); QColor faded(backgroundColor); faded.setAlpha(110); painter.fillRect(top, faded); painter.fillRect(bottom, faded); // add a thin line to limit the scrollbar QColor c(foregroundColor); c.setAlpha(10); painter.setPen(QPen(c,1)); painter.drawLine(0, 0, 0, height()); if (m_showMarks) { QHashIterator it = m_lines; QPen penBg; penBg.setWidth(4); lightShieldColor.setAlpha(180); penBg.setColor(lightShieldColor); painter.setPen(penBg); while (it.hasNext()) { it.next(); int y = (it.key() - grooveRect.top()) * docHeight / grooveRect.height() + docRect.top();; painter.drawLine(6, y, width() - 6, y); } it = m_lines; QPen pen; pen.setWidth(2); while (it.hasNext()) { it.next(); pen.setColor(it.value()); painter.setPen(pen); int y = (it.key() - grooveRect.top()) * docHeight / grooveRect.height() + docRect.top(); painter.drawLine(6, y, width() - 6, y); } } // slider outline QColor sliderColor(highlightColor); sliderColor.setAlpha(50); painter.fillRect(sliderRect, sliderColor); painter.setPen(QPen(highlightColor, 0)); // rounded rect looks ugly for some reason, so we draw 4 lines. painter.drawLine(sliderRect.left(), sliderRect.top() + 1, sliderRect.left(), sliderRect.bottom() - 1); painter.drawLine(sliderRect.right(), sliderRect.top() + 1, sliderRect.right(), sliderRect.bottom() - 1); painter.drawLine(sliderRect.left() + 1, sliderRect.top(), sliderRect.right() - 1, sliderRect.top()); painter.drawLine(sliderRect.left() + 1, sliderRect.bottom(), sliderRect.right() - 1, sliderRect.bottom()); } void KateScrollBar::normalPaintEvent(QPaintEvent *e) { QScrollBar::paintEvent(e); if (!m_showMarks) { return; } QPainter painter(this); QStyleOptionSlider opt; opt.init(this); opt.subControls = QStyle::SC_None; opt.activeSubControls = QStyle::SC_None; opt.orientation = orientation(); opt.minimum = minimum(); opt.maximum = maximum(); opt.sliderPosition = sliderPosition(); opt.sliderValue = value(); opt.singleStep = singleStep(); opt.pageStep = pageStep(); QRect rect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSlider, this); int sideMargin = width() - rect.width(); if (sideMargin < 4) { sideMargin = 4; } sideMargin /= 2; QHashIterator it = m_lines; while (it.hasNext()) { it.next(); painter.setPen(it.value()); if (it.key() < rect.top() || it.key() > rect.bottom()) { painter.drawLine(0, it.key(), width(), it.key()); } else { painter.drawLine(0, it.key(), sideMargin, it.key()); painter.drawLine(width() - sideMargin, it.key(), width(), it.key()); } } } void KateScrollBar::resizeEvent(QResizeEvent *e) { QScrollBar::resizeEvent(e); m_updateTimer.start(); m_lines.clear(); update(); } void KateScrollBar::sliderChange(SliderChange change) { // call parents implementation QScrollBar::sliderChange(change); if (change == QAbstractSlider::SliderValueChange) { redrawMarks(); } else if (change == QAbstractSlider::SliderRangeChange) { marksChanged(); } if (m_leftMouseDown || m_middleMouseDown) { const int fromLine = m_viewInternal->toRealCursor(m_viewInternal->startPos()).line() + 1; const int lastLine = m_viewInternal->toRealCursor(m_viewInternal->endPos()).line() + 1; QToolTip::showText(m_toolTipPos, i18nc("from line - to line", "
%1

%2
", fromLine, lastLine), this); } } void KateScrollBar::marksChanged() { m_lines.clear(); update(); } void KateScrollBar::redrawMarks() { if (!m_showMarks) { return; } update(); } void KateScrollBar::recomputeMarksPositions() { // get the style options to compute the scrollbar pixels QStyleOptionSlider opt; initStyleOption(&opt); QRect grooveRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarGroove, this); // cache top margin and groove height const int top = grooveRect.top(); const int h = grooveRect.height() - 1; // make sure we have a sane height if (h <= 0) { return; } // get total visible (=without folded) lines in the document int visibleLines = m_view->textFolding().visibleLines() - 1; if (m_view->config()->scrollPastEnd()) { visibleLines += m_viewInternal->linesDisplayed() - 1; visibleLines -= m_view->config()->autoCenterLines(); } // now repopulate the scrollbar lines list m_lines.clear(); const QHash &marks = m_doc->marks(); for (QHash::const_iterator i = marks.constBegin(); i != marks.constEnd(); ++i) { KTextEditor::Mark *mark = i.value(); const int line = m_view->textFolding().lineToVisibleLine(mark->line); const double ratio = static_cast(line) / visibleLines; m_lines.insert(top + (int)(h * ratio), KateRendererConfig::global()->lineMarkerColor((KTextEditor::MarkInterface::MarkTypes)mark->type)); } } void KateScrollBar::sliderMaybeMoved(int value) { if (m_middleMouseDown) { // we only need to emit this signal once, as for the following slider // movements the signal sliderMoved() is already emitted. // Thus, set m_middleMouseDown to false right away. m_middleMouseDown = false; emit sliderMMBMoved(value); } } //END //BEGIN KateCmdLineEditFlagCompletion /** * This class provide completion of flags. It shows a short description of * each flag, and flags are appended. */ class KateCmdLineEditFlagCompletion : public KCompletion { public: KateCmdLineEditFlagCompletion() { ; } QString makeCompletion(const QString & /*s*/) override { return QString(); } }; //END KateCmdLineEditFlagCompletion //BEGIN KateCmdLineEdit KateCommandLineBar::KateCommandLineBar(KTextEditor::ViewPrivate *view, QWidget *parent) : KateViewBarWidget(true, parent) { QHBoxLayout *topLayout = new QHBoxLayout(); centralWidget()->setLayout(topLayout); topLayout->setContentsMargins(0, 0, 0, 0); m_lineEdit = new KateCmdLineEdit(this, view); connect(m_lineEdit, SIGNAL(hideRequested()), SIGNAL(hideMe())); topLayout->addWidget(m_lineEdit); QToolButton *helpButton = new QToolButton(this); helpButton->setAutoRaise(true); helpButton->setIcon(QIcon::fromTheme(QStringLiteral("help-contextual"))); topLayout->addWidget(helpButton); connect(helpButton, SIGNAL(clicked()), this, SLOT(showHelpPage())); setFocusProxy(m_lineEdit); } void KateCommandLineBar::showHelpPage() { KHelpClient::invokeHelp(QStringLiteral("advanced-editing-tools-commandline"), QStringLiteral("kate")); } KateCommandLineBar::~KateCommandLineBar() { } // inserts the given string in the command line edit and (if selected = true) selects it so the user // can type over it if they want to void KateCommandLineBar::setText(const QString &text, bool selected) { m_lineEdit->setText(text); if (selected) { m_lineEdit->selectAll(); } } void KateCommandLineBar::execute(const QString &text) { m_lineEdit->slotReturnPressed(text); } KateCmdLineEdit::KateCmdLineEdit(KateCommandLineBar *bar, KTextEditor::ViewPrivate *view) : KLineEdit() , m_view(view) , m_bar(bar) , m_msgMode(false) , m_histpos(0) , m_cmdend(0) , m_command(nullptr) { connect(this, SIGNAL(returnPressed(QString)), this, SLOT(slotReturnPressed(QString))); setCompletionObject(KateCmd::self()->commandCompletionObject()); setAutoDeleteCompletionObject(false); m_hideTimer = new QTimer(this); m_hideTimer->setSingleShot(true); connect(m_hideTimer, SIGNAL(timeout()), this, SLOT(hideLineEdit())); // make sure the timer is stopped when the user switches views. if not, focus will be given to the // wrong view when KateViewBar::hideCurrentBarWidget() is called after 4 seconds. (the timer is // used for showing things like "Success" for four seconds after the user has used the kate // command line) connect(m_view, SIGNAL(focusOut(KTextEditor::View*)), m_hideTimer, SLOT(stop())); } void KateCmdLineEdit::hideEvent(QHideEvent *e) { Q_UNUSED(e); } QString KateCmdLineEdit::helptext(const QPoint &) const { QString beg = QStringLiteral("
Help: "); QString mid = QStringLiteral("
"); QString end = QStringLiteral("
"); QString t = text(); QRegExp re(QLatin1String("\\s*help\\s+(.*)")); if (re.indexIn(t) > -1) { QString s; // get help for command QString name = re.cap(1); if (name == QLatin1String("list")) { return beg + i18n("Available Commands") + mid + KateCmd::self()->commandList().join(QLatin1Char(' ')) + i18n("

For help on individual commands, do 'help <command>'

") + end; } else if (! name.isEmpty()) { KTextEditor::Command *cmd = KateCmd::self()->queryCommand(name); if (cmd) { if (cmd->help(m_view, name, s)) { return beg + name + mid + s + end; } else { return beg + name + mid + i18n("No help for '%1'", name) + end; } } else { return beg + mid + i18n("No such command %1", name) + end; } } } return beg + mid + i18n( "

This is the Katepart command line.
" "Syntax: command [ arguments ]
" "For a list of available commands, enter help list
" "For help for individual commands, enter help <command>

") + end; } bool KateCmdLineEdit::event(QEvent *e) { if (e->type() == QEvent::QueryWhatsThis) { setWhatsThis(helptext(QPoint())); e->accept(); return true; } return KLineEdit::event(e); } /** * Parse the text as a command. * * The following is a simple PEG grammar for the syntax of the command. * (A PEG grammar is like a BNF grammar, except that "/" stands for * ordered choice: only the first matching rule is used. In other words, * the parsing is short-circuited in the manner of the "or" operator in * programming languages, and so the grammar is unambiguous.) * * Text <- Range? Command * / Position * Range <- Position ("," Position)? * / "%" * Position <- Base Offset? * Base <- Line * / LastLine * / ThisLine * / Mark * Offset <- [+-] Base * Line <- [0-9]+ * LastLine <- "$" * ThisLine <- "." * Mark <- "'" [a-z] */ void KateCmdLineEdit::slotReturnPressed(const QString &text) { if (text.isEmpty()) { return; } // silently ignore leading space characters uint n = 0; const uint textlen = text.length(); while ((n < textlen) && (text[n].isSpace())) { n++; } if (n >= textlen) { return; } QString cmd = text.mid(n); // Parse any leading range expression, and strip it (and maybe do some other transforms on the command). QString leadingRangeExpression; KTextEditor::Range range = CommandRangeExpressionParser::parseRangeExpression(cmd, m_view, leadingRangeExpression, cmd); // Built in help: if the command starts with "help", [try to] show some help if (cmd.startsWith(QLatin1String("help"))) { QWhatsThis::showText(mapToGlobal(QPoint(0, 0)), helptext(QPoint())); clear(); KateCmd::self()->appendHistory(cmd); m_histpos = KateCmd::self()->historyLength(); m_oldText.clear(); return; } if (cmd.length() > 0) { KTextEditor::Command *p = KateCmd::self()->queryCommand(cmd); m_oldText = leadingRangeExpression + cmd; m_msgMode = true; // the following commands changes the focus themselves, so bar should be hidden before execution. if (QRegExp(QLatin1String("buffer|b|new|vnew|bp|bprev|bn|bnext|bf|bfirst|bl|blast|edit|e")).exactMatch(cmd.split(QLatin1Char(' ')).at(0))) { emit hideRequested(); } if (!p) { setText(i18n("No such command: \"%1\"", cmd)); } else if (range.isValid() && !p->supportsRange(cmd)) { // Raise message, when the command does not support ranges. setText(i18n("Error: No range allowed for command \"%1\".", cmd)); } else { QString msg; if (p->exec(m_view, cmd, msg, range)) { // append command along with range (will be empty if none given) to history KateCmd::self()->appendHistory(leadingRangeExpression + cmd); m_histpos = KateCmd::self()->historyLength(); m_oldText.clear(); if (msg.length() > 0) { setText(i18n("Success: ") + msg); } else if (isVisible()) { // always hide on success without message emit hideRequested(); } } else { if (msg.length() > 0) { if (msg.contains(QLatin1Char('\n'))) { // multiline error, use widget with more space QWhatsThis::showText(mapToGlobal(QPoint(0, 0)), msg); } else { setText(msg); } } else { setText(i18n("Command \"%1\" failed.", cmd)); } } } } // clean up if (completionObject() != KateCmd::self()->commandCompletionObject()) { KCompletion *c = completionObject(); setCompletionObject(KateCmd::self()->commandCompletionObject()); delete c; } m_command = nullptr; m_cmdend = 0; // the following commands change the focus themselves if (!QRegExp(QLatin1String("buffer|b|new|vnew|bp|bprev|bn|bnext|bf|bfirst|bl|blast|edit|e")).exactMatch(cmd.split(QLatin1Char(' ')).at(0))) { m_view->setFocus(); } if (isVisible()) { m_hideTimer->start(4000); } } void KateCmdLineEdit::hideLineEdit() // unless i have focus ;) { if (! hasFocus()) { emit hideRequested(); } } void KateCmdLineEdit::focusInEvent(QFocusEvent *ev) { if (m_msgMode) { m_msgMode = false; setText(m_oldText); selectAll(); } KLineEdit::focusInEvent(ev); } void KateCmdLineEdit::keyPressEvent(QKeyEvent *ev) { if (ev->key() == Qt::Key_Escape || (ev->key() == Qt::Key_BracketLeft && ev->modifiers() == Qt::ControlModifier)) { m_view->setFocus(); hideLineEdit(); clear(); } else if (ev->key() == Qt::Key_Up) { fromHistory(true); } else if (ev->key() == Qt::Key_Down) { fromHistory(false); } uint cursorpos = cursorPosition(); KLineEdit::keyPressEvent(ev); // during typing, let us see if we have a valid command if (! m_cmdend || cursorpos <= m_cmdend) { QChar c; if (! ev->text().isEmpty()) { c = ev->text().at(0); } if (! m_cmdend && ! c.isNull()) { // we have no command, so lets see if we got one if (! c.isLetterOrNumber() && c != QLatin1Char('-') && c != QLatin1Char('_')) { m_command = KateCmd::self()->queryCommand(text().trimmed()); if (m_command) { //qCDebug(LOG_KTE)<<"keypress in commandline: We have a command! "<queryCommand(text().trimmed()); if (m_command) { //qCDebug(LOG_KTE)<<"keypress in commandline: We have a command! "<commandCompletionObject()) { KCompletion *c = completionObject(); setCompletionObject(KateCmd::self()->commandCompletionObject()); delete c; } m_cmdend = 0; } } // if we got a command, check if it wants to do something. if (m_command) { KCompletion *cmpl = m_command->completionObject(m_view, text().left(m_cmdend).trimmed()); if (cmpl) { // We need to prepend the current command name + flag string // when completion is done //qCDebug(LOG_KTE)<<"keypress in commandline: Setting completion object!"; setCompletionObject(cmpl); } } } else if (m_command && !ev->text().isEmpty()) { // check if we should call the commands processText() if (m_command->wantsToProcessText(text().left(m_cmdend).trimmed())) { m_command->processText(m_view, text()); } } } void KateCmdLineEdit::fromHistory(bool up) { if (! KateCmd::self()->historyLength()) { return; } QString s; if (up) { if (m_histpos > 0) { m_histpos--; s = KateCmd::self()->fromHistory(m_histpos); } } else { if (m_histpos < (KateCmd::self()->historyLength() - 1)) { m_histpos++; s = KateCmd::self()->fromHistory(m_histpos); } else { m_histpos = KateCmd::self()->historyLength(); setText(m_oldText); } } if (! s.isEmpty()) { // Select the argument part of the command, so that it is easy to overwrite setText(s); static QRegExp reCmd = QRegExp(QLatin1String(".*[\\w\\-]+(?:[^a-zA-Z0-9_-]|:\\w+)(.*)")); if (reCmd.indexIn(text()) == 0) { setSelection(text().length() - reCmd.cap(1).length(), reCmd.cap(1).length()); } } } //END KateCmdLineEdit //BEGIN KateIconBorder using namespace KTextEditor; KateIconBorder::KateIconBorder(KateViewInternal *internalView, QWidget *parent) : QWidget(parent) , m_view(internalView->m_view) , m_doc(internalView->doc()) , m_viewInternal(internalView) , m_iconBorderOn(false) , m_lineNumbersOn(false) , m_relLineNumbersOn(false) , m_updateRelLineNumbers(false) , m_foldingMarkersOn(false) , m_dynWrapIndicatorsOn(false) , m_annotationBorderOn(false) , m_updatePositionToArea(true) , m_annotationItemDelegate(new KateAnnotationItemDelegate(m_viewInternal, this)) { setAcceptDrops(true); setAttribute(Qt::WA_StaticContents); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); setMouseTracking(true); m_doc->setMarkDescription(MarkInterface::markType01, i18n("Bookmark")); m_doc->setMarkPixmap(MarkInterface::markType01, QIcon::fromTheme(QStringLiteral("bookmarks")).pixmap(32, 32)); connect(m_annotationItemDelegate, &AbstractAnnotationItemDelegate::sizeHintChanged, this, &KateIconBorder::updateAnnotationBorderWidth); updateFont(); m_antiFlickerTimer.setSingleShot(true); m_antiFlickerTimer.setInterval(300); connect(&m_antiFlickerTimer, &QTimer::timeout, this, &KateIconBorder::highlightFolding); // user interaction (scrolling) hides e.g. preview connect(m_view, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), this, SLOT(displayRangeChanged())); } KateIconBorder::~KateIconBorder() { delete m_foldingPreview; delete m_foldingRange; } void KateIconBorder::setIconBorderOn(bool enable) { if (enable == m_iconBorderOn) { return; } m_iconBorderOn = enable; m_updatePositionToArea = true; QTimer::singleShot(0, this, SLOT(update())); } void KateIconBorder::setAnnotationBorderOn(bool enable) { if (enable == m_annotationBorderOn) { return; } m_annotationBorderOn = enable; // make sure the tooltip is hidden if (!m_annotationBorderOn && !m_hoveredAnnotationGroupIdentifier.isEmpty()) { m_hoveredAnnotationGroupIdentifier.clear(); hideAnnotationTooltip(); } emit m_view->annotationBorderVisibilityChanged(m_view, enable); m_updatePositionToArea = true; QTimer::singleShot(0, this, SLOT(update())); } void KateIconBorder::removeAnnotationHovering() { // remove hovering if it's still there if (m_annotationBorderOn && !m_hoveredAnnotationGroupIdentifier.isEmpty()) { m_hoveredAnnotationGroupIdentifier.clear(); QTimer::singleShot(0, this, SLOT(update())); } } void KateIconBorder::setLineNumbersOn(bool enable) { if (enable == m_lineNumbersOn) { return; } m_lineNumbersOn = enable; m_dynWrapIndicatorsOn = (m_dynWrapIndicators == 1) ? enable : m_dynWrapIndicators; m_updatePositionToArea = true; QTimer::singleShot(0, this, SLOT(update())); } void KateIconBorder::setRelLineNumbersOn(bool enable) { if (enable == m_relLineNumbersOn) { return; } m_relLineNumbersOn = enable; /* * We don't have to touch the m_dynWrapIndicatorsOn because * we already got it right from the m_lineNumbersOn */ m_updatePositionToArea = true; QTimer::singleShot( 0, this, SLOT(update()) ); } void KateIconBorder::updateForCursorLineChange() { if (m_relLineNumbersOn) { m_updateRelLineNumbers = true; } // always do normal update, e.g. for different current line color! update(); } void KateIconBorder::setDynWrapIndicators(int state) { if (state == m_dynWrapIndicators) { return; } m_dynWrapIndicators = state; m_dynWrapIndicatorsOn = (state == 1) ? m_lineNumbersOn : state; m_updatePositionToArea = true; QTimer::singleShot(0, this, SLOT(update())); } void KateIconBorder::setFoldingMarkersOn(bool enable) { if (enable == m_foldingMarkersOn) { return; } m_foldingMarkersOn = enable; m_updatePositionToArea = true; QTimer::singleShot(0, this, SLOT(update())); } QSize KateIconBorder::sizeHint() const { int w = 1; // Must be any value != 0 or we will never painted! const int i = m_positionToArea.size(); if (i > 0) { w = m_positionToArea.at(i - 1).first; } return QSize(w, 0); } // This function (re)calculates the maximum width of any of the digit characters (0 -> 9) // for graceful handling of variable-width fonts as the linenumber font. void KateIconBorder::updateFont() { const QFontMetricsF &fm = m_view->renderer()->config()->fontMetrics(); m_maxCharWidth = 0.0; // Loop to determine the widest numeric character in the current font. // 48 is ascii '0' for (int i = 48; i < 58; i++) { const qreal charWidth = ceil(fm.width(QChar(i))); m_maxCharWidth = qMax(m_maxCharWidth, charWidth); } // NOTE/TODO(or not) Take size of m_dynWrapIndicatorChar into account. // It's a multi-char and it's reported size is, even with a mono-space font, // bigger than each digit, e.g. 10 vs 12. Currently it seems to work even with // "Line Numbers Off" but all these width calculating looks slightly hacky // the icon pane scales with the font... m_iconAreaWidth = fm.height(); // Only for now, later may that become an own value m_foldingAreaWidth = m_iconAreaWidth; calcAnnotationBorderWidth(); m_updatePositionToArea = true; QTimer::singleShot(0, this, SLOT(update())); } int KateIconBorder::lineNumberWidth() const { int width = 0; // Avoid unneeded expensive calculations ;-) if (m_lineNumbersOn) { // width = (number of digits + 1) * char width const int digits = (int) ceil(log10((double)(m_view->doc()->lines() + 1))); width = (int)ceil((digits + 1) * m_maxCharWidth); } if ((width < 1) && m_dynWrapIndicatorsOn && m_view->config()->dynWordWrap()) { // FIXME Why 2x? because of above (number of digits + 1) // -> looks to me like a hint for bad calculation elsewhere width = 2 * m_maxCharWidth; } return width; } void KateIconBorder::dragMoveEvent(QDragMoveEvent *event) { // FIXME Just calling m_view->m_viewInternal->dragMoveEvent(e) don't work // as intended, we need to set the cursor at column 1 // Is there a way to change the pos of the event? QPoint pos(0, event->pos().y()); // Code copy of KateViewInternal::dragMoveEvent m_view->m_viewInternal->placeCursor(pos, true, false); m_view->m_viewInternal->fixDropEvent(event); } void KateIconBorder::dropEvent(QDropEvent *event) { m_view->m_viewInternal->dropEvent(event); } void KateIconBorder::paintEvent(QPaintEvent *e) { paintBorder(e->rect().x(), e->rect().y(), e->rect().width(), e->rect().height()); } static void paintTriangle(QPainter &painter, QColor c, int xOffset, int yOffset, int width, int height, bool open) { painter.setRenderHint(QPainter::Antialiasing); qreal size = qMin(width, height); if (open) { // Paint unfolded icon less pushy if (KColorUtils::luma(c) < 0.25) { c = KColorUtils::darken(c); } else { c = KColorUtils::shade(c, 0.1); } } else { // Paint folded icon in contrast to popup highlighting if (KColorUtils::luma(c) > 0.25) { c = KColorUtils::darken(c); } else { c = KColorUtils::shade(c, 0.1); } } QPen pen; pen.setJoinStyle(Qt::RoundJoin); pen.setColor(c); pen.setWidthF(1.5); painter.setPen(pen); painter.setBrush(c); // let some border, if possible size *= 0.6; qreal halfSize = size / 2; qreal halfSizeP = halfSize * 0.6; QPointF middle(xOffset + (qreal)width / 2, yOffset + (qreal)height / 2); if (open) { QPointF points[3] = { middle + QPointF(-halfSize, -halfSizeP), middle + QPointF(halfSize, -halfSizeP), middle + QPointF(0, halfSizeP) }; painter.drawConvexPolygon(points, 3); } else { QPointF points[3] = { middle + QPointF(-halfSizeP, -halfSize), middle + QPointF(-halfSizeP, halfSize), middle + QPointF(halfSizeP, 0) }; painter.drawConvexPolygon(points, 3); } painter.setRenderHint(QPainter::Antialiasing, false); } /** * Helper class for an identifier which can be an empty or non-empty string or invalid. * Avoids complicated explicit statements in code dealing with the identifier * received as QVariant from a model. */ class KateAnnotationGroupIdentifier { public: KateAnnotationGroupIdentifier(const QVariant &identifier) : m_isValid(identifier.isValid() && identifier.canConvert()) , m_id(m_isValid ? identifier.toString() : QString()) { } KateAnnotationGroupIdentifier() = default; KateAnnotationGroupIdentifier(const KateAnnotationGroupIdentifier &rhs) = default; KateAnnotationGroupIdentifier& operator=(const KateAnnotationGroupIdentifier &rhs) { m_isValid = rhs.m_isValid; m_id = rhs.m_id; return *this; } KateAnnotationGroupIdentifier& operator=(const QVariant &identifier) { m_isValid = (identifier.isValid() && identifier.canConvert()); if (m_isValid) { m_id = identifier.toString(); } else { m_id.clear(); } return *this; } bool operator==(const KateAnnotationGroupIdentifier &rhs) const { return (m_isValid == rhs.m_isValid) && (!m_isValid || (m_id == rhs.m_id)); } bool operator!=(const KateAnnotationGroupIdentifier &rhs) const { return (m_isValid != rhs.m_isValid) || (m_isValid && (m_id != rhs.m_id)); } void clear() { m_isValid = false; m_id.clear(); } bool isValid() const { return m_isValid; } const QString& id() const { return m_id; } private: bool m_isValid = false; QString m_id; }; /** * Helper class for iterative calculation of data regarding the position * of a line with regard to annotation item grouping. */ class KateAnnotationGroupPositionState { public: /** * @param startz first rendered displayed line * @param isUsed flag whether the KateAnnotationGroupPositionState object will * be used or is just created due to being on the stack */ KateAnnotationGroupPositionState(KateViewInternal *viewInternal, const KTextEditor::AnnotationModel *model, const QString &hoveredAnnotationGroupIdentifier, uint startz, bool isUsed); /** * @param styleOption option to fill with data for the given line * @param z rendered displayed line * @param realLine real line which is rendered here (passed to avoid another look-up) */ void nextLine(KTextEditor::StyleOptionAnnotationItem &styleOption, uint z, int realLine); private: KateViewInternal *m_viewInternal; const KTextEditor::AnnotationModel * const m_model; const QString m_hoveredAnnotationGroupIdentifier; int m_visibleWrappedLineInAnnotationGroup = -1; KateAnnotationGroupIdentifier m_lastAnnotationGroupIdentifier; KateAnnotationGroupIdentifier m_nextAnnotationGroupIdentifier; bool m_isSameAnnotationGroupsSinceLast = false; }; KateAnnotationGroupPositionState::KateAnnotationGroupPositionState(KateViewInternal *viewInternal, const KTextEditor::AnnotationModel *model, const QString &hoveredAnnotationGroupIdentifier, uint startz, bool isUsed) : m_viewInternal(viewInternal) , m_model(model) , m_hoveredAnnotationGroupIdentifier(hoveredAnnotationGroupIdentifier) { if (!isUsed) { return; } if (!m_model || (static_cast(startz) >= m_viewInternal->cache()->viewCacheLineCount())) { return; } const auto realLineAtStart = m_viewInternal->cache()->viewLine(startz).line(); m_nextAnnotationGroupIdentifier = m_model->data(realLineAtStart, (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole); if (m_nextAnnotationGroupIdentifier.isValid()) { // estimate state of annotation group before first rendered line if (startz == 0) { if (realLineAtStart > 0) { // TODO: here we would want to scan until the next line that would be displayed, // to see if there are any group changes until then // for now simply taking neighbour line into account, not a grave bug on the first displayed line m_lastAnnotationGroupIdentifier = m_model->data(realLineAtStart - 1, (Qt::ItemDataRole) KTextEditor::AnnotationModel::GroupIdentifierRole); m_isSameAnnotationGroupsSinceLast = (m_lastAnnotationGroupIdentifier == m_nextAnnotationGroupIdentifier); } } else { const auto realLineBeforeStart = m_viewInternal->cache()->viewLine(startz-1).line(); m_lastAnnotationGroupIdentifier = m_model->data(realLineBeforeStart, (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole); if (m_lastAnnotationGroupIdentifier.isValid()) { if (m_lastAnnotationGroupIdentifier.id() == m_nextAnnotationGroupIdentifier.id()) { m_isSameAnnotationGroupsSinceLast = true; // estimate m_visibleWrappedLineInAnnotationGroup from lines before startz for (uint z = startz; z > 0; --z) { const auto realLine = m_viewInternal->cache()->viewLine(z-1).line(); const KateAnnotationGroupIdentifier identifier = m_model->data(realLine, (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole); if (identifier != m_lastAnnotationGroupIdentifier) { break; } ++m_visibleWrappedLineInAnnotationGroup; } } } } } } void KateAnnotationGroupPositionState::nextLine(KTextEditor::StyleOptionAnnotationItem &styleOption, uint z, int realLine) { styleOption.wrappedLine = m_viewInternal->cache()->viewLine(z).viewLine(); styleOption.wrappedLineCount = m_viewInternal->cache()->viewLineCount(realLine); // Estimate position in group const KateAnnotationGroupIdentifier annotationGroupIdentifier = m_nextAnnotationGroupIdentifier; bool isSameAnnotationGroupsSinceThis = false; // Calculate next line's group identifier // shortcut: assuming wrapped lines are always displayed together, test is simple if (styleOption.wrappedLine+1 < styleOption.wrappedLineCount) { m_nextAnnotationGroupIdentifier = annotationGroupIdentifier; isSameAnnotationGroupsSinceThis = true; } else { if (static_cast(z+1) < m_viewInternal->cache()->viewCacheLineCount()) { const int realLineAfter = m_viewInternal->cache()->viewLine(z+1).line(); // search for any realLine with a different group id, also the non-displayed int rl = realLine + 1; for (; rl <= realLineAfter; ++rl) { m_nextAnnotationGroupIdentifier = m_model->data(rl, (Qt::ItemDataRole) KTextEditor::AnnotationModel::GroupIdentifierRole); if (!m_nextAnnotationGroupIdentifier.isValid() || (m_nextAnnotationGroupIdentifier.id() != annotationGroupIdentifier.id())) { break; } } isSameAnnotationGroupsSinceThis = (rl > realLineAfter); if (rl < realLineAfter) { m_nextAnnotationGroupIdentifier = m_model->data(realLineAfter, (Qt::ItemDataRole) KTextEditor::AnnotationModel::GroupIdentifierRole); } } else { // TODO: check next line after display end m_nextAnnotationGroupIdentifier.clear(); isSameAnnotationGroupsSinceThis = false; } } if (annotationGroupIdentifier.isValid()) { if (m_hoveredAnnotationGroupIdentifier == annotationGroupIdentifier.id()) { styleOption.state |= QStyle::State_MouseOver; } else { styleOption.state &= ~QStyle::State_MouseOver; } if (m_isSameAnnotationGroupsSinceLast) { ++m_visibleWrappedLineInAnnotationGroup; } else { m_visibleWrappedLineInAnnotationGroup = 0; } styleOption.annotationItemGroupingPosition = StyleOptionAnnotationItem::InGroup; if (!m_isSameAnnotationGroupsSinceLast) { styleOption.annotationItemGroupingPosition |= StyleOptionAnnotationItem::GroupBegin; } if (!isSameAnnotationGroupsSinceThis) { styleOption.annotationItemGroupingPosition |= StyleOptionAnnotationItem::GroupEnd; } } else { m_visibleWrappedLineInAnnotationGroup = 0; } styleOption.visibleWrappedLineInGroup = m_visibleWrappedLineInAnnotationGroup; m_lastAnnotationGroupIdentifier = m_nextAnnotationGroupIdentifier; m_isSameAnnotationGroupsSinceLast = isSameAnnotationGroupsSinceThis; } void KateIconBorder::paintBorder(int /*x*/, int y, int /*width*/, int height) { const uint h = m_view->renderer()->lineHeight(); const uint startz = (y / h); const uint endz = qMin(startz + 1 + (height / h), static_cast(m_viewInternal->cache()->viewCacheLineCount())); const uint currentLine = m_view->cursorPosition().line(); // center the folding boxes int m_px = (h - 11) / 2; if (m_px < 0) { m_px = 0; } // Ensure we miss no change of the count of line number digits const int newNeededWidth = lineNumberWidth(); if (m_updatePositionToArea || (newNeededWidth != m_lineNumberAreaWidth)) { m_lineNumberAreaWidth = newNeededWidth; m_updatePositionToArea = true; m_positionToArea.clear(); } QPainter p(this); p.setRenderHints(QPainter::TextAntialiasing); p.setFont(m_view->renderer()->config()->font()); // for line numbers KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel(); KateAnnotationGroupPositionState annotationGroupPositionState(m_viewInternal, model, m_hoveredAnnotationGroupIdentifier, startz, m_annotationBorderOn); // Fetch often used data only once, improve readability const int w = width(); const QColor iconBarColor = m_view->renderer()->config()->iconBarColor(); // Effective our background const QColor lineNumberColor = m_view->renderer()->config()->lineNumberColor(); const QColor backgroundColor = m_view->renderer()->config()->backgroundColor(); // Of the edit area // Paint the border in chunks line by line for (uint z = startz; z < endz; z++) { // Painting coordinates, lineHeight * lineNumber const uint y = h * z; // Paint the border in chunks left->right, remember used width uint lnX = 0; // Paint background over full width... p.fillRect(lnX, y, w, h, iconBarColor); // ...and overpaint again the end to simulate some margin to the edit area, // so that the text not looks like stuck to the border p.fillRect(w - m_separatorWidth, y, w, h, backgroundColor); const KateTextLayout lineLayout = m_viewInternal->cache()->viewLine(z); int realLine = lineLayout.line(); if (realLine < 0) { // We have reached the end of the document, just paint background continue; } // icon pane if (m_iconBorderOn) { p.setPen(m_view->renderer()->config()->separatorColor()); p.setBrush(m_view->renderer()->config()->separatorColor()); p.drawLine(lnX + m_iconAreaWidth, y, lnX + m_iconAreaWidth, y + h); const uint mrk(m_doc->mark(realLine)); // call only once if (mrk && lineLayout.startCol() == 0) { for (uint bit = 0; bit < 32; bit++) { MarkInterface::MarkTypes markType = (MarkInterface::MarkTypes)(1 << bit); if (mrk & markType) { QPixmap px_mark(m_doc->markPixmap(markType)); px_mark.setDevicePixelRatio(devicePixelRatioF()); if (!px_mark.isNull() && h > 0 && m_iconAreaWidth > 0) { // scale up to a usable size const int s = qMin(m_iconAreaWidth * devicePixelRatioF(), h * devicePixelRatioF()) - 2; px_mark = px_mark.scaled(s, s, Qt::KeepAspectRatio); // center the mark pixmap int x_px = 0.5 * qMax(m_iconAreaWidth - (s / devicePixelRatioF()), 0.0); int y_px = 0.5 * qMax(h - (s / devicePixelRatioF()), 0.0); p.drawPixmap(lnX + x_px, y + y_px, px_mark); } } } } lnX += m_iconAreaWidth + m_separatorWidth; if (m_updatePositionToArea) { m_positionToArea.append(AreaPosition(lnX, IconBorder)); } } // annotation information if (m_annotationBorderOn) { // Draw a border line between annotations and the line numbers p.setPen(lineNumberColor); p.setBrush(lineNumberColor); const qreal borderX = lnX + m_annotationAreaWidth + 0.5; p.drawLine(QPointF(borderX, y+0.5), QPointF(borderX, y + h - 0.5)); if (model) { KTextEditor::StyleOptionAnnotationItem styleOption; initStyleOption(&styleOption); styleOption.rect.setRect(lnX, y, m_annotationAreaWidth, h); annotationGroupPositionState.nextLine(styleOption, z, realLine); m_annotationItemDelegate->paint(&p, styleOption, model, realLine); } lnX += m_annotationAreaWidth + m_separatorWidth; if (m_updatePositionToArea) { m_positionToArea.append(AreaPosition(lnX, AnnotationBorder)); } } // line number if (m_lineNumbersOn || m_dynWrapIndicatorsOn) { QColor usedLineNumberColor; const int distanceToCurrent = abs(realLine - static_cast(currentLine)); if (distanceToCurrent == 0) { usedLineNumberColor = m_view->renderer()->config()->currentLineNumberColor(); } else { usedLineNumberColor = lineNumberColor; } p.setPen(usedLineNumberColor); p.setBrush(usedLineNumberColor); if (lineLayout.startCol() == 0) { if (m_relLineNumbersOn) { if (distanceToCurrent == 0) { p.drawText(lnX + m_maxCharWidth / 2, y, m_lineNumberAreaWidth - m_maxCharWidth, h, Qt::TextDontClip | Qt::AlignLeft | Qt::AlignVCenter, QString::number(realLine + 1)); } else { p.drawText(lnX + m_maxCharWidth / 2, y, m_lineNumberAreaWidth - m_maxCharWidth, h, Qt::TextDontClip | Qt::AlignRight | Qt::AlignVCenter, QString::number(distanceToCurrent)); } if (m_updateRelLineNumbers) { m_updateRelLineNumbers = false; update(); } } else if (m_lineNumbersOn) { p.drawText(lnX + m_maxCharWidth / 2, y, m_lineNumberAreaWidth - m_maxCharWidth, h, Qt::TextDontClip | Qt::AlignRight | Qt::AlignVCenter, QString::number(realLine + 1)); } } else if (m_dynWrapIndicatorsOn) { p.drawText(lnX + m_maxCharWidth / 2, y, m_lineNumberAreaWidth - m_maxCharWidth, h, Qt::TextDontClip | Qt::AlignRight | Qt::AlignVCenter, m_dynWrapIndicatorChar); } lnX += m_lineNumberAreaWidth + m_separatorWidth; if (m_updatePositionToArea) { m_positionToArea.append(AreaPosition(lnX, LineNumbers)); } } // modified line system if (m_view->config()->lineModification() && !m_doc->url().isEmpty()) { const Kate::TextLine tl = m_doc->plainKateTextLine(realLine); if (tl->markedAsModified()) { p.fillRect(lnX, y, m_modAreaWidth, h, m_view->renderer()->config()->modifiedLineColor()); } else if (tl->markedAsSavedOnDisk()) { p.fillRect(lnX, y, m_modAreaWidth, h, m_view->renderer()->config()->savedLineColor()); } else { p.fillRect(lnX, y, m_modAreaWidth, h, iconBarColor); } lnX += m_modAreaWidth; // No m_separatorWidth if (m_updatePositionToArea) { m_positionToArea.append(AreaPosition(lnX, None)); } } // folding markers if (m_foldingMarkersOn) { const QColor foldingColor(m_view->renderer()->config()->foldingColor()); // possible additional folding highlighting if (m_foldingRange && m_foldingRange->overlapsLine(realLine)) { p.fillRect(lnX, y, m_foldingAreaWidth, h, foldingColor); } if (lineLayout.startCol() == 0) { QVector > startingRanges = m_view->textFolding().foldingRangesStartingOnLine(realLine); bool anyFolded = false; for (int i = 0; i < startingRanges.size(); ++i) { if (startingRanges[i].second & Kate::TextFolding::Folded) { anyFolded = true; } } const Kate::TextLine tl = m_doc->kateTextLine(realLine); if (!startingRanges.isEmpty() || tl->markedAsFoldingStart()) { if (anyFolded) { paintTriangle(p, foldingColor, lnX, y, m_foldingAreaWidth, h, false); } else { // Don't try to use currentLineNumberColor, the folded icon gets also not highligted paintTriangle(p, lineNumberColor, lnX, y, m_foldingAreaWidth, h, true); } } } lnX += m_foldingAreaWidth; if (m_updatePositionToArea) { m_positionToArea.append(AreaPosition(lnX, FoldingMarkers)); } } if (m_updatePositionToArea) { m_updatePositionToArea = false; // Don't forget our "text-stuck-to-border" protector lnX += m_separatorWidth; m_positionToArea.append(AreaPosition(lnX, None)); // Now that we know our needed space, ensure we are painted properly updateGeometry(); update(); return; } } } KateIconBorder::BorderArea KateIconBorder::positionToArea(const QPoint &p) const { for (int i = 0; i < m_positionToArea.size(); ++i) { if (p.x() <= m_positionToArea.at(i).first) { return m_positionToArea.at(i).second; } } return None; } void KateIconBorder::mousePressEvent(QMouseEvent *e) { const KateTextLayout &t = m_viewInternal->yToKateTextLayout(e->y()); if (t.isValid()) { m_lastClickedLine = t.line(); const auto area = positionToArea(e->pos()); // IconBorder and AnnotationBorder have their own behavior; don't forward to view if (area != IconBorder && area != AnnotationBorder) { const auto pos = QPoint(0, e->y()); if (area == LineNumbers && e->button() == Qt::LeftButton && !(e->modifiers() & Qt::ShiftModifier)) { // setup view so the following mousePressEvent will select the line m_viewInternal->beginSelectLine(pos); } QMouseEvent forward(QEvent::MouseButtonPress, pos, e->button(), e->buttons(), e->modifiers()); m_viewInternal->mousePressEvent(&forward); } return e->accept(); } QWidget::mousePressEvent(e); } void KateIconBorder::highlightFoldingDelayed(int line) { if ((line == m_currentLine) || (line >= m_doc->buffer().lines())) { return; } m_currentLine = line; if (m_foldingRange) { // We are for a while in the folding area, no need for delay highlightFolding(); } else if (!m_antiFlickerTimer.isActive()) { m_antiFlickerTimer.start(); } } void KateIconBorder::highlightFolding() { /** * compute to which folding range we belong * FIXME: optimize + perhaps have some better threshold or use timers! */ KTextEditor::Range newRange = KTextEditor::Range::invalid(); for (int line = m_currentLine; line >= qMax(0, m_currentLine - 1024); --line) { /** * try if we have folding range from that line, should be fast per call */ KTextEditor::Range foldingRange = m_doc->buffer().computeFoldingRangeForStartLine(line); if (!foldingRange.isValid()) { continue; } /** * does the range reach us? */ if (foldingRange.overlapsLine(m_currentLine)) { newRange = foldingRange; break; } } if (newRange.isValid() && m_foldingRange && *m_foldingRange == newRange) { // new range equals the old one, nothing to do. return; } // the ranges differ, delete the old, if it exists delete m_foldingRange; m_foldingRange = nullptr; // New range, new preview! delete m_foldingPreview; bool showPreview = false; if (newRange.isValid()) { // When next line is not visible we have a folded range, only then we want a preview! showPreview = !m_view->textFolding().isLineVisible(newRange.start().line() + 1); //qCDebug(LOG_KTE) << "new folding hl-range:" << newRange; m_foldingRange = m_doc->newMovingRange(newRange, KTextEditor::MovingRange::ExpandRight); KTextEditor::Attribute::Ptr attr(new KTextEditor::Attribute()); /** * create highlighting color * we avoid alpha as overpainting leads to ugly lines (https://bugreports.qt.io/browse/QTBUG-66036) */ attr->setBackground(QBrush(m_view->renderer()->config()->foldingColor())); m_foldingRange->setView(m_view); // use z depth defined in moving ranges interface m_foldingRange->setZDepth(-100.0); m_foldingRange->setAttribute(attr); } // show text preview, if a folded region starts here... // ...but only when main window is active (#392396) const bool isWindowActive = !window() || window()->isActiveWindow(); if (showPreview && m_view->config()->foldingPreview() && isWindowActive) { m_foldingPreview = new KateTextPreview(m_view, this); m_foldingPreview->setAttribute(Qt::WA_ShowWithoutActivating); m_foldingPreview->setFrameStyle(QFrame::StyledPanel); // Calc how many lines can be displayed in the popup const int lineHeight = m_view->renderer()->lineHeight(); const int foldingStartLine = m_foldingRange->start().line(); // FIXME Is there really no easier way to find lineInDisplay? const QPoint pos = m_viewInternal->mapFrom(m_view, m_view->cursorToCoordinate(KTextEditor::Cursor(foldingStartLine, 0))); const int lineInDisplay = pos.y() / lineHeight; // Allow slightly overpainting of the view bottom to proper cover all lines const int extra = (m_viewInternal->height() % lineHeight) > (lineHeight * 0.6) ? 1 : 0; const int lineCount = qMin(m_foldingRange->numberOfLines() + 1, m_viewInternal->linesDisplayed() - lineInDisplay + extra); m_foldingPreview->resize(m_viewInternal->width(), lineCount * lineHeight + 2 * m_foldingPreview->frameWidth()); const int xGlobal = mapToGlobal(QPoint(width(), 0)).x(); const int yGlobal = m_view->mapToGlobal(m_view->cursorToCoordinate(KTextEditor::Cursor(foldingStartLine, 0))).y(); m_foldingPreview->move(QPoint(xGlobal, yGlobal) - m_foldingPreview->contentsRect().topLeft()); m_foldingPreview->setLine(foldingStartLine); m_foldingPreview->setCenterView(false); m_foldingPreview->setShowFoldedLines(true); m_foldingPreview->raise(); m_foldingPreview->show(); } } void KateIconBorder::hideFolding() { if (m_antiFlickerTimer.isActive()) { m_antiFlickerTimer.stop(); } m_currentLine = -1; delete m_foldingRange; m_foldingRange = nullptr; delete m_foldingPreview; } void KateIconBorder::leaveEvent(QEvent *event) { hideFolding(); removeAnnotationHovering(); QWidget::leaveEvent(event); } void KateIconBorder::mouseMoveEvent(QMouseEvent *e) { const KateTextLayout &t = m_viewInternal->yToKateTextLayout(e->y()); if (!t.isValid()) { // Cleanup everything which may be shown removeAnnotationHovering(); hideFolding(); } else { const BorderArea area = positionToArea(e->pos()); if (area == FoldingMarkers) { highlightFoldingDelayed(t.line()); } else { hideFolding(); } if (area == AnnotationBorder) { KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel(); if (model) { m_hoveredAnnotationGroupIdentifier = model->data( t.line(), (Qt::ItemDataRole) KTextEditor::AnnotationModel::GroupIdentifierRole ).toString(); const QPoint viewRelativePos = m_view->mapFromGlobal(e->globalPos()); QHelpEvent helpEvent(QEvent::ToolTip, viewRelativePos, e->globalPos()); KTextEditor::StyleOptionAnnotationItem styleOption; initStyleOption(&styleOption); styleOption.rect = annotationLineRectInView(t.line()); setStyleOptionLineData(&styleOption, e->y(), t.line(), model, m_hoveredAnnotationGroupIdentifier); m_annotationItemDelegate->helpEvent(&helpEvent, m_view, styleOption, model, t.line()); QTimer::singleShot(0, this, SLOT(update())); } } else { if (area == IconBorder) { m_doc->requestMarkTooltip(t.line(), e->globalPos()); } m_hoveredAnnotationGroupIdentifier.clear(); QTimer::singleShot(0, this, SLOT(update())); } if (area != IconBorder) { QPoint p = m_viewInternal->mapFromGlobal(e->globalPos()); QMouseEvent forward(QEvent::MouseMove, p, e->button(), e->buttons(), e->modifiers()); m_viewInternal->mouseMoveEvent(&forward); } } QWidget::mouseMoveEvent(e); } void KateIconBorder::mouseReleaseEvent(QMouseEvent *e) { const int cursorOnLine = m_viewInternal->yToKateTextLayout(e->y()).line(); if (cursorOnLine == m_lastClickedLine && cursorOnLine >= 0 && cursorOnLine <= m_doc->lastLine()) { const BorderArea area = positionToArea(e->pos()); if (area == IconBorder) { if (e->button() == Qt::LeftButton) { if (!m_doc->handleMarkClick(cursorOnLine)) { KateViewConfig *config = m_view->config(); const uint editBits = m_doc->editableMarks(); // is the default or the only editable mark const uint singleMark = qPopulationCount(editBits) > 1 ? editBits & config->defaultMarkType() : editBits; if (singleMark) { if (m_doc->mark(cursorOnLine) & singleMark) { m_doc->removeMark(cursorOnLine, singleMark); } else { m_doc->addMark(cursorOnLine, singleMark); } } else if (config->allowMarkMenu()) { showMarkMenu(cursorOnLine, QCursor::pos()); } } } else if (e->button() == Qt::RightButton) { showMarkMenu(cursorOnLine, QCursor::pos()); } } if (area == FoldingMarkers) { // Prefer the highlighted range over the exact clicked line const int lineToToggle = m_foldingRange ? m_foldingRange->toRange().start().line() : cursorOnLine; if (e->button() == Qt::LeftButton) { m_view->toggleFoldingOfLine(lineToToggle); } else if (e->button() == Qt::RightButton) { m_view->toggleFoldingsInRange(lineToToggle); } delete m_foldingPreview; } if (area == AnnotationBorder) { const bool singleClick = style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this); if (e->button() == Qt::LeftButton && singleClick) { emit m_view->annotationActivated(m_view, cursorOnLine); } else if (e->button() == Qt::RightButton) { showAnnotationMenu(cursorOnLine, e->globalPos()); } } } QMouseEvent forward(QEvent::MouseButtonRelease, QPoint(0, e->y()), e->button(), e->buttons(), e->modifiers()); m_viewInternal->mouseReleaseEvent(&forward); } void KateIconBorder::mouseDoubleClickEvent(QMouseEvent *e) { int cursorOnLine = m_viewInternal->yToKateTextLayout(e->y()).line(); if (cursorOnLine == m_lastClickedLine && cursorOnLine <= m_doc->lastLine()) { const BorderArea area = positionToArea(e->pos()); const bool singleClick = style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this); if (area == AnnotationBorder && !singleClick) { emit m_view->annotationActivated(m_view, cursorOnLine); } } QMouseEvent forward(QEvent::MouseButtonDblClick, QPoint(0, e->y()), e->button(), e->buttons(), e->modifiers()); m_viewInternal->mouseDoubleClickEvent(&forward); } void KateIconBorder::wheelEvent(QWheelEvent *e) { QCoreApplication::sendEvent(m_viewInternal, e); } void KateIconBorder::showMarkMenu(uint line, const QPoint &pos) { if (m_doc->handleMarkContextMenu(line, pos)) { return; } if (!m_view->config()->allowMarkMenu()) { return; } QMenu markMenu; QMenu selectDefaultMark; auto selectDefaultMarkActionGroup = new QActionGroup(&selectDefaultMark); QVector vec(33); int i = 1; for (uint bit = 0; bit < 32; bit++) { MarkInterface::MarkTypes markType = (MarkInterface::MarkTypes)(1 << bit); if (!(m_doc->editableMarks() & markType)) { continue; } QAction *mA; QAction *dMA; const QPixmap icon = m_doc->markPixmap(markType); if (!m_doc->markDescription(markType).isEmpty()) { mA = markMenu.addAction(icon, m_doc->markDescription(markType)); dMA = selectDefaultMark.addAction(icon, m_doc->markDescription(markType)); } else { mA = markMenu.addAction(icon, i18n("Mark Type %1", bit + 1)); dMA = selectDefaultMark.addAction(icon, i18n("Mark Type %1", bit + 1)); } selectDefaultMarkActionGroup->addAction(dMA); mA->setData(i); mA->setCheckable(true); dMA->setData(i + 100); dMA->setCheckable(true); if (m_doc->mark(line) & markType) { mA->setChecked(true); } if (markType & KateViewConfig::global()->defaultMarkType()) { dMA->setChecked(true); } vec[i++] = markType; } if (markMenu.actions().count() == 0) { return; } if (markMenu.actions().count() > 1) { markMenu.addAction(i18n("Set Default Mark Type"))->setMenu(&selectDefaultMark); } QAction *rA = markMenu.exec(pos); if (!rA) { return; } int result = rA->data().toInt(); if (result > 100) { - KateViewConfig::global()->setDefaultMarkType(vec[result - 100]); + KateViewConfig::global()->setValue(KateViewConfig::DefaultMarkType, vec[result - 100]); } else { MarkInterface::MarkTypes markType = (MarkInterface::MarkTypes) vec[result]; if (m_doc->mark(line) & markType) { m_doc->removeMark(line, markType); } else { m_doc->addMark(line, markType); } } } KTextEditor::AbstractAnnotationItemDelegate* KateIconBorder::annotationItemDelegate() const { return m_annotationItemDelegate; } void KateIconBorder::setAnnotationItemDelegate(KTextEditor::AbstractAnnotationItemDelegate *delegate) { if (delegate == m_annotationItemDelegate) { return; } // reset to default, but already on that? if (!delegate && m_isDefaultAnnotationItemDelegate) { // nothing to do return; } // make sure the tooltip is hidden if (m_annotationBorderOn && !m_hoveredAnnotationGroupIdentifier.isEmpty()) { m_hoveredAnnotationGroupIdentifier.clear(); hideAnnotationTooltip(); } disconnect(m_annotationItemDelegate, &AbstractAnnotationItemDelegate::sizeHintChanged, this, &KateIconBorder::updateAnnotationBorderWidth); if (!m_isDefaultAnnotationItemDelegate) { disconnect(m_annotationItemDelegate, &QObject::destroyed, this, &KateIconBorder::handleDestroyedAnnotationItemDelegate); } if (!delegate) { // reset to a default delegate m_annotationItemDelegate = new KateAnnotationItemDelegate(m_viewInternal, this); m_isDefaultAnnotationItemDelegate = true; } else { // drop any default delegate if (m_isDefaultAnnotationItemDelegate) { delete m_annotationItemDelegate; m_isDefaultAnnotationItemDelegate = false; } m_annotationItemDelegate = delegate; // catch delegate being destroyed connect(delegate, &QObject::destroyed, this, &KateIconBorder::handleDestroyedAnnotationItemDelegate); } connect(m_annotationItemDelegate, &AbstractAnnotationItemDelegate::sizeHintChanged, this, &KateIconBorder::updateAnnotationBorderWidth); if (m_annotationBorderOn) { updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } } void KateIconBorder::handleDestroyedAnnotationItemDelegate() { setAnnotationItemDelegate(nullptr); } void KateIconBorder::initStyleOption(KTextEditor::StyleOptionAnnotationItem* styleOption) const { styleOption->initFrom(this); styleOption->view = m_view; styleOption->decorationSize = QSize(m_iconAreaWidth, m_iconAreaWidth); styleOption->contentFontMetrics = m_view->renderer()->config()->fontMetrics(); } void KateIconBorder::setStyleOptionLineData(KTextEditor::StyleOptionAnnotationItem* styleOption, int y, int realLine, const KTextEditor::AnnotationModel *model, const QString &annotationGroupIdentifier) const { // calculate rendered displayed line const uint h = m_view->renderer()->lineHeight(); const uint z = (y / h); KateAnnotationGroupPositionState annotationGroupPositionState(m_viewInternal, model, annotationGroupIdentifier, z, true); annotationGroupPositionState.nextLine(*styleOption, z, realLine); } QRect KateIconBorder::annotationLineRectInView(int line) const { int x = 0; if (m_iconBorderOn) { x += m_iconAreaWidth + 2; } const int y = m_view->m_viewInternal->lineToY(line); return QRect(x, y, m_annotationAreaWidth, m_view->renderer()->lineHeight()); } void KateIconBorder::updateAnnotationLine(int line) { // TODO: why has the default value been 8, where is that magic number from? int width = 8; KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel(); if (model) { KTextEditor::StyleOptionAnnotationItem styleOption; initStyleOption(&styleOption); width = m_annotationItemDelegate->sizeHint(styleOption, model, line).width(); } if (width > m_annotationAreaWidth) { m_annotationAreaWidth = width; m_updatePositionToArea = true; QTimer::singleShot(0, this, SLOT(update())); } } void KateIconBorder::showAnnotationMenu(int line, const QPoint &pos) { QMenu menu; QAction a(i18n("Disable Annotation Bar"), &menu); a.setIcon(QIcon::fromTheme(QStringLiteral("dialog-close"))); menu.addAction(&a); emit m_view->annotationContextMenuAboutToShow(m_view, &menu, line); if (menu.exec(pos) == &a) { m_view->setAnnotationBorderVisible(false); } } void KateIconBorder::hideAnnotationTooltip() { m_annotationItemDelegate->hideTooltip(m_view); } void KateIconBorder::updateAnnotationBorderWidth() { calcAnnotationBorderWidth(); m_updatePositionToArea = true; QTimer::singleShot(0, this, SLOT(update())); } void KateIconBorder::calcAnnotationBorderWidth() { // TODO: another magic number, not matching the one in updateAnnotationLine() m_annotationAreaWidth = 6; KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel(); if (model) { KTextEditor::StyleOptionAnnotationItem styleOption; initStyleOption(&styleOption); const int lineCount = m_view->doc()->lines(); if (lineCount > 0) { const int checkedLineCount = m_hasUniformAnnotationItemSizes ? 1 : lineCount; for (int i = 0; i < checkedLineCount; ++i) { const int curwidth = m_annotationItemDelegate->sizeHint(styleOption, model, i).width(); if (curwidth > m_annotationAreaWidth) { m_annotationAreaWidth = curwidth; } } } } } void KateIconBorder::annotationModelChanged(KTextEditor::AnnotationModel *oldmodel, KTextEditor::AnnotationModel *newmodel) { if (oldmodel) { oldmodel->disconnect(this); } if (newmodel) { connect(newmodel, SIGNAL(reset()), this, SLOT(updateAnnotationBorderWidth())); connect(newmodel, SIGNAL(lineChanged(int)), this, SLOT(updateAnnotationLine(int))); } updateAnnotationBorderWidth(); } void KateIconBorder::displayRangeChanged() { hideFolding(); removeAnnotationHovering(); } //END KateIconBorder //BEGIN KateViewEncodingAction // According to http://www.iana.org/assignments/ianacharset-mib // the default/unknown mib value is 2. #define MIB_DEFAULT 2 bool lessThanAction(KSelectAction *a, KSelectAction *b) { return a->text() < b->text(); } void KateViewEncodingAction::Private::init() { QList actions; q->setToolBarMode(MenuMode); int i; foreach (const QStringList &encodingsForScript, KCharsets::charsets()->encodingsByScript()) { KSelectAction *tmp = new KSelectAction(encodingsForScript.at(0), q); for (i = 1; i < encodingsForScript.size(); ++i) { tmp->addAction(encodingsForScript.at(i)); } q->connect(tmp, SIGNAL(triggered(QAction*)), q, SLOT(_k_subActionTriggered(QAction*))); //tmp->setCheckable(true); actions << tmp; } std::sort(actions.begin(), actions.end(), lessThanAction); foreach (KSelectAction *action, actions) { q->addAction(action); } } void KateViewEncodingAction::Private::_k_subActionTriggered(QAction *action) { if (currentSubAction == action) { return; } currentSubAction = action; bool ok = false; int mib = q->mibForName(action->text(), &ok); if (ok) { emit q->KSelectAction::triggered(action->text()); emit q->triggered(q->codecForMib(mib)); } } KateViewEncodingAction::KateViewEncodingAction(KTextEditor::DocumentPrivate *_doc, KTextEditor::ViewPrivate *_view, const QString &text, QObject *parent, bool saveAsMode) : KSelectAction(text, parent), doc(_doc), view(_view), d(new Private(this)) , m_saveAsMode(saveAsMode) { d->init(); connect(menu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShow())); connect(this, SIGNAL(triggered(QString)), this, SLOT(setEncoding(QString))); } KateViewEncodingAction::~KateViewEncodingAction() { delete d; } void KateViewEncodingAction::slotAboutToShow() { setCurrentCodec(doc->config()->encoding()); } void KateViewEncodingAction::setEncoding(const QString &e) { /** * in save as mode => trigger saveAs */ if (m_saveAsMode) { doc->documentSaveAsWithEncoding(e); return; } /** * else switch encoding */ doc->userSetEncodingForNextReload(); doc->setEncoding(e); view->reloadFile(); } int KateViewEncodingAction::mibForName(const QString &codecName, bool *ok) const { // FIXME logic is good but code is ugly bool success = false; int mib = MIB_DEFAULT; KCharsets *charsets = KCharsets::charsets(); QTextCodec *codec = charsets->codecForName(codecName, success); if (!success) { // Maybe we got a description name instead codec = charsets->codecForName(charsets->encodingForName(codecName), success); } if (codec) { mib = codec->mibEnum(); } if (ok) { *ok = success; } if (success) { return mib; } qCWarning(LOG_KTE) << "Invalid codec name: " << codecName; return MIB_DEFAULT; } QTextCodec *KateViewEncodingAction::codecForMib(int mib) const { if (mib == MIB_DEFAULT) { // FIXME offer to change the default codec return QTextCodec::codecForLocale(); } else { return QTextCodec::codecForMib(mib); } } QTextCodec *KateViewEncodingAction::currentCodec() const { return codecForMib(currentCodecMib()); } bool KateViewEncodingAction::setCurrentCodec(QTextCodec *codec) { disconnect(this, SIGNAL(triggered(QString)), this, SLOT(setEncoding(QString))); int i, j; for (i = 0; i < actions().size(); ++i) { if (actions().at(i)->menu()) { for (j = 0; j < actions().at(i)->menu()->actions().size(); ++j) { if (!j && !actions().at(i)->menu()->actions().at(j)->data().isNull()) { continue; } if (actions().at(i)->menu()->actions().at(j)->isSeparator()) { continue; } if (codec == KCharsets::charsets()->codecForName(actions().at(i)->menu()->actions().at(j)->text())) { d->currentSubAction = actions().at(i)->menu()->actions().at(j); d->currentSubAction->setChecked(true); } else { actions().at(i)->menu()->actions().at(j)->setChecked(false); } } } } connect(this, SIGNAL(triggered(QString)), this, SLOT(setEncoding(QString))); return true; } QString KateViewEncodingAction::currentCodecName() const { return d->currentSubAction->text(); } bool KateViewEncodingAction::setCurrentCodec(const QString &codecName) { return setCurrentCodec(KCharsets::charsets()->codecForName(codecName)); } int KateViewEncodingAction::currentCodecMib() const { return mibForName(currentCodecName()); } bool KateViewEncodingAction::setCurrentCodec(int mib) { return setCurrentCodec(codecForMib(mib)); } //END KateViewEncodingAction //BEGIN KateViewBar related classes KateViewBarWidget::KateViewBarWidget(bool addCloseButton, QWidget *parent) : QWidget(parent) , m_viewBar(nullptr) { QHBoxLayout *layout = new QHBoxLayout(this); // NOTE: Here be cosmetics. layout->setContentsMargins(0, 0, 0, 0); // hide button if (addCloseButton) { QToolButton *hideButton = new QToolButton(this); hideButton->setAutoRaise(true); hideButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close"))); connect(hideButton, SIGNAL(clicked()), SIGNAL(hideMe())); layout->addWidget(hideButton); layout->setAlignment(hideButton, Qt::AlignLeft | Qt::AlignTop); } // widget to be used as parent for the real content m_centralWidget = new QWidget(this); layout->addWidget(m_centralWidget); setLayout(layout); setFocusProxy(m_centralWidget); } KateViewBar::KateViewBar(bool external, QWidget *parent, KTextEditor::ViewPrivate *view) : QWidget(parent), m_external(external), m_view(view), m_permanentBarWidget(nullptr) { m_layout = new QVBoxLayout(this); m_stack = new QStackedWidget(this); m_layout->addWidget(m_stack); m_layout->setContentsMargins(0, 0, 0, 0); m_stack->hide(); hide(); } void KateViewBar::addBarWidget(KateViewBarWidget *newBarWidget) { // just ignore additional adds for already existing widgets if (hasBarWidget(newBarWidget)) { return; } // add new widget, invisible... newBarWidget->hide(); m_stack->addWidget(newBarWidget); newBarWidget->setAssociatedViewBar(this); connect(newBarWidget, SIGNAL(hideMe()), SLOT(hideCurrentBarWidget())); } void KateViewBar::removeBarWidget(KateViewBarWidget *barWidget) { // remove only if there if (!hasBarWidget(barWidget)) { return; } m_stack->removeWidget(barWidget); barWidget->setAssociatedViewBar(nullptr); barWidget->hide(); disconnect(barWidget, nullptr, this, nullptr); } void KateViewBar::addPermanentBarWidget(KateViewBarWidget *barWidget) { Q_ASSERT(barWidget); Q_ASSERT(!m_permanentBarWidget); m_stack->addWidget(barWidget); m_stack->setCurrentWidget(barWidget); m_stack->show(); m_permanentBarWidget = barWidget; m_permanentBarWidget->show(); setViewBarVisible(true); } void KateViewBar::removePermanentBarWidget(KateViewBarWidget *barWidget) { Q_ASSERT(m_permanentBarWidget == barWidget); Q_UNUSED(barWidget); const bool hideBar = m_stack->currentWidget() == m_permanentBarWidget; m_permanentBarWidget->hide(); m_stack->removeWidget(m_permanentBarWidget); m_permanentBarWidget = nullptr; if (hideBar) { m_stack->hide(); setViewBarVisible(false); } } bool KateViewBar::hasPermanentWidget(KateViewBarWidget *barWidget) const { return (m_permanentBarWidget == barWidget); } void KateViewBar::showBarWidget(KateViewBarWidget *barWidget) { Q_ASSERT(barWidget != nullptr); if (barWidget != qobject_cast(m_stack->currentWidget())) { hideCurrentBarWidget(); } // raise correct widget m_stack->setCurrentWidget(barWidget); barWidget->show(); barWidget->setFocus(Qt::ShortcutFocusReason); m_stack->show(); setViewBarVisible(true); } bool KateViewBar::hasBarWidget(KateViewBarWidget *barWidget) const { return m_stack->indexOf(barWidget) != -1; } void KateViewBar::hideCurrentBarWidget() { KateViewBarWidget *current = qobject_cast(m_stack->currentWidget()); if (current) { current->closed(); } // if we have any permanent widget, make it visible again if (m_permanentBarWidget) { m_stack->setCurrentWidget (m_permanentBarWidget); } else { // else: hide the bar m_stack->hide(); setViewBarVisible(false); } m_view->setFocus(); } void KateViewBar::setViewBarVisible(bool visible) { if (m_external) { if (visible) { m_view->mainWindow()->showViewBar(m_view); } else { m_view->mainWindow()->hideViewBar(m_view); } } else { setVisible(visible); } } bool KateViewBar::hiddenOrPermanent() const { KateViewBarWidget *current = qobject_cast(m_stack->currentWidget()); if (!isVisible() || (m_permanentBarWidget && m_permanentBarWidget == current)) { return true; } return false; } void KateViewBar::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape) { hideCurrentBarWidget(); return; } QWidget::keyPressEvent(event); } void KateViewBar::hideEvent(QHideEvent *event) { Q_UNUSED(event); // if (!event->spontaneous()) // m_view->setFocus(); } //END KateViewBar related classes KatePasteMenu::KatePasteMenu(const QString &text, KTextEditor::ViewPrivate *view) : KActionMenu(text, view) , m_view(view) { connect(menu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShow())); } void KatePasteMenu::slotAboutToShow() { menu()->clear(); /** * insert complete paste history */ int i = 0; Q_FOREACH (const QString &text, KTextEditor::EditorPrivate::self()->clipboardHistory()) { /** * get text for the menu ;) */ QString leftPart = (text.size() > 48) ? (text.left(48) + QLatin1String("...")) : text; QAction *a = menu()->addAction(leftPart.replace(QLatin1String("\n"), QLatin1String(" ")), this, SLOT(paste())); a->setData(i++); } } void KatePasteMenu::paste() { if (!sender()) { return; } QAction *action = qobject_cast(sender()); if (!action) { return; } // get index int i = action->data().toInt(); if (i >= KTextEditor::EditorPrivate::self()->clipboardHistory().size()) { return; } // paste m_view->paste(&KTextEditor::EditorPrivate::self()->clipboardHistory()[i]); } diff --git a/src/vimode/config/configtab.cpp b/src/vimode/config/configtab.cpp index a792e81a..385298ae 100644 --- a/src/vimode/config/configtab.cpp +++ b/src/vimode/config/configtab.cpp @@ -1,250 +1,250 @@ /* This file is part of the KDE libraries and the Kate part. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include "ui_configwidget.h" using namespace KateVi; ConfigTab::ConfigTab(QWidget *parent, Mappings *mappings) : KateConfigPage(parent) , m_mappings(mappings) { // This will let us have more separation between this page and // the QTabWidget edge (ereslibre) QVBoxLayout *layout = new QVBoxLayout; QWidget *newWidget = new QWidget(this); ui = new Ui::ConfigWidget(); ui->setupUi(newWidget); // Make the header take all the width in equal parts. ui->tblNormalModeMappings->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); ui->tblInsertModeMappings->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); ui->tblVisualModeMappings->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); // What's This? help can be found in the ui file reload(); // // after initial reload, connect the stuff for the changed() signal // connect(ui->chkViCommandsOverride, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->chkViRelLineNumbers, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->tblNormalModeMappings, SIGNAL(cellChanged(int,int)), this, SLOT(slotChanged())); connect(ui->btnAddNewRow, SIGNAL(clicked()), this, SLOT(addMappingRow())); connect(ui->btnAddNewRow, SIGNAL(clicked()), this, SLOT(slotChanged())); connect(ui->btnRemoveSelectedRows, SIGNAL(clicked()), this, SLOT(removeSelectedMappingRows())); connect(ui->btnRemoveSelectedRows, SIGNAL(clicked()), this, SLOT(slotChanged())); connect(ui->btnImportNormal, SIGNAL(clicked()), this, SLOT(importNormalMappingRow())); connect(ui->btnImportNormal, SIGNAL(clicked()), this, SLOT(slotChanged())); layout->addWidget(newWidget); setLayout(layout); } ConfigTab::~ConfigTab() { delete ui; } void ConfigTab::applyTab(QTableWidget *mappingsTable, Mappings::MappingMode mode) { m_mappings->clear(mode); for (int i = 0; i < mappingsTable->rowCount(); i++) { QTableWidgetItem *from = mappingsTable->item(i, 0); QTableWidgetItem *to = mappingsTable->item(i, 1); QTableWidgetItem *recursive = mappingsTable->item(i, 2); if (from && to && recursive) { const Mappings::MappingRecursion recursion = recursive->checkState() == Qt::Checked ? Mappings::Recursive : Mappings::NonRecursive; m_mappings->add(mode, from->text(), to->text(), recursion); } } } void ConfigTab::reloadTab(QTableWidget *mappingsTable, Mappings::MappingMode mode) { const QStringList &l = m_mappings->getAll(mode); mappingsTable->setRowCount(l.size()); int i = 0; foreach (const QString &f, l) { QTableWidgetItem *from = new QTableWidgetItem(KeyParser::self()->decodeKeySequence(f)); QString s = m_mappings->get(mode, f); QTableWidgetItem *to = new QTableWidgetItem(KeyParser::self()->decodeKeySequence(s)); QTableWidgetItem *recursive = new QTableWidgetItem(); recursive->setFlags(Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsSelectable); const bool isRecursive = m_mappings->isRecursive(mode, f); recursive->setCheckState(isRecursive ? Qt::Checked : Qt::Unchecked); mappingsTable->setItem(i, 0, from); mappingsTable->setItem(i, 1, to); mappingsTable->setItem(i, 2, recursive); i++; } } void ConfigTab::apply() { // nothing changed, no need to apply stuff if (!hasChanged()) { return; } m_changed = false; KateViewConfig::global()->configStart(); // General options. - KateViewConfig::global()->setViRelativeLineNumbers(ui->chkViRelLineNumbers->isChecked()); - KateViewConfig::global()->setViInputModeStealKeys(ui->chkViCommandsOverride->isChecked()); + KateViewConfig::global()->setValue(KateViewConfig::ViRelativeLineNumbers, ui->chkViRelLineNumbers->isChecked()); + KateViewConfig::global()->setValue(KateViewConfig::ViInputModeStealKeys, ui->chkViCommandsOverride->isChecked()); // Mappings. applyTab(ui->tblNormalModeMappings, Mappings::NormalModeMapping); applyTab(ui->tblInsertModeMappings, Mappings::InsertModeMapping); applyTab(ui->tblVisualModeMappings, Mappings::VisualModeMapping); KateViewConfig::global()->configEnd(); } void ConfigTab::reload() { // General options. ui->chkViRelLineNumbers->setChecked( KateViewConfig::global()->viRelativeLineNumbers () ); ui->chkViCommandsOverride->setChecked(KateViewConfig::global()->viInputModeStealKeys()); // Mappings. reloadTab(ui->tblNormalModeMappings, Mappings::NormalModeMapping); reloadTab(ui->tblInsertModeMappings, Mappings::InsertModeMapping); reloadTab(ui->tblVisualModeMappings, Mappings::VisualModeMapping); } void ConfigTab::reset() { /* Do nothing. */ } void ConfigTab::defaults() { /* Do nothing. */ } void ConfigTab::showWhatsThis(const QString &text) { QWhatsThis::showText(QCursor::pos(), text); } void ConfigTab::addMappingRow() { // Pick the current widget. QTableWidget *mappingsTable = ui->tblNormalModeMappings; if (ui->tabMappingModes->currentIndex() == 1) { mappingsTable = ui->tblInsertModeMappings; } else if (ui->tabMappingModes->currentIndex() == 2) { mappingsTable = ui->tblVisualModeMappings; } // And add a new row. int rows = mappingsTable->rowCount(); mappingsTable->insertRow(rows); QTableWidgetItem *recursive = new QTableWidgetItem(); recursive->setFlags(Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsSelectable); recursive->setCheckState(Qt::Unchecked); mappingsTable->setItem(rows, 2, recursive); mappingsTable->setCurrentCell(rows, 0); mappingsTable->editItem(mappingsTable->currentItem()); } void ConfigTab::removeSelectedMappingRows() { // Pick the current widget. QTableWidget *mappingsTable = ui->tblNormalModeMappings; if (ui->tabMappingModes->currentIndex() == 1) { mappingsTable = ui->tblInsertModeMappings; } else if (ui->tabMappingModes->currentIndex() == 2) { mappingsTable = ui->tblVisualModeMappings; } // And remove the selected rows. QList l = mappingsTable->selectedRanges(); foreach (const QTableWidgetSelectionRange &range, l) { for (int i = 0; i < range.bottomRow() - range.topRow() + 1; i++) { mappingsTable->removeRow(range.topRow()); } } } void ConfigTab::importNormalMappingRow() { const QString &fileName = QFileDialog::getOpenFileName(this); if (fileName.isEmpty()) { return; } QFile configFile(fileName); if (! configFile.open(QIODevice::ReadOnly | QIODevice::Text)) { KMessageBox::error(this, i18n("Unable to open the config file for reading."), i18n("Unable to open file")); return; } QTextStream stream(&configFile); const QRegularExpression mapleader(QStringLiteral("(\\w:)?mapleader")); while (! stream.atEnd()) { QStringList line = stream.readLine().split(QLatin1Char(' ')); // TODO - allow recursive mappings to be read. if (line.size() > 2 && (line[0] == QLatin1String("noremap") || line[0] == QLatin1String("no") || line[0] == QLatin1String("nnoremap") || line [0] == QLatin1String("nn"))) { int rows = ui->tblNormalModeMappings->rowCount(); ui->tblNormalModeMappings->insertRow(rows); ui->tblNormalModeMappings->setItem(rows, 0, new QTableWidgetItem(line[1])); ui->tblNormalModeMappings->setItem(rows, 1, new QTableWidgetItem(line[2])); QTableWidgetItem *recursive = new QTableWidgetItem(); recursive->setFlags(Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsSelectable); recursive->setCheckState(Qt::Unchecked); ui->tblNormalModeMappings->setItem(rows, 2, recursive); } else if (line.size() == 4 && line[0] == QLatin1String("let") && line[2] == QLatin1String("=") && mapleader.match(line[1]).hasMatch()) { const QString &leader = line[3].mid(1, line[3].length() - 2); if (!leader.isEmpty()) { m_mappings->setLeader(leader[0]); } } } } QString ConfigTab::name() const { return i18n("Vi Input Mode"); }