diff --git a/autotests/src/katedocument_test.cpp b/autotests/src/katedocument_test.cpp index f98254da..1b893304 100644 --- a/autotests/src/katedocument_test.cpp +++ b/autotests/src/katedocument_test.cpp @@ -1,462 +1,463 @@ /* This file is part of the KDE libraries Copyright (C) 2010 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::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::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::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); 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(("क्ति"))); } #include "katedocument_test.moc" diff --git a/src/dialogs/katedialogs.cpp b/src/dialogs/katedialogs.cpp index 28468627..2f718dc9 100644 --- a/src/dialogs/katedialogs.cpp +++ b/src/dialogs/katedialogs.cpp @@ -1,1462 +1,1466 @@ /* 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" #include "kateglobal.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 // trailing slash is important #define HLDOWNLOADPATH QStringLiteral("http://kate.kde.org/syntax/") //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()->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; KateDocumentConfig::global()->configStart(); m_sonnetConfigWidget->save(); 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()); KateViewConfig::global()->setPersistentSelection(ui->cbTextSelectionMode->currentIndex() == 1); KateViewConfig::global()->setScrollPastEnd(ui->chkScrollPastEnd->isChecked()); + KateViewConfig::global()->setBackspaceRemoveComposed(ui->chkBackspaceRemoveComposed->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->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()->setInputModeRaw(ui->cmbInputMode->currentData().toInt()); 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()); 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->setMargin(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->setMargin(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->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->chkShowSpaces, SIGNAL(toggled(bool)), this, SLOT(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(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()->setDynWordWrapIndicators(textareaUi->cmbDynamicWordWrapIndicator->currentIndex()); KateViewConfig::global()->setDynWordWrapAlignIndent(textareaUi->sbDynamicWordWrapDepth->value()); KateDocumentConfig::global()->setShowTabs(textareaUi->chkShowTabs->isChecked()); KateDocumentConfig::global()->setShowSpaces(textareaUi->chkShowSpaces->isChecked()); 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); KateRendererConfig::global()->setShowIndentationLines(textareaUi->chkShowIndentationLines->isChecked()); KateRendererConfig::global()->setShowWholeBracketExpression(textareaUi->chkShowWholeBracketExpression->isChecked()); KateRendererConfig::global()->setAnimateBracketMatching(textareaUi->chkAnimateBracketMatching->isChecked()); KateViewConfig::global()->setFoldFirstLine(textareaUi->chkFoldFirstLine->isChecked()); KateRendererConfig::global()->configEnd(); KateViewConfig::global()->configEnd(); } void KateViewDefaultsConfig::reload() { textareaUi->gbWordWrap->setChecked(KateViewConfig::global()->dynWordWrap()); textareaUi->cmbDynamicWordWrapIndicator->setCurrentIndex(KateViewConfig::global()->dynWordWrapIndicators()); textareaUi->sbDynamicWordWrapDepth->setValue(KateViewConfig::global()->dynWordWrapAlignIndent()); textareaUi->chkShowTabs->setChecked(KateDocumentConfig::global()->showTabs()); textareaUi->chkShowSpaces->setChecked(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()); } 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->setMargin(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("~")); } uint f(0); if (uiadv->chkBackupLocalFiles->isChecked()) { f |= KateDocumentConfig::LocalFiles; } if (uiadv->chkBackupRemoteFiles->isChecked()) { f |= KateDocumentConfig::RemoteFiles; } KateDocumentConfig::global()->setBackupFlags(f); 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 uint f(KateDocumentConfig::global()->backupFlags()); uiadv->chkBackupLocalFiles->setChecked(f & KateDocumentConfig::LocalFiles); uiadv->chkBackupRemoteFiles->setChecked(f & KateDocumentConfig::RemoteFiles); uiadv->edtBackupPrefix->setText(KateDocumentConfig::global()->backupPrefix()); uiadv->edtBackupSuffix->setText(KateDocumentConfig::global()->backupSuffix()); uiadv->cmbSwapFileMode->setCurrentIndex(KateDocumentConfig::global()->swapFileModeRaw()); 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 KateHlDownloadDialog KateHlDownloadDialog::KateHlDownloadDialog(QWidget *parent, const char *name, bool modal) : QDialog(parent) { setWindowTitle(i18n("Highlight Download")); setObjectName(QString::fromUtf8(name)); setModal(modal); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); QLabel *label = new QLabel(i18n("Select the syntax highlighting files you want to update:"), this); mainLayout->addWidget(label); list = new QTreeWidget(this); list->setColumnCount(4); list->setHeaderLabels({ QString(), i18n("Name"), i18n("Installed"), i18n("Latest") }); list->setSelectionMode(QAbstractItemView::MultiSelection); list->setAllColumnsShowFocus(true); list->setRootIsDecorated(false); list->setColumnWidth(0, 22); mainLayout->addWidget(list); label = new QLabel(i18n("Note: New versions are selected automatically."), this); mainLayout->addWidget(label); // buttons QDialogButtonBox *buttons = new QDialogButtonBox(this); mainLayout->addWidget(buttons); m_installButton = new QPushButton(QIcon::fromTheme(QStringLiteral("dialog-ok")), i18n("&Install")); m_installButton->setDefault(true); buttons->addButton(m_installButton, QDialogButtonBox::AcceptRole); connect(m_installButton, SIGNAL(clicked()), this, SLOT(slotInstall())); QPushButton *closeButton = new QPushButton; KGuiItem::assign(closeButton, KStandardGuiItem::cancel()); buttons->addButton(closeButton, QDialogButtonBox::RejectRole); connect(closeButton, SIGNAL(clicked()), this, SLOT(reject())); transferJob = KIO::get(QUrl(QStringLiteral("%1update-%2.%3.xml").arg(HLDOWNLOADPATH).arg(KTEXTEDITOR_VERSION_MAJOR).arg(KTEXTEDITOR_VERSION_MINOR)), KIO::Reload); connect(transferJob, SIGNAL(data(KIO::Job*,QByteArray)), this, SLOT(listDataReceived(KIO::Job*,QByteArray))); // void data( KIO::Job *, const QByteArray &data); resize(450, 400); } KateHlDownloadDialog::~KateHlDownloadDialog() {} /// Split typical version string (\c major.minor.patch) into /// numeric components, convert 'em to \c unsigned and form a /// single value that can be compared w/ other versions /// using relation operators. /// \note It takes into account only first 3 numbers unsigned KateHlDownloadDialog::parseVersion(const QString &version_string) { unsigned vn[3] = {0, 0, 0}; unsigned idx = 0; foreach (const QString &n, version_string.split(QLatin1Char('.'))) { vn[idx++] = n.toUInt(); if (idx == sizeof(vn)) { break; } } return (((vn[0]) << 16) | ((vn[1]) << 8) | (vn[2])); } void KateHlDownloadDialog::listDataReceived(KIO::Job *, const QByteArray &data) { if (!transferJob || transferJob->isErrorPage()) { m_installButton->setEnabled(false); if (data.size() == 0) { KMessageBox::error(this, i18n("The list of highlightings could not be found on / retrieved from the server")); } return; } listData += QLatin1String(data); qCDebug(LOG_KTE) << QStringLiteral("CurrentListData: ") << listData; qCDebug(LOG_KTE) << QStringLiteral("Data length: %1").arg(data.size()); qCDebug(LOG_KTE) << QStringLiteral("listData length: %1").arg(listData.length()); if (data.size() == 0) { if (listData.length() > 0) { QString installedVersion; KateHlManager *hlm = KateHlManager::self(); QDomDocument doc; doc.setContent(listData); QDomElement DocElem = doc.documentElement(); QDomNode n = DocElem.firstChild(); KateHighlighting *hl = nullptr; if (n.isNull()) { qCDebug(LOG_KTE) << QStringLiteral("There is no usable childnode"); } while (!n.isNull()) { installedVersion = QStringLiteral(" --"); QDomElement e = n.toElement(); if (!e.isNull()) { qCDebug(LOG_KTE) << QStringLiteral("NAME: ") << e.tagName() << QStringLiteral(" - ") << e.attribute(QStringLiteral("name")); } n = n.nextSibling(); QString Name = e.attribute(QStringLiteral("name")); for (int i = 0; i < hlm->highlights(); i++) { hl = hlm->getHl(i); if (hl && hl->name() == Name) { installedVersion = QLatin1String(" ") + hl->version(); break; } else { hl = nullptr; } } // autoselect entry if new or updated. QTreeWidgetItem *entry = new QTreeWidgetItem(list); entry->setText(0, QString()); entry->setText(1, e.attribute(QStringLiteral("name"))); entry->setText(2, installedVersion); entry->setText(3, e.attribute(QStringLiteral("version"))); entry->setText(4, e.attribute(QStringLiteral("url"))); bool is_fresh = false; if (hl) { unsigned prev_version = parseVersion(hl->version()); unsigned next_version = parseVersion(e.attribute(QStringLiteral("version"))); is_fresh = prev_version < next_version; } else { is_fresh = true; } if (is_fresh) { entry->treeWidget()->setItemSelected(entry, true); entry->setIcon(0, QIcon::fromTheme((QStringLiteral("get-hot-new-stuff")))); } } list->resizeColumnToContents(1); list->sortItems(1, Qt::AscendingOrder); } } } void KateHlDownloadDialog::slotInstall() { const QString destdir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/org.kde.syntax-highlighting/syntax/"); QDir(destdir).mkpath(QStringLiteral(".")); // make sure the dir is there foreach (QTreeWidgetItem *it, list->selectedItems()) { QUrl src(it->text(4)); QString filename = src.fileName(); // if there is no fileName construct at least something if (filename.isEmpty()) { filename = src.path().replace(QLatin1Char('/'), QLatin1Char('_')); } QUrl dest = QUrl::fromLocalFile(destdir + filename); KIO::FileCopyJob *job = KIO::file_copy(src, dest); KJobWidgets::setWindow(job, this); job->exec(); } } //END KateHlDownloadDialog //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->setMargin(0); gotoRange = new QSpinBox(centralWidget()); QLabel *label = new QLabel(i18n("&Go to line:"), centralWidget()); label->setBuddy(gotoRange); QToolButton *btnOK = new QToolButton(centralWidget()); btnOK->setAutoRaise(true); btnOK->setIcon(QIcon::fromTheme(QStringLiteral("go-jump"))); btnOK->setText(i18n("Go")); btnOK->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); connect(btnOK, SIGNAL(clicked()), this, SLOT(gotoLine())); topLayout->addWidget(label); topLayout->addWidget(gotoRange, 1); topLayout->setStretchFactor(gotoRange, 0); topLayout->addWidget(btnOK); topLayout->addStretch(); setFocusProxy(gotoRange); } void KateGotoBar::updateData() { gotoRange->setMaximum(m_view->document()->lines()); if (!isVisible()) { gotoRange->setValue(m_view->cursorPosition().line() + 1); gotoRange->adjustSize(); // ### does not respect the range :-( } gotoRange->setFocus(Qt::OtherFocusReason); 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(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->setMargin(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) { KTextEditor::Range selection = m_view->selectionRange(); if (selection.isValid() && !selection.isEmpty()) { m_view->doc()->setDictionary(dictionary, selection); } 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) { 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 * 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/dialogs/navigationconfigwidget.ui b/src/dialogs/navigationconfigwidget.ui index c17ce7e6..35be4595 100644 --- a/src/dialogs/navigationconfigwidget.ui +++ b/src/dialogs/navigationconfigwidget.ui @@ -1,155 +1,165 @@ NavigationConfigWidget 0 Text Cursor Movement When selected, pressing the home key will cause the cursor to skip whitespace and go to the start of a line's text. The same applies for the end key. Smart ho&me and smart end Selects whether the PageUp and PageDown keys should alter the vertical position of the cursor relative to the top of the view. &PageUp/PageDown moves cursor 0 &Autocenter cursor: sbAutoCenterCursor Sets the number of lines to maintain visible above and below the cursor when possible. Disabled lines Qt::Horizontal 1 0 Misc Text selection mode: cbTextSelectionMode Normal Persistent Qt::Horizontal 40 20 Allow scrolling past the end of the document + + + + When selected, composed characters are removed with their diacritics instead of only removing the base character. This is useful for Indic locales. + + + Backspace key removes character’s base with its diacritics + + + Qt::Vertical 0 1 diff --git a/src/document/katedocument.cpp b/src/document/katedocument.cpp index c0b1e88b..ad964edf 100644 --- a/src/document/katedocument.cpp +++ b/src/document/katedocument.cpp @@ -1,6007 +1,6008 @@ /* 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 "katehighlighthelpers.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 #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 static inline QChar matchingStartBracket(QChar c, bool withQuotes) { 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 matchingEndBracket(QChar c, bool withQuotes) { 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(QChar c, bool withQuotes) { QChar bracket = matchingStartBracket(c, withQuotes); if (bracket.isNull()) { bracket = matchingEndBracket(c, false); } return bracket; } static inline bool isStartBracket(const QChar &c) { return ! matchingEndBracket(c, false).isNull(); } static inline bool isEndBracket(const QChar &c) { return ! matchingStartBracket(c, false).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_activeView(nullptr), editSessionNumber(0), editIsRunning(false), m_undoMergeAllEdits(false), m_undoManager(new KateUndoManager(this)), m_editableMarks(markType01), m_annotationModel(nullptr), m_buffer(new KateBuffer(this)), m_indenter(new KateAutoIndent(this)), m_hlSetByUser(false), m_bomSetByUser(false), m_indenterSetByUser(false), m_userSetEncodingForNextReload(false), m_modOnHd(false), m_modOnHdReason(OnDiskUnmodified), m_prevModOnHdReason(OnDiskUnmodified), m_docName(QStringLiteral("need init")), m_docNameNumber(0), m_fileType(QStringLiteral("Normal")), m_fileTypeSetByUser(false), m_reloading(false), m_config(new KateDocumentConfig(this)), m_fileChangedDialogsActivated(false), m_onTheFlyChecker(nullptr), m_documentState(DocumentIdle), m_readWriteStateBeforeLoading(false), m_isUntitled(true), m_openingError(false) { /** * 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())); /** * 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, &KTextEditor::DocumentPrivate::textRemoved, this, &KTextEditor::DocumentPrivate::saveEditingPositions); connect(this, &KTextEditor::DocumentPrivate::textInserted, this, &KTextEditor::DocumentPrivate::saveEditingPositions); 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(KTextEditor::Document *, const KTextEditor::Range &range) { if (m_editingStackPosition != m_editingStack.size() - 1) { m_editingStack.resize(m_editingStackPosition); } KTextEditor::MovingInterface *moving = qobject_cast(this); const auto c = range.start(); QSharedPointer mc (moving->newMovingCursor(c)); if (!m_editingStack.isEmpty() && c.line() == m_editingStack.top()->line()) { m_editingStack.pop(); } m_editingStack.push(mc); if (m_editingStack.size() > s_editingStackSizeLimit) { m_editingStack.removeFirst(); } 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++; } } const int tabWidth = config()->tabWidth(); static const QChar newLineChar(QLatin1Char('\n')); int insertColumnExpanded = insertColumn; Kate::TextLine l = plainKateTextLine(currentLine); if (l) { insertColumnExpanded = l->toVirtualColumn(insertColumn, tabWidth); } int positionColumnExpanded = insertColumnExpanded; int pos = 0; for (; pos < totalLength; pos++) { const QChar &ch = text.at(pos); if (ch == newLineChar) { // 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++; l = plainKateTextLine(currentLine); if (block) { if (currentLine == lastLine() + 1) { editInsertLine(currentLine, QString()); } insertColumn = positionColumnExpanded; if (l) { insertColumn = l->fromVirtualColumn(insertColumn, tabWidth); } } currentLineStart = pos + 1; if (l) { insertColumnExpanded = l->toVirtualColumn(insertColumn, tabWidth); } } } // 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; 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); } 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::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); // insert text into line m_buffer->insertText(KTextEditor::Cursor(line, col2), 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); // remove text from line m_buffer->removeText(KTextEditor::Range(KTextEditor::Cursor(line, col), 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; } } 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); } 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)); } 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())); } } 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 (int i = 0; i < KateHlManager::self()->highlights(); ++i) { hls << KateHlManager::self()->hlName(i); } return hls; } QString KTextEditor::DocumentPrivate::highlightingModeSection(int index) const { return KateHlManager::self()->hlSection(index); } 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.\n\nCheck that you have write access to this file or that enough disk space is available.", 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()->backupFlags() & KateDocumentConfig::LocalFiles); const bool backupRemoteFiles = (config()->backupFlags() & KateDocumentConfig::RemoteFiles); /** * 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)); // 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); 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; } /** * auto bracket handling for newly inserted text * remember if we should auto add some */ QChar closingBracket; if (view->config()->autoBrackets() && chars.size() == 1) { /** * we inserted a bracket? * => remember the matching closing one */ closingBracket = matchingEndBracket(chars[0], true); /** * closing bracket for the autobracket we inserted earlier? */ if ( m_currentAutobraceClosingChar == chars[0] && m_currentAutobraceRange ) { // do nothing m_currentAutobraceRange.reset(nullptr); view->cursorRight(); return true; } } /** * selection around => special handling if we want to add auto brackets */ if (view->selection() && !closingBracket.isNull()) { /** * add bracket at start + end of the selection */ KTextEditor::Cursor oldCur = view->cursorPosition(); insertText(view->selectionRange().start(), chars); view->slotTextInserted(view, oldCur, chars); view->setCursorPosition(view->selectionRange().end()); oldCur = view->cursorPosition(); insertText(view->selectionRange().end(), QString(closingBracket)); view->slotTextInserted(view, oldCur, QString(closingBracket)); /** * expand selection */ view->setSelection(KTextEditor::Range(view->selectionRange().start() + Cursor{0, 1}, view->cursorPosition() - Cursor{0, 1})); view->setCursorPosition(view->selectionRange().start()); } /** * else normal handling */ else { editStart(); 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); } } 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 { chars = eventuallyReplaceTabs(view->cursorPosition(), chars); 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 ) { auto context = highlight()->contextForLocation(this, view->cursorPosition() - Cursor{0, 1}); // skip adding ' in spellchecked areas, because those are text skipAutobrace = !context || highlight()->attributeRequiresSpellchecking(context->attr); } if (!closingBracket.isNull() && !skipAutobrace ) { // add bracket to the view const auto cursorPos(view->cursorPosition()); const auto nextChar = view->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); } /** * be done */ 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() > (int)lastLine()) { c.setLine(lastLine()); } if (c.line() < 0) { c.setLine(0); } uint ln = c.line(); Kate::TextLine textLine = plainKateTextLine(ln); if (c.column() > (int)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) { - if (!(config()->backspaceIndents())) { - // ordinary backspace - KTextEditor::Cursor beginCursor(line, view->textLayout(c)->previousCursorPosition(c.column())); - KTextEditor::Cursor endCursor(line, col); - - 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 { + 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); + } + } + if (!config()->backspaceIndents() || pos) { + KTextEditor::Cursor beginCursor(line, 0); + KTextEditor::Cursor endCursor(line, col); + if (!view->config()->backspaceRemoveComposed()) { // Normal backspace behavior + // move to left of surrogate pair + if (!isValidTextPosition(beginCursor)) { + Q_ASSERT(col >= 2); + beginCursor.setColumn(col - 2); + } + beginCursor.setColumn(col - 1); } else { - KTextEditor::Cursor beginCursor(line, view->textLayout(c)->previousCursorPosition(c.column())); - KTextEditor::Cursor endCursor(line, col); - - 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); + 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) == KateHighlighting::CSLPosColumn0) { 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->contextStack().isEmpty()) { startAttrib = highlight()->attribute(ln->contextStack().last()); } 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, false); 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 (!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::reloadTriggered, this, &DocumentPrivate::onModOnHdReload); 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::onModOnHdReload() { m_modOnHd = false; m_prevModOnHdReason = OnDiskUnmodified; emit modifiedOnDisk(this, false, OnDiskUnmodified); documentReload(); delete m_modOnHdHandler; } 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; if (m_modOnHd && m_fileChangedDialogsActivated) { QWidget *parentWidget(dialogParent()); int i = KMessageBox::warningYesNoCancel (parentWidget, reasonedMOHString() + QLatin1String("\n\n") + i18n("What do you want to do?"), i18n("File Was Changed on Disk"), KGuiItem(i18n("&Reload File"), QStringLiteral("view-refresh")), KGuiItem(i18n("&Ignore Changes"), QStringLiteral("dialog-warning"))); if (i != KMessageBox::Yes) { if (i == KMessageBox::No) { m_modOnHd = false; m_modOnHdReason = OnDiskUnmodified; m_prevModOnHdReason = OnDiskUnmodified; emit modifiedOnDisk(this, m_modOnHd, m_modOnHdReason); } // reset some flags only valid for one reload! m_userSetEncodingForNextReload = false; return false; } } 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 < (int)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); } // 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 QStringList vvl { QStringLiteral("dynamic-word-wrap") , QStringLiteral("dynamic-word-wrap-indicators") , QStringLiteral("line-numbers") , QStringLiteral("icon-border") , QStringLiteral("folding-markers") , QStringLiteral("folding-preview") , QStringLiteral("bookmark-sorting") , QStringLiteral("auto-center-lines") , QStringLiteral("icon-bar-color") , QStringLiteral("scrollbar-minimap") , QStringLiteral("scrollbar-preview") // renderer , QStringLiteral("background-color") , QStringLiteral("selection-color") , QStringLiteral("current-line-color") , QStringLiteral("bracket-highlight-color") , QStringLiteral("word-wrap-marker-color") , QStringLiteral("font") , QStringLiteral("font-size") , QStringLiteral("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 (vvl.contains(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 " "http://docs.kde.org/stable/en/applications/kate/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 " "http://docs.kde.org/stable/en/applications/kate/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); } 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 QStringList l{ QStringLiteral("unix"), QStringLiteral("dos"), QStringLiteral("mac") }; if ((n = l.indexOf(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 (vvl.contains(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); } 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 QStringList trueValues{ QStringLiteral("1"), QStringLiteral("on"), QStringLiteral("true") }; if (trueValues.contains(val)) { *result = true; return true; } static const QStringList falseValues{ QStringLiteral("0"), QStringLiteral("off"), QStringLiteral("false") }; if (falseValues.contains(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 { static const QStringList keys = { QStringLiteral("backup-on-save-local"), QStringLiteral("backup-on-save-suffix"), QStringLiteral("backup-on-save-prefix"), QStringLiteral("replace-tabs"), QStringLiteral("indent-pasted-text"), QStringLiteral("tab-width"), QStringLiteral("indent-width"), QStringLiteral("on-the-fly-spellcheck"), }; return keys; } QVariant KTextEditor::DocumentPrivate::configValue(const QString &key) { if (key == QLatin1String("backup-on-save-local")) { return m_config->backupFlags() & KateDocumentConfig::LocalFiles; } else if (key == QLatin1String("backup-on-save-remote")) { return m_config->backupFlags() & KateDocumentConfig::RemoteFiles; } else if (key == QLatin1String("backup-on-save-suffix")) { return m_config->backupSuffix(); } else if (key == QLatin1String("backup-on-save-prefix")) { return m_config->backupPrefix(); } else if (key == QLatin1String("replace-tabs")) { return m_config->replaceTabsDyn(); } else if (key == QLatin1String("indent-pasted-text")) { return m_config->indentPastedText(); } else if (key == QLatin1String("tab-width")) { return m_config->tabWidth(); } else if (key == QLatin1String("indent-width")) { return m_config->indentationWidth(); } else if (key == QLatin1String("on-the-fly-spellcheck")) { return isOnTheFlySpellCheckingEnabled(); } // return invalid variant return QVariant(); } void KTextEditor::DocumentPrivate::setConfigValue(const QString &key, const QVariant &value) { if (value.type() == QVariant::String) { if (key == QLatin1String("backup-on-save-suffix")) { m_config->setBackupSuffix(value.toString()); } else if (key == QLatin1String("backup-on-save-prefix")) { m_config->setBackupPrefix(value.toString()); } } else if (value.type() == QVariant::Bool) { const bool bValue = value.toBool(); if (key == QLatin1String("backup-on-save-local")) { uint f = m_config->backupFlags(); if (bValue) { f |= KateDocumentConfig::LocalFiles; } else { f ^= KateDocumentConfig::LocalFiles; } m_config->setBackupFlags(f); } else if (key == QLatin1String("backup-on-save-remote")) { uint f = m_config->backupFlags(); if (bValue) { f |= KateDocumentConfig::RemoteFiles; } else { f ^= KateDocumentConfig::RemoteFiles; } m_config->setBackupFlags(f); } else if (key == QLatin1String("replace-tabs")) { m_config->setReplaceTabsDyn(bValue); } else if (key == QLatin1String("indent-pasted-text")) { m_config->setIndentPastedText(bValue); } else if (key == QLatin1String("on-the-fly-spellcheck")) { onTheFlySpellCheckingEnabled(bValue); } } else if (value.canConvert(QVariant::Int)) { if (key == QLatin1String("tab-width")) { config()->setTabWidth(value.toInt()); } else if (key == QLatin1String("indent-width")) { config()->setIndentationWidth(value.toInt()); } } } //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) { 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); } emit dictionaryRangesPresent(!m_dictionaryRanges.isEmpty()); } void KTextEditor::DocumentPrivate::revertToDefaultDictionary(const KTextEditor::Range &range) { setDictionary(QString(), range); } 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) { Kate::TextLine kateLine = kateTextLine(position.line()); // const QVector< short >& attrs = kateLine->ctxArray(); // qCDebug(LOG_KTE) << "----------------------------------------------------------------------"; // foreach( short a, attrs ) { // qCDebug(LOG_KTE) << a; // } // qCDebug(LOG_KTE) << "----------------------------------------------------------------------"; // qCDebug(LOG_KTE) << "col: " << position.column() << " lastchar:" << kateLine->lastChar() << " length:" << kateLine->length() << "global mode:" << highlightingMode(); int len = kateLine->length(); int pos = position.column(); if (pos >= len) { const Kate::TextLineData::ContextStack &ctxs = kateLine->contextStack(); int ctxcnt = ctxs.count(); if (ctxcnt == 0) { return highlightingMode(); } int ctx = ctxs.at(ctxcnt - 1); if (ctx == 0) { return highlightingMode(); } return KateHlManager::self()->nameForIdentifier(highlight()->hlKeyForContext(ctx)); } int attr = kateLine->attribute(pos); if (attr == 0) { return mode(); } return KateHlManager::self()->nameForIdentifier(highlight()->hlKeyForAttrib(attr)); } 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()) { KateHlContext *context = tl->contextStack().isEmpty() ? highlight()->contextNum(0) : highlight()->contextNum(tl->contextStack().back()); attribute = context->attr; } 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/utils/kateconfig.cpp b/src/utils/kateconfig.cpp index 5f983f9a..f488f8d1 100644 --- a/src/utils/kateconfig.cpp +++ b/src/utils/kateconfig.cpp @@ -1,3076 +1,3106 @@ /* 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() : configSessionNumber(0), configIsRunning(false) { } KateConfig::~KateConfig() { } 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(); } //END //BEGIN KateDocumentConfig KateGlobalConfig *KateGlobalConfig::s_global = nullptr; KateDocumentConfig *KateDocumentConfig::s_global = nullptr; KateViewConfig *KateViewConfig::s_global = nullptr; KateRendererConfig *KateRendererConfig::s_global = nullptr; KateGlobalConfig::KateGlobalConfig() { s_global = this; // init with defaults from config or really hardcoded ones KConfigGroup cg(KTextEditor::EditorPrivate::config(), "Editor"); readConfig(cg); } KateGlobalConfig::~KateGlobalConfig() { } namespace { const char KEY_PROBER_TYPE[] = "Encoding Prober Type"; const char KEY_FALLBACK_ENCODING[] = "Fallback Encoding"; } void KateGlobalConfig::readConfig(const KConfigGroup &config) { configStart(); setProberType((KEncodingProber::ProberType)config.readEntry(KEY_PROBER_TYPE, (int)KEncodingProber::Universal)); setFallbackEncoding(config.readEntry(KEY_FALLBACK_ENCODING, "")); configEnd(); } void KateGlobalConfig::writeConfig(KConfigGroup &config) { config.writeEntry(KEY_PROBER_TYPE, (int)proberType()); config.writeEntry(KEY_FALLBACK_ENCODING, fallbackEncoding()); } void KateGlobalConfig::updateConfig() { // write config KConfigGroup cg(KTextEditor::EditorPrivate::config(), "Editor"); writeConfig(cg); KTextEditor::EditorPrivate::config()->sync(); } void KateGlobalConfig::setProberType(KEncodingProber::ProberType proberType) { configStart(); m_proberType = proberType; configEnd(); } const QString &KateGlobalConfig::fallbackEncoding() const { return m_fallbackEncoding; } QTextCodec *KateGlobalConfig::fallbackCodec() const { if (m_fallbackEncoding.isEmpty()) { return QTextCodec::codecForName("ISO 8859-15"); } return KCharsets::charsets()->codecForName(m_fallbackEncoding); } bool KateGlobalConfig::setFallbackEncoding(const QString &encoding) { QTextCodec *codec; bool found = false; if (encoding.isEmpty()) { codec = s_global->fallbackCodec(); found = true; } else { codec = KCharsets::charsets()->codecForName(encoding, found); } if (!found || !codec) { return false; } configStart(); m_fallbackEncoding = QString::fromLatin1(codec->name()); configEnd(); return true; } KateDocumentConfig::KateDocumentConfig() : m_indentationWidth(2), m_tabWidth(4), m_tabHandling(tabSmart), m_configFlags(0), m_wordWrapAt(80), m_tabWidthSet(false), m_indentationWidthSet(false), m_indentationModeSet(false), m_wordWrapSet(false), m_wordWrapAtSet(false), m_pageUpDownMovesCursorSet(false), m_keepExtraSpacesSet(false), m_indentPastedTextSet(false), m_backspaceIndentsSet(false), m_smartHomeSet(false), m_showTabsSet(false), m_showSpacesSet(false), m_replaceTabsDynSet(false), m_removeSpacesSet(false), m_newLineAtEofSet(false), m_overwiteModeSet(false), m_tabIndentsSet(false), m_encodingSet(false), m_eolSet(false), m_bomSet(false), m_allowEolDetectionSet(false), m_backupFlagsSet(false), m_backupPrefixSet(false), m_backupSuffixSet(false), m_swapFileModeSet(false), m_swapDirectorySet(false), m_swapSyncIntervalSet(false), m_onTheFlySpellCheckSet(false), m_lineLengthLimitSet(false), m_doc(nullptr) { s_global = this; // init with defaults from config or really hardcoded ones KConfigGroup cg(KTextEditor::EditorPrivate::config(), "Document"); readConfig(cg); } KateDocumentConfig::KateDocumentConfig(const KConfigGroup &cg) : m_indentationWidth(2), m_tabWidth(4), m_tabHandling(tabSmart), m_configFlags(0), m_wordWrapAt(80), m_tabWidthSet(false), m_indentationWidthSet(false), m_indentationModeSet(false), m_wordWrapSet(false), m_wordWrapAtSet(false), m_pageUpDownMovesCursorSet(false), m_keepExtraSpacesSet(false), m_indentPastedTextSet(false), m_backspaceIndentsSet(false), m_smartHomeSet(false), m_showTabsSet(false), m_showSpacesSet(false), m_replaceTabsDynSet(false), m_removeSpacesSet(false), m_newLineAtEofSet(false), m_overwiteModeSet(false), m_tabIndentsSet(false), m_encodingSet(false), m_eolSet(false), m_bomSet(false), m_allowEolDetectionSet(false), m_backupFlagsSet(false), m_backupPrefixSet(false), m_backupSuffixSet(false), m_swapFileModeSet(false), m_swapDirectorySet(false), m_swapSyncIntervalSet(false), m_onTheFlySpellCheckSet(false), m_lineLengthLimitSet(false), m_doc(nullptr) { // init with defaults from config or really hardcoded ones readConfig(cg); } KateDocumentConfig::KateDocumentConfig(KTextEditor::DocumentPrivate *doc) : m_tabHandling(tabSmart), m_configFlags(0), m_tabWidthSet(false), m_indentationWidthSet(false), m_indentationModeSet(false), m_wordWrapSet(false), m_wordWrapAtSet(false), m_pageUpDownMovesCursorSet(false), m_keepExtraSpacesSet(false), m_indentPastedTextSet(false), m_backspaceIndentsSet(false), m_smartHomeSet(false), m_showTabsSet(false), m_showSpacesSet(false), m_replaceTabsDynSet(false), m_removeSpacesSet(false), m_newLineAtEofSet(false), m_overwiteModeSet(false), m_tabIndentsSet(false), m_encodingSet(false), m_eolSet(false), m_bomSet(false), m_allowEolDetectionSet(false), m_backupFlagsSet(false), m_backupPrefixSet(false), m_backupSuffixSet(false), m_swapFileModeSet(false), m_swapDirectorySet(false), m_swapSyncIntervalSet(false), m_onTheFlySpellCheckSet(false), m_lineLengthLimitSet(false), m_doc(doc) { } KateDocumentConfig::~KateDocumentConfig() { } namespace { const char KEY_TAB_WIDTH[] = "Tab Width"; const char KEY_INDENTATION_WIDTH[] = "Indentation Width"; const char KEY_INDENTATION_MODE[] = "Indentation Mode"; const char KEY_TAB_HANDLING[] = "Tab Handling"; const char KEY_WORD_WRAP[] = "Word Wrap"; const char KEY_WORD_WRAP_AT[] = "Word Wrap Column"; const char KEY_PAGEUP_DOWN_MOVES_CURSOR[] = "PageUp/PageDown Moves Cursor"; const char KEY_SMART_HOME[] = "Smart Home"; const char KEY_SHOW_TABS[] = "Show Tabs"; const char KEY_TAB_INDENTS[] = "Indent On Tab"; const char KEY_KEEP_EXTRA_SPACES[] = "Keep Extra Spaces"; const char KEY_INDENT_PASTED_TEXT[] = "Indent On Text Paste"; const char KEY_BACKSPACE_INDENTS[] = "Indent On Backspace"; const char KEY_SHOW_SPACES[] = "Show Spaces"; const char KEY_MARKER_SIZE[] = "Trailing Marker Size"; const char KEY_REPLACE_TABS_DYN[] = "ReplaceTabsDyn"; const char KEY_REMOVE_SPACES[] = "Remove Spaces"; const char KEY_NEWLINE_AT_EOF[] = "Newline at End of File"; const char KEY_OVR[] = "Overwrite Mode"; const char KEY_ENCODING[] = "Encoding"; const char KEY_EOL[] = "End of Line"; const char KEY_ALLOW_EOL_DETECTION[] = "Allow End of Line Detection"; const char KEY_BOM[] = "BOM"; const char KEY_BACKUP_FLAGS[] = "Backup Flags"; const char KEY_BACKUP_PREFIX[] = "Backup Prefix"; const char KEY_BACKUP_SUFFIX[] = "Backup Suffix"; const char KEY_SWAP_FILE_MODE[] = "Swap File Mode"; const char KEY_SWAP_DIRECTORY[] = "Swap Directory"; const char KEY_SWAP_SYNC_INTERVAL[] = "Swap Sync Interval"; const char KEY_ON_THE_FLY_SPELLCHECK[] = "On-The-Fly Spellcheck"; const char KEY_LINE_LENGTH_LIMIT[] = "Line Length Limit"; } void KateDocumentConfig::readConfig(const KConfigGroup &config) { configStart(); setTabWidth(config.readEntry(KEY_TAB_WIDTH, 4)); setIndentationWidth(config.readEntry(KEY_INDENTATION_WIDTH, 4)); setIndentationMode(config.readEntry(KEY_INDENTATION_MODE, "normal")); setTabHandling(config.readEntry(KEY_TAB_HANDLING, int(KateDocumentConfig::tabSmart))); setWordWrap(config.readEntry(KEY_WORD_WRAP, false)); setWordWrapAt(config.readEntry(KEY_WORD_WRAP_AT, 80)); setPageUpDownMovesCursor(config.readEntry(KEY_PAGEUP_DOWN_MOVES_CURSOR, false)); setSmartHome(config.readEntry(KEY_SMART_HOME, true)); setShowTabs(config.readEntry(KEY_SHOW_TABS, true)); setTabIndents(config.readEntry(KEY_TAB_INDENTS, true)); setKeepExtraSpaces(config.readEntry(KEY_KEEP_EXTRA_SPACES, false)); setIndentPastedText(config.readEntry(KEY_INDENT_PASTED_TEXT, false)); setBackspaceIndents(config.readEntry(KEY_BACKSPACE_INDENTS, true)); setShowSpaces(config.readEntry(KEY_SHOW_SPACES, false)); setMarkerSize(config.readEntry(KEY_MARKER_SIZE, 1)); setReplaceTabsDyn(config.readEntry(KEY_REPLACE_TABS_DYN, true)); setRemoveSpaces(config.readEntry(KEY_REMOVE_SPACES, 0)); setNewLineAtEof(config.readEntry(KEY_NEWLINE_AT_EOF, true)); setOvr(config.readEntry(KEY_OVR, false)); setEncoding(config.readEntry(KEY_ENCODING, "")); setEol(config.readEntry(KEY_EOL, 0)); setAllowEolDetection(config.readEntry(KEY_ALLOW_EOL_DETECTION, true)); setBom(config.readEntry(KEY_BOM, false)); setBackupFlags(config.readEntry(KEY_BACKUP_FLAGS, 0)); setBackupPrefix(config.readEntry(KEY_BACKUP_PREFIX, QString())); setBackupSuffix(config.readEntry(KEY_BACKUP_SUFFIX, QStringLiteral("~"))); setSwapFileMode(config.readEntry(KEY_SWAP_FILE_MODE, (uint)EnableSwapFile)); setSwapDirectory(config.readEntry(KEY_SWAP_DIRECTORY, QString())); setSwapSyncInterval(config.readEntry(KEY_SWAP_SYNC_INTERVAL, 15)); setOnTheFlySpellCheck(config.readEntry(KEY_ON_THE_FLY_SPELLCHECK, false)); setLineLengthLimit(config.readEntry(KEY_LINE_LENGTH_LIMIT, 4096)); configEnd(); } void KateDocumentConfig::writeConfig(KConfigGroup &config) { config.writeEntry(KEY_TAB_WIDTH, tabWidth()); config.writeEntry(KEY_INDENTATION_WIDTH, indentationWidth()); config.writeEntry(KEY_INDENTATION_MODE, indentationMode()); config.writeEntry(KEY_TAB_HANDLING, tabHandling()); config.writeEntry(KEY_WORD_WRAP, wordWrap()); config.writeEntry(KEY_WORD_WRAP_AT, wordWrapAt()); config.writeEntry(KEY_PAGEUP_DOWN_MOVES_CURSOR, pageUpDownMovesCursor()); config.writeEntry(KEY_SMART_HOME, smartHome()); config.writeEntry(KEY_SHOW_TABS, showTabs()); config.writeEntry(KEY_TAB_INDENTS, tabIndentsEnabled()); config.writeEntry(KEY_KEEP_EXTRA_SPACES, keepExtraSpaces()); config.writeEntry(KEY_INDENT_PASTED_TEXT, indentPastedText()); config.writeEntry(KEY_BACKSPACE_INDENTS, backspaceIndents()); config.writeEntry(KEY_SHOW_SPACES, showSpaces()); config.writeEntry(KEY_MARKER_SIZE, markerSize()); config.writeEntry(KEY_REPLACE_TABS_DYN, replaceTabsDyn()); config.writeEntry(KEY_REMOVE_SPACES, removeSpaces()); config.writeEntry(KEY_NEWLINE_AT_EOF, newLineAtEof()); config.writeEntry(KEY_OVR, ovr()); config.writeEntry(KEY_ENCODING, encoding()); config.writeEntry(KEY_EOL, eol()); config.writeEntry(KEY_ALLOW_EOL_DETECTION, allowEolDetection()); config.writeEntry(KEY_BOM, bom()); config.writeEntry(KEY_BACKUP_FLAGS, backupFlags()); config.writeEntry(KEY_BACKUP_PREFIX, backupPrefix()); config.writeEntry(KEY_BACKUP_SUFFIX, backupSuffix()); config.writeEntry(KEY_SWAP_FILE_MODE, swapFileModeRaw()); config.writeEntry(KEY_SWAP_DIRECTORY, swapDirectory()); config.writeEntry(KEY_SWAP_SYNC_INTERVAL, swapSyncInterval()); config.writeEntry(KEY_ON_THE_FLY_SPELLCHECK, onTheFlySpellCheck()); config.writeEntry(KEY_LINE_LENGTH_LIMIT, lineLengthLimit()); } 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(), "Document"); writeConfig(cg); KTextEditor::EditorPrivate::config()->sync(); } } int KateDocumentConfig::tabWidth() const { if (m_tabWidthSet || isGlobal()) { return m_tabWidth; } return s_global->tabWidth(); } void KateDocumentConfig::setTabWidth(int tabWidth) { if (tabWidth < 1) { return; } if (m_tabWidthSet && m_tabWidth == tabWidth) { return; } configStart(); m_tabWidthSet = true; m_tabWidth = tabWidth; configEnd(); } int KateDocumentConfig::indentationWidth() const { if (m_indentationWidthSet || isGlobal()) { return m_indentationWidth; } return s_global->indentationWidth(); } void KateDocumentConfig::setIndentationWidth(int indentationWidth) { if (indentationWidth < 1) { return; } if (m_indentationWidthSet && m_indentationWidth == indentationWidth) { return; } configStart(); m_indentationWidthSet = true; m_indentationWidth = indentationWidth; configEnd(); } const QString &KateDocumentConfig::indentationMode() const { if (m_indentationModeSet || isGlobal()) { return m_indentationMode; } return s_global->indentationMode(); } void KateDocumentConfig::setIndentationMode(const QString &indentationMode) { if (m_indentationModeSet && m_indentationMode == indentationMode) { return; } configStart(); m_indentationModeSet = true; m_indentationMode = indentationMode; configEnd(); } uint KateDocumentConfig::tabHandling() const { // This setting is purly a user preference, // hence, there exists only the global setting. if (isGlobal()) { return m_tabHandling; } return s_global->tabHandling(); } void KateDocumentConfig::setTabHandling(uint tabHandling) { configStart(); m_tabHandling = tabHandling; configEnd(); } bool KateDocumentConfig::wordWrap() const { if (m_wordWrapSet || isGlobal()) { return m_wordWrap; } return s_global->wordWrap(); } void KateDocumentConfig::setWordWrap(bool on) { if (m_wordWrapSet && m_wordWrap == on) { return; } configStart(); m_wordWrapSet = true; m_wordWrap = on; configEnd(); } int KateDocumentConfig::wordWrapAt() const { if (m_wordWrapAtSet || isGlobal()) { return m_wordWrapAt; } return s_global->wordWrapAt(); } void KateDocumentConfig::setWordWrapAt(int col) { if (col < 1) { return; } if (m_wordWrapAtSet && m_wordWrapAt == col) { return; } configStart(); m_wordWrapAtSet = true; m_wordWrapAt = col; configEnd(); } bool KateDocumentConfig::pageUpDownMovesCursor() const { if (m_pageUpDownMovesCursorSet || isGlobal()) { return m_pageUpDownMovesCursor; } return s_global->pageUpDownMovesCursor(); } void KateDocumentConfig::setPageUpDownMovesCursor(bool on) { if (m_pageUpDownMovesCursorSet && m_pageUpDownMovesCursor == on) { return; } configStart(); m_pageUpDownMovesCursorSet = true; m_pageUpDownMovesCursor = on; configEnd(); } void KateDocumentConfig::setKeepExtraSpaces(bool on) { if (m_keepExtraSpacesSet && m_keepExtraSpaces == on) { return; } configStart(); m_keepExtraSpacesSet = true; m_keepExtraSpaces = on; configEnd(); } bool KateDocumentConfig::keepExtraSpaces() const { if (m_keepExtraSpacesSet || isGlobal()) { return m_keepExtraSpaces; } return s_global->keepExtraSpaces(); } void KateDocumentConfig::setIndentPastedText(bool on) { if (m_indentPastedTextSet && m_indentPastedText == on) { return; } configStart(); m_indentPastedTextSet = true; m_indentPastedText = on; configEnd(); } bool KateDocumentConfig::indentPastedText() const { if (m_indentPastedTextSet || isGlobal()) { return m_indentPastedText; } return s_global->indentPastedText(); } void KateDocumentConfig::setBackspaceIndents(bool on) { if (m_backspaceIndentsSet && m_backspaceIndents == on) { return; } configStart(); m_backspaceIndentsSet = true; m_backspaceIndents = on; configEnd(); } bool KateDocumentConfig::backspaceIndents() const { if (m_backspaceIndentsSet || isGlobal()) { return m_backspaceIndents; } return s_global->backspaceIndents(); } void KateDocumentConfig::setSmartHome(bool on) { if (m_smartHomeSet && m_smartHome == on) { return; } configStart(); m_smartHomeSet = true; m_smartHome = on; configEnd(); } bool KateDocumentConfig::smartHome() const { if (m_smartHomeSet || isGlobal()) { return m_smartHome; } return s_global->smartHome(); } void KateDocumentConfig::setShowTabs(bool on) { if (m_showTabsSet && m_showTabs == on) { return; } configStart(); m_showTabsSet = true; m_showTabs = on; configEnd(); } bool KateDocumentConfig::showTabs() const { if (m_showTabsSet || isGlobal()) { return m_showTabs; } return s_global->showTabs(); } void KateDocumentConfig::setShowSpaces(bool on) { if (m_showSpacesSet && m_showSpaces == on) { return; } configStart(); m_showSpacesSet = true; m_showSpaces = on; configEnd(); } bool KateDocumentConfig::showSpaces() const { if (m_showSpacesSet || isGlobal()) { return m_showSpaces; } return s_global->showSpaces(); } void KateDocumentConfig::setMarkerSize(uint markerSize) { if (m_markerSize == markerSize) { return; } configStart(); m_markerSize = markerSize; configEnd(); } uint KateDocumentConfig::markerSize() const { if (isGlobal()) { return m_markerSize; } return s_global->markerSize(); } void KateDocumentConfig::setReplaceTabsDyn(bool on) { if (m_replaceTabsDynSet && m_replaceTabsDyn == on) { return; } configStart(); m_replaceTabsDynSet = true; m_replaceTabsDyn = on; configEnd(); } bool KateDocumentConfig::replaceTabsDyn() const { if (m_replaceTabsDynSet || isGlobal()) { return m_replaceTabsDyn; } return s_global->replaceTabsDyn(); } void KateDocumentConfig::setRemoveSpaces(int triState) { if (m_removeSpacesSet && m_removeSpaces == triState) { return; } configStart(); m_removeSpacesSet = true; m_removeSpaces = triState; configEnd(); } int KateDocumentConfig::removeSpaces() const { if (m_removeSpacesSet || isGlobal()) { return m_removeSpaces; } return s_global->removeSpaces(); } void KateDocumentConfig::setNewLineAtEof(bool on) { if (m_newLineAtEofSet && m_newLineAtEof == on) { return; } configStart(); m_newLineAtEofSet = true; m_newLineAtEof = on; configEnd(); } bool KateDocumentConfig::newLineAtEof() const { if (m_newLineAtEofSet || isGlobal()) { return m_newLineAtEof; } return s_global->newLineAtEof(); } void KateDocumentConfig::setOvr(bool on) { if (m_overwiteModeSet && m_overwiteMode == on) { return; } configStart(); m_overwiteModeSet = true; m_overwiteMode = on; configEnd(); } bool KateDocumentConfig::ovr() const { if (m_overwiteModeSet || isGlobal()) { return m_overwiteMode; } return s_global->ovr(); } void KateDocumentConfig::setTabIndents(bool on) { if (m_tabIndentsSet && m_tabIndents == on) { return; } configStart(); m_tabIndentsSet = true; m_tabIndents = on; configEnd(); } bool KateDocumentConfig::tabIndentsEnabled() const { if (m_tabIndentsSet || isGlobal()) { return m_tabIndents; } return s_global->tabIndentsEnabled(); } const QString &KateDocumentConfig::encoding() const { if (m_encodingSet || isGlobal()) { return m_encoding; } return s_global->encoding(); } QTextCodec *KateDocumentConfig::codec() const { if (m_encodingSet || isGlobal()) { if (m_encoding.isEmpty() && isGlobal()) { // default to UTF-8, this makes sense to have a usable encoding detection // else for people that have by bad luck some encoding like latin1 as default, no encoding detection will work // see e.g. bug 362604 for windows return QTextCodec::codecForName("UTF-8"); } else if (m_encoding.isEmpty()) { return s_global->codec(); } else { return KCharsets::charsets()->codecForName(m_encoding); } } return s_global->codec(); } bool KateDocumentConfig::setEncoding(const QString &encoding) { QTextCodec *codec; bool found = false; if (encoding.isEmpty()) { codec = s_global->codec(); found = true; } else { codec = KCharsets::charsets()->codecForName(encoding, found); } if (!found || !codec) { return false; } configStart(); m_encodingSet = true; m_encoding = QString::fromLatin1(codec->name()); configEnd(); return true; } bool KateDocumentConfig::isSetEncoding() const { return m_encodingSet; } int KateDocumentConfig::eol() const { if (m_eolSet || isGlobal()) { return m_eol; } return s_global->eol(); } QString KateDocumentConfig::eolString() { if (eol() == KateDocumentConfig::eolUnix) { return QStringLiteral("\n"); } else if (eol() == KateDocumentConfig::eolDos) { return QStringLiteral("\r\n"); } else if (eol() == KateDocumentConfig::eolMac) { return QStringLiteral("\r"); } return QStringLiteral("\n"); } void KateDocumentConfig::setEol(int mode) { if (m_eolSet && m_eol == mode) { return; } configStart(); m_eolSet = true; m_eol = mode; configEnd(); } void KateDocumentConfig::setBom(bool bom) { if (m_bomSet && m_bom == bom) { return; } configStart(); m_bomSet = true; m_bom = bom; configEnd(); } bool KateDocumentConfig::bom() const { if (m_bomSet || isGlobal()) { return m_bom; } return s_global->bom(); } bool KateDocumentConfig::allowEolDetection() const { if (m_allowEolDetectionSet || isGlobal()) { return m_allowEolDetection; } return s_global->allowEolDetection(); } void KateDocumentConfig::setAllowEolDetection(bool on) { if (m_allowEolDetectionSet && m_allowEolDetection == on) { return; } configStart(); m_allowEolDetectionSet = true; m_allowEolDetection = on; configEnd(); } uint KateDocumentConfig::backupFlags() const { if (m_backupFlagsSet || isGlobal()) { return m_backupFlags; } return s_global->backupFlags(); } void KateDocumentConfig::setBackupFlags(uint flags) { if (m_backupFlagsSet && m_backupFlags == flags) { return; } configStart(); m_backupFlagsSet = true; m_backupFlags = flags; configEnd(); } const QString &KateDocumentConfig::backupPrefix() const { if (m_backupPrefixSet || isGlobal()) { return m_backupPrefix; } return s_global->backupPrefix(); } const QString &KateDocumentConfig::backupSuffix() const { if (m_backupSuffixSet || isGlobal()) { return m_backupSuffix; } return s_global->backupSuffix(); } void KateDocumentConfig::setBackupPrefix(const QString &prefix) { if (m_backupPrefixSet && m_backupPrefix == prefix) { return; } configStart(); m_backupPrefixSet = true; m_backupPrefix = prefix; configEnd(); } void KateDocumentConfig::setBackupSuffix(const QString &suffix) { if (m_backupSuffixSet && m_backupSuffix == suffix) { return; } configStart(); m_backupSuffixSet = true; m_backupSuffix = suffix; configEnd(); } uint KateDocumentConfig::swapSyncInterval() const { if (m_swapSyncIntervalSet || isGlobal()) { return m_swapSyncInterval; } return s_global->swapSyncInterval(); } void KateDocumentConfig::setSwapSyncInterval(uint interval) { if (m_swapSyncIntervalSet && m_swapSyncInterval == interval) { return; } configStart(); m_swapSyncIntervalSet = true; m_swapSyncInterval = interval; configEnd(); } uint KateDocumentConfig::swapFileModeRaw() const { if (m_swapFileModeSet || isGlobal()) { return m_swapFileMode; } return s_global->swapFileModeRaw(); } KateDocumentConfig::SwapFileMode KateDocumentConfig::swapFileMode() const { return static_cast(swapFileModeRaw()); } void KateDocumentConfig::setSwapFileMode(uint mode) { if (m_swapFileModeSet && m_swapFileMode == mode) { return; } configStart(); m_swapFileModeSet = true; m_swapFileMode = mode; configEnd(); } const QString &KateDocumentConfig::swapDirectory() const { if (m_swapDirectorySet || isGlobal()) { return m_swapDirectory; } return s_global->swapDirectory(); } void KateDocumentConfig::setSwapDirectory(const QString &directory) { if (m_swapDirectorySet && m_swapDirectory == directory) { return; } configStart(); m_swapDirectorySet = true; m_swapDirectory = directory; configEnd(); } bool KateDocumentConfig::onTheFlySpellCheck() const { if (isGlobal()) { // WARNING: this is slightly hackish, but it's currently the only way to // do it, see also the KTextEdit class QSettings settings(QStringLiteral("KDE"), QStringLiteral("Sonnet")); return settings.value(QStringLiteral("checkerEnabledByDefault"), false).toBool(); //KConfigGroup configGroup(KSharedConfig::openConfig(), "Spelling"); //return configGroup.readEntry("checkerEnabledByDefault", false); } if (m_onTheFlySpellCheckSet) { return m_onTheFlySpellCheck; } return s_global->onTheFlySpellCheck(); } void KateDocumentConfig::setOnTheFlySpellCheck(bool on) { if (m_onTheFlySpellCheckSet && m_onTheFlySpellCheck == on) { return; } configStart(); m_onTheFlySpellCheckSet = true; m_onTheFlySpellCheck = on; configEnd(); } int KateDocumentConfig::lineLengthLimit() const { if (m_lineLengthLimitSet || isGlobal()) { return m_lineLengthLimit; } return s_global->lineLengthLimit(); } void KateDocumentConfig::setLineLengthLimit(int lineLengthLimit) { if (m_lineLengthLimitSet && m_lineLengthLimit == lineLengthLimit) { return; } configStart(); m_lineLengthLimitSet = true; m_lineLengthLimit = lineLengthLimit; configEnd(); } //END //BEGIN KateViewConfig KateViewConfig::KateViewConfig() : m_showWordCount(false), m_dynWordWrapSet(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_scrollPastEndSet(false), m_allowMarkMenu(true), m_wordCompletionRemoveTailSet(false), m_foldFirstLineSet (false), m_autoBracketsSet(false), + m_backspaceRemoveComposedSet(false), m_view(nullptr) { s_global = this; // init with defaults from config or really hardcoded ones KConfigGroup config(KTextEditor::EditorPrivate::config(), "View"); readConfig(config); } KateViewConfig::KateViewConfig(KTextEditor::ViewPrivate *view) : m_searchFlags(PowerModePlainText), m_maxHistorySize(100), m_showWordCount(false), m_dynWordWrapSet(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_scrollPastEndSet(false), m_allowMarkMenu(true), m_wordCompletionRemoveTailSet(false), - m_foldFirstLineSet (false), + m_foldFirstLineSet(false), m_autoBracketsSet(false), + m_backspaceRemoveComposedSet(false), 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_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_SCROLL_PAST_END[] = "Scroll Past End"; const char KEY_FOLD_FIRST_LINE[] = "Fold First Line"; 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(); // default on setDynWordWrap(config.readEntry(KEY_DYN_WORD_WRAP, true)); 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)); setScrollPastEnd(config.readEntry(KEY_SCROLL_PAST_END, false)); setFoldFirstLine(config.readEntry(KEY_FOLD_FIRST_LINE, 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) { config.writeEntry(KEY_DYN_WORD_WRAP, dynWordWrap()); 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_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_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(), "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(); } 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::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() { return m_showWordCount; } void KateViewConfig::setShowWordCount(bool on) { if (m_showWordCount == on) { return; } configStart(); m_showWordCount = 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_wordWrapMarker(false), m_showIndentationLines(false), m_showWholeBracketExpression(false), m_animateBracketMatching(false), 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(nullptr) { // init bitarray m_lineMarkerColorSet.fill(true); s_global = this; // init with defaults from config or really hardcoded ones KConfigGroup config(KTextEditor::EditorPrivate::config(), "Renderer"); readConfig(config); } KateRendererConfig::KateRendererConfig(KateRenderer *renderer) : 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(); // "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) { 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(), "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; } QFont f(QFontDatabase::systemFont(QFontDatabase::FixedFont)); m_font = config.readEntry("Font", f); m_fontMetrics = QFontMetricsF(m_font); m_fontSet = true; 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(); m_fontSet = true; m_font = font; m_fontMetrics = QFontMetricsF(m_font); configEnd(); } 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 1db82aaf..23645018 100644 --- a/src/utils/kateconfig.h +++ b/src/utils/kateconfig.h @@ -1,853 +1,858 @@ /* 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 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 */ class KateConfig { public: /** * Default Constructor */ KateConfig(); /** * Virtual Destructor */ virtual ~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 */ void configStart(); /** * end a config change transaction, update the concerned * documents/views/renderers */ void configEnd(); protected: /** * do the real update */ virtual void updateConfig() = 0; private: /** * recursion depth */ uint configSessionNumber; /** * is a config session running */ bool configIsRunning; }; class KTEXTEDITOR_EXPORT KateGlobalConfig : public KateConfig { private: friend class KTextEditor::EditorPrivate; /** * only used in KTextEditor::EditorPrivate for the static global fallback !!! */ KateGlobalConfig(); /** * Destructor */ ~KateGlobalConfig(); public: static KateGlobalConfig *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() Q_DECL_OVERRIDE; public: KEncodingProber::ProberType proberType() const { return m_proberType; } void setProberType(KEncodingProber::ProberType proberType); QTextCodec *fallbackCodec() const; const QString &fallbackEncoding() const; bool setFallbackEncoding(const QString &encoding); private: KEncodingProber::ProberType m_proberType; QString m_fallbackEncoding; private: static KateGlobalConfig *s_global; }; class KTEXTEDITOR_EXPORT KateDocumentConfig : public KateConfig { private: friend class KTextEditor::EditorPrivate; KateDocumentConfig(); public: KateDocumentConfig(const KConfigGroup &cg); /** * Construct a DocumentConfig */ KateDocumentConfig(KTextEditor::DocumentPrivate *doc); /** * Cu DocumentConfig */ ~KateDocumentConfig(); inline static KateDocumentConfig *global() { return s_global; } inline bool isGlobal() const { return (this == global()); } public: /** * Read config from object */ void readConfig(const KConfigGroup &config); /** * Write config to object */ void writeConfig(KConfigGroup &config); protected: void updateConfig() Q_DECL_OVERRIDE; public: int tabWidth() const; void setTabWidth(int tabWidth); int indentationWidth() const; void setIndentationWidth(int indentationWidth); const QString &indentationMode() const; void setIndentationMode(const QString &identationMode); enum TabHandling { tabInsertsTab = 0, tabIndents = 1, tabSmart = 2 //!< indents in leading space, otherwise inserts tab }; uint tabHandling() const; void setTabHandling(uint tabHandling); bool wordWrap() const; void setWordWrap(bool on); int wordWrapAt() const; void setWordWrapAt(int col); bool pageUpDownMovesCursor() const; void setPageUpDownMovesCursor(bool on); void setKeepExtraSpaces(bool on); bool keepExtraSpaces() const; void setIndentPastedText(bool on); bool indentPastedText() const; void setBackspaceIndents(bool on); bool backspaceIndents() const; void setSmartHome(bool on); bool smartHome() const; void setShowTabs(bool on); bool showTabs() const; void setShowSpaces(bool on); bool showSpaces() const; void setMarkerSize(uint markerSize); uint markerSize() const; void setReplaceTabsDyn(bool on); bool replaceTabsDyn() const; /** * 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); int removeSpaces() const; void setNewLineAtEof(bool on); bool newLineAtEof() const; void setOvr(bool on); bool ovr() const; void setTabIndents(bool on); bool tabIndentsEnabled() const; QTextCodec *codec() const; const QString &encoding() const; bool setEncoding(const QString &encoding); bool isSetEncoding() const; enum Eol { eolUnix = 0, eolDos = 1, eolMac = 2 }; int eol() const; QString eolString(); void setEol(int mode); bool bom() const; void setBom(bool bom); bool allowEolDetection() const; void setAllowEolDetection(bool on); enum BackupFlags { LocalFiles = 1, RemoteFiles = 2 }; uint backupFlags() const; void setBackupFlags(uint flags); const QString &backupPrefix() const; void setBackupPrefix(const QString &prefix); const QString &backupSuffix() const; void setBackupSuffix(const QString &suffix); const QString &swapDirectory() const; void setSwapDirectory(const QString &directory); enum SwapFileMode { DisableSwapFile = 0, EnableSwapFile, SwapFilePresetDirectory }; uint swapFileModeRaw() const; SwapFileMode swapFileMode() const; void setSwapFileMode(uint mode); uint swapSyncInterval() const; void setSwapSyncInterval(uint interval); bool onTheFlySpellCheck() const; void setOnTheFlySpellCheck(bool on); int lineLengthLimit() const; void setLineLengthLimit(int limit); private: QString m_indentationMode; int m_indentationWidth; int m_tabWidth; uint m_tabHandling; uint m_configFlags; int m_wordWrapAt; bool m_wordWrap; bool m_pageUpDownMovesCursor; bool m_allowEolDetection; int m_eol; bool m_bom; uint m_backupFlags; QString m_encoding; QString m_backupPrefix; QString m_backupSuffix; uint m_swapFileMode; QString m_swapDirectory; uint m_swapSyncInterval; bool m_onTheFlySpellCheck; int m_lineLengthLimit; bool m_tabWidthSet : 1; bool m_indentationWidthSet : 1; bool m_indentationModeSet : 1; bool m_wordWrapSet : 1; bool m_wordWrapAtSet : 1; bool m_pageUpDownMovesCursorSet : 1; bool m_keepExtraSpacesSet : 1; bool m_keepExtraSpaces : 1; bool m_indentPastedTextSet : 1; bool m_indentPastedText : 1; bool m_backspaceIndentsSet : 1; bool m_backspaceIndents : 1; bool m_smartHomeSet : 1; bool m_smartHome : 1; bool m_showTabsSet : 1; bool m_showTabs : 1; bool m_showSpacesSet : 1; bool m_showSpaces : 1; uint m_markerSize; bool m_replaceTabsDynSet : 1; bool m_replaceTabsDyn : 1; bool m_removeSpacesSet : 1; uint m_removeSpaces : 2; bool m_newLineAtEofSet : 1; bool m_newLineAtEof : 1; bool m_overwiteModeSet : 1; bool m_overwiteMode : 1; bool m_tabIndentsSet : 1; bool m_tabIndents : 1; bool m_encodingSet : 1; bool m_eolSet : 1; bool m_bomSet : 1; bool m_allowEolDetectionSet : 1; bool m_backupFlagsSet : 1; bool m_backupPrefixSet : 1; bool m_backupSuffixSet : 1; bool m_swapFileModeSet : 1; bool m_swapDirectorySet : 1; bool m_swapSyncIntervalSet : 1; bool m_onTheFlySpellCheckSet : 1; bool m_lineLengthLimitSet : 1; private: static KateDocumentConfig *s_global; KTextEditor::DocumentPrivate *m_doc; }; 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 */ explicit KateViewConfig(KTextEditor::ViewPrivate *view); /** * Cu DocumentConfig */ ~KateViewConfig(); inline static KateViewConfig *global() { return s_global; } inline bool isGlobal() const { return (this == global()); } public: /** * Read config from object */ void readConfig(const KConfigGroup &config); /** * Write config to object */ void writeConfig(KConfigGroup &config); protected: void updateConfig() Q_DECL_OVERRIDE; public: bool dynWordWrapSet() const { return m_dynWordWrapSet; } bool dynWordWrap() const; void setDynWordWrap(bool wrap); int dynWordWrapIndicators() const; void setDynWordWrapIndicators(int mode); int dynWordWrapAlignIndent() const; void setDynWordWrapAlignIndent(int indent); bool lineNumbers() const; void setLineNumbers(bool on); bool scrollBarMarks() const; void setScrollBarMarks(bool on); bool scrollBarPreview() const; void setScrollBarPreview(bool on); bool scrollBarMiniMap() const; void setScrollBarMiniMap(bool on); bool scrollBarMiniMapAll() const; void setScrollBarMiniMapAll(bool on); int scrollBarMiniMapWidth() const; void setScrollBarMiniMapWidth(int width); /* Whether to show scrollbars */ enum ScrollbarMode { AlwaysOn = 0, ShowWhenNeeded, AlwaysOff }; int showScrollbars() const; void setShowScrollbars(int mode); bool iconBar() const; void setIconBar(bool on); bool foldingBar() const; void setFoldingBar(bool on); bool foldingPreview() const; void setFoldingPreview(bool on); bool lineModification() const; void setLineModification(bool on); int bookmarkSort() const; void setBookmarkSort(int mode); int autoCenterLines() const; void setAutoCenterLines(int lines); 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); int maxHistorySize() const; uint defaultMarkType() const; void setDefaultMarkType(uint type); bool allowMarkMenu() const; void setAllowMarkMenu(bool allow); bool persistentSelection() const; void setPersistentSelection(bool on); KTextEditor::View::InputMode inputMode() const; void setInputMode(KTextEditor::View::InputMode mode); void setInputModeRaw(int rawmode); bool viInputModeStealKeys() const; void setViInputModeStealKeys(bool on); bool viRelativeLineNumbers() const; void setViRelativeLineNumbers(bool on); // 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 keywordCompletion () const; void setKeywordCompletion (bool on); int wordCompletionMinimalWordLength() const; void setWordCompletionMinimalWordLength(int length); bool wordCompletionRemoveTail() const; void setWordCompletionRemoveTail(bool on); bool smartCopyCut() const; void setSmartCopyCut(bool on); bool scrollPastEnd() const; void setScrollPastEnd(bool on); bool foldFirstLine() const; void setFoldFirstLine(bool on); bool showWordCount(); void setShowWordCount(bool on); bool autoBrackets() const; void setAutoBrackets(bool on); + bool backspaceRemoveComposed() const; + void setBackspaceRemoveComposed(bool on); + private: bool m_dynWordWrap; 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_scrollPastEnd; bool m_foldFirstLine; bool m_showWordCount; bool m_autoBrackets; + bool m_backspaceRemoveComposed; bool m_dynWordWrapSet : 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_scrollPastEndSet : 1; bool m_allowMarkMenu : 1; bool m_wordCompletionRemoveTailSet : 1; bool m_foldFirstLineSet : 1; bool m_autoBracketsSet : 1; + bool m_backspaceRemoveComposedSet : 1; private: static KateViewConfig *s_global; KTextEditor::ViewPrivate *m_view; }; 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 */ KateRendererConfig(KateRenderer *renderer); /** * Cu DocumentConfig */ ~KateRendererConfig(); inline static KateRendererConfig *global() { return s_global; } inline bool isGlobal() const { return (this == global()); } public: /** * Read config from object */ void readConfig(const KConfigGroup &config); /** * Write config to object */ void writeConfig(KConfigGroup &config); protected: void updateConfig() Q_DECL_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); 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; bool m_showIndentationLines; bool m_showWholeBracketExpression; bool m_animateBracketMatching; 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; }; #endif