diff --git a/.reviewboardrc b/.reviewboardrc deleted file mode 100644 index c1d7dfe9..00000000 --- a/.reviewboardrc +++ /dev/null @@ -1,4 +0,0 @@ -REVIEWBOARD_URL = "https://git.reviewboard.kde.org" -REPOSITORY = 'git://anongit.kde.org/ktexteditor' -BRANCH = 'master' -TARGET_GROUPS = 'kate' diff --git a/CMakeLists.txt b/CMakeLists.txt index 60450c72..56e48a2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,122 +1,122 @@ cmake_minimum_required(VERSION 3.0) -set(KF5_VERSION "5.42.0") # handled by release scripts -set(KF5_DEP_VERSION "5.41.0") # handled by release scripts +set(KF5_VERSION "5.50.0") # handled by release scripts +set(KF5_DEP_VERSION "5.49.0") # handled by release scripts project(KTextEditor VERSION ${KF5_VERSION}) # ECM setup include(FeatureSummary) -find_package(ECM 5.41.0 NO_MODULE) +find_package(ECM 5.49.0 NO_MODULE) set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://projects.kde.org/projects/kdesupport/extra-cmake-modules") feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) # add own modules to search path, too set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) include(ECMSetupVersion) include(ECMGenerateHeaders) include(CMakePackageConfigHelpers) include(CheckFunctionExists) include(CheckSymbolExists) include(KDEInstallDirs) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) include(KDEPackageAppTemplates) include(GenerateExportHeader) include(ECMAddQch) option(BUILD_QCH "Build API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)" OFF) add_feature_info(QCH ${BUILD_QCH} "API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)") ecm_setup_version( PROJECT VARIABLE_PREFIX KTEXTEDITOR VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/ktexteditor_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5TextEditorConfigVersion.cmake" SOVERSION 5 ) # Dependencies -set(REQUIRED_QT_VERSION 5.7.0) +set(REQUIRED_QT_VERSION 5.8.0) # Required Qt5 components to build this framework find_package(Qt5 ${REQUIRED_QT_VERSION} NO_MODULE REQUIRED Core Widgets Qml PrintSupport Xml XmlPatterns) find_package(KF5Archive ${KF5_DEP_VERSION} REQUIRED) find_package(KF5Config ${KF5_DEP_VERSION} REQUIRED) find_package(KF5GuiAddons ${KF5_DEP_VERSION} REQUIRED) find_package(KF5I18n ${KF5_DEP_VERSION} REQUIRED) find_package(KF5KIO ${KF5_DEP_VERSION} REQUIRED) find_package(KF5Parts ${KF5_DEP_VERSION} REQUIRED) find_package(KF5Sonnet ${KF5_DEP_VERSION} REQUIRED) find_package(KF5IconThemes ${KF5_DEP_VERSION} REQUIRED) find_package(KF5SyntaxHighlighting ${KF5_DEP_VERSION} REQUIRED) # libgit2 integration, at least 0.22 with proper git_libgit2_init() find_package(LibGit2 "0.22.0") # EditorConfig integration find_package(EditorConfig) # vi mode on per default option (BUILD_VIMODE "Build vimode in" ON) # Subdirectories add_definitions(-DTRANSLATION_DOMAIN=\"ktexteditor5\") if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po") ki18n_install(po) endif() add_subdirectory(src) if (BUILD_TESTING) add_subdirectory(autotests) endif() add_subdirectory(templates) # Create a Config.cmake and a ConfigVersion.cmake file and install them set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5TextEditor") if (BUILD_QCH) ecm_install_qch_export( TARGETS KF5TextEditor_QCH FILE KF5TextEditorQchTargets.cmake DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KF5TextEditorQchTargets.cmake\")") endif() configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KF5TextEditorConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5TextEditorConfig.cmake" INSTALL_DESTINATION "${CMAKECONFIG_INSTALL_DIR}" ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5TextEditorConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5TextEditorConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT KF5TextEditorTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5TextEditorTargets.cmake NAMESPACE KF5:: ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/ktexteditor_version.h" DESTINATION "${KDE_INSTALL_INCLUDEDIR_KF5}" COMPONENT Devel ) # config.h check_symbol_exists (fdatasync unistd.h HAVE_FDATASYNC) configure_file (config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) # let our config.h be found first in any case include_directories (BEFORE ${CMAKE_CURRENT_BINARY_DIR}) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/autotests/input/indent/cppstyle/quote1/expected b/autotests/input/indent/cppstyle/quote1/expected index f2b75f2b..1c2ecd22 100644 --- a/autotests/input/indent/cppstyle/quote1/expected +++ b/autotests/input/indent/cppstyle/quote1/expected @@ -1 +1,2 @@ +auto a = "R" auto a = R"~(ok)~" diff --git a/autotests/input/indent/cppstyle/quote1/input.js b/autotests/input/indent/cppstyle/quote1/input.js index 263b83d6..6ee85d40 100644 --- a/autotests/input/indent/cppstyle/quote1/input.js +++ b/autotests/input/indent/cppstyle/quote1/input.js @@ -1,3 +1,5 @@ -v.setCursorPosition(0, 10); +v.setCursorPosition(0, 11); +v.type('"'); +v.setCursorPosition(1, 10); v.type('"'); v.type("ok"); diff --git a/autotests/input/indent/cppstyle/quote1/origin b/autotests/input/indent/cppstyle/quote1/origin index 1f8ca1fb..a41ba614 100644 --- a/autotests/input/indent/cppstyle/quote1/origin +++ b/autotests/input/indent/cppstyle/quote1/origin @@ -1 +1,2 @@ +auto a = "R auto a = R diff --git a/autotests/input/modelines.txt b/autotests/input/modelines.txt new file mode 100644 index 00000000..6872d541 --- /dev/null +++ b/autotests/input/modelines.txt @@ -0,0 +1 @@ +modeline test, see katedocument_test.cpp testModelines() diff --git a/autotests/input/syntax/cpp/results/preprocessor-bug363280.cpp.reference.html b/autotests/input/syntax/cpp/results/preprocessor-bug363280.cpp.reference.html index 9b25c373..8e04f0ef 100644 --- a/autotests/input/syntax/cpp/results/preprocessor-bug363280.cpp.reference.html +++ b/autotests/input/syntax/cpp/results/preprocessor-bug363280.cpp.reference.html @@ -1,22 +1,22 @@ preprocessor-bug363280.cpp
-#if 1
+#if 1
 int x; // variable shall not be grey
 #endif
-#if defined (A)
+#if defined (A)
 int y; // variable shall not be grey
-#elif defined (B)
+#elif defined (B)
 int z; // variable shall not be grey
-#endif
+#endif
 
diff --git a/autotests/src/bug205447.h b/autotests/src/bug205447.h index 98c27a95..3cbb7fb6 100644 --- a/autotests/src/bug205447.h +++ b/autotests/src/bug205447.h @@ -1,41 +1,41 @@ /* This file is part of the KDE libraries Copyright (C) 2015 Zoe Clifford 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_BUG_205447_TEST_H #define KATE_BUG_205447_TEST_H -#include +#include class BugTest : public QObject { Q_OBJECT public: BugTest(); ~BugTest(); private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void deleteSurrogates(); void backspaceSurrogates(); }; #endif diff --git a/autotests/src/bug286887.h b/autotests/src/bug286887.h index 602f2cd8..71142aec 100644 --- a/autotests/src/bug286887.h +++ b/autotests/src/bug286887.h @@ -1,40 +1,40 @@ /* This file is part of the KDE libraries Copyright (C) 2012 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. */ #ifndef KATE_BUG_286887_TEST_H #define KATE_BUG_286887_TEST_H -#include +#include class BugTest : public QObject { Q_OBJECT public: BugTest(); ~BugTest(); private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void ctrlShiftLeft(); }; #endif diff --git a/autotests/src/bug313759.cpp b/autotests/src/bug313759.cpp index d639f98a..d582d7f4 100644 --- a/autotests/src/bug313759.cpp +++ b/autotests/src/bug313759.cpp @@ -1,96 +1,96 @@ /* This file is part of the KDE libraries * Copyright (C) 2013 Gerald Senarclens de Grancy * * 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 "bug313759.h" #include #include #include #include -#include +#include #include #include "testutils.h" QTEST_MAIN(BugTest) using namespace KTextEditor; BugTest::BugTest() : QObject() { } BugTest::~BugTest() { } void BugTest::initTestCase() { KTextEditor::EditorPrivate::enableUnitTestMode(); } void BugTest::cleanupTestCase() { } void BugTest::tryCrash() { // set up document and view KMainWindow *toplevel = new KMainWindow(); KTextEditor::DocumentPrivate *doc = new KTextEditor::DocumentPrivate(true, false, toplevel); KTextEditor::ViewPrivate *view = static_cast(doc->createView(nullptr)); bool outputWasCustomised = false; TestScriptEnv *env = new TestScriptEnv(doc, outputWasCustomised); const QUrl url = QUrl::fromLocalFile(QLatin1String(TEST_DATA_DIR"bug313759.txt")); doc->openUrl(url); // load moveLinesDown and moveLinesUp QFile scriptFile(QLatin1String(JS_DATA_DIR "commands/utils.js")); QVERIFY(scriptFile.exists()); QVERIFY(scriptFile.open(QFile::ReadOnly)); QJSValue result = env->engine()->evaluate(QString::fromLatin1(scriptFile.readAll()), scriptFile.fileName()); QVERIFY2(!result.isError(), result.toString().toUtf8().constData()); // enable on the fly spell checking doc->onTheFlySpellCheckingEnabled(true); // view must be visible... view->show(); view->resize(900, 800); view->setCursorPosition(Cursor(0, 0)); doc->editBegin(); // QTest::qWait(200); // evaluate test-script qDebug() << "attempting crash by moving lines w/ otf spell checking enabled"; QFile sourceFile(QLatin1String(TEST_DATA_DIR"bug313759.js")); QVERIFY(sourceFile.open(QFile::ReadOnly)); QTextStream stream(&sourceFile); stream.setCodec("UTF8"); QString code = stream.readAll(); sourceFile.close(); // execute script result = env->engine()->evaluate(code, QLatin1String(TEST_DATA_DIR"bug313759.txt"), 1); QVERIFY2(!result.isError(), result.toString().toUtf8().constData()); doc->editEnd(); qDebug() << "PASS (no crash)"; } diff --git a/autotests/src/bug313759.h b/autotests/src/bug313759.h index 7800be06..e8bb208e 100644 --- a/autotests/src/bug313759.h +++ b/autotests/src/bug313759.h @@ -1,40 +1,40 @@ /* This file is part of the KDE libraries * Copyright (C) 2012 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. */ #ifndef KATE_BUG_313759_TEST_H #define KATE_BUG_313759_TEST_H -#include +#include class BugTest : public QObject { Q_OBJECT public: BugTest(); ~BugTest(); private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void tryCrash(); }; #endif diff --git a/autotests/src/bug313769.h b/autotests/src/bug313769.h index bd33d0f3..b80c831c 100644 --- a/autotests/src/bug313769.h +++ b/autotests/src/bug313769.h @@ -1,40 +1,40 @@ /* This file is part of the KDE libraries Copyright (C) 2012 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. */ #ifndef KATE_BUG_313769_TEST_H #define KATE_BUG_313769_TEST_H -#include +#include class BugTest : public QObject { Q_OBJECT public: BugTest(); ~BugTest(); private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void tryCrash(); }; #endif diff --git a/autotests/src/bug317111.cpp b/autotests/src/bug317111.cpp index 9870327d..a36425c5 100644 --- a/autotests/src/bug317111.cpp +++ b/autotests/src/bug317111.cpp @@ -1,90 +1,90 @@ /* This file is part of the KDE libraries * Copyright (C) 2013 Gerald Senarclens de Grancy * * 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 "bug317111.h" #include #include #include #include -#include +#include #include #include "testutils.h" QTEST_MAIN(BugTest) using namespace KTextEditor; BugTest::BugTest() : QObject() { } BugTest::~BugTest() { } void BugTest::initTestCase() { KTextEditor::EditorPrivate::enableUnitTestMode(); } void BugTest::cleanupTestCase() { } void BugTest::tryCrash() { // set up document and view KMainWindow *toplevel = new KMainWindow(); KTextEditor::DocumentPrivate *doc = new KTextEditor::DocumentPrivate(true, false, toplevel); KTextEditor::ViewPrivate *view = static_cast(doc->createView(nullptr)); bool outputWasCustomised = false; TestScriptEnv *env = new TestScriptEnv(doc, outputWasCustomised); const QUrl url = QUrl::fromLocalFile(QLatin1String(TEST_DATA_DIR"bug317111.txt")); doc->openUrl(url); // load buggy script QFile scriptFile(QLatin1String(JS_DATA_DIR"commands/utils.js")); QVERIFY(scriptFile.exists()); QVERIFY(scriptFile.open(QFile::ReadOnly)); QJSValue result = env->engine()->evaluate(QString::fromLatin1(scriptFile.readAll()), scriptFile.fileName()); QVERIFY2(!result.isError(), result.toString().toUtf8().constData()); // view must be visible... view->show(); view->resize(900, 800); view->setCursorPosition(Cursor(0, 0)); // evaluate test-script qDebug() << "attempting crash by calling KTextEditor::DocumentPrivate::defStyle(-1, 0)"; QFile sourceFile(QLatin1String(TEST_DATA_DIR "bug317111.js")); QVERIFY(sourceFile.open(QFile::ReadOnly)); QTextStream stream(&sourceFile); stream.setCodec("UTF8"); QString code = stream.readAll(); sourceFile.close(); // execute script result = env->engine()->evaluate(code, QLatin1String(TEST_DATA_DIR "bug317111.txt"), 1); QVERIFY2(!result.isError(), result.toString().toUtf8().constData()); qDebug() << "PASS (no crash)"; } diff --git a/autotests/src/bug317111.h b/autotests/src/bug317111.h index f909ab04..ac4a7dd3 100644 --- a/autotests/src/bug317111.h +++ b/autotests/src/bug317111.h @@ -1,40 +1,40 @@ /* This file is part of the KDE libraries * Copyright (C) 2012 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. */ #ifndef KATE_BUG_317111_TEST_H #define KATE_BUG_317111_TEST_H -#include +#include class BugTest : public QObject { Q_OBJECT public: BugTest(); ~BugTest(); private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void tryCrash(); }; #endif diff --git a/autotests/src/codecompletiontestmodel.h b/autotests/src/codecompletiontestmodel.h index 95428493..77ab32a5 100644 --- a/autotests/src/codecompletiontestmodel.h +++ b/autotests/src/codecompletiontestmodel.h @@ -1,63 +1,63 @@ /* This file is part of the KDE project Copyright (C) 2005 Hamish Rodda 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 CODECOMPLETIONTEST_H #define CODECOMPLETIONTEST_H #include #include namespace KTextEditor { class View; class CodeCompletionInterface; } class CodeCompletionTestModel : public KTextEditor::CodeCompletionModel { Q_OBJECT public: CodeCompletionTestModel(KTextEditor::View *parent = nullptr, const QString &startText = QString()); KTextEditor::View *view() const; KTextEditor::CodeCompletionInterface *cc() const; - void completionInvoked(KTextEditor::View *view, const KTextEditor::Range &range, InvocationType invocationType) Q_DECL_OVERRIDE; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; + void completionInvoked(KTextEditor::View *view, const KTextEditor::Range &range, InvocationType invocationType) override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; private: QString m_startText; bool m_autoStartText; }; class AbbreviationCodeCompletionTestModel : public CodeCompletionTestModel { Q_OBJECT public: AbbreviationCodeCompletionTestModel(KTextEditor::View *parent = nullptr, const QString &startText = QString()); - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; private: QStringList m_items; }; #endif diff --git a/autotests/src/codecompletiontestmodels.h b/autotests/src/codecompletiontestmodels.h index b1dc646e..3c296fed 100644 --- a/autotests/src/codecompletiontestmodels.h +++ b/autotests/src/codecompletiontestmodels.h @@ -1,166 +1,166 @@ /* This file is part of the KDE libraries Copyright (C) 2008 Niko Sams 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_COMPLETIONTESTMODELS_H #define KATE_COMPLETIONTESTMODELS_H #include "codecompletiontestmodel.h" #include #include #include using namespace KTextEditor; class CustomRangeModel : public CodeCompletionTestModel, public CodeCompletionModelControllerInterface { Q_OBJECT Q_INTERFACES(KTextEditor::CodeCompletionModelControllerInterface) public: CustomRangeModel(KTextEditor::View *parent = nullptr, const QString &startText = QString()) : CodeCompletionTestModel(parent, startText) {} - Range completionRange(View *view, const Cursor &position) Q_DECL_OVERRIDE + Range completionRange(View *view, const Cursor &position) override { Range range = CodeCompletionModelControllerInterface::completionRange(view, position); if (range.start().column() > 0) { KTextEditor::Range preRange(Cursor(range.start().line(), range.start().column() - 1), Cursor(range.start().line(), range.start().column())); qDebug() << preRange << view->document()->text(preRange); if (view->document()->text(preRange) == "$") { range.expandToRange(preRange); qDebug() << "using custom completion range" << range; } } return range; } - bool shouldAbortCompletion(View *view, const Range &range, const QString ¤tCompletion) Q_DECL_OVERRIDE + bool shouldAbortCompletion(View *view, const Range &range, const QString ¤tCompletion) override { Q_UNUSED(view); Q_UNUSED(range); static const QRegExp allowedText("^\\$?(\\w*)"); return !allowedText.exactMatch(currentCompletion); } }; class CustomAbortModel : public CodeCompletionTestModel, public CodeCompletionModelControllerInterface { Q_OBJECT Q_INTERFACES(KTextEditor::CodeCompletionModelControllerInterface) public: CustomAbortModel(KTextEditor::View *parent = nullptr, const QString &startText = QString()) : CodeCompletionTestModel(parent, startText) {} - bool shouldAbortCompletion(View *view, const Range &range, const QString ¤tCompletion) Q_DECL_OVERRIDE + bool shouldAbortCompletion(View *view, const Range &range, const QString ¤tCompletion) override { Q_UNUSED(view); Q_UNUSED(range); static const QRegExp allowedText("^([\\w-]*)"); return !allowedText.exactMatch(currentCompletion); } }; class EmptyFilterStringModel : public CodeCompletionTestModel, public CodeCompletionModelControllerInterface { Q_OBJECT Q_INTERFACES(KTextEditor::CodeCompletionModelControllerInterface) public: EmptyFilterStringModel(KTextEditor::View *parent = nullptr, const QString &startText = QString()) : CodeCompletionTestModel(parent, startText) {} - QString filterString(View *, const Range &, const Cursor &) Q_DECL_OVERRIDE + QString filterString(View *, const Range &, const Cursor &) override { return QString(); } }; class UpdateCompletionRangeModel : public CodeCompletionTestModel, public CodeCompletionModelControllerInterface { Q_OBJECT Q_INTERFACES(KTextEditor::CodeCompletionModelControllerInterface) public: UpdateCompletionRangeModel(KTextEditor::View *parent = nullptr, const QString &startText = QString()) : CodeCompletionTestModel(parent, startText) {} - Range updateCompletionRange(View *view, const Range &range) Q_DECL_OVERRIDE + Range updateCompletionRange(View *view, const Range &range) override { Q_UNUSED(view); if (view->document()->text(range) == QString("ab")) { return Range(Cursor(range.start().line(), 0), range.end()); } return range; } - bool shouldAbortCompletion(View *view, const Range &range, const QString ¤tCompletion) Q_DECL_OVERRIDE + bool shouldAbortCompletion(View *view, const Range &range, const QString ¤tCompletion) override { Q_UNUSED(view); Q_UNUSED(range); Q_UNUSED(currentCompletion); return false; } }; class StartCompletionModel : public CodeCompletionTestModel, public CodeCompletionModelControllerInterface { Q_OBJECT Q_INTERFACES(KTextEditor::CodeCompletionModelControllerInterface) public: StartCompletionModel(KTextEditor::View *parent = nullptr, const QString &startText = QString()) : CodeCompletionTestModel(parent, startText) {} - bool shouldStartCompletion(View *view, const QString &insertedText, bool userInsertion, const Cursor &position) Q_DECL_OVERRIDE + bool shouldStartCompletion(View *view, const QString &insertedText, bool userInsertion, const Cursor &position) override { Q_UNUSED(view); Q_UNUSED(userInsertion); Q_UNUSED(position); if (insertedText.isEmpty()) { return false; } QChar lastChar = insertedText.at(insertedText.count() - 1); if (lastChar == '%') { return true; } return false; } }; class ImmideatelyAbortCompletionModel : public CodeCompletionTestModel, public CodeCompletionModelControllerInterface { Q_OBJECT Q_INTERFACES(KTextEditor::CodeCompletionModelControllerInterface) public: ImmideatelyAbortCompletionModel(KTextEditor::View *parent = nullptr, const QString &startText = QString()) : CodeCompletionTestModel(parent, startText) {} - bool shouldAbortCompletion(KTextEditor::View *view, const KTextEditor::Range &range, const QString ¤tCompletion) Q_DECL_OVERRIDE + bool shouldAbortCompletion(KTextEditor::View *view, const KTextEditor::Range &range, const QString ¤tCompletion) override { Q_UNUSED(view); Q_UNUSED(range); Q_UNUSED(currentCompletion); return true; } }; #endif diff --git a/autotests/src/commands_test.h b/autotests/src/commands_test.h index 77be099b..948235e1 100644 --- a/autotests/src/commands_test.h +++ b/autotests/src/commands_test.h @@ -1,35 +1,35 @@ /* 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 COMMANDS_TEST_H #define COMMANDS_TEST_H -#include +#include #include "script_test_base.h" class CommandsTest : public ScriptTestBase { Q_OBJECT private Q_SLOTS: void initTestCase(); void utils_data(); void utils(); }; #endif // COMMANDS_TEST_H diff --git a/autotests/src/configinterface_test.h b/autotests/src/configinterface_test.h index 108b1172..75517e09 100644 --- a/autotests/src/configinterface_test.h +++ b/autotests/src/configinterface_test.h @@ -1,38 +1,38 @@ /* This file is part of the KDE libraries Copyright (C) 2017 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. */ #ifndef KATE_CONFIG_INTERFACE_TEST_H #define KATE_CONFIG_INTERFACE_TEST_H -#include +#include class KateConfigInterfaceTest : public QObject { Q_OBJECT public: KateConfigInterfaceTest(); ~KateConfigInterfaceTest(); private Q_SLOTS: void testDocument(); void testView(); }; #endif // KATE_CONFIG_INTERFACE_TEST_H diff --git a/autotests/src/indenttest.h b/autotests/src/indenttest.h index 4582e52e..7bcfbded 100644 --- a/autotests/src/indenttest.h +++ b/autotests/src/indenttest.h @@ -1,68 +1,68 @@ /* 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 INDENTTEST_H #define INDENTTEST_H -#include +#include #include "script_test_base.h" class IndentTest : public ScriptTestBase { Q_OBJECT private Q_SLOTS: void initTestCase(); void testPython_data(); void testPython(); void testCstyle_data(); void testCstyle(); void testCppstyle_data(); void testCppstyle(); void testCMake_data(); void testCMake(); void testRuby_data(); void testRuby(); void testHaskell_data(); void testHaskell(); void testLatex_data(); void testLatex(); void testPascal_data(); void testPascal(); void testAda_data(); void testAda(); void testXml_data(); void testXml(); void testNormal_data(); void testNormal(); void testReplicode_data(); void testReplicode(); }; #endif // INDENTTEST_H diff --git a/autotests/src/katedocument_test.cpp b/autotests/src/katedocument_test.cpp index 52637838..dea86ee1 100644 --- a/autotests/src/katedocument_test.cpp +++ b/autotests/src/katedocument_test.cpp @@ -1,463 +1,526 @@ /* 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::testModelines() +{ + // honor document variable indent-width + { + KTextEditor::DocumentPrivate doc; + QCOMPARE(doc.config()->indentationWidth(), 4); + doc.readVariableLine(QStringLiteral("kate: indent-width 3;")); + QCOMPARE(doc.config()->indentationWidth(), 3); + } + + // honor document variable indent-width with * wildcard + { + KTextEditor::DocumentPrivate doc; + QCOMPARE(doc.config()->indentationWidth(), 4); + doc.readVariableLine(QStringLiteral("kate-wildcard(*): indent-width 3;")); + QCOMPARE(doc.config()->indentationWidth(), 3); + } + + // ignore document variable indent-width, since the wildcard does not match + { + KTextEditor::DocumentPrivate doc; + QCOMPARE(doc.config()->indentationWidth(), 4); + doc.readVariableLine(QStringLiteral("kate-wildcard(*.txt): indent-width 3;")); + QCOMPARE(doc.config()->indentationWidth(), 4); + } + + // document variable indent-width, since the wildcard does not match + { + KTextEditor::DocumentPrivate doc; + doc.openUrl(QUrl::fromLocalFile(QLatin1String(TEST_DATA_DIR"modelines.txt"))); + QVERIFY(!doc.isEmpty()); + + // ignore wrong wildcard + QCOMPARE(doc.config()->indentationWidth(), 4); + doc.readVariableLine(QStringLiteral("kate-wildcard(*.bar): indent-width 3;")); + QCOMPARE(doc.config()->indentationWidth(), 4); + + // read correct wildcard + QCOMPARE(doc.config()->indentationWidth(), 4); + doc.readVariableLine(QStringLiteral("kate-wildcard(*.txt): indent-width 5;")); + QCOMPARE(doc.config()->indentationWidth(), 5); + + // honor correct wildcard + QCOMPARE(doc.config()->indentationWidth(), 5); + doc.readVariableLine(QStringLiteral("kate-wildcard(*.foo;*.txt;*.bar): indent-width 6;")); + QCOMPARE(doc.config()->indentationWidth(), 6); + + // ignore incorrect mimetype + QCOMPARE(doc.config()->indentationWidth(), 6); + doc.readVariableLine(QStringLiteral("kate-mimetype(text/unknown): indent-width 7;")); + QCOMPARE(doc.config()->indentationWidth(), 6); + + // honor correct mimetype + QCOMPARE(doc.config()->indentationWidth(), 6); + doc.readVariableLine(QStringLiteral("kate-mimetype(text/plain): indent-width 8;")); + QCOMPARE(doc.config()->indentationWidth(), 8); + + // honor correct mimetype + QCOMPARE(doc.config()->indentationWidth(), 8); + doc.readVariableLine(QStringLiteral("kate-mimetype(text/foo;text/plain;text/bar): indent-width 9;")); + QCOMPARE(doc.config()->indentationWidth(), 9); + } +} void KateDocumentTest::testDefStyleNum() { KTextEditor::DocumentPrivate doc; doc.setText("foo\nbar\nasdf"); QCOMPARE(doc.defStyleNum(0, 0), 0); } void KateDocumentTest::testTypeCharsWithSurrogateAndNewLine() { KTextEditor::DocumentPrivate doc; auto view = static_cast(doc.createView(nullptr)); const uint surrogateUcs4String[] = { 0x1f346, '\n', 0x1f346, 0 }; const auto surrogateString = QString::fromUcs4(surrogateUcs4String); doc.typeChars(view, surrogateString); QCOMPARE(doc.text(), surrogateString); } void KateDocumentTest::testRemoveComposedCharacters() { KTextEditor::DocumentPrivate doc; auto view = static_cast(doc.createView(nullptr)); view->config()->setBackspaceRemoveComposed(true); 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/autotests/src/katedocument_test.h b/autotests/src/katedocument_test.h index b8936a7f..713ba23e 100644 --- a/autotests/src/katedocument_test.h +++ b/autotests/src/katedocument_test.h @@ -1,61 +1,62 @@ /* 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. */ #ifndef KATE_DOCUMENT_TEST_H #define KATE_DOCUMENT_TEST_H -#include +#include class KateDocumentTest : public QObject { Q_OBJECT public: KateDocumentTest(); ~KateDocumentTest(); public Q_SLOTS: void initTestCase(); private Q_SLOTS: void testWordWrap(); void testReplaceQStringList(); void testMovingInterfaceSignals(); void testSetTextPerformance(); void testRemoveTextPerformance(); void testForgivingApiUsage(); void testRemoveMultipleLines(); void testInsertNewline(); void testInsertAfterEOF(); void testReplaceTabs(); void testDigest(); + void testModelines(); void testDefStyleNum(); void testTypeCharsWithSurrogateAndNewLine(); void testRemoveComposedCharacters(); }; #endif // KATE_DOCUMENT_TEST_H diff --git a/autotests/src/kateencodingtest.cpp b/autotests/src/kateencodingtest.cpp index 84a86946..5bb6cb17 100644 --- a/autotests/src/kateencodingtest.cpp +++ b/autotests/src/kateencodingtest.cpp @@ -1,63 +1,63 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 Christoph Cullmann * 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 #include "katetextbuffer.h" -#include +#include int main(int argc, char *argv[]) { // construct core app QCoreApplication app(argc, argv); // test mode KTextEditor::EditorPrivate::enableUnitTestMode(); // get arguments QString encoding = app.arguments().at(1); QString inFile = app.arguments().at(2); QString outFile = app.arguments().at(3); Kate::TextBuffer buffer(nullptr); // set codec buffer.setFallbackTextCodec(QTextCodec::codecForName("ISO 8859-15")); buffer.setTextCodec(QTextCodec::codecForName(encoding.toLatin1())); // switch to Mac EOL, this will test eol detection, as files are normal unix or dos buffer.setEndOfLineMode(Kate::TextBuffer::eolMac); // load file bool encodingErrors = false; bool tooLongLines = false; int longestLineLoaded; if (!buffer.load(inFile, encodingErrors, tooLongLines, longestLineLoaded, false) || encodingErrors) { return 1; } // save file if (!buffer.save(outFile)) { return 1; } return 0; } diff --git a/autotests/src/katefoldingtest.h b/autotests/src/katefoldingtest.h index 9482678b..73ec4747 100644 --- a/autotests/src/katefoldingtest.h +++ b/autotests/src/katefoldingtest.h @@ -1,39 +1,39 @@ /* This file is part of the KDE libraries Copyright (C) 2013 Dominik Haumann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KATE_FOLDING_TEST_H #define KATE_FOLDING_TEST_H -#include +#include class KateFoldingTest : public QObject { Q_OBJECT public Q_SLOTS: void initTestCase(); void cleanupTestCase(); private Q_SLOTS: void testCrash311866(); void testBug295632(); void testCrash367466(); }; #endif // KATE_FOLDING_TEST_H diff --git a/autotests/src/katesyntaxtest.h b/autotests/src/katesyntaxtest.h index 5c105a05..2e3348be 100644 --- a/autotests/src/katesyntaxtest.h +++ b/autotests/src/katesyntaxtest.h @@ -1,38 +1,38 @@ /* This file is part of the KDE libraries Copyright (C) 2013 Dominik Haumann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KATE_SYNTAX_TEST_H #define KATE_SYNTAX_TEST_H -#include +#include class KateSyntaxTest : public QObject { Q_OBJECT public Q_SLOTS: void initTestCase(); void cleanupTestCase(); private Q_SLOTS: void testSyntaxHighlighting_data(); void testSyntaxHighlighting(); }; #endif // KATE_FOLDING_TEST_H diff --git a/autotests/src/katetextbuffertest.h b/autotests/src/katetextbuffertest.h index 7a3df3c1..257c8e47 100644 --- a/autotests/src/katetextbuffertest.h +++ b/autotests/src/katetextbuffertest.h @@ -1,46 +1,46 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 Christoph Cullmann * 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. */ #ifndef KATEBUFFERTEST_H #define KATEBUFFERTEST_H -#include -#include +#include +#include class KateTextBufferTest : public QObject { Q_OBJECT public: KateTextBufferTest(); virtual ~KateTextBufferTest(); private Q_SLOTS: void basicBufferTest(); void wrapLineTest(); void insertRemoveTextTest(); void cursorTest(); void foldingTest(); void nestedFoldingTest(); void saveFileInUnwritableFolder(); void saveFileWithElevatedPrivileges(); }; #endif // KATEBUFFERTEST_H diff --git a/autotests/src/kateview_test.cpp b/autotests/src/kateview_test.cpp index a0880f81..d9b56725 100644 --- a/autotests/src/kateview_test.cpp +++ b/autotests/src/kateview_test.cpp @@ -1,396 +1,433 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kateview_test.h" #include "moc_kateview_test.cpp" #include #include #include #include #include #include #include #include #include using namespace KTextEditor; QTEST_MAIN(KateViewTest) KateViewTest::KateViewTest() : QObject() { KTextEditor::EditorPrivate::enableUnitTestMode(); } KateViewTest::~KateViewTest() { } void KateViewTest::testCoordinatesToCursor() { KTextEditor::DocumentPrivate doc(false, false); doc.setText("Hi World!\nHi\n"); KTextEditor::View* view1 = static_cast(doc.createView(nullptr)); view1->resize(400, 300); view1->show(); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(0, 2))), KTextEditor::Cursor(0, 2)); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(1, 1))), KTextEditor::Cursor(1, 1)); // behind end of line should give an invalid cursor QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(1, 5))), KTextEditor::Cursor::invalid()); QCOMPARE(view1->cursorToCoordinate(KTextEditor::Cursor(3, 1)), QPoint(-1, -1)); // check consistency between cursorToCoordinate(view->cursorPosition() and cursorPositionCoordinates() // random position view1->setCursorPosition(KTextEditor::Cursor(0, 3)); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(view1->cursorPosition())), KTextEditor::Cursor(0, 3)); QCOMPARE(view1->coordinatesToCursor(view1->cursorPositionCoordinates()), KTextEditor::Cursor(0, 3)); // end of line view1->setCursorPosition(KTextEditor::Cursor(0, 9)); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(0, 9))), KTextEditor::Cursor(0, 9)); QCOMPARE(view1->coordinatesToCursor(view1->cursorPositionCoordinates()), KTextEditor::Cursor(0, 9)); // empty line view1->setCursorPosition(KTextEditor::Cursor(2, 0)); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(2, 0))), KTextEditor::Cursor(2, 0)); QCOMPARE(view1->coordinatesToCursor(view1->cursorPositionCoordinates()), KTextEditor::Cursor(2, 0)); // same test again, but with message widget on top visible KTextEditor::Message *message = new KTextEditor::Message("Jo World!", KTextEditor::Message::Information); doc.postMessage(message); // wait 500ms until show animation is finished, so the message widget is visible QTest::qWait(500); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(0, 2))), KTextEditor::Cursor(0, 2)); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(1, 1))), KTextEditor::Cursor(1, 1)); // behind end of line should give an invalid cursor QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(1, 5))), KTextEditor::Cursor::invalid()); QCOMPARE(view1->cursorToCoordinate(KTextEditor::Cursor(3, 1)), QPoint(-1, -1)); } void KateViewTest::testCursorToCoordinates() { KTextEditor::DocumentPrivate doc(false, false); doc.setText("int a;"); KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); view->config()->setDynWordWrap(true); view->show(); // don't crash, see https://bugs.kde.org/show_bug.cgi?id=337863 view->cursorToCoordinate(Cursor(0, 0)); view->cursorToCoordinate(Cursor(1, 0)); view->cursorToCoordinate(Cursor(-1, 0)); } void KateViewTest::testReloadMultipleViews() { QTemporaryFile file("XXXXXX.cpp"); file.open(); QTextStream stream(&file); const QString line = "const char* foo = \"asdf\"\n"; for (int i = 0; i < 200; ++i) { stream << line; } stream << flush; file.close(); KTextEditor::DocumentPrivate doc; QVERIFY(doc.openUrl(QUrl::fromLocalFile(file.fileName()))); QCOMPARE(doc.highlightingMode(), QString("C++")); KTextEditor::ViewPrivate *view1 = new KTextEditor::ViewPrivate(&doc, nullptr); KTextEditor::ViewPrivate *view2 = new KTextEditor::ViewPrivate(&doc, nullptr); view1->show(); view2->show(); QCOMPARE(doc.views().count(), 2); QVERIFY(doc.documentReload()); } void KateViewTest::testTabCursorOnReload() { // testcase for https://bugs.kde.org/show_bug.cgi?id=258480 QTemporaryFile file("XXXXXX.cpp"); file.open(); QTextStream stream(&file); stream << "\tfoo\n"; file.close(); KTextEditor::DocumentPrivate doc; QVERIFY(doc.openUrl(QUrl::fromLocalFile(file.fileName()))); KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); const KTextEditor::Cursor cursor(0, 4); view->setCursorPosition(cursor); QCOMPARE(view->cursorPosition(), cursor); QVERIFY(doc.documentReload()); QCOMPARE(view->cursorPosition(), cursor); } void KateViewTest::testLowerCaseBlockSelection() { // testcase for https://bugs.kde.org/show_bug.cgi?id=258480 KTextEditor::DocumentPrivate doc; doc.setText("nY\nnYY\n"); KTextEditor::ViewPrivate *view1 = new KTextEditor::ViewPrivate(&doc, nullptr); view1->setBlockSelection(true); view1->setSelection(Range(0, 1, 1, 3)); view1->lowercase(); QCOMPARE(doc.text(), QString("ny\nnyy\n")); } +namespace +{ + QWidget *findViewInternal(KTextEditor::View* view) + { + foreach (QObject* child, view->children()) { + if (child->metaObject()->className() == QByteArrayLiteral("KateViewInternal")) { + return qobject_cast(child); + } + } + return nullptr; + } +} + void KateViewTest::testSelection() { // see also: https://bugs.kde.org/show_bug.cgi?id=277422 // wrong behavior before: // Open file with text // click at end of some line (A) and drag to right, i.e. without selecting anything // click somewhere else (B) // shift click to another place (C) // => expected: selection from B to C // => actual: selection from A to C QTemporaryFile file("XXXXXX.txt"); file.open(); QTextStream stream(&file); stream << "A\n" << "B\n" << "C"; stream << flush; file.close(); KTextEditor::DocumentPrivate doc; QVERIFY(doc.openUrl(QUrl::fromLocalFile(file.fileName()))); KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); view->resize(100, 200); view->show(); - QObject *internalView = nullptr; - foreach (QObject* child, view->children()) { - if (child->metaObject()->className() == QByteArrayLiteral("KateViewInternal")) { - internalView = child; - break; - } - } + + QObject *internalView = findViewInternal(view); QVERIFY(internalView); const QPoint afterA = view->cursorToCoordinate(Cursor(0, 1)); const QPoint afterB = view->cursorToCoordinate(Cursor(1, 1)); const QPoint afterC = view->cursorToCoordinate(Cursor(2, 1)); // click after A QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonPress, afterA, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonRelease, afterA, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCOMPARE(view->cursorPosition(), Cursor(0, 1)); // drag to right QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonPress, afterA, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseMove, afterA + QPoint(50, 0), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonRelease, afterA + QPoint(50, 0), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCOMPARE(view->cursorPosition(), Cursor(0, 1)); QVERIFY(!view->selection()); // click after C QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonPress, afterC, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonRelease, afterC, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCOMPARE(view->cursorPosition(), Cursor(2, 1)); // shift+click after B QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonPress, afterB, Qt::LeftButton, Qt::LeftButton, Qt::ShiftModifier)); QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonRelease, afterB, Qt::LeftButton, Qt::LeftButton, Qt::ShiftModifier)); QCOMPARE(view->cursorPosition(), Cursor(1, 1)); QCOMPARE(view->selectionRange(), Range(1, 1, 2, 1)); } void KateViewTest::testKillline() { KTextEditor::DocumentPrivate doc; doc.insertLines(0, { "foo", "bar", "baz" }); KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); view->setCursorPositionInternal(KTextEditor::Cursor(1, 2)); view->killLine(); QCOMPARE(doc.text(), QLatin1String("foo\nbaz\n")); doc.clear(); QVERIFY(doc.isEmpty()); doc.insertLines(0, { "foo", "bar", "baz", "xxx" }); view->setCursorPositionInternal(KTextEditor::Cursor(1, 2)); view->shiftDown(); view->killLine(); QCOMPARE(doc.text(), QLatin1String("foo\nxxx\n")); } +void KateViewTest::testScrollPastEndOfDocument() +{ +#if 0 // bug still exists, see bug 306745 + KTextEditor::DocumentPrivate doc; + doc.setText(QStringLiteral("0000000000\n" + "1111111111\n" + "2222222222\n" + "3333333333\n" + "4444444444")); + QCOMPARE(doc.lines(), 5); + + KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); + view->setCursorPosition({ 3, 5 }); + view->resize(400, 300); + view->show(); + + // enable "[x] Scroll past end of document" + view->config()->setScrollPastEnd(true); + QCOMPARE(view->config()->scrollPastEnd(), true); + + // disable dynamic word wrap + view->config()->setDynWordWrap(false); + QCOMPARE(view->config()->dynWordWrap(), false); + + view->scrollDown(); + view->scrollDown(); + view->scrollDown(); + // at this point, only lines 3333333333 and 4444444444 are visible. + view->down(); + QCOMPARE(view->cursorPosition(), KTextEditor::Cursor(4, 5)); + // verify, that only lines 3333333333 and 4444444444 are still visible. + QCOMPARE(view->firstDisplayedLineInternal(KTextEditor::View::RealLine), 3); +#endif +} + void KateViewTest::testFoldFirstLine() { QTemporaryFile file("XXXXXX.cpp"); file.open(); QTextStream stream(&file); stream << "/**\n" << " * foo\n" << " */\n" << "\n" << "int main() {}\n"; file.close(); KTextEditor::DocumentPrivate doc; QVERIFY(doc.openUrl(QUrl::fromLocalFile(file.fileName()))); QCOMPARE(doc.highlightingMode(), QString("C++")); KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); view->config()->setFoldFirstLine(false); view->setCursorPosition({4, 0}); // initially, nothing is folded QVERIFY(view->textFolding().isLineVisible(1)); // now change the config, and expect the header to be folded view->config()->setFoldFirstLine(true); qint64 foldedRangeId = 0; QVERIFY(!view->textFolding().isLineVisible(1, &foldedRangeId)); // now unfold the range QVERIFY(view->textFolding().unfoldRange(foldedRangeId)); QVERIFY(view->textFolding().isLineVisible(1)); // and save the file, we do not expect the folding to change then doc.setModified(true); doc.saveFile(); QVERIFY(view->textFolding().isLineVisible(1)); // now reload the document, nothing should change doc.setModified(false); QVERIFY(doc.documentReload()); QVERIFY(view->textFolding().isLineVisible(1)); } // test for bug https://bugs.kde.org/374163 void KateViewTest::testDragAndDrop() { KTextEditor::DocumentPrivate doc(false, false); doc.setText("line0\n" "line1\n" "line2\n" "\n" "line4"); KTextEditor::View* view = static_cast(doc.createView(nullptr)); view->show(); view->resize(400, 300); - QWidget *internalView = nullptr; - foreach (QObject* child, view->children()) { - if (child->metaObject()->className() == QByteArrayLiteral("KateViewInternal")) { - internalView = qobject_cast(child); - break; - } - } + QWidget *internalView = findViewInternal(view); QVERIFY(internalView); // select "line1\n" view->setSelection(Range(1, 0, 2, 0)); QCOMPARE(view->selectionRange(), Range(1, 0, 2, 0)); QVERIFY(QTest::qWaitForWindowExposed(view)); const QPoint startDragPos = internalView->mapFrom(view, view->cursorToCoordinate(KTextEditor::Cursor(1, 2))); const QPoint endDragPos = internalView->mapFrom(view, view->cursorToCoordinate(KTextEditor::Cursor(3, 0))); const QPoint gStartDragPos = internalView->mapToGlobal(startDragPos); const QPoint gEndDragPos = internalView->mapToGlobal(endDragPos); // now drag and drop selected text to Cursor(3, 0) QMouseEvent pressEvent(QEvent::MouseButtonPress, startDragPos, gStartDragPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QCoreApplication::sendEvent(internalView, &pressEvent); // ugly workaround: Drag & Drop has own blocking event queue. Therefore, we need a single-shot timer to // break out of the blocking event queue, see (*) QTimer::singleShot(50, [&](){ QMouseEvent moveEvent(QEvent::MouseMove, endDragPos + QPoint(5, 0), gEndDragPos + QPoint(5, 0), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QMouseEvent releaseEvent(QEvent::MouseButtonRelease, endDragPos, gEndDragPos, Qt::LeftButton, Qt::NoButton, Qt::NoModifier); QCoreApplication::sendEvent(internalView, &moveEvent); QCoreApplication::sendEvent(internalView, &releaseEvent); }); // (*) this somehow blocks... QMouseEvent moveEvent1(QEvent::MouseMove, endDragPos + QPoint(10, 0), gEndDragPos + QPoint(10, 0), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QCoreApplication::sendEvent(internalView, &moveEvent1); QTest::qWait(100); // final tests of dragged text QCOMPARE(doc.text(), QString("line0\n" "line2\n" "line1\n" "\n" "line4")); QCOMPARE(view->cursorPosition(), KTextEditor::Cursor(3, 0)); QCOMPARE(view->selectionRange(), Range(2, 0, 3, 0)); } // kate: indent-mode cstyle; indent-width 4; replace-tabs on; diff --git a/autotests/src/kateview_test.h b/autotests/src/kateview_test.h index a48720d9..c449bcf7 100644 --- a/autotests/src/kateview_test.h +++ b/autotests/src/kateview_test.h @@ -1,50 +1,51 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KATE_VIEW_TEST_H #define KATE_VIEW_TEST_H -#include +#include class KateViewTest : public QObject { Q_OBJECT public: KateViewTest(); ~KateViewTest(); private Q_SLOTS: void testReloadMultipleViews(); void testTabCursorOnReload(); void testLowerCaseBlockSelection(); void testCoordinatesToCursor(); void testCursorToCoordinates(); void testSelection(); void testKillline(); + void testScrollPastEndOfDocument(); void testFoldFirstLine(); void testDragAndDrop(); }; #endif // KATE_VIEW_TEST_H diff --git a/autotests/src/kte_documentcursor.h b/autotests/src/kte_documentcursor.h index 3e18a124..440750e8 100644 --- a/autotests/src/kte_documentcursor.h +++ b/autotests/src/kte_documentcursor.h @@ -1,43 +1,43 @@ /* This file is part of the KDE libraries Copyright (C) 2012 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. */ #ifndef KTE_DOCUMENTCURSOR_TEST_H #define KTE_DOCUMENTCURSOR_TEST_H -#include +#include class DocumentCursorTest : public QObject { Q_OBJECT public: DocumentCursorTest(); ~DocumentCursorTest(); private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void testConvenienceApi(); void testOperators(); void testValidTextPosition(); }; #endif diff --git a/autotests/src/messagetest.h b/autotests/src/messagetest.h index 7c669cb6..6398cf1f 100644 --- a/autotests/src/messagetest.h +++ b/autotests/src/messagetest.h @@ -1,45 +1,45 @@ /* This file is part of the KDE libraries Copyright (C) 2013 Dominik Haumann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KTEXTEDITOR_MESSAGE_TEST_H #define KTEXTEDITOR_MESSAGE_TEST_H -#include +#include class MessageTest : public QObject { Q_OBJECT public Q_SLOTS: void initTestCase(); void cleanupTestCase(); private Q_SLOTS: void testPostMessage(); void testAutoHide(); private: void testAutoHideAfterUserInteraction(); void testMessageQueue(); void testPriority(); void testCreateView(); void testHideView(); void testHideViewAfterUserInteraction(); }; #endif // KTEXTEDITOR_MESSAGE_TEST_H diff --git a/autotests/src/modificationsystem_test.h b/autotests/src/modificationsystem_test.h index 87c9655b..600641fa 100644 --- a/autotests/src/modificationsystem_test.h +++ b/autotests/src/modificationsystem_test.h @@ -1,57 +1,57 @@ /* This file is part of the KDE libraries Copyright (C) 2011 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. */ #ifndef KATE_MODIFICATION_SYSTEM_TEST_H #define KATE_MODIFICATION_SYSTEM_TEST_H -#include +#include /** * Test the complete Line Modification System. * Covered classes: * - KateModification* in part/undo/ * - modification flags in Kate::TextLine in part/buffer/ */ class ModificationSystemTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void testInsertText(); void testRemoveText(); void testInsertLine(); void testRemoveLine(); void testWrapLineMid(); void testWrapLineAtEnd(); void testWrapLineAtStart(); void testUnWrapLine(); void testUnWrapLine1Empty(); void testUnWrapLine2Empty(); void testNavigation(); }; #endif diff --git a/autotests/src/movingcursor_test.h b/autotests/src/movingcursor_test.h index 6421ed2f..fc435194 100644 --- a/autotests/src/movingcursor_test.h +++ b/autotests/src/movingcursor_test.h @@ -1,40 +1,40 @@ /* 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. */ #ifndef KATE_MOVINGCURSOR_TEST_H #define KATE_MOVINGCURSOR_TEST_H -#include +#include class MovingCursorTest : public QObject { Q_OBJECT public: MovingCursorTest(); ~MovingCursorTest(); private Q_SLOTS: void testMovingCursor(); void testConvenienceApi(); void testOperators(); void testInvalidMovingCursor(); }; #endif // KATE_MOVINGCURSOR_TEST_H diff --git a/autotests/src/movingrange_test.cpp b/autotests/src/movingrange_test.cpp index a4aae438..38f0b96e 100644 --- a/autotests/src/movingrange_test.cpp +++ b/autotests/src/movingrange_test.cpp @@ -1,443 +1,443 @@ /* 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 "movingrange_test.h" #include "moc_movingrange_test.cpp" #include #include #include #include #include #include #include using namespace KTextEditor; QTEST_MAIN(MovingRangeTest) class RangeFeedback : public MovingRangeFeedback { public: RangeFeedback() : MovingRangeFeedback() { reset(); } - void rangeEmpty(MovingRange * /*range*/) Q_DECL_OVERRIDE + void rangeEmpty(MovingRange * /*range*/) override { m_rangeEmptyCalled = true; } - void rangeInvalid(MovingRange * /*range*/) Q_DECL_OVERRIDE + void rangeInvalid(MovingRange * /*range*/) override { m_rangeInvalidCalled = true; } - void mouseEnteredRange(MovingRange * /*range*/, View * /*view*/) Q_DECL_OVERRIDE + void mouseEnteredRange(MovingRange * /*range*/, View * /*view*/) override { m_mouseEnteredRangeCalled = true; } - void mouseExitedRange(MovingRange * /*range*/, View * /*view*/) Q_DECL_OVERRIDE + void mouseExitedRange(MovingRange * /*range*/, View * /*view*/) override { m_mouseExitedRangeCalled = true; } - void caretEnteredRange(MovingRange * /*range*/, View * /*view*/) Q_DECL_OVERRIDE + void caretEnteredRange(MovingRange * /*range*/, View * /*view*/) override { m_caretEnteredRangeCalled = true; } - void caretExitedRange(MovingRange * /*range*/, View * /*view*/) Q_DECL_OVERRIDE + void caretExitedRange(MovingRange * /*range*/, View * /*view*/) override { m_caretExitedRangeCalled = true; } // // Test functions to reset feedback watcher // public: void reset() { m_rangeEmptyCalled = false; m_rangeInvalidCalled = false; m_mouseEnteredRangeCalled = false; m_mouseExitedRangeCalled = false; m_caretEnteredRangeCalled = false; m_caretExitedRangeCalled = false; } void verifyReset() { QVERIFY(!m_rangeEmptyCalled); QVERIFY(!m_rangeInvalidCalled); QVERIFY(!m_mouseEnteredRangeCalled); QVERIFY(!m_mouseExitedRangeCalled); QVERIFY(!m_caretEnteredRangeCalled); QVERIFY(!m_caretExitedRangeCalled); } bool rangeEmptyCalled() const { return m_rangeEmptyCalled; } bool rangeInvalidCalled() const { return m_rangeInvalidCalled; } bool mouseEnteredRangeCalled() const { return m_mouseEnteredRangeCalled; } bool mouseExitedRangeCalled() const { return m_mouseExitedRangeCalled; } bool caretEnteredRangeCalled() const { return m_caretEnteredRangeCalled; } bool caretExitedRangeCalled() const { return m_caretExitedRangeCalled; } private: bool m_rangeEmptyCalled; bool m_rangeInvalidCalled; bool m_mouseEnteredRangeCalled; bool m_mouseExitedRangeCalled; bool m_caretEnteredRangeCalled; bool m_caretExitedRangeCalled; }; MovingRangeTest::MovingRangeTest() : QObject() { KTextEditor::EditorPrivate::enableUnitTestMode(); } MovingRangeTest::~MovingRangeTest() { } // tests: // - RangeFeedback::rangeEmpty void MovingRangeTest::testFeedbackEmptyRange() { KTextEditor::DocumentPrivate doc; // the range created below will span the 'x' characters QString text("..xxxx\n" "xxxx.."); doc.setText(text); // create range feedback RangeFeedback rf; // allow empty MovingRange *range = doc.newMovingRange(Range(Cursor(0, 2), Cursor(1, 4)), KTextEditor::MovingRange::DoNotExpand, KTextEditor::MovingRange::AllowEmpty); range->setFeedback(&rf); rf.verifyReset(); // remove exact range doc.removeText(range->toRange()); QVERIFY(rf.rangeEmptyCalled()); QVERIFY(!rf.rangeInvalidCalled()); QVERIFY(!rf.mouseEnteredRangeCalled()); QVERIFY(!rf.mouseExitedRangeCalled()); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); // clear document: should call rangeInvalid rf.reset(); rf.verifyReset(); doc.clear(); QVERIFY(rf.rangeInvalidCalled()); QVERIFY(!rf.rangeEmptyCalled()); QVERIFY(!rf.mouseEnteredRangeCalled()); QVERIFY(!rf.mouseExitedRangeCalled()); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); // setText: should behave just like clear document: call rangeInvalid again doc.setText(text); range->setRange(Range(Cursor(0, 2), Cursor(1, 4))); rf.reset(); rf.verifyReset(); doc.setText("--yyyy\nyyyy--"); QVERIFY(rf.rangeInvalidCalled()); QVERIFY(!rf.rangeEmptyCalled()); QVERIFY(!rf.mouseEnteredRangeCalled()); QVERIFY(!rf.mouseExitedRangeCalled()); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); // now remove entire document range. In this case, emptyRange should be called // instead of rangeInvalid doc.setText(text); range->setRange(Range(Cursor(0, 2), Cursor(1, 4))); rf.reset(); rf.verifyReset(); doc.removeText(doc.documentRange()); QVERIFY(rf.rangeEmptyCalled()); QVERIFY(!rf.rangeInvalidCalled()); QVERIFY(!rf.mouseEnteredRangeCalled()); QVERIFY(!rf.mouseExitedRangeCalled()); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); } // tests: // - RangeFeedback::rangeInvalid void MovingRangeTest::testFeedbackInvalidRange() { KTextEditor::DocumentPrivate doc; // the range created below will span the 'x' characters QString text("..xxxx\n" "xxxx.."); doc.setText(text); // create range feedback RangeFeedback rf; // allow empty MovingRange *range = doc.newMovingRange(Range(Cursor(0, 2), Cursor(1, 4)), KTextEditor::MovingRange::DoNotExpand, KTextEditor::MovingRange::InvalidateIfEmpty); range->setFeedback(&rf); rf.verifyReset(); // remove exact range doc.removeText(range->toRange()); QVERIFY(!rf.rangeEmptyCalled()); QVERIFY(rf.rangeInvalidCalled()); QVERIFY(!rf.mouseEnteredRangeCalled()); QVERIFY(!rf.mouseExitedRangeCalled()); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); // clear document: should call rangeInvalid again doc.setText(text); range->setRange(Range(Cursor(0, 2), Cursor(1, 4))); rf.reset(); rf.verifyReset(); doc.clear(); QVERIFY(rf.rangeInvalidCalled()); QVERIFY(!rf.rangeEmptyCalled()); QVERIFY(!rf.mouseEnteredRangeCalled()); QVERIFY(!rf.mouseExitedRangeCalled()); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); // setText: should behave just like clear document: call rangeInvalid again doc.setText(text); range->setRange(Range(Cursor(0, 2), Cursor(1, 4))); rf.reset(); rf.verifyReset(); doc.setText("--yyyy\nyyyy--"); QVERIFY(rf.rangeInvalidCalled()); QVERIFY(!rf.rangeEmptyCalled()); QVERIFY(!rf.mouseEnteredRangeCalled()); QVERIFY(!rf.mouseExitedRangeCalled()); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); // now remove entire document range. Call rangeInvalid again doc.setText(text); range->setRange(Range(Cursor(0, 2), Cursor(1, 4))); rf.reset(); rf.verifyReset(); doc.removeText(doc.documentRange()); QVERIFY(rf.rangeInvalidCalled()); QVERIFY(!rf.rangeEmptyCalled()); QVERIFY(!rf.mouseEnteredRangeCalled()); QVERIFY(!rf.mouseExitedRangeCalled()); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); } // tests: // - RangeFeedback::caretEnteredRange // - RangeFeedback::caretExitedRange void MovingRangeTest::testFeedbackCaret() { KTextEditor::DocumentPrivate doc; // the range created below will span the 'x' characters QString text("..xxxx\n" "xxxx.."); doc.setText(text); KTextEditor::ViewPrivate *view = static_cast(doc.createView(nullptr)); // create range feedback RangeFeedback rf; // first test: with ExpandLeft | ExpandRight { view->setCursorPosition(Cursor(1, 6)); MovingRange *range = doc.newMovingRange(Range(Cursor(0, 2), Cursor(1, 4)), KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight, KTextEditor::MovingRange::InvalidateIfEmpty); rf.reset(); range->setFeedback(&rf); rf.verifyReset(); // left view->cursorLeft(); QCOMPARE(view->cursorPosition(), Cursor(1, 5)); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); view->cursorLeft(); QCOMPARE(view->cursorPosition(), Cursor(1, 4)); QVERIFY(rf.caretEnteredRangeCalled()); // ExpandRight: include cursor already now QVERIFY(!rf.caretExitedRangeCalled()); rf.reset(); view->cursorLeft(); QCOMPARE(view->cursorPosition(), Cursor(1, 3)); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); rf.reset(); view->up(); QCOMPARE(view->cursorPosition(), Cursor(0, 3)); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); rf.reset(); view->cursorLeft(); QCOMPARE(view->cursorPosition(), Cursor(0, 2)); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); rf.reset(); view->cursorLeft(); QCOMPARE(view->cursorPosition(), Cursor(0, 1)); // ExpandLeft: now we left it, not before QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(rf.caretExitedRangeCalled()); delete range; } // second test: with DoNotExpand { view->setCursorPosition(Cursor(1, 6)); MovingRange *range = doc.newMovingRange(Range(Cursor(0, 2), Cursor(1, 4)), KTextEditor::MovingRange::DoNotExpand, KTextEditor::MovingRange::InvalidateIfEmpty); rf.reset(); range->setFeedback(&rf); rf.verifyReset(); // left view->cursorLeft(); QCOMPARE(view->cursorPosition(), Cursor(1, 5)); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); view->cursorLeft(); QCOMPARE(view->cursorPosition(), Cursor(1, 4)); QVERIFY(!rf.caretEnteredRangeCalled()); // DoNotExpand: does not include cursor QVERIFY(!rf.caretExitedRangeCalled()); rf.reset(); view->cursorLeft(); QCOMPARE(view->cursorPosition(), Cursor(1, 3)); QVERIFY(rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); rf.reset(); view->up(); QCOMPARE(view->cursorPosition(), Cursor(0, 3)); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); rf.reset(); view->cursorLeft(); QCOMPARE(view->cursorPosition(), Cursor(0, 2)); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(rf.caretExitedRangeCalled()); // DoNotExpand: that's why we leave already now rf.reset(); view->cursorLeft(); QCOMPARE(view->cursorPosition(), Cursor(0, 1)); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); delete range; } } // tests: // - RangeFeedback::mouseEnteredRange // - RangeFeedback::mouseExitedRange void MovingRangeTest::testFeedbackMouse() { KTextEditor::DocumentPrivate doc; // the range created below will span the 'x' characters QString text("..xxxx\n" "xxxx.."); doc.setText(text); KTextEditor::ViewPrivate *view = static_cast(doc.createView(nullptr)); view->setCursorPosition(Cursor(1, 6)); view->show(); view->resize(200, 100); // create range feedback RangeFeedback rf; QVERIFY(!rf.mouseEnteredRangeCalled()); QVERIFY(!rf.mouseExitedRangeCalled()); // allow empty MovingRange *range = doc.newMovingRange(Range(Cursor(0, 2), Cursor(1, 4)), KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight, KTextEditor::MovingRange::InvalidateIfEmpty); range->setFeedback(&rf); rf.verifyReset(); // left (nothing) QTest::mouseMove(view, view->cursorToCoordinate(Cursor(0, 0)) + QPoint(0, 5)); QTest::qWait(200); // process mouse events. do not move mouse manually QVERIFY(!rf.mouseEnteredRangeCalled()); QVERIFY(!rf.mouseExitedRangeCalled()); // middle (enter) rf.reset(); QTest::mouseMove(view, view->cursorToCoordinate(Cursor(0, 3)) + QPoint(0, 5)); QTest::qWait(200); // process mouse events. do not move mouse manually QVERIFY(rf.mouseEnteredRangeCalled()); QVERIFY(!rf.mouseExitedRangeCalled()); // right (exit) rf.reset(); QTest::mouseMove(view, view->cursorToCoordinate(Cursor(1, 6)) + QPoint(10, 5)); QTest::qWait(200); // process mouse events. do not move mouse manually QVERIFY(!rf.mouseEnteredRangeCalled()); QVERIFY(rf.mouseExitedRangeCalled()); } diff --git a/autotests/src/movingrange_test.h b/autotests/src/movingrange_test.h index fae44bd5..132064ac 100644 --- a/autotests/src/movingrange_test.h +++ b/autotests/src/movingrange_test.h @@ -1,40 +1,40 @@ /* 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. */ #ifndef KATE_MOVINGRANGE_TEST_H #define KATE_MOVINGRANGE_TEST_H -#include +#include class MovingRangeTest : public QObject { Q_OBJECT public: MovingRangeTest(); ~MovingRangeTest(); private Q_SLOTS: void testFeedbackEmptyRange(); void testFeedbackInvalidRange(); void testFeedbackCaret(); void testFeedbackMouse(); }; #endif // KATE_MOVINGRANGE_TEST_H diff --git a/autotests/src/plaintextsearch_test.cpp b/autotests/src/plaintextsearch_test.cpp index 179e9bf1..5fe24623 100644 --- a/autotests/src/plaintextsearch_test.cpp +++ b/autotests/src/plaintextsearch_test.cpp @@ -1,169 +1,167 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Bernhard Beschow 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 "plaintextsearch_test.h" #include "moc_plaintextsearch_test.cpp" #include #include #include #include QTEST_MAIN(PlainTextSearchTest) QtMessageHandler PlainTextSearchTest::s_msgHandler = nullptr; void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) { switch (type) { case QtDebugMsg: /* do nothing */ break; default: PlainTextSearchTest::s_msgHandler(type, context, msg); } } void PlainTextSearchTest::initTestCase() { KTextEditor::EditorPrivate::enableUnitTestMode(); s_msgHandler = qInstallMessageHandler(myMessageOutput); } void PlainTextSearchTest::cleanupTestCase() { qInstallMessageHandler(nullptr); } PlainTextSearchTest::PlainTextSearchTest() : QObject() - , m_doc(nullptr) - , m_search(nullptr) { } PlainTextSearchTest::~PlainTextSearchTest() { } void PlainTextSearchTest::init() { m_doc = new KTextEditor::DocumentPrivate(false, false, nullptr, this); m_search = new KatePlainTextSearch(m_doc, Qt::CaseSensitive, false); } void PlainTextSearchTest::cleanup() { delete m_search; delete m_doc; } void PlainTextSearchTest::testSearchBackward_data() { QTest::addColumn("searchRange"); QTest::addColumn("expectedResult"); QTest::newRow("") << KTextEditor::Range(0, 0, 1, 10) << KTextEditor::Range(1, 6, 1, 10); QTest::newRow("") << KTextEditor::Range(0, 0, 1, 5) << KTextEditor::Range(1, 0, 1, 4); QTest::newRow("") << KTextEditor::Range(0, 0, 1, 0) << KTextEditor::Range(0, 10, 0, 14); } void PlainTextSearchTest::testSearchBackward() { QFETCH(KTextEditor::Range, searchRange); QFETCH(KTextEditor::Range, expectedResult); m_doc->setText(QLatin1String("aaaa aaaa aaaa\n" "aaaa aaaa")); QCOMPARE(m_search->search(QLatin1String("aaaa"), searchRange, true), expectedResult); } void PlainTextSearchTest::testSingleLineDocument_data() { QTest::addColumn("searchRange"); QTest::addColumn("forwardResult"); QTest::addColumn("backwardResult"); QTest::newRow("[a a a a a a a a a a a a]") << KTextEditor::Range(0, 0, 0, 23) << KTextEditor::Range(0, 0, 0, 5) << KTextEditor::Range(0, 18, 0, 23); QTest::newRow("[a a a a a a a a a a a ]a") << KTextEditor::Range(0, 0, 0, 22) << KTextEditor::Range(0, 0, 0, 5) << KTextEditor::Range(0, 16, 0, 21); QTest::newRow("a[ a a a a a a a a a a a]") << KTextEditor::Range(0, 1, 0, 23) << KTextEditor::Range(0, 2, 0, 7) << KTextEditor::Range(0, 18, 0, 23); QTest::newRow("a[ a a a a a a a a a a ]a") << KTextEditor::Range(0, 1, 0, 22) << KTextEditor::Range(0, 2, 0, 7) << KTextEditor::Range(0, 16, 0, 21); QTest::newRow("[a a a a] a a a a a a a a") << KTextEditor::Range(0, 0, 0, 7) << KTextEditor::Range(0, 0, 0, 5) << KTextEditor::Range(0, 2, 0, 7); QTest::newRow("[a a a ]a a a a a a a a a") << KTextEditor::Range(0, 0, 0, 6) << KTextEditor::Range(0, 0, 0, 5) << KTextEditor::Range(0, 0, 0, 5); QTest::newRow("[a a a] a a a a a a a a a") << KTextEditor::Range(0, 0, 0, 5) << KTextEditor::Range(0, 0, 0, 5) << KTextEditor::Range(0, 0, 0, 5); QTest::newRow("[a a ]a a a a a a a a a a") << KTextEditor::Range(0, 0, 0, 4) << KTextEditor::Range::invalid() << KTextEditor::Range::invalid(); QTest::newRow("a a a a a a a a [a a a a]") << KTextEditor::Range(0, 16, 0, 23) << KTextEditor::Range(0, 16, 0, 21) << KTextEditor::Range(0, 18, 0, 23); QTest::newRow("a a a a a a a a a[ a a a]") << KTextEditor::Range(0, 17, 0, 23) << KTextEditor::Range(0, 18, 0, 23) << KTextEditor::Range(0, 18, 0, 23); QTest::newRow("a a a a a a a a a [a a a]") << KTextEditor::Range(0, 18, 0, 23) << KTextEditor::Range(0, 18, 0, 23) << KTextEditor::Range(0, 18, 0, 23); QTest::newRow("a a a a a a a a a a[ a a]") << KTextEditor::Range(0, 19, 0, 23) << KTextEditor::Range::invalid() << KTextEditor::Range::invalid(); QTest::newRow("a a a a a[ a a a a] a a a") << KTextEditor::Range(0, 9, 0, 17) << KTextEditor::Range(0, 10, 0, 15) << KTextEditor::Range(0, 12, 0, 17); QTest::newRow("a a a a a[ a a] a a a a a") << KTextEditor::Range(0, 9, 0, 13) << KTextEditor::Range::invalid() << KTextEditor::Range::invalid(); } void PlainTextSearchTest::testSingleLineDocument() { QFETCH(KTextEditor::Range, searchRange); QFETCH(KTextEditor::Range, forwardResult); QFETCH(KTextEditor::Range, backwardResult); m_doc->setText(QLatin1String("a a a a a a a a a a a a")); QCOMPARE(m_search->search(QLatin1String("a a a"), searchRange, false), forwardResult); QCOMPARE(m_search->search(QLatin1String("a a a"), searchRange, true), backwardResult); } void PlainTextSearchTest::testMultilineSearch_data() { QTest::addColumn("pattern"); QTest::addColumn("inputRange"); QTest::addColumn("forwardResult"); QTest::newRow("") << "a a a\na a\na a a" << KTextEditor::Range(0, 0, 2, 5) << KTextEditor::Range(0, 0, 2, 5); QTest::newRow("") << "a a a\na a\na a " << KTextEditor::Range(0, 0, 2, 5) << KTextEditor::Range(0, 0, 2, 4); QTest::newRow("") << "a a a\na a\na a" << KTextEditor::Range(0, 0, 2, 5) << KTextEditor::Range(0, 0, 2, 3); QTest::newRow("") << "a a a\na a\na" << KTextEditor::Range(0, 0, 2, 5) << KTextEditor::Range(0, 0, 2, 1); QTest::newRow("") << "a a a\na a\n" << KTextEditor::Range(0, 0, 2, 5) << KTextEditor::Range(0, 0, 2, 0); QTest::newRow("") << "a a a\na a" << KTextEditor::Range(0, 0, 2, 5) << KTextEditor::Range(0, 0, 1, 3); QTest::newRow("") << "a a\na a" << KTextEditor::Range(0, 0, 2, 5) << KTextEditor::Range(0, 2, 1, 3); QTest::newRow("") << "a a\na a\na a" << KTextEditor::Range(0, 0, 2, 5) << KTextEditor::Range(0, 2, 2, 3); QTest::newRow("") << "\na a\na a" << KTextEditor::Range(0, 0, 2, 5) << KTextEditor::Range(0, 5, 2, 3); QTest::newRow("") << "\na a\n" << KTextEditor::Range(0, 0, 2, 5) << KTextEditor::Range(0, 5, 2, 0); QTest::newRow("") << "a a a\na a\na a a" << KTextEditor::Range(0, 0, 2, 4) << KTextEditor::Range::invalid(); QTest::newRow("") << "a a a\na a\na a " << KTextEditor::Range(0, 0, 2, 4) << KTextEditor::Range(0, 0, 2, 4); QTest::newRow("") << "a a a\na a\n" << KTextEditor::Range(0, 0, 2, 0) << KTextEditor::Range(0, 0, 2, 0); QTest::newRow("") << "a a a\na a\n" << KTextEditor::Range(0, 0, 1, 3) << KTextEditor::Range::invalid(); QTest::newRow("") << "a a\n" << KTextEditor::Range(0, 0, 1, 3) << KTextEditor::Range(0, 2, 1, 0); QTest::newRow("") << "a \n" << KTextEditor::Range(0, 0, 1, 3) << KTextEditor::Range::invalid(); } void PlainTextSearchTest::testMultilineSearch() { QFETCH(QString, pattern); QFETCH(KTextEditor::Range, inputRange); QFETCH(KTextEditor::Range, forwardResult); m_doc->setText(QLatin1String("a a a\n" "a a\n" "a a a")); QCOMPARE(m_search->search(pattern, inputRange, false), forwardResult); } diff --git a/autotests/src/plaintextsearch_test.h b/autotests/src/plaintextsearch_test.h index 2c35fbe4..e1617d91 100644 --- a/autotests/src/plaintextsearch_test.h +++ b/autotests/src/plaintextsearch_test.h @@ -1,60 +1,60 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Bernhard Beschow 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_PLAINTEXTSEARCH_TEST_H #define KATE_PLAINTEXTSEARCH_TEST_H -#include +#include namespace KTextEditor { class DocumentPrivate; } class KatePlainTextSearch; class PlainTextSearchTest : public QObject { Q_OBJECT public: PlainTextSearchTest(); virtual ~PlainTextSearchTest(); private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void init(); void cleanup(); void testSearchBackward_data(); void testSearchBackward(); void testSingleLineDocument_data(); void testSingleLineDocument(); void testMultilineSearch_data(); void testMultilineSearch(); private: - KTextEditor::DocumentPrivate *m_doc; - KatePlainTextSearch *m_search; + KTextEditor::DocumentPrivate *m_doc = nullptr; + KatePlainTextSearch *m_search = nullptr; public: static QtMessageHandler s_msgHandler; }; #endif diff --git a/autotests/src/range_test.h b/autotests/src/range_test.h index 7fa5a07a..c16caeed 100644 --- a/autotests/src/range_test.h +++ b/autotests/src/range_test.h @@ -1,48 +1,48 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Christoph Cullmann Copyright (C) 2005 Hamish Rodda 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_RANGE_TEST_H #define KATE_RANGE_TEST_H -#include +#include #include class RangeTest : public QObject { Q_OBJECT public: RangeTest(); ~RangeTest(); private Q_SLOTS: void testTextEditorRange(); void testTextRange(); void testInsertText(); void testCornerCaseInsertion(); void testCursorStringConversion(); void testRangeStringConversion(); private: void rangeCheck(KTextEditor::Range &valid); }; #endif diff --git a/autotests/src/regexpsearch_test.h b/autotests/src/regexpsearch_test.h index e09579d2..e0a47c9f 100644 --- a/autotests/src/regexpsearch_test.h +++ b/autotests/src/regexpsearch_test.h @@ -1,57 +1,57 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Bernhard Beschow 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_REGEXPSEARCH_TEST_H #define KATE_REGEXPSEARCH_TEST_H -#include +#include class RegExpSearchTest : public QObject { Q_OBJECT public: RegExpSearchTest(); virtual ~RegExpSearchTest(); private Q_SLOTS: void testReplaceEscapeSequences_data(); void testReplaceEscapeSequences(); void testReplacementReferences_data(); void testReplacementReferences(); void testReplacementCaseConversion_data(); void testReplacementCaseConversion(); void testReplacementCounter_data(); void testReplacementCounter(); void testAnchoredRegexp_data(); void testAnchoredRegexp(); void testSearchForward(); void testSearchBackwardInSelection(); void test(); }; #endif diff --git a/autotests/src/revision_test.h b/autotests/src/revision_test.h index 8e5988d0..bdd0369d 100644 --- a/autotests/src/revision_test.h +++ b/autotests/src/revision_test.h @@ -1,38 +1,38 @@ /* 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. */ #ifndef KATE_REVISION_TEST_H #define KATE_REVISION_TEST_H -#include +#include class RevisionTest : public QObject { Q_OBJECT public: RevisionTest(); ~RevisionTest(); private Q_SLOTS: void testTransformCursor(); void testTransformRange(); }; #endif // KATE_REVISION_TEST_H diff --git a/autotests/src/script_test_base.h b/autotests/src/script_test_base.h index 04d6cb6a..89222042 100644 --- a/autotests/src/script_test_base.h +++ b/autotests/src/script_test_base.h @@ -1,56 +1,56 @@ /* 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 SCRIPT_TEST_H #define SCRIPT_TEST_H -#include -#include -#include +#include +#include +#include class TestScriptEnv; namespace KTextEditor { class DocumentPrivate; } namespace KTextEditor { class ViewPrivate; } class QMainWindow; class ScriptTestBase : public QObject { Q_OBJECT protected: void initTestCase(); void cleanupTestCase(); typedef QPair Failure; typedef QList ExpectedFailures; void getTestData(const QString &script); void runTest(const ExpectedFailures &failures); QByteArray digestForFile(const QString &file); TestScriptEnv *m_env; KTextEditor::DocumentPrivate *m_document; QMainWindow *m_toplevel; bool m_outputWasCustomised; QStringList m_commands; KTextEditor::ViewPrivate *m_view; QString m_section; // dir name in testdata/ QString m_script_dir; // dir name in part/script/data/ public: static QtMessageHandler m_msgHandler; }; #endif // SCRIPT_TEST_H diff --git a/autotests/src/scriptdocument_test.cpp b/autotests/src/scriptdocument_test.cpp index ce85e36d..5f587f46 100644 --- a/autotests/src/scriptdocument_test.cpp +++ b/autotests/src/scriptdocument_test.cpp @@ -1,131 +1,128 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Bernhard Beschow 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 "scriptdocument_test.h" #include #include "ktexteditor/cursor.h" #include #include #include #include #include QTEST_MAIN(ScriptDocumentTest) QtMessageHandler ScriptDocumentTest::s_msgHandler = nullptr; void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) { switch (type) { case QtDebugMsg: /* do nothing */ break; default: ScriptDocumentTest::s_msgHandler(type, context, msg); } } void ScriptDocumentTest::initTestCase() { KTextEditor::EditorPrivate::enableUnitTestMode(); s_msgHandler = qInstallMessageHandler(myMessageOutput); } void ScriptDocumentTest::cleanupTestCase() { qInstallMessageHandler(nullptr); } ScriptDocumentTest::ScriptDocumentTest() : QObject() - , m_doc(nullptr) - , m_view(nullptr) - , m_scriptDoc(nullptr) { } ScriptDocumentTest::~ScriptDocumentTest() { } void ScriptDocumentTest::init() { m_doc = new KTextEditor::DocumentPrivate; m_view = m_doc->createView(nullptr); m_scriptDoc = new KateScriptDocument(nullptr, this); m_scriptDoc->setDocument(m_doc); } void ScriptDocumentTest::cleanup() { delete m_scriptDoc; delete m_view; delete m_doc; } #if 0 void ScriptDocumentTest::testRfind_data() { QTest::addColumn("searchRange"); QTest::addColumn("expectedResult"); QTest::newRow("") << KTextEditor::Range(0, 0, 1, 10) << KTextEditor::Range(1, 6, 1, 10); QTest::newRow("") << KTextEditor::Range(0, 0, 1, 5) << KTextEditor::Range(1, 0, 1, 4); QTest::newRow("") << KTextEditor::Range(0, 0, 1, 0) << KTextEditor::Range(0, 10, 0, 14); } void ScriptDocumentTest::testRfind() { QFETCH(KTextEditor::Range, searchRange); QFETCH(KTextEditor::Range, expectedResult); m_doc->setText("aaaa aaaa aaaa\n" "aaaa aaaa"); QCOMPARE(m_search->search(searchRange, "aaaa", true), expectedResult); } #endif void ScriptDocumentTest::testRfind_data() { QTest::addColumn("searchStart"); QTest::addColumn("result"); QTest::newRow("a a a a a a a a a a a a|") << KTextEditor::Cursor(0, 23) << KTextEditor::Cursor(0, 18); QTest::newRow("a a a a a a a a a a a |a") << KTextEditor::Cursor(0, 22) << KTextEditor::Cursor(0, 16); QTest::newRow("a a a a| a a a a a a a a") << KTextEditor::Cursor(0, 7) << KTextEditor::Cursor(0, 2); QTest::newRow("a a a |a a a a a a a a a") << KTextEditor::Cursor(0, 6) << KTextEditor::Cursor(0, 0); QTest::newRow("a a a| a a a a a a a a a") << KTextEditor::Cursor(0, 5) << KTextEditor::Cursor(0, 0); QTest::newRow("a a |a a a a a a a a a a") << KTextEditor::Cursor(0, 4) << KTextEditor::Cursor::invalid(); } void ScriptDocumentTest::testRfind() { QFETCH(KTextEditor::Cursor, searchStart); QFETCH(KTextEditor::Cursor, result); m_scriptDoc->setText("a a a a a a a a a a a a"); KTextEditor::Cursor cursor = m_scriptDoc->rfind(searchStart, "a a a"); QCOMPARE(cursor, result); } #include "moc_scriptdocument_test.cpp" diff --git a/autotests/src/scriptdocument_test.h b/autotests/src/scriptdocument_test.h index 3e684085..91fb74f9 100644 --- a/autotests/src/scriptdocument_test.h +++ b/autotests/src/scriptdocument_test.h @@ -1,60 +1,60 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Bernhard Beschow 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_SCRIPTDOCUMENT_TEST_H #define KATE_SCRIPTDOCUMENT_TEST_H -#include +#include namespace KTextEditor { class View; } namespace KTextEditor { class DocumentPrivate; } class KateScriptDocument; class ScriptDocumentTest : public QObject { Q_OBJECT public: ScriptDocumentTest(); virtual ~ScriptDocumentTest(); private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void init(); void cleanup(); void testRfind_data(); void testRfind(); private: - KTextEditor::DocumentPrivate *m_doc; - KTextEditor::View *m_view; - KateScriptDocument *m_scriptDoc; + KTextEditor::DocumentPrivate *m_doc = nullptr; + KTextEditor::View *m_view = nullptr; + KateScriptDocument *m_scriptDoc = nullptr; public: static QtMessageHandler s_msgHandler; }; #endif diff --git a/autotests/src/scripting_test.cpp b/autotests/src/scripting_test.cpp index f54c1fd6..a62193e2 100644 --- a/autotests/src/scripting_test.cpp +++ b/autotests/src/scripting_test.cpp @@ -1,74 +1,74 @@ /** * This file is part of the KDE project * * Copyright (C) 2013 Gerald Senarclens de Grancy * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ //BEGIN Includes #include "scripting_test.h" #include "kateview.h" #include "katedocument.h" #include "kateconfig.h" #include "katecmd.h" #include "kateglobal.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 "script_test_base.h" #include "testutils.h" QTEST_MAIN(ScriptingTest) #define FAILURE( test, comment ) qMakePair( (test), (comment) ) void ScriptingTest::initTestCase() { ScriptTestBase::initTestCase(); m_section = "scripting"; m_script_dir = ""; } void ScriptingTest::bugs_data() { getTestData("bugs"); } void ScriptingTest::bugs() { runTest(ExpectedFailures()); } diff --git a/autotests/src/scripting_test.h b/autotests/src/scripting_test.h index 3b226f23..06d190ee 100644 --- a/autotests/src/scripting_test.h +++ b/autotests/src/scripting_test.h @@ -1,35 +1,35 @@ /* 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 SCRIPTINGTEST_H #define SCRIPTINGTEST_H -#include +#include #include "script_test_base.h" class ScriptingTest : public ScriptTestBase { Q_OBJECT private Q_SLOTS: void initTestCase(); void bugs_data(); void bugs(); }; #endif // SCRIPTINGTEST_H diff --git a/autotests/src/searchbar_test.h b/autotests/src/searchbar_test.h index 0fa1cb5e..1fc9eed3 100644 --- a/autotests/src/searchbar_test.h +++ b/autotests/src/searchbar_test.h @@ -1,79 +1,79 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Bernhard Beschow 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_SEARCHBAR_TEST_H #define KATE_SEARCHBAR_TEST_H -#include +#include class SearchBarTest : public QObject { Q_OBJECT public: SearchBarTest(); ~SearchBarTest(); public Q_SLOTS: void initTestCase(); void cleanupTestCase(); private Q_SLOTS: void testFindNextIncremental(); void testSetMatchCaseIncremental(); void testSetMatchCasePower(); void testSetSelectionOnlyPower(); void testSetSearchPattern_data(); void testSetSearchPattern(); void testSetSelectionOnly(); void testFindAll_data(); void testFindAll(); void testReplaceAll(); void testFindSelectionForward_data(); void testFindSelectionForward(); void testRemoveWithSelectionForward_data(); void testRemoveWithSelectionForward(); void testRemoveInSelectionForward_data(); void testRemoveInSelectionForward(); void testReplaceWithDoubleSelecion_data(); void testReplaceWithDoubleSelecion(); void testReplaceDollar(); void testSearchHistoryIncremental(); void testSearchHistoryPower(); void testReplaceInBlockMode(); void testReplaceManyCapturesBug365124(); }; #endif diff --git a/autotests/src/templatehandler_test.h b/autotests/src/templatehandler_test.h index 055f6cdb..d396629a 100644 --- a/autotests/src/templatehandler_test.h +++ b/autotests/src/templatehandler_test.h @@ -1,63 +1,63 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Bernhard Beschow Copyright (C) 2014 Sven Brauch 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_UNDOMANAGER_TEST_H #define KATE_UNDOMANAGER_TEST_H -#include +#include class TemplateHandlerTest : public QObject { Q_OBJECT public: TemplateHandlerTest(); private Q_SLOTS: void testUndo(); void testSimpleMirror(); void testSimpleMirror_data(); void testDefaults(); void testDefaults_data(); void testDefaultMirror(); void testFunctionMirror(); void testNotEditableFields(); void testNotEditableFields_data(); void testAdjacentRanges(); void testTab(); void testTab_data(); void testExitAtCursor(); void testAutoSelection(); void testCanRetrieveSelection(); void testEscapes(); }; #endif diff --git a/autotests/src/testutils.h b/autotests/src/testutils.h index 9feab271..5a76bad5 100644 --- a/autotests/src/testutils.h +++ b/autotests/src/testutils.h @@ -1,204 +1,204 @@ /** * This file is part of the KDE project * * Copyright (C) 2001,2003 Peter Kelly (pmk@post.com) * Copyright 2006, 2007 Leo Savernik (l.savernik@aon.at) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #ifndef TESTUTILS_H #define TESTUTILS_H #include "katescriptview.h" #include "katescriptdocument.h" -#include +#include namespace KTextEditor { class ViewPrivate; } class RegressionTest; class KCmdLineArgs; class OutputObject; class KateViewObject; class KateDocumentObject; /** * @internal * The backbone of Kate's automatic regression tests. */ class TestScriptEnv : public QObject { public: explicit TestScriptEnv(KTextEditor::DocumentPrivate *part, bool &cflag); virtual ~TestScriptEnv(); QJSEngine *engine() const { return m_engine; } /** returns the output object */ OutputObject *output() const { return m_output; } private: QJSEngine *m_engine; KateViewObject *m_viewObj; KateDocumentObject *m_docObj; OutputObject *m_output; }; /** * @internal */ class KateViewObject : public KateScriptView { Q_OBJECT public: explicit KateViewObject(QJSEngine *engine, KTextEditor::ViewPrivate *view); virtual ~KateViewObject(); // Edit functions Q_INVOKABLE void keyReturn(int cnt = 1); Q_INVOKABLE void backspace(int cnt = 1); Q_INVOKABLE void deleteWordLeft(int cnt = 1); Q_INVOKABLE void keyDelete(int cnt = 1); Q_INVOKABLE void deleteWordRight(int cnt = 1); Q_INVOKABLE void transpose(int cnt = 1); Q_INVOKABLE void cursorLeft(int cnt = 1); Q_INVOKABLE void shiftCursorLeft(int cnt = 1); Q_INVOKABLE void cursorRight(int cnt = 1); Q_INVOKABLE void shiftCursorRight(int cnt = 1); Q_INVOKABLE void wordLeft(int cnt = 1); Q_INVOKABLE void shiftWordLeft(int cnt = 1); Q_INVOKABLE void wordRight(int cnt = 1); Q_INVOKABLE void shiftWordRight(int cnt = 1); Q_INVOKABLE void home(int cnt = 1); Q_INVOKABLE void shiftHome(int cnt = 1); Q_INVOKABLE void end(int cnt = 1); Q_INVOKABLE void shiftEnd(int cnt = 1); Q_INVOKABLE void up(int cnt = 1); Q_INVOKABLE void shiftUp(int cnt = 1); Q_INVOKABLE void down(int cnt = 1); Q_INVOKABLE void shiftDown(int cnt = 1); Q_INVOKABLE void scrollUp(int cnt = 1); Q_INVOKABLE void scrollDown(int cnt = 1); Q_INVOKABLE void topOfView(int cnt = 1); Q_INVOKABLE void shiftTopOfView(int cnt = 1); Q_INVOKABLE void bottomOfView(int cnt = 1); Q_INVOKABLE void shiftBottomOfView(int cnt = 1); Q_INVOKABLE void pageUp(int cnt = 1); Q_INVOKABLE void shiftPageUp(int cnt = 1); Q_INVOKABLE void pageDown(int cnt = 1); Q_INVOKABLE void shiftPageDown(int cnt = 1); Q_INVOKABLE void top(int cnt = 1); Q_INVOKABLE void shiftTop(int cnt = 1); Q_INVOKABLE void bottom(int cnt = 1); Q_INVOKABLE void shiftBottom(int cnt = 1); Q_INVOKABLE void toMatchingBracket(int cnt = 1); Q_INVOKABLE void shiftToMatchingBracket(int cnt = 1); Q_INVOKABLE bool type(const QString &str); /** * Toggle auto brackets. If you make use of it, make sure to * disable them again at the end of your test, otherwise any following tests may fail. */ Q_INVOKABLE void setAutoBrackets(bool enable = true); // Aliases Q_INVOKABLE void enter(int cnt = 1); // KeyReturn Q_INVOKABLE void cursorPrev(int cnt = 1); // CursorLeft Q_INVOKABLE void left(int cnt = 1); // CursorLeft Q_INVOKABLE void prev(int cnt = 1); // CursorLeft Q_INVOKABLE void shiftCursorPrev(int cnt = 1); // ShiftCursorLeft Q_INVOKABLE void shiftLeft(int cnt = 1); // ShiftCursorLeft Q_INVOKABLE void shiftPrev(int cnt = 1); // ShiftCursorLeft Q_INVOKABLE void cursorNext(int cnt = 1); // CursorRight Q_INVOKABLE void right(int cnt = 1); // CursorRight Q_INVOKABLE void next(int cnt = 1); // CursorRight Q_INVOKABLE void shiftCursorNext(int cnt = 1); // ShiftCursorRight Q_INVOKABLE void shiftRight(int cnt = 1); // ShiftCursorRight Q_INVOKABLE void shiftNext(int cnt = 1); // ShiftCursorRight Q_INVOKABLE void wordPrev(int cnt = 1); // WordLeft Q_INVOKABLE void shiftWordPrev(int cnt = 1); // ShiftWordLeft Q_INVOKABLE void wordNext(int cnt = 1); // WordRight Q_INVOKABLE void shiftWordNext(int cnt = 1); // ShiftWordRight private: Q_DISABLE_COPY(KateViewObject) }; /** * @internal */ class KateDocumentObject : public KateScriptDocument { Q_OBJECT public: explicit KateDocumentObject(QJSEngine *engine, KTextEditor::DocumentPrivate *doc); virtual ~KateDocumentObject(); private: Q_DISABLE_COPY(KateDocumentObject) }; /** * Customizing output to result-files. Writing any output into result files * inhibits outputting the content of the katepart after script execution, * enabling one to check for coordinates and the like. * @internal */ class OutputObject : public QObject { Q_OBJECT public: OutputObject(KTextEditor::ViewPrivate *v, bool &cflag); virtual ~OutputObject(); void output(bool cp, bool ln); Q_INVOKABLE void write(); Q_INVOKABLE void writeln(); Q_INVOKABLE void writeLn(); Q_INVOKABLE void print(); Q_INVOKABLE void println(); Q_INVOKABLE void printLn(); Q_INVOKABLE void writeCursorPosition(); Q_INVOKABLE void writeCursorPositionln(); Q_INVOKABLE void cursorPosition(); Q_INVOKABLE void cursorPositionln(); Q_INVOKABLE void cursorPositionLn(); Q_INVOKABLE void pos(); Q_INVOKABLE void posln(); Q_INVOKABLE void posLn(); private: KTextEditor::ViewPrivate *view; bool &cflag; }; #endif // TESTUTILS_H diff --git a/autotests/src/undomanager_test.h b/autotests/src/undomanager_test.h index 47fc2dab..016a2902 100644 --- a/autotests/src/undomanager_test.h +++ b/autotests/src/undomanager_test.h @@ -1,45 +1,45 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Bernhard Beschow Copyright (C) 2009 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. */ #ifndef KATE_UNDOMANAGER_TEST_H #define KATE_UNDOMANAGER_TEST_H -#include +#include class UndoManagerTest : public QObject { Q_OBJECT public: UndoManagerTest(); private Q_SLOTS: void testUndoRedoCount(); void testSafePoint(); void testCursorPosition(); void testSelectionUndo(); void testUndoWordWrapBug301367(); private: class TestDocument; }; #endif diff --git a/autotests/src/vimode/base.cpp b/autotests/src/vimode/base.cpp index d2c100f9..09d04713 100644 --- a/autotests/src/vimode/base.cpp +++ b/autotests/src/vimode/base.cpp @@ -1,334 +1,337 @@ /* * This file is part of the KDE libraries * * Copyright (C) 2014 Miquel Sabaté Solà * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include "base.h" #include "vimode/macros.h" #include "vimode/mappings.h" #include "vimode/globalstate.h" using namespace KateVi; using namespace KTextEditor; //BEGIN: BaseTest BaseTest::BaseTest() { kate_view = nullptr; kate_document = nullptr; mainWindow = new QMainWindow; - mainWindowLayout = new QVBoxLayout(mainWindow); - mainWindow->setLayout(mainWindowLayout); + auto centralWidget = new QWidget(); + mainWindowLayout = new QVBoxLayout(centralWidget); + centralWidget->setLayout(mainWindowLayout); + mainWindow->setCentralWidget(centralWidget); + mainWindow->resize(640, 480); m_codesToModifiers.insert("ctrl", Qt::ControlModifier); m_codesToModifiers.insert("alt", Qt::AltModifier); m_codesToModifiers.insert("meta", Qt::MetaModifier); m_codesToModifiers.insert("keypad", Qt::KeypadModifier); m_codesToSpecialKeys.insert("backspace", Qt::Key_Backspace); m_codesToSpecialKeys.insert("esc", Qt::Key_Escape); m_codesToSpecialKeys.insert("return", Qt::Key_Return); m_codesToSpecialKeys.insert("enter", Qt::Key_Enter); m_codesToSpecialKeys.insert("left", Qt::Key_Left); m_codesToSpecialKeys.insert("right", Qt::Key_Right); m_codesToSpecialKeys.insert("up", Qt::Key_Up); m_codesToSpecialKeys.insert("down", Qt::Key_Down); m_codesToSpecialKeys.insert("home", Qt::Key_Home); m_codesToSpecialKeys.insert("end", Qt::Key_End); m_codesToSpecialKeys.insert("delete", Qt::Key_Delete); m_codesToSpecialKeys.insert("insert", Qt::Key_Insert); m_codesToSpecialKeys.insert("pageup", Qt::Key_PageUp); m_codesToSpecialKeys.insert("pagedown", Qt::Key_PageDown); } BaseTest::~BaseTest() { delete kate_document; } void BaseTest::waitForCompletionWidgetToActivate(KTextEditor::ViewPrivate *kate_view) { const QDateTime start = QDateTime::currentDateTime(); while (start.msecsTo(QDateTime::currentDateTime()) < 1000) { if (kate_view->isCompletionActive()) { break; } QApplication::processEvents(); } QVERIFY(kate_view->isCompletionActive()); } void BaseTest::init() { delete kate_view; delete kate_document; kate_document = new KTextEditor::DocumentPrivate(false, false, nullptr, nullptr); // fixed indentation options kate_document->config()->setTabWidth(8); kate_document->config()->setIndentationWidth(2); kate_document->config()->setReplaceTabsDyn(false); kate_view = new KTextEditor::ViewPrivate(kate_document, mainWindow); mainWindowLayout->addWidget(kate_view); kate_view->setInputMode(View::ViInputMode); Q_ASSERT(kate_view->currentInputMode()->viewInputMode() == KTextEditor::View::ViInputMode); vi_input_mode = dynamic_cast(kate_view->currentInputMode()); vi_input_mode_manager = vi_input_mode->viInputModeManager(); Q_ASSERT(vi_input_mode_manager); vi_global = vi_input_mode->globalState(); Q_ASSERT(vi_global); kate_document->config()->setShowSpaces(true); // Flush out some issues in the KateRenderer when rendering spaces. kate_view->config()->setScrollBarMiniMap(false); kate_view->config()->setScrollBarPreview(false); connect(kate_document, &KTextEditor::DocumentPrivate::textInserted, this, &BaseTest::textInserted); connect(kate_document, &KTextEditor::DocumentPrivate::textRemoved, this, &BaseTest::textRemoved); } void BaseTest::TestPressKey(const QString &str) { if (m_firstBatchOfKeypressesForTest) { qDebug() << "\n\n>>> running command " << str << " on text " << kate_document->text(); } else { qDebug() << "\n>>> running further keypresses " << str << " on text " << kate_document->text(); } m_firstBatchOfKeypressesForTest = false; for (int i = 0; i < str.length(); i++) { Qt::KeyboardModifiers keyboard_modifier = Qt::NoModifier; QString key; int keyCode = -1; // Looking for keyboard modifiers if (str[i] == QChar('\\')) { int endOfModifier = -1; Qt::KeyboardModifier parsedModifier = parseCodedModifier(str, i, &endOfModifier); int endOfSpecialKey = -1; Qt::Key parsedSpecialKey = parseCodedSpecialKey(str, i, &endOfSpecialKey); if (parsedModifier != Qt::NoModifier) { keyboard_modifier = parsedModifier; // Move to the character after the '-' in the modifier. i = endOfModifier + 1; // Is this a modifier plus special key? int endOfSpecialKeyAfterModifier = -1; const Qt::Key parsedCodedSpecialKeyAfterModifier = parseCodedSpecialKey(str, i, &endOfSpecialKeyAfterModifier); if (parsedCodedSpecialKeyAfterModifier != Qt::Key_unknown) { key = QString(parsedCodedSpecialKeyAfterModifier); keyCode = parsedCodedSpecialKeyAfterModifier; i = endOfSpecialKeyAfterModifier; } } else if (parsedSpecialKey != Qt::Key_unknown) { key = QString(parsedSpecialKey); keyCode = parsedSpecialKey; i = endOfSpecialKey; } else if (str.mid(i, 2) == QString("\\:")) { int start_cmd = i + 2; for (i += 2; true; i++) { if (str.at(i) == '\\') { if (i + 1 < str.length() && str.at(i + 1) == '\\') { // A backslash within a command; skip. i += 2; } else { // End of command. break; } } } const QString commandToExecute = str.mid(start_cmd, i - start_cmd).replace("\\\\", "\\"); qDebug() << "Executing command directly from ViModeTest:\n" << commandToExecute; vi_input_mode->viModeEmulatedCommandBar()->executeCommand(commandToExecute); // We've handled the command; go back round the loop, avoiding sending // the closing \ to vi_input_mode_manager. continue; } else if (str.mid(i, 2) == QString("\\\\")) { key = QString("\\"); keyCode = Qt::Key_Backslash; i++; } else { Q_ASSERT(false); //Do not use "\" in tests except for modifiers, command mode (\\:) and literal backslashes "\\\\") } } if (keyCode == -1) { key = str[i]; keyCode = key[0].unicode(); // Kate Vim mode's internals identifier e.g. CTRL-C by Qt::Key_C plus the control modifier, // so we need to translate e.g. 'c' to Key_C. if (key[0].isLetter()) { if (key[0].toLower() == key[0]) { keyCode = keyCode - 'a' + Qt::Key_A; } else { keyCode = keyCode - 'A' + Qt::Key_A; keyboard_modifier |= Qt::ShiftModifier; } } } QKeyEvent *key_event = new QKeyEvent(QEvent::KeyPress, keyCode, keyboard_modifier, key); // Attempt to simulate how Qt usually sends events - typically, we want to send them // to kate_view->focusProxy() (which is a KateViewInternal). QWidget *destWidget = nullptr; if (QApplication::activePopupWidget()) { // According to the docs, the activePopupWidget, if present, takes all events. destWidget = QApplication::activePopupWidget(); } else if (QApplication::focusWidget()) { if (QApplication::focusWidget()->focusProxy()) { destWidget = QApplication::focusWidget()->focusProxy(); } else { destWidget = QApplication::focusWidget(); } } else { destWidget = kate_view->focusProxy(); } QApplication::postEvent(destWidget, key_event); QApplication::sendPostedEvents(); } } void BaseTest::BeginTest(const QString &original) { vi_input_mode_manager->viEnterNormalMode(); vi_input_mode->reset(); vi_input_mode_manager = vi_input_mode->viInputModeManager(); kate_document->setText(original); kate_document->undoManager()->clearUndo(); kate_document->undoManager()->clearRedo(); kate_view->setCursorPosition(Cursor(0, 0)); m_firstBatchOfKeypressesForTest = true; } void BaseTest::FinishTest_(int line, const char *file, const QString &expected, Expectation expectation, const QString &failureReason) { if (expectation == ShouldFail) { if (!QTest::qExpectFail("", failureReason.toLocal8Bit().constData(), QTest::Continue, file, line)) { return; } qDebug() << "Actual text:\n\t" << kate_document->text() << "\nShould be (for this test to pass):\n\t" << expected; } if (!QTest::qCompare(kate_document->text(), expected, "kate_document->text()", "expected_text", file, line)) { return; } Q_ASSERT(!emulatedCommandBarTextEdit()->isVisible() && "Make sure you close the command bar before the end of a test!"); } void BaseTest::DoTest_(int line, const char *file, const QString &original, const QString &command, const QString &expected, Expectation expectation, const QString &failureReason) { BeginTest(original); TestPressKey(command); FinishTest_(line, file, expected, expectation, failureReason); } Qt::KeyboardModifier BaseTest::parseCodedModifier(const QString &string, int startPos, int *destEndOfCodedModifier) { foreach (const QString &modifierCode, m_codesToModifiers.keys()) { // The "+2" is from the leading '\' and the trailing '-' if (string.mid(startPos, modifierCode.length() + 2) == QString("\\") + modifierCode + "-") { if (destEndOfCodedModifier) { // destEndOfCodeModifier lies on the trailing '-'. *destEndOfCodedModifier = startPos + modifierCode.length() + 1; Q_ASSERT(string[*destEndOfCodedModifier] == '-'); } return m_codesToModifiers.value(modifierCode); } } return Qt::NoModifier; } Qt::Key BaseTest::parseCodedSpecialKey(const QString &string, int startPos, int *destEndOfCodedKey) { foreach (const QString &specialKeyCode, m_codesToSpecialKeys.keys()) { // "+1" is for the leading '\'. if (string.mid(startPos, specialKeyCode.length() + 1) == QString("\\") + specialKeyCode) { if (destEndOfCodedKey) { *destEndOfCodedKey = startPos + specialKeyCode.length(); } return m_codesToSpecialKeys.value(specialKeyCode); } } return Qt::Key_unknown; } KateVi::EmulatedCommandBar * BaseTest::emulatedCommandBar() { KateVi::EmulatedCommandBar *emulatedCommandBar = vi_input_mode->viModeEmulatedCommandBar(); Q_ASSERT(emulatedCommandBar); return emulatedCommandBar; } QLineEdit * BaseTest::emulatedCommandBarTextEdit() { QLineEdit *emulatedCommandBarText = emulatedCommandBar()->findChild("commandtext"); Q_ASSERT(emulatedCommandBarText); return emulatedCommandBarText; } void BaseTest::ensureKateViewVisible() { mainWindow->show(); kate_view->show(); QApplication::setActiveWindow(mainWindow); kate_view->setFocus(); const QDateTime startTime = QDateTime::currentDateTime(); while (startTime.msecsTo(QDateTime::currentDateTime()) < 3000 && !mainWindow->isActiveWindow()) { QApplication::processEvents(); } QVERIFY(kate_view->isVisible()); QVERIFY(mainWindow->isActiveWindow()); } void BaseTest::clearAllMappings() { vi_global->mappings()->clear(Mappings::NormalModeMapping); vi_global->mappings()->clear(Mappings::VisualModeMapping); vi_global->mappings()->clear(Mappings::InsertModeMapping); vi_global->mappings()->clear(Mappings::CommandModeMapping); } void BaseTest::clearAllMacros() { vi_global->macros()->clear(); } void BaseTest::textInserted(Document *document, KTextEditor::Range range) { m_docChanges.append(DocChange(DocChange::TextInserted, range, document->text(range))); } void BaseTest::textRemoved(Document *document, KTextEditor::Range range) { Q_UNUSED(document); m_docChanges.append(DocChange(DocChange::TextRemoved, range)); } //END: BaseTest diff --git a/autotests/src/vimode/completion.h b/autotests/src/vimode/completion.h index 19aa6a7f..25ef3470 100644 --- a/autotests/src/vimode/completion.h +++ b/autotests/src/vimode/completion.h @@ -1,70 +1,70 @@ /* * This file is part of the KDE libraries * * Copyright (C) 2014 Miquel Sabaté Solà * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef COMPLETION_TEST_H #define COMPLETION_TEST_H #include "base.h" #include "fakecodecompletiontestmodel.h" /** * This class handles implements a completion model for the completion * tests defined in the CompletionTest class. */ class VimCodeCompletionTestModel : public KTextEditor::CodeCompletionModel { public: VimCodeCompletionTestModel(KTextEditor::View *parent); - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; KTextEditor::CodeCompletionInterface *cc() const; }; /** * This class implements a completion model used in the CompletionTest class * to test that code completion is not invoked. */ class FailTestOnInvocationModel : public KTextEditor::CodeCompletionModel { public: FailTestOnInvocationModel(KTextEditor::View *parent); - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; void failTest() const; KTextEditor::CodeCompletionInterface *cc() const; }; class CompletionTest : public BaseTest { Q_OBJECT private Q_SLOTS: void FakeCodeCompletionTests(); void CompletionTests(); private: void waitForCompletionWidgetToActivate(); void clearTrackedDocumentChanges(); }; #endif /* COMPLETION_TEST_H */ diff --git a/autotests/src/vimode/emulatedcommandbar.cpp b/autotests/src/vimode/emulatedcommandbar.cpp index c1a89b14..74938373 100644 --- a/autotests/src/vimode/emulatedcommandbar.cpp +++ b/autotests/src/vimode/emulatedcommandbar.cpp @@ -1,3377 +1,3377 @@ /* This file is part of the KDE libraries Copyright (C) 2011 Kuzmich Svyatoslav Copyright (C) 2012 - 2013 Simon St James This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include "emulatedcommandbar.h" #include #include #include #include "keys.h" #include "view.h" #include "emulatedcommandbarsetupandteardown.h" #include "vimode/mappings.h" #include "vimode/globalstate.h" #include #include #include #include #include #include #include QTEST_MAIN(EmulatedCommandBarTest) using namespace KTextEditor; using namespace KateVi; void EmulatedCommandBarTest::EmulatedCommandBarTests() { // Ensure that some preconditions for these tests are setup, and (more importantly) // ensure that they are reverted no matter how these tests end. EmulatedCommandBarSetUpAndTearDown emulatedCommandBarSetUpAndTearDown(vi_input_mode, kate_view, mainWindow); // Verify that we can get a non-null pointer to the emulated command bar. EmulatedCommandBar *emulatedCommandBar = vi_input_mode->viModeEmulatedCommandBar(); QVERIFY(emulatedCommandBar); // Should initially be hidden. QVERIFY(!emulatedCommandBar->isVisible()); // Test that "/" invokes the emulated command bar (if we are configured to use it) BeginTest(""); TestPressKey("/"); QVERIFY(emulatedCommandBar->isVisible()); QCOMPARE(emulatedCommandTypeIndicator()->text(), QString("/")); QVERIFY(emulatedCommandTypeIndicator()->isVisible()); QVERIFY(emulatedCommandBarTextEdit()); QVERIFY(emulatedCommandBarTextEdit()->text().isEmpty()); // Make sure the keypresses end up changing the text. QVERIFY(emulatedCommandBarTextEdit()->isVisible()); TestPressKey("foo"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo")); // Make sure ctrl-c dismisses it (assuming we allow Vim to steal the ctrl-c shortcut). TestPressKey("\\ctrl-c"); QVERIFY(!emulatedCommandBar->isVisible()); // Ensure that ESC dismisses it, too. BeginTest(""); TestPressKey("/"); QVERIFY(emulatedCommandBar->isVisible()); TestPressKey("\\esc"); QVERIFY(!emulatedCommandBar->isVisible()); FinishTest(""); // Ensure that Ctrl-[ dismisses it, too. BeginTest(""); TestPressKey("/"); QVERIFY(emulatedCommandBar->isVisible()); TestPressKey("\\ctrl-["); QVERIFY(!emulatedCommandBar->isVisible()); FinishTest(""); // Ensure that Enter dismisses it, too. BeginTest(""); TestPressKey("/"); QVERIFY(emulatedCommandBar->isVisible()); TestPressKey("\\enter"); QVERIFY(!emulatedCommandBar->isVisible()); FinishTest(""); // Ensure that Return dismisses it, too. BeginTest(""); TestPressKey("/"); QVERIFY(emulatedCommandBar->isVisible()); TestPressKey("\\return"); QVERIFY(!emulatedCommandBar->isVisible()); FinishTest(""); // Ensure that text is always initially empty. BeginTest(""); TestPressKey("/a\\enter"); TestPressKey("/"); QVERIFY(emulatedCommandBarTextEdit()->text().isEmpty()); TestPressKey("\\enter"); FinishTest(""); // Check backspace works. BeginTest(""); TestPressKey("/foo\\backspace"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("fo")); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-h works. BeginTest(""); TestPressKey("/bar\\ctrl-h"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("ba")); TestPressKey("\\enter"); FinishTest(""); // ctrl-h should dismiss bar when empty. BeginTest(""); TestPressKey("/\\ctrl-h"); QVERIFY(!emulatedCommandBar->isVisible()); FinishTest(""); // ctrl-h should not dismiss bar when there is stuff to the left of cursor. BeginTest(""); TestPressKey("/a\\ctrl-h"); QVERIFY(emulatedCommandBar->isVisible()); TestPressKey("\\enter"); FinishTest(""); // ctrl-h should not dismiss bar when bar is not empty, even if there is nothing to the left of cursor. BeginTest(""); TestPressKey("/a\\left\\ctrl-h"); QVERIFY(emulatedCommandBar->isVisible()); TestPressKey("\\enter"); FinishTest(""); // Same for backspace. BeginTest(""); TestPressKey("/bar\\backspace"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("ba")); TestPressKey("\\enter"); FinishTest(""); BeginTest(""); TestPressKey("/\\backspace"); QVERIFY(!emulatedCommandBar->isVisible()); FinishTest(""); BeginTest(""); TestPressKey("/a\\backspace"); QVERIFY(emulatedCommandBar->isVisible()); TestPressKey("\\enter"); FinishTest(""); BeginTest(""); TestPressKey("/a\\left\\backspace"); QVERIFY(emulatedCommandBar->isVisible()); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-b works. BeginTest(""); TestPressKey("/bar foo xyz\\ctrl-bX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("Xbar foo xyz")); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-e works. BeginTest(""); TestPressKey("/bar foo xyz\\ctrl-b\\ctrl-eX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("bar foo xyzX")); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-w works. BeginTest(""); TestPressKey("/foo bar\\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo ")); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-w works on empty command bar. BeginTest(""); TestPressKey("/\\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("")); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-w works in middle of word. BeginTest(""); TestPressKey("/foo bar\\left\\left\\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo ar")); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-w leaves the cursor in the right place when in the middle of word. BeginTest(""); TestPressKey("/foo bar\\left\\left\\ctrl-wX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo Xar")); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-w works when at the beginning of the text. BeginTest(""); TestPressKey("/foo\\left\\left\\left\\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo")); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-w works when the character to the left is a space. BeginTest(""); TestPressKey("/foo bar \\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo ")); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-w works when all characters to the left of the cursor are spaces. BeginTest(""); TestPressKey("/ \\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("")); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-w works when all characters to the left of the cursor are non-spaces. BeginTest(""); TestPressKey("/foo\\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("")); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-w does not continue to delete subsequent alphanumerics if the characters to the left of the cursor // are non-space, non-alphanumerics. BeginTest(""); TestPressKey("/foo!!!\\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo")); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-w does not continue to delete subsequent alphanumerics if the characters to the left of the cursor // are non-space, non-alphanumerics. BeginTest(""); TestPressKey("/foo!!!\\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo")); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-w deletes underscores and alphanumerics to the left of the cursor, but stops when it reaches a // character that is none of these. BeginTest(""); TestPressKey("/foo!!!_d1\\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo!!!")); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-w doesn't swallow the spaces preceding the block of non-word chars. BeginTest(""); TestPressKey("/foo !!!\\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo ")); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-w doesn't swallow the spaces preceding the word. BeginTest(""); TestPressKey("/foo 1d_\\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo ")); TestPressKey("\\enter"); FinishTest(""); // Check there is a "waiting for register" indicator, initially hidden. BeginTest(""); TestPressKey("/"); QLabel* waitingForRegisterIndicator = emulatedCommandBar->findChild("waitingforregisterindicator"); QVERIFY(waitingForRegisterIndicator); QVERIFY(!waitingForRegisterIndicator->isVisible()); QCOMPARE(waitingForRegisterIndicator->text(), QString("\"")); TestPressKey("\\enter"); FinishTest(""); // Test that ctrl-r causes it to become visible. It is displayed to the right of the text edit. BeginTest(""); TestPressKey("/\\ctrl-r"); QVERIFY(waitingForRegisterIndicator->isVisible()); QVERIFY(waitingForRegisterIndicator->x() >= emulatedCommandBarTextEdit()->x() + emulatedCommandBarTextEdit()->width()); TestPressKey("\\ctrl-c"); TestPressKey("\\ctrl-c"); FinishTest(""); // The first ctrl-c after ctrl-r (when no register entered) hides the waiting for register // indicator, but not the bar. BeginTest(""); TestPressKey("/\\ctrl-r"); QVERIFY(waitingForRegisterIndicator->isVisible()); TestPressKey("\\ctrl-c"); QVERIFY(!waitingForRegisterIndicator->isVisible()); QVERIFY(emulatedCommandBar->isVisible()); TestPressKey("\\ctrl-c"); // Dismiss the bar. FinishTest(""); // The first ctrl-c after ctrl-r (when no register entered) aborts waiting for register. BeginTest("foo"); TestPressKey("\"cyiw/\\ctrl-r\\ctrl-ca"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("a")); TestPressKey("\\ctrl-c"); // Dismiss the bar. FinishTest("foo"); // Same as above, but for ctrl-[ instead of ctrl-c. BeginTest(""); TestPressKey("/\\ctrl-r"); QVERIFY(waitingForRegisterIndicator->isVisible()); TestPressKey("\\ctrl-["); QVERIFY(!waitingForRegisterIndicator->isVisible()); QVERIFY(emulatedCommandBar->isVisible()); TestPressKey("\\ctrl-c"); // Dismiss the bar. FinishTest(""); BeginTest("foo"); TestPressKey("\"cyiw/\\ctrl-r\\ctrl-[a"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("a")); TestPressKey("\\ctrl-c"); // Dismiss the bar. FinishTest("foo"); // Check ctrl-r works with registers, and hides the "waiting for register" indicator. BeginTest("xyz"); TestPressKey("\"ayiw/foo\\ctrl-ra"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("fooxyz")); QVERIFY(!waitingForRegisterIndicator->isVisible()); TestPressKey("\\enter"); FinishTest("xyz"); // Check ctrl-r inserts text at the current cursor position. BeginTest("xyz"); TestPressKey("\"ayiw/foo\\left\\ctrl-ra"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foxyzo")); TestPressKey("\\enter"); FinishTest("xyz"); // Check ctrl-r ctrl-w inserts word under the cursor, and hides the "waiting for register" indicator. BeginTest("foo bar xyz"); TestPressKey("w/\\left\\ctrl-r\\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("bar")); QVERIFY(!waitingForRegisterIndicator->isVisible()); TestPressKey("\\enter"); FinishTest("foo bar xyz"); // Check ctrl-r ctrl-w doesn't insert the contents of register w! BeginTest("foo baz xyz"); TestPressKey("\"wyiww/\\left\\ctrl-r\\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("baz")); TestPressKey("\\enter"); FinishTest("foo baz xyz"); // Check ctrl-r ctrl-w inserts at the current cursor position. BeginTest("foo nose xyz"); TestPressKey("w/bar\\left\\ctrl-r\\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("banoser")); TestPressKey("\\enter"); FinishTest("foo nose xyz"); // Cursor position is at the end of the inserted text after ctrl-r ctrl-w. BeginTest("foo nose xyz"); TestPressKey("w/bar\\left\\ctrl-r\\ctrl-wX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("banoseXr")); TestPressKey("\\enter"); FinishTest("foo nose xyz"); // Cursor position is at the end of the inserted register contents after ctrl-r. BeginTest("xyz"); TestPressKey("\"ayiw/foo\\left\\ctrl-raX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foxyzXo")); TestPressKey("\\enter"); FinishTest("xyz"); // Insert clipboard contents on ctrl-r +. We implicitly need to test the ability to handle // shift key key events when waiting for register (they should be ignored). BeginTest("xyz"); QApplication::clipboard()->setText("vimodetestclipboardtext"); TestPressKey("/\\ctrl-r"); QKeyEvent *shiftKeyDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Shift, Qt::NoModifier); QApplication::postEvent(emulatedCommandBarTextEdit(), shiftKeyDown); QApplication::sendPostedEvents(); TestPressKey("+"); QKeyEvent *shiftKeyUp = new QKeyEvent(QEvent::KeyPress, Qt::Key_Shift, Qt::NoModifier); QApplication::postEvent(emulatedCommandBarTextEdit(), shiftKeyUp); QApplication::sendPostedEvents(); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("vimodetestclipboardtext")); TestPressKey("\\enter"); FinishTest("xyz"); // Similarly, test that we can press "ctrl" after ctrl-r without it being taken for a register. BeginTest("wordundercursor"); TestPressKey("/\\ctrl-r"); QKeyEvent *ctrlKeyDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Control, Qt::NoModifier); QApplication::postEvent(emulatedCommandBarTextEdit(), ctrlKeyDown); QApplication::sendPostedEvents(); QKeyEvent *ctrlKeyUp = new QKeyEvent(QEvent::KeyRelease, Qt::Key_Control, Qt::NoModifier); QApplication::postEvent(emulatedCommandBarTextEdit(), ctrlKeyUp); QApplication::sendPostedEvents(); QVERIFY(waitingForRegisterIndicator->isVisible()); TestPressKey("\\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("wordundercursor")); TestPressKey("\\ctrl-c"); // Dismiss the bar. FinishTest("wordundercursor"); // Begin tests for ctrl-g, which is almost identical to ctrl-r save that the contents, when added, // are escaped for searching. // Normal register contents/ word under cursor are added as normal. BeginTest("wordinregisterb wordundercursor"); TestPressKey("\"byiw"); TestPressKey("/\\ctrl-g"); QVERIFY(waitingForRegisterIndicator->isVisible()); QVERIFY(waitingForRegisterIndicator->x() >= emulatedCommandBarTextEdit()->x() + emulatedCommandBarTextEdit()->width()); TestPressKey("b"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("wordinregisterb")); QVERIFY(!waitingForRegisterIndicator->isVisible()); TestPressKey("\\ctrl-c\\ctrl-cw/\\ctrl-g\\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("wordundercursor")); QVERIFY(!waitingForRegisterIndicator->isVisible()); TestPressKey("\\ctrl-c"); TestPressKey("\\ctrl-c"); FinishTest("wordinregisterb wordundercursor"); // \'s must be escaped when inserted via ctrl-g. DoTest("foo a\\b\\\\c\\\\\\d", "wYb/\\ctrl-g0\\enterrX", "foo X\\b\\\\c\\\\\\d"); // $'s must be escaped when inserted via ctrl-g. DoTest("foo a$b", "wYb/\\ctrl-g0\\enterrX", "foo X$b"); DoTest("foo a$b$c", "wYb/\\ctrl-g0\\enterrX", "foo X$b$c"); DoTest("foo a\\$b\\$c", "wYb/\\ctrl-g0\\enterrX", "foo X\\$b\\$c"); // ^'s must be escaped when inserted via ctrl-g. DoTest("foo a^b", "wYb/\\ctrl-g0\\enterrX", "foo X^b"); DoTest("foo a^b^c", "wYb/\\ctrl-g0\\enterrX", "foo X^b^c"); DoTest("foo a\\^b\\^c", "wYb/\\ctrl-g0\\enterrX", "foo X\\^b\\^c"); // .'s must be escaped when inserted via ctrl-g. DoTest("foo axb a.b", "wwYgg/\\ctrl-g0\\enterrX", "foo axb X.b"); DoTest("foo a\\xb Na\\.b", "fNlYgg/\\ctrl-g0\\enterrX", "foo a\\xb NX\\.b"); // *'s must be escaped when inserted via ctrl-g DoTest("foo axxxxb ax*b", "wwYgg/\\ctrl-g0\\enterrX", "foo axxxxb Xx*b"); DoTest("foo a\\xxxxb Na\\x*X", "fNlYgg/\\ctrl-g0\\enterrX", "foo a\\xxxxb NX\\x*X"); // /'s must be escaped when inserted via ctrl-g. DoTest("foo a a/b", "wwYgg/\\ctrl-g0\\enterrX", "foo a X/b"); DoTest("foo a a/b/c", "wwYgg/\\ctrl-g0\\enterrX", "foo a X/b/c"); DoTest("foo a a\\/b\\/c", "wwYgg/\\ctrl-g0\\enterrX", "foo a X\\/b\\/c"); // ['s and ]'s must be escaped when inserted via ctrl-g. DoTest("foo axb a[xyz]b", "wwYgg/\\ctrl-g0\\enterrX", "foo axb X[xyz]b"); DoTest("foo a[b", "wYb/\\ctrl-g0\\enterrX", "foo X[b"); DoTest("foo a[b[c", "wYb/\\ctrl-g0\\enterrX", "foo X[b[c"); DoTest("foo a\\[b\\[c", "wYb/\\ctrl-g0\\enterrX", "foo X\\[b\\[c"); DoTest("foo a]b", "wYb/\\ctrl-g0\\enterrX", "foo X]b"); DoTest("foo a]b]c", "wYb/\\ctrl-g0\\enterrX", "foo X]b]c"); DoTest("foo a\\]b\\]c", "wYb/\\ctrl-g0\\enterrX", "foo X\\]b\\]c"); // Test that expressions involving {'s and }'s work when inserted via ctrl-g. DoTest("foo {", "wYgg/\\ctrl-g0\\enterrX", "foo X"); DoTest("foo }", "wYgg/\\ctrl-g0\\enterrX", "foo X"); DoTest("foo aaaaa \\aaaaa a\\{5}", "WWWYgg/\\ctrl-g0\\enterrX", "foo aaaaa \\aaaaa X\\{5}"); DoTest("foo }", "wYgg/\\ctrl-g0\\enterrX", "foo X"); // Transform newlines into "\\n" when inserted via ctrl-g. DoTest(" \nfoo\nfoo\nxyz\nbar\n123", "jjvjjllygg/\\ctrl-g0\\enterrX", " \nfoo\nXoo\nxyz\nbar\n123"); DoTest(" \nfoo\nfoo\nxyz\nbar\n123", "jjvjjllygg/\\ctrl-g0/e\\enterrX", " \nfoo\nfoo\nxyz\nbaX\n123"); // Don't do any escaping for ctrl-r, though. BeginTest("foo .*$^\\/"); TestPressKey("wY/\\ctrl-r0"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString(".*$^\\/")); TestPressKey("\\ctrl-c"); TestPressKey("\\ctrl-c"); FinishTest("foo .*$^\\/"); // Ensure that the flag that says "next register insertion should be escaped for searching" // is cleared if we do ctrl-g but then abort with ctrl-c. DoTest("foo a$b", "/\\ctrl-g\\ctrl-c\\ctrl-cwYgg/\\ctrl-r0\\enterrX", "Xoo a$b"); // Ensure that we actually perform a search while typing. BeginTest("abcd"); TestPressKey("/c"); verifyCursorAt(Cursor(0, 2)); TestPressKey("\\enter"); FinishTest("abcd"); // Ensure that the search is from the cursor. BeginTest("acbcd"); TestPressKey("ll/c"); verifyCursorAt(Cursor(0, 3)); TestPressKey("\\enter"); FinishTest("acbcd"); // Reset the cursor to the original position on Ctrl-C BeginTest("acbcd"); TestPressKey("ll/c\\ctrl-crX"); FinishTest("acXcd"); // Reset the cursor to the original position on Ctrl-[ BeginTest("acbcd"); TestPressKey("ll/c\\ctrl-[rX"); FinishTest("acXcd"); // Reset the cursor to the original position on ESC BeginTest("acbcd"); TestPressKey("ll/c\\escrX"); FinishTest("acXcd"); // *Do not* reset the cursor to the original position on Enter. BeginTest("acbcd"); TestPressKey("ll/c\\enterrX"); FinishTest("acbXd"); // *Do not* reset the cursor to the original position on Return. BeginTest("acbcd"); TestPressKey("ll/c\\returnrX"); FinishTest("acbXd"); // Should work with mappings. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'testmapping", "/crX", Mappings::Recursive); BeginTest("acbcd"); TestPressKey("'testmapping"); FinishTest("aXbcd"); clearAllMappings(); // Don't send keys that were part of a mapping to the emulated command bar. vi_global->mappings()->add(Mappings::NormalModeMapping, "H", "/a", Mappings::Recursive); BeginTest("foo a aH"); TestPressKey("H\\enterrX"); FinishTest("foo X aH"); clearAllMappings(); // Incremental searching from the original position. BeginTest("foo bar foop fool food"); TestPressKey("ll/foo"); verifyCursorAt(Cursor(0, 8)); TestPressKey("l"); verifyCursorAt(Cursor(0, 13)); TestPressKey("\\backspace"); verifyCursorAt(Cursor(0, 8)); TestPressKey("\\enter"); FinishTest("foo bar foop fool food"); // End up back at the start if no match found BeginTest("foo bar foop fool food"); TestPressKey("ll/fool"); verifyCursorAt(Cursor(0, 13)); TestPressKey("\\backspacex"); verifyCursorAt(Cursor(0, 2)); TestPressKey("\\enter"); FinishTest("foo bar foop fool food"); // Wrap around if no match found. BeginTest("afoom bar foop fool food"); TestPressKey("lll/foom"); verifyCursorAt(Cursor(0, 1)); TestPressKey("\\enter"); FinishTest("afoom bar foop fool food"); // SmartCase: match case-insensitively if the search text is all lower-case. DoTest("foo BaR", "ll/bar\\enterrX", "foo XaR"); // SmartCase: match case-sensitively if the search text is mixed case. DoTest("foo BaR bAr", "ll/bAr\\enterrX", "foo BaR XAr"); // Assume regex by default. DoTest("foo bwibblear", "ll/b.*ar\\enterrX", "foo Xwibblear"); // Set the last search pattern. DoTest("foo bar", "ll/bar\\enterggnrX", "foo Xar"); // Make sure the last search pattern is a regex, too. DoTest("foo bwibblear", "ll/b.*ar\\enterggnrX", "foo Xwibblear"); // 'n' should search case-insensitively if the original search was case-insensitive. DoTest("foo bAR", "ll/bar\\enterggnrX", "foo XAR"); // 'n' should search case-sensitively if the original search was case-sensitive. DoTest("foo bar bAR", "ll/bAR\\enterggnrX", "foo bar XAR"); // 'N' should search case-insensitively if the original search was case-insensitive. DoTest("foo bAR xyz", "ll/bar\\enter$NrX", "foo XAR xyz"); // 'N' should search case-sensitively if the original search was case-sensitive. DoTest("foo bAR bar", "ll/bAR\\enter$NrX", "foo XAR bar"); // Don't forget to set the last search to case-insensitive. DoTest("foo bAR bar", "ll/bAR\\enter^/bar\\enter^nrX", "foo XAR bar"); // Usage of \C for manually specifying case sensitivity. // Strip occurrences of "\C" from the pattern to find. DoTest("foo bar", "/\\\\Cba\\\\Cr\\enterrX", "foo Xar"); // Be careful about escaping, though! DoTest("foo \\Cba\\Cr", "/\\\\\\\\Cb\\\\Ca\\\\\\\\C\\\\C\\\\Cr\\enterrX", "foo XCba\\Cr"); // The item added to the search history should contain all the original \C's. clearSearchHistory(); BeginTest("foo \\Cba\\Cr"); TestPressKey("/\\\\\\\\Cb\\\\Ca\\\\\\\\C\\\\C\\\\Cr\\enterrX"); QCOMPARE(searchHistory().first(), QString("\\\\Cb\\Ca\\\\C\\C\\Cr")); FinishTest("foo XCba\\Cr"); // If there is an escaped C, assume case sensitivity. DoTest("foo bAr BAr bar", "/ba\\\\Cr\\enterrX", "foo bAr BAr Xar"); // The last search pattern should be the last search with escaped C's stripped. DoTest("foo \\Cbar\nfoo \\Cbar", "/\\\\\\\\Cba\\\\C\\\\Cr\\enterggjnrX", "foo \\Cbar\nfoo XCbar"); // If the last search pattern had an escaped "\C", then the next search should be case-sensitive. DoTest("foo bar\nfoo bAr BAr bar", "/ba\\\\Cr\\enterggjnrX", "foo bar\nfoo bAr BAr Xar"); // Don't set the last search parameters if we abort, though. DoTest("foo bar xyz", "/bar\\enter/xyz\\ctrl-cggnrX", "foo Xar xyz"); DoTest("foo bar bAr", "/bar\\enter/bA\\ctrl-cggnrX", "foo Xar bAr"); DoTest("foo bar bar", "/bar\\enter?ba\\ctrl-cggnrX", "foo Xar bar"); // Don't let ":" trample all over the search parameters, either. DoTest("foo bar xyz foo", "/bar\\entergg*:yank\\enterggnrX", "foo bar xyz Xoo"); // Some mirror tests for "?" // Test that "?" summons the search bar, with empty text and with the "?" indicator. QVERIFY(!emulatedCommandBar->isVisible()); BeginTest(""); TestPressKey("?"); QVERIFY(emulatedCommandBar->isVisible()); QCOMPARE(emulatedCommandTypeIndicator()->text(), QString("?")); QVERIFY(emulatedCommandTypeIndicator()->isVisible()); QVERIFY(emulatedCommandBarTextEdit()); QVERIFY(emulatedCommandBarTextEdit()->text().isEmpty()); TestPressKey("\\enter"); FinishTest(""); // Search backwards. DoTest("foo foo bar foo foo", "ww?foo\\enterrX", "foo Xoo bar foo foo"); // Reset cursor if we find nothing. BeginTest("foo foo bar foo foo"); TestPressKey("ww?foo"); verifyCursorAt(Cursor(0, 4)); TestPressKey("d"); verifyCursorAt(Cursor(0, 8)); TestPressKey("\\enter"); FinishTest("foo foo bar foo foo"); // Wrap to the end if we find nothing. DoTest("foo foo bar xyz xyz", "ww?xyz\\enterrX", "foo foo bar xyz Xyz"); // Specify that the last was backwards when using '?' DoTest("foo foo bar foo foo", "ww?foo\\enter^wwnrX", "foo Xoo bar foo foo"); // ... and make sure we do the equivalent with "/" BeginTest("foo foo bar foo foo"); TestPressKey("ww?foo\\enter^ww/foo"); QCOMPARE(emulatedCommandTypeIndicator()->text(), QString("/")); TestPressKey("\\enter^wwnrX"); FinishTest("foo foo bar Xoo foo"); // If we are at the beginning of a word, that word is not the first match in a search // for that word. DoTest("foo foo foo", "w/foo\\enterrX", "foo foo Xoo"); DoTest("foo foo foo", "w?foo\\enterrX", "Xoo foo foo"); // When searching backwards, ensure we can find a match whose range includes the starting cursor position, // if we allow it to wrap around. DoTest("foo foofoofoo bar", "wlll?foofoofoo\\enterrX", "foo Xoofoofoo bar"); // When searching backwards, ensure we can find a match whose range includes the starting cursor position, // even if we don't allow it to wrap around. DoTest("foo foofoofoo foofoofoo", "wlll?foofoofoo\\enterrX", "foo Xoofoofoo foofoofoo"); // The same, but where we the match ends at the end of the line or document. DoTest("foo foofoofoo\nfoofoofoo", "wlll?foofoofoo\\enterrX", "foo Xoofoofoo\nfoofoofoo"); DoTest("foo foofoofoo", "wlll?foofoofoo\\enterrX", "foo Xoofoofoo"); // Searching forwards for just "/" repeats last search. DoTest("foo bar", "/bar\\entergg//\\enterrX", "foo Xar"); // The "last search" can be one initiated via e.g. "*". DoTest("foo bar foo", "/bar\\entergg*gg//\\enterrX", "foo bar Xoo"); // Searching backwards for just "?" repeats last search. DoTest("foo bar bar", "/bar\\entergg??\\enterrX", "foo bar Xar"); // Search forwards treats "?" as a literal. DoTest("foo ?ba?r", "/?ba?r\\enterrX", "foo Xba?r"); // As always, be careful with escaping! DoTest("foo ?ba\\?r", "/?ba\\\\\\\\\\\\?r\\enterrX", "foo Xba\\?r"); // Searching forwards for just "?" finds literal question marks. DoTest("foo ??", "/?\\enterrX", "foo X?"); // Searching backwards for just "/" finds literal forward slashes. DoTest("foo //", "?/\\enterrX", "foo /X"); // Searching forwards, stuff after (and including) an unescaped "/" is ignored. DoTest("foo ba bar bar/xyz", "/bar/xyz\\enterrX", "foo ba Xar bar/xyz"); // Needs to be unescaped, though! DoTest("foo bar bar/xyz", "/bar\\\\/xyz\\enterrX", "foo bar Xar/xyz"); DoTest("foo bar bar\\/xyz", "/bar\\\\\\\\/xyz\\enterrX", "foo bar Xar\\/xyz"); // Searching backwards, stuff after (and including) an unescaped "?" is ignored. DoTest("foo bar bar?xyz bar ba", "?bar?xyz\\enterrX", "foo bar bar?xyz Xar ba"); // Needs to be unescaped, though! DoTest("foo bar bar?xyz bar ba", "?bar\\\\?xyz\\enterrX", "foo bar Xar?xyz bar ba"); DoTest("foo bar bar\\?xyz bar ba", "?bar\\\\\\\\?xyz\\enterrX", "foo bar Xar\\?xyz bar ba"); // If, in a forward search, the first character after the first unescaped "/" is an e, then // we place the cursor at the end of the word. DoTest("foo ba bar bar/eyz", "/bar/e\\enterrX", "foo ba baX bar/eyz"); // Needs to be unescaped, though! DoTest("foo bar bar/eyz", "/bar\\\\/e\\enterrX", "foo bar Xar/eyz"); DoTest("foo bar bar\\/xyz", "/bar\\\\\\\\/e\\enterrX", "foo bar barX/xyz"); // If, in a backward search, the first character after the first unescaped "?" is an e, then // we place the cursor at the end of the word. DoTest("foo bar bar?eyz bar ba", "?bar?e\\enterrX", "foo bar bar?eyz baX ba"); // Needs to be unescaped, though! DoTest("foo bar bar?eyz bar ba", "?bar\\\\?e\\enterrX", "foo bar Xar?eyz bar ba"); DoTest("foo bar bar\\?eyz bar ba", "?bar\\\\\\\\?e\\enterrX", "foo bar barX?eyz bar ba"); // Quick check that repeating the last search and placing the cursor at the end of the match works. DoTest("foo bar bar", "/bar\\entergg//e\\enterrX", "foo baX bar"); DoTest("foo bar bar", "?bar\\entergg??e\\enterrX", "foo bar baX"); // When repeating a change, don't try to convert from Vim to Qt regex again. DoTest("foo bar()", "/bar()\\entergg//e\\enterrX", "foo bar(X"); DoTest("foo bar()", "?bar()\\entergg??e\\enterrX", "foo bar(X"); // If the last search said that we should place the cursor at the end of the match, then // do this with n & N. DoTest("foo bar bar foo", "/bar/e\\enterggnrX", "foo baX bar foo"); DoTest("foo bar bar foo", "/bar/e\\enterggNrX", "foo bar baX foo"); // Don't do this if that search was aborted, though. DoTest("foo bar bar foo", "/bar\\enter/bar/e\\ctrl-cggnrX", "foo Xar bar foo"); DoTest("foo bar bar foo", "/bar\\enter/bar/e\\ctrl-cggNrX", "foo bar Xar foo"); // "#" and "*" reset the "place cursor at the end of the match" to false. DoTest("foo bar bar foo", "/bar/e\\enterggw*nrX", "foo Xar bar foo"); DoTest("foo bar bar foo", "/bar/e\\enterggw#nrX", "foo Xar bar foo"); // "/" and "?" should be usable as motions. DoTest("foo bar", "ld/bar\\enter", "fbar"); // They are not linewise. DoTest("foo bar\nxyz", "ld/yz\\enter", "fyz"); DoTest("foo bar\nxyz", "jld?oo\\enter", "fyz"); // Should be usable in Visual Mode without aborting Visual Mode. DoTest("foo bar", "lv/bar\\enterd", "far"); // Same for ?. DoTest("foo bar", "$hd?oo\\enter", "far"); DoTest("foo bar", "$hv?oo\\enterd", "fr"); DoTest("foo bar", "lv?bar\\enterd", "far"); // If we abort the "/" / "?" motion, the command should be aborted, too. DoTest("foo bar", "d/bar\\esc", "foo bar"); DoTest("foo bar", "d/bar\\ctrl-c", "foo bar"); DoTest("foo bar", "d/bar\\ctrl-[", "foo bar"); // We should be able to repeat a command using "/" or "?" as the motion. DoTest("foo bar bar bar", "d/bar\\enter.", "bar bar"); // The "synthetic" Enter keypress should not be logged as part of the command to be repeated. DoTest("foo bar bar bar\nxyz", "d/bar\\enter.rX", "Xar bar\nxyz"); // Counting. DoTest("foo bar bar bar", "2/bar\\enterrX", "foo bar Xar bar"); // Counting with wraparound. DoTest("foo bar bar bar", "4/bar\\enterrX", "foo Xar bar bar"); // Counting in Visual Mode. DoTest("foo bar bar bar", "v2/bar\\enterd", "ar bar"); // Should update the selection in Visual Mode as we search. BeginTest("foo bar bbc"); TestPressKey("vl/b"); QCOMPARE(kate_view->selectionText(), QString("foo b")); TestPressKey("b"); QCOMPARE(kate_view->selectionText(), QString("foo bar b")); TestPressKey("\\ctrl-h"); QCOMPARE(kate_view->selectionText(), QString("foo b")); TestPressKey("notexists"); QCOMPARE(kate_view->selectionText(), QString("fo")); TestPressKey("\\enter"); // Dismiss bar. QCOMPARE(kate_view->selectionText(), QString("fo")); FinishTest("foo bar bbc"); BeginTest("foo\nxyz\nbar\nbbc"); TestPressKey("Vj/b"); QCOMPARE(kate_view->selectionText(), QString("foo\nxyz\nbar")); TestPressKey("b"); QCOMPARE(kate_view->selectionText(), QString("foo\nxyz\nbar\nbbc")); TestPressKey("\\ctrl-h"); QCOMPARE(kate_view->selectionText(), QString("foo\nxyz\nbar")); TestPressKey("notexists"); QCOMPARE(kate_view->selectionText(), QString("foo\nxyz")); TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest("foo\nxyz\nbar\nbbc"); // Dismissing the search bar in visual mode should leave original selection. BeginTest("foo bar bbc"); TestPressKey("vl/\\ctrl-c"); QCOMPARE(kate_view->selectionText(), QString("fo")); FinishTest("foo bar bbc"); BeginTest("foo bar bbc"); TestPressKey("vl?\\ctrl-c"); QCOMPARE(kate_view->selectionText(), QString("fo")); FinishTest("foo bar bbc"); BeginTest("foo bar bbc"); TestPressKey("vl/b\\ctrl-c"); QCOMPARE(kate_view->selectionText(), QString("fo")); FinishTest("foo bar bbc"); BeginTest("foo\nbar\nbbc"); TestPressKey("Vl/b\\ctrl-c"); QCOMPARE(kate_view->selectionText(), QString("foo")); FinishTest("foo\nbar\nbbc"); // Search-highlighting tests. const QColor searchHighlightColour = kate_view->renderer()->config()->searchHighlightColor(); BeginTest("foo bar xyz"); // Sanity test. const QList rangesInitial = rangesOnFirstLine(); Q_ASSERT(rangesInitial.isEmpty() && "Assumptions about ranges are wrong - this test is invalid and may need updating!"); FinishTest("foo bar xyz"); // Test highlighting single character match. BeginTest("foo bar xyz"); TestPressKey("/b"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size() + 1); QCOMPARE(rangesOnFirstLine().first()->attribute()->background().color(), searchHighlightColour); QCOMPARE(rangesOnFirstLine().first()->start().line(), 0); QCOMPARE(rangesOnFirstLine().first()->start().column(), 4); QCOMPARE(rangesOnFirstLine().first()->end().line(), 0); QCOMPARE(rangesOnFirstLine().first()->end().column(), 5); TestPressKey("\\enter"); FinishTest("foo bar xyz"); // Test highlighting two character match. BeginTest("foo bar xyz"); TestPressKey("/ba"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size() + 1); QCOMPARE(rangesOnFirstLine().first()->start().line(), 0); QCOMPARE(rangesOnFirstLine().first()->start().column(), 4); QCOMPARE(rangesOnFirstLine().first()->end().line(), 0); QCOMPARE(rangesOnFirstLine().first()->end().column(), 6); TestPressKey("\\enter"); FinishTest("foo bar xyz"); // Test no highlighting if no longer a match. BeginTest("foo bar xyz"); TestPressKey("/baz"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size()); TestPressKey("\\enter"); FinishTest("foo bar xyz"); // Test highlighting on wraparound. BeginTest(" foo bar xyz"); TestPressKey("ww/foo"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size() + 1); QCOMPARE(rangesOnFirstLine().first()->start().line(), 0); QCOMPARE(rangesOnFirstLine().first()->start().column(), 1); QCOMPARE(rangesOnFirstLine().first()->end().line(), 0); QCOMPARE(rangesOnFirstLine().first()->end().column(), 4); TestPressKey("\\enter"); FinishTest(" foo bar xyz"); // Test highlighting backwards BeginTest("foo bar xyz"); TestPressKey("$?ba"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size() + 1); QCOMPARE(rangesOnFirstLine().first()->start().line(), 0); QCOMPARE(rangesOnFirstLine().first()->start().column(), 4); QCOMPARE(rangesOnFirstLine().first()->end().line(), 0); QCOMPARE(rangesOnFirstLine().first()->end().column(), 6); TestPressKey("\\enter"); FinishTest("foo bar xyz"); // Test no highlighting when no match is found searching backwards BeginTest("foo bar xyz"); TestPressKey("$?baz"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size()); TestPressKey("\\enter"); FinishTest("foo bar xyz"); // Test highlight when wrapping around after searching backwards. BeginTest("foo bar xyz"); TestPressKey("w?xyz"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size() + 1); QCOMPARE(rangesOnFirstLine().first()->start().line(), 0); QCOMPARE(rangesOnFirstLine().first()->start().column(), 8); QCOMPARE(rangesOnFirstLine().first()->end().line(), 0); QCOMPARE(rangesOnFirstLine().first()->end().column(), 11); TestPressKey("\\enter"); FinishTest("foo bar xyz"); // Test no highlighting when bar is dismissed. DoTest("foo bar xyz", "/bar\\ctrl-c", "foo bar xyz"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size()); DoTest("foo bar xyz", "/bar\\enter", "foo bar xyz"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size()); DoTest("foo bar xyz", "/bar\\ctrl-[", "foo bar xyz"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size()); DoTest("foo bar xyz", "/bar\\return", "foo bar xyz"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size()); DoTest("foo bar xyz", "/bar\\esc", "foo bar xyz"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size()); // Update colour on config change. BeginTest("foo bar xyz"); TestPressKey("/xyz"); const QColor newSearchHighlightColour = QColor(255, 0, 0); kate_view->renderer()->config()->setSearchHighlightColor(newSearchHighlightColour); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size() + 1); QCOMPARE(rangesOnFirstLine().first()->attribute()->background().color(), newSearchHighlightColour); TestPressKey("\\enter"); FinishTest("foo bar xyz"); // Set the background colour appropriately. KColorScheme currentColorScheme(QPalette::Normal); const QColor normalBackgroundColour = QPalette().brush(QPalette::Base).color(); const QColor matchBackgroundColour = currentColorScheme.background(KColorScheme::PositiveBackground).color(); const QColor noMatchBackgroundColour = currentColorScheme.background(KColorScheme::NegativeBackground).color(); BeginTest("foo bar xyz"); TestPressKey("/xyz"); verifyTextEditBackgroundColour(matchBackgroundColour); TestPressKey("a"); verifyTextEditBackgroundColour(noMatchBackgroundColour); TestPressKey("\\ctrl-w"); verifyTextEditBackgroundColour(normalBackgroundColour); TestPressKey("/xyz\\enter/"); verifyTextEditBackgroundColour(normalBackgroundColour); TestPressKey("\\enter"); FinishTest("foo bar xyz"); // Escape regex's in a Vim-ish style. // Unescaped ( and ) are always literals. DoTest("foo bar( xyz", "/bar(\\enterrX", "foo Xar( xyz"); DoTest("foo bar) xyz", "/bar)\\enterrX", "foo Xar) xyz"); // + is literal, unless it is already escaped. DoTest("foo bar+ xyz", "/bar+ \\enterrX", "foo Xar+ xyz"); DoTest(" foo+AAAAbar", "/foo+A\\\\+bar\\enterrX", " Xoo+AAAAbar"); DoTest(" foo++++bar", "/foo+\\\\+bar\\enterrX", " Xoo++++bar"); DoTest(" foo++++bar", "/+\\enterrX", " fooX+++bar"); // An escaped "\" is a literal, of course. DoTest("foo x\\y", "/x\\\\\\\\y\\enterrX", "foo X\\y"); // ( and ), if escaped, are not literals. DoTest("foo barbarxyz", "/ \\\\(bar\\\\)\\\\+xyz\\enterrX", "foo Xbarbarxyz"); // Handle escaping correctly if we have an escaped and unescaped bracket next to each other. DoTest("foo x(A)y", "/x(\\\\(.\\\\))y\\enterrX", "foo X(A)y"); // |, if unescaped, is literal. DoTest("foo |bar", "/|\\enterrX", "foo Xbar"); // |, if escaped, is not a literal. DoTest("foo xfoo\\y xbary", "/x\\\\(foo\\\\|bar\\\\)y\\enterrX", "foo xfoo\\y Xbary"); // A single [ is a literal. DoTest("foo bar[", "/bar[\\enterrX", "foo Xar["); // A single ] is a literal. DoTest("foo bar]", "/bar]\\enterrX", "foo Xar]"); // A matching [ and ] are *not* literals. DoTest("foo xbcay", "/x[abc]\\\\+y\\enterrX", "foo Xbcay"); DoTest("foo xbcay", "/[abc]\\\\+y\\enterrX", "foo xXcay"); DoTest("foo xbaadcdcy", "/x[ab]\\\\+[cd]\\\\+y\\enterrX", "foo Xbaadcdcy"); // Need to be an unescaped match, though. DoTest("foo xbcay", "/x[abc\\\\]\\\\+y\\enterrX", "Xoo xbcay"); DoTest("foo xbcay", "/x\\\\[abc]\\\\+y\\enterrX", "Xoo xbcay"); DoTest("foo x[abc]]]]]y", "/x\\\\[abc]\\\\+y\\enterrX", "foo X[abc]]]]]y"); // An escaped '[' between matching unescaped '[' and ']' is treated as a literal '[' DoTest("foo xb[cay", "/x[a\\\\[bc]\\\\+y\\enterrX", "foo Xb[cay"); // An escaped ']' between matching unescaped '[' and ']' is treated as a literal ']' DoTest("foo xb]cay", "/x[a\\\\]bc]\\\\+y\\enterrX", "foo Xb]cay"); // An escaped '[' not between other square brackets is a literal. DoTest("foo xb[cay", "/xb\\\\[\\enterrX", "foo Xb[cay"); DoTest("foo xb[cay", "/\\\\[ca\\enterrX", "foo xbXcay"); // An escaped ']' not between other square brackets is a literal. DoTest("foo xb]cay", "/xb\\\\]\\enterrX", "foo Xb]cay"); DoTest("foo xb]cay", "/\\\\]ca\\enterrX", "foo xbXcay"); // An unescaped '[' not between other square brackets is a literal. DoTest("foo xbaba[y", "/x[ab]\\\\+[y\\enterrX", "foo Xbaba[y"); DoTest("foo xbaba[dcdcy", "/x[ab]\\\\+[[cd]\\\\+y\\enterrX", "foo Xbaba[dcdcy"); // An unescaped ']' not between other square brackets is a literal. DoTest("foo xbaba]y", "/x[ab]\\\\+]y\\enterrX", "foo Xbaba]y"); DoTest("foo xbaba]dcdcy", "/x[ab]\\\\+][cd]\\\\+y\\enterrX", "foo Xbaba]dcdcy"); // Be more clever about how we indentify escaping: the presence of a preceding // backslash is not always sufficient! DoTest("foo x\\babay", "/x\\\\\\\\[ab]\\\\+y\\enterrX", "foo X\\babay"); DoTest("foo x\\[abc]]]]y", "/x\\\\\\\\\\\\[abc]\\\\+y\\enterrX", "foo X\\[abc]]]]y"); DoTest("foo xa\\b\\c\\y", "/x[abc\\\\\\\\]\\\\+y\\enterrX", "foo Xa\\b\\c\\y"); DoTest("foo x[abc\\]]]]y", "/x[abc\\\\\\\\\\\\]\\\\+y\\enterrX", "foo X[abc\\]]]]y"); DoTest("foo xa[\\b\\[y", "/x[ab\\\\\\\\[]\\\\+y\\enterrX", "foo Xa[\\b\\[y"); DoTest("foo x\\[y", "/x\\\\\\\\[y\\enterrX", "foo X\\[y"); DoTest("foo x\\]y", "/x\\\\\\\\]y\\enterrX", "foo X\\]y"); DoTest("foo x\\+y", "/x\\\\\\\\+y\\enterrX", "foo X\\+y"); // A dot is not a literal, nor is a star. DoTest("foo bar", "/o.*b\\enterrX", "fXo bar"); // Escaped dots and stars are literals, though. DoTest("foo xay x.y", "/x\\\\.y\\enterrX", "foo xay X.y"); DoTest("foo xaaaay xa*y", "/xa\\\\*y\\enterrX", "foo xaaaay Xa*y"); // Unescaped curly braces are literals. DoTest("foo x{}y", "/x{}y\\enterrX", "foo X{}y"); // Escaped curly brackets are quantifers. DoTest("foo xaaaaay", "/xa\\\\{5\\\\}y\\enterrX", "foo Xaaaaay"); // Matching curly brackets where only the first is escaped are also quantifiers. DoTest("foo xaaaaaybbbz", "/xa\\\\{5}yb\\\\{3}z\\enterrX", "foo Xaaaaaybbbz"); // Make sure it really is escaped, though! DoTest("foo xa\\{5}", "/xa\\\\\\\\{5}\\enterrX", "foo Xa\\{5}"); // Don't crash if the first character is a } DoTest("foo aaaaay", "/{\\enterrX", "Xoo aaaaay"); // Vim's '\<' and '\>' map, roughly, to Qt's '\b' DoTest("foo xbar barx bar", "/bar\\\\>\\enterrX", "foo xXar barx bar"); DoTest("foo xbar barx bar", "/\\\\\\enterrX", "foo xbar barx Xar "); DoTest("foo xbar barx bar", "/\\\\\\enterrX", "foo xbar barx Xar"); DoTest("foo xbar barx\nbar", "/\\\\\\enterrX", "foo xbar barx\nXar"); // Escaped "^" and "$" are treated as literals. DoTest("foo x^$y", "/x\\\\^\\\\$y\\enterrX", "foo X^$y"); // Ensure that it is the escaped version of the pattern that is recorded as the last search pattern. DoTest("foo bar( xyz", "/bar(\\enterggnrX", "foo Xar( xyz"); // Don't log keypresses sent to the emulated command bar as commands to be repeated via "."! DoTest("foo", "/diw\\enterciwbar\\ctrl-c.", "bar"); // Don't leave Visual mode on aborting a search. DoTest("foo bar", "vw/\\ctrl-cd", "ar"); DoTest("foo bar", "vw/\\ctrl-[d", "ar"); // Don't crash on leaving Visual Mode on aborting a search. This is perhaps the most opaque regression // test ever; what it's testing for is the situation where the synthetic keypress issue by the emulated // command bar on the "ctrl-[" is sent to the key mapper. This in turn converts it into a weird character // which is then, upon not being recognised as part of a mapping, sent back around the keypress processing, // where it ends up being sent to the emulated command bar's text edit, which in turn issues a "text changed" // event where the text is still empty, which tries to move the cursor to (-1, -1), which causes a crash deep // within Kate. So, in a nutshell: this test ensures that the keymapper never handles the synthetic keypress :) DoTest("", "ifoo\\ctrl-cv/\\ctrl-[", "foo"); // History auto-completion tests. clearSearchHistory(); QVERIFY(searchHistory().isEmpty()); vi_global->searchHistory()->append("foo"); vi_global->searchHistory()->append("bar"); QCOMPARE(searchHistory(), QStringList() << "foo" << "bar"); clearSearchHistory(); QVERIFY(searchHistory().isEmpty()); // Ensure current search bar text is added to the history if we press enter. DoTest("foo bar", "/bar\\enter", "foo bar"); DoTest("foo bar", "/xyz\\enter", "foo bar"); QCOMPARE(searchHistory(), QStringList() << "bar" << "xyz"); // Interesting - Vim adds the search bar text to the history even if we abort via e.g. ctrl-c, ctrl-[, etc. clearSearchHistory(); DoTest("foo bar", "/baz\\ctrl-[", "foo bar"); QCOMPARE(searchHistory(), QStringList() << "baz"); clearSearchHistory(); DoTest("foo bar", "/foo\\esc", "foo bar"); QCOMPARE(searchHistory(), QStringList() << "foo"); clearSearchHistory(); DoTest("foo bar", "/nose\\ctrl-c", "foo bar"); QCOMPARE(searchHistory(), QStringList() << "nose"); clearSearchHistory(); vi_global->searchHistory()->append("foo"); vi_global->searchHistory()->append("bar"); QVERIFY(emulatedCommandBarCompleter() != nullptr); BeginTest("foo bar"); TestPressKey("/\\ctrl-p"); verifyCommandBarCompletionVisible(); // Make sure the completion appears in roughly the correct place: this is a little fragile :/ const QPoint completerRectTopLeft = emulatedCommandBarCompleter()->popup()->mapToGlobal(emulatedCommandBarCompleter()->popup()->rect().topLeft()) ; const QPoint barEditBottomLeft = emulatedCommandBarTextEdit()->mapToGlobal(emulatedCommandBarTextEdit()->rect().bottomLeft()); QCOMPARE(completerRectTopLeft.x(), barEditBottomLeft.x()); QVERIFY(qAbs(completerRectTopLeft.y() - barEditBottomLeft.y()) <= 1); // Will activate the current completion item, activating the search, and dismissing the bar. TestPressKey("\\enter"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); // Close the command bar. FinishTest("foo bar"); // Don't show completion with an empty search bar. clearSearchHistory(); vi_global->searchHistory()->append("foo"); BeginTest("foo bar"); TestPressKey("/"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\enter"); FinishTest("foo bar"); // Don't auto-complete, either. clearSearchHistory(); vi_global->searchHistory()->append("foo"); BeginTest("foo bar"); TestPressKey("/f"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\enter"); FinishTest("foo bar"); clearSearchHistory(); vi_global->searchHistory()->append("xyz"); vi_global->searchHistory()->append("bar"); QVERIFY(emulatedCommandBarCompleter() != nullptr); BeginTest("foo bar"); TestPressKey("/\\ctrl-p"); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("bar")); TestPressKey("\\enter"); // Dismiss bar. FinishTest("foo bar"); clearSearchHistory(); vi_global->searchHistory()->append("xyz"); vi_global->searchHistory()->append("bar"); vi_global->searchHistory()->append("foo"); QVERIFY(emulatedCommandBarCompleter() != nullptr); BeginTest("foo bar"); TestPressKey("/\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo")); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("foo")); QCOMPARE(emulatedCommandBarCompleter()->popup()->currentIndex().row(), 0); TestPressKey("\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("bar")); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("bar")); QCOMPARE(emulatedCommandBarCompleter()->popup()->currentIndex().row(), 1); TestPressKey("\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("xyz")); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("xyz")); QCOMPARE(emulatedCommandBarCompleter()->popup()->currentIndex().row(), 2); TestPressKey("\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo")); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("foo")); // Wrap-around QCOMPARE(emulatedCommandBarCompleter()->popup()->currentIndex().row(), 0); TestPressKey("\\enter"); // Dismiss bar. FinishTest("foo bar"); clearSearchHistory(); vi_global->searchHistory()->append("xyz"); vi_global->searchHistory()->append("bar"); vi_global->searchHistory()->append("foo"); QVERIFY(emulatedCommandBarCompleter() != nullptr); BeginTest("foo bar"); TestPressKey("/\\ctrl-n"); verifyCommandBarCompletionVisible(); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("xyz")); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("xyz")); QCOMPARE(emulatedCommandBarCompleter()->popup()->currentIndex().row(), 2); TestPressKey("\\ctrl-n"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("bar")); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("bar")); QCOMPARE(emulatedCommandBarCompleter()->popup()->currentIndex().row(), 1); TestPressKey("\\ctrl-n"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo")); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("foo")); QCOMPARE(emulatedCommandBarCompleter()->popup()->currentIndex().row(), 0); TestPressKey("\\ctrl-n"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("xyz")); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("xyz")); // Wrap-around. QCOMPARE(emulatedCommandBarCompleter()->popup()->currentIndex().row(), 2); TestPressKey("\\enter"); // Dismiss bar. FinishTest("foo bar"); clearSearchHistory(); vi_global->searchHistory()->append("xyz"); vi_global->searchHistory()->append("bar"); vi_global->searchHistory()->append("foo"); BeginTest("foo bar"); TestPressKey("/\\ctrl-n\\ctrl-n"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("bar")); TestPressKey("\\enter"); // Dismiss bar. FinishTest("foo bar"); // If we add something to the history, remove any earliest occurrences (this is what Vim appears to do) // and append to the end. clearSearchHistory(); vi_global->searchHistory()->append("bar"); vi_global->searchHistory()->append("xyz"); vi_global->searchHistory()->append("foo"); vi_global->searchHistory()->append("xyz"); QCOMPARE(searchHistory(), QStringList() << "bar" << "foo" << "xyz"); // Push out older entries if we have too many search items in the history. const int HISTORY_SIZE_LIMIT = 100; clearSearchHistory(); for (int i = 1; i <= HISTORY_SIZE_LIMIT; i++) { vi_global->searchHistory()->append(QString("searchhistoryitem %1").arg(i)); } QCOMPARE(searchHistory().size(), HISTORY_SIZE_LIMIT); QCOMPARE(searchHistory().first(), QString("searchhistoryitem 1")); QCOMPARE(searchHistory().last(), QString("searchhistoryitem 100")); vi_global->searchHistory()->append(QString("searchhistoryitem %1").arg(HISTORY_SIZE_LIMIT + 1)); QCOMPARE(searchHistory().size(), HISTORY_SIZE_LIMIT); QCOMPARE(searchHistory().first(), QString("searchhistoryitem 2")); QCOMPARE(searchHistory().last(), QString("searchhistoryitem %1").arg(HISTORY_SIZE_LIMIT + 1)); // Don't add empty searches to the history. clearSearchHistory(); DoTest("foo bar", "/\\enter", "foo bar"); QVERIFY(searchHistory().isEmpty()); // "*" and "#" should add the relevant word to the search history, enclosed between \< and \> clearSearchHistory(); BeginTest("foo bar"); TestPressKey("*"); QVERIFY(!searchHistory().isEmpty()); QCOMPARE(searchHistory().last(), QString("\\")); TestPressKey("w#"); QCOMPARE(searchHistory().size(), 2); QCOMPARE(searchHistory().last(), QString("\\")); // Auto-complete words from the document on ctrl-space. // Test that we can actually find a single word and add it to the list of completions. BeginTest("foo"); TestPressKey("/\\ctrl- "); verifyCommandBarCompletionVisible(); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("foo")); TestPressKey("\\enter\\enter"); // Dismiss completion, then bar. FinishTest("foo"); // Count digits and underscores as being part of a word. BeginTest("foo_12"); TestPressKey("/\\ctrl- "); verifyCommandBarCompletionVisible(); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("foo_12")); TestPressKey("\\enter\\enter"); // Dismiss completion, then bar. FinishTest("foo_12"); // This feels a bit better to me, usability-wise: in the special case of completion from document, where // the completion list is manually summoned, allow one to press Enter without the bar being dismissed // (just dismiss the completion list instead). BeginTest("foo_12"); TestPressKey("/\\ctrl- \\ctrl-p\\enter"); QVERIFY(emulatedCommandBar->isVisible()); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\enter"); // Dismiss bar. FinishTest("foo_12"); // Check that we can find multiple words on one line. BeginTest("bar (foo) [xyz]"); TestPressKey("/\\ctrl- "); QStringListModel *completerStringListModel = dynamic_cast(emulatedCommandBarCompleter()->model()); Q_ASSERT(completerStringListModel); QCOMPARE(completerStringListModel->stringList(), QStringList() << "bar" << "foo" << "xyz"); TestPressKey("\\enter\\enter"); // Dismiss completion, then bar. FinishTest("bar (foo) [xyz]"); // Check that we arrange the found words in case-insensitive sorted order. BeginTest("D c e a b f"); TestPressKey("/\\ctrl- "); verifyCommandBarCompletionsMatches(QStringList() << "a" << "b" << "c" << "D" << "e" << "f"); TestPressKey("\\enter\\enter"); // Dismiss completion, then bar. FinishTest("D c e a b f"); // Check that we don't include the same word multiple times. BeginTest("foo bar bar bar foo"); TestPressKey("/\\ctrl- "); verifyCommandBarCompletionsMatches(QStringList() << "bar" << "foo"); TestPressKey("\\enter\\enter"); // Dismiss completion, then bar. FinishTest("foo bar bar bar foo"); // Check that we search only a narrow portion of the document, around the cursor (4096 lines either // side, say). QStringList manyLines; for (int i = 1; i < 2 * 4096 + 3; i++) { // Pad the digits so that when sorted alphabetically, they are also sorted numerically. manyLines << QString("word%1").arg(i, 5, 10, QChar('0')); } QStringList allButFirstAndLastOfManyLines = manyLines; allButFirstAndLastOfManyLines.removeFirst(); allButFirstAndLastOfManyLines.removeLast(); BeginTest(manyLines.join("\n")); TestPressKey("4097j/\\ctrl- "); verifyCommandBarCompletionsMatches(allButFirstAndLastOfManyLines); TestPressKey("\\enter\\enter"); // Dismiss completion, then bar. FinishTest(manyLines.join("\n")); // "The current word" means the word before the cursor in the command bar, and includes numbers // and underscores. Make sure also that the completion prefix is set when the completion is first invoked. BeginTest("foo fee foa_11 foa_11b"); // Write "bar(foa112$nose" and position cursor before the "2", then invoke completion. TestPressKey("/bar(foa_112$nose\\left\\left\\left\\left\\left\\left\\ctrl- "); verifyCommandBarCompletionsMatches(QStringList() << "foa_11" << "foa_11b"); TestPressKey("\\enter\\enter"); // Dismiss completion, then bar. FinishTest("foo fee foa_11 foa_11b"); // But don't count "-" as being part of the current word. BeginTest("foo_12"); TestPressKey("/bar-foo\\ctrl- "); verifyCommandBarCompletionVisible(); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("foo_12")); TestPressKey("\\enter\\enter"); // Dismiss completion, then bar. FinishTest("foo_12"); // Be case insensitive. BeginTest("foo Fo12 fOo13 FO45"); TestPressKey("/fo\\ctrl- "); verifyCommandBarCompletionsMatches(QStringList() << "Fo12" << "FO45" << "foo" << "fOo13"); TestPressKey("\\enter\\enter"); // Dismiss completion, then bar. FinishTest("foo Fo12 fOo13 FO45"); // Feed the current word to complete to the completer as we type/ edit. BeginTest("foo fee foa foab"); TestPressKey("/xyz|f\\ctrl- o"); verifyCommandBarCompletionsMatches(QStringList() << "foa" << "foab" << "foo"); TestPressKey("a"); verifyCommandBarCompletionsMatches(QStringList() << "foa" << "foab"); TestPressKey("\\ctrl-h"); verifyCommandBarCompletionsMatches(QStringList() << "foa" << "foab" << "foo"); TestPressKey("o"); verifyCommandBarCompletionsMatches(QStringList() << "foo"); TestPressKey("\\enter\\enter"); // Dismiss completion, then bar. FinishTest("foo fee foa foab"); // Upon selecting a completion with an empty command bar, add the completed text to the command bar. BeginTest("foo fee fob foables"); TestPressKey("/\\ctrl- foa\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foables")); verifyCommandBarCompletionVisible(); TestPressKey("\\enter\\enter"); // Dismiss completion, then bar. FinishTest("foo fee fob foables"); // If bar is non-empty, replace the word under the cursor. BeginTest("foo fee foa foab"); TestPressKey("/xyz|f$nose\\left\\left\\left\\left\\left\\ctrl- oa\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("xyz|foab$nose")); TestPressKey("\\enter\\enter"); // Dismiss completion, then bar. FinishTest("foo fee foa foab"); // Place the cursor at the end of the completed text. BeginTest("foo fee foa foab"); TestPressKey("/xyz|f$nose\\left\\left\\left\\left\\left\\ctrl- oa\\ctrl-p\\enterX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("xyz|foabX$nose")); TestPressKey("\\ctrl-c"); // Dismiss completion, then bar. FinishTest("foo fee foa foab"); // If we're completing from history, though, the entire text gets set, and the completion prefix // is the beginning of the entire text, not the current word before the cursor. clearSearchHistory(); vi_global->searchHistory()->append("foo(bar"); BeginTest(""); TestPressKey("/foo(b\\ctrl-p"); verifyCommandBarCompletionVisible(); verifyCommandBarCompletionsMatches(QStringList() << "foo(bar"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo(bar")); TestPressKey("\\enter"); // Dismiss bar. FinishTest(""); // If we're completing from history and we abort the completion via ctrl-c or ctrl-[, we revert the whole // text to the last manually typed text. clearSearchHistory(); vi_global->searchHistory()->append("foo(b|ar"); BeginTest(""); TestPressKey("/foo(b\\ctrl-p"); verifyCommandBarCompletionVisible(); verifyCommandBarCompletionsMatches(QStringList() << "foo(b|ar"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo(b|ar")); TestPressKey("\\ctrl-c"); // Dismiss completion. QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo(b")); TestPressKey("\\enter"); // Dismiss bar. FinishTest(""); // Scroll completion list if necessary so that currently selected completion is visible. BeginTest("a b c d e f g h i j k l m n o p q r s t u v w x y z"); TestPressKey("/\\ctrl- "); const int lastItemRow = 25; const QRect initialLastCompletionItemRect = emulatedCommandBarCompleter()->popup()->visualRect(emulatedCommandBarCompleter()->popup()->model()->index(lastItemRow, 0)); QVERIFY(!emulatedCommandBarCompleter()->popup()->rect().contains(initialLastCompletionItemRect)); // If this fails, then we have an error in the test setup: initally, the last item in the list should be outside of the bounds of the popup. TestPressKey("\\ctrl-n"); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("z")); const QRect lastCompletionItemRect = emulatedCommandBarCompleter()->popup()->visualRect(emulatedCommandBarCompleter()->popup()->model()->index(lastItemRow, 0)); QVERIFY(emulatedCommandBarCompleter()->popup()->rect().contains(lastCompletionItemRect)); TestPressKey("\\enter\\enter"); // Dismiss completion, then bar. FinishTest("a b c d e f g h i j k l m n o p q r s t u v w x y z"); // Ensure that the completion list changes size appropriately as the number of candidate completions changes. BeginTest("a ab abc"); TestPressKey("/\\ctrl- "); const int initialPopupHeight = emulatedCommandBarCompleter()->popup()->height(); TestPressKey("ab"); const int popupHeightAfterEliminatingOne = emulatedCommandBarCompleter()->popup()->height(); QVERIFY(popupHeightAfterEliminatingOne < initialPopupHeight); TestPressKey("\\enter\\enter"); // Dismiss completion, then bar. FinishTest("a ab abc"); // Ensure that the completion list disappears when no candidate completions are found, but re-appears // when some are found. BeginTest("a ab abc"); TestPressKey("/\\ctrl- "); TestPressKey("abd"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\ctrl-h"); verifyCommandBarCompletionVisible(); TestPressKey("\\enter\\enter"); // Dismiss completion, then bar. FinishTest("a ab abc"); // ctrl-c and ctrl-[ when the completion list is visible should dismiss the completion list, but *not* // the emulated command bar. TODO - same goes for ESC, but this is harder as KateViewInternal dismisses it // itself. BeginTest("a ab abc"); TestPressKey("/\\ctrl- \\ctrl-cdiw"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); QVERIFY(emulatedCommandBar->isVisible()); TestPressKey("\\enter"); // Dismiss bar. TestPressKey("/\\ctrl- \\ctrl-[diw"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); QVERIFY(emulatedCommandBar->isVisible()); TestPressKey("\\enter"); // Dismiss bar. FinishTest("a ab abc"); // If we implicitly choose an element from the summoned completion list (by highlighting it, then // continuing to edit the text), the completion box should not re-appear unless explicitly summoned // again, even if the current word has a valid completion. BeginTest("a ab abc"); TestPressKey("/\\ctrl- \\ctrl-p"); TestPressKey(".a"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\enter"); // Dismiss bar. FinishTest("a ab abc"); // If we dismiss the summoned completion list via ctrl-c or ctrl-[, it should not re-appear unless explicitly summoned // again, even if the current word has a valid completion. BeginTest("a ab abc"); TestPressKey("/\\ctrl- \\ctrl-c"); TestPressKey(".a"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\enter"); TestPressKey("/\\ctrl- \\ctrl-["); TestPressKey(".a"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\enter"); // Dismiss bar. FinishTest("a ab abc"); // If we select a completion from an empty bar, but then dismiss it via ctrl-c or ctrl-[, then we // should restore the empty text. BeginTest("foo"); TestPressKey("/\\ctrl- \\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo")); TestPressKey("\\ctrl-c"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); QVERIFY(emulatedCommandBar->isVisible()); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("")); TestPressKey("\\enter"); // Dismiss bar. FinishTest("foo"); BeginTest("foo"); TestPressKey("/\\ctrl- \\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo")); TestPressKey("\\ctrl-["); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); QVERIFY(emulatedCommandBar->isVisible()); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("")); TestPressKey("\\enter"); // Dismiss bar. FinishTest("foo"); // If we select a completion but then dismiss it via ctrl-c or ctrl-[, then we // should restore the last manually typed word. BeginTest("fooabc"); TestPressKey("/f\\ctrl- o\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("fooabc")); TestPressKey("\\ctrl-c"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); QVERIFY(emulatedCommandBar->isVisible()); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("fo")); TestPressKey("\\enter"); // Dismiss bar. FinishTest("fooabc"); // If we select a completion but then dismiss it via ctrl-c or ctrl-[, then we // should restore the word currently being typed to the last manually typed word. BeginTest("fooabc"); TestPressKey("/ab\\ctrl- |fo\\ctrl-p"); TestPressKey("\\ctrl-c"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("ab|fo")); TestPressKey("\\enter"); // Dismiss bar. FinishTest("fooabc"); // Set the completion prefix for the search history completion as soon as it is shown. clearSearchHistory(); vi_global->searchHistory()->append("foo(bar"); vi_global->searchHistory()->append("xyz"); BeginTest(""); TestPressKey("/f\\ctrl-p"); verifyCommandBarCompletionVisible(); verifyCommandBarCompletionsMatches(QStringList() << "foo(bar"); TestPressKey("\\enter"); // Dismiss bar. FinishTest(""); // Command Mode (:) tests. // ":" should summon the command bar, with ":" as the label. BeginTest(""); TestPressKey(":"); QVERIFY(emulatedCommandBar->isVisible()); QCOMPARE(emulatedCommandTypeIndicator()->text(), QString(":")); QVERIFY(emulatedCommandTypeIndicator()->isVisible()); QVERIFY(emulatedCommandBarTextEdit()); QVERIFY(emulatedCommandBarTextEdit()->text().isEmpty()); TestPressKey("\\esc"); FinishTest(""); // If we have a selection, it should be encoded as a range in the text edit. BeginTest("d\nb\na\nc"); TestPressKey("Vjjj:"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("'<,'>")); TestPressKey("\\esc"); FinishTest("d\nb\na\nc"); // If we have a count, it should be encoded as a range in the text edit. BeginTest("d\nb\na\nc"); TestPressKey("7:"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString(".,.+6")); TestPressKey("\\esc"); FinishTest("d\nb\na\nc"); // Don't go doing an incremental search when we press keys! BeginTest("foo bar xyz"); TestPressKey(":bar"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size()); TestPressKey("\\esc"); FinishTest("foo bar xyz"); // Execute the command on Enter. DoTest("d\nb\na\nc", "Vjjj:sort\\enter", "a\nb\nc\nd"); // Don't crash if we call a non-existent command with a range. DoTest("123", ":42nonexistentcommand\\enter", "123"); // Bar background should always be normal for command bar. BeginTest("foo"); TestPressKey("/foo\\enter:"); verifyTextEditBackgroundColour(normalBackgroundColour); TestPressKey("\\ctrl-c/bar\\enter:"); verifyTextEditBackgroundColour(normalBackgroundColour); TestPressKey("\\esc"); FinishTest("foo"); const int commandResponseMessageTimeOutMSOverride = QString::fromLatin1(qgetenv("KATE_VIMODE_TEST_COMMANDRESPONSEMESSAGETIMEOUTMS")).toInt(); const long commandResponseMessageTimeOutMS = (commandResponseMessageTimeOutMSOverride > 0) ? commandResponseMessageTimeOutMSOverride : 4000; { // If there is any output from the command, show it in a label for a short amount of time // (make sure the bar type indicator is hidden, here, as it looks messy). emulatedCommandBar->setCommandResponseMessageTimeout(commandResponseMessageTimeOutMS); BeginTest("foo bar xyz"); const QDateTime timeJustBeforeCommandExecuted = QDateTime::currentDateTime(); TestPressKey(":commandthatdoesnotexist\\enter"); QVERIFY(emulatedCommandBar->isVisible()); QVERIFY(commandResponseMessageDisplay()); QVERIFY(commandResponseMessageDisplay()->isVisible()); QVERIFY(!emulatedCommandBarTextEdit()->isVisible()); QVERIFY(!emulatedCommandTypeIndicator()->isVisible()); // Be a bit vague about the exact message, due to i18n, etc. QVERIFY(commandResponseMessageDisplay()->text().contains("commandthatdoesnotexist")); waitForEmulatedCommandBarToHide(4 * commandResponseMessageTimeOutMS); QVERIFY(timeJustBeforeCommandExecuted.msecsTo(QDateTime::currentDateTime()) >= commandResponseMessageTimeOutMS - 500); // "- 500" because coarse timers can fire up to 500ms *prematurely*. QVERIFY(!emulatedCommandBar->isVisible()); // Piggy-back on this test, as the bug we're about to test for would actually make setting // up the conditions again in a separate test impossible ;) // When we next summon the bar, the response message should be invisible; the editor visible & editable; // and the bar type indicator visible again. TestPressKey("/"); QVERIFY(!commandResponseMessageDisplay()->isVisible()); QVERIFY(emulatedCommandBarTextEdit()->isVisible()); QVERIFY(emulatedCommandBarTextEdit()->isEnabled()); QVERIFY(emulatedCommandBar->isVisible()); TestPressKey("\\esc"); // Dismiss the bar. FinishTest("foo bar xyz"); } { // Show the same message twice in a row. BeginTest("foo bar xyz"); TestPressKey(":othercommandthatdoesnotexist\\enter"); QDateTime startWaitingForMessageToHide = QDateTime::currentDateTime(); waitForEmulatedCommandBarToHide(4 * commandResponseMessageTimeOutMS); TestPressKey(":othercommandthatdoesnotexist\\enter"); QVERIFY(commandResponseMessageDisplay()->isVisible()); // Wait for it to disappear again, as a courtesy for the next test. waitForEmulatedCommandBarToHide(4 * commandResponseMessageTimeOutMS); } { // Emulated command bar should not steal keypresses when it is merely showing the results of an executed command. BeginTest("foo bar"); TestPressKey(":commandthatdoesnotexist\\enterrX"); Q_ASSERT_X(commandResponseMessageDisplay()->isVisible(), "running test", "Need to increase timeJustBeforeCommandExecuted!"); FinishTest("Xoo bar"); } { // Don't send the synthetic "enter" keypress (for making search-as-a-motion work) when we finally hide. BeginTest("foo bar\nbar"); TestPressKey(":commandthatdoesnotexist\\enter"); Q_ASSERT_X(commandResponseMessageDisplay()->isVisible(), "running test", "Need to increase timeJustBeforeCommandExecuted!"); waitForEmulatedCommandBarToHide(commandResponseMessageTimeOutMS * 4); TestPressKey("rX"); FinishTest("Xoo bar\nbar"); } { // The timeout should be cancelled when we invoke the command bar again. BeginTest(""); TestPressKey(":commandthatdoesnotexist\\enter"); const QDateTime waitStartedTime = QDateTime::currentDateTime(); TestPressKey(":"); // Wait ample time for the timeout to fire. Do not use waitForEmulatedCommandBarToHide for this! while(waitStartedTime.msecsTo(QDateTime::currentDateTime()) < commandResponseMessageTimeOutMS * 2) { QApplication::processEvents(); } QVERIFY(emulatedCommandBar->isVisible()); TestPressKey("\\esc"); // Dismiss the bar. FinishTest(""); } { // The timeout should not cause kate_view to regain focus if we have manually taken it away. qDebug()<< " NOTE: this test is weirdly fragile, so if it starts failing, comment it out and e-mail me: it may well be more trouble that it's worth."; BeginTest(""); TestPressKey(":commandthatdoesnotexist\\enter"); while (QApplication::hasPendingEvents()) { // Wait for any focus changes to take effect. QApplication::processEvents(); } const QDateTime waitStartedTime = QDateTime::currentDateTime(); QLineEdit *dummyToFocus = new QLineEdit(QString("Sausage"), mainWindow); // Take focus away from kate_view by giving it to dummyToFocus. QApplication::setActiveWindow(mainWindow); kate_view->setFocus(); mainWindowLayout->addWidget(dummyToFocus); dummyToFocus->show(); dummyToFocus->setEnabled(true); dummyToFocus->setFocus(); // Allow dummyToFocus to receive focus. while(!dummyToFocus->hasFocus()) { QApplication::processEvents(); } QVERIFY(dummyToFocus->hasFocus()); // Wait ample time for the timeout to fire. Do not use waitForEmulatedCommandBarToHide for this - // the bar never actually hides in this instance, and I think it would take some deep changes in // Kate to make it do so (the KateCommandLineBar as the same issue). while(waitStartedTime.msecsTo(QDateTime::currentDateTime()) < commandResponseMessageTimeOutMS * 2) { QApplication::processEvents(); } QVERIFY(dummyToFocus->hasFocus()); QVERIFY(emulatedCommandBar->isVisible()); mainWindowLayout->removeWidget(dummyToFocus); // Restore focus to the kate_view. kate_view->setFocus(); while(!kate_view->hasFocus()) { QApplication::processEvents(); } // *Now* wait for the command bar to disappear - giving kate_view focus should trigger it. waitForEmulatedCommandBarToHide(commandResponseMessageTimeOutMS * 4); FinishTest(""); } { // No completion should be shown when the bar is first shown: this gives us an opportunity // to invoke command history via ctrl-p and ctrl-n. BeginTest(""); TestPressKey(":"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest(""); } { // Should be able to switch to completion from document, even when we have a completion from commands. BeginTest("soggy1 soggy2"); TestPressKey(":so"); verifyCommandBarCompletionContains(QStringList() << "sort"); TestPressKey("\\ctrl- "); verifyCommandBarCompletionsMatches(QStringList() << "soggy1" << "soggy2"); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest("soggy1 soggy2"); } { // If we dismiss the command completion then change the text, it should summon the completion // again. BeginTest(""); TestPressKey(":so"); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("r"); verifyCommandBarCompletionVisible(); verifyCommandBarCompletionContains(QStringList() << "sort"); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest(""); } { // Completion should be dismissed when we are showing command response text. BeginTest(""); TestPressKey(":set-au\\enter"); QVERIFY(commandResponseMessageDisplay()->isVisible()); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); waitForEmulatedCommandBarToHide(commandResponseMessageTimeOutMS * 4); FinishTest(""); } // If we abort completion via ctrl-c or ctrl-[, we should revert the current word to the last // manually entered word. BeginTest(""); TestPressKey(":se\\ctrl-p"); verifyCommandBarCompletionVisible(); QVERIFY(emulatedCommandBarTextEdit()->text() != "se"); TestPressKey("\\ctrl-c"); // Dismiss completer QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("se")); TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest(""); // In practice, it's annoying if, as we enter ":s/se", completions pop up after the "se": // for now, only summon completion if we are on the first word in the text. BeginTest(""); TestPressKey(":s/se"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest(""); BeginTest(""); TestPressKey(":.,.+7s/se"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest(""); // Don't blank the text if we activate command history completion with no command history. BeginTest(""); clearCommandHistory(); TestPressKey(":s/se\\ctrl-p"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/se")); TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest(""); // On completion, only update the command in front of the cursor. BeginTest(""); clearCommandHistory(); TestPressKey(":.,.+6s/se\\left\\left\\leftet-auto-in\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString(".,.+6set-auto-indent/se")); TestPressKey("\\ctrl-c"); // Dismiss completer. TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest(""); // On completion, place the cursor after the new command. BeginTest(""); clearCommandHistory(); TestPressKey(":.,.+6s/fo\\left\\left\\leftet-auto-in\\ctrl-pX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString(".,.+6set-auto-indentX/fo")); TestPressKey("\\ctrl-c"); // Dismiss completer. TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest(""); // "The current word", for Commands, can contain "-". BeginTest(""); TestPressKey(":set-\\ctrl-p"); verifyCommandBarCompletionVisible(); QVERIFY(emulatedCommandBarTextEdit()->text() != "set-"); QVERIFY(emulatedCommandBarCompleter()->currentCompletion().startsWith("set-")); QCOMPARE(emulatedCommandBarTextEdit()->text(), emulatedCommandBarCompleter()->currentCompletion()); TestPressKey("\\ctrl-c"); // Dismiss completion. TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); { // Don't switch from word-from-document to command-completion just because we press a key, though! BeginTest("soggy1 soggy2"); TestPressKey(":\\ctrl- s"); TestPressKey("o"); verifyCommandBarCompletionVisible(); verifyCommandBarCompletionsMatches(QStringList() << "soggy1" << "soggy2"); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest("soggy1 soggy2"); } { // If we're in a place where there is no command completion allowed, don't go hiding the word // completion as we type. BeginTest("soggy1 soggy2"); TestPressKey(":s/s\\ctrl- o"); verifyCommandBarCompletionVisible(); verifyCommandBarCompletionsMatches(QStringList() << "soggy1" << "soggy2"); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest("soggy1 soggy2"); } { // Don't show command completion before we start typing a command: we want ctrl-p/n // to go through command history instead (we'll test for that second part later). BeginTest("soggy1 soggy2"); TestPressKey(":"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\ctrl-cvl:"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest("soggy1 soggy2"); } { // Aborting ":" should leave us in normal mode with no selection. BeginTest("foo bar"); TestPressKey("vw:\\ctrl-["); QVERIFY(kate_view->selectionText().isEmpty()); TestPressKey("wdiw"); BeginTest("foo "); } // Command history tests. clearCommandHistory(); QVERIFY(commandHistory().isEmpty()); vi_global->commandHistory()->append("foo"); vi_global->commandHistory()->append("bar"); QCOMPARE(commandHistory(), QStringList() << "foo" << "bar"); clearCommandHistory(); QVERIFY(commandHistory().isEmpty()); // If we add something to the history, remove any earliest occurrences (this is what Vim appears to do) // and append to the end. clearCommandHistory(); vi_global->commandHistory()->append("bar"); vi_global->commandHistory()->append("xyz"); vi_global->commandHistory()->append("foo"); vi_global->commandHistory()->append("xyz"); QCOMPARE(commandHistory(), QStringList() << "bar" << "foo" << "xyz"); // Push out older entries if we have too many command items in the history. clearCommandHistory(); for (int i = 1; i <= HISTORY_SIZE_LIMIT; i++) { vi_global->commandHistory()->append(QString("commandhistoryitem %1").arg(i)); } QCOMPARE(commandHistory().size(), HISTORY_SIZE_LIMIT); QCOMPARE(commandHistory().first(), QString("commandhistoryitem 1")); QCOMPARE(commandHistory().last(), QString("commandhistoryitem 100")); vi_global->commandHistory()->append(QString("commandhistoryitem %1").arg(HISTORY_SIZE_LIMIT + 1)); QCOMPARE(commandHistory().size(), HISTORY_SIZE_LIMIT); QCOMPARE(commandHistory().first(), QString("commandhistoryitem 2")); QCOMPARE(commandHistory().last(), QString("commandhistoryitem %1").arg(HISTORY_SIZE_LIMIT + 1)); // Don't add empty commands to the history. clearCommandHistory(); DoTest("foo bar", ":\\enter", "foo bar"); QVERIFY(commandHistory().isEmpty()); clearCommandHistory(); BeginTest(""); TestPressKey(":sort\\enter"); QCOMPARE(commandHistory(), QStringList() << "sort"); TestPressKey(":yank\\enter"); QCOMPARE(commandHistory(), QStringList() << "sort" << "yank"); // Add to history immediately: don't wait for the command response display to timeout. TestPressKey(":commandthatdoesnotexist\\enter"); QCOMPARE(commandHistory(), QStringList() << "sort" << "yank" << "commandthatdoesnotexist"); // Vim adds aborted commands to the history too, oddly. TestPressKey(":abortedcommand\\ctrl-c"); QCOMPARE(commandHistory(), QStringList() << "sort" << "yank" << "commandthatdoesnotexist" << "abortedcommand"); // Only add for commands, not searches! TestPressKey("/donotaddme\\enter?donotaddmeeither\\enter/donotaddme\\ctrl-c?donotaddmeeither\\ctrl-c"); QCOMPARE(commandHistory(), QStringList() << "sort" << "yank" << "commandthatdoesnotexist" << "abortedcommand"); FinishTest(""); // Commands should not be added to the search history! clearCommandHistory(); clearSearchHistory(); BeginTest(""); TestPressKey(":sort\\enter"); QVERIFY(searchHistory().isEmpty()); FinishTest(""); // With an empty command bar, ctrl-p / ctrl-n should go through history. clearCommandHistory(); vi_global->commandHistory()->append("command1"); vi_global->commandHistory()->append("command2"); BeginTest(""); TestPressKey(":\\ctrl-p"); verifyCommandBarCompletionVisible(); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("command2")); QCOMPARE(emulatedCommandBarTextEdit()->text(), emulatedCommandBarCompleter()->currentCompletion()); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest(""); clearCommandHistory(); vi_global->commandHistory()->append("command1"); vi_global->commandHistory()->append("command2"); BeginTest(""); TestPressKey(":\\ctrl-n"); verifyCommandBarCompletionVisible(); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("command1")); QCOMPARE(emulatedCommandBarTextEdit()->text(), emulatedCommandBarCompleter()->currentCompletion()); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest(""); // If we're at a place where command completions are not allowed, ctrl-p/n should go through history. clearCommandHistory(); vi_global->commandHistory()->append("s/command1"); vi_global->commandHistory()->append("s/command2"); BeginTest(""); TestPressKey(":s/\\ctrl-p"); verifyCommandBarCompletionVisible(); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("s/command2")); QCOMPARE(emulatedCommandBarTextEdit()->text(), emulatedCommandBarCompleter()->currentCompletion()); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest(""); clearCommandHistory(); vi_global->commandHistory()->append("s/command1"); vi_global->commandHistory()->append("s/command2"); BeginTest(""); TestPressKey(":s/\\ctrl-n"); verifyCommandBarCompletionVisible(); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("s/command1")); QCOMPARE(emulatedCommandBarTextEdit()->text(), emulatedCommandBarCompleter()->currentCompletion()); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest(""); // Cancelling word-from-document completion should revert the whole text to what it was before. BeginTest("sausage bacon"); TestPressKey(":s/b\\ctrl- \\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/bacon")); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/b")); TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest("sausage bacon"); // "Replace" history tests. clearReplaceHistory(); QVERIFY(replaceHistory().isEmpty()); vi_global->replaceHistory()->append("foo"); vi_global->replaceHistory()->append("bar"); QCOMPARE(replaceHistory(), QStringList() << "foo" << "bar"); clearReplaceHistory(); QVERIFY(replaceHistory().isEmpty()); // If we add something to the history, remove any earliest occurrences (this is what Vim appears to do) // and append to the end. clearReplaceHistory(); vi_global->replaceHistory()->append("bar"); vi_global->replaceHistory()->append("xyz"); vi_global->replaceHistory()->append("foo"); vi_global->replaceHistory()->append("xyz"); QCOMPARE(replaceHistory(), QStringList() << "bar" << "foo" << "xyz"); // Push out older entries if we have too many replace items in the history. clearReplaceHistory(); for (int i = 1; i <= HISTORY_SIZE_LIMIT; i++) { vi_global->replaceHistory()->append(QString("replacehistoryitem %1").arg(i)); } QCOMPARE(replaceHistory().size(), HISTORY_SIZE_LIMIT); QCOMPARE(replaceHistory().first(), QString("replacehistoryitem 1")); QCOMPARE(replaceHistory().last(), QString("replacehistoryitem 100")); vi_global->replaceHistory()->append(QString("replacehistoryitem %1").arg(HISTORY_SIZE_LIMIT + 1)); QCOMPARE(replaceHistory().size(), HISTORY_SIZE_LIMIT); QCOMPARE(replaceHistory().first(), QString("replacehistoryitem 2")); QCOMPARE(replaceHistory().last(), QString("replacehistoryitem %1").arg(HISTORY_SIZE_LIMIT + 1)); // Don't add empty replaces to the history. clearReplaceHistory(); vi_global->replaceHistory()->append(""); QVERIFY(replaceHistory().isEmpty()); // Some misc SedReplace tests. DoTest("x\\/y", ":s/\\\\//replace/g\\enter", "x\\replacey"); DoTest("x\\/y", ":s/\\\\\\\\\\\\//replace/g\\enter", "xreplacey"); DoTest("x\\/y", ":s:/:replace:g\\enter", "x\\replacey"); DoTest("foo\nbar\nxyz", ":%delete\\enter", ""); DoTest("foo\nbar\nxyz\nbaz", "jVj:delete\\enter", "foo\nbaz"); DoTest("foo\nbar\nxyz\nbaz", "j2:delete\\enter", "foo\nbaz"); // On ctrl-d, delete the "search" term in a s/search/replace/xx BeginTest("foo bar"); TestPressKey(":s/x\\\\\\\\\\\\/yz/rep\\\\\\\\\\\\/lace/g\\ctrl-d"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s//rep\\\\\\/lace/g")); TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest("foo bar"); // Move cursor to position of deleted search term. BeginTest("foo bar"); TestPressKey(":s/x\\\\\\\\\\\\/yz/rep\\\\\\\\\\\\/lace/g\\ctrl-dX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/X/rep\\\\\\/lace/g")); TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest("foo bar"); // Do nothing on ctrl-d in search mode. BeginTest("foo bar"); TestPressKey("/s/search/replace/g\\ctrl-d"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/search/replace/g")); TestPressKey("\\ctrl-c?s/searchbackwards/replace/g\\ctrl-d"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/searchbackwards/replace/g")); TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest("foo bar"); // On ctrl-f, delete "replace" term in a s/search/replace/xx BeginTest("foo bar"); TestPressKey(":s/a\\\\\\\\\\\\/bc/rep\\\\\\\\\\\\/lace/g\\ctrl-f"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/a\\\\\\/bc//g")); TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest("foo bar"); // Move cursor to position of deleted replace term. BeginTest("foo bar"); TestPressKey(":s:a/bc:replace:g\\ctrl-fX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s:a/bc:X:g")); TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest("foo bar"); // Do nothing on ctrl-d in search mode. BeginTest("foo bar"); TestPressKey("/s/search/replace/g\\ctrl-f"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/search/replace/g")); TestPressKey("\\ctrl-c?s/searchbackwards/replace/g\\ctrl-f"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/searchbackwards/replace/g")); TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest("foo bar"); // Do nothing on ctrl-d / ctrl-f if the current expression is not a sed expression. BeginTest("foo bar"); TestPressKey(":s/notasedreplaceexpression::gi\\ctrl-f\\ctrl-dX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/notasedreplaceexpression::giX")); TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest("foo bar"); // Need to convert Vim-style regex's to Qt one's in Sed Replace. DoTest("foo xbacba(boo)|[y", ":s/x[abc]\\\\+(boo)|[y/boo/g\\enter", "foo boo"); DoTest("foo xbacba(boo)|[y\nfoo xbacba(boo)|[y", "Vj:s/x[abc]\\\\+(boo)|[y/boo/g\\enter", "foo boo\nfoo boo"); // Just convert the search term, please :) DoTest("foo xbacba(boo)|[y", ":s/x[abc]\\\\+(boo)|[y/boo()/g\\enter", "foo boo()"); // With an empty search expression, ctrl-d should still position the cursor correctly. BeginTest("foo bar"); TestPressKey(":s//replace/g\\ctrl-dX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/X/replace/g")); TestPressKey("\\ctrl-c"); // Dismiss bar. TestPressKey(":s::replace:g\\ctrl-dX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s:X:replace:g")); TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest("foo bar"); // With an empty replace expression, ctrl-f should still position the cursor correctly. BeginTest("foo bar"); TestPressKey(":s/sear\\\\/ch//g\\ctrl-fX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/sear\\/ch/X/g")); TestPressKey("\\ctrl-c"); // Dismiss bar. TestPressKey(":s:sear\\\\:ch::g\\ctrl-fX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s:sear\\:ch:X:g")); TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest("foo bar"); // With both empty search *and* replace expressions, ctrl-f should still position the cursor correctly. BeginTest("foo bar"); TestPressKey(":s///g\\ctrl-fX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s//X/g")); TestPressKey("\\ctrl-c"); // Dismiss bar. TestPressKey(":s:::g\\ctrl-fX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s::X:g")); TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest("foo bar"); // Should be able to undo ctrl-f or ctrl-d. BeginTest("foo bar"); TestPressKey(":s/find/replace/g\\ctrl-d"); emulatedCommandBarTextEdit()->undo(); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/find/replace/g")); TestPressKey("\\ctrl-f"); emulatedCommandBarTextEdit()->undo(); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/find/replace/g")); TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest("foo bar"); // ctrl-f / ctrl-d should cleanly finish sed find/ replace history completion. clearReplaceHistory(); clearSearchHistory(); vi_global->searchHistory()->append("searchxyz"); vi_global->replaceHistory()->append("replacexyz"); TestPressKey(":s///g\\ctrl-d\\ctrl-p"); QVERIFY(emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\ctrl-f"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/searchxyz//g")); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\ctrl-p"); QVERIFY(emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\ctrl-d"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s//replacexyz/g")); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest("foo bar"); // Don't hang if we execute a sed replace with empty search term. DoTest("foo bar", ":s//replace/g\\enter", "foo bar"); // ctrl-f & ctrl-d should work even when there is a range expression at the beginning of the sed replace. BeginTest("foo bar"); TestPressKey(":'<,'>s/search/replace/g\\ctrl-d"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("'<,'>s//replace/g")); TestPressKey("\\ctrl-c:.,.+6s/search/replace/g\\ctrl-f"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString(".,.+6s/search//g")); TestPressKey("\\ctrl-c:%s/search/replace/g\\ctrl-f"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("%s/search//g")); // Place the cursor in the right place even when there is a range expression. TestPressKey("\\ctrl-c:.,.+6s/search/replace/g\\ctrl-fX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString(".,.+6s/search/X/g")); TestPressKey("\\ctrl-c:%s/search/replace/g\\ctrl-fX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("%s/search/X/g")); TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest("foo bar"); // Don't crash on ctrl-f/d if we have an empty command. DoTest("", ":\\ctrl-f\\ctrl-d\\ctrl-c", ""); // Parser regression test: Don't crash on ctrl-f/d with ".,.+". DoTest("", ":.,.+\\ctrl-f\\ctrl-d\\ctrl-c", ""); // Command-completion should be invoked on the command being typed even when preceded by a range expression. BeginTest(""); TestPressKey(":0,'>so"); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Command-completion should ignore the range expression. BeginTest(""); TestPressKey(":.,.+6so"); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // A sed-replace should immediately add the search term to the search history. clearSearchHistory(); BeginTest(""); TestPressKey(":s/search/replace/g\\enter"); QCOMPARE(searchHistory(), QStringList() << "search"); FinishTest(""); // An aborted sed-replace should not add the search term to the search history. clearSearchHistory(); BeginTest(""); TestPressKey(":s/search/replace/g\\ctrl-c"); QCOMPARE(searchHistory(), QStringList()); FinishTest(""); // A non-sed-replace should leave the search history unchanged. clearSearchHistory(); BeginTest(""); TestPressKey(":s,search/replace/g\\enter"); QCOMPARE(searchHistory(), QStringList()); FinishTest(""); // A sed-replace should immediately add the replace term to the replace history. clearReplaceHistory(); BeginTest(""); TestPressKey(":s/search/replace/g\\enter"); QCOMPARE(replaceHistory(), QStringList() << "replace"); clearReplaceHistory(); TestPressKey(":'<,'>s/search/replace1/g\\enter"); QCOMPARE(replaceHistory(), QStringList() << "replace1"); FinishTest(""); // An aborted sed-replace should not add the replace term to the replace history. clearReplaceHistory(); BeginTest(""); TestPressKey(":s/search/replace/g\\ctrl-c"); QCOMPARE(replaceHistory(), QStringList()); FinishTest(""); // A non-sed-replace should leave the replace history unchanged. clearReplaceHistory(); BeginTest(""); TestPressKey(":s,search/replace/g\\enter"); QCOMPARE(replaceHistory(), QStringList()); FinishTest(""); // Misc tests for sed replace. These are for the *generic* Kate sed replace; they should all // use EmulatedCommandBarTests' built-in command execution stuff (\\:\\\) rather than // invoking a EmulatedCommandBar and potentially doing some Vim-specific transforms to // the command. DoTest("foo foo foo", "\\:s/foo/bar/\\", "bar foo foo"); DoTest("foo foo xyz foo", "\\:s/foo/bar/g\\", "bar bar xyz bar"); DoTest("foofooxyzfoo", "\\:s/foo/bar/g\\", "barbarxyzbar"); DoTest("foofooxyzfoo", "\\:s/foo/b/g\\", "bbxyzb"); DoTest("ffxyzf", "\\:s/f/b/g\\", "bbxyzb"); DoTest("ffxyzf", "\\:s/f/bar/g\\", "barbarxyzbar"); DoTest("foo Foo fOO FOO foo", "\\:s/foo/bar/\\", "bar Foo fOO FOO foo"); DoTest("Foo foo fOO FOO foo", "\\:s/foo/bar/\\", "Foo bar fOO FOO foo"); DoTest("Foo foo fOO FOO foo", "\\:s/foo/bar/g\\", "Foo bar fOO FOO bar"); DoTest("foo Foo fOO FOO foo", "\\:s/foo/bar/i\\", "bar Foo fOO FOO foo"); DoTest("Foo foo fOO FOO foo", "\\:s/foo/bar/i\\", "bar foo fOO FOO foo"); DoTest("Foo foo fOO FOO foo", "\\:s/foo/bar/gi\\", "bar bar bar bar bar"); DoTest("Foo foo fOO FOO foo", "\\:s/foo/bar/ig\\", "bar bar bar bar bar"); // There are some oddities to do with how EmulatedCommandBarTest's "execute command directly" stuff works with selected ranges: // basically, we need to do our selection in Visual mode, then exit back to Normal mode before running the //command. DoTest("foo foo\nbar foo foo\nxyz foo foo\nfoo bar foo", "jVj\\esc\\:'<,'>s/foo/bar/\\", "foo foo\nbar bar foo\nxyz bar foo\nfoo bar foo"); DoTest("foo foo\nbar foo foo\nxyz foo foo\nfoo bar foo", "jVj\\esc\\:'<,'>s/foo/bar/g\\", "foo foo\nbar bar bar\nxyz bar bar\nfoo bar foo"); DoTest("Foo foo fOO FOO foo", "\\:s/foo/barfoo/g\\", "Foo barfoo fOO FOO barfoo"); DoTest("Foo foo fOO FOO foo", "\\:s/foo/foobar/g\\", "Foo foobar fOO FOO foobar"); DoTest("axyzb", "\\:s/a(.*)b/d\\\\1f/\\", "dxyzf"); DoTest("ayxzzyxzfddeefdb", "\\:s/a([xyz]+)([def]+)b/<\\\\1|\\\\2>/\\", ""); DoTest("foo", "\\:s/.*//g\\", ""); DoTest("foo", "\\:s/.*/f/g\\", "f"); DoTest("foo/bar", "\\:s/foo\\\\/bar/123\\\\/xyz/g\\", "123/xyz"); DoTest("foo:bar", "\\:s:foo\\\\:bar:123\\\\:xyz:g\\", "123:xyz"); const bool oldReplaceTabsDyn = kate_document->config()->replaceTabsDyn(); kate_document->config()->setReplaceTabsDyn(false); DoTest("foo\tbar", "\\:s/foo\\\\tbar/replace/g\\", "replace"); DoTest("foo\tbar", "\\:s/foo\\\\tbar/rep\\\\tlace/g\\", "rep\tlace"); kate_document->config()->setReplaceTabsDyn(oldReplaceTabsDyn); DoTest("foo", "\\:s/foo/replaceline1\\\\nreplaceline2/g\\", "replaceline1\nreplaceline2"); DoTest("foofoo", "\\:s/foo/replaceline1\\\\nreplaceline2/g\\", "replaceline1\nreplaceline2replaceline1\nreplaceline2"); DoTest("foofoo\nfoo", "\\:s/foo/replaceline1\\\\nreplaceline2/g\\", "replaceline1\nreplaceline2replaceline1\nreplaceline2\nfoo"); DoTest("fooafoob\nfooc\nfood", "Vj\\esc\\:'<,'>s/foo/replaceline1\\\\nreplaceline2/g\\", "replaceline1\nreplaceline2areplaceline1\nreplaceline2b\nreplaceline1\nreplaceline2c\nfood"); DoTest("fooafoob\nfooc\nfood", "Vj\\esc\\:'<,'>s/foo/replaceline1\\\\nreplaceline2/\\", "replaceline1\nreplaceline2afoob\nreplaceline1\nreplaceline2c\nfood"); DoTest("fooafoob\nfooc\nfood", "Vj\\esc\\:'<,'>s/foo/replaceline1\\\\nreplaceline2\\\\nreplaceline3/g\\", "replaceline1\nreplaceline2\nreplaceline3areplaceline1\nreplaceline2\nreplaceline3b\nreplaceline1\nreplaceline2\nreplaceline3c\nfood"); DoTest("foofoo", "\\:s/foo/replace\\\\nfoo/g\\", "replace\nfooreplace\nfoo"); DoTest("foofoo", "\\:s/foo/replacefoo\\\\nfoo/g\\", "replacefoo\nfooreplacefoo\nfoo"); DoTest("foofoo", "\\:s/foo/replacefoo\\\\n/g\\", "replacefoo\nreplacefoo\n"); DoTest("ff", "\\:s/f/f\\\\nf/g\\", "f\nff\nf"); DoTest("ff", "\\:s/f/f\\\\n/g\\", "f\nf\n"); DoTest("foo\nbar", "\\:s/foo\\\\n//g\\", "bar"); DoTest("foo\n\n\nbar", "\\:s/foo(\\\\n)*bar//g\\", ""); DoTest("foo\n\n\nbar", "\\:s/foo(\\\\n*)bar/123\\\\1456/g\\", "123\n\n\n456"); DoTest("xAbCy", "\\:s/x(.)(.)(.)y/\\\\L\\\\1\\\\U\\\\2\\\\3/g\\", "aBC"); DoTest("foo", "\\:s/foo/\\\\a/g\\", "\x07"); // End "generic" (i.e. not involving any Vi mode tricks/ transformations) sed replace tests: the remaining // ones should go via the EmulatedCommandBar. BeginTest("foo foo\nxyz\nfoo"); TestPressKey(":%s/foo/bar/g\\enter"); verifyShowsNumberOfReplacementsAcrossNumberOfLines(3, 2); FinishTest("bar bar\nxyz\nbar"); // ctrl-p on the first character of the search term in a sed-replace should // invoke search history completion. clearSearchHistory(); vi_global->searchHistory()->append("search"); BeginTest(""); TestPressKey(":s/search/replace/g\\ctrl-b\\right\\right\\ctrl-p"); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. TestPressKey(":'<,'>s/search/replace/g\\ctrl-b\\right\\right\\right\\right\\right\\right\\right\\ctrl-p"); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // ctrl-p on the last character of the search term in a sed-replace should // invoke search history completion. clearSearchHistory(); vi_global->searchHistory()->append("xyz"); BeginTest(""); TestPressKey(":s/xyz/replace/g\\ctrl-b\\right\\right\\right\\right\\ctrl-p"); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. QVERIFY(!emulatedCommandBar->isVisible()); TestPressKey(":'<,'>s/xyz/replace/g\\ctrl-b\\right\\right\\right\\right\\right\\right\\right\\right\\right\\ctrl-p"); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // ctrl-p on some arbitrary character of the search term in a sed-replace should // invoke search history completion. clearSearchHistory(); vi_global->searchHistory()->append("xyzaaaaaa"); BeginTest(""); TestPressKey(":s/xyzaaaaaa/replace/g\\ctrl-b\\right\\right\\right\\right\\right\\right\\right\\ctrl-p"); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. TestPressKey(":'<,'>s/xyzaaaaaa/replace/g\\ctrl-b\\right\\right\\right\\right\\right\\right\\right\\right\\right\\right\\right\\right\\ctrl-p"); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // ctrl-p on some character *after" the search term should // *not* invoke search history completion. // Note: in s/xyz/replace/g, the "/" after the "z" is counted as part of the find term; // this allows us to do xyz and get completions. clearSearchHistory(); clearCommandHistory(); clearReplaceHistory(); vi_global->searchHistory()->append("xyz"); BeginTest(""); TestPressKey(":s/xyz/replace/g\\ctrl-b\\right\\right\\right\\right\\right\\right\\ctrl-p"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. clearSearchHistory(); clearCommandHistory(); TestPressKey(":'<,'>s/xyz/replace/g\\ctrl-b\\right\\right\\right\\right\\right\\right\\right\\right\\right\\right\\right\\right\\ctrl-p"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. // Make sure it's the search history we're invoking. clearSearchHistory(); vi_global->searchHistory()->append("xyzaaaaaa"); BeginTest(""); TestPressKey(":s//replace/g\\ctrl-b\\right\\right\\ctrl-p"); verifyCommandBarCompletionVisible(); verifyCommandBarCompletionsMatches(QStringList() << "xyzaaaaaa"); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. TestPressKey(":.,.+6s//replace/g\\ctrl-b\\right\\right\\right\\right\\right\\right\\right\\ctrl-p"); verifyCommandBarCompletionsMatches(QStringList() << "xyzaaaaaa"); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // (Search history should be reversed). clearSearchHistory(); vi_global->searchHistory()->append("xyzaaaaaa"); vi_global->searchHistory()->append("abc"); vi_global->searchHistory()->append("def"); BeginTest(""); TestPressKey(":s//replace/g\\ctrl-b\\right\\right\\ctrl-p"); verifyCommandBarCompletionVisible(); verifyCommandBarCompletionsMatches(QStringList() << "def" << "abc" << "xyzaaaaaa"); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Completion prefix is the current find term. clearSearchHistory(); vi_global->searchHistory()->append("xy:zaaaaaa"); vi_global->searchHistory()->append("abc"); vi_global->searchHistory()->append("def"); vi_global->searchHistory()->append("xy:zbaaaaa"); vi_global->searchHistory()->append("xy:zcaaaaa"); BeginTest(""); TestPressKey(":s//replace/g\\ctrl-dxy:z\\ctrl-p"); verifyCommandBarCompletionVisible(); verifyCommandBarCompletionsMatches(QStringList() << "xy:zcaaaaa" << "xy:zbaaaaa" << "xy:zaaaaaa"); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Replace entire search term with completion. clearSearchHistory(); vi_global->searchHistory()->append("ab,cd"); vi_global->searchHistory()->append("ab,xy"); BeginTest(""); TestPressKey(":s//replace/g\\ctrl-dab,\\ctrl-p\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/ab,cd/replace/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. TestPressKey(":'<,'>s//replace/g\\ctrl-dab,\\ctrl-p\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("'<,'>s/ab,cd/replace/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Place the cursor at the end of find term. clearSearchHistory(); vi_global->searchHistory()->append("ab,xy"); BeginTest(""); TestPressKey(":s//replace/g\\ctrl-dab,\\ctrl-pX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/ab,xyX/replace/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. TestPressKey(":.,.+7s//replace/g\\ctrl-dab,\\ctrl-pX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString(".,.+7s/ab,xyX/replace/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Leave find term unchanged if there is no search history. clearSearchHistory(); BeginTest(""); TestPressKey(":s/nose/replace/g\\ctrl-b\\right\\right\\right\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/nose/replace/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Leave cursor position unchanged if there is no search history. clearSearchHistory(); BeginTest(""); TestPressKey(":s/nose/replace/g\\ctrl-b\\right\\right\\right\\ctrl-pX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/nXose/replace/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // ctrl-p on the first character of the replace term in a sed-replace should // invoke replace history completion. clearSearchHistory(); clearReplaceHistory(); clearCommandHistory(); vi_global->replaceHistory()->append("replace"); BeginTest(""); TestPressKey(":s/search/replace/g\\left\\left\\left\\left\\left\\left\\left\\left\\left\\ctrl-p"); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. TestPressKey(":'<,'>s/search/replace/g\\left\\left\\left\\left\\left\\left\\left\\left\\left\\ctrl-p"); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // ctrl-p on the last character of the replace term in a sed-replace should // invoke replace history completion. clearReplaceHistory(); vi_global->replaceHistory()->append("replace"); BeginTest(""); TestPressKey(":s/xyz/replace/g\\left\\left\\ctrl-p"); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. TestPressKey(":'<,'>s/xyz/replace/g\\left\\left\\ctrl-p"); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // ctrl-p on some arbitrary character of the search term in a sed-replace should // invoke search history completion. clearReplaceHistory(); vi_global->replaceHistory()->append("replaceaaaaaa"); BeginTest(""); TestPressKey(":s/xyzaaaaaa/replace/g\\left\\left\\left\\left\\ctrl-p"); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. TestPressKey(":'<,'>s/xyzaaaaaa/replace/g\\left\\left\\left\\left\\ctrl-p"); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // ctrl-p on some character *after" the replace term should // *not* invoke replace history completion. // Note: in s/xyz/replace/g, the "/" after the "e" is counted as part of the replace term; // this allows us to do replace and get completions. clearSearchHistory(); clearCommandHistory(); clearReplaceHistory(); vi_global->replaceHistory()->append("xyz"); BeginTest(""); TestPressKey(":s/xyz/replace/g\\left\\ctrl-p"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. clearSearchHistory(); clearCommandHistory(); TestPressKey(":'<,'>s/xyz/replace/g\\left\\ctrl-p"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. // (Replace history should be reversed). clearReplaceHistory(); vi_global->replaceHistory()->append("xyzaaaaaa"); vi_global->replaceHistory()->append("abc"); vi_global->replaceHistory()->append("def"); BeginTest(""); TestPressKey(":s/search//g\\left\\left\\ctrl-p"); verifyCommandBarCompletionVisible(); verifyCommandBarCompletionsMatches(QStringList() << "def" << "abc" << "xyzaaaaaa"); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Completion prefix is the current replace term. clearReplaceHistory(); vi_global->replaceHistory()->append("xy:zaaaaaa"); vi_global->replaceHistory()->append("abc"); vi_global->replaceHistory()->append("def"); vi_global->replaceHistory()->append("xy:zbaaaaa"); vi_global->replaceHistory()->append("xy:zcaaaaa"); BeginTest(""); TestPressKey(":'<,'>s/replace/search/g\\ctrl-fxy:z\\ctrl-p"); verifyCommandBarCompletionVisible(); verifyCommandBarCompletionsMatches(QStringList() << "xy:zcaaaaa" << "xy:zbaaaaa" << "xy:zaaaaaa"); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Replace entire search term with completion. clearReplaceHistory(); clearSearchHistory(); vi_global->replaceHistory()->append("ab,cd"); vi_global->replaceHistory()->append("ab,xy"); BeginTest(""); TestPressKey(":s/search//g\\ctrl-fab,\\ctrl-p\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/search/ab,cd/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. TestPressKey(":'<,'>s/search//g\\ctrl-fab,\\ctrl-p\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("'<,'>s/search/ab,cd/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Place the cursor at the end of replace term. clearReplaceHistory(); vi_global->replaceHistory()->append("ab,xy"); BeginTest(""); TestPressKey(":s/search//g\\ctrl-fab,\\ctrl-pX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/search/ab,xyX/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. TestPressKey(":.,.+7s/search//g\\ctrl-fab,\\ctrl-pX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString(".,.+7s/search/ab,xyX/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Leave replace term unchanged if there is no replace history. clearReplaceHistory(); BeginTest(""); TestPressKey(":s/nose/replace/g\\left\\left\\left\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/nose/replace/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Leave cursor position unchanged if there is no replace history. clearSearchHistory(); BeginTest(""); TestPressKey(":s/nose/replace/g\\left\\left\\left\\left\\ctrl-pX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/nose/replaXce/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Invoke replacement history even when the "find" term is empty. BeginTest(""); clearReplaceHistory(); clearSearchHistory(); vi_global->replaceHistory()->append("ab,xy"); vi_global->searchHistory()->append("whoops"); TestPressKey(":s///g\\ctrl-f\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s//ab,xy/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Move the cursor back to the last manual edit point when aborting completion. BeginTest(""); clearSearchHistory(); vi_global->searchHistory()->append("xyzaaaaa"); TestPressKey(":s/xyz/replace/g\\ctrl-b\\right\\right\\right\\right\\righta\\ctrl-p\\ctrl-[X"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/xyzaX/replace/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Don't blank the "find" term if there is no search history that begins with the // current "find" term. BeginTest(""); clearSearchHistory(); vi_global->searchHistory()->append("doesnothavexyzasaprefix"); TestPressKey(":s//replace/g\\ctrl-dxyz\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/xyz/replace/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Escape the delimiter if it occurs in a search history term - searching for it likely won't // work, but at least it won't crash! BeginTest(""); clearSearchHistory(); vi_global->searchHistory()->append("search"); vi_global->searchHistory()->append("aa/aa\\/a"); vi_global->searchHistory()->append("ss/ss"); TestPressKey(":s//replace/g\\ctrl-d\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/ss\\/ss/replace/g")); TestPressKey("\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/aa\\/aa\\/a/replace/g")); TestPressKey("\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/search/replace/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. clearSearchHistory(); // Now do the same, but with a different delimiter. vi_global->searchHistory()->append("search"); vi_global->searchHistory()->append("aa:aa\\:a"); vi_global->searchHistory()->append("ss:ss"); TestPressKey(":s::replace:g\\ctrl-d\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s:ss\\:ss:replace:g")); TestPressKey("\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s:aa\\:aa\\:a:replace:g")); TestPressKey("\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s:search:replace:g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Remove \C if occurs in search history. BeginTest(""); clearSearchHistory(); vi_global->searchHistory()->append("s\\Cear\\\\Cch"); TestPressKey(":s::replace:g\\ctrl-d\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s:sear\\\\Cch:replace:g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Don't blank the "replace" term if there is no search history that begins with the // current "replace" term. BeginTest(""); clearReplaceHistory(); vi_global->replaceHistory()->append("doesnothavexyzasaprefix"); TestPressKey(":s/search//g\\ctrl-fxyz\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/search/xyz/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Escape the delimiter if it occurs in a replace history term - searching for it likely won't // work, but at least it won't crash! BeginTest(""); clearReplaceHistory(); vi_global->replaceHistory()->append("replace"); vi_global->replaceHistory()->append("aa/aa\\/a"); vi_global->replaceHistory()->append("ss/ss"); TestPressKey(":s/search//g\\ctrl-f\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/search/ss\\/ss/g")); TestPressKey("\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/search/aa\\/aa\\/a/g")); TestPressKey("\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/search/replace/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. clearReplaceHistory(); // Now do the same, but with a different delimiter. vi_global->replaceHistory()->append("replace"); vi_global->replaceHistory()->append("aa:aa\\:a"); vi_global->replaceHistory()->append("ss:ss"); TestPressKey(":s:search::g\\ctrl-f\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s:search:ss\\:ss:g")); TestPressKey("\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s:search:aa\\:aa\\:a:g")); TestPressKey("\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s:search:replace:g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // In search mode, don't blank current text on completion if there is no item in the search history which // has the current text as a prefix. BeginTest(""); clearSearchHistory(); vi_global->searchHistory()->append("doesnothavexyzasaprefix"); TestPressKey("/xyz\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("xyz")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Don't dismiss the command completion just because the cursor ends up *temporarily* at a place where // command completion is disallowed when cycling through completions. BeginTest(""); TestPressKey(":set/se\\left\\left\\left-\\ctrl-p"); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Don't expand mappings meant for Normal mode in the emulated command bar. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "foo", "xyz", Mappings::NonRecursive); DoTest("bar foo xyz", "/foo\\enterrX", "bar Xoo xyz"); clearAllMappings(); // Incremental search and replace. QLabel* interactiveSedReplaceLabel = emulatedCommandBar->findChild("interactivesedreplace"); QVERIFY(interactiveSedReplaceLabel); BeginTest("foo"); TestPressKey(":s/foo/bar/c\\enter"); QVERIFY(interactiveSedReplaceLabel->isVisible()); QVERIFY(!commandResponseMessageDisplay()->isVisible()); QVERIFY(!emulatedCommandBarTextEdit()->isVisible()); QVERIFY(!emulatedCommandTypeIndicator()->isVisible()); TestPressKey("\\ctrl-c"); // Dismiss search and replace. QVERIFY(!emulatedCommandBar->isVisible()); FinishTest("foo"); // Clear the flag that stops the command response from being shown after an incremental search and // replace, and also make sure that the edit and bar type indicator are not forcibly hidden. BeginTest("foo"); TestPressKey(":s/foo/bar/c\\enter\\ctrl-c"); TestPressKey(":s/foo/bar/"); QVERIFY(emulatedCommandBarTextEdit()->isVisible()); QVERIFY(emulatedCommandTypeIndicator()->isVisible()); TestPressKey("\\enter"); QVERIFY(commandResponseMessageDisplay()->isVisible()); FinishTest("bar"); // Hide the incremental search and replace label when we show the bar. BeginTest("foo"); TestPressKey(":s/foo/bar/c\\enter\\ctrl-c"); TestPressKey(":"); QVERIFY(!interactiveSedReplaceLabel->isVisible()); TestPressKey("\\ctrl-c"); FinishTest("foo"); // The "c" marker can be anywhere in the three chars following the delimiter. BeginTest("foo"); TestPressKey(":s/foo/bar/cgi\\enter"); QVERIFY(interactiveSedReplaceLabel->isVisible()); TestPressKey("\\ctrl-c"); FinishTest("foo"); BeginTest("foo"); TestPressKey(":s/foo/bar/igc\\enter"); QVERIFY(interactiveSedReplaceLabel->isVisible()); TestPressKey("\\ctrl-c"); FinishTest("foo"); BeginTest("foo"); TestPressKey(":s/foo/bar/icg\\enter"); QVERIFY(interactiveSedReplaceLabel->isVisible()); TestPressKey("\\ctrl-c"); FinishTest("foo"); BeginTest("foo"); TestPressKey(":s/foo/bar/ic\\enter"); QVERIFY(interactiveSedReplaceLabel->isVisible()); TestPressKey("\\ctrl-c"); FinishTest("foo"); BeginTest("foo"); TestPressKey(":s/foo/bar/ci\\enter"); QVERIFY(interactiveSedReplaceLabel->isVisible()); TestPressKey("\\ctrl-c"); FinishTest("foo"); // Emulated command bar is still active during an incremental search and replace. BeginTest("foo"); TestPressKey(":s/foo/bar/c\\enter"); TestPressKey("idef\\esc"); FinishTest("foo"); // Emulated command bar text is not edited during an incremental search and replace. BeginTest("foo"); TestPressKey(":s/foo/bar/c\\enter"); TestPressKey("def"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/foo/bar/c")); TestPressKey("\\ctrl-c"); FinishTest("foo"); // Pressing "n" when there is only a single change we can make aborts incremental search // and replace. BeginTest("foo"); TestPressKey(":s/foo/bar/c\\enter"); TestPressKey("n"); QVERIFY(!interactiveSedReplaceLabel->isVisible()); TestPressKey("ixyz\\esc"); FinishTest("xyzfoo"); // Pressing "n" when there is only a single change we can make aborts incremental search // and replace, and shows the no replacements on no lines. BeginTest("foo"); TestPressKey(":s/foo/bar/c\\enter"); TestPressKey("n"); QVERIFY(commandResponseMessageDisplay()->isVisible()); verifyShowsNumberOfReplacementsAcrossNumberOfLines(0, 0); FinishTest("foo"); // First possible match is highlighted when we start an incremental search and replace, and // cleared if we press 'n'. BeginTest(" xyz 123 foo bar"); TestPressKey(":s/foo/bar/gc\\enter"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size() + 1); QCOMPARE(rangesOnFirstLine().first()->start().line(), 0); QCOMPARE(rangesOnFirstLine().first()->start().column(), 10); QCOMPARE(rangesOnFirstLine().first()->end().line(), 0); QCOMPARE(rangesOnFirstLine().first()->end().column(), 13); TestPressKey("n"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size()); FinishTest(" xyz 123 foo bar"); // Second possible match highlighted if we start incremental search and replace and press 'n', // cleared if we press 'n' again. BeginTest(" xyz 123 foo foo bar"); TestPressKey(":s/foo/bar/gc\\enter"); TestPressKey("n"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size() + 1); QCOMPARE(rangesOnFirstLine().first()->start().line(), 0); QCOMPARE(rangesOnFirstLine().first()->start().column(), 14); QCOMPARE(rangesOnFirstLine().first()->end().line(), 0); QCOMPARE(rangesOnFirstLine().first()->end().column(), 17); TestPressKey("n"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size()); FinishTest(" xyz 123 foo foo bar"); // Perform replacement if we press 'y' on the first match. BeginTest(" xyz foo 123 foo bar"); TestPressKey(":s/foo/bar/gc\\enter"); TestPressKey("y"); TestPressKey("\\ctrl-c"); FinishTest(" xyz bar 123 foo bar"); // Replacement uses grouping, etc. BeginTest(" xyz def 123 foo bar"); TestPressKey(":s/d\\\\(e\\\\)\\\\(f\\\\)/x\\\\1\\\\U\\\\2/gc\\enter"); TestPressKey("y"); TestPressKey("\\ctrl-c"); FinishTest(" xyz xeF 123 foo bar"); // On replacement, highlight next match. BeginTest(" xyz foo 123 foo bar"); TestPressKey(":s/foo/bar/cg\\enter"); TestPressKey("y"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size() + 1); QCOMPARE(rangesOnFirstLine().first()->start().line(), 0); QCOMPARE(rangesOnFirstLine().first()->start().column(), 14); QCOMPARE(rangesOnFirstLine().first()->end().line(), 0); QCOMPARE(rangesOnFirstLine().first()->end().column(), 17); TestPressKey("\\ctrl-c"); FinishTest(" xyz bar 123 foo bar"); // On replacement, if there is no further match, abort incremental search and replace. BeginTest(" xyz foo 123 foa bar"); TestPressKey(":s/foo/bar/cg\\enter"); TestPressKey("y"); QVERIFY(commandResponseMessageDisplay()->isVisible()); TestPressKey("ggidone\\esc"); FinishTest("done xyz bar 123 foa bar"); // After replacement, the next match is sought after the end of the replacement text. BeginTest("foofoo"); TestPressKey(":s/foo/barfoo/cg\\enter"); TestPressKey("y"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size() + 1); QCOMPARE(rangesOnFirstLine().first()->start().line(), 0); QCOMPARE(rangesOnFirstLine().first()->start().column(), 6); QCOMPARE(rangesOnFirstLine().first()->end().line(), 0); QCOMPARE(rangesOnFirstLine().first()->end().column(), 9); TestPressKey("\\ctrl-c"); FinishTest("barfoofoo"); BeginTest("xffy"); TestPressKey(":s/f/bf/cg\\enter"); TestPressKey("yy"); FinishTest("xbfbfy"); // Make sure the incremental search bar label contains the "instruction" keypresses. const QString interactiveSedReplaceShortcuts = "(y/n/a/q/l)"; BeginTest("foofoo"); TestPressKey(":s/foo/barfoo/cg\\enter"); QVERIFY(interactiveSedReplaceLabel->text().contains(interactiveSedReplaceShortcuts)); TestPressKey("\\ctrl-c"); FinishTest("foofoo"); // Make sure the incremental search bar label contains a reference to the text we're going to // replace with. // We're going to be a bit vague about the precise text due to localisation issues. BeginTest("fabababbbar"); TestPressKey(":s/f\\\\([ab]\\\\+\\\\)/1\\\\U\\\\12/c\\enter"); QVERIFY(interactiveSedReplaceLabel->text().contains("1ABABABBBA2")); TestPressKey("\\ctrl-c"); FinishTest("fabababbbar"); // Replace newlines in the "replace?" message with "\\n" BeginTest("foo"); TestPressKey(":s/foo/bar\\\\nxyz\\\\n123/c\\enter"); QVERIFY(interactiveSedReplaceLabel->text().contains("bar\\nxyz\\n123")); TestPressKey("\\ctrl-c"); FinishTest("foo"); // Update the "confirm replace?" message on pressing "y". BeginTest("fabababbbar fabbb"); TestPressKey(":s/f\\\\([ab]\\\\+\\\\)/1\\\\U\\\\12/gc\\enter"); TestPressKey("y"); QVERIFY(interactiveSedReplaceLabel->text().contains("1ABBB2")); QVERIFY(interactiveSedReplaceLabel->text().contains(interactiveSedReplaceShortcuts)); TestPressKey("\\ctrl-c"); FinishTest("1ABABABBBA2r fabbb"); // Update the "confirm replace?" message on pressing "n". BeginTest("fabababbbar fabab"); TestPressKey(":s/f\\\\([ab]\\\\+\\\\)/1\\\\U\\\\12/gc\\enter"); TestPressKey("n"); QVERIFY(interactiveSedReplaceLabel->text().contains("1ABAB2")); QVERIFY(interactiveSedReplaceLabel->text().contains(interactiveSedReplaceShortcuts)); TestPressKey("\\ctrl-c"); FinishTest("fabababbbar fabab"); // Cursor is placed at the beginning of first match. BeginTest(" foo foo foo"); TestPressKey(":s/foo/bar/c\\enter"); verifyCursorAt(Cursor(0, 2)); TestPressKey("\\ctrl-c"); FinishTest(" foo foo foo"); // "y" and "n" update the cursor pos. BeginTest(" foo foo foo"); TestPressKey(":s/foo/bar/cg\\enter"); TestPressKey("y"); verifyCursorAt(Cursor(0, 8)); TestPressKey("n"); verifyCursorAt(Cursor(0, 12)); TestPressKey("\\ctrl-c"); FinishTest(" bar foo foo"); // If we end due to a "y" or "n" on the final match, leave the cursor at the beginning of the final match. BeginTest(" foo"); TestPressKey(":s/foo/bar/c\\enter"); TestPressKey("y"); verifyCursorAt(Cursor(0, 2)); FinishTest(" bar"); BeginTest(" foo"); TestPressKey(":s/foo/bar/c\\enter"); TestPressKey("n"); verifyCursorAt(Cursor(0, 2)); FinishTest(" foo"); // Respect ranges. BeginTest("foo foo\nfoo foo\nfoo foo\nfoo foo\n"); TestPressKey("jVj:s/foo/bar/gc\\enter"); TestPressKey("ynny"); QVERIFY(commandResponseMessageDisplay()->isVisible()); TestPressKey("ggidone \\ctrl-c"); FinishTest("done foo foo\nbar foo\nfoo bar\nfoo foo\n"); BeginTest("foo foo\nfoo foo\nfoo foo\nfoo foo\n"); TestPressKey("jVj:s/foo/bar/gc\\enter"); TestPressKey("nyyn"); QVERIFY(commandResponseMessageDisplay()->isVisible()); TestPressKey("ggidone \\ctrl-c"); FinishTest("done foo foo\nfoo bar\nbar foo\nfoo foo\n"); BeginTest("foo foo\nfoo foo\nfoo foo\nfoo foo\n"); TestPressKey("j:s/foo/bar/gc\\enter"); TestPressKey("ny"); QVERIFY(commandResponseMessageDisplay()->isVisible()); TestPressKey("ggidone \\ctrl-c"); FinishTest("done foo foo\nfoo bar\nfoo foo\nfoo foo\n"); BeginTest("foo foo\nfoo foo\nfoo foo\nfoo foo\n"); TestPressKey("j:s/foo/bar/gc\\enter"); TestPressKey("yn"); QVERIFY(commandResponseMessageDisplay()->isVisible()); TestPressKey("ggidone \\ctrl-c"); FinishTest("done foo foo\nbar foo\nfoo foo\nfoo foo\n"); // If no initial match can be found, abort and show a "no replacements" message. // The cursor position should remain unnchanged. BeginTest("fab"); TestPressKey("l:s/fee/bar/c\\enter"); QVERIFY(commandResponseMessageDisplay()->isVisible()); verifyShowsNumberOfReplacementsAcrossNumberOfLines(0, 0); QVERIFY(!interactiveSedReplaceLabel->isVisible()); TestPressKey("rX"); BeginTest("fXb"); // Case-sensitive by default. BeginTest("foo Foo FOo foo foO"); TestPressKey(":s/foo/bar/cg\\enter"); TestPressKey("yyggidone\\esc"); FinishTest("donebar Foo FOo bar foO"); // Case-insensitive if "i" flag is used. BeginTest("foo Foo FOo foo foO"); TestPressKey(":s/foo/bar/icg\\enter"); TestPressKey("yyyyyggidone\\esc"); FinishTest("donebar bar bar bar bar"); // Only one replacement per-line unless "g" flag is used. BeginTest("boo foo 123 foo\nxyz foo foo\nfoo foo foo\nxyz\nfoo foo\nfoo 123 foo"); TestPressKey("jVjjj:s/foo/bar/c\\enter"); TestPressKey("yynggidone\\esc"); FinishTest("doneboo foo 123 foo\nxyz bar foo\nbar foo foo\nxyz\nfoo foo\nfoo 123 foo"); BeginTest("boo foo 123 foo\nxyz foo foo\nfoo foo foo\nxyz\nfoo foo\nfoo 123 foo"); TestPressKey("jVjjj:s/foo/bar/c\\enter"); TestPressKey("nnyggidone\\esc"); FinishTest("doneboo foo 123 foo\nxyz foo foo\nfoo foo foo\nxyz\nbar foo\nfoo 123 foo"); // If replacement contains new lines, adjust the end line down. BeginTest("foo\nfoo1\nfoo2\nfoo3"); TestPressKey("jVj:s/foo/bar\\\\n/gc\\enter"); TestPressKey("yyggidone\\esc"); FinishTest("donefoo\nbar\n1\nbar\n2\nfoo3"); BeginTest("foo\nfoo1\nfoo2\nfoo3"); TestPressKey("jVj:s/foo/bar\\\\nboo\\\\n/gc\\enter"); TestPressKey("yyggidone\\esc"); FinishTest("donefoo\nbar\nboo\n1\nbar\nboo\n2\nfoo3"); // With "g" and a replacement that involves multiple lines, resume search from the end of the last line added. BeginTest("foofoo"); TestPressKey(":s/foo/bar\\\\n/gc\\enter"); TestPressKey("yyggidone\\esc"); FinishTest("donebar\nbar\n"); BeginTest("foofoo"); TestPressKey(":s/foo/bar\\\\nxyz\\\\nfoo/gc\\enter"); TestPressKey("yyggidone\\esc"); FinishTest("donebar\nxyz\nfoobar\nxyz\nfoo"); // Without "g" and with a replacement that involves multiple lines, resume search from the line after the line just added. BeginTest("foofoo1\nfoo2\nfoo3"); TestPressKey("Vj:s/foo/bar\\\\nxyz\\\\nfoo/c\\enter"); TestPressKey("yyggidone\\esc"); FinishTest("donebar\nxyz\nfoofoo1\nbar\nxyz\nfoo2\nfoo3"); // Regression test: handle 'g' when it occurs before 'i' and 'c'. BeginTest("foo fOo"); TestPressKey(":s/foo/bar/gci\\enter"); TestPressKey("yyggidone\\esc"); FinishTest("donebar bar"); // When the search terms swallows several lines, move the endline up accordingly. BeginTest("foo\nfoo1\nfoo\nfoo2\nfoo\nfoo3"); TestPressKey("V3j:s/foo\\\\nfoo/bar/cg\\enter"); TestPressKey("yyggidone\\esc"); FinishTest("donebar1\nbar2\nfoo\nfoo3"); BeginTest("foo\nfoo\nfoo1\nfoo\nfoo\nfoo2\nfoo\nfoo\nfoo3"); TestPressKey("V5j:s/foo\\\\nfoo\\\\nfoo/bar/cg\\enter"); TestPressKey("yyggidone\\esc"); FinishTest("donebar1\nbar2\nfoo\nfoo\nfoo3"); // Make sure we still adjust endline down if the replacement text has '\n's. BeginTest("foo\nfoo\nfoo1\nfoo\nfoo\nfoo2\nfoo\nfoo\nfoo3"); TestPressKey("V5j:s/foo\\\\nfoo\\\\nfoo/bar\\\\n/cg\\enter"); TestPressKey("yyggidone\\esc"); FinishTest("donebar\n1\nbar\n2\nfoo\nfoo\nfoo3"); // Status reports. BeginTest("foo"); TestPressKey(":s/foo/bar/c\\enter"); TestPressKey("y"); verifyShowsNumberOfReplacementsAcrossNumberOfLines(1, 1); FinishTest("bar"); BeginTest("foo foo foo"); TestPressKey(":s/foo/bar/gc\\enter"); TestPressKey("yyy"); verifyShowsNumberOfReplacementsAcrossNumberOfLines(3, 1); FinishTest("bar bar bar"); BeginTest("foo foo foo"); TestPressKey(":s/foo/bar/gc\\enter"); TestPressKey("yny"); verifyShowsNumberOfReplacementsAcrossNumberOfLines(2, 1); FinishTest("bar foo bar"); BeginTest("foo\nfoo"); TestPressKey(":%s/foo/bar/gc\\enter"); TestPressKey("yy"); verifyShowsNumberOfReplacementsAcrossNumberOfLines(2, 2); FinishTest("bar\nbar"); BeginTest("foo foo\nfoo foo\nfoo foo"); TestPressKey(":%s/foo/bar/gc\\enter"); TestPressKey("yynnyy"); verifyShowsNumberOfReplacementsAcrossNumberOfLines(4, 2); FinishTest("bar bar\nfoo foo\nbar bar"); BeginTest("foofoo"); TestPressKey(":s/foo/bar\\\\nxyz/gc\\enter"); TestPressKey("yy"); verifyShowsNumberOfReplacementsAcrossNumberOfLines(2, 1); FinishTest("bar\nxyzbar\nxyz"); BeginTest("foofoofoo"); TestPressKey(":s/foo/bar\\\\nxyz\\\\nboo/gc\\enter"); TestPressKey("yyy"); verifyShowsNumberOfReplacementsAcrossNumberOfLines(3, 1); FinishTest("bar\nxyz\nboobar\nxyz\nboobar\nxyz\nboo"); // Tricky one: how many lines are "touched" if a single replacement // swallows multiple lines? I'm going to say the number of lines swallowed. BeginTest("foo\nfoo\nfoo"); TestPressKey(":s/foo\\\\nfoo\\\\nfoo/bar/c\\enter"); TestPressKey("y"); verifyShowsNumberOfReplacementsAcrossNumberOfLines(1, 3); FinishTest("bar"); BeginTest("foo\nfoo\nfoo\n"); TestPressKey(":s/foo\\\\nfoo\\\\nfoo\\\\n/bar/c\\enter"); TestPressKey("y"); verifyShowsNumberOfReplacementsAcrossNumberOfLines(1, 4); FinishTest("bar"); // "Undo" undoes last replacement. BeginTest("foo foo foo foo"); TestPressKey(":s/foo/bar/cg\\enter"); TestPressKey("nyynu"); FinishTest("foo bar foo foo"); // "l" does the current replacement then exits. BeginTest("foo foo foo foo foo foo"); TestPressKey(":s/foo/bar/cg\\enter"); TestPressKey("nnl"); verifyShowsNumberOfReplacementsAcrossNumberOfLines(1, 1); FinishTest("foo foo bar foo foo foo"); // "q" just exits. BeginTest("foo foo foo foo foo foo"); TestPressKey(":s/foo/bar/cg\\enter"); TestPressKey("yyq"); verifyShowsNumberOfReplacementsAcrossNumberOfLines(2, 1); FinishTest("bar bar foo foo foo foo"); // "a" replaces all remaining, then exits. BeginTest("foo foo foo foo foo foo"); TestPressKey(":s/foo/bar/cg\\enter"); TestPressKey("nna"); verifyShowsNumberOfReplacementsAcrossNumberOfLines(4, 1); FinishTest("foo foo bar bar bar bar"); // The results of "a" can be undone in one go. BeginTest("foo foo foo foo foo foo"); TestPressKey(":s/foo/bar/cg\\enter"); TestPressKey("ya"); verifyShowsNumberOfReplacementsAcrossNumberOfLines(6, 1); TestPressKey("u"); FinishTest("bar foo foo foo foo foo"); #if 0 // XXX - as of Qt 5.5, simply replaying the correct QKeyEvents does *not* cause shortcuts // to be triggered, so these tests cannot pass. // It's possible that a solution involving QTestLib will be workable in the future, though. { // Test the test suite: ensure that shortcuts are still being sent and received correctly. // The test shortcut chosen should be one that does not conflict with built-in Kate ones. FailsIfSlotNotCalled failsIfActionNotTriggered; QAction *dummyAction = kate_view->actionCollection()->addAction("Woo"); dummyAction->setShortcut(QKeySequence("Ctrl+]")); QVERIFY(connect(dummyAction, SIGNAL(triggered()), &failsIfActionNotTriggered, SLOT(slot()))); DoTest("foo", "\\ctrl-]", "foo"); // Processing shortcuts seems to require events to be processed. while (QApplication::hasPendingEvents()) { QApplication::processEvents(); } delete dummyAction; } { // Test that shortcuts involving ctrl+ work correctly. FailsIfSlotNotCalled failsIfActionNotTriggered; QAction *dummyAction = kate_view->actionCollection()->addAction("Woo"); dummyAction->setShortcut(QKeySequence("Ctrl+1")); QVERIFY(connect(dummyAction, SIGNAL(triggered()), &failsIfActionNotTriggered, SLOT(slot()))); DoTest("foo", "\\ctrl-1", "foo"); // Processing shortcuts seems to require events to be processed. while (QApplication::hasPendingEvents()) { QApplication::processEvents(); } delete dummyAction; } { // Test that shortcuts involving alt+ work correctly. FailsIfSlotNotCalled failsIfActionNotTriggered; QAction *dummyAction = kate_view->actionCollection()->addAction("Woo"); dummyAction->setShortcut(QKeySequence("Alt+1")); QVERIFY(connect(dummyAction, SIGNAL(triggered()), &failsIfActionNotTriggered, SLOT(slot()))); DoTest("foo", "\\alt-1", "foo"); // Processing shortcuts seems to require events to be processed. while (QApplication::hasPendingEvents()) { QApplication::processEvents(); } delete dummyAction; } #endif // Find the "Print" action for later use. QAction *printAction = nullptr; foreach(QAction* action, kate_view->actionCollection()->actions()) { if (action->shortcut() == QKeySequence("Ctrl+p")) { printAction = action; break; } } // Test that we don't inadvertantly trigger shortcuts in kate_view when typing them in the // emulated command bar. Requires the above test for shortcuts to be sent and received correctly // to pass. { QVERIFY(mainWindow->isActiveWindow()); QVERIFY(printAction); FailsIfSlotCalled failsIfActionTriggered("The kate_view shortcut should not be triggered by typing it in emulated command bar!"); // Don't invoke Print on failure, as this hangs instead of failing. //disconnect(printAction, SIGNAL(triggered(bool)), kate_document, SLOT(print())); connect(printAction, SIGNAL(triggered(bool)), &failsIfActionTriggered, SLOT(slot())); DoTest("foo bar foo bar", "/bar\\enterggd/\\ctrl-p\\enter.", "bar"); // Processing shortcuts seems to require events to be processed. while (QApplication::hasPendingEvents()) { QApplication::processEvents(); } } // Test that the interactive search replace does not handle general keypresses like ctrl-p ("invoke // completion in emulated command bar"). // Unfortunately, "ctrl-p" in kate_view, which is what will be triggered if this // test succeeds, hangs due to showing the print dialog, so we need to temporarily // block the Print action. clearCommandHistory(); if (printAction) { printAction->blockSignals(true); } vi_global->commandHistory()->append("s/foo/bar/caa"); BeginTest("foo"); TestPressKey(":s/foo/bar/c\\ctrl-b\\enter\\ctrl-p"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\ctrl-c"); if (printAction) { printAction->blockSignals(false); } FinishTest("foo"); // The interactive sed replace command is added to the history straight away. clearCommandHistory(); BeginTest("foo"); TestPressKey(":s/foo/bar/c\\enter"); QCOMPARE(commandHistory(), QStringList() << "s/foo/bar/c"); TestPressKey("\\ctrl-c"); FinishTest("foo"); clearCommandHistory(); BeginTest("foo"); TestPressKey(":s/notfound/bar/c\\enter"); QCOMPARE(commandHistory(), QStringList() << "s/notfound/bar/c"); TestPressKey("\\ctrl-c"); FinishTest("foo"); // Should be usable in mappings. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "H", ":s/foo/bar/gcnnyyl", Mappings::Recursive); DoTest("foo foo foo foo foo foo", "H", "foo foo bar bar bar foo"); clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "H", ":s/foo/bar/gcnna", Mappings::Recursive); DoTest("foo foo foo foo foo foo", "H", "foo foo bar bar bar bar"); clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "H", ":s/foo/bar/gcnnyqggidone", Mappings::Recursive); DoTest("foo foo foo foo foo foo", "H", "donefoo foo bar foo foo foo"); // Don't swallow "Ctrl+" meant for the text edit. if (QKeySequence::keyBindings(QKeySequence::Undo).contains(QKeySequence("Ctrl+Z"))) { DoTest("foo bar", "/bar\\ctrl-z\\enterrX", "Xoo bar"); } else { qWarning() << "Skipped test: Ctrl+Z is not Undo on this platform"; } // Don't give invalid cursor position to updateCursor in Visual Mode: it will cause a crash! DoTest("xyz\nfoo\nbar\n123", "/foo\\\\nbar\\\\n\\enterggv//e\\enter\\ctrl-crX", "xyz\nfoo\nbaX\n123"); DoTest("\nfooxyz\nbar;\n" , "/foo.*\\\\n.*;\\enterggv//e\\enter\\ctrl-crX", "\nfooxyz\nbarX\n"); } QCompleter* EmulatedCommandBarTest::emulatedCommandBarCompleter() { return vi_input_mode->viModeEmulatedCommandBar()->findChild("completer"); } void EmulatedCommandBarTest::verifyCommandBarCompletionVisible() { if (!emulatedCommandBarCompleter()->popup()->isVisible()) { qDebug() << "Emulated command bar completer not visible."; QStringListModel *completionModel = qobject_cast(emulatedCommandBarCompleter()->model()); Q_ASSERT(completionModel); QStringList allAvailableCompletions = completionModel->stringList(); qDebug() << " Completion list: " << allAvailableCompletions; qDebug() << " Completion prefix: " << emulatedCommandBarCompleter()->completionPrefix(); bool candidateCompletionFound = false; foreach (const QString& availableCompletion, allAvailableCompletions) { if (availableCompletion.startsWith(emulatedCommandBarCompleter()->completionPrefix())) { candidateCompletionFound = true; break; } } if (candidateCompletionFound) { qDebug() << " The current completion prefix is a prefix of one of the available completions, so either complete() was not called, or the popup was manually hidden since then"; } else { qDebug() << " The current completion prefix is not a prefix of one of the available completions; this may or may not be why it is not visible"; } } QVERIFY(emulatedCommandBarCompleter()->popup()->isVisible()); } void EmulatedCommandBarTest::verifyCommandBarCompletionsMatches(const QStringList& expectedCompletionList) { verifyCommandBarCompletionVisible(); QStringList actualCompletionList; for (int i = 0; emulatedCommandBarCompleter()->setCurrentRow(i); i++) actualCompletionList << emulatedCommandBarCompleter()->currentCompletion(); if (expectedCompletionList != actualCompletionList) { qDebug() << "Actual completions:\n " << actualCompletionList << "\n\ndo not match expected:\n" << expectedCompletionList; } QCOMPARE(actualCompletionList, expectedCompletionList); } void EmulatedCommandBarTest::verifyCommandBarCompletionContains(const QStringList& expectedCompletionList) { verifyCommandBarCompletionVisible(); QStringList actualCompletionList; for (int i = 0; emulatedCommandBarCompleter()->setCurrentRow(i); i++) { actualCompletionList << emulatedCommandBarCompleter()->currentCompletion(); } foreach(const QString& expectedCompletion, expectedCompletionList) { if (!actualCompletionList.contains(expectedCompletion)) { qDebug() << "Whoops: " << actualCompletionList << " does not contain " << expectedCompletion; } QVERIFY(actualCompletionList.contains(expectedCompletion)); } } QLabel* EmulatedCommandBarTest::emulatedCommandTypeIndicator() { return emulatedCommandBar()->findChild("bartypeindicator"); } void EmulatedCommandBarTest::verifyCursorAt(const Cursor& expectedCursorPos) { QCOMPARE(kate_view->cursorPosition().line(), expectedCursorPos.line()); QCOMPARE(kate_view->cursorPosition().column(), expectedCursorPos.column()); } void EmulatedCommandBarTest::clearSearchHistory() { vi_global->searchHistory()->clear(); } QStringList EmulatedCommandBarTest::searchHistory() { return vi_global->searchHistory()->items(); } void EmulatedCommandBarTest::clearCommandHistory() { vi_global->commandHistory()->clear(); } QStringList EmulatedCommandBarTest::commandHistory() { return vi_global->commandHistory()->items(); } void EmulatedCommandBarTest::clearReplaceHistory() { vi_global->replaceHistory()->clear(); } QStringList EmulatedCommandBarTest::replaceHistory() { return vi_global->replaceHistory()->items(); } QList EmulatedCommandBarTest::rangesOnFirstLine() { return kate_document->buffer().rangesForLine(0, kate_view, true); } void EmulatedCommandBarTest::verifyTextEditBackgroundColour(const QColor& expectedBackgroundColour) { QCOMPARE(emulatedCommandBarTextEdit()->palette().brush(QPalette::Base).color(), expectedBackgroundColour); } QLabel* EmulatedCommandBarTest::commandResponseMessageDisplay() { QLabel* commandResponseMessageDisplay = emulatedCommandBar()->findChild("commandresponsemessage"); Q_ASSERT(commandResponseMessageDisplay); return commandResponseMessageDisplay; } void EmulatedCommandBarTest::waitForEmulatedCommandBarToHide(long int timeout) { const QDateTime waitStartedTime = QDateTime::currentDateTime(); while(emulatedCommandBar()->isVisible() && waitStartedTime.msecsTo(QDateTime::currentDateTime()) < timeout) { QApplication::processEvents(); } QVERIFY(!emulatedCommandBar()->isVisible()); } void EmulatedCommandBarTest::verifyShowsNumberOfReplacementsAcrossNumberOfLines(int numReplacements, int acrossNumLines) { QVERIFY(commandResponseMessageDisplay()->isVisible()); QVERIFY(!emulatedCommandTypeIndicator()->isVisible()); const QString commandMessageResponseText = commandResponseMessageDisplay()->text(); const QString expectedNumReplacementsAsString = QString::number(numReplacements); const QString expectedAcrossNumLinesAsString = QString::number(acrossNumLines); // Be a bit vague about the actual contents due to e.g. localisation. // TODO - see if we can insist that en_US is available on the Kate Jenkins server and // insist that we use it ... ? QRegExp numReplacementsMessageRegex("^.*(\\d+).*(\\d+).*$"); QVERIFY(numReplacementsMessageRegex.exactMatch(commandMessageResponseText)); const QString actualNumReplacementsAsString = numReplacementsMessageRegex.cap(1); const QString actualAcrossNumLinesAsString = numReplacementsMessageRegex.cap(2); QCOMPARE(actualNumReplacementsAsString, expectedNumReplacementsAsString); QCOMPARE(actualAcrossNumLinesAsString, expectedAcrossNumLinesAsString); } -FailsIfSlotNotCalled::FailsIfSlotNotCalled(): QObject(), m_slotWasCalled(false) +FailsIfSlotNotCalled::FailsIfSlotNotCalled(): QObject() { } FailsIfSlotNotCalled::~FailsIfSlotNotCalled() { QVERIFY(m_slotWasCalled); } void FailsIfSlotNotCalled::slot() { m_slotWasCalled = true; } FailsIfSlotCalled::FailsIfSlotCalled(const QString& failureMessage): QObject(), m_failureMessage(failureMessage) { } void FailsIfSlotCalled::slot() { QFAIL(qPrintable(m_failureMessage.toLatin1())); } diff --git a/autotests/src/vimode/emulatedcommandbar.h b/autotests/src/vimode/emulatedcommandbar.h index f1cfe4c4..79bfc835 100644 --- a/autotests/src/vimode/emulatedcommandbar.h +++ b/autotests/src/vimode/emulatedcommandbar.h @@ -1,87 +1,87 @@ /* This file is part of the KDE libraries Copyright (C) 2011 Kuzmich Svyatoslav Copyright (C) 2012 - 2013 Simon St James This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef EMULATEDCOMMANDBAR_TEST_H #define EMULATEDCOMMANDBAR_TEST_H #include "base.h" class QCompleter; class QLabel; class QColor; class EmulatedCommandBarTest : public BaseTest { Q_OBJECT private Q_SLOTS: void EmulatedCommandBarTests(); private: QCompleter *emulatedCommandBarCompleter(); void verifyCommandBarCompletionVisible(); void verifyCommandBarCompletionsMatches(const QStringList& expectedCompletionList); void verifyCommandBarCompletionContains(const QStringList& expectedCompletionList); QLabel *emulatedCommandTypeIndicator(); void verifyCursorAt(const KTextEditor::Cursor& expectedCursorPos); void clearSearchHistory(); QStringList searchHistory(); void clearCommandHistory(); QStringList commandHistory(); void clearReplaceHistory(); QStringList replaceHistory(); QList rangesOnFirstLine(); void verifyTextEditBackgroundColour(const QColor& expectedBackgroundColour); QLabel* commandResponseMessageDisplay(); void waitForEmulatedCommandBarToHide(long int timeout); void verifyShowsNumberOfReplacementsAcrossNumberOfLines(int numReplacements, int acrossNumLines); }; class FailsIfSlotNotCalled : public QObject { Q_OBJECT public: FailsIfSlotNotCalled(); ~FailsIfSlotNotCalled(); public Q_SLOTS: void slot(); private: - bool m_slotWasCalled; + bool m_slotWasCalled = false; }; class FailsIfSlotCalled : public QObject { Q_OBJECT public: FailsIfSlotCalled(const QString& failureMessage); public Q_SLOTS: void slot(); private: const QString m_failureMessage; }; #endif // kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/autotests/src/vimode/emulatedcommandbarsetupandteardown.h b/autotests/src/vimode/emulatedcommandbarsetupandteardown.h index 30197ed4..a91caea0 100644 --- a/autotests/src/vimode/emulatedcommandbarsetupandteardown.h +++ b/autotests/src/vimode/emulatedcommandbarsetupandteardown.h @@ -1,68 +1,68 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2013-2016 Simon St James * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include namespace KTextEditor { class ViewPrivate; } class KateViInputMode; class QMainWindow; /** * This class is used by the EmulatedCommandBarSetUpAndTearDown class so * the main window is active all the time. */ class WindowKeepActive : public QObject { Q_OBJECT public: WindowKeepActive(QMainWindow *mainWindow); public Q_SLOTS: - bool eventFilter(QObject *object, QEvent *event) Q_DECL_OVERRIDE; + bool eventFilter(QObject *object, QEvent *event) override; private: QMainWindow *m_mainWindow; }; /** * Helper class that is used to setup and tear down tests affecting * the command bar in any way. */ class EmulatedCommandBarSetUpAndTearDown { public: EmulatedCommandBarSetUpAndTearDown(KateViInputMode *inputMode, KTextEditor::ViewPrivate *view, QMainWindow *window); ~EmulatedCommandBarSetUpAndTearDown(); private: KTextEditor::ViewPrivate *m_view; QMainWindow *m_window; WindowKeepActive m_windowKeepActive; KateViInputMode *m_viInputMode; }; diff --git a/autotests/src/vimode/fakecodecompletiontestmodel.h b/autotests/src/vimode/fakecodecompletiontestmodel.h index f643dee6..e9587126 100644 --- a/autotests/src/vimode/fakecodecompletiontestmodel.h +++ b/autotests/src/vimode/fakecodecompletiontestmodel.h @@ -1,102 +1,102 @@ /* * This file is part of the KDE libraries * * Copyright (C) 2014 Miquel Sabaté Solà * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef FAKE_CODE_COMPLETION_TEST_MODEL_H #define FAKE_CODE_COMPLETION_TEST_MODEL_H #include /** * Helper class that mimics some of the behaviour of KDevelop's code completion, in particular * whether it performs "bracket merging" on completed function calls e.g. if we complete a call * to "functionCall(int a)" at the end of the -> here: * * object->( * * we end up with * * object->functionCall( * * and the cursor placed after the closing bracket: the opening bracket is merged with the existing * bracket. * * However, if we do the same with * * object-> * * we end up with * * object->functionCall() * * again with the cursor placed after the opening bracket. This time, the brackets were not merged. * * This helper class is used to test how Macros and replaying of last changes works with complex * code completion. */ class FakeCodeCompletionTestModel : public KTextEditor::CodeCompletionModel { Q_OBJECT public: FakeCodeCompletionTestModel(KTextEditor::View *parent); /** * List of completions, in sorted order. * A string ending with "()" is treated as a call to a function with no arguments. * A string ending with "(...)" is treated as a call to a function with at least one argument. The "..." is not * inserted into the text. * A string ending with "();" or "(...);" is the same as above, and the semi-colon is added. Bracket merging * never happens with strings ending with ";". */ void setCompletions(const QStringList &completions); void setRemoveTailOnComplete(bool removeTailOnCompletion); void setFailTestOnInvocation(bool failTestOnInvocation); bool wasInvoked(); void clearWasInvoked(); /** * A more reliable form of setAutomaticInvocationEnabled(). */ void forceInvocationIfDocTextIs(const QString &desiredDocText); void doNotForceInvocation(); - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; - void executeCompletionItem (KTextEditor::View *view, const KTextEditor::Range &word, const QModelIndex &index) const Q_DECL_OVERRIDE; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + void executeCompletionItem (KTextEditor::View *view, const KTextEditor::Range &word, const QModelIndex &index) const override; KTextEditor::CodeCompletionInterface *cc() const; private: void failTest() const; QStringList m_completions; KTextEditor::ViewPrivate *m_kateView; KTextEditor::Document *m_kateDoc; bool m_removeTailOnCompletion; bool m_failTestOnInvocation; mutable bool m_wasInvoked; QString m_forceInvocationIfDocTextIs; private Q_SLOTS: void textInserted(KTextEditor::Document *document, KTextEditor::Range range); void textRemoved(KTextEditor::Document *document, KTextEditor::Range range); void checkIfShouldForceInvocation(); }; #endif /* FAKE_CODE_COMPLETION_TEST_MODEL_H */ diff --git a/autotests/src/vimode/view.cpp b/autotests/src/vimode/view.cpp index 2f357170..bdcfefee 100644 --- a/autotests/src/vimode/view.cpp +++ b/autotests/src/vimode/view.cpp @@ -1,422 +1,416 @@ /* * This file is part of the KDE libraries * * Copyright (C) 2014 Miquel Sabaté Solà * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include "view.h" using namespace KTextEditor; QTEST_MAIN(ViewTest) void ViewTest::yankHighlightingTests() { const QColor yankHighlightColour = kate_view->renderer()->config()->savedLineColor(); BeginTest("foo bar xyz"); const QList rangesInitial = rangesOnFirstLine(); Q_ASSERT(rangesInitial.isEmpty() && "Assumptions about ranges are wrong - this test is invalid and may need updating!"); TestPressKey("wyiw"); { const QList rangesAfterYank = rangesOnFirstLine(); QCOMPARE(rangesAfterYank.size(), rangesInitial.size() + 1); QCOMPARE(rangesAfterYank.first()->attribute()->background().color(), yankHighlightColour); QCOMPARE(rangesAfterYank.first()->start().line(), 0); QCOMPARE(rangesAfterYank.first()->start().column(), 4); QCOMPARE(rangesAfterYank.first()->end().line(), 0); QCOMPARE(rangesAfterYank.first()->end().column(), 7); } FinishTest("foo bar xyz"); BeginTest("foom bar xyz"); TestPressKey("wY"); { const QList rangesAfterYank = rangesOnFirstLine(); QCOMPARE(rangesAfterYank.size(), rangesInitial.size() + 1); QCOMPARE(rangesAfterYank.first()->attribute()->background().color(), yankHighlightColour); QCOMPARE(rangesAfterYank.first()->start().line(), 0); QCOMPARE(rangesAfterYank.first()->start().column(), 5); QCOMPARE(rangesAfterYank.first()->end().line(), 0); QCOMPARE(rangesAfterYank.first()->end().column(), 12); } FinishTest("foom bar xyz"); // Unhighlight on keypress. DoTest("foo bar xyz", "yiww", "foo bar xyz"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size()); // Update colour on config change. DoTest("foo bar xyz", "yiw", "foo bar xyz"); const QColor newYankHighlightColour = QColor(255, 0, 0); kate_view->renderer()->config()->setSavedLineColor(newYankHighlightColour); QCOMPARE(rangesOnFirstLine().first()->attribute()->background().color(), newYankHighlightColour); // Visual Mode. DoTest("foo", "viwy", "foo"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size() + 1); // Unhighlight on keypress in Visual Mode DoTest("foo", "viwyw", "foo"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size()); // Add a yank highlight and directly (i.e. without using Vim commands, // which would clear the highlight) delete all text; if this deletes the yank highlight behind our back // and we don't respond correctly to this, it will be double-deleted by KateViNormalMode. // Currently, this seems like it doesn't occur, but better safe than sorry :) BeginTest("foo bar xyz"); TestPressKey("yiw"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size() + 1); kate_document->documentReload(); kate_document->clear(); vi_input_mode->reset(); vi_input_mode_manager = vi_input_mode->viInputModeManager(); FinishTest(""); } void ViewTest::visualLineUpDownTests() { // Need to ensure we have dynamic wrap, a fixed width font, and a decent size kate_view. ensureKateViewVisible(); - const QSize oldSize = kate_view->size(); - kate_view->resize(400, 400); // Default size is too cramped to have interesting text in. const QFont oldFont = kate_view->renderer()->config()->font(); QFont fixedWidthFont("Courier"); fixedWidthFont.setStyleHint(QFont::TypeWriter); Q_ASSERT_X(QFontInfo(fixedWidthFont).fixedPitch(), "setting up visual line up down tests", "Need a fixed pitch font!"); kate_view->renderer()->config()->setFont(fixedWidthFont); const bool oldDynWordWrap = KateViewConfig::global()->dynWordWrap(); KateViewConfig::global()->setDynWordWrap(true); const bool oldReplaceTabsDyn = kate_document->config()->replaceTabsDyn(); kate_document->config()->setReplaceTabsDyn(false); const int oldTabWidth = kate_document->config()->tabWidth(); const int tabWidth = 5; kate_document->config()->setTabWidth(tabWidth); KateViewConfig::global()->setShowScrollbars(0); // Compute the maximum width of text before line-wrapping sets it. int textWrappingLength = 1; while (true) { QString text = QString("X").repeated(textWrappingLength) + ' ' + 'O'; const int posOfO = text.length() - 1; kate_document->setText(text); if (kate_view->cursorToCoordinate(Cursor(0, posOfO)).y() != kate_view->cursorToCoordinate(Cursor(0, 0)).y()) { textWrappingLength++; // Number of x's, plus space. break; } textWrappingLength++; } const QString fillsLineAndEndsOnSpace = QString("X").repeated(textWrappingLength - 1) + ' '; // Create a QString consisting of enough concatenated fillsLineAndEndsOnSpace to completely // fill the viewport of the kate View. QString fillsView = fillsLineAndEndsOnSpace; while (true) { kate_document->setText(fillsView); const QString visibleText = kate_document->text(kate_view->visibleRange()); if (fillsView.length() > visibleText.length() * 2) { // Overkill. break; } fillsView += fillsLineAndEndsOnSpace; } const int numVisibleLinesToFillView = fillsView.length() / fillsLineAndEndsOnSpace.length(); { // gk/ gj when there is only one line. DoTest("foo", "lgkr.", "f.o"); DoTest("foo", "lgjr.", "f.o"); } { // gk when sticky bit is set to the end. const QString originalText = fillsLineAndEndsOnSpace.repeated(2); QString expectedText = originalText; kate_document->setText(originalText); Q_ASSERT(expectedText[textWrappingLength - 1] == ' '); expectedText[textWrappingLength - 1] = '.'; DoTest(originalText, "$gkr.", expectedText); } { // Regression test: more than fill the view up, go to end, and do gk on wrapped text (used to crash). // First work out the text that will fill up the view. QString expectedText = fillsView; Q_ASSERT(expectedText[expectedText.length() - textWrappingLength - 1] == ' '); expectedText[expectedText.length() - textWrappingLength - 1] = '.'; DoTest(fillsView, "$gkr.", expectedText); } { // Jump down a few lines all in one go, where we have some variable length lines to navigate. const int numVisualLinesOnLine[] = { 3, 5, 2, 3 }; const int numLines = sizeof(numVisualLinesOnLine) / sizeof(int); const int startVisualLine = 2; const int numberLinesToGoDownInOneGo = 10; int totalVisualLines = 0; for (int i = 0; i < numLines; i++) { totalVisualLines += numVisualLinesOnLine[i]; } QString startText; for (int i = 0; i < numLines; i++) { QString thisLine = fillsLineAndEndsOnSpace.repeated(numVisualLinesOnLine[i]); // Replace trailing space with carriage return. thisLine.chop(1); thisLine.append('\n'); startText += thisLine; } QString expectedText = startText; expectedText[((startVisualLine - 1) + numberLinesToGoDownInOneGo) * fillsLineAndEndsOnSpace.length()] = '.'; Q_ASSERT(numberLinesToGoDownInOneGo + startVisualLine < totalVisualLines); Q_ASSERT(numberLinesToGoDownInOneGo + startVisualLine < numVisibleLinesToFillView); DoTest(startText, QString("gj").repeated(startVisualLine - 1) + QString::number(numberLinesToGoDownInOneGo) + "gjr.", expectedText); // Now go up a few lines. const int numLinesToGoBackUp = 7; expectedText = startText; expectedText[((startVisualLine - 1) + numberLinesToGoDownInOneGo - numLinesToGoBackUp) * fillsLineAndEndsOnSpace.length()] = '.'; DoTest(startText, QString("gj").repeated(startVisualLine - 1) + QString::number(numberLinesToGoDownInOneGo) + "gj" + QString::number(numLinesToGoBackUp) + "gkr.", expectedText); } { // Move down enough lines in one go to disappear off the view. // About half-a-viewport past the end of the current viewport. const int numberLinesToGoDown = numVisibleLinesToFillView * 3 / 2; const int visualColumnNumber = 7; Q_ASSERT(fillsLineAndEndsOnSpace.length() > visualColumnNumber); QString expectedText = fillsView.repeated(2); Q_ASSERT(expectedText[expectedText.length() - textWrappingLength - 1] == ' '); expectedText[visualColumnNumber + fillsLineAndEndsOnSpace.length() * numberLinesToGoDown] = '.'; DoTest(fillsView.repeated(2), QString("l").repeated(visualColumnNumber) + QString::number(numberLinesToGoDown) + "gjr.", expectedText); } { // Deal with dynamic wrapping and indented blocks - continuations of a line are "invisibly" idented by // the same amount as the beginning of the line, and we have to subtract this indentation. const QString unindentedFirstLine = "stickyhelper\n"; const int numIndentationSpaces = 5; Q_ASSERT(textWrappingLength > numIndentationSpaces * 2 /* keep some wriggle room */); const QString indentedFillsLineEndsOnSpace = QString(" ").repeated(numIndentationSpaces) + QString("X").repeated(textWrappingLength - 1 - numIndentationSpaces) + ' '; DoTest(unindentedFirstLine + indentedFillsLineEndsOnSpace + "LINE3", QString("l").repeated(numIndentationSpaces) + "jgjr.", unindentedFirstLine + indentedFillsLineEndsOnSpace + ".INE3"); // The first, non-wrapped portion of the line is not invisibly indented, though, so ensure we don't mess that up. QString expectedSecondLine = indentedFillsLineEndsOnSpace; expectedSecondLine[numIndentationSpaces] = '.'; DoTest(unindentedFirstLine + indentedFillsLineEndsOnSpace + "LINE3", QString("l").repeated(numIndentationSpaces) + "jgjgkr.", unindentedFirstLine + expectedSecondLine + "LINE3"); } { // Take into account any invisible indentation when setting the sticky column. const int numIndentationSpaces = 5; Q_ASSERT(textWrappingLength > numIndentationSpaces * 2 /* keep some wriggle room */); const QString indentedFillsLineEndsOnSpace = QString(" ").repeated(numIndentationSpaces) + QString("X").repeated(textWrappingLength - 1 - numIndentationSpaces) + ' '; const int posInSecondWrappedLineToChange = 3; QString expectedText = indentedFillsLineEndsOnSpace + fillsLineAndEndsOnSpace; expectedText[textWrappingLength + posInSecondWrappedLineToChange] = '.'; DoTest(indentedFillsLineEndsOnSpace + fillsLineAndEndsOnSpace, QString::number(textWrappingLength + posInSecondWrappedLineToChange) + "lgkgjr.", expectedText); // Make sure we can do this more than once (i.e. clear any flags that need clearing). DoTest(indentedFillsLineEndsOnSpace + fillsLineAndEndsOnSpace, QString::number(textWrappingLength + posInSecondWrappedLineToChange) + "lgkgjr.", expectedText); } { // Take into account any invisible indentation when setting the sticky column as above, but use tabs. const QString indentedFillsLineEndsOnSpace = QString("\t") + QString("X").repeated(textWrappingLength - 1 - tabWidth) + ' '; const int posInSecondWrappedLineToChange = 3; QString expectedText = indentedFillsLineEndsOnSpace + fillsLineAndEndsOnSpace; expectedText[textWrappingLength - tabWidth + posInSecondWrappedLineToChange] = '.'; DoTest(indentedFillsLineEndsOnSpace + fillsLineAndEndsOnSpace, QString("fXf ") + QString::number(posInSecondWrappedLineToChange) + "lgkgjr.", expectedText); } { // Deal with the fact that j/ k may set a sticky column that is impossible to adhere to in visual mode because // it is too high. // Here, we have one dummy line and one wrapped line. We start from the beginning of the wrapped line and // move right until we wrap and end up at posInWrappedLineToChange one the second line of the wrapped line. // We then move up and down with j and k to set the sticky column to a value to large to adhere to in a // visual line, and try to move a visual line up. const QString dummyLineForUseWithK("dummylineforusewithk\n"); QString startText = dummyLineForUseWithK + fillsLineAndEndsOnSpace.repeated(2); const int posInWrappedLineToChange = 3; QString expectedText = startText; expectedText[dummyLineForUseWithK.length() + posInWrappedLineToChange] = '.'; DoTest(startText, "j" + QString::number(textWrappingLength + posInWrappedLineToChange) + "lkjgkr.", expectedText); } { // Ensure gj works in Visual mode. Q_ASSERT(fillsLineAndEndsOnSpace.toLower() != fillsLineAndEndsOnSpace); QString expectedText = fillsLineAndEndsOnSpace.toLower() + fillsLineAndEndsOnSpace; expectedText[textWrappingLength] = expectedText[textWrappingLength].toLower(); DoTest(fillsLineAndEndsOnSpace.repeated(2), "vgjgu", expectedText); } { // Ensure gk works in Visual mode. Q_ASSERT(fillsLineAndEndsOnSpace.toLower() != fillsLineAndEndsOnSpace); DoTest(fillsLineAndEndsOnSpace.repeated(2), "$vgkgu", fillsLineAndEndsOnSpace + fillsLineAndEndsOnSpace.toLower()); } { // Some tests for how well we handle things with real tabs. QString beginsWithTabFillsLineEndsOnSpace = "\t"; while (beginsWithTabFillsLineEndsOnSpace.length() + (tabWidth - 1) < textWrappingLength - 1) { beginsWithTabFillsLineEndsOnSpace += 'X'; } beginsWithTabFillsLineEndsOnSpace += ' '; const QString unindentedFirstLine = "stockyhelper\n"; const int posOnThirdLineToChange = 3; QString expectedThirdLine = fillsLineAndEndsOnSpace; expectedThirdLine[posOnThirdLineToChange] = '.'; DoTest(unindentedFirstLine + beginsWithTabFillsLineEndsOnSpace + fillsLineAndEndsOnSpace, QString("l").repeated(tabWidth + posOnThirdLineToChange) + "gjgjr.", unindentedFirstLine + beginsWithTabFillsLineEndsOnSpace + expectedThirdLine); // As above, but go down twice and return to the middle line. const int posOnSecondLineToChange = 2; QString expectedSecondLine = beginsWithTabFillsLineEndsOnSpace; expectedSecondLine[posOnSecondLineToChange + 1 /* "+1" as we're not counting the leading tab as a pos */] = '.'; DoTest(unindentedFirstLine + beginsWithTabFillsLineEndsOnSpace + fillsLineAndEndsOnSpace, QString("l").repeated(tabWidth + posOnSecondLineToChange) + "gjgjgkr.", unindentedFirstLine + expectedSecondLine + fillsLineAndEndsOnSpace); } // Restore back to how we were before. - kate_view->resize(oldSize); kate_view->renderer()->config()->setFont(oldFont); KateViewConfig::global()->setDynWordWrap(oldDynWordWrap); kate_document->config()->setReplaceTabsDyn(oldReplaceTabsDyn); kate_document->config()->setTabWidth(oldTabWidth); } void ViewTest::ScrollViewTests() { QSKIP("This is too unstable in Jenkins", SkipAll); // First of all, we have to initialize some sizes and fonts. ensureKateViewVisible(); - const QSize oldSize = kate_view->size(); - kate_view->resize(200, 200); const QFont oldFont = kate_view->renderer()->config()->font(); QFont fixedWidthFont("Monospace"); fixedWidthFont.setStyleHint(QFont::TypeWriter); fixedWidthFont.setPixelSize(14); Q_ASSERT_X(QFontInfo(fixedWidthFont).fixedPitch(), "setting up ScrollViewTests", "Need a fixed pitch font!"); kate_view->renderer()->config()->setFont(fixedWidthFont); // Generating our text here. QString text; for (int i = 0; i < 20; i++) { text += " aaaaaaaaaaaaaaaa\n"; } // TODO: fix the visibleRange's tests. // zz BeginTest(text); TestPressKey("10l9jzz"); QCOMPARE(kate_view->cursorPosition().line(), 9); QCOMPARE(kate_view->cursorPosition().column(), 10); QCOMPARE(kate_view->visibleRange(), Range(4, 0, 13, 20)); FinishTest(text); // z. BeginTest(text); TestPressKey("10l9jz."); QCOMPARE(kate_view->cursorPosition().line(), 9); QCOMPARE(kate_view->cursorPosition().column(), 4); QCOMPARE(kate_view->visibleRange(), Range(4, 0, 13, 20)); FinishTest(text); // zt BeginTest(text); TestPressKey("10l9jzt"); QCOMPARE(kate_view->cursorPosition().line(), 9); QCOMPARE(kate_view->cursorPosition().column(), 10); QCOMPARE(kate_view->visibleRange(), Range(9, 0, 18, 20)); FinishTest(text); // z BeginTest(text); TestPressKey("10l9jz\\return"); QCOMPARE(kate_view->cursorPosition().line(), 9); QCOMPARE(kate_view->cursorPosition().column(), 4); QCOMPARE(kate_view->visibleRange(), Range(9, 0, 18, 20)); FinishTest(text); // zb BeginTest(text); TestPressKey("10l9jzb"); QCOMPARE(kate_view->cursorPosition().line(), 9); QCOMPARE(kate_view->cursorPosition().column(), 10); QCOMPARE(kate_view->visibleRange(), Range(0, 0, 9, 20)); FinishTest(text); // z- BeginTest(text); TestPressKey("10l9jz-"); QCOMPARE(kate_view->cursorPosition().line(), 9); QCOMPARE(kate_view->cursorPosition().column(), 4); QCOMPARE(kate_view->visibleRange(), Range(0, 0, 9, 20)); FinishTest(text); // Restore back to how we were before. - kate_view->resize(oldSize); kate_view->renderer()->config()->setFont(oldFont); } void ViewTest::clipboardTests_data() { QTest::addColumn("text"); QTest::addColumn("commands"); QTest::addColumn("clipboard"); QTest::newRow("yank") << "yyfoo\nbar" << "yy" << "yyfoo\n"; QTest::newRow("delete") << "ddfoo\nbar" << "dd" << "ddfoo\n"; QTest::newRow("yank empty line") << "\nbar" << "yy" << QString(); QTest::newRow("delete word") << "word foo" << "dw" << "word "; QTest::newRow("delete onechar word") << "w foo" << "dw" << "w "; QTest::newRow("delete onechar") << "word foo" << "dc" << QString(); QTest::newRow("delete empty lines") << " \t\n\n \nfoo" << "d3d" << QString(); } void ViewTest::clipboardTests() { QFETCH(QString, text); QFETCH(QString, commands); QFETCH(QString, clipboard); QApplication::clipboard()->clear(); BeginTest(text); TestPressKey(commands); QCOMPARE(QApplication::clipboard()->text(), clipboard); } QList ViewTest::rangesOnFirstLine() { return kate_document->buffer().rangesForLine(0, kate_view, true); } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d856196e..b4625c96 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,375 +1,376 @@ # handle data files, .desktop & .cmake add_subdirectory(data) # jscripts for the part add_subdirectory( script/data ) # set right defines for libgit2 usage if(LIBGIT2_FOUND) SET (CMAKE_REQUIRED_LIBRARIES LibGit2::LibGit2) set (KTEXTEDITOR_OPTIONAL_LIBS ${KTEXTEDITOR_OPTIONAL_LIBS} LibGit2::LibGit2) endif() if(EditorConfig_FOUND) SET (CMAKE_REQUIRED_LIBRARIES ${EditorConfig_LIBRARIES}) set (KTEXTEDITOR_OPTIONAL_LIBS ${KTEXTEDITOR_OPTIONAL_LIBS} ${EditorConfig_LIBRARIES}) endif() # handle include files, both normal ones and generated ones add_subdirectory(include) # includes include_directories( # for config.h ${CMAKE_BINARY_DIR} # for generated ktexteditor headers ${CMAKE_CURRENT_BINARY_DIR}/include # for normal sources ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/include/ktexteditor ${CMAKE_CURRENT_SOURCE_DIR}/buffer ${CMAKE_CURRENT_SOURCE_DIR}/completion ${CMAKE_CURRENT_SOURCE_DIR}/dialogs ${CMAKE_CURRENT_SOURCE_DIR}/document ${CMAKE_CURRENT_SOURCE_DIR}/script ${CMAKE_CURRENT_SOURCE_DIR}/mode ${CMAKE_CURRENT_SOURCE_DIR}/render ${CMAKE_CURRENT_SOURCE_DIR}/search ${CMAKE_CURRENT_SOURCE_DIR}/syntax ${CMAKE_CURRENT_SOURCE_DIR}/schema ${CMAKE_CURRENT_SOURCE_DIR}/undo ${CMAKE_CURRENT_SOURCE_DIR}/utils ${CMAKE_CURRENT_SOURCE_DIR}/inputmode ${CMAKE_CURRENT_SOURCE_DIR}/view ${CMAKE_CURRENT_SOURCE_DIR}/swapfile ${CMAKE_CURRENT_SOURCE_DIR}/variableeditor) # KTextEditor interface sources set(ktexteditor_LIB_SRCS # text buffer & buffer helpers buffer/katesecuretextbuffer.cpp buffer/katetextbuffer.cpp buffer/katetextblock.cpp buffer/katetextline.cpp buffer/katetextcursor.cpp buffer/katetextrange.cpp buffer/katetexthistory.cpp buffer/katetextfolding.cpp # completion (widget, model, delegate, ...) completion/katecompletionwidget.cpp completion/katecompletionmodel.cpp completion/katecompletiontree.cpp completion/katecompletionconfig.cpp completion/kateargumenthinttree.cpp completion/kateargumenthintmodel.cpp completion/katecompletiondelegate.cpp completion/expandingtree/expandingwidgetmodel.cpp completion/expandingtree/expandingdelegate.cpp completion/expandingtree/expandingtree.cpp # simple internal word completion completion/katewordcompletion.cpp # internal syntax-file based keyword completion completion/katekeywordcompletion.cpp # dialogs dialogs/kateconfigpage.cpp dialogs/katedialogs.cpp dialogs/kateconfigpage.cpp # document (THE document, buffer, lines/cursors/..., CORE STUFF) document/katedocument.cpp document/katebuffer.cpp # undo undo/kateundo.cpp undo/katemodifiedundo.cpp undo/kateundomanager.cpp # scripting script/katescript.cpp script/kateindentscript.cpp script/katecommandlinescript.cpp script/katescriptmanager.cpp script/katescriptaction.cpp # scripting wrappers +script/katescripteditor.cpp script/katescriptdocument.cpp script/katescriptview.cpp script/katescripthelpers.cpp # mode (modemanager and co) mode/katemodemanager.cpp mode/katemodeconfigpage.cpp mode/katemodemenu.cpp mode/katewildcardmatcher.cpp # modeline variable editor variableeditor/variablelineedit.cpp variableeditor/variablelistview.cpp variableeditor/variableeditor.cpp variableeditor/variableitem.cpp variableeditor/katehelpbutton.cpp # printing classes printing/kateprinter.cpp printing/printpainter.cpp printing/printconfigwidgets.cpp # rendering stuff (katerenderer and helpers) render/katerenderer.cpp render/katerenderrange.cpp render/katelayoutcache.cpp render/katetextlayout.cpp render/katelinelayout.cpp # search stuff search/kateregexp.cpp search/kateplaintextsearch.cpp search/kateregexpsearch.cpp search/katematch.cpp search/katesearchbar.cpp # syntax related stuff (highlighting, xml file parsing, ...) syntax/katesyntaxmanager.cpp syntax/katehighlight.cpp syntax/katehighlighthelpers.cpp syntax/katehighlightmenu.cpp syntax/katesyntaxdocument.cpp syntax/katehighlightingcmds.cpp # view stuff (THE view and its helpers) view/kateview.cpp view/kateviewinternal.cpp view/kateviewhelpers.cpp view/katemessagewidget.cpp view/katefadeeffect.cpp view/kateanimation.cpp view/katetextanimation.cpp view/katetextpreview.cpp view/katestatusbar.cpp view/wordcounter.cpp view/katemulticursor.cpp view/katemulticlipboard.cpp # spell checking spellcheck/prefixstore.h spellcheck/prefixstore.cpp spellcheck/ontheflycheck.h spellcheck/ontheflycheck.cpp spellcheck/spellcheck.h spellcheck/spellcheck.cpp spellcheck/spellcheckdialog.h spellcheck/spellcheckdialog.cpp spellcheck/spellcheckbar.cpp spellcheck/spellingmenu.h spellcheck/spellingmenu.cpp # generic stuff, unsorted... utils/katecmds.cpp utils/kateconfig.cpp utils/katebookmarks.cpp utils/kateautoindent.cpp utils/katetemplatehandler.cpp utils/kateglobal.cpp utils/katecmd.cpp utils/ktexteditor.cpp utils/document.cpp utils/range.cpp utils/documentcursor.cpp utils/attribute.cpp utils/codecompletioninterface.cpp utils/codecompletionmodel.cpp utils/codecompletionmodelcontrollerinterface.cpp utils/configinterface.cpp utils/movinginterface.cpp utils/movingcursor.cpp utils/movingrange.cpp utils/movingrangefeedback.cpp utils/messageinterface.cpp utils/application.cpp utils/mainwindow.cpp utils/katedefaultcolors.cpp utils/katecommandrangeexpressionparser.cpp utils/katesedcmd.cpp # schema schema/kateschema.cpp schema/kateschemaconfig.cpp schema/katestyletreewidget.cpp schema/katecolortreewidget.cpp schema/katecategorydrawer.cpp # swapfile swapfile/kateswapdiffcreator.cpp swapfile/kateswapfile.cpp # export as HTML export/exporter.cpp export/htmlexporter.cpp # input modes inputmode/kateabstractinputmode.cpp inputmode/kateabstractinputmodefactory.cpp inputmode/katenormalinputmode.cpp inputmode/katenormalinputmodefactory.cpp ) # optionally compile with EditorConfig support if(EditorConfig_FOUND) set(ktexteditor_LIB_SRCS ${ktexteditor_LIB_SRCS} document/editorconfig.cpp) endif() ki18n_wrap_ui(ktexteditor_LIB_SRCS dialogs/textareaappearanceconfigwidget.ui dialogs/bordersappearanceconfigwidget.ui dialogs/commandmenuconfigwidget.ui dialogs/commandmenueditwidget.ui dialogs/completionconfigtab.ui dialogs/navigationconfigwidget.ui dialogs/editconfigwidget.ui dialogs/filetypeconfigwidget.ui dialogs/indentationconfigwidget.ui dialogs/opensaveconfigwidget.ui dialogs/opensaveconfigadvwidget.ui dialogs/completionconfigwidget.ui search/searchbarincremental.ui search/searchbarpower.ui spellcheck/spellcheckbar.ui dialogs/spellcheckconfigwidget.ui schema/howtoimportschema.ui ) # add the resource files, the one with mascot + ui file and the generated ones qt5_add_resources( ktexteditor_LIB_SRCS data/ktexteditor.qrc "${CMAKE_CURRENT_BINARY_DIR}/script/data/script.qrc") if (BUILD_VIMODE) ki18n_wrap_ui(ktexteditor_LIB_SRCS vimode/config/configwidget.ui) set(ktexteditor_LIB_SRCS ${ktexteditor_LIB_SRCS} inputmode/kateviinputmode.cpp inputmode/kateviinputmodefactory.cpp # vi input mode vimode/config/configtab.cpp vimode/modes/insertvimode.cpp vimode/modes/modebase.cpp vimode/modes/normalvimode.cpp vimode/modes/replacevimode.cpp vimode/modes/visualvimode.cpp vimode/appcommands.cpp vimode/cmds.cpp vimode/inputmodemanager.cpp vimode/command.cpp vimode/motion.cpp vimode/range.cpp vimode/keyparser.cpp vimode/globalstate.cpp vimode/emulatedcommandbar/emulatedcommandbar.cpp vimode/emulatedcommandbar/matchhighlighter.cpp vimode/emulatedcommandbar/completer.cpp vimode/emulatedcommandbar/activemode.cpp vimode/emulatedcommandbar/interactivesedreplacemode.cpp vimode/emulatedcommandbar/searchmode.cpp vimode/emulatedcommandbar/commandmode.cpp vimode/commandrangeexpressionparser.cpp vimode/keymapper.cpp vimode/marks.cpp vimode/jumps.cpp vimode/history.cpp vimode/macros.cpp vimode/mappings.cpp vimode/registers.cpp vimode/searcher.cpp vimode/completion.cpp vimode/completionrecorder.cpp vimode/completionreplayer.cpp vimode/macrorecorder.cpp vimode/lastchangerecorder.cpp ) endif() add_library(KF5TextEditor ${ktexteditor_LIB_SRCS} ${KTEXTEDITOR_PUBLIC_HEADERS}) generate_export_header(KF5TextEditor BASE_NAME KTextEditor) add_library(KF5::TextEditor ALIAS KF5TextEditor) target_include_directories(KF5TextEditor INTERFACE "$") # API is more or less KParts++, other stuff is used only internally target_link_libraries(KF5TextEditor PUBLIC KF5::Parts PRIVATE Qt5::Qml Qt5::PrintSupport KF5::I18n KF5::Archive KF5::GuiAddons KF5::IconThemes KF5::ItemViews KF5::SonnetCore KF5::SyntaxHighlighting ${KTEXTEDITOR_OPTIONAL_LIBS} ) set_target_properties(KF5TextEditor PROPERTIES VERSION ${KTEXTEDITOR_VERSION_STRING} SOVERSION ${KTEXTEDITOR_SOVERSION} EXPORT_NAME "TextEditor" ) install(TARGETS KF5TextEditor EXPORT KF5TextEditorTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ktexteditor_export.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KTextEditor COMPONENT Devel ) if(BUILD_QCH) ecm_add_qch( KF5TextEditor_QCH NAME KTextEditor BASE_NAME KF5TextEditor VERSION ${KF5_VERSION} ORG_DOMAIN org.kde SOURCES # using only public headers, to cover only public API ${KTEXTEDITOR_PUBLIC_HEADERS} "${CMAKE_SOURCE_DIR}/docs/apidocs-groups.dox" "${CMAKE_SOURCE_DIR}/docs/coding-guidelines.dox" "${CMAKE_SOURCE_DIR}/docs/design.dox" "${CMAKE_SOURCE_DIR}/docs/porting.dox" MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md" IMAGE_DIRS "${CMAKE_SOURCE_DIR}/docs/pics" LINK_QCHS KF5Parts_QCH BLANK_MACROS KTEXTEDITOR_EXPORT KTEXTEDITOR_DEPRECATED KTEXTEDITOR_DEPRECATED_EXPORT TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} COMPONENT Devel ) endif() include(ECMGeneratePriFile) ecm_generate_pri_file(BASE_NAME KTextEditor LIB_NAME KF5TextEditor DEPS "KParts" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/KTextEditor) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) add_executable(kauth_ktexteditor_helper buffer/katesecuretextbuffer.cpp) target_link_libraries(kauth_ktexteditor_helper KF5::Auth ) install(TARGETS kauth_ktexteditor_helper DESTINATION ${KAUTH_HELPER_INSTALL_DIR} ) kauth_install_helper_files(kauth_ktexteditor_helper org.kde.ktexteditor.katetextbuffer root) kauth_install_actions(org.kde.ktexteditor.katetextbuffer buffer/org.kde.ktexteditor.katetextbuffer.actions) # add part add_subdirectory(part) diff --git a/src/buffer/katesecuretextbuffer.cpp b/src/buffer/katesecuretextbuffer.cpp index 26e0a10d..c0146089 100644 --- a/src/buffer/katesecuretextbuffer.cpp +++ b/src/buffer/katesecuretextbuffer.cpp @@ -1,168 +1,161 @@ /* This file is part of the KTextEditor project. * * Copyright (C) 2017 KDE Developers * * 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 "katesecuretextbuffer_p.h" #include "config.h" #ifndef Q_OS_WIN #include #include #endif #include #include #include #include #include KAUTH_HELPER_MAIN("org.kde.ktexteditor.katetextbuffer", SecureTextBuffer) ActionReply SecureTextBuffer::savefile(const QVariantMap &args) { const QString sourceFile = args[QStringLiteral("sourceFile")].toString(); const QString targetFile = args[QStringLiteral("targetFile")].toString(); const QByteArray checksum = args[QStringLiteral("checksum")].toByteArray(); const uint ownerId = (uint) args[QStringLiteral("ownerId")].toInt(); const uint groupId = (uint) args[QStringLiteral("groupId")].toInt(); if (saveFileInternal(sourceFile, targetFile, checksum, ownerId, groupId)) { return ActionReply::SuccessReply(); } return ActionReply::HelperErrorReply(); } bool SecureTextBuffer::saveFileInternal(const QString &sourceFile, const QString &targetFile, const QByteArray &checksum, const uint ownerId, const uint groupId) { - QFileInfo targetFileInfo(targetFile); - if (!QDir::setCurrent(targetFileInfo.dir().path())) { + /** + * open source file for reading + * if not possible, signal error + */ + QFile readFile(sourceFile); + if (!readFile.open(QIODevice::ReadOnly)) { return false; } - // get information about target file - const QString targetFileName = targetFileInfo.fileName(); - targetFileInfo.setFile(targetFileName); - const bool newFile = !targetFileInfo.exists(); - - // open source and target file - QFile readFile(sourceFile); - //TODO use QSaveFile for saving contents and automatic atomic move on commit() when QSaveFile's security problem - // (default temporary file permissions) is fixed - // - // We will first generate temporary filename and then use it relatively to prevent an attacker - // to trick us to write contents to a different file by changing underlying directory. - QTemporaryFile tempFile(targetFileName); + /** + * construct file info for target file + * we need to know things like path/exists/permissions + */ + const QFileInfo targetFileInfo(targetFile); + + /** + * create temporary file in current directory to be able to later do an atomic rename + * we need to pass full path, else QTemporaryFile uses the temporary directory + * if not possible, signal error, this catches e.g. a non-existing target directory, too + */ + QTemporaryFile tempFile(targetFileInfo.absolutePath() + QStringLiteral("/secureXXXXXX")); if (!tempFile.open()) { return false; } - tempFile.close(); - QString tempFileName = QFileInfo(tempFile).fileName(); - tempFile.setFileName(tempFileName); - if (!readFile.open(QIODevice::ReadOnly) || !tempFile.open()) { - return false; - } - const int tempFileDescriptor = tempFile.handle(); - // prepare checksum maker + /** + * copy contents + do checksumming + * if not possible, signal error + */ QCryptographicHash cryptographicHash(checksumAlgorithm); - - // copy contents + const qint64 bufferLength = 4096; char buffer[bufferLength]; qint64 read = -1; while ((read = readFile.read(buffer, bufferLength)) > 0) { cryptographicHash.addData(buffer, read); if (tempFile.write(buffer, read) == -1) { return false; } } - // check that copying was successful and checksum matched - QByteArray localChecksum = cryptographicHash.result(); - if (read == -1 || localChecksum != checksum || !tempFile.flush()) { + /** + * check that copying was successful and checksum matched + * we need to flush the file, as QTemporaryFile keeps the handle open + * and we later do things like renaming of the file! + * if not possible, signal error + */ + if ((read == -1) || (cryptographicHash.result() != checksum) || !tempFile.flush()) { return false; } - tempFile.close(); - - if (newFile) { + /** + * try to preserve the permissions + */ + if (!targetFileInfo.exists()) { // ensure new file is readable by anyone tempFile.setPermissions(tempFile.permissions() | QFile::Permission::ReadGroup | QFile::Permission::ReadOther); } else { // ensure the same file permissions tempFile.setPermissions(targetFileInfo.permissions()); + // ensure file has the same owner and group as before - setOwner(tempFileDescriptor, ownerId, groupId); + setOwner(tempFile.handle(), ownerId, groupId); } - // rename temporary file to the target file - if (moveFile(tempFileName, targetFileName)) { + /** + * try to (atomic) rename temporary file to the target file + */ + if (moveFile(tempFile.fileName(), targetFileInfo.filePath())) { // temporary file was renamed, there is nothing to remove anymore tempFile.setAutoRemove(false); return true; } + + /** + * we failed + * QTemporaryFile will handle cleanup + */ return false; } void SecureTextBuffer::setOwner(const int filedes, const uint ownerId, const uint groupId) { #ifndef Q_OS_WIN if (ownerId != (uint)-2 && groupId != (uint)-2) { const int result = fchown(filedes, ownerId, groupId); // set at least correct group if owner cannot be changed if (result != 0 && errno == EPERM) { fchown(filedes, getuid(), groupId); } } #else // no-op for windows #endif } bool SecureTextBuffer::moveFile(const QString &sourceFile, const QString &targetFile) { -#ifndef Q_OS_WIN +#if !defined(Q_OS_WIN) && !defined(Q_OS_ANDROID) const int result = std::rename(QFile::encodeName(sourceFile).constData(), QFile::encodeName(targetFile).constData()); - if (result == 0) { - syncToDisk(QFile(targetFile).handle()); - return true; - } - return false; + return (result == 0); #else // use racy fallback for windows QFile::remove(targetFile); return QFile::rename(sourceFile, targetFile); #endif } - -void SecureTextBuffer::syncToDisk(const int fd) -{ -#ifndef Q_OS_WIN -#if HAVE_FDATASYNC - fdatasync(fd); -#else - fsync(fd); -#endif -#else - // no-op for windows -#endif -} - diff --git a/src/buffer/katesecuretextbuffer_p.h b/src/buffer/katesecuretextbuffer_p.h index a38285b6..e00721c7 100644 --- a/src/buffer/katesecuretextbuffer_p.h +++ b/src/buffer/katesecuretextbuffer_p.h @@ -1,80 +1,76 @@ /* This file is part of the KTextEditor project. * * Copyright (C) 2017 KDE Developers * * 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_SECURE_TEXTBUFFER_P_H #define KATE_SECURE_TEXTBUFFER_P_H #include #include #include #include using namespace KAuth; /** * Class used as KAuth helper binary. * It is supposed to be called through KAuth action. * * It also contains couple of common methods intended to be used * directly by TextBuffer as well as from helper binary. * * This class should only be used by TextBuffer. */ class SecureTextBuffer : public QObject { Q_OBJECT public: SecureTextBuffer() {} ~SecureTextBuffer() {} /** * Common helper method */ static void setOwner(const int filedes, const uint ownerId, const uint groupId); static const QCryptographicHash::Algorithm checksumAlgorithm = QCryptographicHash::Algorithm::Sha512; private: - static const qint64 bufferLength = 4096; - /** * Saves file contents using sets permissions. */ static bool saveFileInternal(const QString &sourceFile, const QString &targetFile, const QByteArray &checksum, const uint ownerId, const uint groupId); static bool moveFile(const QString &sourceFile, const QString &targetFile); - static void syncToDisk(const int fd); - public Q_SLOTS: /** * KAuth action to perform both prepare or move work based on given parameters. * We keep this code in one method to prevent multiple KAuth user queries during one save action. */ static ActionReply savefile(const QVariantMap &args); }; #endif diff --git a/src/buffer/katetextcursor.h b/src/buffer/katetextcursor.h index ded57069..341fcf3f 100644 --- a/src/buffer/katetextcursor.h +++ b/src/buffer/katetextcursor.h @@ -1,260 +1,260 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 Christoph Cullmann * * Based on code of the SmartCursor/Range by: * Copyright (C) 2003-2005 Hamish Rodda * * 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_TEXTCURSOR_H #define KATE_TEXTCURSOR_H #include #include #include "katetextblock.h" namespace Kate { class TextBuffer; class TextBlock; class TextRange; /** * Class representing a 'clever' text cursor. * It will automagically move if the text inside the buffer it belongs to is modified. * By intention no subclass of KTextEditor::Cursor, must be converted manually. */ class KTEXTEDITOR_EXPORT TextCursor : public KTextEditor::MovingCursor { // range wants direct access to some internals friend class TextRange; // this is a friend, because this is needed to efficiently transfer cursors from on to an other block friend class TextBlock; private: /** * Construct a text cursor with given range as parent, private, used by TextRange constructor only. * @param buffer text buffer this cursor belongs to * @param range text range this cursor is part of * @param position wanted cursor position, if not valid for given buffer, will lead to invalid cursor * @param insertBehavior behavior of this cursor on insert of text at its position */ TextCursor(TextBuffer &buffer, TextRange *range, const KTextEditor::Cursor &position, InsertBehavior insertBehavior); public: /** * Construct a text cursor. * @param buffer text buffer this cursor belongs to * @param position wanted cursor position, if not valid for given buffer, will lead to invalid cursor * @param insertBehavior behavior of this cursor on insert of text at its position */ TextCursor(TextBuffer &buffer, const KTextEditor::Cursor &position, InsertBehavior insertBehavior); /** * Destruct the text cursor */ - ~TextCursor(); + ~TextCursor() override; /** * Set insert behavior. * @param insertBehavior new insert behavior */ - void setInsertBehavior(InsertBehavior insertBehavior) Q_DECL_OVERRIDE + void setInsertBehavior(InsertBehavior insertBehavior) override { m_moveOnInsert = insertBehavior == MoveOnInsert; } /** * Get current insert behavior. * @return current insert behavior */ - InsertBehavior insertBehavior() const Q_DECL_OVERRIDE + InsertBehavior insertBehavior() const override { return m_moveOnInsert ? MoveOnInsert : StayOnInsert; } /** * Gets the document to which this cursor is bound. * \return a pointer to the document */ - KTextEditor::Document *document() const Q_DECL_OVERRIDE; + KTextEditor::Document *document() const override; /** * Fast way to set the current cursor position to \e position. * * \param position new cursor position */ void setPosition(const TextCursor &position); /** * Set the current cursor position to \e position. * * \param position new cursor position */ - void setPosition(const KTextEditor::Cursor &position) Q_DECL_OVERRIDE; + void setPosition(const KTextEditor::Cursor &position) override; /** * \overload * * Set the cursor position to \e line and \e column. * * \param line new cursor line * \param column new cursor column */ void setPosition(int line, int column) { KTextEditor::MovingCursor::setPosition(line, column); } /** * Retrieve the line on which this cursor is situated. * \return line number, where 0 is the first line. */ - int line() const Q_DECL_OVERRIDE; + int line() const override; /** * Non-virtual version of line(), which is faster. * Inlined for fast access (especially in KateTextBuffer::rangesForLine * \return line number, where 0 is the first line. */ int lineInternal() const { // invalid cursor have no block if (!m_block) { return -1; } // else, calculate real line return m_block->startLine() + m_line; } /** * Retrieve the column on which this cursor is situated. * \return column number, where 0 is the first column. */ - int column() const Q_DECL_OVERRIDE + int column() const override { return m_column; } /** * Non-virtual version of column(), which is faster. * \return column number, where 0 is the first column. * */ int columnInternal() const { return m_column; } /** * Get range this cursor belongs to, if any * @return range this pointer is part of, else 0 */ - KTextEditor::MovingRange *range() const Q_DECL_OVERRIDE; + KTextEditor::MovingRange *range() const override; /** * Get range this cursor belongs to, if any * @return range this pointer is part of, else 0 */ Kate::TextRange *kateRange() const { return m_range; } /** * Get block this cursor belongs to, if any * @return block this pointer is part of, else 0 */ TextBlock *block() const { return m_block; } /** * Get offset into block this cursor belongs to, if any * @return offset into block this pointer is part of, else -1 */ int lineInBlock() const { if (m_block) { return m_line; } return -1; } private: /** * no copy constructor, don't allow this to be copied. */ TextCursor(const TextCursor &); /** * no assignment operator, no copying around. */ TextCursor &operator= (const TextCursor &); /** * Set the current cursor position to \e position. * Internal helper to allow the same code be used for constructor and * setPosition. * * @param position new cursor position * @param init is this the initial setup of the position in the constructor? */ void setPosition(const KTextEditor::Cursor &position, bool init); private: /** * parent text buffer * is a reference, and no pointer, as this must always exist and can't change */ TextBuffer &m_buffer; /** * range this cursor belongs to * may be null, then no range owns this cursor * can not change after initial assignment */ TextRange *const m_range; /** * parent text block, valid cursors always belong to a block, else they are invalid. */ TextBlock *m_block; /** * line, offset in block, or -1 */ int m_line; /** * column */ int m_column; /** * should this cursor move on insert */ bool m_moveOnInsert; }; } #endif diff --git a/src/buffer/katetexthistory.h b/src/buffer/katetexthistory.h index 898407a4..707912c0 100644 --- a/src/buffer/katetexthistory.h +++ b/src/buffer/katetexthistory.h @@ -1,245 +1,244 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 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_TEXTHISTORY_H #define KATE_TEXTHISTORY_H #include #include #include #include "katetextrange.h" namespace Kate { class TextBuffer; /** * Class representing the editing history of a TextBuffer */ class KTEXTEDITOR_EXPORT TextHistory { friend class TextBuffer; friend class TextBlock; public: /** * Current revision, just relay the revision of the buffer * @return current revision */ qint64 revision() const; /** * Last revision the buffer got successful saved * @return last revision buffer got saved, -1 if none */ qint64 lastSavedRevision() const { return m_lastSavedRevision; } /** * Lock a revision, this will keep it around until released again. * But all revisions will always be cleared on buffer clear() (and therefor load()) * @param revision revision to lock */ void lockRevision(qint64 revision); /** * Release a revision. * @param revision revision to release */ void unlockRevision(qint64 revision); /** * Transform a cursor from one revision to an other. * @param line line number of the cursor to transform * @param column column number of the cursor to transform * @param insertBehavior behavior of this cursor on insert of text at its position * @param fromRevision from this revision we want to transform * @param toRevision to this revision we want to transform, default of -1 is current revision */ void transformCursor(int &line, int &column, KTextEditor::MovingCursor::InsertBehavior insertBehavior, qint64 fromRevision, qint64 toRevision = -1); /** * Transform a range from one revision to an other. * @param range range to transform * @param insertBehaviors behavior of this range on insert of text at its position * @param emptyBehavior behavior on becoming empty * @param fromRevision from this revision we want to transform * @param toRevision to this revision we want to transform, default of -1 is current revision */ void transformRange(KTextEditor::Range &range, KTextEditor::MovingRange::InsertBehaviors insertBehaviors, KTextEditor::MovingRange::EmptyBehavior emptyBehavior, qint64 fromRevision, qint64 toRevision = -1); private: /** * Class representing one entry in the editing history. */ class Entry { public: /** * transform cursor for this history entry * @param line line number of the cursor to transform * @param column column number of the cursor to transform * @param moveOnInsert behavior of this cursor on insert of text at its position */ void transformCursor(int &line, int &column, bool moveOnInsert) const; /** * reverse transform cursor for this history entry * @param line line number of the cursor to transform * @param column column number of the cursor to transform * @param moveOnInsert behavior of this cursor on insert of text at its position */ void reverseTransformCursor(int &line, int &column, bool moveOnInsert) const; /** * Types of entries, matching editing primitives of buffer and placeholder */ enum Type { NoChange , WrapLine , UnwrapLine , InsertText , RemoveText }; /** * Default Constructor, invalidates all fields */ Entry() - : referenceCounter(0), type(NoChange), line(-1), column(-1), length(-1), oldLineLength(-1) { } /** * Reference counter, how often ist this entry referenced from the outside? */ - unsigned int referenceCounter; + unsigned int referenceCounter = 0; /** * Type of change */ - Type type; + Type type = NoChange; /** * line the change occurred */ - int line; + int line = -1; /** * column the change occurred */ - int column; + int column = -1; /** * length of change (length of insert or removed text) */ - int length; + int length = -1; /** * old line length (needed for unwrap and insert) */ - int oldLineLength; + int oldLineLength = -1; }; /** * Construct an empty text history. * @param buffer buffer this text history belongs to */ explicit TextHistory(TextBuffer &buffer); /** * Destruct the text history */ ~TextHistory(); /** * Clear the edit history, this is done on clear() in buffer. */ void clear(); /** * Set current revision as last saved revision */ void setLastSavedRevision(); /** * Notify about wrap line at given cursor position. * @param position line/column as cursor where to wrap */ void wrapLine(const KTextEditor::Cursor &position); /** * Notify about unwrap given line. * @param line line to unwrap * @param oldLineLength text length of the line in front of this one before this unwrap */ void unwrapLine(int line, int oldLineLength); /** * Notify about insert text at given cursor position. * @param position position where to insert text * @param length text length to be inserted * @param oldLineLength text length of the line before this insert */ void insertText(const KTextEditor::Cursor &position, int length, int oldLineLength); /** * Notify about remove text at given range. * @param range range of text to remove, must be on one line only. * @param oldLineLength text length of the line before this remove */ void removeText(const KTextEditor::Range &range, int oldLineLength); /** * Generic function to add a entry to the history. Is used by the above functions for the different editing primitives. * @param entry new entry to add */ void addEntry(const Entry &entry); private: /** * TextBuffer this history belongs to */ TextBuffer &m_buffer; /** * Last revision the buffer got saved */ qint64 m_lastSavedRevision; /** * history of edits */ QList m_historyEntries; /** * offset for the first entry in m_history, to which revision it really belongs? */ qint64 m_firstHistoryEntryRevision; }; } #endif diff --git a/src/buffer/katetextline.cpp b/src/buffer/katetextline.cpp index 78a7eccc..8693d0de 100644 --- a/src/buffer/katetextline.cpp +++ b/src/buffer/katetextline.cpp @@ -1,208 +1,219 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 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. */ #include "katetextline.h" namespace Kate { TextLineData::TextLineData() - : m_flags(0) { } TextLineData::TextLineData(const QString &text) : m_text(text) , m_flags(0) { } TextLineData::~TextLineData() { } int TextLineData::firstChar() const { return nextNonSpaceChar(0); } int TextLineData::lastChar() const { return previousNonSpaceChar(m_text.length() - 1); } int TextLineData::nextNonSpaceChar(int pos) const { Q_ASSERT(pos >= 0); for (int i = pos; i < m_text.length(); i++) if (!m_text[i].isSpace()) { return i; } return -1; } int TextLineData::previousNonSpaceChar(int pos) const { if (pos >= m_text.length()) { pos = m_text.length() - 1; } for (int i = pos; i >= 0; i--) if (!m_text[i].isSpace()) { return i; } return -1; } QString TextLineData::leadingWhitespace() const { if (firstChar() < 0) { return string(0, length()); } return string(0, firstChar()); } int TextLineData::indentDepth(int tabWidth) const { int d = 0; const int len = m_text.length(); const QChar *unicode = m_text.unicode(); for (int i = 0; i < len; ++i) { if (unicode[i].isSpace()) { if (unicode[i] == QLatin1Char('\t')) { d += tabWidth - (d % tabWidth); } else { d++; } } else { return d; } } return d; } bool TextLineData::matchesAt(int column, const QString &match) const { if (column < 0) { return false; } const int len = m_text.length(); const int matchlen = match.length(); if ((column + matchlen) > len) { return false; } const QChar *unicode = m_text.unicode(); const QChar *matchUnicode = match.unicode(); for (int i = 0; i < matchlen; ++i) if (unicode[i + column] != matchUnicode[i]) { return false; } return true; } int TextLineData::toVirtualColumn(int column, int tabWidth) const { if (column < 0) { return 0; } int x = 0; const int zmax = qMin(column, m_text.length()); const QChar *unicode = m_text.unicode(); for (int z = 0; z < zmax; ++z) { if (unicode[z] == QLatin1Char('\t')) { x += tabWidth - (x % tabWidth); } else { x++; } } return x + column - zmax; } int TextLineData::fromVirtualColumn(int column, int tabWidth) const { if (column < 0) { return 0; } const int zmax = qMin(m_text.length(), column); const QChar *unicode = m_text.unicode(); int x = 0; int z = 0; for (; z < zmax; ++z) { int diff = 1; if (unicode[z] == QLatin1Char('\t')) { diff = tabWidth - (x % tabWidth); } if (x + diff > column) { break; } x += diff; } return z + qMax(column - x, 0); } int TextLineData::virtualLength(int tabWidth) const { int x = 0; const int len = m_text.length(); const QChar *unicode = m_text.unicode(); for (int z = 0; z < len; ++z) { if (unicode[z] == QLatin1Char('\t')) { x += tabWidth - (x % tabWidth); } else { x++; } } return x; } void TextLineData::addAttribute(const Attribute &attribute) { // try to append to previous range, if no folding info + same attribute value if ((attribute.foldingValue == 0) && !m_attributesList.isEmpty() && (m_attributesList.back().foldingValue == 0) && (m_attributesList.back().attributeValue == attribute.attributeValue) && ((m_attributesList.back().offset + m_attributesList.back().length) == attribute.offset)) { m_attributesList.back().length += attribute.length; return; } m_attributesList.append(attribute); } +short TextLineData::attribute(int pos) const +{ + auto found = std::upper_bound(m_attributesList.cbegin(), m_attributesList.cend(), pos, + [](const int &p, const Attribute &x) { + return p < x.offset + x.length; + }); + if (found != m_attributesList.cend() && found->offset <= pos && pos < (found->offset + found->length)) { + return found->attributeValue; + } + return 0; +} + } diff --git a/src/buffer/katetextline.h b/src/buffer/katetextline.h index 81682638..19167c99 100644 --- a/src/buffer/katetextline.h +++ b/src/buffer/katetextline.h @@ -1,496 +1,483 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 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_TEXTLINE_H #define KATE_TEXTLINE_H #include #include #include #include namespace Kate { /** * Class representing a single text line. * For efficience reasons, not only pure text is stored here, but also additional data. * Will be only accessed over shared pointers. */ class KTEXTEDITOR_EXPORT TextLineData { /** * TexBlock is a friend class, only one allowed to touch the text content. */ friend class TextBlock; public: /** * Context stack */ typedef QVector ContextStack; /** * Attribute storage */ class Attribute { public: /** * Attribute constructor * @param _offset offset * @param _length length * @param _attributeValue attribute value * @param _foldingValue folding value */ Attribute(int _offset = 0, int _length = 0, short _attributeValue = 0, short _foldingValue = 0) : offset(_offset) , length(_length) , attributeValue(_attributeValue) , foldingValue(_foldingValue) { } /** * offset */ int offset; /** * length */ int length; /** * attribute value (to encode type of this range) */ short attributeValue; /** * folding value (begin/end type) */ short foldingValue; }; /** * Flags of TextLineData */ enum Flags { flagHlContinue = 1, flagAutoWrapped = 2, flagFoldingStartAttribute = 4, flagFoldingStartIndentation = 8, flagLineModified = 16, flagLineSavedOnDisk = 32 }; /** * Construct an empty text line. */ TextLineData(); /** * Construct an text line with given text. * @param text text to use for this line */ explicit TextLineData(const QString &text); /** * Destruct the text line */ ~TextLineData(); /** * Accessor to the text contained in this line. * @return text of this line as constant reference */ const QString &text() const { return m_text; } /** * Returns the position of the first non-whitespace character * @return position of first non-whitespace char or -1 if there is none */ int firstChar() const; /** * Returns the position of the last non-whitespace character * @return position of last non-whitespace char or -1 if there is none */ int lastChar() const; /** * Find the position of the next char that is not a space. * @param pos Column of the character which is examined first. * @return True if the specified or a following character is not a space * Otherwise false. */ int nextNonSpaceChar(int pos) const; /** * Find the position of the previous char that is not a space. * @param pos Column of the character which is examined first. * @return The position of the first non-whitespace character preceding pos, * or -1 if none is found. */ int previousNonSpaceChar(int pos) const; /** * Returns the character at the given \e column. If \e column is out of * range, the return value is QChar(). * @param column column you want char for * @return char at given column or QChar() */ inline QChar at(int column) const { if (column >= 0 && column < m_text.length()) { return m_text[column]; } return QChar(); } /** * Same as at(). * @param column column you want char for * @return char at given column or QChar() */ inline QChar operator[](int column) const { if (column >= 0 && column < m_text.length()) { return m_text[column]; } return QChar(); } inline void markAsModified(bool modified) { if (modified) { m_flags |= flagLineModified; m_flags &= (~flagLineSavedOnDisk); } else { m_flags &= (~flagLineModified); } } inline bool markedAsModified() const { return m_flags & flagLineModified; } inline void markAsSavedOnDisk(bool savedOnDisk) { if (savedOnDisk) { m_flags |= flagLineSavedOnDisk; m_flags &= (~flagLineModified); } else { m_flags &= (~flagLineSavedOnDisk); } } inline bool markedAsSavedOnDisk() const { return m_flags & flagLineSavedOnDisk; } /** * Is on this line a folding start? * @return folding start line or not? */ bool markedAsFoldingStart() const { return m_flags & (flagFoldingStartAttribute | flagFoldingStartIndentation); } /** * Clear folding start status. */ void clearMarkedAsFoldingStart() { m_flags &= ~(flagFoldingStartAttribute | flagFoldingStartIndentation); } /** * Is on this line a folding start per attribute? * @return folding start line per attribute? or not? */ bool markedAsFoldingStartAttribute() const { return m_flags & flagFoldingStartAttribute; } /** * Is on this line a folding start per indentation? * @return folding start line per indentation? or not? */ bool markedAsFoldingStartIndentation() const { return m_flags & flagFoldingStartIndentation; } /** * Mark as folding start line of an attribute based folding. */ void markAsFoldingStartAttribute() { clearMarkedAsFoldingStart(); m_flags |= flagFoldingStartAttribute; } /** * Mark as folding start line of an indentation based folding. */ void markAsFoldingStartIndentation() { clearMarkedAsFoldingStart(); m_flags |= flagFoldingStartIndentation; } /** * Returns the line's length. */ int length() const { return m_text.length(); } /** * Returns \e true, if the line's hl-continue flag is set, otherwise returns * \e false. The hl-continue flag is set in the hl-definition files. * @return hl-continue flag is set */ bool hlLineContinue() const { return m_flags & flagHlContinue; } /** * Returns \e true, if the line was automagically wrapped, otherwise returns * \e false. * @return was this line auto-wrapped? */ bool isAutoWrapped() const { return m_flags & flagAutoWrapped; } /** * Returns the complete text line (as a QString reference). * @return text of this line, read-only */ const QString &string() const { return m_text; } /** * Returns the substring with \e length beginning at the given \e column. * @param column start column of text to return * @param length length of text to return * @return wanted part of text */ QString string(int column, int length) const { return m_text.mid(column, length); } /** * Leading whitespace of this line * @return leading whitespace of this line */ QString leadingWhitespace() const; /** * Returns the indentation depth with each tab expanded into \e tabWidth characters. */ int indentDepth(int tabWidth) const; /** * Returns the \e column with each tab expanded into \e tabWidth characters. */ int toVirtualColumn(int column, int tabWidth) const; /** * Returns the "real" column where each tab only counts one character. * The conversion calculates with \e tabWidth characters for each tab. */ int fromVirtualColumn(int column, int tabWidth) const; /** * Returns the text length with each tab expanded into \e tabWidth characters. */ int virtualLength(int tabWidth) const; /** * Returns \e true, if \e match equals to the text at position \e column, * otherwise returns \e false. */ bool matchesAt(int column, const QString &match) const; /** * Returns \e true, if the line starts with \e match, otherwise returns \e false. */ bool startsWith(const QString &match) const { return m_text.startsWith(match); } /** * Returns \e true, if the line ends with \e match, otherwise returns \e false. */ bool endsWith(const QString &match) const { return m_text.endsWith(match); } /** * context stack * @return context stack */ const ContextStack &contextStack() const { return m_contextStack; } /** * Sets the syntax highlight context number * @param val new context array */ void setContextStack(const ContextStack &val) { m_contextStack = val; } /** * Add attribute to this line. * @param attribute new attribute to append */ void addAttribute(const Attribute &attribute); /** * Clear attributes of this line */ void clearAttributes() { m_attributesList.clear(); } /** * Accessor to attributes * @return attributes of this line */ const QVector &attributesList() const { return m_attributesList; } /** * Gets the attribute at the given position * use KRenderer::attributes to get the KTextAttribute for this. * * @param pos position of attribute requested * @return value of attribute */ - short attribute(int pos) const - { - for (int i = 0; i < m_attributesList.size(); ++i) { - if (pos >= m_attributesList[i].offset && pos < (m_attributesList[i].offset + m_attributesList[i].length)) { - return m_attributesList[i].attributeValue; - } - - if (pos < m_attributesList[i].offset) { - break; - } - } - - return 0; - } + short attribute(int pos) const; /** * set hl continue flag * @param cont continue flag? */ void setHlLineContinue(bool cont) { if (cont) { m_flags = m_flags | flagHlContinue; } else { m_flags = m_flags & ~ flagHlContinue; } } /** * set auto-wrapped property * @param wrapped line was wrapped? */ void setAutoWrapped(bool wrapped) { if (wrapped) { m_flags = m_flags | flagAutoWrapped; } else { m_flags = m_flags & ~ flagAutoWrapped; } } private: /** * Accessor to the text contained in this line. * This accessor is private, only the friend class text buffer/block is allowed to access the text read/write. * @return text of this line */ QString &textReadWrite() { return m_text; } private: /** * text of this line */ QString m_text; /** * attributes of this line */ QVector m_attributesList; /** * context stack of this line */ ContextStack m_contextStack; /** * flags of this line */ - unsigned int m_flags; + unsigned int m_flags = 0; }; /** * The normal world only accesses the text lines with shared pointers. */ typedef QSharedPointer TextLine; } #endif diff --git a/src/buffer/katetextloader.h b/src/buffer/katetextloader.h index c0b1b4b3..2122c9d2 100644 --- a/src/buffer/katetextloader.h +++ b/src/buffer/katetextloader.h @@ -1,414 +1,414 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 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_TEXTLOADER_H #define KATE_TEXTLOADER_H #include #include #include #include // on the fly compression #include namespace Kate { /** * loader block size, load 256 kb at once per default * if file size is smaller, fall back to file size * must be a multiple of 2 */ static const qint64 KATE_FILE_LOADER_BS = 256 * 1024; /** * File Loader, will handle reading of files + detecting encoding */ class TextLoader { public: /** * Construct file loader for given file. * @param filename file to open * @param proberType prober type */ TextLoader(const QString &filename, KEncodingProber::ProberType proberType) : m_codec(nullptr) , m_eof(false) // default to not eof , m_lastWasEndOfLine(true) // at start of file, we had a virtual newline , m_lastWasR(false) // we have not found a \r as last char , m_position(0) , m_lastLineStart(0) , m_eol(TextBuffer::eolUnknown) // no eol type detected atm , m_buffer(KATE_FILE_LOADER_BS, 0) , m_digest(QCryptographicHash::Sha1) , m_converterState(nullptr) , m_bomFound(false) , m_firstRead(true) , m_proberType(proberType) , m_fileSize(0) { // try to get mimetype for on the fly decompression, don't rely on filename! QFile testMime(filename); m_mimeType = QMimeDatabase().mimeTypeForFileNameAndData(filename, &testMime).name(); m_fileSize = testMime.size(); // construct filter device KCompressionDevice::CompressionType compressionType = KFilterDev::compressionTypeForMimeType(m_mimeType); m_file = new KCompressionDevice(filename, compressionType); } /** * Destructor */ ~TextLoader() { delete m_file; delete m_converterState; } /** * open file with given codec * @param codec codec to use, if 0, will do some auto-dectect or fallback * @return success */ bool open(QTextCodec *codec) { m_codec = codec; m_eof = false; m_lastWasEndOfLine = true; m_lastWasR = false; m_position = 0; m_lastLineStart = 0; m_eol = TextBuffer::eolUnknown; m_text.clear(); delete m_converterState; m_converterState = new QTextCodec::ConverterState(QTextCodec::ConvertInvalidToNull); m_bomFound = false; m_firstRead = true; - + // init the hash with the git header const QString header = QStringLiteral("blob %1").arg(m_fileSize); m_digest.reset(); m_digest.addData(header.toLatin1() + '\0'); - + // if already opened, close the file... if (m_file->isOpen()) { m_file->close(); } return m_file->open(QIODevice::ReadOnly); } /** * end of file reached? * @return end of file reached */ bool eof() const { return m_eof && !m_lastWasEndOfLine && (m_lastLineStart == m_text.length()); } /** * Detected end of line mode for this file. * Detected during reading, is valid after complete file is read. * @return eol mode of this file */ TextBuffer::EndOfLineMode eol() const { return m_eol; } /** * BOM found? * @return byte order mark found? */ bool byteOrderMarkFound() const { return m_bomFound; } /** * mime type used to create filter dev * @return mime-type of filter device */ const QString &mimeTypeForFilterDev() const { return m_mimeType; } /** * internal Unicode data array * @return internal Unicode data */ const QChar *unicode() const { return m_text.unicode(); } /** * Get codec for this loader * @return currently in use codec of this loader */ QTextCodec *textCodec() const { return m_codec; } /** * read a line, return length + offset in Unicode data * @param offset offset into internal Unicode data for read line * @param length length of read line * @return true if no encoding errors occurred */ bool readLine(int &offset, int &length) { length = 0; offset = 0; bool encodingError = false; static const QLatin1Char cr(QLatin1Char('\r')); static const QLatin1Char lf(QLatin1Char('\n')); /** * did we read two time but got no stuff? encoding error * fixes problem with one character latin-1 files, which lead to crash otherwise! * bug 272579 */ bool failedToConvertOnce = false; /** * reading loop */ while (m_position <= m_text.length()) { if (m_position == m_text.length()) { // try to load more text if something is around if (!m_eof) { // kill the old lines... m_text.remove(0, m_lastLineStart); - + // try to read new data const int c = m_file->read(m_buffer.data(), m_buffer.size()); // if any text is there, append it.... if (c > 0) { // update hash sum m_digest.addData(m_buffer.data(), c); - + // detect byte order marks & codec for byte order marks on first read int bomBytes = 0; if (m_firstRead) { // use first 16 bytes max to allow BOM detection of codec QByteArray bom(m_buffer.data(), qMin(16, c)); QTextCodec *codecForByteOrderMark = QTextCodec::codecForUtfText(bom, nullptr); // if codec != null, we found a BOM! if (codecForByteOrderMark) { m_bomFound = true; // eat away the different boms! int mib = codecForByteOrderMark->mibEnum(); if (mib == 106) { // utf8 bomBytes = 3; } if (mib == 1013 || mib == 1014 || mib == 1015) { // utf16 bomBytes = 2; } if (mib == 1017 || mib == 1018 || mib == 1019) { // utf32 bomBytes = 4; } } /** * if no codec given, do autodetection */ if (!m_codec) { /** * byte order said something about encoding? */ if (codecForByteOrderMark) { m_codec = codecForByteOrderMark; } else { /** * no Unicode BOM found, trigger prober */ - + /** * first: try to get HTML header encoding */ if (QTextCodec *codecForHtml = QTextCodec::codecForHtml (m_buffer, nullptr)) { m_codec = codecForHtml; } - + /** * else: use KEncodingProber */ else { KEncodingProber prober(m_proberType); prober.feed(m_buffer.constData(), c); // we found codec with some confidence? if (prober.confidence() > 0.5) { m_codec = QTextCodec::codecForName(prober.encoding()); } } // no codec, no chance, encoding error if (!m_codec) { return false; } } } m_firstRead = false; } Q_ASSERT(m_codec); QString unicode = m_codec->toUnicode(m_buffer.constData() + bomBytes, c - bomBytes, m_converterState); // detect broken encoding for (int i = 0; i < unicode.size(); ++i) { - if (unicode[i] == 0) { + if (unicode.at(i).isNull()) { encodingError = true; break; } } m_text.append(unicode); } // is file completely read ? m_eof = (c == -1) || (c == 0); // recalc current pos and last pos m_position -= m_lastLineStart; m_lastLineStart = 0; } // oh oh, end of file, escape ! if (m_eof && (m_position == m_text.length())) { m_lastWasEndOfLine = false; // line data offset = m_lastLineStart; length = m_position - m_lastLineStart; m_lastLineStart = m_position; return !encodingError && !failedToConvertOnce; } // empty? try again if (m_position == m_text.length()) { failedToConvertOnce = true; continue; } } if (m_text.at(m_position) == lf) { m_lastWasEndOfLine = true; if (m_lastWasR) { m_lastLineStart++; m_lastWasR = false; m_eol = TextBuffer::eolDos; } else { // line data offset = m_lastLineStart; length = m_position - m_lastLineStart; m_lastLineStart = m_position + 1; m_position++; // only win, if not dos! if (m_eol != TextBuffer::eolDos) { m_eol = TextBuffer::eolUnix; } return !encodingError; } } else if (m_text.at(m_position) == cr) { m_lastWasEndOfLine = true; m_lastWasR = true; // line data offset = m_lastLineStart; length = m_position - m_lastLineStart; m_lastLineStart = m_position + 1; m_position++; // should only win of first time! if (m_eol == TextBuffer::eolUnknown) { m_eol = TextBuffer::eolMac; } return !encodingError; } else if (m_text.at(m_position) == QChar::LineSeparator) { m_lastWasEndOfLine = true; // line data offset = m_lastLineStart; length = m_position - m_lastLineStart; m_lastLineStart = m_position + 1; m_position++; return !encodingError; } else { m_lastWasEndOfLine = false; m_lastWasR = false; } m_position++; } return !encodingError; } QByteArray digest() { return m_digest.result(); } private: QTextCodec *m_codec; bool m_eof; bool m_lastWasEndOfLine; bool m_lastWasR; int m_position; int m_lastLineStart; TextBuffer::EndOfLineMode m_eol; QString m_mimeType; QIODevice *m_file; QByteArray m_buffer; QCryptographicHash m_digest; QString m_text; QTextCodec::ConverterState *m_converterState; bool m_bomFound; bool m_firstRead; KEncodingProber::ProberType m_proberType; quint64 m_fileSize; }; } #endif diff --git a/src/buffer/katetextrange.h b/src/buffer/katetextrange.h index 74383c57..a2694dad 100644 --- a/src/buffer/katetextrange.h +++ b/src/buffer/katetextrange.h @@ -1,363 +1,363 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 Christoph Cullmann * * Based on code of the SmartCursor/Range by: * Copyright (C) 2003-2005 Hamish Rodda * * 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_TEXTRANGE_H #define KATE_TEXTRANGE_H #include #include #include #include #include "katetextcursor.h" namespace Kate { class TextBuffer; /** * Class representing a 'clever' text range. * It will automagically move if the text inside the buffer it belongs to is modified. * By intention no subclass of KTextEditor::Range, must be converted manually. * A TextRange is allowed to be empty. If you call setInvalidateIfEmpty(true), * a TextRange will become automatically invalid as soon as start() == end() * position holds. */ class KTEXTEDITOR_EXPORT TextRange : public KTextEditor::MovingRange { // this is a friend, block changes might invalidate ranges... friend class TextBlock; public: /** * Construct a text range. * A TextRange is not allowed to be empty, as soon as start == end position, it will become * automatically invalid! * @param buffer parent text buffer * @param range The initial text range assumed by the new range. * @param insertBehavior Define whether the range should expand when text is inserted adjacent to the range. * @param emptyBehavior Define whether the range should invalidate itself on becoming empty. */ TextRange(TextBuffer &buffer, const KTextEditor::Range &range, InsertBehaviors insertBehavior, EmptyBehavior emptyBehavior = AllowEmpty); /** * Destruct the text block */ - ~TextRange(); + ~TextRange() override; /** * Set insert behaviors. * @param insertBehaviors new insert behaviors */ - void setInsertBehaviors(InsertBehaviors insertBehaviors) Q_DECL_OVERRIDE; + void setInsertBehaviors(InsertBehaviors insertBehaviors) override; /** * Get current insert behaviors. * @return current insert behaviors */ - InsertBehaviors insertBehaviors() const Q_DECL_OVERRIDE; + InsertBehaviors insertBehaviors() const override; /** * Set if this range will invalidate itself if it becomes empty. * @param emptyBehavior behavior on becoming empty */ - void setEmptyBehavior(EmptyBehavior emptyBehavior) Q_DECL_OVERRIDE; + void setEmptyBehavior(EmptyBehavior emptyBehavior) override; /** * Will this range invalidate itself if it becomes empty? * @return behavior on becoming empty */ - EmptyBehavior emptyBehavior() const Q_DECL_OVERRIDE + EmptyBehavior emptyBehavior() const override { return m_invalidateIfEmpty ? InvalidateIfEmpty : AllowEmpty; } /** * Gets the document to which this range is bound. * \return a pointer to the document */ - KTextEditor::Document *document() const Q_DECL_OVERRIDE; + KTextEditor::Document *document() const override; /** * Set the range of this range. * A TextRange is not allowed to be empty, as soon as start == end position, it will become * automatically invalid! * @param range new range for this clever range */ - void setRange(const KTextEditor::Range &range) Q_DECL_OVERRIDE; + void setRange(const KTextEditor::Range &range) override; /** * \overload * Set the range of this range * A TextRange is not allowed to be empty, as soon as start == end position, it will become * automatically invalid! * @param start new start for this clever range * @param end new end for this clever range */ void setRange(const KTextEditor::Cursor &start, const KTextEditor::Cursor &end) { KTextEditor::MovingRange::setRange(start, end); } /** * Retrieve start cursor of this range, read-only. * @return start cursor */ - const KTextEditor::MovingCursor &start() const Q_DECL_OVERRIDE + const KTextEditor::MovingCursor &start() const override { return m_start; } /** * Non-virtual version of start(), which is faster. * @return start cursor */ const TextCursor &startInternal() const { return m_start; } /** * Retrieve end cursor of this range, read-only. * @return end cursor */ - const KTextEditor::MovingCursor &end() const Q_DECL_OVERRIDE + const KTextEditor::MovingCursor &end() const override { return m_end; } /** * Nonvirtual version of end(), which is faster. * @return end cursor */ const TextCursor &endInternal() const { return m_end; } /** * Convert this clever range into a dumb one. * @return normal range */ const KTextEditor::Range toRange() const { return KTextEditor::Range(start().toCursor(), end().toCursor()); } /** * Convert this clever range into a dumb one. Equal to toRange, allowing to use implicit conversion. * @return normal range */ operator KTextEditor::Range() const { return KTextEditor::Range(start().toCursor(), end().toCursor()); } /** * Gets the active view for this range. Might be already invalid, internally only used for pointer comparisons. * * \return a pointer to the active view */ - KTextEditor::View *view() const Q_DECL_OVERRIDE + KTextEditor::View *view() const override { return m_view; } /** * Sets the currently active view for this range. * This will trigger update of the relevant view parts, if the view changed. * Set view before the attribute, that will avoid not needed redraws. * * \param view View to assign to this range. If null, simply * removes the previous view. */ - void setView(KTextEditor::View *view) Q_DECL_OVERRIDE; + void setView(KTextEditor::View *view) override; /** * Gets the active Attribute for this range. * * \return a pointer to the active attribute */ - KTextEditor::Attribute::Ptr attribute() const Q_DECL_OVERRIDE + KTextEditor::Attribute::Ptr attribute() const override { return m_attribute; } /** * \return whether a nonzero attribute is set. This is faster than checking attribute(), * because the reference-counting is omitted. */ bool hasAttribute() const { return m_attribute.constData(); } /** * Sets the currently active attribute for this range. * This will trigger update of the relevant view parts. * * \param attribute Attribute to assign to this range. If null, simply * removes the previous Attribute. */ - void setAttribute(KTextEditor::Attribute::Ptr attribute) Q_DECL_OVERRIDE; + void setAttribute(KTextEditor::Attribute::Ptr attribute) override; /** * Gets the active MovingRangeFeedback for this range. * * \return a pointer to the active MovingRangeFeedback */ - KTextEditor::MovingRangeFeedback *feedback() const Q_DECL_OVERRIDE + KTextEditor::MovingRangeFeedback *feedback() const override { return m_feedback; } /** * Sets the currently active MovingRangeFeedback for this range. * This will trigger evaluation if feedback must be send again (for example if mouse is already inside range). * * \param feedback MovingRangeFeedback to assign to this range. If null, simply * removes the previous MovingRangeFeedback. */ - void setFeedback(KTextEditor::MovingRangeFeedback *feedback) Q_DECL_OVERRIDE; + void setFeedback(KTextEditor::MovingRangeFeedback *feedback) override; /** * Is this range's attribute only visible in views, not for example prints? * Default is false. * @return range visible only for views */ - bool attributeOnlyForViews() const Q_DECL_OVERRIDE + bool attributeOnlyForViews() const override { return m_attributeOnlyForViews; } /** * Set if this range's attribute is only visible in views, not for example prints. * @param onlyForViews attribute only valid for views */ - void setAttributeOnlyForViews(bool onlyForViews) Q_DECL_OVERRIDE; + void setAttributeOnlyForViews(bool onlyForViews) override; /** * Gets the current Z-depth of this range. * Ranges with smaller Z-depth than others will win during rendering. * Default is 0.0. * * \return current Z-depth of this range */ - qreal zDepth() const Q_DECL_OVERRIDE + qreal zDepth() const override { return m_zDepth; } /** * Set the current Z-depth of this range. * Ranges with smaller Z-depth than others will win during rendering. * This will trigger update of the relevant view parts, if the depth changed. * Set depth before the attribute, that will avoid not needed redraws. * Default is 0.0. * * \param zDepth new Z-depth of this range */ - void setZDepth(qreal zDepth) Q_DECL_OVERRIDE; + void setZDepth(qreal zDepth) override; private: /** * no copy constructor, don't allow this to be copied. */ TextRange(const TextRange &); /** * no assignment operator, no copying around. */ TextRange &operator= (const TextRange &); /** * Check if range is valid, used by constructor and setRange. * If at least one cursor is invalid, both will set to invalid. * Same if range itself is invalid (start >= end). * @param oldStartLine old start line of this range before changing of cursors, needed to add/remove range from m_ranges in blocks * @param oldEndLine old end line of this range * @param notifyAboutChange should feedback be emitted or not? */ void checkValidity(int oldStartLine = -1, int oldEndLine = -1, bool notifyAboutChange = true); /** * Add/Remove range from the lookup m_ranges hash of each block * @param oldStartLine old start line of this range before changing of cursors, needed to add/remove range from m_ranges in blocks * @param oldEndLine old end line of this range * @param startLine start line to start looking for the range to remove * @param endLine end line of this range */ void fixLookup(int oldStartLine, int oldEndLine, int startLine, int endLine); private: /** * parent text buffer * is a reference, and no pointer, as this must always exist and can't change */ TextBuffer &m_buffer; /** * Start cursor for this range, is a clever cursor */ TextCursor m_start; /** * End cursor for this range, is a clever cursor */ TextCursor m_end; /** * The view for which the attribute is valid, 0 means any view */ KTextEditor::View *m_view; /** * This range's current attribute. */ KTextEditor::Attribute::Ptr m_attribute; /** * pointer to the active MovingRangeFeedback */ KTextEditor::MovingRangeFeedback *m_feedback; /** * Z-depth of this range for rendering */ qreal m_zDepth; /** * Is this range's attribute only visible in views, not for example prints? */ bool m_attributeOnlyForViews; /** * Will this range invalidate itself if it becomes empty? */ bool m_invalidateIfEmpty; }; } #endif diff --git a/src/buffer/org.kde.ktexteditor.katetextbuffer.actions b/src/buffer/org.kde.ktexteditor.katetextbuffer.actions index 2cdc63d2..ee833f61 100644 --- a/src/buffer/org.kde.ktexteditor.katetextbuffer.actions +++ b/src/buffer/org.kde.ktexteditor.katetextbuffer.actions @@ -1,105 +1,108 @@ [Domain] Name=Document Actions Name[ca]=Accions de document Name[ca@valencia]=Accions de document Name[cs]=Činnosti dokumentu Name[da]=Dokumenthandlinger Name[de]=Dokument-Aktionen Name[en_GB]=Document Actions Name[es]=Acciones de documento Name[eu]=Dokumentuen ekintzak Name[fi]=Tiedosto-toiminnot Name[fr]=Actions du document Name[gl]=Accións do documento Name[ia]=Actiones de documento +Name[id]=Aksi Dokumen Name[it]=Azioni documenti Name[ko]=문서 동작 Name[nl]=Documentacties Name[nn]=Dokumenthandlingar Name[pl]=Działania na dokumentach Name[pt]=Acções do Documento Name[pt_BR]=Ações do documento Name[ru]=Действия над документом Name[sl]=Dejanja dokumenta Name[sr]=Радње над документом Name[sr@ijekavian]=Радње над документом Name[sr@ijekavianlatin]=Radnje nad dokumentom Name[sr@latin]=Radnje nad dokumentom Name[sv]=Dokumentåtgärder Name[tr]=Belge İşlemleri Name[uk]=Дії над документом Name[x-test]=xxDocument Actionsxx Name[zh_CN]=文档动作 Name[zh_TW]=文件動作 Policy=auth_admin Persistence=session [org.kde.ktexteditor.katetextbuffer.savefile] Name=Save Document Name[ast]=Guardar documentu Name[ca]=Desa el document Name[ca@valencia]=Guarda el document Name[cs]=Uložit dokument Name[da]=Gem dokument Name[de]=Dokument speichern Name[en_GB]=Save Document Name[es]=Guardar documento Name[eu]=Gorde dokumentua Name[fi]=Tallenna tiedosto Name[fr]=Enregistrer le document Name[gl]=Gardar o documento Name[ia]=Salveguarda documento +Name[id]=Simpan Dokumen Name[it]=Salva documento Name[ko]=문서 저장 Name[nl]=Document opslaan Name[nn]=Lagra dokument Name[pl]=Zapisz dokument Name[pt]=Gravar o Documento Name[pt_BR]=Salvar documento Name[ru]=Сохранение документа Name[sl]=Shrani dokument Name[sr]=Сачувај документ Name[sr@ijekavian]=Сачувај документ Name[sr@ijekavianlatin]=Sačuvaj dokument Name[sr@latin]=Sačuvaj dokument Name[sv]=Spara dokument Name[tr]=Belgeyi Kaydet Name[uk]=Зберегти документ Name[x-test]=xxSave Documentxx Name[zh_CN]=保存文档 Name[zh_TW]=儲存文件 Description=Root privileges are needed to save this document Description[ast]=Precísense privilexos root pa guardar esti documentu Description[ca]=Es requereixen privilegis d'administrador per desar aquest document -Description[ca@valencia]=Es requereixen privilegis d'administrador per guardar este document +Description[ca@valencia]=Es requereixen privilegis d'administrador per guardar aquest document Description[cs]=K uložení dokumentu je potřeba práva uživatele root Description[da]=Root-rettigheder kræves for at gemme dette dokument Description[de]=Rechte als Systemverwalter sind für das Speichern diese Dokuments erforderlich Description[en_GB]=Root privileges are needed to save this document Description[es]=Se necesitan permisos de «root» para guardar este documento Description[eu]=Root pribilegioak behar dira dokumentu hau gordetzeko Description[fi]=Tämän tiedoston tallentamiseen tarvitaan pääkäyttäjän oikeudet Description[fr]=Vous devez disposer des privilèges de superutilisateur pour enregistrer ce document Description[gl]=Necesítanse privilexios de administrador para gardar este documento. Description[ia]=Privilegios de radice root es necessari per salveguardar iste documento +Description[id]=Hak istimewa root diperlukan untuk menyimpan dokumen ini Description[it]=Sono necessari i privilegi di root per salvare questo documento Description[ko]=이 문서를 저장하려면 루트 권한이 필요함 Description[nl]=Om dit document op te slaan zijn root-rechten nodig Description[nn]=Du må ha rotløyve for å kunna lagra dokumentet Description[pl]=Wymagane są uprawnienia administratora do zapisania tego dokumentu Description[pt]=São necessários privilégios de 'root' para gravar este documento Description[pt_BR]=São necessários privilégios de root para salvar este documento Description[ru]=Для сохранения этого документа необходимы права администратора Description[sl]=Za shranjevanje tega dokumenta so zahtevana skrbniška dovoljenja Description[sr]=Уписивање овог документа захтева корена овлашћења Description[sr@ijekavian]=Уписивање овог документа захтева корена овлашћења Description[sr@ijekavianlatin]=Upisivanje ovog dokumenta zahteva korena ovlašćenja Description[sr@latin]=Upisivanje ovog dokumenta zahteva korena ovlašćenja Description[sv]=Systemadministratörsprivilegier krävs för att spara dokumentet Description[tr]=Bu belgeyi kaydetmek için yönetici ayrıcalıkları gereklidir Description[uk]=Для збереження документа потрібні права доступу користувача root Description[x-test]=xxRoot privileges are needed to save this documentxx Description[zh_CN]=保存文档需要超级用户权限 Description[zh_TW]=需要 root 權限以儲存此文件 Policy=auth_admin Persistence=session diff --git a/src/completion/expandingtree/expandingdelegate.h b/src/completion/expandingtree/expandingdelegate.h index 3395b380..3a72bf63 100644 --- a/src/completion/expandingtree/expandingdelegate.h +++ b/src/completion/expandingtree/expandingdelegate.h @@ -1,86 +1,86 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2006 Hamish Rodda * * 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 ExpandingDelegate_H #define ExpandingDelegate_H #include #include #include class ExpandingWidgetModel; class QVariant; class QStyleOptionViewItem; /** * This is a delegate that cares, together with ExpandingWidgetModel, about embedded widgets in tree-view. * */ class ExpandingDelegate : public QItemDelegate { Q_OBJECT public: explicit ExpandingDelegate(ExpandingWidgetModel *model, QObject *parent = nullptr); // Overridden to create highlighting for current index - void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE; + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; // Returns the basic size-hint as reported by QItemDelegate QSize basicSizeHint(const QModelIndex &index) const; ExpandingWidgetModel *model() const; protected: //Called right before paint to allow last-minute changes to the style virtual void adjustStyle(const QModelIndex &index, QStyleOptionViewItem &option) const; - void drawDisplay(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect, const QString &text) const Q_DECL_OVERRIDE; - QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE; - bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) Q_DECL_OVERRIDE; + void drawDisplay(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect, const QString &text) const override; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; + bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override; virtual void drawBackground(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; - void drawDecoration(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect, const QPixmap &pixmap) const Q_DECL_OVERRIDE; + void drawDecoration(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect, const QPixmap &pixmap) const override; //option can be changed virtual QList createHighlighting(const QModelIndex &index, QStyleOptionViewItem &option) const; void adjustRect(QRect &rect) const; /** * Creates a list of FormatRanges as should be returned by createHighlighting from a list of QVariants as described in the kde header ktexteditor/codecompletionmodel.h * */ QList highlightingFromVariantList(const QList &customHighlights) const; //Called when an item was expanded/unexpanded and the height changed virtual void heightChanged() const; //Initializes the style options from the index void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const; mutable int m_currentColumnStart; //Text-offset for custom highlighting, will be applied to m_cachedHighlights(Only highlights starting after this will be used). Shoult be zero of the highlighting is not taken from kate. mutable QList m_currentColumnStarts; mutable QList m_cachedHighlights; mutable Qt::Alignment m_cachedAlignment; mutable QColor m_backgroundColor; mutable QModelIndex m_currentIndex; private: ExpandingWidgetModel *m_model; }; #endif diff --git a/src/completion/expandingtree/expandingtree.cpp b/src/completion/expandingtree/expandingtree.cpp index b1ff3712..7e6a6ceb 100644 --- a/src/completion/expandingtree/expandingtree.cpp +++ b/src/completion/expandingtree/expandingtree.cpp @@ -1,65 +1,67 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2007 David Nolden * * 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 "expandingtree.h" #include #include +#include #include #include #include "katepartdebug.h" #include "expandingwidgetmodel.h" ExpandingTree::ExpandingTree(QWidget *parent) : QTreeView(parent) { m_drawText.documentLayout()->setPaintDevice(this); setUniformRowHeights(false); + header()->setMinimumSectionSize(0); } void ExpandingTree::drawRow(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QTreeView::drawRow(painter, option, index); const ExpandingWidgetModel *eModel = qobject_cast(model()); if (eModel && eModel->isPartiallyExpanded(index)) { QRect rect = eModel->partialExpandRect(index); if (rect.isValid()) { painter->fillRect(rect, QBrush(0xffffffff)); QAbstractTextDocumentLayout::PaintContext ctx; // since arbitrary HTML can be shown use a black on white color scheme here ctx.palette = QPalette(Qt::black, Qt::white); ctx.clip = QRectF(0, 0, rect.width(), rect.height());; painter->setViewTransformEnabled(true); painter->translate(rect.left(), rect.top()); m_drawText.setHtml(eModel->partialExpandText(index)); m_drawText.setPageSize(QSizeF(rect.width(), rect.height())); m_drawText.documentLayout()->draw(painter, ctx); painter->translate(-rect.left(), -rect.top()); } } } int ExpandingTree::sizeHintForColumn(int column) const { return columnWidth(column); } diff --git a/src/completion/expandingtree/expandingtree.h b/src/completion/expandingtree/expandingtree.h index 783897f1..426d729b 100644 --- a/src/completion/expandingtree/expandingtree.h +++ b/src/completion/expandingtree/expandingtree.h @@ -1,39 +1,39 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2007 David Nolden * * 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 ExpandingTree_H #define ExpandingTree_H #include #include //A tree that allows drawing additional information class ExpandingTree : public QTreeView { public: explicit ExpandingTree(QWidget *parent); protected: - void drawRow(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE; - int sizeHintForColumn(int column) const Q_DECL_OVERRIDE; + void drawRow(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + int sizeHintForColumn(int column) const override; private: mutable QTextDocument m_drawText; }; #endif diff --git a/src/completion/expandingtree/expandingwidgetmodel.cpp b/src/completion/expandingtree/expandingwidgetmodel.cpp index 3b38d24a..a5307167 100644 --- a/src/completion/expandingtree/expandingwidgetmodel.cpp +++ b/src/completion/expandingtree/expandingwidgetmodel.cpp @@ -1,560 +1,556 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2007 David Nolden * * 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 "expandingwidgetmodel.h" #include #include #include #include #include #include #include #include "kcolorutils.h" #include "expandingdelegate.h" #include "katepartdebug.h" using namespace KTextEditor; inline QModelIndex firstColumn(const QModelIndex &index) { return index.sibling(index.row(), 0); } ExpandingWidgetModel::ExpandingWidgetModel(QWidget *parent) : QAbstractItemModel(parent) { } ExpandingWidgetModel::~ExpandingWidgetModel() { clearExpanding(); } static QColor doAlternate(QColor color) { QColor background = QApplication::palette().background().color(); return KColorUtils::mix(color, background, 0.15); } uint ExpandingWidgetModel::matchColor(const QModelIndex &index) const { int matchQuality = contextMatchQuality(index.sibling(index.row(), 0)); if (matchQuality > 0) { bool alternate = index.row() & 1; QColor badMatchColor(0xff00aa44); //Blue-ish green QColor goodMatchColor(0xff00ff00); //Green QColor background = treeView()->palette().light().color(); QColor totalColor = KColorUtils::mix(badMatchColor, goodMatchColor, ((float)matchQuality) / 10.0); if (alternate) { totalColor = doAlternate(totalColor); } const qreal dynamicTint = 0.2; const qreal minimumTint = 0.2; qreal tintStrength = (dynamicTint * matchQuality) / 10; - if (tintStrength) { + if (tintStrength != 0.0) { tintStrength += minimumTint; //Some minimum tinting strength, else it's not visible any more } return KColorUtils::tint(background, totalColor, tintStrength).rgb(); } else { return 0; } } QVariant ExpandingWidgetModel::data(const QModelIndex &index, int role) const { switch (role) { case Qt::BackgroundRole: { if (index.column() == 0) { //Highlight by match-quality uint color = matchColor(index); if (color) { return QBrush(color); } } //Use a special background-color for expanded items if (isExpanded(index)) { if (index.row() & 1) { return doAlternate(treeView()->palette().toolTipBase().color()); } else { return treeView()->palette().toolTipBase(); } } } } return QVariant(); } void ExpandingWidgetModel::clearMatchQualities() { m_contextMatchQualities.clear(); } QModelIndex ExpandingWidgetModel::partiallyExpandedRow() const { if (m_partiallyExpanded.isEmpty()) { return QModelIndex(); } else { return m_partiallyExpanded.constBegin().key(); } } void ExpandingWidgetModel::clearExpanding() { clearMatchQualities(); QMap oldExpandState = m_expandState; foreach (const QPointer &widget, m_expandingWidgets) if (widget) { widget->deleteLater(); // By using deleteLater, we prevent crashes when an action within a widget makes the completion cancel } m_expandingWidgets.clear(); m_expandState.clear(); m_partiallyExpanded.clear(); for (QMap::const_iterator it = oldExpandState.constBegin(); it != oldExpandState.constEnd(); ++it) if (it.value() == Expanded) { emit dataChanged(it.key(), it.key()); } } ExpandingWidgetModel::ExpansionType ExpandingWidgetModel::isPartiallyExpanded(const QModelIndex &index) const { if (m_partiallyExpanded.contains(firstColumn(index))) { return m_partiallyExpanded[firstColumn(index)]; } else { return NotExpanded; } } void ExpandingWidgetModel::partiallyUnExpand(const QModelIndex &idx_) { QModelIndex index(firstColumn(idx_)); m_partiallyExpanded.remove(index); m_partiallyExpanded.remove(idx_); } int ExpandingWidgetModel::partiallyExpandWidgetHeight() const { return 60; ///@todo use font-metrics text-height*2 for 2 lines } void ExpandingWidgetModel::rowSelected(const QModelIndex &idx_) { QModelIndex idx(firstColumn(idx_)); if (!m_partiallyExpanded.contains(idx)) { QModelIndex oldIndex = partiallyExpandedRow(); //Unexpand the previous partially expanded row if (!m_partiallyExpanded.isEmpty()) { ///@todo allow multiple partially expanded rows while (!m_partiallyExpanded.isEmpty()) { m_partiallyExpanded.erase(m_partiallyExpanded.begin()); } //partiallyUnExpand( m_partiallyExpanded.begin().key() ); } //Notify the underlying models that the item was selected, and eventually get back the text for the expanding widget. if (!idx.isValid()) { //All items have been unselected if (oldIndex.isValid()) { emit dataChanged(oldIndex, oldIndex); } } else { QVariant variant = data(idx, CodeCompletionModel::ItemSelected); if (!isExpanded(idx) && variant.type() == QVariant::String) { //Either expand upwards or downwards, choose in a way that //the visible fields of the new selected entry are not moved. if (oldIndex.isValid() && (oldIndex < idx || (!(oldIndex < idx) && oldIndex.parent() < idx.parent()))) { m_partiallyExpanded.insert(idx, ExpandUpwards); } else { m_partiallyExpanded.insert(idx, ExpandDownwards); } //Say that one row above until one row below has changed, so no items will need to be moved(the space that is taken from one item is given to the other) if (oldIndex.isValid() && oldIndex < idx) { emit dataChanged(oldIndex, idx); if (treeView()->verticalScrollMode() == QAbstractItemView::ScrollPerItem) { //Qt fails to correctly scroll in ScrollPerItem mode, so the selected index is completely visible, //so we do the scrolling by hand. QRect selectedRect = treeView()->visualRect(idx); QRect frameRect = treeView()->frameRect(); if (selectedRect.bottom() > frameRect.bottom()) { int diff = selectedRect.bottom() - frameRect.bottom(); //We need to scroll down QModelIndex newTopIndex = idx; QModelIndex nextTopIndex = idx; QRect nextRect = treeView()->visualRect(nextTopIndex); while (nextTopIndex.isValid() && nextRect.isValid() && nextRect.top() >= diff) { newTopIndex = nextTopIndex; nextTopIndex = treeView()->indexAbove(nextTopIndex); if (nextTopIndex.isValid()) { nextRect = treeView()->visualRect(nextTopIndex); } } treeView()->scrollTo(newTopIndex, QAbstractItemView::PositionAtTop); } } //This is needed to keep the item we are expanding completely visible. Qt does not scroll the view to keep the item visible. //But we must make sure that it isn't too expensive. //We need to make sure that scrolling is efficient, and the whole content is not repainted. //Since we are scrolling anyway, we can keep the next line visible, which might be a cool feature. //Since this also doesn't work smoothly, leave it for now //treeView()->scrollTo( nextLine, QAbstractItemView::EnsureVisible ); } else if (oldIndex.isValid() && idx < oldIndex) { emit dataChanged(idx, oldIndex); //For consistency with the down-scrolling, we keep one additional line visible above the current visible. //Since this also doesn't work smoothly, leave it for now /* QModelIndex prevLine = idx.sibling(idx.row()-1, idx.column()); if( prevLine.isValid() ) treeView()->scrollTo( prevLine );*/ } else { emit dataChanged(idx, idx); } } else if (oldIndex.isValid()) { //We are not partially expanding a new row, but we previously had a partially expanded row. So signalize that it has been unexpanded. emit dataChanged(oldIndex, oldIndex); } } } else { qCDebug(LOG_KTE) << "ExpandingWidgetModel::rowSelected: Row is already partially expanded"; } } QString ExpandingWidgetModel::partialExpandText(const QModelIndex &idx) const { if (!idx.isValid()) { return QString(); } return data(firstColumn(idx), CodeCompletionModel::ItemSelected).toString(); } QRect ExpandingWidgetModel::partialExpandRect(const QModelIndex &idx_) const { QModelIndex idx(firstColumn(idx_)); if (!idx.isValid()) { return QRect(); } ExpansionType expansion = ExpandDownwards; if (m_partiallyExpanded.find(idx) != m_partiallyExpanded.constEnd()) { expansion = m_partiallyExpanded[idx]; } //Get the whole rectangle of the row: QModelIndex rightMostIndex = idx; QModelIndex tempIndex = idx; while ((tempIndex = rightMostIndex.sibling(rightMostIndex.row(), rightMostIndex.column() + 1)).isValid()) { rightMostIndex = tempIndex; } QRect rect = treeView()->visualRect(idx); QRect rightMostRect = treeView()->visualRect(rightMostIndex); rect.setLeft(rect.left() + 20); rect.setRight(rightMostRect.right() - 5); //These offsets must match exactly those used in ExpandingDelegate::sizeHint() int top = rect.top() + 5; int bottom = rightMostRect.bottom() - 5; if (expansion == ExpandDownwards) { top += basicRowHeight(idx); } else { bottom -= basicRowHeight(idx); } rect.setTop(top); rect.setBottom(bottom); return rect; } bool ExpandingWidgetModel::isExpandable(const QModelIndex &idx_) const { QModelIndex idx(firstColumn(idx_)); if (!m_expandState.contains(idx)) { m_expandState.insert(idx, NotExpandable); QVariant v = data(idx, CodeCompletionModel::IsExpandable); if (v.canConvert() && v.toBool()) { m_expandState[idx] = Expandable; } } return m_expandState[idx] != NotExpandable; } bool ExpandingWidgetModel::isExpanded(const QModelIndex &idx_) const { QModelIndex idx(firstColumn(idx_)); return m_expandState.contains(idx) && m_expandState[idx] == Expanded; } void ExpandingWidgetModel::setExpanded(QModelIndex idx_, bool expanded) { QModelIndex idx(firstColumn(idx_)); //qCDebug(LOG_KTE) << "Setting expand-state of row " << idx.row() << " to " << expanded; if (!idx.isValid()) { return; } if (isExpandable(idx)) { if (!expanded && m_expandingWidgets.contains(idx) && m_expandingWidgets[idx]) { m_expandingWidgets[idx]->hide(); } m_expandState[idx] = expanded ? Expanded : Expandable; if (expanded) { partiallyUnExpand(idx); } if (expanded && !m_expandingWidgets.contains(idx)) { QVariant v = data(idx, CodeCompletionModel::ExpandingWidget); if (v.canConvert()) { m_expandingWidgets[idx] = v.value(); } else if (v.canConvert()) { //Create a html widget that shows the given string KTextEdit *edit = new KTextEdit(v.toString()); edit->setReadOnly(true); edit->resize(200, 50); //Make the widget small so it embeds nicely. m_expandingWidgets[idx] = edit; } else { m_expandingWidgets[idx] = nullptr; } } //Eventually partially expand the row if (!expanded && firstColumn(treeView()->currentIndex()) == idx && !isPartiallyExpanded(idx)) { rowSelected(idx); //Partially expand the row. } emit dataChanged(idx, idx); if (treeView()) { treeView()->scrollTo(idx); } } } int ExpandingWidgetModel::basicRowHeight(const QModelIndex &idx_) const { QModelIndex idx(firstColumn(idx_)); ExpandingDelegate *delegate = dynamic_cast(treeView()->itemDelegate(idx)); if (!delegate || !idx.isValid()) { qCDebug(LOG_KTE) << "ExpandingWidgetModel::basicRowHeight: Could not get delegate"; return 15; } return delegate->basicSizeHint(idx).height(); } void ExpandingWidgetModel::placeExpandingWidget(const QModelIndex &idx_) { QModelIndex idx(firstColumn(idx_)); - - QWidget *w = nullptr; - if (m_expandingWidgets.contains(idx)) { - w = m_expandingWidgets[idx]; + if (!idx.isValid() || !isExpanded(idx)) { + return; } - if (w && isExpanded(idx)) { - if (!idx.isValid()) { - return; - } + QWidget *w = m_expandingWidgets.value(idx); + if (!w) { + return; + } - QRect rect = treeView()->visualRect(idx); + QRect rect = treeView()->visualRect(idx); - if (!rect.isValid() || rect.bottom() < 0 || rect.top() >= treeView()->height()) { - //The item is currently not visible - w->hide(); - return; - } + if (!rect.isValid() || rect.bottom() < 0 || rect.top() >= treeView()->height()) { + //The item is currently not visible + w->hide(); + return; + } - QModelIndex rightMostIndex = idx; - QModelIndex tempIndex = idx; - while ((tempIndex = rightMostIndex.sibling(rightMostIndex.row(), rightMostIndex.column() + 1)).isValid()) { - rightMostIndex = tempIndex; + //Find out the basic width of the row + rect.setLeft(rect.left() + 20); + for (int i = 0, numColumns = idx.model()->columnCount(idx.parent()); i < numColumns; ++i) { + QModelIndex rightMostIndex = idx.sibling(idx.row(), i); + int right = treeView()->visualRect(rightMostIndex).right(); + if (right > rect.right()) { + rect.setRight(right); } + } + rect.setRight(rect.right() - 5); - QRect rightMostRect = treeView()->visualRect(rightMostIndex); - - //Find out the basic height of the row - rect.setLeft(rect.left() + 20); - rect.setRight(rightMostRect.right() - 5); - - //These offsets must match exactly those used in KateCompletionDeleage::sizeHint() - rect.setTop(rect.top() + basicRowHeight(idx) + 5); - rect.setHeight(w->height()); + //These offsets must match exactly those used in KateCompletionDeleage::sizeHint() + rect.setTop(rect.top() + basicRowHeight(idx) + 5); + rect.setHeight(w->height()); - if (w->parent() != treeView()->viewport() || w->geometry() != rect || !w->isVisible()) { - w->setParent(treeView()->viewport()); + if (w->parent() != treeView()->viewport() || w->geometry() != rect || !w->isVisible()) { + w->setParent(treeView()->viewport()); - w->setGeometry(rect); - w->show(); - } + w->setGeometry(rect); + w->show(); } } void ExpandingWidgetModel::placeExpandingWidgets() { for (QMap >::const_iterator it = m_expandingWidgets.constBegin(); it != m_expandingWidgets.constEnd(); ++it) { placeExpandingWidget(it.key()); } } int ExpandingWidgetModel::expandingWidgetsHeight() const { int sum = 0; for (QMap >::const_iterator it = m_expandingWidgets.constBegin(); it != m_expandingWidgets.constEnd(); ++it) { if (isExpanded(it.key()) && (*it)) { sum += (*it)->height(); } } return sum; } QWidget *ExpandingWidgetModel::expandingWidget(const QModelIndex &idx_) const { QModelIndex idx(firstColumn(idx_)); if (m_expandingWidgets.contains(idx)) { return m_expandingWidgets[idx]; } else { return nullptr; } } void ExpandingWidgetModel::cacheIcons() const { if (m_expandedIcon.isNull()) { m_expandedIcon = QIcon::fromTheme(QStringLiteral("arrow-down")); } if (m_collapsedIcon.isNull()) { m_collapsedIcon = QIcon::fromTheme(QStringLiteral("arrow-right")); } } QList mergeCustomHighlighting(int leftSize, const QList &left, int rightSize, const QList &right) { QList ret = left; if (left.isEmpty()) { ret << QVariant(0); ret << QVariant(leftSize); ret << QTextFormat(QTextFormat::CharFormat); } if (right.isEmpty()) { ret << QVariant(leftSize); ret << QVariant(rightSize); ret << QTextFormat(QTextFormat::CharFormat); } else { QList::const_iterator it = right.constBegin(); while (it != right.constEnd()) { { QList::const_iterator testIt = it; for (int a = 0; a < 2; a++) { ++testIt; if (testIt == right.constEnd()) { qCWarning(LOG_KTE) << "Length of input is not multiple of 3"; break; } } } ret << QVariant((*it).toInt() + leftSize); ++it; ret << QVariant((*it).toInt()); ++it; ret << *it; if (!(*it).value().isValid()) { qCDebug(LOG_KTE) << "Text-format is invalid"; } ++it; } } return ret; } //It is assumed that between each two strings, one space is inserted QList mergeCustomHighlighting(QStringList strings, QList highlights, int grapBetweenStrings) { if (strings.isEmpty()) { qCWarning(LOG_KTE) << "List of strings is empty"; return QList(); } if (highlights.isEmpty()) { qCWarning(LOG_KTE) << "List of highlightings is empty"; return QList(); } if (strings.count() != highlights.count()) { qCWarning(LOG_KTE) << "Length of string-list is " << strings.count() << " while count of highlightings is " << highlights.count() << ", should be same"; return QList(); } //Merge them together QString totalString = strings[0]; QVariantList totalHighlighting = highlights[0]; strings.pop_front(); highlights.pop_front(); while (!strings.isEmpty()) { totalHighlighting = mergeCustomHighlighting(totalString.length(), totalHighlighting, strings[0].length(), highlights[0]); totalString += strings[0]; for (int a = 0; a < grapBetweenStrings; a++) { totalString += QLatin1Char(' '); } strings.pop_front(); highlights.pop_front(); } //Combine the custom-highlightings return totalHighlighting; } diff --git a/src/completion/expandingtree/expandingwidgetmodel.h b/src/completion/expandingtree/expandingwidgetmodel.h index d7119e1f..e6881fcc 100644 --- a/src/completion/expandingtree/expandingwidgetmodel.h +++ b/src/completion/expandingtree/expandingwidgetmodel.h @@ -1,157 +1,157 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2007 David Nolden * * 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 EXPANDING_WIDGET_MODEL_H #define EXPANDING_WIDGET_MODEL_H #include #include #include class QTreeView; /** * Cares about expanding/un-expanding items in a tree-view together with ExpandingDelegate */ class ExpandingWidgetModel : public QAbstractItemModel { Q_OBJECT public: explicit ExpandingWidgetModel(QWidget *parent); - virtual ~ExpandingWidgetModel(); + ~ExpandingWidgetModel() override; enum ExpandingType { NotExpandable = 0, Expandable, Expanded }; ///The following three are convenience-functions for the current item that could be replaced by the later ones ///@return whether the current item can be expanded bool canExpandCurrentItem() const; ///@return whether the current item can be collapsed bool canCollapseCurrentItem() const; ///Expand/collapse the current item void setCurrentItemExpanded(bool); void clearMatchQualities(); ///Unexpand all rows and clear all cached information about them(this includes deleting the expanding-widgets) void clearExpanding(); ///@return whether the row given through index is expandable bool isExpandable(const QModelIndex &index) const; enum ExpansionType { NotExpanded = 0, ExpandDownwards, //The additional(expanded) information is shown UNDER the original information ExpandUpwards //The additional(expanded) information is shown ABOVE the original information }; ///Returns whether the given index is currently partially expanded. Does not do any other checks like calling models for data. ExpansionType isPartiallyExpanded(const QModelIndex &index) const; ///@return whether row is currently expanded bool isExpanded(const QModelIndex &row) const; ///Change the expand-state of the row given through index. The display will be updated. void setExpanded(QModelIndex index, bool expanded); ///Returns the total height added through all open expanding-widgets int expandingWidgetsHeight() const; ///@return the expanding-widget for the given row, if available. Expanding-widgets are in best case available for all expanded rows. ///This does not return the partially-expand widget. QWidget *expandingWidget(const QModelIndex &row) const; ///Amount by which the height of a row increases when it is partially expanded int partiallyExpandWidgetHeight() const; /** * Notifies underlying models that the item was selected, collapses any previous partially expanded line, * checks whether this line should be partially expanded, and eventually does it. * Does nothing when nothing needs to be done. * Does NOT show the expanding-widget. That is done immediately when painting by ExpandingDelegate, * to reduce flickering. @see showPartialExpandWidget() * @param row The row * */ /// virtual void rowSelected(const QModelIndex &row); ///Returns the rectangle for the partially expanded part of the given row QRect partialExpandRect(const QModelIndex &row) const; QString partialExpandText(const QModelIndex &row) const; ///Places and shows the expanding-widget for the given row, if it should be visible and is valid. ///Also shows the partial-expanding-widget when it should be visible. void placeExpandingWidget(const QModelIndex &row); virtual QTreeView *treeView() const = 0; ///Should return true if the given row should be painted like a contained item(as opposed to label-rows etc.) virtual bool indexIsItem(const QModelIndex &index) const = 0; ///Does not request data from index, this only returns local data like highlighting for expanded rows and similar - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; ///Returns the first row that is currently partially expanded. QModelIndex partiallyExpandedRow() const; ///Returns the match-color for the given index, or zero if match-quality could not be computed. uint matchColor(const QModelIndex &index) const; public Q_SLOTS: ///Place or hides all expanding-widgets to the correct positions. Should be called after the view was scrolled. void placeExpandingWidgets(); protected: /** * @return the context-match quality from 0 to 10 if it could be determined, else -1 * */ virtual int contextMatchQuality(const QModelIndex &index) const = 0; //Makes sure m_expandedIcon and m_collapsedIcon are loaded void cacheIcons() const; mutable QIcon m_expandedIcon; mutable QIcon m_collapsedIcon; //Does not update the view void partiallyUnExpand(const QModelIndex &index); //Finds out the basic height of the row represented by the given index. Basic means without respecting any expansion. int basicRowHeight(const QModelIndex &index) const; private: QMap m_partiallyExpanded; // Store expanding-widgets and cache whether items can be expanded mutable QMap m_expandState; QMap< QModelIndex, QPointer > m_expandingWidgets; //Map rows to their expanding-widgets QMap< QModelIndex, int > m_contextMatchQualities; //Map rows to their context-match qualities(undefined if unknown, else 0 to 10). Not used yet, eventually remove. }; /** * Helper-function to merge custom-highlighting variant-lists. * * @param strings A list of strings that should be merged * @param highlights One variant-list for highlighting, as described in the kde header ktextedtor/codecompletionmodel.h * @param gapBetweenStrings How many signs are inserted between 2 strings? * */ QList mergeCustomHighlighting(QStringList strings, QList highlights, int gapBetweenStrings = 0); #endif diff --git a/src/completion/kateargumenthintmodel.h b/src/completion/kateargumenthintmodel.h index d47bfd2c..82d06956 100644 --- a/src/completion/kateargumenthintmodel.h +++ b/src/completion/kateargumenthintmodel.h @@ -1,72 +1,72 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2007 David Nolden * * 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 KATEARGUMENTHINTMODEL_H #define KATEARGUMENTHINTMODEL_H #include #include "katecompletionmodel.h" #include "expandingtree/expandingwidgetmodel.h" class KateCompletionWidget; class KateArgumentHintModel : public ExpandingWidgetModel { Q_OBJECT public: explicit KateArgumentHintModel(KateCompletionWidget *parent); - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - int rowCount(const QModelIndex &parent = {}) const Q_DECL_OVERRIDE; + int rowCount(const QModelIndex &parent = {}) const override; - int columnCount(const QModelIndex & parent = {}) const Q_DECL_OVERRIDE; + int columnCount(const QModelIndex & parent = {}) const override; - QModelIndex index(int row, int column, const QModelIndex& parent = {}) const Q_DECL_OVERRIDE; + QModelIndex index(int row, int column, const QModelIndex& parent = {}) const override; - QModelIndex parent(const QModelIndex& parent) const Q_DECL_OVERRIDE; + QModelIndex parent(const QModelIndex& parent) const override; - QTreeView *treeView() const Q_DECL_OVERRIDE; + QTreeView *treeView() const override; - bool indexIsItem(const QModelIndex &index) const Q_DECL_OVERRIDE; + bool indexIsItem(const QModelIndex &index) const override; void emitDataChanged(const QModelIndex &start, const QModelIndex &end); //Returns the index in the source-model for an index within this model QModelIndex mapToSource(const QModelIndex &proxyIndex) const; void buildRows(); void clear(); protected: - int contextMatchQuality(const QModelIndex &row) const Q_DECL_OVERRIDE; + int contextMatchQuality(const QModelIndex &row) const override; public Q_SLOTS: void parentModelReset(); Q_SIGNALS: void contentStateChanged(bool hasContent); private: KateCompletionModel::Group *group() const; KateCompletionModel *model() const; QList m_rows; //Maps rows to either a positive row-number in the source group, or to a negative number which indicates a label KateCompletionWidget *m_parent; }; #endif diff --git a/src/completion/kateargumenthinttree.cpp b/src/completion/kateargumenthinttree.cpp index b7f650d9..af8f67e2 100644 --- a/src/completion/kateargumenthinttree.cpp +++ b/src/completion/kateargumenthinttree.cpp @@ -1,316 +1,316 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2007 David Nolden * * 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 "kateargumenthinttree.h" #include #include #include #include #include "kateargumenthintmodel.h" #include "katecompletionwidget.h" #include "expandingtree/expandingwidgetmodel.h" #include "katecompletiondelegate.h" #include "kateview.h" #include KateArgumentHintTree::KateArgumentHintTree(KateCompletionWidget *parent) : ExpandingTree(nullptr), m_parent(parent) //Do not use the completion-widget as widget-parent, because the argument-hint-tree will be rendered separately { setFrameStyle(QFrame::Box | QFrame::Plain); setLineWidth(1); connect(parent, SIGNAL(destroyed(QObject*)), this, SLOT(deleteLater())); setFrameStyle(QFrame::NoFrame); setFrameStyle(QFrame::Box | QFrame::Plain); setFocusPolicy(Qt::NoFocus); setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); setUniformRowHeights(false); setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); header()->hide(); setRootIsDecorated(false); setIndentation(0); setAllColumnsShowFocus(true); setAlternatingRowColors(true); setItemDelegate(new KateCompletionDelegate(parent->argumentHintModel(), parent)); } void KateArgumentHintTree::clearCompletion() { setCurrentIndex(QModelIndex()); } KateArgumentHintModel *KateArgumentHintTree::model() const { return m_parent->argumentHintModel(); } void KateArgumentHintTree::paintEvent(QPaintEvent *event) { QTreeView::paintEvent(event); updateGeometry(); ///@todo delay this. It is needed here, because visualRect(...) returns an invalid rect in updateGeometry before the content is painted } void KateArgumentHintTree::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector< int > &roles) { Q_UNUSED(roles) QTreeView::dataChanged(topLeft, bottomRight); //updateGeometry(); } void KateArgumentHintTree::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) { /* qCDebug(LOG_KTE) << "currentChanged()";*/ static_cast(model())->rowSelected(current); QTreeView::currentChanged(current, previous); } void KateArgumentHintTree::rowsInserted(const QModelIndex &parent, int start, int end) { QTreeView::rowsInserted(parent, start, end); updateGeometry(); } int KateArgumentHintTree::sizeHintForColumn(int column) const { return QTreeView::sizeHintForColumn(column); } unsigned int KateArgumentHintTree::rowHeight(const QModelIndex &index) const { uint max = sizeHintForIndex(index).height(); for (int a = 0; a < index.model()->columnCount(index.parent()); ++a) { QModelIndex i = index.sibling(index.row(), a); uint cSize = sizeHintForIndex(i).height(); if (cSize > max) { max = cSize; } } return max; } void KateArgumentHintTree::updateGeometry(QRect geom) { //Avoid recursive calls of updateGeometry static bool updatingGeometry = false; if (updatingGeometry) { return; } updatingGeometry = true; if (model()->rowCount(QModelIndex()) == 0) { /* qCDebug(LOG_KTE) << "KateArgumentHintTree:: empty model";*/ hide(); setGeometry(geom); updatingGeometry = false; return; } int bottom = geom.bottom(); - int totalWidth = resizeColumns(); + int totalWidth = std::max(geom.width(), resizeColumns()); int totalHeight = 0; for (int a = 0; a < model()->rowCount(QModelIndex()); ++a) { QModelIndex index(model()->index(a, 0)); totalHeight += rowHeight(index); for (int b = 0; b < model()->rowCount(index); ++b) { QModelIndex childIndex = index.child(b, 0); totalHeight += rowHeight(childIndex); } } totalHeight += frameWidth() * 2; geom.setHeight(totalHeight); geom.moveBottom(bottom); // if( totalWidth > geom.width() ) geom.setWidth(totalWidth); bool enableScrollBars = false; //Resize and move so it fits the screen horizontally int maxWidth = (QApplication::desktop()->screenGeometry(m_parent->view()).width() * 3) / 4; if (geom.width() > maxWidth) { geom.setWidth(maxWidth); geom.setHeight(geom.height() + horizontalScrollBar()->height() + 2); geom.moveBottom(bottom); enableScrollBars = true; } if (geom.right() > QApplication::desktop()->screenGeometry(m_parent->view()).right()) { geom.moveRight(QApplication::desktop()->screenGeometry(m_parent->view()).right()); } if (geom.left() < QApplication::desktop()->screenGeometry(m_parent->view()).left()) { geom.moveLeft(QApplication::desktop()->screenGeometry(m_parent->view()).left()); } //Resize and move so it fits the screen vertically bool resized = false; if (geom.top() < QApplication::desktop()->screenGeometry(this).top()) { int offset = QApplication::desktop()->screenGeometry(this).top() - geom.top(); geom.setBottom(geom.bottom() - offset); geom.moveTo(geom.left(), QApplication::desktop()->screenGeometry(this).top()); resized = true; } if (geom != geometry()) { setUpdatesEnabled(false); setAnimated(false); setHorizontalScrollBarPolicy(enableScrollBars ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff); /* qCDebug(LOG_KTE) << "KateArgumentHintTree::updateGeometry: updating geometry to " << geom;*/ setGeometry(geom); if (resized && currentIndex().isValid()) { scrollTo(currentIndex()); } setUpdatesEnabled(true); } updatingGeometry = false; } int KateArgumentHintTree::resizeColumns() { int totalSize = 0; for (int a = 0; a < header()->count(); a++) { int columnSize = sizeHintForColumn(a); setColumnWidth(a, columnSize); totalSize += columnSize; } return totalSize; } void KateArgumentHintTree::updateGeometry() { updateGeometry(geometry()); } bool KateArgumentHintTree::nextCompletion() { QModelIndex current; QModelIndex firstCurrent = currentIndex(); do { QModelIndex oldCurrent = currentIndex(); current = moveCursor(MoveDown, Qt::NoModifier); if (current != oldCurrent && current.isValid()) { setCurrentIndex(current); } else { if (firstCurrent.isValid()) { setCurrentIndex(firstCurrent); } return false; } } while (!model()->indexIsItem(current)); return true; } bool KateArgumentHintTree::previousCompletion() { QModelIndex current; QModelIndex firstCurrent = currentIndex(); do { QModelIndex oldCurrent = currentIndex(); current = moveCursor(MoveUp, Qt::NoModifier); if (current != oldCurrent && current.isValid()) { setCurrentIndex(current); } else { if (firstCurrent.isValid()) { setCurrentIndex(firstCurrent); } return false; } } while (!model()->indexIsItem(current)); return true; } bool KateArgumentHintTree::pageDown() { QModelIndex old = currentIndex(); QModelIndex current = moveCursor(MovePageDown, Qt::NoModifier); if (current.isValid()) { setCurrentIndex(current); if (!model()->indexIsItem(current)) if (!nextCompletion()) { previousCompletion(); } } return current != old; } bool KateArgumentHintTree::pageUp() { QModelIndex old = currentIndex(); QModelIndex current = moveCursor(MovePageUp, Qt::NoModifier); if (current.isValid()) { setCurrentIndex(current); if (!model()->indexIsItem(current)) if (!previousCompletion()) { nextCompletion(); } } return current != old; } void KateArgumentHintTree::top() { QModelIndex current = moveCursor(MoveHome, Qt::NoModifier); setCurrentIndex(current); if (current.isValid()) { setCurrentIndex(current); if (!model()->indexIsItem(current)) { nextCompletion(); } } } void KateArgumentHintTree::bottom() { QModelIndex current = moveCursor(MoveEnd, Qt::NoModifier); setCurrentIndex(current); if (current.isValid()) { setCurrentIndex(current); if (!model()->indexIsItem(current)) { previousCompletion(); } } } diff --git a/src/completion/kateargumenthinttree.h b/src/completion/kateargumenthinttree.h index 41b18d78..176992a1 100644 --- a/src/completion/kateargumenthinttree.h +++ b/src/completion/kateargumenthinttree.h @@ -1,64 +1,64 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2007 David Nolden * * 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 KATEARGUMENTHINTTREE_H #define KATEARGUMENTHINTTREE_H #include "expandingtree/expandingtree.h" class KateCompletionWidget; class KateArgumentHintModel; class QRect; class KateArgumentHintTree : public ExpandingTree { Q_OBJECT public: explicit KateArgumentHintTree(KateCompletionWidget *parent); // Navigation bool nextCompletion(); bool previousCompletion(); bool pageDown(); bool pageUp(); void top(); void bottom(); //Returns the total size of all columns int resizeColumns(); void clearCompletion(); public Q_SLOTS: void updateGeometry(); void updateGeometry(QRect rect); protected: - void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE; - void rowsInserted(const QModelIndex &parent, int start, int end) Q_DECL_OVERRIDE; - void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles = QVector()) Q_DECL_OVERRIDE; - void currentChanged(const QModelIndex ¤t, const QModelIndex &previous) Q_DECL_OVERRIDE; + void paintEvent(QPaintEvent *event) override; + void rowsInserted(const QModelIndex &parent, int start, int end) override; + void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles = QVector()) override; + void currentChanged(const QModelIndex ¤t, const QModelIndex &previous) override; private: uint rowHeight(const QModelIndex &index) const; KateArgumentHintModel *model() const; - int sizeHintForColumn(int column) const Q_DECL_OVERRIDE; + int sizeHintForColumn(int column) const override; KateCompletionWidget *m_parent; }; #endif diff --git a/src/completion/katecompletionconfig.cpp b/src/completion/katecompletionconfig.cpp index 4ebd13f5..7e73f8e8 100644 --- a/src/completion/katecompletionconfig.cpp +++ b/src/completion/katecompletionconfig.cpp @@ -1,436 +1,448 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2006 Hamish Rodda * * 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 "katecompletionconfig.h" #include "katecompletionmodel.h" #include "kateglobal.h" #include "ui_completionconfigwidget.h" #include #include #include #include #include #include #include using namespace KTextEditor; KateCompletionConfig::KateCompletionConfig(KateCompletionModel *model, QWidget *parent) : QDialog(parent) , ui(new Ui::CompletionConfigWidget()) , m_model(model) { setWindowTitle(i18n("Code Completion Configuration")); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); QWidget *mw = new QWidget(this); mainLayout->addWidget(mw); ui->setupUi(mw); // Sorting ui->sorting->setChecked(m_model->isSortingEnabled()); ui->sortingAlphabetical->setChecked(m_model->isSortingAlphabetical()); ui->sortingCaseSensitive->setChecked(m_model->sortingCaseSensitivity() == Qt::CaseSensitive); ui->groupingOrderUp->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); ui->groupingOrderDown->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); connect(ui->groupingOrderUp, SIGNAL(pressed()), SLOT(moveGroupingOrderUp())); connect(ui->groupingOrderDown, SIGNAL(pressed()), SLOT(moveGroupingOrderDown())); // Filtering ui->filtering->setChecked(m_model->isFilteringEnabled()); ui->filteringContextMatchOnly->setChecked(m_model->filterContextMatchesOnly()); ui->filteringHideAttributes->setChecked(m_model->filterByAttribute()); for (CodeCompletionModel::CompletionProperty i = CodeCompletionModel::FirstProperty; i <= CodeCompletionModel::LastProperty; i = static_cast(i << 1)) { QListWidgetItem *item = new QListWidgetItem(m_model->propertyName(i), ui->filteringAttributesList, i); item->setCheckState((m_model->filterAttributes() & i) ? Qt::Checked : Qt::Unchecked); } ui->filteringMaximumInheritanceDepth->setValue(m_model->maximumInheritanceDepth()); // Grouping ui->grouping->setChecked(m_model->isGroupingEnabled()); ui->groupingUp->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); ui->groupingDown->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); m_groupingScopeType = ui->groupingMethods->topLevelItem(0); m_groupingScopeType->setCheckState(0, (m_model->groupingMethod() & KateCompletionModel::ScopeType) ? Qt::Checked : Qt::Unchecked); m_groupingScope = ui->groupingMethods->topLevelItem(1); m_groupingScope->setCheckState(0, (m_model->groupingMethod() & KateCompletionModel::Scope) ? Qt::Checked : Qt::Unchecked); m_groupingAccessType = ui->groupingMethods->topLevelItem(2); m_groupingAccessType->setCheckState(0, (m_model->groupingMethod() & KateCompletionModel::AccessType) ? Qt::Checked : Qt::Unchecked); m_groupingItemType = ui->groupingMethods->topLevelItem(3); m_groupingItemType->setCheckState(0, (m_model->groupingMethod() & KateCompletionModel::ItemType) ? Qt::Checked : Qt::Unchecked); ui->accessConst->setChecked(m_model->accessIncludeConst()); ui->accessStatic->setChecked(m_model->accessIncludeStatic()); ui->accessSignalSlot->setChecked(m_model->accessIncludeSignalSlot()); for (int i = 0; i < 4; ++i) { ui->groupingMethods->topLevelItem(i)->setCheckState(0, Qt::Unchecked); } connect(ui->groupingUp, SIGNAL(pressed()), SLOT(moveGroupingUp())); connect(ui->groupingDown, SIGNAL(pressed()), SLOT(moveGroupingDown())); // Column merging ui->columnMerging->setChecked(m_model->isColumnMergingEnabled()); ui->columnUp->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); ui->columnDown->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); connect(ui->columnUp, SIGNAL(pressed()), SLOT(moveColumnUp())); connect(ui->columnDown, SIGNAL(pressed()), SLOT(moveColumnDown())); QList mergedColumns; if (!m_model->columnMerges().isEmpty()) { foreach (const QList &list, m_model->columnMerges()) { bool first = true; foreach (int column, list) { QTreeWidgetItem *item = new QTreeWidgetItem(ui->columnMergeTree, column); item->setText(0, KateCompletionModel::columnName(column) + QString::fromLatin1(" %1").arg(column)); item->setCheckState(1, first ? Qt::Unchecked : Qt::Checked); if (column == KTextEditor::CodeCompletionModel::Name) { item->setText(2, i18n("Always")); } else { item->setCheckState(2, Qt::Checked); } first = false; mergedColumns << column; } } for (int column = 0; column < KTextEditor::CodeCompletionModel::ColumnCount; ++column) { if (!mergedColumns.contains(column)) { QTreeWidgetItem *item = new QTreeWidgetItem(ui->columnMergeTree, column); item->setText(0, KateCompletionModel::columnName(column) + QString::fromLatin1(" %1").arg(column)); item->setCheckState(1, Qt::Unchecked); Q_ASSERT(column != KTextEditor::CodeCompletionModel::Name); item->setCheckState(2, Qt::Unchecked); } } } else { for (int column = 0; column < KTextEditor::CodeCompletionModel::ColumnCount; ++column) { QTreeWidgetItem *item = new QTreeWidgetItem(ui->columnMergeTree, column); item->setText(0, KateCompletionModel::columnName(column) + QString::fromLatin1(" %1").arg(column)); item->setCheckState(1, Qt::Unchecked); if (column == KTextEditor::CodeCompletionModel::Name) { item->setText(2, i18n("Always")); } else { item->setCheckState(2, Qt::Checked); } } } // init with defaults from config or really hardcoded ones KConfigGroup config(KTextEditor::EditorPrivate::config(), "Code Completion"); readConfig(config); // buttons QDialogButtonBox *buttons = new QDialogButtonBox(this); mainLayout->addWidget(buttons); QPushButton *okButton = new QPushButton(this); okButton->setDefault(true); KGuiItem::assign(okButton, KStandardGuiItem::ok()); buttons->addButton(okButton, QDialogButtonBox::AcceptRole); connect(okButton, SIGNAL(clicked()), this, SLOT(apply())); QPushButton *cancelButton = new QPushButton(this); KGuiItem::assign(cancelButton, KStandardGuiItem::cancel()); buttons->addButton(okButton, QDialogButtonBox::RejectRole); connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject())); } KateCompletionConfig::~ KateCompletionConfig() { delete ui; } void KateCompletionConfig::readConfig(const KConfigGroup &config) { configStart(); // Sorting ui->sorting->setChecked(config.readEntry("Sorting Enabled", true)); ui->sortingAlphabetical->setChecked(config.readEntry("Sort Alphabetically", true)); ui->sortingCaseSensitive->setChecked(config.readEntry("Case Sensitive Sort", false)); ui->sortingInheritanceDepth->setChecked(config.readEntry("Sort by Inheritance Depth", true)); // Filtering ui->filtering->setChecked(config.readEntry("Filtering Enabled", false)); ui->filteringContextMatchOnly->setChecked(config.readEntry("Filter by Context Match Only", false)); ui->filteringHideAttributes->setChecked(config.readEntry("Hide Completions by Attribute", false)); int attributes = config.readEntry("Filter Attribute Mask", 0); + for (int i = 0; i < ui->filteringAttributesList->count(); ++i) { QListWidgetItem *item = ui->filteringAttributesList->item(i); - item->setCheckState(((1 << (i - 1)) & attributes) ? Qt::Checked : Qt::Unchecked); + if (i == 0) { + // Avoid a negative shift. 0xFFFFFFFF80000000 is 1 << -1. + item->setCheckState((0xFFFFFFFF80000000 & attributes) ? Qt::Checked : Qt::Unchecked); + } else { + item->setCheckState(((1 << (i -1)) & attributes) ? Qt::Checked : Qt::Unchecked); + } } ui->filteringMaximumInheritanceDepth->setValue(config.readEntry("Filter by Maximum Inheritance Depth", 0)); // Grouping ui->grouping->setChecked(config.readEntry("Grouping Enabled", true)); m_groupingScopeType->setCheckState(0, config.readEntry("Group by Scope Type", true) ? Qt::Checked : Qt::Unchecked); m_groupingScope->setCheckState(0, config.readEntry("Group by Scope", false) ? Qt::Checked : Qt::Unchecked); m_groupingAccessType->setCheckState(0, config.readEntry("Group by Access Type", true) ? Qt::Checked : Qt::Unchecked); m_groupingItemType->setCheckState(0, config.readEntry("Group by Item Type", false) ? Qt::Checked : Qt::Unchecked); ui->accessConst->setChecked(config.readEntry("Group by Const", false)); ui->accessStatic->setChecked(config.readEntry("Group by Static", false)); ui->accessSignalSlot->setChecked(config.readEntry("Group by Signals and Slots", false)); // Column merging ui->columnMerging->setChecked(config.readEntry("Column Merging Enabled", true)); for (int i = 0; i < ui->columnMergeTree->topLevelItemCount(); ++i) { QTreeWidgetItem *item = ui->columnMergeTree->topLevelItem(i); ///Initialize a standard column-merging: Merge Scope, Name, Arguments and Postfix item->setCheckState(1, config.readEntry(QStringLiteral("Column %1 Merge").arg(i), (i == CodeCompletionModel::Scope || i == CodeCompletionModel::Name || i == CodeCompletionModel::Arguments)) ? Qt::Checked : Qt::Unchecked); item->setCheckState(2, config.readEntry(QStringLiteral("Column %1 Show").arg(i), true) ? Qt::Checked : Qt::Unchecked); } applyInternal(); configEnd(); } void KateCompletionConfig::writeConfig(KConfigGroup &config) { // Sorting config.writeEntry("Sorting Enabled", ui->sorting->isChecked()); config.writeEntry("Sort Alphabetically", ui->sortingAlphabetical->isChecked()); config.writeEntry("Case Sensitive Sort", ui->sortingCaseSensitive->isChecked()); config.writeEntry("Sort by Inheritance Depth", ui->sortingInheritanceDepth->isChecked()); // Filtering config.writeEntry("Filtering Enabled", ui->filtering->isChecked()); config.writeEntry("Filter by Context Match Only", ui->filteringContextMatchOnly->isChecked()); config.writeEntry("Hide Completions by Attribute", ui->filteringHideAttributes->isChecked()); int attributes = 0; for (int i = 0; i < ui->filteringAttributesList->count(); ++i) { QListWidgetItem *item = ui->filteringAttributesList->item(i); if (item->checkState() == Qt::Checked) { - attributes |= 1 << (i - 1); + if (i == 0) { + // Avoid a negative shift. 0xFFFFFFFF80000000 is 1 << -1. + attributes |= 0xFFFFFFFF80000000; + } + else { + attributes |= 1 << (i - 1); + } } } config.writeEntry("Filter Attribute Mask", attributes); config.writeEntry("Filter by Maximum Inheritance Depth", ui->filteringMaximumInheritanceDepth->value()); // Grouping config.writeEntry("Grouping Enabled", ui->grouping->isChecked()); config.writeEntry("Group by Scope Type", m_groupingScopeType->checkState(0) == Qt::Checked ? true : false); config.writeEntry("Group by Scope", m_groupingScope->checkState(0) == Qt::Checked ? true : false); config.writeEntry("Group by Access Type", m_groupingAccessType->checkState(0) == Qt::Checked ? true : false); config.writeEntry("Group by Item Type", m_groupingItemType->checkState(0) == Qt::Checked ? true : false); config.writeEntry("Group by Const", ui->accessConst->isChecked()); config.writeEntry("Group by Static", ui->accessStatic->isChecked()); config.writeEntry("Group by Signals and Slots", ui->accessSignalSlot->isChecked()); // Column merging config.writeEntry("Column Merging Enabled", ui->columnMerging->isChecked()); for (int i = 0; i < ui->columnMergeTree->topLevelItemCount(); ++i) { QTreeWidgetItem *item = ui->columnMergeTree->topLevelItem(i); config.writeEntry(QStringLiteral("Column %1 Merge").arg(i), item->checkState(1) == Qt::Checked ? true : false); config.writeEntry(QStringLiteral("Column %1 Show").arg(i), item->checkState(2) == Qt::Checked ? true : false); } config.sync(); } void KateCompletionConfig::updateConfig() { // Ah, nothing to do, I think...? } void KateCompletionConfig::moveColumnUp() { QTreeWidgetItem *item = ui->columnMergeTree->currentItem(); if (item) { int index = ui->columnMergeTree->indexOfTopLevelItem(item); if (index > 0) { ui->columnMergeTree->takeTopLevelItem(index); ui->columnMergeTree->insertTopLevelItem(index - 1, item); ui->columnMergeTree->setCurrentItem(item); } } } void KateCompletionConfig::moveColumnDown() { QTreeWidgetItem *item = ui->columnMergeTree->currentItem(); if (item) { int index = ui->columnMergeTree->indexOfTopLevelItem(item); if (index < ui->columnMergeTree->topLevelItemCount() - 1) { ui->columnMergeTree->takeTopLevelItem(index); ui->columnMergeTree->insertTopLevelItem(index + 1, item); ui->columnMergeTree->setCurrentItem(item); } } } void KateCompletionConfig::apply() { applyInternal(); KConfigGroup config(KTextEditor::EditorPrivate::config(), "Code Completion"); writeConfig(config); } void KateCompletionConfig::applyInternal() { // Sorting m_model->setSortingEnabled(ui->sorting->isChecked()); m_model->setSortingAlphabetical(ui->sortingAlphabetical->isChecked()); m_model->setSortingCaseSensitivity(ui->sortingCaseSensitive->isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive); m_model->setSortingByInheritanceDepth(ui->sortingInheritanceDepth->isChecked()); // Filtering m_model->setFilteringEnabled(ui->filtering->isChecked()); m_model->setFilterContextMatchesOnly(ui->filteringContextMatchOnly->isChecked()); m_model->setFilterByAttribute(ui->filteringHideAttributes->isChecked()); CodeCompletionModel::CompletionProperties attributes = CodeCompletionModel::NoProperty; for (int i = 0; i < ui->filteringAttributesList->count(); ++i) { QListWidgetItem *item = ui->filteringAttributesList->item(i); if (item->checkState() == Qt::Checked) { attributes |= static_cast(item->type()); } } m_model->setFilterAttributes(attributes); m_model->setMaximumInheritanceDepth(ui->filteringMaximumInheritanceDepth->value()); // Grouping m_model->setGroupingEnabled(ui->grouping->isChecked()); KateCompletionModel::GroupingMethods groupingMethod = KateCompletionModel::GroupingMethods(); if (m_groupingScopeType->checkState(0) == Qt::Checked) { groupingMethod = KateCompletionModel::ScopeType; } if (m_groupingScope->checkState(0) == Qt::Checked) { groupingMethod |= KateCompletionModel::Scope; } if (m_groupingAccessType->checkState(0) == Qt::Checked) { groupingMethod |= KateCompletionModel::AccessType; } if (m_groupingItemType->checkState(0) == Qt::Checked) { groupingMethod |= KateCompletionModel::ItemType; } m_model->setGroupingMethod(groupingMethod); m_model->setAccessIncludeConst(ui->accessConst->isChecked()); m_model->setAccessIncludeStatic(ui->accessStatic->isChecked()); m_model->setAccessIncludeSignalSlot(ui->accessSignalSlot->isChecked()); // Column merging m_model->setColumnMergingEnabled(ui->columnMerging->isChecked()); QList< QList > mergedColumns; QList oneMerge; for (int i = 0; i < ui->columnMergeTree->topLevelItemCount(); ++i) { QTreeWidgetItem *item = ui->columnMergeTree->topLevelItem(i); if (item->type() != KTextEditor::CodeCompletionModel::Name && item->checkState(2) == Qt::Unchecked) { continue; } if (item->checkState(1) == Qt::Unchecked) { if (!oneMerge.isEmpty()) { mergedColumns.append(oneMerge); } oneMerge.clear(); } oneMerge.append(item->type()); } if (!oneMerge.isEmpty()) { mergedColumns.append(oneMerge); } m_model->setColumnMerges(mergedColumns); } void KateCompletionConfig::moveGroupingUp() { QTreeWidgetItem *item = ui->groupingMethods->currentItem(); if (item) { int index = ui->groupingMethods->indexOfTopLevelItem(item); if (index > 0) { ui->groupingMethods->takeTopLevelItem(index); ui->groupingMethods->insertTopLevelItem(index - 1, item); ui->groupingMethods->setCurrentItem(item); } } } void KateCompletionConfig::moveGroupingDown() { QTreeWidgetItem *item = ui->groupingMethods->currentItem(); if (item) { int index = ui->groupingMethods->indexOfTopLevelItem(item); if (index < ui->groupingMethods->topLevelItemCount() - 1) { ui->groupingMethods->takeTopLevelItem(index); ui->groupingMethods->insertTopLevelItem(index + 1, item); ui->groupingMethods->setCurrentItem(item); } } } void KateCompletionConfig::moveGroupingOrderUp() { QListWidgetItem *item = ui->sortGroupingOrder->currentItem(); int index = ui->sortGroupingOrder->currentRow(); if (index > 0) { ui->sortGroupingOrder->takeItem(index); ui->sortGroupingOrder->insertItem(index - 1, item); ui->sortGroupingOrder->setCurrentItem(item); } } void KateCompletionConfig::moveGroupingOrderDown() { QListWidgetItem *item = ui->sortGroupingOrder->currentItem(); int index = ui->sortGroupingOrder->currentRow(); if (index < ui->sortGroupingOrder->count() - 1) { ui->sortGroupingOrder->takeItem(index); ui->sortGroupingOrder->insertItem(index + 1, item); ui->sortGroupingOrder->setCurrentItem(item); } } diff --git a/src/completion/katecompletionconfig.h b/src/completion/katecompletionconfig.h index 3999ba46..45b0f1b7 100644 --- a/src/completion/katecompletionconfig.h +++ b/src/completion/katecompletionconfig.h @@ -1,83 +1,83 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2006 Hamish Rodda * * 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 KATECOMPLETIONCONFIG_H #define KATECOMPLETIONCONFIG_H #include #include "kateconfig.h" namespace Ui { class CompletionConfigWidget; } class QTreeWidgetItem; class KateCompletionModel; /** * @author Hamish Rodda */ class KateCompletionConfig : public QDialog, public KateConfig { Q_OBJECT public: explicit KateCompletionConfig(KateCompletionModel *model, QWidget *parent = nullptr); - virtual ~KateCompletionConfig(); + ~KateCompletionConfig() override; /** * Read config from object */ void readConfig(const KConfigGroup &config); /** * Write config to object */ void writeConfig(KConfigGroup &config); public Q_SLOTS: void apply(); protected: - void updateConfig() Q_DECL_OVERRIDE; + void updateConfig() override; private Q_SLOTS: void moveColumnUp(); void moveColumnDown(); void moveGroupingUp(); void moveGroupingDown(); void moveGroupingOrderUp(); void moveGroupingOrderDown(); private: void applyInternal(); Ui::CompletionConfigWidget *ui; KateCompletionModel *m_model; QTreeWidgetItem *m_groupingScopeType; QTreeWidgetItem *m_groupingScope; QTreeWidgetItem *m_groupingAccessType; QTreeWidgetItem *m_groupingItemType; }; #endif diff --git a/src/completion/katecompletiondelegate.h b/src/completion/katecompletiondelegate.h index 361511bb..0124eb6b 100644 --- a/src/completion/katecompletiondelegate.h +++ b/src/completion/katecompletiondelegate.h @@ -1,47 +1,47 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2007 David Nolden * * 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 KATECOMPLETIONDELEGATE_H #define KATECOMPLETIONDELEGATE_H #include "expandingtree/expandingdelegate.h" class KateRenderer; namespace KTextEditor { class DocumentPrivate; } class KateCompletionWidget; class KateCompletionDelegate : public ExpandingDelegate { public: explicit KateCompletionDelegate(ExpandingWidgetModel *model, KateCompletionWidget *parent); KateRenderer *renderer() const; KateCompletionWidget *widget() const; KTextEditor::DocumentPrivate *document() const; protected: - void adjustStyle(const QModelIndex &index, QStyleOptionViewItem &option) const Q_DECL_OVERRIDE; + void adjustStyle(const QModelIndex &index, QStyleOptionViewItem &option) const override; mutable int m_cachedRow; mutable QList m_cachedColumnStarts; - void heightChanged() const Q_DECL_OVERRIDE; - QList createHighlighting(const QModelIndex &index, QStyleOptionViewItem &option) const Q_DECL_OVERRIDE; + void heightChanged() const override; + QList createHighlighting(const QModelIndex &index, QStyleOptionViewItem &option) const override; }; #endif diff --git a/src/completion/katecompletionmodel.cpp b/src/completion/katecompletionmodel.cpp index 366e3a8b..66520f00 100644 --- a/src/completion/katecompletionmodel.cpp +++ b/src/completion/katecompletionmodel.cpp @@ -1,2419 +1,2404 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2005-2006 Hamish Rodda * Copyright (C) 2007-2008 David Nolden * * 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 "katecompletionmodel.h" #include "katecompletionwidget.h" #include "katecompletiontree.h" #include "katecompletiondelegate.h" #include "kateargumenthintmodel.h" #include "kateview.h" #include "katerenderer.h" #include "kateconfig.h" #include #include "katepartdebug.h" #include #include #include #include #include #include using namespace KTextEditor; ///A helper-class for handling completion-models with hierarchical grouping/optimization class HierarchicalModelHandler { public: explicit HierarchicalModelHandler(CodeCompletionModel *model); void addValue(CodeCompletionModel::ExtraItemDataRoles role, const QVariant &value); //Walks the index upwards and collects all defined completion-roles on the way void collectRoles(const QModelIndex &index); void takeRole(const QModelIndex &index); CodeCompletionModel *model() const; //Assumes that index is a sub-index of the indices where role-values were taken QVariant getData(CodeCompletionModel::ExtraItemDataRoles role, const QModelIndex &index) const; bool hasHierarchicalRoles() const; int inheritanceDepth(const QModelIndex &i) const; QString customGroup() const { return m_customGroup; } int customGroupingKey() const { return m_groupSortingKey; } private: typedef QMap RoleMap; RoleMap m_roleValues; QString m_customGroup; int m_groupSortingKey; CodeCompletionModel *m_model; }; CodeCompletionModel *HierarchicalModelHandler::model() const { return m_model; } bool HierarchicalModelHandler::hasHierarchicalRoles() const { return !m_roleValues.isEmpty(); } void HierarchicalModelHandler::collectRoles(const QModelIndex &index) { if (index.parent().isValid()) { collectRoles(index.parent()); } if (m_model->rowCount(index) != 0) { takeRole(index); } } int HierarchicalModelHandler::inheritanceDepth(const QModelIndex &i) const { return getData(CodeCompletionModel::InheritanceDepth, i).toInt(); } void HierarchicalModelHandler::takeRole(const QModelIndex &index) { QVariant v = index.data(CodeCompletionModel::GroupRole); if (v.isValid() && v.canConvert(QVariant::Int)) { QVariant value = index.data(v.toInt()); if (v.toInt() == Qt::DisplayRole) { m_customGroup = index.data(Qt::DisplayRole).toString(); QVariant sortingKey = index.data(CodeCompletionModel::InheritanceDepth); if (sortingKey.canConvert(QVariant::Int)) { m_groupSortingKey = sortingKey.toInt(); } } else { m_roleValues[(CodeCompletionModel::ExtraItemDataRoles)v.toInt()] = value; } } else { qCDebug(LOG_KTE) << "Did not return valid GroupRole in hierarchical completion-model"; } } QVariant HierarchicalModelHandler::getData(CodeCompletionModel::ExtraItemDataRoles role, const QModelIndex &index) const { RoleMap::const_iterator it = m_roleValues.find(role); if (it != m_roleValues.end()) { return *it; } else { return index.data(role); } } HierarchicalModelHandler::HierarchicalModelHandler(CodeCompletionModel *model) : m_groupSortingKey(-1), m_model(model) { } void HierarchicalModelHandler::addValue(CodeCompletionModel::ExtraItemDataRoles role, const QVariant &value) { m_roleValues[role] = value; } KateCompletionModel::KateCompletionModel(KateCompletionWidget *parent) : ExpandingWidgetModel(parent) - , m_hasGroups(false) - , m_matchCaseSensitivity(Qt::CaseInsensitive) , m_ungrouped(new Group({}, 0, this)) , m_argumentHints(new Group(i18n("Argument-hints"), -1, this)) , m_bestMatches(new Group(i18n("Best matches"), BestMatchesProperty, this)) - , m_sortingEnabled(false) - , m_sortingAlphabetical(false) - , m_isSortingByInheritance(false) - , m_sortingCaseSensitivity(Qt::CaseInsensitive) - , m_filteringEnabled(false) - , m_filterContextMatchesOnly(false) - , m_filterByAttribute(false) , m_filterAttributes(KTextEditor::CodeCompletionModel::NoProperty) - , m_maximumInheritanceDepth(0) - , m_groupingEnabled(false) - , m_accessConst(false) - , m_accessStatic(false) - , m_accesSignalSlot(false) - , m_columnMergingEnabled(false) -// , m_haveExactMatch(false) + { m_emptyGroups.append(m_ungrouped); m_emptyGroups.append(m_argumentHints); m_emptyGroups.append(m_bestMatches); m_updateBestMatchesTimer = new QTimer(this); m_updateBestMatchesTimer->setSingleShot(true); connect(m_updateBestMatchesTimer, SIGNAL(timeout()), this, SLOT(updateBestMatches())); m_groupHash.insert(0, m_ungrouped); m_groupHash.insert(-1, m_argumentHints); m_groupHash.insert(BestMatchesProperty, m_argumentHints); } KateCompletionModel::~KateCompletionModel() { clearCompletionModels(); delete m_argumentHints; delete m_ungrouped; delete m_bestMatches; } QTreeView *KateCompletionModel::treeView() const { return view()->completionWidget()->treeView(); } QVariant KateCompletionModel::data(const QModelIndex &index, int role) const { if (!hasCompletionModel() || !index.isValid()) { return QVariant(); } if (role == Qt::DecorationRole && index.column() == KTextEditor::CodeCompletionModel::Prefix && isExpandable(index)) { cacheIcons(); if (!isExpanded(index)) { return QVariant(m_collapsedIcon); } else { return QVariant(m_expandedIcon); } } //groupOfParent returns a group when the index is a member of that group, but not the group head/label. if (!hasGroups() || groupOfParent(index)) { if ( role == Qt::TextAlignmentRole ) { if (isColumnMergingEnabled() && !m_columnMerges.isEmpty()) { int c = 0; foreach (const QList &list, m_columnMerges) { if (index.column() < c + list.size()) { c += list.size(); continue; } else if (list.count() == 1 && list.first() == CodeCompletionModel::Scope) { return Qt::AlignRight; } else { return QVariant(); } } } else if ((!isColumnMergingEnabled() || m_columnMerges.isEmpty()) && index.column() == CodeCompletionModel::Scope) { return Qt::AlignRight; } } // Merge text for column merging if (role == Qt::DisplayRole && !m_columnMerges.isEmpty() && isColumnMergingEnabled()) { QString text; foreach (int column, m_columnMerges[index.column()]) { QModelIndex sourceIndex = mapToSource(createIndex(index.row(), column, index.internalPointer())); text.append(sourceIndex.data(role).toString()); } return text; } if (role == CodeCompletionModel::HighlightingMethod) { //Return that we are doing custom-highlighting of one of the sub-strings does it. Unfortunately internal highlighting does not work for the other substrings. foreach (int column, m_columnMerges[index.column()]) { QModelIndex sourceIndex = mapToSource(createIndex(index.row(), column, index.internalPointer())); QVariant method = sourceIndex.data(CodeCompletionModel::HighlightingMethod); if (method.type() == QVariant::Int && method.toInt() == CodeCompletionModel::CustomHighlighting) { return QVariant(CodeCompletionModel::CustomHighlighting); } } return QVariant(); } if (role == CodeCompletionModel::CustomHighlight) { //Merge custom highlighting if multiple columns were merged QStringList strings; //Collect strings foreach (int column, m_columnMerges[index.column()]) { strings << mapToSource(createIndex(index.row(), column, index.internalPointer())).data(Qt::DisplayRole).toString(); } QList highlights; //Collect custom-highlightings foreach (int column, m_columnMerges[index.column()]) { highlights << mapToSource(createIndex(index.row(), column, index.internalPointer())).data(CodeCompletionModel::CustomHighlight).toList(); } return mergeCustomHighlighting(strings, highlights, 0); } QVariant v = mapToSource(index).data(role); if (v.isValid()) { return v; } else { return ExpandingWidgetModel::data(index, role); } } //Returns a nonzero group if this index is the head of a group(A Label in the list) Group *g = groupForIndex(index); if (g && (!g->isEmpty)) { switch (role) { case Qt::DisplayRole: if (!index.column()) { return g->title; } break; case Qt::FontRole: if (!index.column()) { QFont f = view()->renderer()->config()->font(); f.setBold(true); return f; } break; case Qt::ForegroundRole: return QApplication::palette().toolTipText().color(); case Qt::BackgroundRole: return QApplication::palette().toolTipBase().color(); } } return QVariant(); } int KateCompletionModel::contextMatchQuality(const QModelIndex &index) const { if (!index.isValid()) { return 0; } Group *g = groupOfParent(index); if (!g || g->filtered.size() < index.row()) { return 0; } return contextMatchQuality(g->filtered[index.row()].sourceRow()); } int KateCompletionModel::contextMatchQuality(const ModelRow &source) const { QModelIndex realIndex = source.second; int bestMatch = -1; //Iterate through all argument-hints and find the best match-quality foreach (const Item &item, m_argumentHints->filtered) { const ModelRow &row(item.sourceRow()); if (realIndex.model() != row.first) { continue; //We can only match within the same source-model } QModelIndex hintIndex = row.second; QVariant depth = hintIndex.data(CodeCompletionModel::ArgumentHintDepth); if (!depth.isValid() || depth.type() != QVariant::Int || depth.toInt() != 1) { continue; //Only match completion-items to argument-hints of depth 1(the ones the item will be given to as argument) } hintIndex.data(CodeCompletionModel::SetMatchContext); QVariant matchQuality = realIndex.data(CodeCompletionModel::MatchQuality); if (matchQuality.isValid() && matchQuality.type() == QVariant::Int) { int m = matchQuality.toInt(); if (m > bestMatch) { bestMatch = m; } } } if (m_argumentHints->filtered.isEmpty()) { QVariant matchQuality = realIndex.data(CodeCompletionModel::MatchQuality); if (matchQuality.isValid() && matchQuality.type() == QVariant::Int) { int m = matchQuality.toInt(); if (m > bestMatch) { bestMatch = m; } } } return bestMatch; } Qt::ItemFlags KateCompletionModel::flags(const QModelIndex &index) const { if (!hasCompletionModel() || !index.isValid()) { return Qt::NoItemFlags; } if (!hasGroups() || groupOfParent(index)) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } return Qt::ItemIsEnabled; } KateCompletionWidget *KateCompletionModel::widget() const { return static_cast(QObject::parent()); } KTextEditor::ViewPrivate *KateCompletionModel::view() const { return widget()->view(); } void KateCompletionModel::setMatchCaseSensitivity(Qt::CaseSensitivity cs) { m_matchCaseSensitivity = cs; } int KateCompletionModel::columnCount(const QModelIndex &) const { return isColumnMergingEnabled() && !m_columnMerges.isEmpty() ? m_columnMerges.count() : KTextEditor::CodeCompletionModel::ColumnCount; } KateCompletionModel::ModelRow KateCompletionModel::modelRowPair(const QModelIndex &index) const { return qMakePair(static_cast(const_cast(index.model())), index); } bool KateCompletionModel::hasChildren(const QModelIndex &parent) const { if (!hasCompletionModel()) { return false; } if (!parent.isValid()) { if (hasGroups()) { return true; } return !m_ungrouped->filtered.isEmpty(); } if (parent.column() != 0) { return false; } if (!hasGroups()) { return false; } if (Group *g = groupForIndex(parent)) { return !g->filtered.isEmpty(); } return false; } QModelIndex KateCompletionModel::index(int row, int column, const QModelIndex &parent) const { if (row < 0 || column < 0 || column >= columnCount(QModelIndex())) { return QModelIndex(); } if (parent.isValid() || !hasGroups()) { if (parent.isValid() && parent.column() != 0) { return QModelIndex(); } Group *g = groupForIndex(parent); if (!g) { return QModelIndex(); } if (row >= g->filtered.count()) { //qCWarning(LOG_KTE) << "Invalid index requested: row " << row << " beyond indivdual range in group " << g; return QModelIndex(); } //qCDebug(LOG_KTE) << "Returning index for child " << row << " of group " << g; return createIndex(row, column, g); } if (row >= m_rowTable.count()) { //qCWarning(LOG_KTE) << "Invalid index requested: row " << row << " beyond group range."; return QModelIndex(); } //qCDebug(LOG_KTE) << "Returning index for group " << m_rowTable[row]; return createIndex(row, column, quintptr(0)); } /*QModelIndex KateCompletionModel::sibling( int row, int column, const QModelIndex & index ) const { if (row < 0 || column < 0 || column >= columnCount(QModelIndex())) return QModelIndex(); if (!index.isValid()) { } if (Group* g = groupOfParent(index)) { if (row >= g->filtered.count()) return QModelIndex(); return createIndex(row, column, g); } if (hasGroups()) return QModelIndex(); if (row >= m_ungrouped->filtered.count()) return QModelIndex(); return createIndex(row, column, m_ungrouped); }*/ bool KateCompletionModel::hasIndex(int row, int column, const QModelIndex &parent) const { if (row < 0 || column < 0 || column >= columnCount(QModelIndex())) { return false; } if (parent.isValid() || !hasGroups()) { if (parent.isValid() && parent.column() != 0) { return false; } Group *g = groupForIndex(parent); if (row >= g->filtered.count()) { return false; } return true; } if (row >= m_rowTable.count()) { return false; } return true; } QModelIndex KateCompletionModel::indexForRow(Group *g, int row) const { if (row < 0 || row >= g->filtered.count()) { return QModelIndex(); } return createIndex(row, 0, g); } QModelIndex KateCompletionModel::indexForGroup(Group *g) const { if (!hasGroups()) { return QModelIndex(); } int row = m_rowTable.indexOf(g); if (row == -1) { return QModelIndex(); } return createIndex(row, 0, quintptr(0)); } void KateCompletionModel::clearGroups() { clearExpanding(); m_ungrouped->clear(); m_argumentHints->clear(); m_bestMatches->clear(); // Don't bother trying to work out where it is m_rowTable.removeAll(m_ungrouped); m_emptyGroups.removeAll(m_ungrouped); m_rowTable.removeAll(m_argumentHints); m_emptyGroups.removeAll(m_argumentHints); m_rowTable.removeAll(m_bestMatches); m_emptyGroups.removeAll(m_bestMatches); qDeleteAll(m_rowTable); qDeleteAll(m_emptyGroups); m_rowTable.clear(); m_emptyGroups.clear(); m_groupHash.clear(); m_customGroupHash.clear(); m_emptyGroups.append(m_ungrouped); m_groupHash.insert(0, m_ungrouped); m_emptyGroups.append(m_argumentHints); m_groupHash.insert(-1, m_argumentHints); m_emptyGroups.append(m_bestMatches); m_groupHash.insert(BestMatchesProperty, m_bestMatches); } QSet KateCompletionModel::createItems(const HierarchicalModelHandler &_handler, const QModelIndex &i, bool notifyModel) { HierarchicalModelHandler handler(_handler); QSet ret; if (handler.model()->rowCount(i) == 0) { //Leaf node, create an item ret.insert(createItem(handler, i, notifyModel)); } else { //Non-leaf node, take the role from the node, and recurse to the sub-nodes handler.takeRole(i); for (int a = 0; a < handler.model()->rowCount(i); a++) { ret += createItems(handler, i.child(a, 0), notifyModel); } } return ret; } QSet KateCompletionModel::deleteItems(const QModelIndex &i) { QSet ret; if (i.model()->rowCount(i) == 0) { //Leaf node, delete the item Group *g = groupForIndex(mapFromSource(i)); ret.insert(g); g->removeItem(ModelRow(const_cast(static_cast(i.model())), i)); } else { //Non-leaf node for (int a = 0; a < i.model()->rowCount(i); a++) { ret += deleteItems(i.child(a, 0)); } } return ret; } void KateCompletionModel::createGroups() { beginResetModel(); //After clearing the model, it has to be reset, else we will be in an invalid state while inserting //new groups. clearGroups(); bool has_groups = false; foreach (CodeCompletionModel *sourceModel, m_completionModels) { has_groups |= sourceModel->hasGroups(); for (int i = 0; i < sourceModel->rowCount(); ++i) { createItems(HierarchicalModelHandler(sourceModel), sourceModel->index(i, 0)); } } m_hasGroups = has_groups; //debugStats(); foreach (Group *g, m_rowTable) { hideOrShowGroup(g); } foreach (Group *g, m_emptyGroups) { hideOrShowGroup(g); } makeGroupItemsUnique(); updateBestMatches(); endResetModel(); } KateCompletionModel::Group *KateCompletionModel::createItem(const HierarchicalModelHandler &handler, const QModelIndex &sourceIndex, bool notifyModel) { //QModelIndex sourceIndex = sourceModel->index(row, CodeCompletionModel::Name, QModelIndex()); int completionFlags = handler.getData(CodeCompletionModel::CompletionRole, sourceIndex).toInt(); //Scope is expensive, should not be used with big models QString scopeIfNeeded = (groupingMethod() & Scope) ? sourceIndex.sibling(sourceIndex.row(), CodeCompletionModel::Scope).data(Qt::DisplayRole).toString() : QString(); int argumentHintDepth = handler.getData(CodeCompletionModel::ArgumentHintDepth, sourceIndex).toInt(); Group *g; if (argumentHintDepth) { g = m_argumentHints; } else { QString customGroup = handler.customGroup(); if (!customGroup.isNull() && m_hasGroups) { if (m_customGroupHash.contains(customGroup)) { g = m_customGroupHash[customGroup]; } else { g = new Group(customGroup, 0, this); g->customSortingKey = handler.customGroupingKey(); m_emptyGroups.append(g); m_customGroupHash.insert(customGroup, g); } } else { g = fetchGroup(completionFlags, scopeIfNeeded, handler.hasHierarchicalRoles()); } } Item item = Item(g != m_argumentHints, this, handler, ModelRow(handler.model(), sourceIndex)); if (g != m_argumentHints) { item.match(); } g->addItem(item, notifyModel); return g; } void KateCompletionModel::slotRowsInserted(const QModelIndex &parent, int start, int end) { QSet affectedGroups; HierarchicalModelHandler handler(static_cast(sender())); if (parent.isValid()) { handler.collectRoles(parent); } for (int i = start; i <= end; ++i) { affectedGroups += createItems(handler, parent.isValid() ? parent.child(i, 0) : handler.model()->index(i, 0), true); } foreach (Group *g, affectedGroups) { hideOrShowGroup(g, true); } } void KateCompletionModel::slotRowsRemoved(const QModelIndex &parent, int start, int end) { CodeCompletionModel *source = static_cast(sender()); QSet affectedGroups; for (int i = start; i <= end; ++i) { QModelIndex index = parent.isValid() ? parent.child(i, 0) : source->index(i, 0); affectedGroups += deleteItems(index); } foreach (Group *g, affectedGroups) { hideOrShowGroup(g, true); } } KateCompletionModel::Group *KateCompletionModel::fetchGroup(int attribute, const QString &scope, bool forceGrouping) { Q_UNUSED(forceGrouping); ///@todo use forceGrouping if (!hasGroups()) { return m_ungrouped; } int groupingAttribute = groupingAttributes(attribute); //qCDebug(LOG_KTE) << attribute << " " << groupingAttribute; if (m_groupHash.contains(groupingAttribute)) { if (groupingMethod() & Scope) { for (QHash::ConstIterator it = m_groupHash.constFind(groupingAttribute); it != m_groupHash.constEnd() && it.key() == groupingAttribute; ++it) if (it.value()->scope == scope) { return it.value(); } } else { return m_groupHash.value(groupingAttribute); } } QString st, at, it; QString title; if (groupingMethod() & ScopeType) { if (attribute & KTextEditor::CodeCompletionModel::GlobalScope) { st = QStringLiteral("Global"); } else if (attribute & KTextEditor::CodeCompletionModel::NamespaceScope) { st = QStringLiteral("Namespace"); } else if (attribute & KTextEditor::CodeCompletionModel::LocalScope) { st = QStringLiteral("Local"); } title = st; } if (groupingMethod() & Scope) { if (!title.isEmpty()) { title.append(QLatin1String(" ")); } title.append(scope); } if (groupingMethod() & AccessType) { if (attribute & KTextEditor::CodeCompletionModel::Public) { at = QStringLiteral("Public"); } else if (attribute & KTextEditor::CodeCompletionModel::Protected) { at = QStringLiteral("Protected"); } else if (attribute & KTextEditor::CodeCompletionModel::Private) { at = QStringLiteral("Private"); } if (accessIncludeStatic() && attribute & KTextEditor::CodeCompletionModel::Static) { at.append(QLatin1String(" Static")); } if (accessIncludeConst() && attribute & KTextEditor::CodeCompletionModel::Const) { at.append(QLatin1String(" Const")); } if (!at.isEmpty()) { if (!title.isEmpty()) { title.append(QLatin1String(", ")); } title.append(at); } } if (groupingMethod() & ItemType) { if (attribute & CodeCompletionModel::Namespace) { it = i18n("Namespaces"); } else if (attribute & CodeCompletionModel::Class) { it = i18n("Classes"); } else if (attribute & CodeCompletionModel::Struct) { it = i18n("Structs"); } else if (attribute & CodeCompletionModel::Union) { it = i18n("Unions"); } else if (attribute & CodeCompletionModel::Function) { it = i18n("Functions"); } else if (attribute & CodeCompletionModel::Variable) { it = i18n("Variables"); } else if (attribute & CodeCompletionModel::Enum) { it = i18n("Enumerations"); } if (!it.isEmpty()) { if (!title.isEmpty()) { title.append(QLatin1String(" ")); } title.append(it); } } Group *ret = new Group(title, attribute, this); ret->scope = scope; m_emptyGroups.append(ret); m_groupHash.insert(groupingAttribute, ret); return ret; } bool KateCompletionModel::hasGroups() const { //qCDebug(LOG_KTE) << "m_groupHash.size()"<= m_rowTable.count()) { return m_ungrouped; } return m_rowTable[index.row()]; } /*QMap< int, QVariant > KateCompletionModel::itemData( const QModelIndex & index ) const { if (!hasGroups() || groupOfParent(index)) { QModelIndex index = mapToSource(index); if (index.isValid()) return index.model()->itemData(index); } return QAbstractItemModel::itemData(index); }*/ QModelIndex KateCompletionModel::parent(const QModelIndex &index) const { if (!index.isValid()) { return QModelIndex(); } if (Group *g = groupOfParent(index)) { if (!hasGroups()) { Q_ASSERT(g == m_ungrouped); return QModelIndex(); } int row = m_rowTable.indexOf(g); if (row == -1) { qCWarning(LOG_KTE) << "Couldn't find parent for index" << index; return QModelIndex(); } return createIndex(row, 0, quintptr(0)); } return QModelIndex(); } int KateCompletionModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { if (hasGroups()) { //qCDebug(LOG_KTE) << "Returning row count for toplevel " << m_rowTable.count(); return m_rowTable.count(); } else { //qCDebug(LOG_KTE) << "Returning ungrouped row count for toplevel " << m_ungrouped->filtered.count(); return m_ungrouped->filtered.count(); } } if (parent.column() > 0) { // only the first column has children return 0; } Group *g = groupForIndex(parent); // This is not an error, seems you don't have to check hasChildren() if (!g) { return 0; } //qCDebug(LOG_KTE) << "Returning row count for group " << g << " as " << g->filtered.count(); return g->filtered.count(); } void KateCompletionModel::sort(int column, Qt::SortOrder order) { Q_UNUSED(column) Q_UNUSED(order) } QModelIndex KateCompletionModel::mapToSource(const QModelIndex &proxyIndex) const { if (!proxyIndex.isValid()) { return QModelIndex(); } if (Group *g = groupOfParent(proxyIndex)) { if (proxyIndex.row() >= 0 && proxyIndex.row() < g->filtered.count()) { ModelRow source = g->filtered[proxyIndex.row()].sourceRow(); return source.second.sibling(source.second.row(), proxyIndex.column()); } else { qCDebug(LOG_KTE) << "Invalid proxy-index"; } } return QModelIndex(); } QModelIndex KateCompletionModel::mapFromSource(const QModelIndex &sourceIndex) const { if (!sourceIndex.isValid()) { return QModelIndex(); } if (!hasGroups()) { return index(m_ungrouped->rowOf(modelRowPair(sourceIndex)), sourceIndex.column(), QModelIndex()); } foreach (Group *g, m_rowTable) { int row = g->rowOf(modelRowPair(sourceIndex)); if (row != -1) { return index(row, sourceIndex.column(), indexForGroup(g)); } } // Copied from above foreach (Group *g, m_emptyGroups) { int row = g->rowOf(modelRowPair(sourceIndex)); if (row != -1) { return index(row, sourceIndex.column(), indexForGroup(g)); } } return QModelIndex(); } void KateCompletionModel::setCurrentCompletion(KTextEditor::CodeCompletionModel *model, const QString &completion) { if (m_currentMatch[model] == completion) { return; } if (!hasCompletionModel()) { m_currentMatch[model] = completion; return; } changeTypes changeType = Change; if (m_currentMatch[model].length() > completion.length() && m_currentMatch[model].startsWith(completion, m_matchCaseSensitivity)) { // Filter has been broadened changeType = Broaden; } else if (m_currentMatch[model].length() < completion.length() && completion.startsWith(m_currentMatch[model], m_matchCaseSensitivity)) { // Filter has been narrowed changeType = Narrow; } //qCDebug(LOG_KTE) << model << "Old match: " << m_currentMatch[model] << ", new: " << completion << ", type: " << changeType; m_currentMatch[model] = completion; const bool resetModel = (changeType != Narrow); if (resetModel) { beginResetModel(); } if (!hasGroups()) { changeCompletions(m_ungrouped, changeType, !resetModel); } else { foreach (Group *g, m_rowTable) { if (g != m_argumentHints) { changeCompletions(g, changeType, !resetModel); } } foreach (Group *g, m_emptyGroups) { if (g != m_argumentHints) { changeCompletions(g, changeType, !resetModel); } } } // NOTE: best matches are also updated in resort resort(); if (resetModel) { endResetModel(); } clearExpanding(); //We need to do this, or be aware of expanding-widgets while filtering. emit layoutChanged(); } QString KateCompletionModel::commonPrefixInternal(const QString &forcePrefix) const { QString commonPrefix; // isNull() = true QList< Group * > groups = m_rowTable; groups += m_ungrouped; foreach (Group *g, groups) { foreach (const Item &item, g->filtered) { uint startPos = m_currentMatch[item.sourceRow().first].length(); const QString candidate = item.name().mid(startPos); if (!candidate.startsWith(forcePrefix)) { continue; } if (commonPrefix.isNull()) { commonPrefix = candidate; //Replace QString::null prefix with QString(), so we won't initialize it again if (commonPrefix.isNull()) { commonPrefix = QString(); // isEmpty() = true, isNull() = false } } else { commonPrefix = commonPrefix.left(candidate.length()); for (int a = 0; a < commonPrefix.length(); ++a) { if (commonPrefix[a] != candidate[a]) { commonPrefix = commonPrefix.left(a); break; } } } } } return commonPrefix; } QString KateCompletionModel::commonPrefix(QModelIndex selectedIndex) const { QString commonPrefix = commonPrefixInternal(QString()); if (commonPrefix.isEmpty() && selectedIndex.isValid()) { Group *g = m_ungrouped; if (hasGroups()) { g = groupOfParent(selectedIndex); } if (g && selectedIndex.row() < g->filtered.size()) { //Follow the path of the selected item, finding the next non-empty common prefix Item item = g->filtered[selectedIndex.row()]; int matchLength = m_currentMatch[item.sourceRow().first].length(); commonPrefix = commonPrefixInternal(item.name().mid(matchLength).left(1)); } } return commonPrefix; } void KateCompletionModel::changeCompletions(Group *g, changeTypes changeType, bool notifyModel) { if (changeType != Narrow) { g->filtered = g->prefilter; //In the "Broaden" or "Change" case, just re-filter everything, //and don't notify the model. The model is notified afterwards through a reset(). } //This code determines what of the filtered items still fit, and computes the ranges that were removed, giving //them to beginRemoveRows(..) in batches QList newFiltered; int deleteUntil = -1; //In each state, the range [currentRow+1, deleteUntil] needs to be deleted for (int currentRow = g->filtered.count() - 1; currentRow >= 0; --currentRow) { if (g->filtered[currentRow].match()) { //This row does not need to be deleted, which means that currentRow+1 to deleteUntil need to be deleted now if (deleteUntil != -1 && notifyModel) { beginRemoveRows(indexForGroup(g), currentRow + 1, deleteUntil); endRemoveRows(); } deleteUntil = -1; newFiltered.prepend(g->filtered[currentRow]); } else { if (deleteUntil == -1) { deleteUntil = currentRow; //Mark that this row needs to be deleted } } } if (deleteUntil != -1 && notifyModel) { beginRemoveRows(indexForGroup(g), 0, deleteUntil); endRemoveRows(); } g->filtered = newFiltered; hideOrShowGroup(g, notifyModel); } int KateCompletionModel::Group::orderNumber() const { if (this == model->m_ungrouped) { return 700; } if (customSortingKey != -1) { return customSortingKey; } if (attribute & BestMatchesProperty) { return 1; } if (attribute & KTextEditor::CodeCompletionModel::LocalScope) { return 100; } else if (attribute & KTextEditor::CodeCompletionModel::Public) { return 200; } else if (attribute & KTextEditor::CodeCompletionModel::Protected) { return 300; } else if (attribute & KTextEditor::CodeCompletionModel::Private) { return 400; } else if (attribute & KTextEditor::CodeCompletionModel::NamespaceScope) { return 500; } else if (attribute & KTextEditor::CodeCompletionModel::GlobalScope) { return 600; } return 700; } bool KateCompletionModel::Group::orderBefore(Group *other) const { return orderNumber() < other->orderNumber(); } void KateCompletionModel::hideOrShowGroup(Group *g, bool notifyModel) { if (g == m_argumentHints) { emit argumentHintsChanged(); m_updateBestMatchesTimer->start(200); //We have new argument-hints, so we have new best matches return; //Never show argument-hints in the normal completion-list } if (!g->isEmpty) { if (g->filtered.isEmpty()) { // Move to empty group list g->isEmpty = true; int row = m_rowTable.indexOf(g); if (row != -1) { if (hasGroups() && notifyModel) { beginRemoveRows(QModelIndex(), row, row); } m_rowTable.removeAt(row); if (hasGroups() && notifyModel) { endRemoveRows(); } m_emptyGroups.append(g); } else { qCWarning(LOG_KTE) << "Group " << g << " not found in row table!!"; } } } else { if (!g->filtered.isEmpty()) { // Move off empty group list g->isEmpty = false; int row = 0; //Find row where to insert for (int a = 0; a < m_rowTable.count(); a++) { if (g->orderBefore(m_rowTable[a])) { row = a; break; } row = a + 1; } if (notifyModel) { if (hasGroups()) { beginInsertRows(QModelIndex(), row, row); } else { beginInsertRows(QModelIndex(), 0, g->filtered.count()); } } m_rowTable.insert(row, g); if (notifyModel) { endInsertRows(); } m_emptyGroups.removeAll(g); } } } bool KateCompletionModel::indexIsItem(const QModelIndex &index) const { if (!hasGroups()) { return true; } if (groupOfParent(index)) { return true; } return false; } void KateCompletionModel::slotModelReset() { createGroups(); //debugStats(); } void KateCompletionModel::debugStats() { if (!hasGroups()) { qCDebug(LOG_KTE) << "Model groupless, " << m_ungrouped->filtered.count() << " items."; } else { qCDebug(LOG_KTE) << "Model grouped (" << m_rowTable.count() << " groups):"; foreach (Group *g, m_rowTable) { qCDebug(LOG_KTE) << "Group" << g << "count" << g->filtered.count(); } } } bool KateCompletionModel::hasCompletionModel() const { return !m_completionModels.isEmpty(); } void KateCompletionModel::setFilteringEnabled(bool enable) { if (m_filteringEnabled != enable) { m_filteringEnabled = enable; } } void KateCompletionModel::setSortingEnabled(bool enable) { if (m_sortingEnabled != enable) { m_sortingEnabled = enable; beginResetModel(); resort(); endResetModel(); } } void KateCompletionModel::setGroupingEnabled(bool enable) { if (m_groupingEnabled != enable) { m_groupingEnabled = enable; } } void KateCompletionModel::setColumnMergingEnabled(bool enable) { if (m_columnMergingEnabled != enable) { m_columnMergingEnabled = enable; } } bool KateCompletionModel::isColumnMergingEnabled() const { return m_columnMergingEnabled; } bool KateCompletionModel::isGroupingEnabled() const { return m_groupingEnabled; } bool KateCompletionModel::isFilteringEnabled() const { return m_filteringEnabled; } bool KateCompletionModel::isSortingEnabled() const { return m_sortingEnabled; } QString KateCompletionModel::columnName(int column) { switch (column) { case KTextEditor::CodeCompletionModel::Prefix: return i18n("Prefix"); case KTextEditor::CodeCompletionModel::Icon: return i18n("Icon"); case KTextEditor::CodeCompletionModel::Scope: return i18n("Scope"); case KTextEditor::CodeCompletionModel::Name: return i18n("Name"); case KTextEditor::CodeCompletionModel::Arguments: return i18n("Arguments"); case KTextEditor::CodeCompletionModel::Postfix: return i18n("Postfix"); } return QString(); } const QList< QList < int > > &KateCompletionModel::columnMerges() const { return m_columnMerges; } void KateCompletionModel::setColumnMerges(const QList< QList < int > > &columnMerges) { beginResetModel(); m_columnMerges = columnMerges; endResetModel(); } int KateCompletionModel::translateColumn(int sourceColumn) const { if (m_columnMerges.isEmpty()) { return sourceColumn; } /* Debugging - dump column merge list QString columnMerge; foreach (const QList& list, m_columnMerges) { columnMerge += '['; foreach (int column, list) { columnMerge += QString::number(column) + " "; } columnMerge += "] "; } qCDebug(LOG_KTE) << k_funcinfo << columnMerge;*/ int c = 0; foreach (const QList &list, m_columnMerges) { foreach (int column, list) { if (column == sourceColumn) { return c; } } c++; } return -1; } int KateCompletionModel::groupingAttributes(int attribute) const { int ret = 0; if (m_groupingMethod & ScopeType) { if (countBits(attribute & ScopeTypeMask) > 1) { qCWarning(LOG_KTE) << "Invalid completion model metadata: more than one scope type modifier provided."; } if (attribute & KTextEditor::CodeCompletionModel::GlobalScope) { ret |= KTextEditor::CodeCompletionModel::GlobalScope; } else if (attribute & KTextEditor::CodeCompletionModel::NamespaceScope) { ret |= KTextEditor::CodeCompletionModel::NamespaceScope; } else if (attribute & KTextEditor::CodeCompletionModel::LocalScope) { ret |= KTextEditor::CodeCompletionModel::LocalScope; } } if (m_groupingMethod & AccessType) { if (countBits(attribute & AccessTypeMask) > 1) { qCWarning(LOG_KTE) << "Invalid completion model metadata: more than one access type modifier provided."; } if (attribute & KTextEditor::CodeCompletionModel::Public) { ret |= KTextEditor::CodeCompletionModel::Public; } else if (attribute & KTextEditor::CodeCompletionModel::Protected) { ret |= KTextEditor::CodeCompletionModel::Protected; } else if (attribute & KTextEditor::CodeCompletionModel::Private) { ret |= KTextEditor::CodeCompletionModel::Private; } if (accessIncludeStatic() && attribute & KTextEditor::CodeCompletionModel::Static) { ret |= KTextEditor::CodeCompletionModel::Static; } if (accessIncludeConst() && attribute & KTextEditor::CodeCompletionModel::Const) { ret |= KTextEditor::CodeCompletionModel::Const; } } if (m_groupingMethod & ItemType) { if (countBits(attribute & ItemTypeMask) > 1) { qCWarning(LOG_KTE) << "Invalid completion model metadata: more than one item type modifier provided."; } if (attribute & KTextEditor::CodeCompletionModel::Namespace) { ret |= KTextEditor::CodeCompletionModel::Namespace; } else if (attribute & KTextEditor::CodeCompletionModel::Class) { ret |= KTextEditor::CodeCompletionModel::Class; } else if (attribute & KTextEditor::CodeCompletionModel::Struct) { ret |= KTextEditor::CodeCompletionModel::Struct; } else if (attribute & KTextEditor::CodeCompletionModel::Union) { ret |= KTextEditor::CodeCompletionModel::Union; } else if (attribute & KTextEditor::CodeCompletionModel::Function) { ret |= KTextEditor::CodeCompletionModel::Function; } else if (attribute & KTextEditor::CodeCompletionModel::Variable) { ret |= KTextEditor::CodeCompletionModel::Variable; } else if (attribute & KTextEditor::CodeCompletionModel::Enum) { ret |= KTextEditor::CodeCompletionModel::Enum; } /* if (itemIncludeTemplate() && attribute & KTextEditor::CodeCompletionModel::Template) ret |= KTextEditor::CodeCompletionModel::Template;*/ } return ret; } void KateCompletionModel::setGroupingMethod(GroupingMethods m) { m_groupingMethod = m; createGroups(); } bool KateCompletionModel::accessIncludeConst() const { return m_accessConst; } void KateCompletionModel::setAccessIncludeConst(bool include) { if (m_accessConst != include) { m_accessConst = include; if (groupingMethod() & AccessType) { createGroups(); } } } bool KateCompletionModel::accessIncludeStatic() const { return m_accessStatic; } void KateCompletionModel::setAccessIncludeStatic(bool include) { if (m_accessStatic != include) { m_accessStatic = include; if (groupingMethod() & AccessType) { createGroups(); } } } bool KateCompletionModel::accessIncludeSignalSlot() const { return m_accesSignalSlot; } void KateCompletionModel::setAccessIncludeSignalSlot(bool include) { if (m_accesSignalSlot != include) { m_accesSignalSlot = include; if (groupingMethod() & AccessType) { createGroups(); } } } int KateCompletionModel::countBits(int value) const { int count = 0; for (int i = 1; i; i <<= 1) if (i & value) { count++; } return count; } KateCompletionModel::GroupingMethods KateCompletionModel::groupingMethod() const { return m_groupingMethod; } bool KateCompletionModel::isSortingByInheritanceDepth() const { return m_isSortingByInheritance; } void KateCompletionModel::setSortingByInheritanceDepth(bool byInheritance) { m_isSortingByInheritance = byInheritance; } bool KateCompletionModel::isSortingAlphabetical() const { return m_sortingAlphabetical; } Qt::CaseSensitivity KateCompletionModel::sortingCaseSensitivity() const { return m_sortingCaseSensitivity; } KateCompletionModel::Item::Item(bool doInitialMatch, KateCompletionModel *m, const HierarchicalModelHandler &handler, ModelRow sr) : model(m) , m_sourceRow(sr) , matchCompletion(StartsWithMatch) , matchFilters(true) , m_haveExactMatch(false) { inheritanceDepth = handler.getData(CodeCompletionModel::InheritanceDepth, m_sourceRow.second).toInt(); m_unimportant = handler.getData(CodeCompletionModel::UnimportantItemRole, m_sourceRow.second).toBool(); QModelIndex nameSibling = sr.second.sibling(sr.second.row(), CodeCompletionModel::Name); m_nameColumn = nameSibling.data(Qt::DisplayRole).toString(); if (doInitialMatch) { filter(); match(); } } bool KateCompletionModel::Item::operator <(const Item &rhs) const { int ret = 0; //qCDebug(LOG_KTE) << c1 << " c/w " << c2 << " -> " << (model->isSortingReverse() ? ret > 0 : ret < 0) << " (" << ret << ")"; if(m_unimportant && !rhs.m_unimportant){ return false; } if(!m_unimportant && rhs.m_unimportant){ return true; } if (matchCompletion < rhs.matchCompletion) { // enums are ordered in the order items should be displayed return true; } if (matchCompletion > rhs.matchCompletion) { return false; } if (model->isSortingByInheritanceDepth()) { ret = inheritanceDepth - rhs.inheritanceDepth; } if (ret == 0 && model->isSortingAlphabetical()) { // Do not use localeAwareCompare, because it is simply too slow for a list of about 1000 items ret = QString::compare(m_nameColumn, rhs.m_nameColumn, model->sortingCaseSensitivity()); } if (ret == 0) { const QString& filter = rhs.model->currentCompletion(rhs.m_sourceRow.first); if( m_nameColumn.startsWith(filter, Qt::CaseSensitive) ) { return true; } if( rhs.m_nameColumn.startsWith(filter, Qt::CaseSensitive) ) { return false; } // FIXME need to define a better default ordering for multiple model display ret = m_sourceRow.second.row() - rhs.m_sourceRow.second.row(); } return ret < 0; } void KateCompletionModel::Group::addItem(Item i, bool notifyModel) { if (isEmpty) { notifyModel = false; } QModelIndex groupIndex; if (notifyModel) { groupIndex = model->indexForGroup(this); } if (model->isSortingEnabled()) { prefilter.insert(qUpperBound(prefilter.begin(), prefilter.end(), i), i); if (i.isVisible()) { QList::iterator it = qUpperBound(filtered.begin(), filtered.end(), i); uint rowNumber = it - filtered.begin(); if (notifyModel) { model->beginInsertRows(groupIndex, rowNumber, rowNumber); } filtered.insert(it, i); } } else { if (notifyModel) { model->beginInsertRows(groupIndex, prefilter.size(), prefilter.size()); } if (i.isVisible()) { prefilter.append(i); } } if (notifyModel) { model->endInsertRows(); } } bool KateCompletionModel::Group::removeItem(const ModelRow &row) { for (int pi = 0; pi < prefilter.count(); ++pi) if (prefilter[pi].sourceRow() == row) { int index = rowOf(row); if (index != -1) { model->beginRemoveRows(model->indexForGroup(this), index, index); } filtered.removeAt(index); prefilter.removeAt(pi); if (index != -1) { model->endRemoveRows(); } return index != -1; } Q_ASSERT(false); return false; } KateCompletionModel::Group::Group(const QString& title, int attribute, KateCompletionModel *m) : model(m) , attribute(attribute) // ugly hack to add some left margin , title(QLatin1Char(' ') + title) , isEmpty(true) , customSortingKey(-1) { Q_ASSERT(model); } void KateCompletionModel::setSortingAlphabetical(bool alphabetical) { if (m_sortingAlphabetical != alphabetical) { m_sortingAlphabetical = alphabetical; beginResetModel(); resort(); endResetModel(); } } void KateCompletionModel::Group::resort() { qStableSort(filtered.begin(), filtered.end()); model->hideOrShowGroup(this); } void KateCompletionModel::setSortingCaseSensitivity(Qt::CaseSensitivity cs) { if (m_sortingCaseSensitivity != cs) { m_sortingCaseSensitivity = cs; beginResetModel(); resort(); endResetModel(); } } void KateCompletionModel::resort() { foreach (Group *g, m_rowTable) { g->resort(); } foreach (Group *g, m_emptyGroups) { g->resort(); } // call updateBestMatches here, so they are moved to the top again. updateBestMatches(); } bool KateCompletionModel::Item::isValid() const { return model && m_sourceRow.first && m_sourceRow.second.row() >= 0; } void KateCompletionModel::Group::clear() { prefilter.clear(); filtered.clear(); isEmpty = true; } bool KateCompletionModel::filterContextMatchesOnly() const { return m_filterContextMatchesOnly; } void KateCompletionModel::setFilterContextMatchesOnly(bool filter) { if (m_filterContextMatchesOnly != filter) { m_filterContextMatchesOnly = filter; refilter(); } } bool KateCompletionModel::filterByAttribute() const { return m_filterByAttribute; } void KateCompletionModel::setFilterByAttribute(bool filter) { if (m_filterByAttribute == filter) { m_filterByAttribute = filter; refilter(); } } KTextEditor::CodeCompletionModel::CompletionProperties KateCompletionModel::filterAttributes() const { return m_filterAttributes; } void KateCompletionModel::setFilterAttributes(KTextEditor::CodeCompletionModel::CompletionProperties attributes) { if (m_filterAttributes == attributes) { m_filterAttributes = attributes; refilter(); } } int KateCompletionModel::maximumInheritanceDepth() const { return m_maximumInheritanceDepth; } void KateCompletionModel::setMaximumInheritanceDepth(int maxDepth) { if (m_maximumInheritanceDepth != maxDepth) { m_maximumInheritanceDepth = maxDepth; refilter(); } } void KateCompletionModel::refilter() { beginResetModel(); m_ungrouped->refilter(); foreach (Group *g, m_rowTable) if (g != m_argumentHints) { g->refilter(); } foreach (Group *g, m_emptyGroups) if (g != m_argumentHints) { g->refilter(); } updateBestMatches(); clearExpanding(); //We need to do this, or be aware of expanding-widgets while filtering. endResetModel(); } void KateCompletionModel::Group::refilter() { filtered.clear(); foreach (const Item &i, prefilter) if (!i.isFiltered()) { filtered.append(i); } } bool KateCompletionModel::Item::filter() { matchFilters = false; if (model->isFilteringEnabled()) { QModelIndex sourceIndex = m_sourceRow.second.sibling(m_sourceRow.second.row(), CodeCompletionModel::Name); if (model->filterContextMatchesOnly()) { QVariant contextMatch = sourceIndex.data(CodeCompletionModel::MatchQuality); if (contextMatch.canConvert(QVariant::Int) && !contextMatch.toInt()) { return false; } } if (model->filterByAttribute()) { int completionFlags = sourceIndex.data(CodeCompletionModel::CompletionRole).toInt(); if (model->filterAttributes() & completionFlags) { return false; } } if (model->maximumInheritanceDepth() > 0) { int inheritanceDepth = sourceIndex.data(CodeCompletionModel::InheritanceDepth).toInt(); if (inheritanceDepth > model->maximumInheritanceDepth()) { return false; } } } matchFilters = true; return matchFilters; } uint KateCompletionModel::filteredItemCount() const { uint ret = 0; foreach (Group *group, m_rowTable) { ret += group->filtered.size(); } return ret; } bool KateCompletionModel::shouldMatchHideCompletionList() const { // @todo Make this faster bool doHide = false; CodeCompletionModel *hideModel = nullptr; foreach (Group *group, m_rowTable) foreach (const Item &item, group->filtered) if (item.haveExactMatch()) { KTextEditor::CodeCompletionModelControllerInterface *iface3 = dynamic_cast(item.sourceRow().first); bool hide = false; if (!iface3) { hide = true; } if (iface3 && iface3->matchingItem(item.sourceRow().second) == KTextEditor::CodeCompletionModelControllerInterface::HideListIfAutomaticInvocation) { hide = true; } if (hide) { doHide = true; hideModel = item.sourceRow().first; } } if (doHide) { // Check if all other visible items are from the same model foreach (Group *group, m_rowTable) foreach (const Item &item, group->filtered) if (item.sourceRow().first != hideModel) { return false; } } return doHide; } static inline bool matchesAbbreviationHelper(const QString &word, const QString &typed, const QVarLengthArray &offsets, int &depth, int atWord = -1, int i = 0) { int atLetter = 1; for (; i < typed.size(); i++) { const QChar c = typed.at(i).toLower(); bool haveNextWord = offsets.size() > atWord + 1; bool canCompare = atWord != -1 && word.size() > offsets.at(atWord) + atLetter; if (canCompare && c == word.at(offsets.at(atWord) + atLetter).toLower()) { // the typed letter matches a letter after the current word beginning if (! haveNextWord || c != word.at(offsets.at(atWord + 1)).toLower()) { // good, simple case, no conflict atLetter += 1; continue; } // For maliciously crafted data, the code used here theoretically can have very high // complexity. Thus ensure we don't run into this case, by limiting the amount of branches // we walk through to 128. depth++; if (depth > 128) { return false; } // the letter matches both the next word beginning and the next character in the word if (haveNextWord && matchesAbbreviationHelper(word, typed, offsets, depth, atWord + 1, i + 1)) { // resolving the conflict by taking the next word's first character worked, fine return true; } // otherwise, continue by taking the next letter in the current word. atLetter += 1; continue; } else if (haveNextWord && c == word.at(offsets.at(atWord + 1)).toLower()) { // the typed letter matches the next word beginning atWord++; atLetter = 1; continue; } // no match return false; } // all characters of the typed word were matched return true; } bool KateCompletionModel::matchesAbbreviation(const QString &word, const QString &typed) { // A mismatch is very likely for random even for the first letter, // thus this optimization makes sense. if (word.at(0).toLower() != typed.at(0).toLower()) { return false; } // First, check if all letters are contained in the word in the right order. int atLetter = 0; foreach (const QChar c, typed) { while (c.toLower() != word.at(atLetter).toLower()) { atLetter += 1; if (atLetter >= word.size()) { return false; } } } bool haveUnderscore = true; QVarLengthArray offsets; // We want to make "KComplM" match "KateCompletionModel"; this means we need // to allow parts of the typed text to be not part of the actual abbreviation, // which consists only of the uppercased / underscored letters (so "KCM" in this case). // However it might be ambigous whether a letter is part of such a word or part of // the following abbreviation, so we need to find all possible word offsets first, // then compare. for (int i = 0; i < word.size(); i++) { const QChar c = word.at(i); if (c == QLatin1Char('_')) { haveUnderscore = true; } else if (haveUnderscore || c.isUpper()) { offsets.append(i); haveUnderscore = false; } } int depth = 0; return matchesAbbreviationHelper(word, typed, offsets, depth); } static inline bool containsAtWordBeginning(const QString &word, const QString &typed, Qt::CaseSensitivity caseSensitive) { for (int i = 1; i < word.size(); i++) { // The current position is a word beginning if the previous character was an underscore // or if the current character is uppercase. Subsequent uppercase characters do not count, // to handle the special case of UPPER_CASE_VARS properly. const QChar c = word.at(i); const QChar prev = word.at(i - 1); if (!(prev == QLatin1Char('_') || (c.isUpper() && !prev.isUpper()))) { continue; } if (word.midRef(i).startsWith(typed, caseSensitive)) { return true; } } return false; } KateCompletionModel::Item::MatchType KateCompletionModel::Item::match() { QString match = model->currentCompletion(m_sourceRow.first); m_haveExactMatch = false; // Hehe, everything matches nothing! (ie. everything matches a blank string) if (match.isEmpty()) { return PerfectMatch; } if (m_nameColumn.isEmpty()) { return NoMatch; } matchCompletion = (m_nameColumn.startsWith(match, model->matchCaseSensitivity()) ? StartsWithMatch : NoMatch); if (matchCompletion == NoMatch) { // if no match, try for "contains" // Only match when the occurence is at a "word" beginning, marked by // an underscore or a capital. So Foo matches BarFoo and Bar_Foo, but not barfoo. // Starting at 1 saves looking at the beginning of the word, that was already checked above. if (containsAtWordBeginning(m_nameColumn, match, model->matchCaseSensitivity())) { matchCompletion = ContainsMatch; } } if (matchCompletion == NoMatch && !m_nameColumn.isEmpty() && !match.isEmpty()) { // if still no match, try abbreviation matching if (matchesAbbreviation(m_nameColumn, match)) { matchCompletion = AbbreviationMatch; } } if (matchCompletion && match.length() == m_nameColumn.length()) { matchCompletion = PerfectMatch; m_haveExactMatch = true; } return matchCompletion; } QString KateCompletionModel::propertyName(KTextEditor::CodeCompletionModel::CompletionProperty property) { switch (property) { case CodeCompletionModel::Public: return i18n("Public"); case CodeCompletionModel::Protected: return i18n("Protected"); case CodeCompletionModel::Private: return i18n("Private"); case CodeCompletionModel::Static: return i18n("Static"); case CodeCompletionModel::Const: return i18n("Constant"); case CodeCompletionModel::Namespace: return i18n("Namespace"); case CodeCompletionModel::Class: return i18n("Class"); case CodeCompletionModel::Struct: return i18n("Struct"); case CodeCompletionModel::Union: return i18n("Union"); case CodeCompletionModel::Function: return i18n("Function"); case CodeCompletionModel::Variable: return i18n("Variable"); case CodeCompletionModel::Enum: return i18n("Enumeration"); case CodeCompletionModel::Template: return i18n("Template"); case CodeCompletionModel::Virtual: return i18n("Virtual"); case CodeCompletionModel::Override: return i18n("Override"); case CodeCompletionModel::Inline: return i18n("Inline"); case CodeCompletionModel::Friend: return i18n("Friend"); case CodeCompletionModel::Signal: return i18n("Signal"); case CodeCompletionModel::Slot: return i18n("Slot"); case CodeCompletionModel::LocalScope: return i18n("Local Scope"); case CodeCompletionModel::NamespaceScope: return i18n("Namespace Scope"); case CodeCompletionModel::GlobalScope: return i18n("Global Scope"); default: return i18n("Unknown Property"); } } bool KateCompletionModel::Item::isVisible() const { return matchCompletion && matchFilters; } bool KateCompletionModel::Item::isFiltered() const { return !matchFilters; } bool KateCompletionModel::Item::isMatching() const { return matchFilters; } const KateCompletionModel::ModelRow &KateCompletionModel::Item::sourceRow() const { return m_sourceRow; } QString KateCompletionModel::currentCompletion(KTextEditor::CodeCompletionModel *model) const { return m_currentMatch.value(model); } Qt::CaseSensitivity KateCompletionModel::matchCaseSensitivity() const { return m_matchCaseSensitivity; } void KateCompletionModel::addCompletionModel(KTextEditor::CodeCompletionModel *model) { if (m_completionModels.contains(model)) { return; } m_completionModels.append(model); connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(slotRowsInserted(QModelIndex,int,int))); connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(slotRowsRemoved(QModelIndex,int,int))); connect(model, SIGNAL(modelReset()), SLOT(slotModelReset())); // This performs the reset createGroups(); } void KateCompletionModel::setCompletionModel(KTextEditor::CodeCompletionModel *model) { clearCompletionModels(); addCompletionModel(model); } void KateCompletionModel::setCompletionModels(const QList &models) { //if (m_completionModels == models) //return; clearCompletionModels(); m_completionModels = models; foreach (KTextEditor::CodeCompletionModel *model, models) { connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(slotRowsInserted(QModelIndex,int,int))); connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(slotRowsRemoved(QModelIndex,int,int))); connect(model, SIGNAL(modelReset()), SLOT(slotModelReset())); } // This performs the reset createGroups(); } QList< KTextEditor::CodeCompletionModel * > KateCompletionModel::completionModels() const { return m_completionModels; } void KateCompletionModel::removeCompletionModel(CodeCompletionModel *model) { if (!model || !m_completionModels.contains(model)) { return; } beginResetModel(); m_currentMatch.remove(model); clearGroups(); model->disconnect(this); m_completionModels.removeAll(model); endResetModel(); if (!m_completionModels.isEmpty()) { // This performs the reset createGroups(); } } void KateCompletionModel::makeGroupItemsUnique(bool onlyFiltered) { struct FilterItems { FilterItems(KateCompletionModel &model, const QVector &needShadowing) : m_model(model), m_needShadowing(needShadowing) { } QHash had; KateCompletionModel &m_model; const QVector< KTextEditor::CodeCompletionModel * > m_needShadowing; void filter(QList &items) { QList temp; foreach (const Item &item, items) { QHash::const_iterator it = had.constFind(item.name()); if (it != had.constEnd() && *it != item.sourceRow().first && m_needShadowing.contains(item.sourceRow().first)) { continue; } had.insert(item.name(), item.sourceRow().first); temp.push_back(item); } items = temp; } void filter(Group *group, bool onlyFiltered) { if (group->prefilter.size() == group->filtered.size()) { // Filter only once filter(group->filtered); if (!onlyFiltered) { group->prefilter = group->filtered; } } else { // Must filter twice filter(group->filtered); if (!onlyFiltered) { filter(group->prefilter); } } if (group->filtered.isEmpty()) { m_model.hideOrShowGroup(group); } } }; QVector needShadowing; foreach (KTextEditor::CodeCompletionModel *model, m_completionModels) { KTextEditor::CodeCompletionModelControllerInterface *v4 = dynamic_cast(model); if (v4 && v4->shouldHideItemsWithEqualNames()) { needShadowing.push_back(model); } } if (needShadowing.isEmpty()) { return; } FilterItems filter(*this, needShadowing); filter.filter(m_ungrouped, onlyFiltered); foreach (Group *group, m_rowTable) { filter.filter(group, onlyFiltered); } } //Updates the best-matches group void KateCompletionModel::updateBestMatches() { int maxMatches = 300; //We cannot do too many operations here, because they are all executed whenever a character is added. Would be nice if we could split the operations up somewhat using a timer. m_updateBestMatchesTimer->stop(); //Maps match-qualities to ModelRows paired together with the BestMatchesCount returned by the items. typedef QMultiMap > BestMatchMap; BestMatchMap matches; if (!hasGroups()) { //If there is no grouping, just change the order of the items, moving the best matching ones to the front QMultiMap rowsForQuality; int row = 0; foreach (const Item &item, m_ungrouped->filtered) { ModelRow source = item.sourceRow(); QVariant v = source.second.data(CodeCompletionModel::BestMatchesCount); if (v.type() == QVariant::Int && v.toInt() > 0) { int quality = contextMatchQuality(source); if (quality > 0) { rowsForQuality.insert(quality, row); } } ++row; --maxMatches; if (maxMatches < 0) { break; } } if (!rowsForQuality.isEmpty()) { //Rewrite m_ungrouped->filtered in a new order QSet movedToFront; QList newFiltered; for (QMultiMap::const_iterator it = rowsForQuality.constBegin(); it != rowsForQuality.constEnd(); ++it) { newFiltered.prepend(m_ungrouped->filtered[it.value()]); movedToFront.insert(it.value()); } { - uint size = m_ungrouped->filtered.size(); - for (uint a = 0; a < size; ++a) + int size = m_ungrouped->filtered.size(); + for (int a = 0; a < size; ++a) if (!movedToFront.contains(a)) { newFiltered.append(m_ungrouped->filtered[a]); } } m_ungrouped->filtered = newFiltered; } return; } ///@todo Cache the CodeCompletionModel::BestMatchesCount foreach (Group *g, m_rowTable) { if (g == m_bestMatches) { continue; } for (int a = 0; a < g->filtered.size(); a++) { ModelRow source = g->filtered[a].sourceRow(); QVariant v = source.second.data(CodeCompletionModel::BestMatchesCount); if (v.type() == QVariant::Int && v.toInt() > 0) { //Return the best match with any of the argument-hints int quality = contextMatchQuality(source); if (quality > 0) { matches.insert(quality, qMakePair(v.toInt(), g->filtered[a].sourceRow())); } --maxMatches; } if (maxMatches < 0) { break; } } if (maxMatches < 0) { break; } } //Now choose how many of the matches will be taken. This is done with the rule: //The count of shown best-matches should equal the average count of their BestMatchesCounts int cnt = 0; int matchesSum = 0; BestMatchMap::const_iterator it = matches.constEnd(); while (it != matches.constBegin()) { --it; ++cnt; matchesSum += (*it).first; if (cnt > matchesSum / cnt) { break; } } m_bestMatches->filtered.clear(); it = matches.constEnd(); while (it != matches.constBegin() && cnt > 0) { --it; --cnt; m_bestMatches->filtered.append(Item(true, this, HierarchicalModelHandler((*it).second.first), (*it).second)); } hideOrShowGroup(m_bestMatches); } void KateCompletionModel::rowSelected(const QModelIndex &row) { ExpandingWidgetModel::rowSelected(row); ///@todo delay this int rc = widget()->argumentHintModel()->rowCount(QModelIndex()); if (rc == 0) { return; } //For now, simply update the whole column 0 QModelIndex start = widget()->argumentHintModel()->index(0, 0); QModelIndex end = widget()->argumentHintModel()->index(rc - 1, 0); widget()->argumentHintModel()->emitDataChanged(start, end); } void KateCompletionModel::clearCompletionModels() { if (m_completionModels.isEmpty()) { return; } beginResetModel(); foreach (CodeCompletionModel *model, m_completionModels) { model->disconnect(this); } m_completionModels.clear(); m_currentMatch.clear(); clearGroups(); endResetModel(); } diff --git a/src/completion/katecompletionmodel.h b/src/completion/katecompletionmodel.h index 36eda387..65b0033b 100644 --- a/src/completion/katecompletionmodel.h +++ b/src/completion/katecompletionmodel.h @@ -1,417 +1,417 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2005-2006 Hamish Rodda * Copyright (C) 2007-2008 David Nolden * * 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 KATECOMPLETIONMODEL_H #define KATECOMPLETIONMODEL_H #include #include #include #include #include #include "expandingtree/expandingwidgetmodel.h" class KateCompletionWidget; class KateArgumentHintModel; namespace KTextEditor { class ViewPrivate; } class QWidget; class QTextEdit; class QTimer; class HierarchicalModelHandler; /** * This class has the responsibility for filtering, sorting, and manipulating * code completion data provided by a CodeCompletionModel. * * @author Hamish Rodda */ class KTEXTEDITOR_EXPORT KateCompletionModel : public ExpandingWidgetModel { Q_OBJECT public: explicit KateCompletionModel(KateCompletionWidget *parent = nullptr); - ~KateCompletionModel(); + ~KateCompletionModel() override; QList completionModels() const; void clearCompletionModels(); void addCompletionModel(KTextEditor::CodeCompletionModel *model); void setCompletionModel(KTextEditor::CodeCompletionModel *model); void setCompletionModels(const QList &models); void removeCompletionModel(KTextEditor::CodeCompletionModel *model); KTextEditor::ViewPrivate *view() const; KateCompletionWidget *widget() const; QString currentCompletion(KTextEditor::CodeCompletionModel *model) const; void setCurrentCompletion(KTextEditor::CodeCompletionModel *model, const QString &completion); Qt::CaseSensitivity matchCaseSensitivity() const; void setMatchCaseSensitivity(Qt::CaseSensitivity cs); static QString columnName(int column); int translateColumn(int sourceColumn) const; static QString propertyName(KTextEditor::CodeCompletionModel::CompletionProperty property); ///Returns a common prefix for all current visible completion entries ///If there is no common prefix, extracts the next useful prefix for the selected index QString commonPrefix(QModelIndex selectedIndex) const; - void rowSelected(const QModelIndex &row) Q_DECL_OVERRIDE; + void rowSelected(const QModelIndex &row) override; - bool indexIsItem(const QModelIndex &index) const Q_DECL_OVERRIDE; + bool indexIsItem(const QModelIndex &index) const override; - int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; - Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE; - bool hasChildren(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + bool hasChildren(const QModelIndex &parent = QModelIndex()) const override; virtual bool hasIndex(int row, int column, const QModelIndex &parent = QModelIndex()) const; - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; // Disabled in case of bugs, reenable once fully debugged. //virtual QMap itemData ( const QModelIndex & index ) const; - QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE; - int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; + QModelIndex parent(const QModelIndex &index) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; // Disabled in case of bugs, reenable once fully debugged. //virtual QModelIndex sibling ( int row, int column, const QModelIndex & index ) const; - void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) Q_DECL_OVERRIDE; + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override; ///Maps from this display-model into the appropriate source code-completion model virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const; ///Maps from an index in a source-model to the index of the item in this display-model virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const; // Sorting bool isSortingEnabled() const; bool isSortingAlphabetical() const; bool isSortingByInheritanceDepth() const; void setSortingByInheritanceDepth(bool byIneritance); void setSortingAlphabetical(bool alphabetical); Qt::CaseSensitivity sortingCaseSensitivity() const; void setSortingCaseSensitivity(Qt::CaseSensitivity cs); bool isSortingReverse() const; void setSortingReverse(bool reverse); // Filtering bool isFilteringEnabled() const; bool filterContextMatchesOnly() const; void setFilterContextMatchesOnly(bool filter); bool filterByAttribute() const; void setFilterByAttribute(bool filter); KTextEditor::CodeCompletionModel::CompletionProperties filterAttributes() const; void setFilterAttributes(KTextEditor::CodeCompletionModel::CompletionProperties attributes); // A maximum depth of <= 0 equals don't filter by inheritance depth (i.e. infinity) and is default int maximumInheritanceDepth() const; void setMaximumInheritanceDepth(int maxDepth); // Grouping bool isGroupingEnabled() const; enum gm { ScopeType = 0x1, Scope = 0x2, AccessType = 0x4, ItemType = 0x8 }; enum { //An own property that will be used to mark the best-matches group internally BestMatchesProperty = 2 * KTextEditor::CodeCompletionModel::LastProperty }; Q_DECLARE_FLAGS(GroupingMethods, gm) static const int ScopeTypeMask = 0x380000; static const int AccessTypeMask = 0x7; static const int ItemTypeMask = 0xfe0; GroupingMethods groupingMethod() const; void setGroupingMethod(GroupingMethods m); bool accessIncludeConst() const; void setAccessIncludeConst(bool include); bool accessIncludeStatic() const; void setAccessIncludeStatic(bool include); bool accessIncludeSignalSlot() const; void setAccessIncludeSignalSlot(bool include); // Column merging bool isColumnMergingEnabled() const; const QList< QList > &columnMerges() const; void setColumnMerges(const QList< QList > &columnMerges); void debugStats(); ///Returns whether one of the filtered items exactly matches its completion string bool shouldMatchHideCompletionList() const; uint filteredItemCount() const; protected: - int contextMatchQuality(const QModelIndex &index) const Q_DECL_OVERRIDE; + int contextMatchQuality(const QModelIndex &index) const override; Q_SIGNALS: void expandIndex(const QModelIndex &index); //Emitted whenever something has changed about the group of argument-hints void argumentHintsChanged(); public Q_SLOTS: void setSortingEnabled(bool enable); void setFilteringEnabled(bool enable); void setGroupingEnabled(bool enable); void setColumnMergingEnabled(bool enable); private Q_SLOTS: void slotRowsInserted(const QModelIndex &parent, int start, int end); void slotRowsRemoved(const QModelIndex &parent, int start, int end); void slotModelReset(); //Updates the best-matches group void updateBestMatches(); //Makes sure that the ungrouped group contains each item only once //Must only be called right after the group was created void makeGroupItemsUnique(bool onlyFiltered = false); private: typedef QPair ModelRow; virtual int contextMatchQuality(const ModelRow &sourceRow) const; - QTreeView *treeView() const Q_DECL_OVERRIDE; + QTreeView *treeView() const override; friend class KateArgumentHintModel; ModelRow modelRowPair(const QModelIndex &index) const; // Represents a source row; provides sorting method class Item { public: Item(bool doInitialMatch, KateCompletionModel *model, const HierarchicalModelHandler &handler, ModelRow sourceRow); bool isValid() const; // Returns true if the item is not filtered and matches the current completion string bool isVisible() const; // Returns whether the item is filtered or not bool isFiltered() const; // Returns whether the item matches the current completion string bool isMatching() const; bool filter(); enum MatchType { NoMatch = 0, PerfectMatch, StartsWithMatch, AbbreviationMatch, ContainsMatch }; MatchType match(); const ModelRow &sourceRow() const; // Sorting operator bool operator<(const Item &rhs) const; bool haveExactMatch() const { return m_haveExactMatch; } void clearExactMatch() { m_haveExactMatch = false; } QString name() const { return m_nameColumn; } private: KateCompletionModel *model; ModelRow m_sourceRow; mutable QString m_nameColumn; int inheritanceDepth; // True when currently matching completion string MatchType matchCompletion; // True when passes all active filters bool matchFilters; bool m_haveExactMatch; bool m_unimportant; QString completionSortingName() const; }; public: // Grouping and sorting of rows class Group { public: explicit Group(const QString& title, int attribute, KateCompletionModel *model); void addItem(Item i, bool notifyModel = false); /// Removes the item specified by \a row. Returns true if a change was made to rows. bool removeItem(const ModelRow &row); void resort(); void refilter(); void clear(); //Returns whether this group should be ordered before other bool orderBefore(Group *other) const; //Returns a number that can be used for ordering int orderNumber() const; ///Returns the row in the this group's filtered list of the given model-row in a source-model ///-1 if the item is not in the filtered list ///@todo Implement an efficient way of doing this map, that does _not_ iterate over all items! int rowOf(ModelRow item) { for (int a = 0; a < filtered.size(); ++a) if (filtered[a].sourceRow() == item) { return a; } return -1; } KateCompletionModel *model; int attribute; QString title, scope; QList filtered; QList prefilter; bool isEmpty; //-1 if none was set int customSortingKey; }; bool hasGroups() const; private: QString commonPrefixInternal(const QString &forcePrefix) const; /// @note performs model reset void createGroups(); ///Creates all sub-items of index i, or the item corresponding to index i. Returns the affected groups. ///i must be an index in the source model QSet createItems(const HierarchicalModelHandler &, const QModelIndex &i, bool notifyModel = false); ///Deletes all sub-items of index i, or the item corresponding to index i. Returns the affected groups. ///i must be an index in the source model QSet deleteItems(const QModelIndex &i); Group *createItem(const HierarchicalModelHandler &, const QModelIndex &i, bool notifyModel = false); /// @note Make sure you're in a {begin,end}ResetModel block when calling this! void clearGroups(); void hideOrShowGroup(Group *g, bool notifyModel = false); /// When forceGrouping is enabled, all given attributes will be used for grouping, regardless of the completion settings. Group *fetchGroup(int attribute, const QString &scope = QString(), bool forceGrouping = false); //If this returns nonzero on an index, the index is the header of the returned group Group *groupForIndex(const QModelIndex &index) const; inline Group *groupOfParent(const QModelIndex &child) const { return static_cast(child.internalPointer()); } QModelIndex indexForRow(Group *g, int row) const; QModelIndex indexForGroup(Group *g) const; enum changeTypes { Broaden, Narrow, Change }; //Returns whether the model needs to be reset void changeCompletions(Group *g, changeTypes changeType, bool notifyModel); bool hasCompletionModel() const; /// Removes attributes not used in grouping from the input \a attribute int groupingAttributes(int attribute) const; int countBits(int value) const; void resort(); void refilter(); static bool matchesAbbreviation(const QString &word, const QString &typed); - bool m_hasGroups; + bool m_hasGroups = false; // ### Runtime state // General QList m_completionModels; QMap m_currentMatch; - Qt::CaseSensitivity m_matchCaseSensitivity; + Qt::CaseSensitivity m_matchCaseSensitivity = Qt::CaseInsensitive; // Column merging QList< QList > m_columnMerges; QTimer *m_updateBestMatchesTimer; Group *m_ungrouped; Group *m_argumentHints; //The argument-hints will be passed on to another model, to be shown in another widget Group *m_bestMatches; //A temporary group used for holding the best matches of all visible items // Storing the sorted order QList m_rowTable; QList m_emptyGroups; // Quick access to each specific group (if it exists) QMultiHash m_groupHash; // Maps custom group-names to their specific groups QHash m_customGroupHash; // ### Configurable state // Sorting - bool m_sortingEnabled; - bool m_sortingAlphabetical; - bool m_isSortingByInheritance; - Qt::CaseSensitivity m_sortingCaseSensitivity; + bool m_sortingEnabled = false; + bool m_sortingAlphabetical = false; + bool m_isSortingByInheritance = false; + Qt::CaseSensitivity m_sortingCaseSensitivity = Qt::CaseInsensitive; QHash< int, QList > m_sortingGroupingOrder; // Filtering - bool m_filteringEnabled; - bool m_filterContextMatchesOnly; - bool m_filterByAttribute; + bool m_filteringEnabled = false; + bool m_filterContextMatchesOnly = false; + bool m_filterByAttribute = false; KTextEditor::CodeCompletionModel::CompletionProperties m_filterAttributes; - int m_maximumInheritanceDepth; + int m_maximumInheritanceDepth = 0; // Grouping - bool m_groupingEnabled; + bool m_groupingEnabled = false; GroupingMethods m_groupingMethod; - bool m_accessConst, m_accessStatic, m_accesSignalSlot; + bool m_accessConst = false, m_accessStatic = false, m_accesSignalSlot = false; // Column merging - bool m_columnMergingEnabled/*, m_haveExactMatch*/; + bool m_columnMergingEnabled = false/*, m_haveExactMatch*/; friend class CompletionTest; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KateCompletionModel::GroupingMethods) #endif diff --git a/src/completion/katecompletiontree.h b/src/completion/katecompletiontree.h index 12c35669..207da277 100644 --- a/src/completion/katecompletiontree.h +++ b/src/completion/katecompletiontree.h @@ -1,71 +1,71 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2006 Hamish Rodda * Copyright (C) 2007-2008 David Nolden * * 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 KATECOMPLETIONTREE_H #define KATECOMPLETIONTREE_H #include "expandingtree/expandingtree.h" class KateCompletionWidget; class KateCompletionModel; class QTimer; class KateCompletionTree : public ExpandingTree { Q_OBJECT public: explicit KateCompletionTree(KateCompletionWidget *parent); KateCompletionWidget *widget() const; KateCompletionModel *kateModel() const; void resizeColumns(bool firstShow = false, bool forceResize = false); // Navigation bool nextCompletion(); bool previousCompletion(); bool pageDown(); bool pageUp(); void top(); void bottom(); void scheduleUpdate(); void setScrollingEnabled(bool); /// Returns the approximated viewport position of the text in the given column, skipping an eventual icon int columnTextViewportPosition(int column) const; private Q_SLOTS: void resizeColumnsSlot(); protected: - void currentChanged(const QModelIndex ¤t, const QModelIndex &previous) Q_DECL_OVERRIDE; ///Not available as a signal in this way - void scrollContentsBy(int dx, int dy) Q_DECL_OVERRIDE; - QStyleOptionViewItem viewOptions() const Q_DECL_OVERRIDE; + void currentChanged(const QModelIndex ¤t, const QModelIndex &previous) override; ///Not available as a signal in this way + void scrollContentsBy(int dx, int dy) override; + QStyleOptionViewItem viewOptions() const override; private: bool m_scrollingEnabled; QTimer *m_resizeTimer; }; #endif diff --git a/src/completion/katecompletionwidget.cpp b/src/completion/katecompletionwidget.cpp index 2107540f..aa2af465 100644 --- a/src/completion/katecompletionwidget.cpp +++ b/src/completion/katecompletionwidget.cpp @@ -1,1474 +1,1487 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2005-2006 Hamish Rodda * Copyright (C) 2007-2008 David Nolden * * 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 "katecompletionwidget.h" #include #include "kateview.h" #include "katerenderer.h" #include "kateconfig.h" #include "katedocument.h" #include "katebuffer.h" #include "katecompletionmodel.h" #include "katecompletiontree.h" #include "katecompletionconfig.h" #include "kateargumenthinttree.h" #include "kateargumenthintmodel.h" #include "katepartdebug.h" #include #include #include #include #include #include #include #include #include #include #include #include const bool hideAutomaticCompletionOnExactMatch = true; //If this is true, the completion-list is navigated up/down when 'tab' is pressed, instead of doing partial completion const bool shellLikeTabCompletion = false; #define CALLCI(WHAT,WHATELSE,WHAT2,model,FUNC) \ {\ static KTextEditor::CodeCompletionModelControllerInterface defaultIf;\ KTextEditor::CodeCompletionModelControllerInterface* ret =\ dynamic_cast(model);\ if (!ret) {\ WHAT2 defaultIf.FUNC;\ }else \ WHAT2 ret->FUNC;\ } static KTextEditor::Range _completionRange(KTextEditor::CodeCompletionModel *model, KTextEditor::View *view, const KTextEditor::Cursor &cursor) { CALLCI(return,, return, model, completionRange(view, cursor)); } static KTextEditor::Range _updateRange(KTextEditor::CodeCompletionModel *model, KTextEditor::View *view, KTextEditor::Range &range) { CALLCI(, return range, return, model, updateCompletionRange(view, range)); } static QString _filterString(KTextEditor::CodeCompletionModel *model, KTextEditor::View *view, const KTextEditor::Range &range, const KTextEditor::Cursor &cursor) { CALLCI(return,, return, model, filterString(view, range, cursor)); } static bool _shouldAbortCompletion(KTextEditor::CodeCompletionModel *model, KTextEditor::View *view, const KTextEditor::Range &range, const QString ¤tCompletion) { CALLCI(return,, return, model, shouldAbortCompletion(view, range, currentCompletion)); } static void _aborted(KTextEditor::CodeCompletionModel *model, KTextEditor::View *view) { CALLCI(return,, return, model, aborted(view)); } static bool _shouldStartCompletion(KTextEditor::CodeCompletionModel *model, KTextEditor::View *view, QString m_automaticInvocationLine, bool m_lastInsertionByUser, const KTextEditor::Cursor &cursor) { CALLCI(return,, return, model, shouldStartCompletion(view, m_automaticInvocationLine, m_lastInsertionByUser, cursor)); } KateCompletionWidget::KateCompletionWidget(KTextEditor::ViewPrivate *parent) : QFrame(parent, Qt::ToolTip) , m_presentationModel(new KateCompletionModel(this)) , m_entryList(new KateCompletionTree(this)) , m_argumentHintModel(new KateArgumentHintModel(this)) , m_argumentHintTree(new KateArgumentHintTree(this)) , m_automaticInvocationDelay(100) , m_filterInstalled(false) , m_configWidget(new KateCompletionConfig(m_presentationModel, view())) , m_lastInsertionByUser(false) , m_inCompletionList(false) , m_isSuspended(false) , m_dontShowArgumentHints(false) , m_needShow(false) , m_hadCompletionNavigation(false) , m_noAutoHide(false) , m_completionEditRunning(false) , m_expandedAddedHeightBase(0) , m_lastInvocationType(KTextEditor::CodeCompletionModel::AutomaticInvocation) { connect(parent, SIGNAL(navigateAccept()), SLOT(navigateAccept())); connect(parent, SIGNAL(navigateBack()), SLOT(navigateBack())); connect(parent, SIGNAL(navigateDown()), SLOT(navigateDown())); connect(parent, SIGNAL(navigateLeft()), SLOT(navigateLeft())); connect(parent, SIGNAL(navigateRight()), SLOT(navigateRight())); connect(parent, SIGNAL(navigateUp()), SLOT(navigateUp())); setFrameStyle(QFrame::Box | QFrame::Plain); setLineWidth(1); //setWindowOpacity(0.8); m_entryList->setModel(m_presentationModel); m_entryList->setColumnWidth(0, 0); //These will be determined automatically in KateCompletionTree::resizeColumns m_entryList->setColumnWidth(1, 0); m_entryList->setColumnWidth(2, 0); m_entryList->setVerticalScrollMode(QAbstractItemView::ScrollPerItem); m_argumentHintTree->setParent(nullptr, Qt::ToolTip); m_argumentHintTree->setModel(m_argumentHintModel); // trigger completion on double click on completion list connect(m_entryList, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(execute())); connect(m_entryList->verticalScrollBar(), SIGNAL(valueChanged(int)), m_presentationModel, SLOT(placeExpandingWidgets())); connect(m_argumentHintTree->verticalScrollBar(), SIGNAL(valueChanged(int)), m_argumentHintModel, SLOT(placeExpandingWidgets())); connect(view(), SIGNAL(focusOut(KTextEditor::View*)), this, SLOT(viewFocusOut())); m_automaticInvocationTimer = new QTimer(this); m_automaticInvocationTimer->setSingleShot(true); connect(m_automaticInvocationTimer, SIGNAL(timeout()), this, SLOT(automaticInvocation())); // Keep branches expanded connect(m_presentationModel, SIGNAL(modelReset()), this, SLOT(modelReset())); connect(m_presentationModel, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(rowsInserted(QModelIndex,int,int))); connect(m_argumentHintModel, SIGNAL(contentStateChanged(bool)), this, SLOT(argumentHintsChanged(bool))); // No smart lock, no queued connects connect(view(), SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(cursorPositionChanged())); connect(view(), SIGNAL(verticalScrollPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(updatePositionSlot())); /** * connect to all possible editing primitives */ connect(&view()->doc()->buffer(), SIGNAL(lineWrapped(KTextEditor::Cursor)), this, SLOT(wrapLine(KTextEditor::Cursor))); connect(&view()->doc()->buffer(), SIGNAL(lineUnwrapped(int)), this, SLOT(unwrapLine(int))); connect(&view()->doc()->buffer(), SIGNAL(textInserted(KTextEditor::Cursor,QString)), this, SLOT(insertText(KTextEditor::Cursor,QString))); connect(&view()->doc()->buffer(), SIGNAL(textRemoved(KTextEditor::Range,QString)), this, SLOT(removeText(KTextEditor::Range))); // This is a non-focus widget, it is passed keyboard input from the view //We need to do this, because else the focus goes to nirvana without any control when the completion-widget is clicked. setFocusPolicy(Qt::ClickFocus); m_argumentHintTree->setFocusPolicy(Qt::ClickFocus); foreach (QWidget *childWidget, findChildren()) { childWidget->setFocusPolicy(Qt::NoFocus); } //Position the entry-list so a frame can be drawn around it m_entryList->move(frameWidth(), frameWidth()); } KateCompletionWidget::~KateCompletionWidget() { } void KateCompletionWidget::viewFocusOut() { if (QApplication::focusWidget() != this) { abortCompletion(); } } void KateCompletionWidget::focusOutEvent(QFocusEvent *) { abortCompletion(); } void KateCompletionWidget::modelContentChanged() { ////qCDebug(LOG_KTE)<<">>>>>>>>>>>>>>>>"; if (m_completionRanges.isEmpty()) { //qCDebug(LOG_KTE) << "content changed, but no completion active"; abortCompletion(); return; } if (!view()->hasFocus()) { //qCDebug(LOG_KTE) << "view does not have focus"; return; } if (!m_waitingForReset.isEmpty()) { //qCDebug(LOG_KTE) << "waiting for" << m_waitingForReset.size() << "completion-models to reset"; return; } int realItemCount = 0; foreach (KTextEditor::CodeCompletionModel *model, m_presentationModel->completionModels()) { realItemCount += model->rowCount(); } if (!m_isSuspended && ((isHidden() && m_argumentHintTree->isHidden()) || m_needShow) && realItemCount != 0) { m_needShow = false; updateAndShow(); } if (m_argumentHintModel->rowCount(QModelIndex()) == 0) { m_argumentHintTree->hide(); } if (m_presentationModel->rowCount(QModelIndex()) == 0) { hide(); } //With each filtering items can be added or removed, so we have to reset the current index here so we always have a selected item m_entryList->setCurrentIndex(model()->index(0, 0)); if (!model()->indexIsItem(m_entryList->currentIndex())) { QModelIndex firstIndex = model()->index(0, 0, m_entryList->currentIndex()); m_entryList->setCurrentIndex(firstIndex); //m_entryList->scrollTo(firstIndex, QAbstractItemView::PositionAtTop); } updateHeight(); //New items for the argument-hint tree may have arrived, so check whether it needs to be shown if (m_argumentHintTree->isHidden() && !m_dontShowArgumentHints && m_argumentHintModel->rowCount(QModelIndex()) != 0) { m_argumentHintTree->show(); } if (!m_noAutoHide && hideAutomaticCompletionOnExactMatch && !isHidden() && m_lastInvocationType == KTextEditor::CodeCompletionModel::AutomaticInvocation && m_presentationModel->shouldMatchHideCompletionList()) { hide(); } else if (isHidden() && !m_presentationModel->shouldMatchHideCompletionList() && m_presentationModel->rowCount(QModelIndex())) { show(); } } KateArgumentHintTree *KateCompletionWidget::argumentHintTree() const { return m_argumentHintTree; } KateArgumentHintModel *KateCompletionWidget::argumentHintModel() const { return m_argumentHintModel; } const KateCompletionModel *KateCompletionWidget::model() const { return m_presentationModel; } KateCompletionModel *KateCompletionWidget::model() { return m_presentationModel; } void KateCompletionWidget::rowsInserted(const QModelIndex &parent, int rowFrom, int rowEnd) { m_entryList->setAnimated(false); if (!model()->isGroupingEnabled()) { return; } if (!parent.isValid()) for (int i = rowFrom; i <= rowEnd; ++i) { m_entryList->expand(m_presentationModel->index(i, 0, parent)); } } KTextEditor::ViewPrivate *KateCompletionWidget::view() const { return static_cast(const_cast(parent())); } void KateCompletionWidget::argumentHintsChanged(bool hasContent) { m_dontShowArgumentHints = !hasContent; if (m_dontShowArgumentHints) { m_argumentHintTree->hide(); } else { updateArgumentHintGeometry(); } } void KateCompletionWidget::startCompletion(KTextEditor::CodeCompletionModel::InvocationType invocationType, const QList &models) { if (invocationType == KTextEditor::CodeCompletionModel::UserInvocation) { abortCompletion(); } startCompletion(KTextEditor::Range(KTextEditor::Cursor(-1, -1), KTextEditor::Cursor(-1, -1)), models, invocationType); } void KateCompletionWidget::deleteCompletionRanges() { ////qCDebug(LOG_KTE); foreach (const CompletionRange &r, m_completionRanges) { delete r.range; } m_completionRanges.clear(); } void KateCompletionWidget::startCompletion(const KTextEditor::Range &word, KTextEditor::CodeCompletionModel *model, KTextEditor::CodeCompletionModel::InvocationType invocationType) { QList models; if (model) { models << model; } else { models = m_sourceModels; } startCompletion(word, models, invocationType); } void KateCompletionWidget::startCompletion(const KTextEditor::Range &word, const QList &modelsToStart, KTextEditor::CodeCompletionModel::InvocationType invocationType) { ////qCDebug(LOG_KTE)<<"============"; m_isSuspended = false; m_inCompletionList = true; //Always start at the top of the completion-list m_needShow = true; if (m_completionRanges.isEmpty()) { m_noAutoHide = false; //Re-enable auto-hide on every clean restart of the completion } m_lastInvocationType = invocationType; disconnect(this->model(), SIGNAL(layoutChanged()), this, SLOT(modelContentChanged())); disconnect(this->model(), SIGNAL(modelReset()), this, SLOT(modelContentChanged())); m_dontShowArgumentHints = true; QList models = (modelsToStart.isEmpty() ? m_sourceModels : modelsToStart); foreach (KTextEditor::CodeCompletionModel *model, m_completionRanges.keys()) if (!models.contains(model)) { models << model; } if (!m_filterInstalled) { if (!QApplication::activeWindow()) { qCWarning(LOG_KTE) << "No active window to install event filter on!!"; return; } // Enable the cc box to move when the editor window is moved QApplication::activeWindow()->installEventFilter(this); m_filterInstalled = true; } m_presentationModel->clearCompletionModels(); if (invocationType == KTextEditor::CodeCompletionModel::UserInvocation) { deleteCompletionRanges(); } foreach (KTextEditor::CodeCompletionModel *model, models) { KTextEditor::Range range; if (word.isValid()) { range = word; //qCDebug(LOG_KTE)<<"word is used"; } else { range = _completionRange(model, view(), view()->cursorPosition()); //qCDebug(LOG_KTE)<<"completionRange has been called, cursor pos is"<cursorPosition(); } //qCDebug(LOG_KTE)<<"range is"<completionInvoked(view(), range, invocationType); disconnect(model, SIGNAL(waitForReset()), this, SLOT(waitForModelReset())); m_completionRanges[model] = CompletionRange(view()->doc()->newMovingRange(range, KTextEditor::MovingRange::ExpandRight | KTextEditor::MovingRange::ExpandLeft)); //In automatic invocation mode, hide the completion widget as soon as the position where the completion was started is passed to the left m_completionRanges[model].leftBoundary = view()->cursorPosition(); //In manual invocation mode, bound the activity either the point from where completion was invoked, or to the start of the range if (invocationType != KTextEditor::CodeCompletionModel::AutomaticInvocation) if (range.start() < m_completionRanges[model].leftBoundary) { m_completionRanges[model].leftBoundary = range.start(); } if (!m_completionRanges[model].range->toRange().isValid()) { qCWarning(LOG_KTE) << "Could not construct valid smart-range from" << range << "instead got" << *m_completionRanges[model].range; abortCompletion(); return; } } m_presentationModel->setCompletionModels(models); cursorPositionChanged(); if (!m_completionRanges.isEmpty()) { connect(this->model(), SIGNAL(layoutChanged()), this, SLOT(modelContentChanged())); connect(this->model(), SIGNAL(modelReset()), this, SLOT(modelContentChanged())); //Now that all models have been notified, check whether the widget should be displayed instantly modelContentChanged(); } else { abortCompletion(); } } void KateCompletionWidget::waitForModelReset() { KTextEditor::CodeCompletionModel *senderModel = qobject_cast(sender()); if (!senderModel) { qCWarning(LOG_KTE) << "waitForReset signal from bad model"; return; } m_waitingForReset.insert(senderModel); } void KateCompletionWidget::updateAndShow() { //qCDebug(LOG_KTE)<<"*******************************************"; if (!view()->hasFocus()) { qCDebug(LOG_KTE) << "view does not have focus"; return; } setUpdatesEnabled(false); modelReset(); m_argumentHintModel->buildRows(); if (m_argumentHintModel->rowCount(QModelIndex()) != 0) { argumentHintsChanged(true); } // } //We do both actions twice here so they are stable, because they influence each other: //updatePosition updates the height, resizeColumns needs the correct height to decide over //how many rows it computs the column-width updatePosition(true); m_entryList->resizeColumns(true, true); updatePosition(true); m_entryList->resizeColumns(true, true); setUpdatesEnabled(true); if (m_argumentHintModel->rowCount(QModelIndex())) { updateArgumentHintGeometry(); m_argumentHintTree->show(); } else { m_argumentHintTree->hide(); } if (m_presentationModel->rowCount() && (!m_presentationModel->shouldMatchHideCompletionList() || !hideAutomaticCompletionOnExactMatch || m_lastInvocationType != KTextEditor::CodeCompletionModel::AutomaticInvocation)) { show(); } else { hide(); } } void KateCompletionWidget::updatePositionSlot() { updatePosition(); } bool KateCompletionWidget::updatePosition(bool force) { if (!force && !isCompletionActive()) { return false; } if (!completionRange()) { return false; } QPoint cursorPosition = view()->cursorToCoordinate(completionRange()->start()); if (cursorPosition == QPoint(-1, -1)) { // Start of completion range is now off-screen -> abort abortCompletion(); return false; } QPoint p = view()->mapToGlobal(cursorPosition); int x = p.x() - m_entryList->columnTextViewportPosition(m_presentationModel->translateColumn(KTextEditor::CodeCompletionModel::Name)) - 7 - (m_entryList->viewport()->pos().x()); int y = p.y(); y += view()->renderer()->config()->fontMetrics().height() + 2; bool borderHit = false; if (x + width() > QApplication::desktop()->screenGeometry(view()).right()) { x = QApplication::desktop()->screenGeometry(view()).right() - width(); borderHit = true; } if (x < QApplication::desktop()->screenGeometry(view()).left()) { x = QApplication::desktop()->screenGeometry(view()).left(); borderHit = true; } move(QPoint(x, y)); updateHeight(); - updateArgumentHintGeometry(); - // //qCDebug(LOG_KTE) << "updated to" << geometry() << m_entryList->geometry() << borderHit; return borderHit; } void KateCompletionWidget::updateArgumentHintGeometry() { if (!m_dontShowArgumentHints) { //Now place the argument-hint widget QRect geom = m_argumentHintTree->geometry(); geom.moveTo(pos()); geom.setWidth(width()); geom.moveBottom(pos().y() - view()->renderer()->config()->fontMetrics().height() * 2); m_argumentHintTree->updateGeometry(geom); } } //Checks whether the given model has at least "rows" rows, also searching the second level of the tree. bool hasAtLeastNRows(int rows, QAbstractItemModel *model) { int count = 0; for (int row = 0; row < model->rowCount(); ++row) { ++count; QModelIndex index(model->index(row, 0)); if (index.isValid()) { count += model->rowCount(index); } if (count > rows) { return true; } } return false; } void KateCompletionWidget::updateHeight() { QRect geom = geometry(); int minBaseHeight = 10; int maxBaseHeight = 300; int baseHeight = 0; int calculatedCustomHeight = 0; if (hasAtLeastNRows(15, m_presentationModel)) { //If we know there is enough rows, always use max-height, we don't need to calculate size-hints baseHeight = maxBaseHeight; } else { //Calculate size-hints to determine the best height for (int row = 0; row < m_presentationModel->rowCount(); ++row) { baseHeight += treeView()->sizeHintForRow(row); QModelIndex index(m_presentationModel->index(row, 0)); if (index.isValid()) { for (int row2 = 0; row2 < m_presentationModel->rowCount(index); ++row2) { int h = 0; for (int a = 0; a < m_presentationModel->columnCount(index); ++a) { int localHeight = treeView()->sizeHintForIndex(index.child(row2, a)).height(); if (localHeight > h) { h = localHeight; } } baseHeight += h; if (baseHeight > maxBaseHeight) { break; } } if (baseHeight > maxBaseHeight) { break; } } } calculatedCustomHeight = baseHeight; } baseHeight += 2 * frameWidth(); if (m_entryList->horizontalScrollBar()->isVisible()) { baseHeight += m_entryList->horizontalScrollBar()->height(); } if (baseHeight < minBaseHeight) { baseHeight = minBaseHeight; } if (baseHeight > maxBaseHeight) { baseHeight = maxBaseHeight; m_entryList->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); } else { //Somewhere there seems to be a bug that makes QTreeView add a scroll-bar //even if the content exactly fits in. So forcefully disable the scroll-bar in that case m_entryList->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } int newExpandingAddedHeight = 0; if (baseHeight == maxBaseHeight && model()->expandingWidgetsHeight()) { //Eventually add some more height if (calculatedCustomHeight && calculatedCustomHeight > baseHeight && calculatedCustomHeight < (maxBaseHeight + model()->expandingWidgetsHeight())) { newExpandingAddedHeight = calculatedCustomHeight - baseHeight; } else { newExpandingAddedHeight = model()->expandingWidgetsHeight(); } } if (m_expandedAddedHeightBase != baseHeight && m_expandedAddedHeightBase - baseHeight > -2 && m_expandedAddedHeightBase - baseHeight < 2) { //Re-use the stored base-height if it only slightly differs from the current one. //Reason: Qt seems to apply slightly wrong sizes when the completion-widget is moved out of the screen at the bottom, // which completely breaks this algorithm. Solution: re-use the old base-size if it only slightly differs from the computed one. baseHeight = m_expandedAddedHeightBase; } int screenBottom = QApplication::desktop()->screenGeometry(view()).bottom(); //Limit the height to the bottom of the screen int bottomPosition = baseHeight + newExpandingAddedHeight + geometry().top(); if (bottomPosition > screenBottom) { newExpandingAddedHeight -= bottomPosition - (screenBottom); } int finalHeight = baseHeight + newExpandingAddedHeight; if (finalHeight < 10) { m_entryList->resize(m_entryList->width(), height() - 2 * frameWidth()); return; } m_expandedAddedHeightBase = geometry().height(); geom.setHeight(finalHeight); //Work around a crash deep within the Qt 4.5 raster engine m_entryList->setScrollingEnabled(false); if (geometry() != geom) { setGeometry(geom); } QSize entryListSize = QSize(m_entryList->width(), finalHeight - 2 * frameWidth()); if (m_entryList->size() != entryListSize) { m_entryList->resize(entryListSize); } m_entryList->setScrollingEnabled(true); } void KateCompletionWidget::cursorPositionChanged() { ////qCDebug(LOG_KTE); if (m_completionRanges.isEmpty()) { return; } QModelIndex oldCurrentSourceIndex; if (m_inCompletionList && m_entryList->currentIndex().isValid()) { oldCurrentSourceIndex = m_presentationModel->mapToSource(m_entryList->currentIndex()); } //Check the models and eventuall abort some const QList checkCompletionRanges = m_completionRanges.keys(); for (QList::const_iterator it = checkCompletionRanges.begin(); it != checkCompletionRanges.end(); ++it) { KTextEditor::CodeCompletionModel *model = *it; if (!m_completionRanges.contains(model)) { continue; } //qCDebug(LOG_KTE)<<"range before _updateRange:"<< *range; // this might invalidate the range, therefore re-check afterwards KTextEditor::Range rangeTE = m_completionRanges[model].range->toRange(); KTextEditor::Range newRange = _updateRange(model, view(), rangeTE); if (!m_completionRanges.contains(model)) { continue; } // update value m_completionRanges[model].range->setRange(newRange); //qCDebug(LOG_KTE)<<"range after _updateRange:"<< *range; QString currentCompletion = _filterString(model, view(), *m_completionRanges[model].range, view()->cursorPosition()); if (!m_completionRanges.contains(model)) { continue; } //qCDebug(LOG_KTE)<<"after _filterString, currentCompletion="<< currentCompletion; bool abort = _shouldAbortCompletion(model, view(), *m_completionRanges[model].range, currentCompletion); if (!m_completionRanges.contains(model)) { continue; } //qCDebug(LOG_KTE)<<"after _shouldAbortCompletion:abort="<cursorPosition() < m_completionRanges[model].leftBoundary) { //qCDebug(LOG_KTE) << "aborting because of boundary: cursor:"<cursorPosition()<<"completion_Range_left_boundary:"<removeCompletionModel(model); } } else { m_presentationModel->setCurrentCompletion(model, currentCompletion); } } if (oldCurrentSourceIndex.isValid()) { QModelIndex idx = m_presentationModel->mapFromSource(oldCurrentSourceIndex); if (idx.isValid()) { //qCDebug(LOG_KTE) << "setting" << idx; m_entryList->setCurrentIndex(idx.sibling(idx.row(), 0)); // m_entryList->nextCompletion(); // m_entryList->previousCompletion(); } else { //qCDebug(LOG_KTE) << "failed to map from source"; } } m_entryList->scheduleUpdate(); } bool KateCompletionWidget::isCompletionActive() const { return !m_completionRanges.isEmpty() && ((!isHidden() && isVisible()) || (!m_argumentHintTree->isHidden() && m_argumentHintTree->isVisible())); } void KateCompletionWidget::abortCompletion() { //qCDebug(LOG_KTE) ; m_isSuspended = false; bool wasActive = isCompletionActive(); if (hasFocus()) { view()->activateWindow(); view()->setFocus(); } clear(); if (!isHidden()) { hide(); } if (!m_argumentHintTree->isHidden()) { m_argumentHintTree->hide(); } if (wasActive) { view()->sendCompletionAborted(); } } void KateCompletionWidget::clear() { m_presentationModel->clearCompletionModels(); m_argumentHintTree->clearCompletion(); m_argumentHintModel->clear(); foreach (KTextEditor::CodeCompletionModel *model, m_completionRanges.keys()) { _aborted(model, view()); } deleteCompletionRanges(); } bool KateCompletionWidget::navigateAccept() { m_hadCompletionNavigation = true; if (currentEmbeddedWidget()) { QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetAccept"); } QModelIndex index = selectedIndex(); if (index.isValid()) { index.data(KTextEditor::CodeCompletionModel::AccessibilityAccept); return true; } return false; } void KateCompletionWidget::execute() { //qCDebug(LOG_KTE) ; if (!isCompletionActive()) { return; } QModelIndex index = selectedIndex(); if (!index.isValid()) { return abortCompletion(); } QModelIndex toExecute; if (index.model() == m_presentationModel) { toExecute = m_presentationModel->mapToSource(index); } else { toExecute = m_argumentHintModel->mapToSource(index); } if (!toExecute.isValid()) { qCWarning(LOG_KTE) << "Could not map index" << m_entryList->selectionModel()->currentIndex() << "to source index."; return abortCompletion(); } // encapsulate all editing as being from the code completion, and undo-able in one step. view()->doc()->editStart(); m_completionEditRunning = true; // create scoped pointer, to ensure deletion of cursor QScopedPointer oldPos(view()->doc()->newMovingCursor(view()->cursorPosition(), KTextEditor::MovingCursor::StayOnInsert)); KTextEditor::CodeCompletionModel *model = static_cast(const_cast(toExecute.model())); Q_ASSERT(model); Q_ASSERT(m_completionRanges.contains(model)); KTextEditor::Cursor start = m_completionRanges[model].range->start(); model->executeCompletionItem(view(), *m_completionRanges[model].range, toExecute); view()->doc()->editEnd(); m_completionEditRunning = false; abortCompletion(); view()->sendCompletionExecuted(start, model, toExecute); KTextEditor::Cursor newPos = view()->cursorPosition(); if (newPos > *oldPos) { m_automaticInvocationAt = newPos; m_automaticInvocationLine = view()->doc()->text(KTextEditor::Range(*oldPos, newPos)); //qCDebug(LOG_KTE) << "executed, starting automatic invocation with line" << m_automaticInvocationLine; m_lastInsertionByUser = false; m_automaticInvocationTimer->start(); } } void KateCompletionWidget::resizeEvent(QResizeEvent *event) { QFrame::resizeEvent(event); + + // keep argument hint geometry in sync + if (m_argumentHintTree->isVisible()) { + updateArgumentHintGeometry(); + } +} + +void KateCompletionWidget::moveEvent(QMoveEvent *event) +{ + QFrame::moveEvent(event); + + // keep argument hint geometry in sync + if (m_argumentHintTree->isVisible()) { + updateArgumentHintGeometry(); + } } void KateCompletionWidget::showEvent(QShowEvent *event) { m_isSuspended = false; QFrame::showEvent(event); if (!m_dontShowArgumentHints && m_argumentHintModel->rowCount(QModelIndex()) != 0) { m_argumentHintTree->show(); } } KTextEditor::MovingRange *KateCompletionWidget::completionRange(KTextEditor::CodeCompletionModel *model) const { if (!model) { if (m_completionRanges.isEmpty()) { return nullptr; } KTextEditor::MovingRange *ret = m_completionRanges.begin()->range; foreach (const CompletionRange &range, m_completionRanges) if (range.range->start() > ret->start()) { ret = range.range; } return ret; } if (m_completionRanges.contains(model)) { return m_completionRanges[model].range; } else { return nullptr; } } QMap KateCompletionWidget::completionRanges() const { return m_completionRanges; } void KateCompletionWidget::modelReset() { setUpdatesEnabled(false); m_entryList->setAnimated(false); m_argumentHintTree->setAnimated(false); ///We need to do this by hand, because QTreeView::expandAll is very inefficient. ///It creates a QPersistentModelIndex for every single item in the whole tree.. for (int row = 0; row < m_argumentHintModel->rowCount(QModelIndex()); ++row) { QModelIndex index(m_argumentHintModel->index(row, 0, QModelIndex())); if (!m_argumentHintTree->isExpanded(index)) { m_argumentHintTree->expand(index); } } for (int row = 0; row < m_entryList->model()->rowCount(QModelIndex()); ++row) { QModelIndex index(m_entryList->model()->index(row, 0, QModelIndex())); if (!m_entryList->isExpanded(index)) { m_entryList->expand(index); } } setUpdatesEnabled(true); } KateCompletionTree *KateCompletionWidget::treeView() const { return m_entryList; } QModelIndex KateCompletionWidget::selectedIndex() const { if (!isCompletionActive()) { return QModelIndex(); } if (m_inCompletionList) { return m_entryList->currentIndex(); } else { return m_argumentHintTree->currentIndex(); } } bool KateCompletionWidget::navigateLeft() { m_hadCompletionNavigation = true; if (currentEmbeddedWidget()) { QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetLeft"); } QModelIndex index = selectedIndex(); if (index.isValid()) { index.data(KTextEditor::CodeCompletionModel::AccessibilityPrevious); return true; } return false; } bool KateCompletionWidget::navigateRight() { m_hadCompletionNavigation = true; if (currentEmbeddedWidget()) { ///@todo post 4.2: Make these slots public interface, or create an interface using virtual functions QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetRight"); } QModelIndex index = selectedIndex(); if (index.isValid()) { index.data(KTextEditor::CodeCompletionModel::AccessibilityNext); return true; } return false; } bool KateCompletionWidget::hadNavigation() const { return m_hadCompletionNavigation; } void KateCompletionWidget::resetHadNavigation() { m_hadCompletionNavigation = false; } bool KateCompletionWidget::navigateBack() { m_hadCompletionNavigation = true; if (currentEmbeddedWidget()) { QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetBack"); } return false; } bool KateCompletionWidget::toggleExpanded(bool forceExpand, bool forceUnExpand) { if ((canExpandCurrentItem() || forceExpand) && !forceUnExpand) { bool ret = canExpandCurrentItem(); setCurrentItemExpanded(true); return ret; } else if (canCollapseCurrentItem() || forceUnExpand) { bool ret = canCollapseCurrentItem(); setCurrentItemExpanded(false); return ret; } return false; } bool KateCompletionWidget::canExpandCurrentItem() const { if (m_inCompletionList) { if (!m_entryList->currentIndex().isValid()) { return false; } return model()->isExpandable(m_entryList->currentIndex()) && !model()->isExpanded(m_entryList->currentIndex()); } else { if (!m_argumentHintTree->currentIndex().isValid()) { return false; } return argumentHintModel()->isExpandable(m_argumentHintTree->currentIndex()) && !argumentHintModel()->isExpanded(m_argumentHintTree->currentIndex()); } } bool KateCompletionWidget::canCollapseCurrentItem() const { if (m_inCompletionList) { if (!m_entryList->currentIndex().isValid()) { return false; } return model()->isExpandable(m_entryList->currentIndex()) && model()->isExpanded(m_entryList->currentIndex()); } else { if (!m_argumentHintTree->currentIndex().isValid()) { return false; } return m_argumentHintModel->isExpandable(m_argumentHintTree->currentIndex()) && m_argumentHintModel->isExpanded(m_argumentHintTree->currentIndex()); } } void KateCompletionWidget::setCurrentItemExpanded(bool expanded) { if (m_inCompletionList) { if (!m_entryList->currentIndex().isValid()) { return; } model()->setExpanded(m_entryList->currentIndex(), expanded); updateHeight(); } else { if (!m_argumentHintTree->currentIndex().isValid()) { return; } m_argumentHintModel->setExpanded(m_argumentHintTree->currentIndex(), expanded); } } bool KateCompletionWidget::eventFilter(QObject *watched, QEvent *event) { bool ret = QFrame::eventFilter(watched, event); if (watched != this) if (event->type() == QEvent::Move) { updatePosition(); } return ret; } bool KateCompletionWidget::navigateDown() { m_hadCompletionNavigation = true; if (currentEmbeddedWidget()) { QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetDown"); } return false; } bool KateCompletionWidget::navigateUp() { m_hadCompletionNavigation = true; if (currentEmbeddedWidget()) { QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetUp"); } return false; } QWidget *KateCompletionWidget::currentEmbeddedWidget() { QModelIndex index = selectedIndex(); if (!index.isValid()) { return nullptr; } if (qobject_cast(index.model())) { const ExpandingWidgetModel *model = static_cast(index.model()); if (model->isExpanded(index)) { return model->expandingWidget(index); } } return nullptr; } void KateCompletionWidget::cursorDown() { bool wasPartiallyExpanded = model()->partiallyExpandedRow().isValid(); if (m_inCompletionList) { m_entryList->nextCompletion(); } else { if (!m_argumentHintTree->nextCompletion()) { switchList(); } } if (wasPartiallyExpanded != model()->partiallyExpandedRow().isValid()) { updateHeight(); } } void KateCompletionWidget::cursorUp() { bool wasPartiallyExpanded = model()->partiallyExpandedRow().isValid(); if (m_inCompletionList) { if (!m_entryList->previousCompletion()) { switchList(); } } else { m_argumentHintTree->previousCompletion(); } if (wasPartiallyExpanded != model()->partiallyExpandedRow().isValid()) { updateHeight(); } } void KateCompletionWidget::pageDown() { bool wasPartiallyExpanded = model()->partiallyExpandedRow().isValid(); if (m_inCompletionList) { m_entryList->pageDown(); } else { if (!m_argumentHintTree->pageDown()) { switchList(); } } if (wasPartiallyExpanded != model()->partiallyExpandedRow().isValid()) { updateHeight(); } } void KateCompletionWidget::pageUp() { bool wasPartiallyExpanded = model()->partiallyExpandedRow().isValid(); if (m_inCompletionList) { if (!m_entryList->pageUp()) { switchList(); } } else { m_argumentHintTree->pageUp(); } if (wasPartiallyExpanded != model()->partiallyExpandedRow().isValid()) { updateHeight(); } } void KateCompletionWidget::top() { bool wasPartiallyExpanded = model()->partiallyExpandedRow().isValid(); if (m_inCompletionList) { m_entryList->top(); } else { m_argumentHintTree->top(); } if (wasPartiallyExpanded != model()->partiallyExpandedRow().isValid()) { updateHeight(); } } void KateCompletionWidget::bottom() { bool wasPartiallyExpanded = model()->partiallyExpandedRow().isValid(); if (m_inCompletionList) { m_entryList->bottom(); } else { m_argumentHintTree->bottom(); } if (wasPartiallyExpanded != model()->partiallyExpandedRow().isValid()) { updateHeight(); } } void KateCompletionWidget::switchList() { if (m_inCompletionList) { if (m_argumentHintModel->rowCount(QModelIndex()) != 0) { m_entryList->setCurrentIndex(QModelIndex()); m_argumentHintTree->setCurrentIndex(m_argumentHintModel->index(m_argumentHintModel->rowCount(QModelIndex()) - 1, 0)); m_inCompletionList = false; } } else { if (m_presentationModel->rowCount(QModelIndex()) != 0) { m_argumentHintTree->setCurrentIndex(QModelIndex()); m_entryList->setCurrentIndex(m_presentationModel->index(0, 0)); if (model()->hasGroups()) { //If we have groups we have to move on, because the first item is a label m_entryList->nextCompletion(); } m_inCompletionList = true; } } } void KateCompletionWidget::showConfig() { abortCompletion(); m_configWidget->exec(); } void KateCompletionWidget::completionModelReset() { KTextEditor::CodeCompletionModel *model = qobject_cast(sender()); if (!model) { qCWarning(LOG_KTE) << "bad sender"; return; } if (!m_waitingForReset.contains(model)) { return; } m_waitingForReset.remove(model); if (m_waitingForReset.isEmpty()) { if (!isCompletionActive()) { //qCDebug(LOG_KTE) << "all completion-models we waited for are ready. Last one: " << model->objectName(); //Eventually show the completion-list if this was the last model we were waiting for //Use a queued connection once again to make sure that KateCompletionModel is notified before we are QMetaObject::invokeMethod(this, "modelContentChanged", Qt::QueuedConnection); } } } void KateCompletionWidget::modelDestroyed(QObject *model) { m_sourceModels.removeAll(static_cast(model)); abortCompletion(); } void KateCompletionWidget::registerCompletionModel(KTextEditor::CodeCompletionModel *model) { if (m_sourceModels.contains(model)) { return; } connect(model, SIGNAL(destroyed(QObject*)), SLOT(modelDestroyed(QObject*))); //This connection must not be queued connect(model, SIGNAL(modelReset()), SLOT(completionModelReset())); m_sourceModels.append(model); if (isCompletionActive()) { m_presentationModel->addCompletionModel(model); } } void KateCompletionWidget::unregisterCompletionModel(KTextEditor::CodeCompletionModel *model) { disconnect(model, SIGNAL(destroyed(QObject*)), this, SLOT(modelDestroyed(QObject*))); disconnect(model, SIGNAL(modelReset()), this, SLOT(completionModelReset())); m_sourceModels.removeAll(model); abortCompletion(); } bool KateCompletionWidget::isCompletionModelRegistered(KTextEditor::CodeCompletionModel *model) const { return m_sourceModels.contains(model); } int KateCompletionWidget::automaticInvocationDelay() const { return m_automaticInvocationDelay; } void KateCompletionWidget::setAutomaticInvocationDelay(int delay) { m_automaticInvocationDelay = delay; } void KateCompletionWidget::wrapLine(const KTextEditor::Cursor &) { m_lastInsertionByUser = !m_completionEditRunning; // wrap line, be done m_automaticInvocationLine.clear(); m_automaticInvocationTimer->stop(); } void KateCompletionWidget::unwrapLine(int) { m_lastInsertionByUser = !m_completionEditRunning; // just removal m_automaticInvocationLine.clear(); m_automaticInvocationTimer->stop(); } void KateCompletionWidget::insertText(const KTextEditor::Cursor &position, const QString &text) { m_lastInsertionByUser = !m_completionEditRunning; // no invoke? if (!view()->isAutomaticInvocationEnabled()) { m_automaticInvocationLine.clear(); m_automaticInvocationTimer->stop(); return; } if (m_automaticInvocationAt != position) { m_automaticInvocationLine.clear(); m_lastInsertionByUser = !m_completionEditRunning; } m_automaticInvocationLine += text; m_automaticInvocationAt = position; m_automaticInvocationAt.setColumn(position.column() + text.length()); if (m_automaticInvocationLine.isEmpty()) { m_automaticInvocationTimer->stop(); return; } m_automaticInvocationTimer->start(m_automaticInvocationDelay); } void KateCompletionWidget::removeText(const KTextEditor::Range &) { m_lastInsertionByUser = !m_completionEditRunning; // just removal m_automaticInvocationLine.clear(); m_automaticInvocationTimer->stop(); } void KateCompletionWidget::automaticInvocation() { //qCDebug(LOG_KTE)<<"m_automaticInvocationAt:"<cursorPosition(); if (m_automaticInvocationAt != view()->cursorPosition()) { return; } bool start = false; QList models; //qCDebug(LOG_KTE)<<"checking models"; foreach (KTextEditor::CodeCompletionModel *model, m_sourceModels) { //qCDebug(LOG_KTE)<<"m_completionRanges contains model?:"<cursorPosition()); //qCDebug(LOG_KTE)<<"start="<commonPrefix((m_inCompletionList && !shellLikeTabCompletion) ? m_entryList->currentIndex() : QModelIndex()); if (!prefix.isEmpty()) { view()->insertText(prefix); } else if (shellLikeTabCompletion) { cursorDown(); return; } } else { if (shellLikeTabCompletion) { cursorUp(); return; } //Reset left boundaries, so completion isn't stopped typedef QMap CompletionRangeMap; for (CompletionRangeMap::iterator it = m_completionRanges.begin(); it != m_completionRanges.end(); ++it) { (*it).leftBoundary = (*it).range->start(); } //Remove suffix until the completion-list filter is widened again uint itemCount = m_presentationModel->filteredItemCount(); while (view()->cursorPosition().column() > 0 && m_presentationModel->filteredItemCount() == itemCount) { KTextEditor::Range lastcharRange = KTextEditor::Range(view()->cursorPosition() - KTextEditor::Cursor(0, 1), view()->cursorPosition()); QString cursorText = view()->document()->text(lastcharRange); if (!cursorText[0].isSpace()) { view()->document()->removeText(lastcharRange); QApplication::sendPostedEvents(); } else { break; } } } } diff --git a/src/completion/katecompletionwidget.h b/src/completion/katecompletionwidget.h index 3df2e773..c8b7ec60 100644 --- a/src/completion/katecompletionwidget.h +++ b/src/completion/katecompletionwidget.h @@ -1,247 +1,248 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2005-2006 Hamish Rodda * Copyright (C) 2007-2008 David Nolden * * 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 KATECOMPLETIONWIDGET_H #define KATECOMPLETIONWIDGET_H #include #include #include #include #include #include class QToolButton; class QPushButton; class QLabel; class QTimer; namespace KTextEditor { class ViewPrivate; } class KateCompletionModel; class KateCompletionTree; class KateArgumentHintTree; class KateArgumentHintModel; namespace KTextEditor { class EmbeddedWidgetInterface; } /** * This is the code completion's main widget, and also contains the * core interface logic. * * @author Hamish Rodda */ class KTEXTEDITOR_EXPORT KateCompletionWidget : public QFrame { Q_OBJECT public: explicit KateCompletionWidget(KTextEditor::ViewPrivate *parent); - ~KateCompletionWidget(); + ~KateCompletionWidget() override; KTextEditor::ViewPrivate *view() const; KateCompletionTree *treeView() const; bool isCompletionActive() const; void startCompletion(KTextEditor::CodeCompletionModel::InvocationType invocationType, const QList &models = QList()); void startCompletion(const KTextEditor::Range &word, KTextEditor::CodeCompletionModel *model, KTextEditor::CodeCompletionModel::InvocationType invocationType = KTextEditor::CodeCompletionModel::ManualInvocation); void startCompletion(const KTextEditor::Range &word, const QList &models = QList(), KTextEditor::CodeCompletionModel::InvocationType invocationType = KTextEditor::CodeCompletionModel::ManualInvocation); void userInvokedCompletion(); public Q_SLOTS: //Executed when return is pressed while completion is active. void execute(); void cursorDown(); void cursorUp(); public: void tab(bool shift); ///Returns whether the current item was expanded/unexpanded bool toggleExpanded(bool forceExpand = false, bool forceUnExpand = false); const KateCompletionModel *model() const; KateCompletionModel *model(); void registerCompletionModel(KTextEditor::CodeCompletionModel *model); void unregisterCompletionModel(KTextEditor::CodeCompletionModel *model); bool isCompletionModelRegistered(KTextEditor::CodeCompletionModel *model) const; int automaticInvocationDelay() const; void setAutomaticInvocationDelay(int delay); struct CompletionRange { - CompletionRange() : range(nullptr) + CompletionRange() { } explicit CompletionRange(KTextEditor::MovingRange *r) : range(r) { } bool operator==(const CompletionRange &rhs) const { return range->toRange() == rhs.range->toRange(); } - KTextEditor::MovingRange *range; + KTextEditor::MovingRange *range = nullptr; //Whenever the cursor goes before this position, the completion is stopped, unless it is invalid. KTextEditor::Cursor leftBoundary; }; KTextEditor::MovingRange *completionRange(KTextEditor::CodeCompletionModel *model = nullptr) const; QMap completionRanges() const; // Navigation void pageDown(); void pageUp(); void top(); void bottom(); QWidget *currentEmbeddedWidget(); bool canExpandCurrentItem() const; bool canCollapseCurrentItem() const; void setCurrentItemExpanded(bool); //Returns true if a screen border has been hit bool updatePosition(bool force = false); - bool eventFilter(QObject *watched, QEvent *event) Q_DECL_OVERRIDE; + bool eventFilter(QObject *watched, QEvent *event) override; KateArgumentHintTree *argumentHintTree() const; KateArgumentHintModel *argumentHintModel() const; ///Called by KateViewInternal, because we need the specific information from the event. void updateHeight(); public Q_SLOTS: void waitForModelReset(); void abortCompletion(); void showConfig(); /* void viewFocusIn(); void viewFocusOut();*/ void updatePositionSlot(); void automaticInvocation(); /* void updateFocus();*/ void argumentHintsChanged(bool hasContent); bool navigateUp(); bool navigateDown(); bool navigateLeft(); bool navigateRight(); bool navigateAccept(); bool navigateBack(); bool hadNavigation() const; void resetHadNavigation(); protected: - void showEvent(QShowEvent *event) Q_DECL_OVERRIDE; - void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE; - void focusOutEvent(QFocusEvent * event) Q_DECL_OVERRIDE; + void showEvent(QShowEvent *event) override; + void resizeEvent(QResizeEvent *event) override; + void moveEvent(QMoveEvent *event) override; + void focusOutEvent(QFocusEvent * event) override; private Q_SLOTS: void completionModelReset(); void modelDestroyed(QObject *model); void modelContentChanged(); void cursorPositionChanged(); void modelReset(); void rowsInserted(const QModelIndex &parent, int row, int rowEnd); void viewFocusOut(); void wrapLine(const KTextEditor::Cursor &position); void unwrapLine(int line); void insertText(const KTextEditor::Cursor &position, const QString &text); void removeText(const KTextEditor::Range &range); private: void updateAndShow(); void updateArgumentHintGeometry(); QModelIndex selectedIndex() const; void clear(); //Switch cursor between argument-hint list / completion-list void switchList(); KTextEditor::Range determineRange() const; void completionRangeChanged(KTextEditor::CodeCompletionModel *, const KTextEditor::Range &word); void deleteCompletionRanges(); QList m_sourceModels; KateCompletionModel *m_presentationModel; QMap m_completionRanges; QSet m_waitingForReset; KTextEditor::Cursor m_lastCursorPosition; KateCompletionTree *m_entryList; KateArgumentHintModel *m_argumentHintModel; KateArgumentHintTree *m_argumentHintTree; QTimer *m_automaticInvocationTimer; //QTimer* m_updateFocusTimer; QWidget *m_statusBar; QToolButton *m_sortButton; QLabel *m_sortText; QToolButton *m_filterButton; QLabel *m_filterText; QPushButton *m_configButton; KTextEditor::Cursor m_automaticInvocationAt; QString m_automaticInvocationLine; int m_automaticInvocationDelay; bool m_filterInstalled; class KateCompletionConfig *m_configWidget; bool m_lastInsertionByUser; bool m_inCompletionList; //Are we in the completion-list? If not, we're in the argument-hint list bool m_isSuspended; bool m_dontShowArgumentHints; //Used temporarily to prevent flashing bool m_needShow; bool m_hadCompletionNavigation; bool m_haveExactMatch; bool m_noAutoHide; /** * is a completion edit ongoing? */ bool m_completionEditRunning; int m_expandedAddedHeightBase; KTextEditor::CodeCompletionModel::InvocationType m_lastInvocationType; }; #endif diff --git a/src/completion/katekeywordcompletion.h b/src/completion/katekeywordcompletion.h index fe068021..3c7903f3 100644 --- a/src/completion/katekeywordcompletion.h +++ b/src/completion/katekeywordcompletion.h @@ -1,62 +1,62 @@ /* This file is part of the KDE libraries Copyright (C) 2014 Sven Brauch 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, or any later version, 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. */ #ifndef KATEKEYWORDCOMPLETIONMODEL_H #define KATEKEYWORDCOMPLETIONMODEL_H #include "ktexteditor/codecompletionmodel.h" #include "codecompletionmodelcontrollerinterface.h" /** * @brief Highlighting-file based keyword completion for the editor. * * This model offers completion of language-specific keywords based on information * taken from the kate syntax files. It queries the highlighting engine to get the * correct context for a given cursor position, then suggests all keyword items * from the XML file for the active language. */ class KateKeywordCompletionModel : public KTextEditor::CodeCompletionModel , public KTextEditor::CodeCompletionModelControllerInterface { Q_OBJECT Q_INTERFACES(KTextEditor::CodeCompletionModelControllerInterface) public: explicit KateKeywordCompletionModel(QObject* parent); - QVariant data(const QModelIndex& index, int role) const Q_DECL_OVERRIDE; - int rowCount(const QModelIndex& parent = QModelIndex()) const Q_DECL_OVERRIDE; - QModelIndex parent(const QModelIndex& index) const Q_DECL_OVERRIDE; - QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const Q_DECL_OVERRIDE; - virtual void completionInvoked(KTextEditor::View* view, const KTextEditor::Range& range, - InvocationType invocationType) Q_DECL_OVERRIDE; - KTextEditor::Range completionRange(KTextEditor::View* view, const KTextEditor::Cursor& position) Q_DECL_OVERRIDE; - virtual bool shouldAbortCompletion(KTextEditor::View* view, const KTextEditor::Range& range, - const QString& currentCompletion) Q_DECL_OVERRIDE; - virtual bool shouldStartCompletion(KTextEditor::View* view, const QString& insertedText, bool userInsertion, - const KTextEditor::Cursor& position) Q_DECL_OVERRIDE; - MatchReaction matchingItem(const QModelIndex& matched) Q_DECL_OVERRIDE; - bool shouldHideItemsWithEqualNames() const Q_DECL_OVERRIDE; + QVariant data(const QModelIndex& index, int role) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex& index) const override; + QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; + void completionInvoked(KTextEditor::View* view, const KTextEditor::Range& range, + InvocationType invocationType) override; + KTextEditor::Range completionRange(KTextEditor::View* view, const KTextEditor::Cursor& position) override; + bool shouldAbortCompletion(KTextEditor::View* view, const KTextEditor::Range& range, + const QString& currentCompletion) override; + bool shouldStartCompletion(KTextEditor::View* view, const QString& insertedText, bool userInsertion, + const KTextEditor::Cursor& position) override; + MatchReaction matchingItem(const QModelIndex& matched) override; + bool shouldHideItemsWithEqualNames() const override; private: QList m_items; }; #endif // KATEKEYWORDCOMPLETIONMODEL_H // kate: indent-width 4; replace-tabs on diff --git a/src/completion/katewordcompletion.h b/src/completion/katewordcompletion.h index 8f6a6fd8..a9bc6018 100644 --- a/src/completion/katewordcompletion.h +++ b/src/completion/katewordcompletion.h @@ -1,114 +1,114 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2003 Anders Lund * Copyright (C) 2010 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 _KateWordCompletion_h_ #define _KateWordCompletion_h_ #include #include #include #include #include #include #include #include "katepartdebug.h" #include class KTEXTEDITOR_EXPORT KateWordCompletionModel : public KTextEditor::CodeCompletionModel, public KTextEditor::CodeCompletionModelControllerInterface { Q_OBJECT Q_INTERFACES(KTextEditor::CodeCompletionModelControllerInterface) public: explicit KateWordCompletionModel(QObject *parent); - ~KateWordCompletionModel(); + ~KateWordCompletionModel() override; /** * This function is responsible to generating / updating the list of current * completions. The default implementation does nothing. * * When implementing this function, remember to call setRowCount() (or implement * rowCount()), and to generate the appropriate change notifications (for instance * by calling QAbstractItemModel::reset()). * @param view The view to generate completions for * @param range The range of text to generate completions for * */ - void completionInvoked(KTextEditor::View *view, const KTextEditor::Range &range, InvocationType invocationType) Q_DECL_OVERRIDE; + void completionInvoked(KTextEditor::View *view, const KTextEditor::Range &range, InvocationType invocationType) override; - bool shouldStartCompletion(KTextEditor::View *view, const QString &insertedText, bool userInsertion, const KTextEditor::Cursor &position) Q_DECL_OVERRIDE; - bool shouldAbortCompletion(KTextEditor::View *view, const KTextEditor::Range &range, const QString ¤tCompletion) Q_DECL_OVERRIDE; + bool shouldStartCompletion(KTextEditor::View *view, const QString &insertedText, bool userInsertion, const KTextEditor::Cursor &position) override; + bool shouldAbortCompletion(KTextEditor::View *view, const KTextEditor::Range &range, const QString ¤tCompletion) override; void saveMatches(KTextEditor::View *view, const KTextEditor::Range &range); - int rowCount(const QModelIndex &parent) const Q_DECL_OVERRIDE; + int rowCount(const QModelIndex &parent) const override; - QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE; - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; - QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE; - MatchReaction matchingItem(const QModelIndex &matched) Q_DECL_OVERRIDE; + QVariant data(const QModelIndex &index, int role) const override; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + MatchReaction matchingItem(const QModelIndex &matched) override; - KTextEditor::Range completionRange(KTextEditor::View *view, const KTextEditor::Cursor &position) Q_DECL_OVERRIDE; + KTextEditor::Range completionRange(KTextEditor::View *view, const KTextEditor::Cursor &position) override; - bool shouldHideItemsWithEqualNames() const Q_DECL_OVERRIDE; + bool shouldHideItemsWithEqualNames() const override; QStringList allMatches(KTextEditor::View *view, const KTextEditor::Range &range) const; - void executeCompletionItem (KTextEditor::View *view, const KTextEditor::Range &word, const QModelIndex &index) const Q_DECL_OVERRIDE; + void executeCompletionItem (KTextEditor::View *view, const KTextEditor::Range &word, const QModelIndex &index) const override; private: QStringList m_matches; bool m_automatic; }; class KateWordCompletionView : public QObject { Q_OBJECT public: KateWordCompletionView(KTextEditor::View *view, KActionCollection *ac); ~KateWordCompletionView(); private Q_SLOTS: void completeBackwards(); void completeForwards(); void slotCursorMoved(); void shellComplete(); void popupCompletionList(); private: void complete(bool fw = true); QString word() const; KTextEditor::Range range() const; QString findLongestUnique(const QStringList &matches, int lead) const; KTextEditor::View *m_view; KateWordCompletionModel *m_dWCompletionModel; struct KateWordCompletionViewPrivate *d; }; #endif // _DocWordCompletionPlugin_h_ diff --git a/src/data/ktexteditor.desktop b/src/data/ktexteditor.desktop index f5eef4f0..ff39ccbb 100644 --- a/src/data/ktexteditor.desktop +++ b/src/data/ktexteditor.desktop @@ -1,60 +1,61 @@ [Desktop Entry] Type=ServiceType X-KDE-ServiceType=KTextEditor/Document X-KDE-Derived=KParts/ReadWritePart Comment=Embeddable Text Editor Component (with Doc/View Separation) Comment[ar]=مكوّن محرّر نصوص يمكن تضمينه (يفصل بين Doc والمناظير) Comment[ast]=Componente d'editor empotrable de testu (con separtación de documentos/vista) Comment[bg]=Текстов редактор (с разделение между Doc/View) Comment[bs]=Ugradiva komponenta uređivača teksta (s razdvajanjem dokumenta i pogleda) Comment[ca]=Component incrustable de l'editor de text (amb separació Doc/Vista) Comment[ca@valencia]=Component incrustable de l'editor de text (amb separació Doc/Vista) Comment[cs]=Pohltitelná komponenta textového editoru (s oddělením Doc/View) Comment[da]=Teksteditorkomponent som kan indlejres (med dok./visning-adskillelse) Comment[de]=Einbettungsfähige Editorkomponente (mit Text/Ansicht-Aufteilung) Comment[el]=Ενσωματώσιμο συστατικό επεξεργαστή κειμένου (με διαχωρισμό των δεδομένων από την προβολή τους) Comment[en_GB]=Embeddable Text Editor Component (with Doc/View Separation) Comment[es]=Componente empotrable de edición de texto (con separación documentos/vista) Comment[et]=Põimitav tekstiredaktori komponent (dokumendi/vaate eraldamisega) Comment[eu]=Testu-editore osagai txertagarria (dokumentu/ikuspegi banaketarekin) Comment[fi]=Upotettava tekstimuokkauskomponentti (Tiedosto/Näkymä-jaolla) Comment[fr]=Composant intégrable d'édition de texte (avec séparation Doc / Vue) Comment[ga]=Comhpháirt eagarthóireacht téacs inleabaithe (le deighilt idir cáipéis agus amharc) Comment[gd]=Co-phàirt deasaiche teacsa a ghabhas leabachadh (le sgaradh eadar modh deasachaidh is seallaidh) Comment[gl]=Compoñente integrábel de edición de texto (cunha Separación Doc/Vista) Comment[hu]=Beágyazható szövegszerkesztő (dokumentum/nézet modellel) Comment[ia]=Componente del editor interne de texto (con separation Doc/Vista) +Comment[id]=Komponen Pengedit Teks Terbenam (dengan Doc/Tampilan Pemisah ) Comment[is]=Ívafin textaritilseining (með skjal/sýn aðskilnaði) Comment[it]=Componente editor di testo integrabile (con separazione documento/vista) Comment[kk]=Ендірілетін мәтін өңдеу компоненті (Құжат/көрініс үлгіні қолдайтын) Comment[km]=សមាសភាគ​កម្មវិធី​និពន្ធ​អត្ថបទ​ដែល​អាច​បង្កប់​បាន​​ (ជា​មួយ​ការ​បំបែក Doc/View ) Comment[ko]=끼워넣을 수 있는 텍스트 편집기 구성 요소(문서/뷰 구분) Comment[lt]=Įtraukiamas tekstų redagavimo komponentas (su dokumento/žiūrėjimo atskyrimu) Comment[lv]=Iegultā teksta redaktora komponente (ar redaktora/skatītāja atdalīšanu) Comment[mr]=अंतर्भूतयोग्य पाठ्य संपादक घटक (Doc/View विभाजनासकट) Comment[nb]=Innebygget skriveprogram-komponent (med Doc/View-skille) Comment[nds]=Inbettbor Texteditor-Komponent (mit Dokment-/Ansicht-Trennen) Comment[nl]=Ingebed tekstinvoercomponent (met scheiding van tekst/weergave) Comment[nn]=Innebyggbar skriveprogramkomponent (med Doc/View-skilje) Comment[pa]=ਇੰਬੈੱਡਯੋਗ ਟੈਕਸਟ ਐਡੀਟਰ ਭਾਗ (Doc/ਝਲਕ ਵੱਖ ਕਰਨ ਨਾਲ) Comment[pl]=Komponent osadzanego edytora tekstu (z podziałem Dokument/Widok) Comment[pt]=Componente Incorporado do Editor de Texto (com Separação entre Documentos) Comment[pt_BR]=Componente de edição de textos integrado (com separação de documentação/visualização) Comment[ro]=Componentă de Editare Text Încorporabilă (cu Separarea Documentului/Vizualizării) Comment[ru]=Встраиваемый компонент редактора текста (с поддержкой модели документ/вид) Comment[si]=තිළැලිය හැකි පෙළ සකසන සංරචකය (ලේඛන/දසුන් වෙන්කිරීම සහිතව) Comment[sk]=Vložiteľný komponent textového editora (s oddelením Doc/Pohľad) Comment[sl]=Vgradljiva enota urejevalnika besedil (z ločevanjem pogleda in dokumenta) Comment[sr]=Угнездива компонента уређивача текста (уз раздвајање документ-приказ) Comment[sr@ijekavian]=Угњездива компонента уређивача текста (уз раздвајање документ-приказ) Comment[sr@ijekavianlatin]=Ugnjezdiva komponenta uređivača teksta (uz razdvajanje dokument-prikaz) Comment[sr@latin]=Ugnezdiva komponenta uređivača teksta (uz razdvajanje dokument-prikaz) Comment[sv]=Inbäddningsbar texteditor (med dok/vyseparation) Comment[tg]=Қисми таҳриргари матнии дарунсохтшаванда (бо тақсимкунии Санад/Намоиш) Comment[tr]=Gömülebilir Metin Düzenleyici Bileşeni (Doc/View ayrımı ile) Comment[ug]=سىڭدۈرۈشچان تېكىست تەھرىرلىگۈچ بۆلىكى(پۈتۈك/كۆرۈنۈش ئايرىلىدۇ) Comment[uk]=Компонент редактора текстів, який можна вбудовувати (з розділенням документ/вигляд) Comment[wa]=Ravalé compôzant aspougneu d' tecse (avou dispårtaedje documint/vuwe) Comment[x-test]=xxEmbeddable Text Editor Component (with Doc/View Separation)xx Comment[zh_CN]=可嵌入的文本编辑器部件(文档和视图分离) Comment[zh_TW]=可嵌入的文字編輯器元件 (Doc/View 分開) diff --git a/src/data/ktexteditorplugin.desktop b/src/data/ktexteditorplugin.desktop index 6ed7531d..dfdb3f13 100644 --- a/src/data/ktexteditorplugin.desktop +++ b/src/data/ktexteditorplugin.desktop @@ -1,61 +1,62 @@ [Desktop Entry] Type=ServiceType X-KDE-ServiceType=KTextEditor/Plugin X-KDE-Derived= Comment=KTextEditor Plugin Comment[ar]=ملحقة «محرّر نصوصك» Comment[ast]=Complementu de KTextEditor Comment[bg]=Приставка за KTextEditor Comment[bs]=Priključak za KTextEditor Comment[ca]=Connector del KTextEditor Comment[ca@valencia]=Connector del KTextEditor Comment[cs]=Modul textového editoru Comment[da]=KTextEditor-plugin Comment[de]=KTextEditor-Modul Comment[el]=Πρόσθετο του KTextEditor Comment[en_GB]=KTextEditor Plugin Comment[es]=Complemento KTextEditor Comment[et]=KTextEditori plugin Comment[eu]=KTextEditor plugina Comment[fi]=KTextEditor-liitännäinen Comment[fr]=Module externe KTextEditor Comment[ga]=Breiseán KTextEditor Comment[gd]=Plugan KTextEditor Comment[gl]=Complemento de KTextEditor Comment[hu]=KTextEditor-bővítmény Comment[ia]=KTextEditor Plugin +Comment[id]=Plugin KTextEditor Comment[is]=KTextEditor-viðbót Comment[it]=Estensione KTextEditor Comment[ja]=KTextEditor プラグイン Comment[kk]=KTextEditor плагині Comment[km]=កម្មវិធី​ជំនួយ​របស់ KTextEditor Comment[ko]=KTextEditor 플러그인 Comment[lt]=KTextEditor papildinys Comment[lv]=KTextEditor spraudnis Comment[mr]=के-टेक्स्ट-एडिटर प्लगइन Comment[nb]=Programtillegget KTextEditor Comment[nds]=KTextEditor-Moduul Comment[nl]=KTextEditor-plug-in Comment[nn]=KTextEditor-tillegg Comment[pa]=KTextEditor ਪਲੱਗਇਨ Comment[pl]=Wtyczka edytora tekstu Comment[pt]='Plugin' do KTextEditor Comment[pt_BR]=Plugin do KTextEditor Comment[ro]=Modul editor de text Comment[ru]=Расширение KTextEditor Comment[si]=Kපෙළසැකසීම ප්ලගිනය Comment[sk]=Plugin KTextEditor Comment[sl]=Vstavek za KTextEditor Comment[sr]=Прикључак за KTextEditor Comment[sr@ijekavian]=Прикључак за KTextEditor Comment[sr@ijekavianlatin]=Priključak za KTextEditor Comment[sr@latin]=Priključak za KTextEditor Comment[sv]=Insticksprogram för texteditor Comment[tg]=Плагини KTextEditor Comment[tr]=KTextEditor Eklentisi Comment[ug]=KTextEditor قىستۇرما Comment[uk]=Додаток KTextEditor Comment[wa]=Tchôke-divins KTextEditor Comment[x-test]=xxKTextEditor Pluginxx Comment[zh_CN]=KTextEditor 插件 Comment[zh_TW]=KTextEditor 外掛程式 diff --git a/src/dialogs/kateconfigpage.cpp b/src/dialogs/kateconfigpage.cpp index 4bf62350..a075b1d8 100644 --- a/src/dialogs/kateconfigpage.cpp +++ b/src/dialogs/kateconfigpage.cpp @@ -1,40 +1,39 @@ /* This file is part of the KDE libraries * * 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 "kateconfigpage.h" KateConfigPage::KateConfigPage(QWidget *parent, const char *) : KTextEditor::ConfigPage(parent) - , m_changed(false) { connect(this, SIGNAL(changed()), this, SLOT(somethingHasChanged())); } KateConfigPage::~KateConfigPage() { } void KateConfigPage::slotChanged() { emit changed(); } void KateConfigPage::somethingHasChanged() { m_changed = true; } diff --git a/src/dialogs/kateconfigpage.h b/src/dialogs/kateconfigpage.h index 17582550..96c720dd 100644 --- a/src/dialogs/kateconfigpage.h +++ b/src/dialogs/kateconfigpage.h @@ -1,49 +1,49 @@ /* This file is part of the KDE libraries * * 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_PAGE_H__ #define __KATE_CONFIG_PAGE_H__ #include class KateConfigPage : public KTextEditor::ConfigPage { Q_OBJECT public: explicit KateConfigPage(QWidget *parent = nullptr, const char *name = nullptr); virtual ~KateConfigPage(); virtual void reload() = 0; public: bool hasChanged() { return m_changed; } protected Q_SLOTS: void slotChanged(); private Q_SLOTS: void somethingHasChanged(); protected: - bool m_changed; + bool m_changed = false; }; #endif diff --git a/src/dialogs/katedialogs.cpp b/src/dialogs/katedialogs.cpp index 2f718dc9..4732c97e 100644 --- a/src/dialogs/katedialogs.cpp +++ b/src/dialogs/katedialogs.cpp @@ -1,1466 +1,1472 @@ /* 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(textareaUi->chkShowWordCount, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); + connect(textareaUi->chkShowLinesCount, 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()); + KateViewConfig::global()->setShowWordCount(textareaUi->chkShowWordCount->isChecked()); + KateViewConfig::global()->setShowLinesCount(textareaUi->chkShowLinesCount->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()); + textareaUi->chkShowWordCount->setChecked(KateViewConfig::global()->showWordCount()); + textareaUi->chkShowLinesCount->setChecked(KateViewConfig::global()->showLinesCount()); } 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/katedialogs.h b/src/dialogs/katedialogs.h index a0d3f806..13aa6d19 100644 --- a/src/dialogs/katedialogs.h +++ b/src/dialogs/katedialogs.h @@ -1,374 +1,374 @@ /* 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-2016 Dominik Haumann Copyright (C) 2007 Mirko Stocker Copyright (C) 2009 Michel Ludwig 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 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_DIALOGS_H__ #define __KATE_DIALOGS_H__ #include "katehighlight.h" #include "kateviewhelpers.h" #include "kateconfigpage.h" #include #include #include #include #include #include #include #include #include class ModeConfigPage; namespace KTextEditor { class DocumentPrivate; } namespace KTextEditor { class ViewPrivate; } namespace KTextEditor { class Message; } namespace KIO { class Job; class TransferJob; } class KComboBox; class KShortcutsEditor; class QSpinBox; class KPluginSelector; class KPluginInfo; class KProcess; class QCheckBox; class QLabel; class QCheckBox; class QKeyEvent; class QTemporaryFile; class QTableWidget; namespace Ui { class TextareaAppearanceConfigWidget; class BordersAppearanceConfigWidget; class NavigationConfigWidget; class EditConfigWidget; class IndentationConfigWidget; class OpenSaveConfigWidget; class OpenSaveConfigAdvWidget; class CompletionConfigTab; class SpellCheckConfigWidget; } class KateGotoBar : public KateViewBarWidget { Q_OBJECT public: explicit KateGotoBar(KTextEditor::View *view, QWidget *parent = nullptr); void updateData(); protected Q_SLOTS: void gotoLine(); protected: - void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE; + void keyPressEvent(QKeyEvent *event) override; private: KTextEditor::View *const m_view; QSpinBox *gotoRange; }; class KateDictionaryBar : public KateViewBarWidget { Q_OBJECT public: explicit KateDictionaryBar(KTextEditor::ViewPrivate *view, QWidget *parent = nullptr); virtual ~KateDictionaryBar(); public Q_SLOTS: void updateData(); protected Q_SLOTS: void dictionaryChanged(const QString &dictionary); private: KTextEditor::ViewPrivate *m_view; Sonnet::DictionaryComboBox *m_dictionaryComboBox; }; class KateIndentConfigTab : public KateConfigPage { Q_OBJECT public: explicit KateIndentConfigTab(QWidget *parent); - ~KateIndentConfigTab(); - QString name() const Q_DECL_OVERRIDE; + ~KateIndentConfigTab() override; + QString name() const override; protected: Ui::IndentationConfigWidget *ui; public Q_SLOTS: - void apply() Q_DECL_OVERRIDE; - void reload() Q_DECL_OVERRIDE; - void reset() Q_DECL_OVERRIDE {} - void defaults() Q_DECL_OVERRIDE {} + void apply() override; + void reload() override; + void reset() override {} + void defaults() override {} private Q_SLOTS: void slotChanged(); void showWhatsThis(const QString &text); }; class KateCompletionConfigTab : public KateConfigPage { Q_OBJECT public: explicit KateCompletionConfigTab(QWidget *parent); - ~KateCompletionConfigTab(); - QString name() const Q_DECL_OVERRIDE; + ~KateCompletionConfigTab() override; + QString name() const override; protected: Ui::CompletionConfigTab *ui; public Q_SLOTS: - void apply() Q_DECL_OVERRIDE; - void reload() Q_DECL_OVERRIDE; - void reset() Q_DECL_OVERRIDE {} - void defaults() Q_DECL_OVERRIDE {} + void apply() override; + void reload() override; + void reset() override {} + void defaults() override {} private Q_SLOTS: void showWhatsThis(const QString &text); }; class KateEditGeneralConfigTab : public KateConfigPage { Q_OBJECT public: explicit KateEditGeneralConfigTab(QWidget *parent); - ~KateEditGeneralConfigTab(); - QString name() const Q_DECL_OVERRIDE; + ~KateEditGeneralConfigTab() override; + QString name() const override; private: Ui::EditConfigWidget *ui; public Q_SLOTS: - void apply() Q_DECL_OVERRIDE; - void reload() Q_DECL_OVERRIDE; - void reset() Q_DECL_OVERRIDE {} - void defaults() Q_DECL_OVERRIDE {} + void apply() override; + void reload() override; + void reset() override {} + void defaults() override {} }; class KateNavigationConfigTab : public KateConfigPage { Q_OBJECT public: explicit KateNavigationConfigTab(QWidget *parent); - ~KateNavigationConfigTab(); - QString name() const Q_DECL_OVERRIDE; + ~KateNavigationConfigTab() override; + QString name() const override; private: Ui::NavigationConfigWidget *ui; public Q_SLOTS: - void apply() Q_DECL_OVERRIDE; - void reload() Q_DECL_OVERRIDE; - void reset() Q_DECL_OVERRIDE {} - void defaults() Q_DECL_OVERRIDE {} + void apply() override; + void reload() override; + void reset() override {} + void defaults() override {} }; class KateSpellCheckConfigTab : public KateConfigPage { Q_OBJECT public: explicit KateSpellCheckConfigTab(QWidget *parent); - ~KateSpellCheckConfigTab(); - QString name() const Q_DECL_OVERRIDE; + ~KateSpellCheckConfigTab() override; + QString name() const override; protected: Ui::SpellCheckConfigWidget *ui; Sonnet::ConfigWidget *m_sonnetConfigWidget; public Q_SLOTS: - void apply() Q_DECL_OVERRIDE; - void reload() Q_DECL_OVERRIDE; - void reset() Q_DECL_OVERRIDE {} - void defaults() Q_DECL_OVERRIDE {} + void apply() override; + void reload() override; + void reset() override {} + void defaults() override {} private Q_SLOTS: void showWhatsThis(const QString &text); }; class KateEditConfigTab : public KateConfigPage { Q_OBJECT public: explicit KateEditConfigTab(QWidget *parent); - ~KateEditConfigTab(); - QString name() const Q_DECL_OVERRIDE; - QString fullName() const Q_DECL_OVERRIDE; - QIcon icon() const Q_DECL_OVERRIDE; + ~KateEditConfigTab() override; + QString name() const override; + QString fullName() const override; + QIcon icon() const override; public Q_SLOTS: - void apply() Q_DECL_OVERRIDE; - void reload() Q_DECL_OVERRIDE; - void reset() Q_DECL_OVERRIDE; - void defaults() Q_DECL_OVERRIDE; + void apply() override; + void reload() override; + void reset() override; + void defaults() override; private: KateEditGeneralConfigTab *editConfigTab; KateNavigationConfigTab *navigationConfigTab; KateIndentConfigTab *indentConfigTab; KateCompletionConfigTab *completionConfigTab; KateSpellCheckConfigTab *spellCheckConfigTab; QList m_inputModeConfigTabs; }; class KateViewDefaultsConfig : public KateConfigPage { Q_OBJECT public: explicit KateViewDefaultsConfig(QWidget *parent); - ~KateViewDefaultsConfig(); - QString name() const Q_DECL_OVERRIDE; - QString fullName() const Q_DECL_OVERRIDE; - QIcon icon() const Q_DECL_OVERRIDE; + ~KateViewDefaultsConfig() override; + QString name() const override; + QString fullName() const override; + QIcon icon() const override; public Q_SLOTS: - void apply() Q_DECL_OVERRIDE; - void reload() Q_DECL_OVERRIDE; - void reset() Q_DECL_OVERRIDE; - void defaults() Q_DECL_OVERRIDE; + void apply() override; + void reload() override; + void reset() override; + void defaults() override; private: Ui::TextareaAppearanceConfigWidget *const textareaUi; Ui::BordersAppearanceConfigWidget *const bordersUi; }; class KateSaveConfigTab : public KateConfigPage { Q_OBJECT public: explicit KateSaveConfigTab(QWidget *parent); - ~KateSaveConfigTab(); - QString name() const Q_DECL_OVERRIDE; - QString fullName() const Q_DECL_OVERRIDE; - QIcon icon() const Q_DECL_OVERRIDE; + ~KateSaveConfigTab() override; + QString name() const override; + QString fullName() const override; + QIcon icon() const override; public Q_SLOTS: - void apply() Q_DECL_OVERRIDE; - void reload() Q_DECL_OVERRIDE; - void reset() Q_DECL_OVERRIDE; - void defaults() Q_DECL_OVERRIDE; + void apply() override; + void reload() override; + void reset() override; + void defaults() override; void swapFileModeChanged(int); protected: //why? //KComboBox *m_encoding, *m_encodingDetection, *m_eol; QCheckBox *cbLocalFiles, *cbRemoteFiles; QCheckBox *replaceTabs, *removeSpaces, *allowEolDetection; class QSpinBox *blockCount; class QLabel *blockCountLabel; private: Ui::OpenSaveConfigWidget *ui; Ui::OpenSaveConfigAdvWidget *uiadv; ModeConfigPage *modeConfigPage; }; class KateHlDownloadDialog: public QDialog { Q_OBJECT public: KateHlDownloadDialog(QWidget *parent, const char *name, bool modal); ~KateHlDownloadDialog(); private: static unsigned parseVersion(const QString &); class QTreeWidget *list; class QString listData; KIO::TransferJob *transferJob; QPushButton *m_installButton; private Q_SLOTS: void listDataReceived(KIO::Job *, const QByteArray &data); void slotInstall(); }; /** * This dialog will prompt the user for what do with a file that is * modified on disk. * If the file wasn't deleted, it has a 'diff' button, which will create * a diff file (uing diff(1)) and launch that using KRun. */ class KateModOnHdPrompt : public QObject { Q_OBJECT public: enum Status { Reload = 1, // 0 is QDialog::Rejected Save, Overwrite, Ignore, Close }; KateModOnHdPrompt(KTextEditor::DocumentPrivate *doc, KTextEditor::ModificationInterface::ModifiedOnDiskReason modtype, const QString &reason); ~KateModOnHdPrompt(); Q_SIGNALS: void saveAsTriggered(); void ignoreTriggered(); void reloadTriggered(); private Q_SLOTS: /** * Show a diff between the document text and the disk file. */ void slotDiff(); private Q_SLOTS: void slotDataAvailable(); ///< read data from the process void slotPDone(); ///< Runs the diff file when done private: KTextEditor::DocumentPrivate *m_doc; QPointer m_message; KTextEditor::ModificationInterface::ModifiedOnDiskReason m_modtype; KProcess *m_proc; QTemporaryFile *m_diffFile; QAction *m_diffAction; }; #endif diff --git a/src/dialogs/textareaappearanceconfigwidget.ui b/src/dialogs/textareaappearanceconfigwidget.ui index 9fb1e60c..9cf0a29b 100644 --- a/src/dialogs/textareaappearanceconfigwidget.ui +++ b/src/dialogs/textareaappearanceconfigwidget.ui @@ -1,246 +1,266 @@ TextareaAppearanceConfigWidget If this option is checked, the text lines will be wrapped at the view border on the screen. &Dynamic Word Wrap true Dynamic &word wrap indicators (if applicable): cmbDynamicWordWrapIndicator Choose when the Dynamic Word Wrap Indicators should be displayed. Align dynamically wrapped lines to indentation depth: sbDynamicWordWrapDepth 0 0 <p>Enables the start of dynamically wrapped lines to be aligned vertically to the indentation level of the first line. This can help to make code and markup more readable.</p><p>Additionally, this allows you to set a maximum width of the screen, as a percentage, after which dynamically wrapped lines will no longer be vertically aligned. For example, at 50%, lines whose indentation levels are deeper than 50% of the width of the screen will not have vertical alignment applied to subsequent wrapped lines.</p> Disabled % of View Width 80 10 Whitespace Highlighting The editor will display a symbol to indicate the presence of a tab in the text. &Highlight tabulators Highlight trailing &spaces 1 Highlight marker size: Size of the visible highlight marker. Qt::Horizontal 1 5 1 Advanced If this is enabled, the editor will display vertical lines to help identify indent lines. Show i&ndentation lines If this is enabled, the range between the selected matching brackets will be highlighted. Highlight range between selected brackets Flash matching brackets If this is enabled, matching brackets are animated for better visibility. Animate bracket matching When this setting is enabled, the editor view automatically folds comment blocks that start on the first line of the document. This is helpful to hide license headers which are commonly placed at the beginning of a file. Fold first line + + + + Show/hide word count in status bar + + + Show word count + + + + + + + Show/hide Lines count in status bar + + + Show Lines Count + + + Qt::Vertical 0 1 KComboBox QComboBox
kcombobox.h
gbWordWrap toggled(bool) cmbDynamicWordWrapIndicator setEnabled(bool) 115 7 340 44 gbWordWrap toggled(bool) sbDynamicWordWrapDepth setEnabled(bool) 59 6 385 72
diff --git a/src/document/katebuffer.h b/src/document/katebuffer.h index d96003a8..09a9d772 100644 --- a/src/document/katebuffer.h +++ b/src/document/katebuffer.h @@ -1,294 +1,294 @@ /* This file is part of the KDE libraries Copyright (c) 2000 Waldo Bastian Copyright (C) 2002-2004 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_BUFFER_H__ #define __KATE_BUFFER_H__ #include "katetextbuffer.h" #include #include class KateLineInfo; namespace KTextEditor { class DocumentPrivate; } class KateHighlighting; /** * The KateBuffer class maintains a collections of lines. * * @author Waldo Bastian * @author Christoph Cullmann */ class KTEXTEDITOR_EXPORT KateBuffer : public Kate::TextBuffer { Q_OBJECT public: /** * Create an empty buffer. * @param doc parent document */ explicit KateBuffer(KTextEditor::DocumentPrivate *doc); /** * Goodbye buffer */ - ~KateBuffer(); + ~KateBuffer() override; public: /** * start some editing action */ void editStart(); /** * finish some editing action */ void editEnd(); /** * were there changes in the current running * editing session? * @return changes done? */ inline bool editChanged() const { return editingChangedBuffer(); } /** * dirty lines start * @return start line */ inline int editTagStart() const { return editingMinimalLineChanged(); } /** * dirty lines end * @return end line */ inline int editTagEnd() const { return editingMaximalLineChanged(); } /** * line inserted/removed? * @return line inserted/removed? */ inline bool editTagFrom() const { return editingChangedNumberOfLines() != 0; } public: /** * Clear the buffer. */ - void clear() Q_DECL_OVERRIDE; + void clear() override; /** * Open a file, use the given filename * @param m_file filename to open * @param enforceTextCodec enforce to use only the set text codec * @return success */ bool openFile(const QString &m_file, bool enforceTextCodec); /** * Did encoding errors occur on load? * @return encoding errors occurred on load? */ bool brokenEncoding() const { return m_brokenEncoding; } /** * Too long lines wrapped on load? * @return too long lines wrapped on load? */ bool tooLongLinesWrapped() const { return m_tooLongLinesWrapped; } int longestLineLoaded() const { return m_longestLineLoaded; } /** * Can the current codec handle all chars * @return chars can be encoded */ bool canEncode(); /** * Save the buffer to a file, use the given filename + codec + end of line chars (internal use of qtextstream) * @param m_file filename to save to * @return success */ bool saveFile(const QString &m_file); public: /** * Return line @p lineno. * Highlighting of returned line might be out-dated, which may be sufficient * for pure text manipulation functions, like search/replace. * If you require highlighting to be up to date, call @ref ensureHighlighted * prior to this method. */ inline Kate::TextLine plainLine(int lineno) { if (lineno < 0 || lineno >= lines()) { return Kate::TextLine(); } return line(lineno); } /** * Update highlighting of given line @p line, if needed. * If @p line is already highlighted, this function does nothing. * If @p line is not highlighted, all lines up to line + lookAhead * are highlighted. * @param lookAhead also highlight these following lines */ void ensureHighlighted(int line, int lookAhead = 64); /** * Return the total number of lines in the buffer. */ inline int count() const { return lines(); } /** * Unwrap given line. * @param line line to unwrap */ - void unwrapLine(int line) Q_DECL_OVERRIDE; + void unwrapLine(int line) override; /** * Wrap line at given cursor position. * @param position line/column as cursor where to wrap */ - void wrapLine(const KTextEditor::Cursor &position) Q_DECL_OVERRIDE; + void wrapLine(const KTextEditor::Cursor &position) override; public: inline int tabWidth() const { return m_tabWidth; } public: void setTabWidth(int w); /** * Use @p highlight for highlighting * * @p highlight may be 0 in which case highlighting * will be disabled. */ void setHighlight(int hlMode); KateHighlighting *highlight() { return m_highlight; } /** * Invalidate highlighting of whole buffer. */ void invalidateHighlighting(); /** * For a given line, compute the folding range that starts there * to be used to fold e.g. from the icon border * @param startLine start line * @return folding range starting at the given line or invalid range */ KTextEditor::Range computeFoldingRangeForStartLine(int startLine); private: /** * Highlight information needs to be updated. * * @param from first line in range * @param to last line in range * @param invalidate should the rehighlighted lines be tagged? */ void doHighlight(int from, int to, bool invalidate); Q_SIGNALS: /** * Emitted when the highlighting of a certain range has * changed. */ void tagLines(int start, int end); void respellCheckBlock(int start, int end); private: /** * document we belong to */ KTextEditor::DocumentPrivate *const m_doc; /** * file loaded with encoding problems? */ bool m_brokenEncoding; /** * too long lines wrapped on load? */ bool m_tooLongLinesWrapped; /** * length of the longest line loaded */ int m_longestLineLoaded; /** * current highlighting mode or 0 */ KateHighlighting *m_highlight; // for the scrapty indent sensitive langs int m_tabWidth; /** * last line with valid highlighting */ int m_lineHighlighted; /** * number of dynamic contexts causing a full invalidation */ int m_maxDynamicContexts; }; #endif diff --git a/src/document/katedocument.cpp b/src/document/katedocument.cpp index 9cd7c14d..1e7320ba 100644 --- a/src/document/katedocument.cpp +++ b/src/document/katedocument.cpp @@ -1,6025 +1,6036 @@ /* 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 +template +static int indexOf(const std::initializer_list & list, const E& entry) +{ + auto it = std::find(list.begin(), list.end(), entry); + return it == list.end() ? -1 : std::distance(list.begin(), it); +} + +template +static bool contains(const std::initializer_list & list, const E& entry) +{ + return indexOf(list, entry) >= 0; +} + static inline QChar matchingStartBracket(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) + + m_config(new KateDocumentConfig(this)) + { /** * no plugins from kparts here */ setPluginLoadingMode (DoNotLoadPlugins); /** * pass on our component data, do this after plugin loading is off */ setComponentData(KTextEditor::EditorPrivate::self()->aboutData()); /** * avoid spamming plasma and other window managers with progress dialogs * we show such stuff inline in the views! */ setProgressInfoEnabled(false); // register doc at factory KTextEditor::EditorPrivate::self()->registerDocument(this); // normal hl m_buffer->setHighlight(0); // swap file m_swapfile = (config()->swapFileMode() == KateDocumentConfig::DisableSwapFile) ? nullptr : new Kate::SwapFile(this); // important, fill in the config into the indenter we use... m_indenter->updateConfig(); // some nice signals from the buffer connect(m_buffer, SIGNAL(tagLines(int,int)), this, SLOT(tagLines(int,int))); // if the user changes the highlight with the dialog, notify the doc connect(KateHlManager::self(), SIGNAL(changed()), SLOT(internalHlChanged())); // signals for mod on hd connect(KTextEditor::EditorPrivate::self()->dirWatch(), SIGNAL(dirty(QString)), this, SLOT(slotModOnHdDirty(QString))); connect(KTextEditor::EditorPrivate::self()->dirWatch(), SIGNAL(created(QString)), this, SLOT(slotModOnHdCreated(QString))); connect(KTextEditor::EditorPrivate::self()->dirWatch(), SIGNAL(deleted(QString)), this, SLOT(slotModOnHdDeleted(QString))); /** * singleshot timer to handle updates of mod on hd state delayed */ m_modOnHdTimer.setSingleShot(true); m_modOnHdTimer.setInterval(200); connect(&m_modOnHdTimer, SIGNAL(timeout()), this, SLOT(slotDelayedHandleModOnHd())); /** * 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 +#ifdef Q_OS_ANDROID +int log2(double d) { + int result; + std::frexp(d, &result); + return result-1; +} +#endif 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)); + m_viewsCache.append(view); // apply the view & renderer vars from the file type if (!m_fileType.isEmpty()) { readVariableLine(KTextEditor::EditorPrivate::self()->modeManager()->fileType(m_fileType).varLine, true); } // apply the view & renderer vars from the file readVariables(true); setActiveView(view); } void KTextEditor::DocumentPrivate::removeView(KTextEditor::View *view) { Q_ASSERT (m_views.contains(view)); m_views.remove(view); + m_viewsCache.removeAll(view); if (activeView() == view) { setActiveView(nullptr); } } void KTextEditor::DocumentPrivate::setActiveView(KTextEditor::View *view) { if (m_activeView == view) { return; } m_activeView = static_cast(view); } bool KTextEditor::DocumentPrivate::ownedView(KTextEditor::ViewPrivate *view) { // do we own the given view? return (m_views.contains(view)); } int KTextEditor::DocumentPrivate::toVirtualColumn(int line, int column) const { Kate::TextLine textLine = m_buffer->plainLine(line); if (textLine) { return textLine->toVirtualColumn(column, config()->tabWidth()); } else { return 0; } } int KTextEditor::DocumentPrivate::toVirtualColumn(const KTextEditor::Cursor &cursor) const { return toVirtualColumn(cursor.line(), cursor.column()); } int KTextEditor::DocumentPrivate::fromVirtualColumn(int line, int column) const { Kate::TextLine textLine = m_buffer->plainLine(line); if (textLine) { return textLine->fromVirtualColumn(column, config()->tabWidth()); } else { return 0; } } int KTextEditor::DocumentPrivate::fromVirtualColumn(const KTextEditor::Cursor &cursor) const { return fromVirtualColumn(cursor.line(), cursor.column()); } bool KTextEditor::DocumentPrivate::typeChars(KTextEditor::ViewPrivate *view, const QString &realChars) { /** * filter out non-printable chars (convert to utf-32 to support surrogate pairs) */ const auto realUcs4Chars = realChars.toUcs4(); QVector ucs4Chars; Q_FOREACH (auto c, realUcs4Chars) if (QChar::isPrint(c) || c == QChar::fromLatin1('\t') || c == QChar::fromLatin1('\n') || c == QChar::fromLatin1('\r')) { ucs4Chars.append(c); } QString chars = QString::fromUcs4(ucs4Chars.data(), ucs4Chars.size()); /** * no printable chars => nothing to insert! */ if (chars.isEmpty()) { return false; } /** * always unfreeze on typing */ view->cursors()->setSecondaryFrozen(false); view->cursors()->removeDuplicateCursors(); /** * auto bracket handling for newly inserted text * remember if we should auto add some */ QChar closingBracket; if (view->config()->autoBrackets() && chars.size() == 1 && !view->cursors()->hasSecondaryCursors()) { /** * 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(); } auto oldCursors = view->allCursors(); if (view->currentInputMode()->overwrite()) { Q_FOREACH ( const auto& cursor, view->allCursors() ) { auto line = cursor.line(); auto virtualColumn = toVirtualColumn(view->cursorPosition()); 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 #warning fix this: backspace in overwrite mode restores characters // if (oldCur.column() < lineLength(line)) { // QChar removed = characterAt(KTextEditor::Cursor(line, column)); // view->currentInputMode()->overwrittenChar(removed); // } removeText(r); } } Q_FOREACH ( const auto& cursor, view->allCursors() ) { auto adjusted = eventuallyReplaceTabs(cursor, chars); insertText(cursor, adjusted); } /** * 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 Q_FOREACH ( const auto& cursorPos, view->allCursors() ) { 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(); if ( ! view->cursors()->hasSecondaryCursors() ) { // 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 */ Q_FOREACH ( const auto& oldCur, oldCursors ) { 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 Q_FOREACH ( auto c, v->allCursors() ) { - if (c.line() > (int)lastLine()) { + if (c.line() > 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()); + if (c.column() > textLine->length()) { + c.setColumn(textLine->length()); + } } // 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) { /** * always unfreeze secondary cursors on typing */ view->cursors()->setSecondaryFrozen(false); if (!view->config()->persistentSelection() && view->selection()) { view->removeSelectedText(); return; } auto col = qMax(c.column(), 0); auto line = qMax(c.line(), 0); if ((col == 0) && (line == 0)) { return; } if (col > 0) { bool useNextBlock = false; if (config()->backspaceIndents()) { // backspace indents: erase to next indent position Kate::TextLine textLine = m_buffer->plainLine(line); // don't forget this check!!!! really!!!! if (!textLine) { return; } if ( view->blockSelection() && col > textLine->length() ) { view->clearSelection(false); insertText(c, QStringLiteral(" "), true); removeText({c-KTextEditor::Cursor{0, 1}, c}, true); } int colX = textLine->toVirtualColumn(col, config()->tabWidth()); int pos = textLine->firstChar(); if (pos > 0) { pos = textLine->toVirtualColumn(pos, config()->tabWidth()); } if (pos < 0 || pos >= (int)colX) { // only spaces on left side of cursor indent(KTextEditor::Range(line, 0, line, 0), -1); } else { useNextBlock = true; } } if (!config()->backspaceIndents() || useNextBlock) { KTextEditor::Cursor beginCursor(line, 0); KTextEditor::Cursor endCursor(line, col); if (!view->config()->backspaceRemoveComposed()) { // Normal backspace behavior + beginCursor.setColumn(col - 1); // move to left of surrogate pair if (!isValidTextPosition(beginCursor)) { Q_ASSERT(col >= 2); beginCursor.setColumn(col - 2); } - beginCursor.setColumn(col - 1); } else { beginCursor.setColumn(view->textLayout(c)->previousCursorPosition(c.column())); } removeText(KTextEditor::Range(beginCursor, endCursor)); // in most cases cursor is moved by removeText, but we should do it manually // for past-end-of-line cursors in block mode view->setCursorPosition(beginCursor); } } else { // col == 0: wrap to previous line if (line >= 1) { Kate::TextLine textLine = m_buffer->plainLine(line - 1); // don't forget this check!!!! really!!!! if (!textLine) { return; } if (config()->wordWrap() && textLine->endsWith(QLatin1String(" "))) { // gg: in hard wordwrap mode, backspace must also eat the trailing space removeText(KTextEditor::Range(line - 1, textLine->length() - 1, line, 0)); } else { removeText(KTextEditor::Range(line - 1, textLine->length(), line, 0)); } } } if ( m_currentAutobraceRange ) { const auto r = m_currentAutobraceRange->toRange(); if ( r.columnWidth() == 1 && view->cursorPosition() == r.start() ) { // start parenthesis removed and range length is 1, remove end as well del(view, view->cursorPosition()); m_currentAutobraceRange.clear(); } } } void KTextEditor::DocumentPrivate::del(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &c) { if (!view->config()->persistentSelection() && view->selection()) { if (view->blockSelection() && view->selection() && toVirtualColumn(view->selectionRange().start()) == toVirtualColumn(view->selectionRange().end())) { // Remove one character after selection line KTextEditor::Range range = view->selectionRange(); range.setEnd(KTextEditor::Cursor(range.end().line(), range.end().column() + 1)); view->setSelection(range); } view->removeSelectedText(); return; } if (c.column() < (int) m_buffer->plainLine(c.line())->length()) { KTextEditor::Cursor endCursor(c.line(), view->textLayout(c)->nextCursorPosition(c.column())); removeText(KTextEditor::Range(c, endCursor)); } else if (c.line() < lastLine()) { removeText(KTextEditor::Range(c.line(), c.column(), c.line() + 1, 0)); } } void KTextEditor::DocumentPrivate::paste(KTextEditor::ViewPrivate *view, const QString &text) { Q_ASSERT(false); // TODO this function should go away static const QChar newLineChar(QLatin1Char('\n')); QString s = text; if (s.isEmpty()) { return; } int lines = s.count(newLineChar); auto paste_text_at = [this, view, s, lines](const KTextEditor::Cursor& pos) { editStart(); #warning TODO handle overwrite mode /** 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(); editStart(); if (!view->config()->persistentSelection() && view->selection()) { auto 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(); } editEnd(); Q_FOREACH ( const auto& cursor, view->allCursors() ) { paste_text_at(cursor); } 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; } editStart(); Q_FOREACH ( auto c, view->allCursors() ) { int lineLen = line(c.line()).length(); if (!view->config()->persistentSelection() && view->selection()) { view->removeSelectedText(); } else if (view->currentInputMode()->overwrite() && c.column() < lineLen) { KTextEditor::Range r = KTextEditor::Range(c, 1); // replace mode needs to know what was removed so it can be restored with backspace QChar removed = line(c.line()).at(r.start().column()); view->currentInputMode()->overwrittenChar(removed); removeText(r); } 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 (z < lines()) { if (line(tmp.at(z).mark.line) == tmp.at(z).line) { setMark(tmp.at(z).mark.line, tmp.at(z).mark.type); } } } if (byUser) { setMode(oldMode); } setHighlightingMode(hl_mode); emit reloaded(this); return true; } bool KTextEditor::DocumentPrivate::documentSave() { if (!url().isValid() || !isReadWrite()) { return documentSaveAs(); } return save(); } bool KTextEditor::DocumentPrivate::documentSaveAs() { const QUrl saveUrl = QFileDialog::getSaveFileUrl(dialogParent(), i18n("Save File"), url(), {}, nullptr, QFileDialog::DontConfirmOverwrite); if (saveUrl.isEmpty() || !checkOverwrite(saveUrl, dialogParent())) { return false; } return saveAs(saveUrl); } bool KTextEditor::DocumentPrivate::documentSaveAsWithEncoding(const QString &encoding) { const QUrl saveUrl = QFileDialog::getSaveFileUrl(dialogParent(), i18n("Save File"), url(), {}, nullptr, QFileDialog::DontConfirmOverwrite); if (saveUrl.isEmpty() || !checkOverwrite(saveUrl, dialogParent())) { return false; } setEncoding(encoding); return saveAs(saveUrl); } bool KTextEditor::DocumentPrivate::documentSaveCopyAs() { const QUrl saveUrl = QFileDialog::getSaveFileUrl(dialogParent(), i18n("Save Copy of File"), url(), {}, nullptr, QFileDialog::DontConfirmOverwrite); if (saveUrl.isEmpty() || !checkOverwrite(saveUrl, dialogParent())) { return false; } QTemporaryFile file; if (!file.open()) { return false; } if (!m_buffer->saveFile(file.fileName())) { KMessageBox::error(dialogParent(), i18n("The document could not be saved, as it was not possible to write to %1.\n\nCheck that you have write access to this file or that enough disk space is available.", this->url().toDisplayString(QUrl::PreferLocalFile))); return false; } // get the right permissions, start with safe default KIO::StatJob *statJob = KIO::stat(url(), KIO::StatJob::SourceSide, 2); KJobWidgets::setWindow(statJob, QApplication::activeWindow()); int permissions = -1; if (statJob->exec()) { permissions = KFileItem(statJob->statResult(), url()).permissions(); } // KIO move, important: allow overwrite, we checked above! KIO::FileCopyJob *job = KIO::file_copy(QUrl::fromLocalFile(file.fileName()), saveUrl, permissions, KIO::Overwrite); KJobWidgets::setWindow(job, QApplication::activeWindow()); return job->exec(); } void KTextEditor::DocumentPrivate::setWordWrap(bool on) { config()->setWordWrap(on); } bool KTextEditor::DocumentPrivate::wordWrap() const { return config()->wordWrap(); } void KTextEditor::DocumentPrivate::setWordWrapAt(uint col) { config()->setWordWrapAt(col); } unsigned int KTextEditor::DocumentPrivate::wordWrapAt() const { return config()->wordWrapAt(); } void KTextEditor::DocumentPrivate::setPageUpDownMovesCursor(bool on) { config()->setPageUpDownMovesCursor(on); } bool KTextEditor::DocumentPrivate::pageUpDownMovesCursor() const { return config()->pageUpDownMovesCursor(); } //END bool KTextEditor::DocumentPrivate::setEncoding(const QString &e) { return m_config->setEncoding(e); } QString KTextEditor::DocumentPrivate::encoding() const { return m_config->encoding(); } void KTextEditor::DocumentPrivate::updateConfig() { m_undoManager->updateConfig(); // switch indenter if needed and update config.... m_indenter->setMode(m_config->indentationMode()); m_indenter->updateConfig(); // set tab width there, too m_buffer->setTabWidth(config()->tabWidth()); // update all views, does tagAll and updateView... foreach (KTextEditor::ViewPrivate *view, m_views) { view->updateDocumentConfig(); } // update on-the-fly spell checking as spell checking defaults might have changes if (m_onTheFlyChecker) { m_onTheFlyChecker->updateConfig(); } emit configChanged(); } //BEGIN Variable reader // "local variable" feature by anders, 2003 /* TODO add config options (how many lines to read, on/off) add interface for plugins/apps to set/get variables add view stuff */ void KTextEditor::DocumentPrivate::readVariables(bool onlyViewAndRenderer) { if (!onlyViewAndRenderer) { m_config->configStart(); } // views! KTextEditor::ViewPrivate *v; foreach (v, m_views) { v->config()->configStart(); v->renderer()->config()->configStart(); } // read a number of lines in the top/bottom of the document for (int i = 0; i < qMin(9, lines()); ++i) { readVariableLine(line(i), onlyViewAndRenderer); } if (lines() > 10) { for (int i = qMax(10, lines() - 10); i < lines(); i++) { readVariableLine(line(i), onlyViewAndRenderer); } } if (!onlyViewAndRenderer) { m_config->configEnd(); } foreach (v, m_views) { v->config()->configEnd(); v->renderer()->config()->configEnd(); } } void KTextEditor::DocumentPrivate::readVariableLine(QString t, bool onlyViewAndRenderer) { static const QRegularExpression kvLine(QStringLiteral("kate:(.*)")); static const QRegularExpression kvLineWildcard(QStringLiteral("kate-wildcard\\((.*)\\):(.*)")); static const QRegularExpression kvLineMime(QStringLiteral("kate-mimetype\\((.*)\\):(.*)")); static const QRegularExpression kvVar(QStringLiteral("([\\w\\-]+)\\s+([^;]+)")); // simple check first, no regex // no kate inside, no vars, simple... if (!t.contains(QLatin1String("kate"))) { return; } // found vars, if any QString s; // now, try first the normal ones auto match = kvLine.match(t); if (match.hasMatch()) { s = match.captured(1); //qCDebug(LOG_KTE) << "normal variable line kate: matched: " << s; } else if ((match = kvLineWildcard.match(t)).hasMatch()) { // regex given const QStringList wildcards(match.captured(1).split(QLatin1Char(';'), QString::SkipEmptyParts)); const QString nameOfFile = url().fileName(); bool found = false; foreach (const QString &pattern, wildcards) { QRegExp wildcard(pattern, Qt::CaseSensitive, QRegExp::Wildcard); found = wildcard.exactMatch(nameOfFile); + if (found) { + break; + } } // nothing usable found... if (!found) { return; } s = match.captured(2); //qCDebug(LOG_KTE) << "guarded variable line kate-wildcard: matched: " << s; } else if ((match = kvLineMime.match(t)).hasMatch()) { // mime-type given const QStringList types(match.captured(1).split(QLatin1Char(';'), QString::SkipEmptyParts)); // no matching type found if (!types.contains(mimeType())) { return; } s = match.captured(2); //qCDebug(LOG_KTE) << "guarded variable line kate-mimetype: matched: " << s; } else { // nothing found return; } // view variable names - static const 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") + static const auto vvl = { + QLatin1String("dynamic-word-wrap") + , QLatin1String("dynamic-word-wrap-indicators") + , QLatin1String("line-numbers") + , QLatin1String("icon-border") + , QLatin1String("folding-markers") + , QLatin1String("folding-preview") + , QLatin1String("bookmark-sorting") + , QLatin1String("auto-center-lines") + , QLatin1String("icon-bar-color") + , QLatin1String("scrollbar-minimap") + , QLatin1String("scrollbar-preview") // renderer - , 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") + , QLatin1String("background-color") + , QLatin1String("selection-color") + , QLatin1String("current-line-color") + , QLatin1String("bracket-highlight-color") + , QLatin1String("word-wrap-marker-color") + , QLatin1String("font") + , QLatin1String("font-size") + , QLatin1String("scheme") }; int spaceIndent = -1; // for backward compatibility; see below bool replaceTabsSet = false; int startPos(0); QString var, val; while ((match = kvVar.match(s, startPos)).hasMatch()) { startPos = match.capturedEnd(0); var = match.captured(1); val = match.captured(2).trimmed(); bool state; // store booleans here int n; // store ints here // only apply view & renderer config stuff if (onlyViewAndRenderer) { - if (vvl.contains(var)) { // FIXME define above + if (contains(vvl, var)) { // FIXME define above setViewVariable(var, val); } } else { // BOOL SETTINGS if (var == QLatin1String("word-wrap") && checkBoolValue(val, &state)) { setWordWrap(state); // ??? FIXME CHECK } // KateConfig::configFlags // FIXME should this be optimized to only a few calls? how? else if (var == QLatin1String("backspace-indents") && checkBoolValue(val, &state)) { m_config->setBackspaceIndents(state); } else if (var == QLatin1String("indent-pasted-text") && checkBoolValue(val, &state)) { m_config->setIndentPastedText(state); } else if (var == QLatin1String("replace-tabs") && checkBoolValue(val, &state)) { m_config->setReplaceTabsDyn(state); replaceTabsSet = true; // for backward compatibility; see below } else if (var == QLatin1String("remove-trailing-space") && checkBoolValue(val, &state)) { qCWarning(LOG_KTE) << i18n("Using deprecated modeline 'remove-trailing-space'. " "Please replace with 'remove-trailing-spaces modified;', see " "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) { + const auto l = { QLatin1String("unix"), QLatin1String("dos"), QLatin1String("mac") }; + if ((n = indexOf(l, val.toLower())) != -1) { /** * set eol + avoid that it is overwritten by auto-detection again! * this fixes e.g. .kateconfig files with // kate: eol dos; to work, bug 365705 */ m_config->setEol(n); m_config->setAllowEolDetection(false); } } else if (var == QLatin1String("bom") || var == QLatin1String("byte-order-mark") || var == QLatin1String("byte-order-marker")) { if (checkBoolValue(val, &state)) { m_config->setBom(state); } } else if (var == QLatin1String("remove-trailing-spaces")) { val = val.toLower(); if (val == QLatin1String("1") || val == QLatin1String("modified") || val == QLatin1String("mod") || val == QLatin1String("+")) { m_config->setRemoveSpaces(1); } else if (val == QLatin1String("2") || val == QLatin1String("all") || val == QLatin1String("*")) { m_config->setRemoveSpaces(2); } else { m_config->setRemoveSpaces(0); } } else if (var == QLatin1String("syntax") || var == QLatin1String("hl")) { setHighlightingMode(val); } else if (var == QLatin1String("mode")) { setMode(val); } else if (var == QLatin1String("encoding")) { setEncoding(val); } else if (var == QLatin1String("default-dictionary")) { setDefaultDictionary(val); } else if (var == QLatin1String("automatic-spell-checking") && checkBoolValue(val, &state)) { onTheFlySpellCheckingEnabled(state); } // VIEW SETTINGS - else if (vvl.contains(var)) { + else if (contains(vvl, var)) { setViewVariable(var, val); } else { m_storedVariables.insert(var, val); } } } // Backward compatibility // If space-indent was set, but replace-tabs was not set, we assume // that the user wants to replace tabulators and set that flag. // If both were set, replace-tabs has precedence. // At this point spaceIndent is -1 if it was never set, // 0 if it was set to off, and 1 if it was set to on. // Note that if onlyViewAndRenderer was requested, spaceIndent is -1. if (!replaceTabsSet && spaceIndent >= 0) { m_config->setReplaceTabsDyn(spaceIndent > 0); } } void KTextEditor::DocumentPrivate::setViewVariable(QString var, QString val) { KTextEditor::ViewPrivate *v; bool state; int n; QColor c; foreach (v, m_views) { if (var == QLatin1String("auto-brackets") && checkBoolValue(val, &state)) { v->config()->setAutoBrackets(state); } 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)) { + static const auto trueValues = { QLatin1String("1"), QLatin1String("on"), QLatin1String("true") }; + if (contains(trueValues, val)) { *result = true; return true; } - static const QStringList falseValues{ QStringLiteral("0"), QStringLiteral("off"), QStringLiteral("false") }; - if (falseValues.contains(val)) { + static const auto falseValues = { QLatin1String("0"), QLatin1String("off"), QLatin1String("false") }; + if (contains(falseValues, val)) { *result = false; return true; } return false; } bool KTextEditor::DocumentPrivate::checkIntValue(QString val, int *result) { bool ret(false); *result = val.toInt(&ret); return ret; } bool KTextEditor::DocumentPrivate::checkColorValue(QString val, QColor &c) { c.setNamedColor(val); return c.isValid(); } // KTextEditor::variable QString KTextEditor::DocumentPrivate::variable(const QString &name) const { return m_storedVariables.value(name, QString()); } void KTextEditor::DocumentPrivate::setVariable(const QString &name, const QString &value) { QString s = QStringLiteral("kate: "); s.append(name); s.append(QLatin1Char(' ')); s.append(value); readVariableLine(s); } //END void KTextEditor::DocumentPrivate::slotModOnHdDirty(const QString &path) { if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != OnDiskModified)) { m_modOnHd = true; m_modOnHdReason = OnDiskModified; if (!m_modOnHdTimer.isActive()) { m_modOnHdTimer.start(); } } } void KTextEditor::DocumentPrivate::slotModOnHdCreated(const QString &path) { if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != OnDiskCreated)) { m_modOnHd = true; m_modOnHdReason = OnDiskCreated; if (!m_modOnHdTimer.isActive()) { m_modOnHdTimer.start(); } } } void KTextEditor::DocumentPrivate::slotModOnHdDeleted(const QString &path) { if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != OnDiskDeleted)) { m_modOnHd = true; m_modOnHdReason = OnDiskDeleted; if (!m_modOnHdTimer.isActive()) { m_modOnHdTimer.start(); } } } void KTextEditor::DocumentPrivate::slotDelayedHandleModOnHd() { // compare git hash with the one we have (if we have one) const QByteArray oldDigest = checksum(); if (!oldDigest.isEmpty() && !url().isEmpty() && url().isLocalFile()) { /** * if current checksum == checksum of new file => unmodified */ if (m_modOnHdReason != OnDiskDeleted && createDigest() && oldDigest == checksum()) { m_modOnHd = false; m_modOnHdReason = OnDiskUnmodified; m_prevModOnHdReason = OnDiskUnmodified; } #if LIBGIT2_FOUND /** * if still modified, try to take a look at git * skip that, if document is modified! * only do that, if the file is still there, else reload makes no sense! */ if (m_modOnHd && !isModified() && QFile::exists(url().toLocalFile())) { /** * try to discover the git repo of this file * libgit2 docs state that UTF-8 is the right encoding, even on windows * I hope that is correct! */ git_repository *repository = nullptr; const QByteArray utf8Path = url().toLocalFile().toUtf8(); if (git_repository_open_ext(&repository, utf8Path.constData(), 0, nullptr) == 0) { /** * if we have repo, convert the git hash to an OID */ git_oid oid; if (git_oid_fromstr(&oid, oldDigest.toHex().data()) == 0) { /** * finally: is there a blob for this git hash? */ git_blob *blob = nullptr; if (git_blob_lookup(&blob, repository, &oid) == 0) { /** * this hash exists still in git => just reload */ m_modOnHd = false; m_modOnHdReason = OnDiskUnmodified; m_prevModOnHdReason = OnDiskUnmodified; documentReload(); } git_blob_free(blob); } } git_repository_free(repository); } #endif } /** * emit our signal to the outside! */ emit modifiedOnDisk(this, m_modOnHd, m_modOnHdReason); } QByteArray KTextEditor::DocumentPrivate::checksum() const { return m_buffer->digest(); } bool KTextEditor::DocumentPrivate::createDigest() { QByteArray digest; if (url().isLocalFile()) { QFile f(url().toLocalFile()); if (f.open(QIODevice::ReadOnly)) { // init the hash with the git header QCryptographicHash crypto(QCryptographicHash::Sha1); const QString header = QStringLiteral("blob %1").arg(f.size()); crypto.addData(header.toLatin1() + '\0'); while (!f.atEnd()) { crypto.addData(f.read(256 * 1024)); } digest = crypto.result(); } } /** * set new digest */ m_buffer->setDigest(digest); return !digest.isEmpty(); } QString KTextEditor::DocumentPrivate::reasonedMOHString() const { // squeeze path const QString str = KStringHandler::csqueeze(url().toDisplayString(QUrl::PreferLocalFile)); switch (m_modOnHdReason) { case OnDiskModified: return i18n("The file '%1' was modified by another program.", str); break; case OnDiskCreated: return i18n("The file '%1' was created by another program.", str); break; case OnDiskDeleted: return i18n("The file '%1' was deleted by another program.", str); break; default: return QString(); } Q_UNREACHABLE(); return QString(); } void KTextEditor::DocumentPrivate::removeTrailingSpaces() { const int remove = config()->removeSpaces(); if (remove == 0) { return; } // temporarily disable static word wrap (see bug #328900) const bool wordWrapEnabled = config()->wordWrap(); if (wordWrapEnabled) { setWordWrap(false); } editStart(); for (int line = 0; line < lines(); ++line) { Kate::TextLine textline = plainKateTextLine(line); // remove trailing spaces in entire document, remove = 2 // remove trailing spaces of touched lines, remove = 1 // remove trailing spaces of lines saved on disk, remove = 1 if (remove == 2 || textline->markedAsModified() || textline->markedAsSavedOnDisk()) { const int p = textline->lastChar() + 1; const int l = textline->length() - p; if (l > 0) { editRemoveText(line, p, l); } } } editEnd(); // enable word wrap again, if it was enabled (see bug #328900) if (wordWrapEnabled) { setWordWrap(true); // see begin of this function } } void KTextEditor::DocumentPrivate::updateFileType(const QString &newType, bool user) { if (user || !m_fileTypeSetByUser) { if (!newType.isEmpty()) { // remember that we got set by user m_fileTypeSetByUser = user; m_fileType = newType; m_config->configStart(); if (!m_hlSetByUser && !KTextEditor::EditorPrivate::self()->modeManager()->fileType(newType).hl.isEmpty()) { int hl(KateHlManager::self()->nameFind(KTextEditor::EditorPrivate::self()->modeManager()->fileType(newType).hl)); if (hl >= 0) { m_buffer->setHighlight(hl); } } /** * set the indentation mode, if any in the mode... * and user did not set it before! */ if (!m_indenterSetByUser && !KTextEditor::EditorPrivate::self()->modeManager()->fileType(newType).indenter.isEmpty()) { config()->setIndentationMode(KTextEditor::EditorPrivate::self()->modeManager()->fileType(newType).indenter); } // views! KTextEditor::ViewPrivate *v; foreach (v, m_views) { v->config()->configStart(); v->renderer()->config()->configStart(); } bool bom_settings = false; if (m_bomSetByUser) { bom_settings = m_config->bom(); } readVariableLine(KTextEditor::EditorPrivate::self()->modeManager()->fileType(newType).varLine); if (m_bomSetByUser) { m_config->setBom(bom_settings); } m_config->configEnd(); foreach (v, m_views) { v->config()->configEnd(); v->renderer()->config()->configEnd(); } } } // fixme, make this better... emit modeChanged(this); } void KTextEditor::DocumentPrivate::slotQueryClose_save(bool *handled, bool *abortClosing) { *handled = true; *abortClosing = true; if (this->url().isEmpty()) { QWidget *parentWidget(dialogParent()); const QUrl res = QFileDialog::getSaveFileUrl(parentWidget, i18n("Save File"), QUrl(), {}, nullptr, QFileDialog::DontConfirmOverwrite); if (res.isEmpty() || !checkOverwrite(res, parentWidget)) { *abortClosing = true; return; } saveAs(res); *abortClosing = false; } else { save(); *abortClosing = false; } } bool KTextEditor::DocumentPrivate::checkOverwrite(QUrl u, QWidget *parent) { if (!u.isLocalFile()) { return true; } QFileInfo info(u.path()); if (!info.exists()) { return true; } return KMessageBox::Cancel != KMessageBox::warningContinueCancel(parent, i18n("A file named \"%1\" already exists. " "Are you sure you want to overwrite it?", info.fileName()), i18n("Overwrite File?"), KStandardGuiItem::overwrite(), KStandardGuiItem::cancel(), QString(), KMessageBox::Options(KMessageBox::Notify | KMessageBox::Dangerous)); } //BEGIN KTextEditor::ConfigInterface // BEGIN ConfigInterface stff QStringList KTextEditor::DocumentPrivate::configKeys() const { 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"), + QLatin1String("backup-on-save-local"), + QLatin1String("backup-on-save-suffix"), + QLatin1String("backup-on-save-prefix"), + QLatin1String("replace-tabs"), + QLatin1String("indent-pasted-text"), + QLatin1String("tab-width"), + QLatin1String("indent-width"), + QLatin1String("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/document/katedocument.h b/src/document/katedocument.h index 9ef90629..fa05c2b3 100644 --- a/src/document/katedocument.h +++ b/src/document/katedocument.h @@ -1,1410 +1,1414 @@ /* 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 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. */ #ifndef _KATE_DOCUMENT_H_ #define _KATE_DOCUMENT_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "katetextline.h" class KateTemplateHandler; namespace KTextEditor { class Plugin; class Attribute; class TemplateScript; } namespace KIO { class TransferJob; } namespace Kate { class SwapFile; } class KateBuffer; namespace KTextEditor { class ViewPrivate; } class KateDocumentConfig; class KateHighlighting; class KateUndoManager; class KateOnTheFlyChecker; class KateDocumentTest; class KateAutoIndent; class KateModOnHdPrompt; /** * @brief Backend of KTextEditor::Document related public KTextEditor interfaces. * * @warning This file is @e private API and not part of the public * KTextEditor interfaces. */ class KTEXTEDITOR_EXPORT KTextEditor::DocumentPrivate : public KTextEditor::Document, public KTextEditor::MarkInterface, public KTextEditor::ModificationInterface, public KTextEditor::ConfigInterface, public KTextEditor::AnnotationInterface, public KTextEditor::MovingInterface, private KTextEditor::MovingRangeFeedback { Q_OBJECT Q_INTERFACES(KTextEditor::MarkInterface) Q_INTERFACES(KTextEditor::ModificationInterface) Q_INTERFACES(KTextEditor::AnnotationInterface) Q_INTERFACES(KTextEditor::ConfigInterface) Q_INTERFACES(KTextEditor::MovingInterface) friend class KTextEditor::Document; friend class ::KateDocumentTest; friend class ::KateBuffer; public: explicit DocumentPrivate(bool bSingleViewMode = false, bool bReadOnly = false, QWidget *parentWidget = nullptr, QObject * = nullptr); - ~DocumentPrivate(); + ~DocumentPrivate() override; using ReadWritePart::closeUrl; - bool closeUrl() Q_DECL_OVERRIDE; + bool closeUrl() override; - bool openUrl(const QUrl &url) Q_DECL_OVERRIDE; + bool openUrl(const QUrl &url) override; KTextEditor::Range rangeOnLine(KTextEditor::Range range, int line) const; private: void showAndSetOpeningErrorAccess(); /* * Overload this to have on-demand view creation */ public: /** * @return The widget defined by this part, set by setWidget(). */ - QWidget *widget() Q_DECL_OVERRIDE; + QWidget *widget() override; public: bool readOnly() const { return m_bReadOnly; } bool singleViewMode() const { return m_bSingleViewMode; } private: // only to make part work, don't change it ! const bool m_bSingleViewMode; const bool m_bReadOnly; // // KTextEditor::Document stuff // public: - KTextEditor::View *createView(QWidget *parent, KTextEditor::MainWindow *mainWindow = nullptr) Q_DECL_OVERRIDE; + KTextEditor::View *createView(QWidget *parent, KTextEditor::MainWindow *mainWindow = nullptr) override; - QList views() const Q_DECL_OVERRIDE + QList views() const override { - return m_views.keys(); + return m_viewsCache; } virtual KTextEditor::View *activeView() const { return m_activeView; } private: QHash m_views; - KTextEditor::View *m_activeView; + KTextEditor::View *m_activeView = nullptr; // // KTextEditor::EditInterface stuff // public Q_SLOTS: - bool setText(const QString &) Q_DECL_OVERRIDE; - bool setText(const QStringList &text) Q_DECL_OVERRIDE; - bool clear() Q_DECL_OVERRIDE; + bool setText(const QString &) override; + bool setText(const QStringList &text) override; + bool clear() override; - bool insertText(const KTextEditor::Cursor &position, const QString &s, bool block = false) Q_DECL_OVERRIDE; - bool insertText(const KTextEditor::Cursor &position, const QStringList &text, bool block = false) Q_DECL_OVERRIDE; + bool insertText(const KTextEditor::Cursor &position, const QString &s, bool block = false) override; + bool insertText(const KTextEditor::Cursor &position, const QStringList &text, bool block = false) override; - bool insertLine(int line, const QString &s) Q_DECL_OVERRIDE; - bool insertLines(int line, const QStringList &s) Q_DECL_OVERRIDE; + bool insertLine(int line, const QString &s) override; + bool insertLines(int line, const QStringList &s) override; - bool removeText(const KTextEditor::Range &range, bool block = false) Q_DECL_OVERRIDE; - bool removeLine(int line) Q_DECL_OVERRIDE; + bool removeText(const KTextEditor::Range &range, bool block = false) override; + bool removeLine(int line) override; - bool replaceText(const KTextEditor::Range &range, const QString &s, bool block = false) Q_DECL_OVERRIDE; + bool replaceText(const KTextEditor::Range &range, const QString &s, bool block = false) override; // unhide method... - bool replaceText(const KTextEditor::Range &r, const QStringList &l, bool b) Q_DECL_OVERRIDE + bool replaceText(const KTextEditor::Range &r, const QStringList &l, bool b) override { return KTextEditor::Document::replaceText(r, l, b); } public: - bool isEditingTransactionRunning() const Q_DECL_OVERRIDE; - QString text(const KTextEditor::Range &range, bool blockwise = false) const Q_DECL_OVERRIDE; - QStringList textLines(const KTextEditor::Range &range, bool block = false) const Q_DECL_OVERRIDE; - QString text() const Q_DECL_OVERRIDE; - QString line(int line) const Q_DECL_OVERRIDE; - QChar characterAt(const KTextEditor::Cursor &position) const Q_DECL_OVERRIDE; - QString wordAt(const KTextEditor::Cursor &cursor) const Q_DECL_OVERRIDE; - KTextEditor::Range wordRangeAt(const KTextEditor::Cursor &cursor) const Q_DECL_OVERRIDE; - bool isValidTextPosition(const KTextEditor::Cursor& cursor) const Q_DECL_OVERRIDE; - int lines() const Q_DECL_OVERRIDE; - bool isLineModified(int line) const Q_DECL_OVERRIDE; - bool isLineSaved(int line) const Q_DECL_OVERRIDE; - bool isLineTouched(int line) const Q_DECL_OVERRIDE; - KTextEditor::Cursor documentEnd() const Q_DECL_OVERRIDE; - int totalCharacters() const Q_DECL_OVERRIDE; - int lineLength(int line) const Q_DECL_OVERRIDE; + bool isEditingTransactionRunning() const override; + QString text(const KTextEditor::Range &range, bool blockwise = false) const override; + QStringList textLines(const KTextEditor::Range &range, bool block = false) const override; + QString text() const override; + QString line(int line) const override; + QChar characterAt(const KTextEditor::Cursor &position) const override; + QString wordAt(const KTextEditor::Cursor &cursor) const override; + KTextEditor::Range wordRangeAt(const KTextEditor::Cursor &cursor) const override; + bool isValidTextPosition(const KTextEditor::Cursor& cursor) const override; + int lines() const override; + bool isLineModified(int line) const override; + bool isLineSaved(int line) const override; + bool isLineTouched(int line) const override; + KTextEditor::Cursor documentEnd() const override; + int totalCharacters() const override; + int lineLength(int line) const override; Q_SIGNALS: void charactersSemiInteractivelyInserted(const KTextEditor::Cursor &position, const QString &text); /** * The \p document emits this signal whenever text was inserted. The * insertion occurred at range.start(), and new text now occupies up to * range.end(). * \param document document which emitted this signal * \param range range that the newly inserted text occupies * \see insertText(), insertLine() */ void textInserted(KTextEditor::Document *document, const KTextEditor::Range &range); /** * The \p document emits this signal whenever \p range was removed, i.e. * text was removed. * \param document document which emitted this signal * \param range range that the removed text previously occupied * \param oldText the text that has been removed * \see removeText(), removeLine(), clear() */ void textRemoved(KTextEditor::Document *document, const KTextEditor::Range &range, const QString &oldText); public: //BEGIN editStart/editEnd (start, end, undo, cursor update, view update) /** * Enclose editor actions with @p editStart() and @p editEnd() to group * them. */ bool editStart(); /** * Alias for @p editStart() */ void editBegin() { editStart(); } /** * End a editor operation. * @see editStart() */ bool editEnd(); void pushEditState(); void popEditState(); virtual bool startEditing() { return editStart(); } virtual bool finishEditing() { return editEnd(); } //END editStart/editEnd void inputMethodStart(); void inputMethodEnd(); //BEGIN LINE BASED INSERT/REMOVE STUFF (editStart() and editEnd() included) /** * Add a string in the given line/column * @param line line number * @param col column * @param s string to be inserted * @return true on success */ bool editInsertText(int line, int col, const QString &s); /** * Remove a string in the given line/column * @param line line number * @param col column * @param len length of text to be removed * @return true on success */ bool editRemoveText(int line, int col, int len); /** * Mark @p line as @p autowrapped. This is necessary if static word warp is * enabled, because we have to know whether to insert a new line or add the * wrapped words to the followin line. * @param line line number * @param autowrapped autowrapped? * @return true on success */ bool editMarkLineAutoWrapped(int line, bool autowrapped); /** * Wrap @p line. If @p newLine is true, ignore the textline's flag * KateTextLine::flagAutoWrapped and force a new line. Whether a new line * was needed/added you can grab with @p newLineAdded. * @param line line number * @param col column * @param newLine if true, force a new line * @param newLineAdded return value is true, if new line was added (may be 0) * @return true on success */ bool editWrapLine(int line, int col, bool newLine = true, bool *newLineAdded = nullptr); /** * Unwrap @p line. If @p removeLine is true, we force to join the lines. If * @p removeLine is true, @p length is ignored (eg not needed). * @param line line number * @param removeLine if true, force to remove the next line * @return true on success */ bool editUnWrapLine(int line, bool removeLine = true, int length = 0); /** * Insert a string at the given line. * @param line line number * @param s string to insert * @return true on success */ bool editInsertLine(int line, const QString &s); /** * Remove a line * @param line line number * @return true on success */ bool editRemoveLine(int line); bool editRemoveLines(int from, int to); /** * Remove a line * @param startLine line to begin wrapping * @param endLine line to stop wrapping * @return true on success */ bool wrapText(int startLine, int endLine); //END LINE BASED INSERT/REMOVE STUFF Q_SIGNALS: /** * Emmitted when text from @p line was wrapped at position pos onto line @p nextLine. */ void editLineWrapped(int line, int col, int len); /** * Emitted each time text from @p nextLine was upwrapped onto @p line. */ void editLineUnWrapped(int line, int col); public: bool isEditRunning() const; void setUndoMergeAllEdits(bool merge); enum EditingPositionKind { Previous, Next }; /** *Returns the next or previous position cursor in this document from the stack depending on the argument passed. *@return cursor invalid if m_editingStack empty */ KTextEditor::Cursor lastEditingPosition(EditingPositionKind nextOrPrevious, KTextEditor::Cursor); private: - int editSessionNumber; + int editSessionNumber = 0; QStack editStateStack; - bool editIsRunning; - bool m_undoMergeAllEdits; + bool editIsRunning = false; + bool m_undoMergeAllEdits = false; QStack> m_editingStack; int m_editingStackPosition = -1; static const int s_editingStackSizeLimit = 32; // // KTextEditor::UndoInterface stuff // public Q_SLOTS: void undo(); void redo(); /** * Removes all the elements in m_editingStack of the respective document. */ void clearEditingPosStack(); /** * Saves the editing positions into the stack. * If the consecutive editings happens in the same line, then remove * the previous and add the new one with updated column no. */ void saveEditingPositions(KTextEditor::Document *, const KTextEditor::Range &range); public: uint undoCount() const; uint redoCount() const; KateUndoManager *undoManager() { return m_undoManager; } protected: KateUndoManager *const m_undoManager; Q_SIGNALS: void undoChanged(); public: QVector searchText( const KTextEditor::Range &range, const QString &pattern, const KTextEditor::SearchOptions options) const; private: /** * Return a widget suitable to be used as a dialog parent. */ QWidget *dialogParent(); /* * Access to the mode/highlighting subsystem */ public: /** * @copydoc KTextEditor::Document::defaultStyleAt() */ - KTextEditor::DefaultStyle defaultStyleAt(const KTextEditor::Cursor &position) const Q_DECL_OVERRIDE; + KTextEditor::DefaultStyle defaultStyleAt(const KTextEditor::Cursor &position) const override; /** * Return the name of the currently used mode * \return name of the used mode */ - QString mode() const Q_DECL_OVERRIDE; + QString mode() const override; /** * Return the name of the currently used mode * \return name of the used mode */ - QString highlightingMode() const Q_DECL_OVERRIDE; + QString highlightingMode() const override; /** * Return a list of the names of all possible modes * \return list of mode names */ - QStringList modes() const Q_DECL_OVERRIDE; + QStringList modes() const override; /** * Return a list of the names of all possible modes * \return list of mode names */ - QStringList highlightingModes() const Q_DECL_OVERRIDE; + QStringList highlightingModes() const override; /** * Set the current mode of the document by giving its name * \param name name of the mode to use for this document * \return \e true on success, otherwise \e false */ - bool setMode(const QString &name) Q_DECL_OVERRIDE; + bool setMode(const QString &name) override; /** * Set the current mode of the document by giving its name * \param name name of the mode to use for this document * \return \e true on success, otherwise \e false */ - bool setHighlightingMode(const QString &name) Q_DECL_OVERRIDE; + bool setHighlightingMode(const QString &name) override; /** * Returns the name of the section for a highlight given its @p index in the highlight * list (as returned by highlightModes()). * You can use this function to build a tree of the highlight names, organized in sections. * \param index in the highlight list for which to find the section name. */ - QString highlightingModeSection(int index) const Q_DECL_OVERRIDE; + QString highlightingModeSection(int index) const override; /** * Returns the name of the section for a mode given its @p index in the highlight * list (as returned by modes()). * You can use this function to build a tree of the mode names, organized in sections. * \param index index in the highlight list for which to find the section name. */ - QString modeSection(int index) const Q_DECL_OVERRIDE; + QString modeSection(int index) const override; /* * Helpers.... */ public: void bufferHlChanged(); /** * allow to mark, that we changed hl on user wish and should not reset it * atm used for the user visible menu to select highlightings */ void setDontChangeHlOnSave(); /** * Set that the BOM marker is forced via the tool menu */ void bomSetByUser(); public: /** * Read session settings from the given \p config. * * Known flags: * "SkipUrl" => don't save/restore the file * "SkipMode" => don't save/restore the mode * "SkipHighlighting" => don't save/restore the highlighting * "SkipEncoding" => don't save/restore the encoding * * \param config read the session settings from this KConfigGroup * \param flags additional flags * \see writeSessionConfig() */ - void readSessionConfig(const KConfigGroup &config, const QSet &flags = QSet()) Q_DECL_OVERRIDE; + void readSessionConfig(const KConfigGroup &config, const QSet &flags = QSet()) override; /** * Write session settings to the \p config. * See readSessionConfig() for more details. * * \param config write the session settings to this KConfigGroup * \param flags additional flags * \see readSessionConfig() */ - void writeSessionConfig(KConfigGroup &config, const QSet &flags = QSet()) Q_DECL_OVERRIDE; + void writeSessionConfig(KConfigGroup &config, const QSet &flags = QSet()) override; Q_SIGNALS: void configChanged(); // // KTextEditor::MarkInterface // public Q_SLOTS: - void setMark(int line, uint markType) Q_DECL_OVERRIDE; - void clearMark(int line) Q_DECL_OVERRIDE; + void setMark(int line, uint markType) override; + void clearMark(int line) override; - void addMark(int line, uint markType) Q_DECL_OVERRIDE; - void removeMark(int line, uint markType) Q_DECL_OVERRIDE; + void addMark(int line, uint markType) override; + void removeMark(int line, uint markType) override; - void clearMarks() Q_DECL_OVERRIDE; + void clearMarks() override; void requestMarkTooltip(int line, QPoint position); ///Returns true if the click on the mark should not be further processed bool handleMarkClick(int line); ///Returns true if the context-menu event should not further be processed bool handleMarkContextMenu(int line, QPoint position); - void setMarkPixmap(MarkInterface::MarkTypes, const QPixmap &) Q_DECL_OVERRIDE; + void setMarkPixmap(MarkInterface::MarkTypes, const QPixmap &) override; - void setMarkDescription(MarkInterface::MarkTypes, const QString &) Q_DECL_OVERRIDE; + void setMarkDescription(MarkInterface::MarkTypes, const QString &) override; - void setEditableMarks(uint markMask) Q_DECL_OVERRIDE; + void setEditableMarks(uint markMask) override; public: - uint mark(int line) Q_DECL_OVERRIDE; - const QHash &marks() Q_DECL_OVERRIDE; - QPixmap markPixmap(MarkInterface::MarkTypes) const Q_DECL_OVERRIDE; - QString markDescription(MarkInterface::MarkTypes) const Q_DECL_OVERRIDE; + uint mark(int line) override; + const QHash &marks() override; + QPixmap markPixmap(MarkInterface::MarkTypes) const override; + QString markDescription(MarkInterface::MarkTypes) const override; virtual QColor markColor(MarkInterface::MarkTypes) const; - uint editableMarks() const Q_DECL_OVERRIDE; + uint editableMarks() const override; Q_SIGNALS: void markToolTipRequested(KTextEditor::Document *document, KTextEditor::Mark mark, QPoint position, bool &handled); void markContextMenuRequested(KTextEditor::Document *document, KTextEditor::Mark mark, QPoint pos, bool &handled); void markClicked(KTextEditor::Document *document, KTextEditor::Mark mark, bool &handled); - void marksChanged(KTextEditor::Document *) Q_DECL_OVERRIDE; - void markChanged(KTextEditor::Document *, KTextEditor::Mark, KTextEditor::MarkInterface::MarkChangeAction) Q_DECL_OVERRIDE; + void marksChanged(KTextEditor::Document *) override; + void markChanged(KTextEditor::Document *, KTextEditor::Mark, KTextEditor::MarkInterface::MarkChangeAction) override; private: QHash m_marks; QHash m_markPixmaps; QHash m_markDescriptions; - uint m_editableMarks; + uint m_editableMarks = markType01; // KTextEditor::PrintInterface // public Q_SLOTS: - bool print() Q_DECL_OVERRIDE; - void printPreview() Q_DECL_OVERRIDE; + bool print() override; + void printPreview() override; // // KTextEditor::DocumentInfoInterface ( ### unfinished ) // public: /** * Tries to detect mime-type based on file name and content of buffer. * * @return the name of the mimetype for the document. */ - QString mimeType() Q_DECL_OVERRIDE; + QString mimeType() override; // // once was KTextEditor::VariableInterface // public: /** * Returns the value for the variable @p name. * If the Document does not have a variable called @p name, * an empty QString() is returned. * * @param name variable to query * @return value of the variable @p name * @see setVariable() */ virtual QString variable(const QString &name) const; /** * Set the variable @p name to @p value. Setting and changing a variable * has immediate effect on the Document. For instance, setting the variable * @e indent-mode to @e cstyle will immediately cause the Document to load * the C Style indenter. * * @param name the variable name * @param value the value to be set * @see variable() */ virtual void setVariable(const QString &name, const QString &value); private: QMap m_storedVariables; // // MovingInterface API // public: /** * Create a new moving cursor for this document. * @param position position of the moving cursor to create * @param insertBehavior insertion behavior * @return new moving cursor for the document */ - KTextEditor::MovingCursor *newMovingCursor(const KTextEditor::Cursor &position, KTextEditor::MovingCursor::InsertBehavior insertBehavior = KTextEditor::MovingCursor::MoveOnInsert) Q_DECL_OVERRIDE; + KTextEditor::MovingCursor *newMovingCursor(const KTextEditor::Cursor &position, KTextEditor::MovingCursor::InsertBehavior insertBehavior = KTextEditor::MovingCursor::MoveOnInsert) override; /** * Create a new moving range for this document. * @param range range of the moving range to create * @param insertBehaviors insertion behaviors * @param emptyBehavior behavior on becoming empty * @return new moving range for the document */ - virtual KTextEditor::MovingRange *newMovingRange(const KTextEditor::Range &range, KTextEditor::MovingRange::InsertBehaviors insertBehaviors = KTextEditor::MovingRange::DoNotExpand - , KTextEditor::MovingRange::EmptyBehavior emptyBehavior = KTextEditor::MovingRange::AllowEmpty) Q_DECL_OVERRIDE; + KTextEditor::MovingRange *newMovingRange(const KTextEditor::Range &range, KTextEditor::MovingRange::InsertBehaviors insertBehaviors = KTextEditor::MovingRange::DoNotExpand + , KTextEditor::MovingRange::EmptyBehavior emptyBehavior = KTextEditor::MovingRange::AllowEmpty) override; /** * Current revision * @return current revision */ - qint64 revision() const Q_DECL_OVERRIDE; + qint64 revision() const override; /** * Last revision the buffer got successful saved * @return last revision buffer got saved, -1 if none */ - qint64 lastSavedRevision() const Q_DECL_OVERRIDE; + qint64 lastSavedRevision() const override; /** * Lock a revision, this will keep it around until released again. * But all revisions will always be cleared on buffer clear() (and therefor load()) * @param revision revision to lock */ - void lockRevision(qint64 revision) Q_DECL_OVERRIDE; + void lockRevision(qint64 revision) override; /** * Release a revision. * @param revision revision to release */ - void unlockRevision(qint64 revision) Q_DECL_OVERRIDE; + void unlockRevision(qint64 revision) override; /** * Transform a cursor from one revision to an other. * @param cursor cursor to transform * @param insertBehavior behavior of this cursor on insert of text at its position * @param fromRevision from this revision we want to transform * @param toRevision to this revision we want to transform, default of -1 is current revision */ - void transformCursor(KTextEditor::Cursor &cursor, KTextEditor::MovingCursor::InsertBehavior insertBehavior, qint64 fromRevision, qint64 toRevision = -1) Q_DECL_OVERRIDE; + void transformCursor(KTextEditor::Cursor &cursor, KTextEditor::MovingCursor::InsertBehavior insertBehavior, qint64 fromRevision, qint64 toRevision = -1) override; /** * Transform a cursor from one revision to an other. * @param line line number of the cursor to transform * @param column column number of the cursor to transform * @param insertBehavior behavior of this cursor on insert of text at its position * @param fromRevision from this revision we want to transform * @param toRevision to this revision we want to transform, default of -1 is current revision */ - void transformCursor(int &line, int &column, KTextEditor::MovingCursor::InsertBehavior insertBehavior, qint64 fromRevision, qint64 toRevision = -1) Q_DECL_OVERRIDE; + void transformCursor(int &line, int &column, KTextEditor::MovingCursor::InsertBehavior insertBehavior, qint64 fromRevision, qint64 toRevision = -1) override; /** * Transform a range from one revision to an other. * @param range range to transform * @param insertBehaviors behavior of this range on insert of text at its position * @param emptyBehavior behavior on becoming empty * @param fromRevision from this revision we want to transform * @param toRevision to this revision we want to transform, default of -1 is current revision */ - void transformRange(KTextEditor::Range &range, KTextEditor::MovingRange::InsertBehaviors insertBehaviors, KTextEditor::MovingRange::EmptyBehavior emptyBehavior, qint64 fromRevision, qint64 toRevision = -1) Q_DECL_OVERRIDE; + void transformRange(KTextEditor::Range &range, KTextEditor::MovingRange::InsertBehaviors insertBehaviors, KTextEditor::MovingRange::EmptyBehavior emptyBehavior, qint64 fromRevision, qint64 toRevision = -1) override; // // MovingInterface Signals // Q_SIGNALS: /** * This signal is emitted before the cursors/ranges/revisions of a document are destroyed as the document is deleted. * @param document the document which the interface belongs too which is in the process of being deleted */ void aboutToDeleteMovingInterfaceContent(KTextEditor::Document *document); /** * This signal is emitted before the ranges of a document are invalidated and the revisions are deleted as the document is cleared (for example on load/reload). * While this signal is emitted, still the old document content is around before the clear. * @param document the document which the interface belongs too which will invalidate its data */ void aboutToInvalidateMovingInterfaceContent(KTextEditor::Document *document); // // Annotation Interface // public: - void setAnnotationModel(KTextEditor::AnnotationModel *model) Q_DECL_OVERRIDE; - KTextEditor::AnnotationModel *annotationModel() const Q_DECL_OVERRIDE; + void setAnnotationModel(KTextEditor::AnnotationModel *model) override; + KTextEditor::AnnotationModel *annotationModel() const override; Q_SIGNALS: void annotationModelChanged(KTextEditor::AnnotationModel *, KTextEditor::AnnotationModel *); private: - KTextEditor::AnnotationModel *m_annotationModel; + KTextEditor::AnnotationModel *m_annotationModel = nullptr; // // KParts::ReadWrite stuff // public: /** * open the file obtained by the kparts framework * the framework abstracts the loading of remote files * @return success */ - bool openFile() Q_DECL_OVERRIDE; + bool openFile() override; /** * save the file obtained by the kparts framework * the framework abstracts the uploading of remote files * @return success */ - bool saveFile() Q_DECL_OVERRIDE; + bool saveFile() override; - void setReadWrite(bool rw = true) Q_DECL_OVERRIDE; + void setReadWrite(bool rw = true) override; - void setModified(bool m) Q_DECL_OVERRIDE; + void setModified(bool m) override; private: void activateDirWatch(const QString &useFileName = QString()); void deactivateDirWatch(); QString m_dirWatchFile; /** * Make backup copy during saveFile, if configured that way. * @return success? else saveFile should return false and not write the file */ bool createBackupFile(); public: /** * Type chars in a view. * Will filter out non-printable chars from the realChars array before inserting. */ bool typeChars(KTextEditor::ViewPrivate *type, const QString &realChars); /** * gets the last line number (lines() - 1) */ inline int lastLine() const { return lines() - 1; } // Repaint all of all of the views void repaintViews(bool paintOnlyDirty = true); KateHighlighting *highlight() const; public Q_SLOTS: void tagLines(int start, int end); private Q_SLOTS: void internalHlChanged(); public: void addView(KTextEditor::View *); /** removes the view from the list of views. The view is *not* deleted. * That's your job. Or, easier, just delete the view in the first place. * It will remove itself. TODO: this could be converted to a private slot * connected to the view's destroyed() signal. It is not currently called * anywhere except from the KTextEditor::ViewPrivate destructor. */ void removeView(KTextEditor::View *); void setActiveView(KTextEditor::View *); bool ownedView(KTextEditor::ViewPrivate *); int toVirtualColumn(int line, int column) const; int toVirtualColumn(const KTextEditor::Cursor &) const; int fromVirtualColumn(int line, int column) const; int fromVirtualColumn(const KTextEditor::Cursor &) const; void newLine(KTextEditor::ViewPrivate *view); // Changes input void backspace(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &); void del(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &); void transpose(const KTextEditor::Cursor &); void paste(KTextEditor::ViewPrivate *view, const QString &text); public: void indent(KTextEditor::Range range, int change); void comment(KTextEditor::ViewPrivate *view, uint line, uint column, int change); void align(KTextEditor::ViewPrivate *view, const KTextEditor::Range &range); void insertTab(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &); enum TextTransform { Uppercase, Lowercase, Capitalize }; /** Handling uppercase, lowercase and capitalize for the view. If there is a selection, that is transformed, otherwise for uppercase or lowercase the character right of the cursor is transformed, for capitalize the word under the cursor is transformed. */ void transform(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &, TextTransform); /** Unwrap a range of lines. */ void joinLines(uint first, uint last); private: bool removeStringFromBeginning(int line, const QString &str); bool removeStringFromEnd(int line, const QString &str); /** Expand tabs to spaces in typed text, if enabled. @param cursorPos The current cursor position for the inserted characters. @param str The typed characters to expand. */ QString eventuallyReplaceTabs(const KTextEditor::Cursor &cursorPos, const QString &str) const; /** Find the position (line and col) of the next char that is not a space. If found line and col point to the found character. Otherwise they have both the value -1. @param line Line of the character which is examined first. @param col Column of the character which is examined first. @return True if the specified or a following character is not a space Otherwise false. */ bool nextNonSpaceCharPos(int &line, int &col); /** Find the position (line and col) of the previous char that is not a space. If found line and col point to the found character. Otherwise they have both the value -1. @return True if the specified or a preceding character is not a space. Otherwise false. */ bool previousNonSpaceCharPos(int &line, int &col); /** * Sets a comment marker as defined by the language providing the attribute * @p attrib on the line @p line */ void addStartLineCommentToSingleLine(int line, int attrib = 0); /** * Removes a comment marker as defined by the language providing the attribute * @p attrib on the line @p line */ bool removeStartLineCommentFromSingleLine(int line, int attrib = 0); /** * @see addStartLineCommentToSingleLine. */ void addStartStopCommentToSingleLine(int line, int attrib = 0); /** *@see removeStartLineCommentFromSingleLine. */ bool removeStartStopCommentFromSingleLine(int line, int attrib = 0); /** *@see removeStartLineCommentFromSingleLine. */ bool removeStartStopCommentFromRegion(const KTextEditor::Cursor &start, const KTextEditor::Cursor &end, int attrib = 0); /** * Add a comment marker as defined by the language providing the attribute * @p attrib to each line in the selection. */ void addStartStopCommentToSelection(KTextEditor::ViewPrivate *view, int attrib = 0); /** * @see addStartStopCommentToSelection. */ void addStartLineCommentToSelection(KTextEditor::ViewPrivate *view, int attrib = 0); /** * Removes comment markers relevant to the language providing * the attribuge @p attrib from each line in the selection. * * @return whether the operation succeeded. */ bool removeStartStopCommentFromSelection(KTextEditor::ViewPrivate *view, int attrib = 0); /** * @see removeStartStopCommentFromSelection. */ bool removeStartLineCommentFromSelection(KTextEditor::ViewPrivate *view, int attrib = 0); public: KTextEditor::Range findMatchingBracket(const KTextEditor::Cursor & start, int maxLines); public: - QString documentName() const Q_DECL_OVERRIDE + QString documentName() const override { return m_docName; } private: void updateDocName(); public: /** * @return whether the document is modified on disk since last saved */ bool isModifiedOnDisc() { return m_modOnHd; } - void setModifiedOnDisk(ModifiedOnDiskReason reason) Q_DECL_OVERRIDE; + void setModifiedOnDisk(ModifiedOnDiskReason reason) override; - void setModifiedOnDiskWarning(bool on) Q_DECL_OVERRIDE; + void setModifiedOnDiskWarning(bool on) override; public Q_SLOTS: /** * Ask the user what to do, if the file has been modified on disk. * Reimplemented from KTextEditor::Document. */ virtual void slotModifiedOnDisk(KTextEditor::View *v = nullptr); /** * Reloads the current document from disk if possible */ - bool documentReload() Q_DECL_OVERRIDE; + bool documentReload() override; - bool documentSave() Q_DECL_OVERRIDE; - bool documentSaveAs() Q_DECL_OVERRIDE; + bool documentSave() override; + bool documentSaveAs() override; bool documentSaveAsWithEncoding(const QString &encoding); bool documentSaveCopyAs(); - bool save() Q_DECL_OVERRIDE; + bool save() override; public: - bool saveAs(const QUrl &url) Q_DECL_OVERRIDE; + bool saveAs(const QUrl &url) override; Q_SIGNALS: /** * Indicate this file is modified on disk * @param doc the KTextEditor::Document object that represents the file on disk * @param isModified indicates the file was modified rather than created or deleted * @param reason the reason we are emitting the signal. */ - void modifiedOnDisk(KTextEditor::Document *doc, bool isModified, KTextEditor::ModificationInterface::ModifiedOnDiskReason reason) Q_DECL_OVERRIDE; + void modifiedOnDisk(KTextEditor::Document *doc, bool isModified, KTextEditor::ModificationInterface::ModifiedOnDiskReason reason) override; private: // helper to handle the embedded notification for externally modified files QPointer m_modOnHdHandler; private Q_SLOTS: void onModOnHdSaveAs(); void onModOnHdReload(); void onModOnHdIgnore(); public: - bool setEncoding(const QString &e) Q_DECL_OVERRIDE; - QString encoding() const Q_DECL_OVERRIDE; + bool setEncoding(const QString &e) override; + QString encoding() const override; public Q_SLOTS: void setWordWrap(bool on); void setWordWrapAt(uint col); public: bool wordWrap() const; uint wordWrapAt() const; public Q_SLOTS: void setPageUpDownMovesCursor(bool on); public: bool pageUpDownMovesCursor() const; // code folding public: /** * Same as plainKateTextLine(), except that it is made sure * the line is highlighted. */ Kate::TextLine kateTextLine(int i); //! @copydoc KateBuffer::plainLine() Kate::TextLine plainKateTextLine(int i); Q_SIGNALS: void aboutToRemoveText(const KTextEditor::Range &); private Q_SLOTS: void slotModOnHdDirty(const QString &path); void slotModOnHdCreated(const QString &path); void slotModOnHdDeleted(const QString &path); void slotDelayedHandleModOnHd(); private: /** * Create a git compatible sha1 checksum of the file, if it is a local file. * The result can be accessed through KateBuffer::digest(). * * @return wheather the operation was attempted and succeeded. */ bool createDigest(); /** * create a string for the modonhd warnings, giving the reason. */ QString reasonedMOHString() const; /** * Removes all trailing whitespace in the document. */ void removeTrailingSpaces(); public: /** * Returns a git compatible sha1 checksum of this document on disk. * @return checksum for this document on disk */ - QByteArray checksum() const Q_DECL_OVERRIDE; + QByteArray checksum() const override; void updateFileType(const QString &newType, bool user = false); QString fileType() const { return m_fileType; } /** * Get access to buffer of this document. * Is needed to create cursors and ranges for example. * @return document buffer */ KateBuffer &buffer() { return *m_buffer; } /** * set indentation mode by user * this will remember that a user did set it and will avoid reset on save */ void rememberUserDidSetIndentationMode() { m_indenterSetByUser = true; } /** * User did set encoding for next reload => enforce it! */ void userSetEncodingForNextReload() { m_userSetEncodingForNextReload = true; } // // REALLY internal data ;) // private: // text buffer KateBuffer *const m_buffer; // indenter KateAutoIndent *const m_indenter; - bool m_hlSetByUser; - bool m_bomSetByUser; - bool m_indenterSetByUser; - bool m_userSetEncodingForNextReload; + bool m_hlSetByUser = false; + bool m_bomSetByUser = false; + bool m_indenterSetByUser = false; + bool m_userSetEncodingForNextReload = false; - bool m_modOnHd; - ModifiedOnDiskReason m_modOnHdReason; - ModifiedOnDiskReason m_prevModOnHdReason; + bool m_modOnHd = false; + ModifiedOnDiskReason m_modOnHdReason = OnDiskUnmodified; + ModifiedOnDiskReason m_prevModOnHdReason = OnDiskUnmodified; QString m_docName; - int m_docNameNumber; + int m_docNameNumber = 0; // file type !!! QString m_fileType; - bool m_fileTypeSetByUser; + bool m_fileTypeSetByUser = false; /** * document is still reloading a file */ - bool m_reloading; + bool m_reloading = false; public Q_SLOTS: void slotQueryClose_save(bool *handled, bool *abortClosing); public: - bool queryClose() Q_DECL_OVERRIDE; + bool queryClose() override; void makeAttribs(bool needInvalidate = true); static bool checkOverwrite(QUrl u, QWidget *parent); /** * Configuration */ public: KateDocumentConfig *config() { return m_config; } KateDocumentConfig *config() const { return m_config; } void updateConfig(); private: KateDocumentConfig *const m_config; /** * Variable Reader * TODO add register functionality/ktexteditor interface */ private: /** * read dir config file */ void readDirConfig(); /** Reads all the variables in the document. Called when opening/saving a document */ void readVariables(bool onlyViewAndRenderer = false); /** Reads and applies the variables in a single line TODO registered variables gets saved in a [map] */ void readVariableLine(QString t, bool onlyViewAndRenderer = false); /** Sets a view variable in all the views. */ void setViewVariable(QString var, QString val); /** @return weather a string value could be converted to a bool value as supported. The value is put in *result. */ static bool checkBoolValue(QString value, bool *result); /** @return weather a string value could be converted to a integer value. The value is put in *result. */ static bool checkIntValue(QString value, int *result); /** Feeds value into @p col using QColor::setNamedColor() and returns wheather the color is valid */ static bool checkColorValue(QString value, QColor &col); - bool m_fileChangedDialogsActivated; + bool m_fileChangedDialogsActivated = false; // // KTextEditor::ConfigInterface // public: - QStringList configKeys() const Q_DECL_OVERRIDE; - QVariant configValue(const QString &key) Q_DECL_OVERRIDE; - void setConfigValue(const QString &key, const QVariant &value) Q_DECL_OVERRIDE; + QStringList configKeys() const override; + QVariant configValue(const QString &key) override; + void setConfigValue(const QString &key, const QVariant &value) override; // // KTextEditor::RecoveryInterface // public: - bool isDataRecoveryAvailable() const Q_DECL_OVERRIDE; - void recoverData() Q_DECL_OVERRIDE; - void discardDataRecovery() Q_DECL_OVERRIDE; + bool isDataRecoveryAvailable() const override; + void recoverData() override; + void discardDataRecovery() override; // // Highlighting information // public: - QStringList embeddedHighlightingModes() const Q_DECL_OVERRIDE; - QString highlightingModeAt(const KTextEditor::Cursor &position) Q_DECL_OVERRIDE; + QStringList embeddedHighlightingModes() const override; + QString highlightingModeAt(const KTextEditor::Cursor &position) override; // TODO KDE5: move to View virtual KTextEditor::Attribute::Ptr attributeAt(const KTextEditor::Cursor &position); // //BEGIN: KTextEditor::MessageInterface // public: - bool postMessage(KTextEditor::Message *message) Q_DECL_OVERRIDE; + bool postMessage(KTextEditor::Message *message) override; public Q_SLOTS: void messageDestroyed(KTextEditor::Message *message); private: QHash > > m_messageHash; //END KTextEditor::MessageInterface public: QString defaultDictionary() const; QList > dictionaryRanges() const; bool isOnTheFlySpellCheckingEnabled() const; QString dictionaryForMisspelledRange(const KTextEditor::Range &range) const; void clearMisspellingForWord(const QString &word); public Q_SLOTS: void clearDictionaryRanges(); void setDictionary(const QString &dict, const KTextEditor::Range &range); void revertToDefaultDictionary(const KTextEditor::Range &range); void setDefaultDictionary(const QString &dict); void onTheFlySpellCheckingEnabled(bool enable); void refreshOnTheFlyCheck(const KTextEditor::Range &range = KTextEditor::Range::invalid()); Q_SIGNALS: void dictionaryRangesPresent(bool yesNo); void defaultDictionaryChanged(KTextEditor::DocumentPrivate *document); public: bool containsCharacterEncoding(const KTextEditor::Range &range); typedef QList > OffsetList; int computePositionWrtOffsets(const OffsetList &offsetList, int pos); /** * The first OffsetList is from decoded to encoded, and the second OffsetList from * encoded to decoded. **/ QString decodeCharacters(const KTextEditor::Range &range, KTextEditor::DocumentPrivate::OffsetList &decToEncOffsetList, KTextEditor::DocumentPrivate::OffsetList &encToDecOffsetList); void replaceCharactersByEncoding(const KTextEditor::Range &range); enum EncodedCharaterInsertionPolicy {EncodeAlways, EncodeWhenPresent, EncodeNever}; protected: - KateOnTheFlyChecker *m_onTheFlyChecker; + KateOnTheFlyChecker *m_onTheFlyChecker = nullptr; QString m_defaultDictionary; QList > m_dictionaryRanges; // from KTextEditor::MovingRangeFeedback - void rangeInvalid(KTextEditor::MovingRange *movingRange) Q_DECL_OVERRIDE; - void rangeEmpty(KTextEditor::MovingRange *movingRange) Q_DECL_OVERRIDE; + void rangeInvalid(KTextEditor::MovingRange *movingRange) override; + void rangeEmpty(KTextEditor::MovingRange *movingRange) override; void deleteDictionaryRange(KTextEditor::MovingRange *movingRange); private: Kate::SwapFile *m_swapfile; public: Kate::SwapFile *swapFile(); //helpers for scripting and codefolding int defStyleNum(int line, int column); bool isComment(int line, int column); public: /** * Find the next modified/saved line, starting at @p startLine. If @p down * is \e true, the search is performed downwards, otherwise upwards. * @return the touched line in the requested search direction, or -1 if not found */ int findTouchedLine(int startLine, bool down); private Q_SLOTS: /** * watch for all started io jobs to remember if file is perhaps loading atm * @param job started job */ void slotStarted(KIO::Job *job); void slotCompleted(); void slotCanceled(); /** * trigger display of loading message, after 1000 ms */ void slotTriggerLoadingMessage(); /** * Abort loading */ void slotAbortLoading(); void slotUrlChanged(const QUrl &url); private: /** * different possible states */ enum DocumentStates { /** * Idle */ DocumentIdle, /** * Loading */ DocumentLoading, /** * Saving */ DocumentSaving, /** * Pre Saving As, this is between ::saveAs is called and ::save */ DocumentPreSavingAs, /** * Saving As */ DocumentSavingAs }; /** * current state */ - DocumentStates m_documentState; + DocumentStates m_documentState = DocumentIdle; /** * read-write state before loading started */ - bool m_readWriteStateBeforeLoading; + bool m_readWriteStateBeforeLoading = false; /** * if the document is untitled */ - bool m_isUntitled; + bool m_isUntitled = true; /** * loading job, we want to cancel with cancel in the loading message */ QPointer m_loadingJob; /** * message to show during loading */ QPointer m_loadingMessage; /** * Was there any open error on last file loading? */ - bool m_openingError; + bool m_openingError = false; /** * Last open file error message */ QString m_openingErrorMessage; public: /** * reads the line length limit from config, if it is not overriden */ int lineLengthLimit() const; public Q_SLOTS: void openWithLineLengthLimitOverride(); private: /** * timer for delayed handling of mod on hd */ QTimer m_modOnHdTimer; private: /** * currently active template handler; there can be only one */ QPointer m_activeTemplateHandler; private: /** * current autobrace range */ QSharedPointer m_currentAutobraceRange; /** * current autobrace closing charater (e.g. ']') */ QChar m_currentAutobraceClosingChar; private Q_SLOTS: void checkCursorForAutobrace(KTextEditor::View* view, const KTextEditor::Cursor& newPos); public: void setActiveTemplateHandler(KateTemplateHandler* handler); Q_SIGNALS: void loaded(KTextEditor::DocumentPrivate *document); private Q_SLOTS: /** * trigger a close of this document in the application */ void closeDocumentInApplication(); +private: + // To calculate a QHash.keys() is quite expensive, + // better keep a copy of that list updated when a view is added or removed. + QList m_viewsCache; }; #endif diff --git a/src/export/htmlexporter.h b/src/export/htmlexporter.h index a40485a0..5492213d 100644 --- a/src/export/htmlexporter.h +++ b/src/export/htmlexporter.h @@ -1,38 +1,38 @@ /** * This file is part of the KDE libraries * Copyright (C) 2009 Milian Wolff * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef HTMLEXPORTER_H #define HTMLEXPORTER_H #include "abstractexporter.h" /// TODO: add abstract interface for future exporters class HTMLExporter : public AbstractExporter { public: HTMLExporter(KTextEditor::View *view, QTextStream &output, const bool withHeaderFooter = false); - virtual ~HTMLExporter(); + ~HTMLExporter() override; - void openLine() Q_DECL_OVERRIDE; - void closeLine(const bool lastLine) Q_DECL_OVERRIDE; - void exportText(const QString &text, const KTextEditor::Attribute::Ptr &attrib) Q_DECL_OVERRIDE; + void openLine() override; + void closeLine(const bool lastLine) override; + void exportText(const QString &text, const KTextEditor::Attribute::Ptr &attrib) override; }; #endif diff --git a/src/include/ktexteditor/codecompletionmodel.h b/src/include/ktexteditor/codecompletionmodel.h index 7ee6fe72..31cb2f2a 100644 --- a/src/include/ktexteditor/codecompletionmodel.h +++ b/src/include/ktexteditor/codecompletionmodel.h @@ -1,475 +1,475 @@ /* This file is part of the KDE libraries Copyright (C) 2007-2008 David Nolden Copyright (C) 2005-2006 Hamish Rodda * * 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 KTEXTEDITOR_CODECOMPLETIONMODEL_H #define KTEXTEDITOR_CODECOMPLETIONMODEL_H #include #include #include namespace KTextEditor { class Document; class View; /** * \short An item model for providing code completion, and meta information for * enhanced presentation. * * \section compmodel_intro Introduction * * The CodeCompletionModel is the actual workhorse to provide code completions * in a KTextEditor::View. It is meant to be used in conjunction with the * CodeCompletionInterface. The CodeCompletionModel is not meant to be used as * is. Rather you need to implement a subclass of CodeCompletionModel to actually * generate completions appropriate for your type of Document. * * \section compmodel_implementing Implementing a CodeCompletionModel * * The CodeCompletionModel is a QAbstractItemModel, and can be subclassed in the * same way. It provides default implementations of several members, however, so * in most cases (if your completions are essentially a non-hierarchical, flat list * of matches) you will only need to overload few virtual functions. * * \section compmodel_flatlist Implementing a CodeCompletionModel for a flat list * * For the simple case of a flat list of completions, you will need to: * - Implement completionInvoked() to actually generate/update the list of completion * matches * - implement itemData() (or QAbstractItemModel::data()) to return the information that * should be displayed for each match. * - use setRowCount() to reflect the number of matches. * * \section compmodel_roles_columns Columns and roles * * \todo document the meaning and usage of the columns and roles used by the * CodeCompletionInterface * * \section compmodel_usage Using the new CodeCompletionModel * * To start using your CodeCompletionModel, refer to CodeCompletionInterface. * * \section compmodel_controller ControllerInterface to get more control * * To have more control over code completion implement * CodeCompletionModelControllerInterface in your CodeCompletionModel. * * \see CodeCompletionInterface, CodeCompletionModelControllerInterface * @author Hamish Rodda */ class KTEXTEDITOR_EXPORT CodeCompletionModel : public QAbstractItemModel { Q_OBJECT public: - CodeCompletionModel(QObject *parent); - virtual ~CodeCompletionModel(); + explicit CodeCompletionModel(QObject *parent); + ~CodeCompletionModel() override; enum Columns { Prefix = 0, /// Icon representing the type of completion. We have a separate icon field /// so that names remain aligned where only some completions have icons, /// and so that they can be rearranged by the user. Icon, Scope, Name, Arguments, Postfix }; static const int ColumnCount = Postfix + 1; enum CompletionProperty { NoProperty = 0x0, FirstProperty = 0x1, // Access specifiers - no more than 1 per item Public = 0x1, Protected = 0x2, Private = 0x4, // Extra access specifiers - any number per item Static = 0x8, Const = 0x10, // Type - no more than 1 per item (except for Template) Namespace = 0x20, Class = 0x40, Struct = 0x80, Union = 0x100, Function = 0x200, Variable = 0x400, Enum = 0x800, Template = 0x1000, TypeAlias = 0x2000, // Special attributes - any number per item Virtual = 0x4000, Override = 0x8000, Inline = 0x10000, Friend = 0x20000, Signal = 0x40000, Slot = 0x80000, // Scope - no more than 1 per item LocalScope = 0x100000, NamespaceScope = 0x200000, GlobalScope = 0x400000, // Keep this in sync so the code knows when to stop LastProperty = GlobalScope }; Q_DECLARE_FLAGS(CompletionProperties, CompletionProperty) enum HighlightMethod { NoHighlighting = 0x0, InternalHighlighting = 0x1, CustomHighlighting = 0x2 }; Q_DECLARE_FLAGS(HighlightMethods, HighlightMethod) /// Meta information is passed through extra {Qt::ItemDataRole}s. /// This information should be returned when requested on the Name column. enum ExtraItemDataRoles { /// The model should return a set of CompletionProperties. CompletionRole = Qt::UserRole, /// The model should return an index to the scope /// -1 represents no scope /// \todo how to sort scope? ScopeIndex, /** * If requested, your model should try to determine whether the * completion in question is a suitable match for the context (ie. * is accessible, exported, + returns the data type required). * * The returned data should ideally be matched against the argument-hint context * set earlier by SetMatchContext. * * Return an integer value that should be positive if the completion is suitable, * and zero if the completion is not suitable. The value should be between 0 an 10, where * 10 means perfect match. * * Return QVariant::Invalid if you are unable to determine this. */ MatchQuality, /** * Is requested before MatchQuality(..) is requested. The item on which this is requested * is an argument-hint item(@see ArgumentHintDepth). When this role is requested, the item should * be noted, and whenever MatchQuality is requested, it should be computed by matching the item given * with MatchQuality into the context chosen by SetMatchContext. * * Feel free to ignore this, but ideally you should return QVariant::Invalid to make clear that your model does not support this. * */ SetMatchContext, /** * Define which highlighting method will be used: * - QVariant::Invalid - allows the editor to choose (usually internal highlighting) * - QVariant::Integer - highlight as specified by HighlightMethods. */ HighlightingMethod, /** * Allows an item to provide custom highlighting. Return a * QList\ in the following format: * - int startColumn (where 0 = start of the completion entry) * - int endColumn (note: not length) * - QTextFormat attribute (note: attribute may be a KTextEditor::Attribute, as it is a child class) * If the attribute is invalid, and the item is an argument-hint, the text will be drawn with * a background-color depending on match-quality, or yellow. * You can use that to mark the actual arguments that are matched in an argument-hint. * * Repeat this triplet as many times as required, however each column must be >= the previous, * and startColumn != endColumn. */ CustomHighlight, /** * Returns the inheritance depth of the completion. For example, a completion * which comes from the base class would have depth 0, one from a parent class * would have depth 1, one from that class' parent 2, etc. you can use this to * symbolize the general distance of a completion-item from a user. It will be used * for sorting. */ InheritanceDepth, /** * This allows items in the completion-list to be expandable. If a model returns an QVariant bool value * that evaluates to true, the completion-widget will draw a handle to expand the item, and will also make * that action accessible through keyboard. */ IsExpandable, /** * After a model returned true for a row on IsExpandable, the row may be expanded by the user. * When this happens, ExpandingWidget is requested. * * The model may return two types of values: * QWidget*: * If the model returns a QVariant of type QWidget*, the code-completion takes over the given widget * and embeds it into the completion-list under the completion-item. The widget will be automatically deleted at some point. * The completion-widget will use the height of the widget as a hint for its preferred size, but it will * resize the widget at will. * QString: * If the mode returns a QVariant of type QString, it will create a small html-widget showing the given html-code, * and embed it into the completion-list under the completion-item. * * Warning: * QWidget* widget; * return QVariant(widget); * Will not work correctly! * Use the following instead.: * QVariant v; * v.setValue(widget); * return v; * * */ ExpandingWidget, /** * Whenever an item is selected, this will be requested from the underlying model. * It may be used as a simple notification that the item was selected. * * Above that, the model may return a QString, which then should then contain html-code. A html-widget * will then be displayed as a one- or two-liner under the currently selected item(it will be partially expanded) * */ ItemSelected, /**Is this completion-item an argument-hint? * The model should return an integral positive number if the item is an argument-hint, and QVariant() or 0 if it is not one. * * The returned depth-integer is important for sorting and matching. * Example: * "otherFunction(function1(function2(" * all functions named function2 should have ArgumentHintDepth 1, all functions found for function1 should have ArgumentHintDepth 2, * and all functions named otherFunction should have ArgumentHintDepth 3 * * Later, a completed item may then be matched with the first argument of function2, the return-type of function2 with the first * argument-type of function1, and the return-type of function1 with the argument-type of otherFunction. * * If the model returns a positive value on this role for a row, the content will be treated specially: * - It will be shown in a separate argument-hint list * - It will be sorted by Argument-hint depth * - Match-qualities will be illustrated by differently highlighting the matched argument if possible * The argument-hint list strings will be built from all source-model, with a little special behavior: * Prefix - Should be all text of the function-signature up to left of the matched argument of the function * Name - Should be the type and name of the function's matched argument. This part will be highlighted differently depending on the match-quality * Suffix - Should be all the text of the function-signature behind the matched argument * * Example: You are matching a function with signature "void test(int param1, int param2)", and you are matching the first argument. * The model should then return: * Prefix: "void test(" * Name: "int param1" * Suffix: ", int param2)" * * If you don't use the highlighting, matching, etc. you can also return the columns in the usual way. * */ ArgumentHintDepth, /** * This will be requested for each item to ask whether it should be included in computing a best-matches list. * If you return a valid positive integer n here, the n best matches will be listed at top of the completion-list separately. * * This is expensive because all items of the whole completion-list will be tested for their matching-quality, with each of the level 1 * argument-hints. * * For that reason the end-user should be able to disable this feature. */ BestMatchesCount, /** * The following three enumeration-values are only used on expanded completion-list items that contain an expanding-widget(@see ExpandingWidget) * * You can use them to allow the user to interact with the widget by keyboard. * * AccessibilityNext will be requested on an item if it is expanded, contains an expanding-widget, and the user triggers a special navigation * short-cut to go to navigate to the next position within the expanding-widget(if applicable). * * Return QVariant(true) if the input was used. * */ AccessibilityNext, /** * AccessibilityPrevious will be requested on an item if it is expanded, contains an expanding-widget, and the user triggers a special navigation * short-cut to go to navigate to the previous position within the expanding-widget(if applicable). * * Return QVariant(true) if the input was used. * */ AccessibilityPrevious, /** * AccessibilityAccept will be requested on an item if it is expanded, contains an expanding-widget, and the user triggers a special * shortcut to trigger the action associated with the position within the expanding-widget the user has navigated to using AccessibilityNext and AccessibilityPrevious. * * This should return QVariant(true) if an action was triggered, else QVariant(false) or QVariant(). * */ AccessibilityAccept, /** * Using this Role, it is possible to greatly optimize the time needed to process very long completion-lists. * * In the completion-list, the items are usually ordered by some properties like argument-hint depth, * inheritance-depth and attributes. Kate usually has to query the completion-models for these values * for each item in the completion-list in order to extract the argument-hints and correctly sort the * completion-list. However, with a very long completion-list, only a very small fraction of the items is actually * visible. * * By using a tree structure you can give the items in a grouped order to kate, so it does not need to look at each * item and query data in order to initially show the completion-list. * * This is how it works: * - You create a tree-structure for your items * - Every inner node of the tree defines one attribute value that all sub-nodes have in common. * - When the inner node is queried for GroupRole, it should return the "ExtraItemDataRoles" that all sub-nodes have in common * - When the inner node is then queried for that exact role, it should return that value. * - No other queries will be done to inner nodes. * - Every leaf node stands for an actual item in the completion list. * * The recommended grouping order is: Argument-hint depth, inheritance depth, attributes. * * This role can also be used to define completely custom groups, bypassing the editors builtin grouping: * - Return Qt::DisplayRole when GroupRole is requested * - Return the lable text of the group when Qt::DisplayRole * - Optional: Return an integer sorting-value when InheritanceDepth is requested. This number will * be used to determine the order of the groups. The order of the builtin groups is: * 1 = Best Matches, 100 = Local Scope, 200 = Public, 300 = Protected, 400 = Private, 500 = Namespace, 600 = Global * You can pick any arbitrary number to position your group relative to these builtin groups. * */ GroupRole, /** * Return a nonzero value here to enforce sorting the item at the end of the list. */ UnimportantItemRole, LastExtraItemDataRole }; void setRowCount(int rowCount); enum InvocationType { AutomaticInvocation, UserInvocation, ManualInvocation }; /** * This function is responsible to generating / updating the list of current * completions. The default implementation does nothing. * * When implementing this function, remember to call setRowCount() (or implement * rowCount()), and to generate the appropriate change notifications (for instance * by calling QAbstractItemModel::reset()). * @param view The view to generate completions for * @param range The range of text to generate completions for * @param invocationType How the code completion was triggered * */ virtual void completionInvoked(KTextEditor::View *view, const KTextEditor::Range &range, InvocationType invocationType); /** * This function is responsible for inserting a selected completion into the * view. The default implementation replaces the text that the completions * were based on with the Qt::DisplayRole of the Name column of the given match. * * @param view the view to insert the completion into * @param word the Range that the completions are based on (what the user entered * so far) * @param index identifies the completion match to insert * */ virtual void executeCompletionItem(KTextEditor::View *view, const Range &word, const QModelIndex &index) const; // Reimplementations /** * Reimplemented from QAbstractItemModel::columnCount(). The default implementation * returns ColumnCount for all indices. * */ - int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; /** * Reimplemented from QAbstractItemModel::index(). The default implementation * returns a standard QModelIndex as long as the row and column are valid. * */ - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; /** * Reimplemented from QAbstractItemModel::itemData(). The default implementation * returns a map with the QAbstractItemModel::data() for all roles that are used * by the CodeCompletionInterface. You will need to reimplement either this * function or QAbstractItemModel::data() in your CodeCompletionModel. * */ - QMap itemData(const QModelIndex &index) const Q_DECL_OVERRIDE; + QMap itemData(const QModelIndex &index) const override; /** * Reimplemented from QAbstractItemModel::parent(). The default implementation * returns an invalid QModelIndex for all items. This is appropriate for * non-hierarchical / flat lists of completions. * */ - QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE; + QModelIndex parent(const QModelIndex &index) const override; /** * Reimplemented from QAbstractItemModel::rowCount(). The default implementation * returns the value set by setRowCount() for invalid (toplevel) indices, and 0 * for all other indices. This is appropriate for non-hierarchical / flat lists * of completions * */ - int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; /** * This function returns true if the model needs grouping, otherwise false. * The default is false if not changed via setHasGroups(). */ bool hasGroups() const; Q_SIGNALS: /** * Emit this if the code-completion for this model was invoked, some time is needed in order to get the data, * and the model is reset once the data is available. * * This only has an effect if emitted from within completionInvoked(..). * * This prevents the code-completion list from showing until this model is reset, * so there is no annoying flashing in the user-interface resulting from other models * supplying their data earlier. * * @note The implementation may choose to show the completion-list anyway after some timeout * * @warning If you emit this, you _must_ also reset the model at some point, * else the code-completion will be completely broken to the user. * Consider that there may always be additional completion-models apart from yours. * * @since 4.3 */ void waitForReset(); /** * Internal */ void hasGroupsChanged(KTextEditor::CodeCompletionModel *model, bool hasGroups); protected: void setHasGroups(bool hasGroups); private: class CodeCompletionModelPrivate *const d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(CodeCompletionModel::CompletionProperties) Q_DECLARE_OPERATORS_FOR_FLAGS(CodeCompletionModel::HighlightMethods) } #endif // KTEXTEDITOR_CODECOMPLETIONMODEL_H diff --git a/src/include/ktexteditor/configinterface.h b/src/include/ktexteditor/configinterface.h index f175e237..1ca20c0b 100644 --- a/src/include/ktexteditor/configinterface.h +++ b/src/include/ktexteditor/configinterface.h @@ -1,148 +1,148 @@ /* This file is part of the KDE project Copyright (C) 2006 Matt Broadstone (mbroadst@gmail.com) 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 KTEXTEDITOR_CONFIGINTERFACE_H #define KTEXTEDITOR_CONFIGINTERFACE_H #include #include #include namespace KTextEditor { /** * \brief Config interface extension for the Document and View. * * \ingroup kte_group_view_extensions * \ingroup kte_group_doc_extensions * * \section config_intro Introduction * * The ConfigInterface provides methods to access and modify the low level * config information for a given Document or View. Examples of this config data can be * displaying the icon bar, showing line numbers, etc. This generally allows * access to settings that otherwise are only accessible during runtime. * * \section config_access Accessing the Interface * * The ConfigInterface is supposed to be an extension interface for a Document or View, * i.e. the Document or View inherits the interface \e provided that the * KTextEditor library in use implements the interface. Use qobject_cast to access * the interface: * \code * // ptr is of type KTextEditor::Document* or KTextEditor::View* * KTextEditor::ConfigInterface *iface = * qobject_cast( ptr ); * * if( iface ) { * * // the implementation supports the interface * // do stuff * } * \endcode * * \section config_data Accessing Data * * A list of available config variables (or keys) can be optained by calling * configKeys(). For all available keys configValue() returns the corresponding * value as QVariant. A value for a given key can be set by calling * setConfigValue(). Right now, when using KatePart as editor component, * KTextEditor::View has support for the following tuples: * - line-numbers [bool], show/hide line numbers * - icon-bar [bool], show/hide icon bar * - folding-bar [bool], show/hide the folding bar * - folding-preview [bool], enable/disable folding preview when mouse hovers * on folded region * - dynamic-word-wrap [bool], enable/disable dynamic word wrap * - background-color [QColor], read/set the default background color * - selection-color [QColor], read/set the default color for selections * - search-highlight-color [QColor], read/set the background color for search * - replace-highlight-color [QColor], read/set the background color for replaces * - default-mark-type [uint], read/set the default mark type * - allow-mark-menu [bool], enable/disable the menu shown when right clicking * on the left gutter. When disabled, click on the gutter will always set * or clear the mark of default type. * - icon-border-color [QColor] read/set the icon border color (on the left, * with the line numbers) * - folding-marker-color [QColor] read/set folding marker colors (in the icon border) * - line-number-color [QColor] read/set line number colors (in the icon border) * - current-line-number-color [QColor] read/set current line number color (in the icon border) * - modification-markers [bool] read/set whether the modification markers are shown * - word-count [bool] enable/disable the counting of words and characters in the statusbar * - scrollbar-minimap [bool] enable/disable scrollbar minimap * - scrollbar-preview [bool] enable/disable scrollbar text preview on hover * - font [QFont] change the font * * KTextEditor::Document has support for the following: * - backup-on-save-local [bool], enable/disable backup when saving local files * - backup-on-save-remote [bool], enable/disable backup when saving remote files * - backup-on-save-suffix [string], set the suffix for file backups, e.g. "~" * - backup-on-save-prefix [string], set the prefix for file backups, e.g. "." * - replace-tabs [bool], whether to replace tabs * - indent-pasted-text [bool], whether to indent pasted text * - tab-width [int], read/set the width for tabs * - indent-width [int], read/set the indentation width * - on-the-fly-spellcheck [bool], enable/disable on the fly spellcheck * * Either interface should emit the \p configChanged signal when appropriate. * TODO: Add to interface in KDE 5. * * For instance, if you want to enable dynamic word wrap of a KTextEditor::View * simply call * \code * iface->setConfigValue("dynamic-word-wrap", true); * \endcode * * \see KTextEditor::View, KTextEditor::Document * \author Matt Broadstone \ */ class KTEXTEDITOR_EXPORT ConfigInterface { public: ConfigInterface(); /** * Virtual destructor. */ virtual ~ConfigInterface(); public: /** * Get a list of all available keys. */ virtual QStringList configKeys() const = 0; /** * Get a value for the \p key. */ virtual QVariant configValue(const QString &key) = 0; /** * Set a the \p key's value to \p value. */ virtual void setConfigValue(const QString &key, const QVariant &value) = 0; private: - class ConfigInterfacePrivate *const d; + class ConfigInterfacePrivate *const d = nullptr; }; } Q_DECLARE_INTERFACE(KTextEditor::ConfigInterface, "org.kde.KTextEditor.ConfigInterface") #endif diff --git a/src/include/ktexteditor/cursor.h b/src/include/ktexteditor/cursor.h index 3140397f..26799e82 100644 --- a/src/include/ktexteditor/cursor.h +++ b/src/include/ktexteditor/cursor.h @@ -1,411 +1,411 @@ /* This file is part of the KDE project Copyright (C) 2003-2005 Hamish Rodda Copyright (C) 2001-2005 Christoph Cullmann Copyright (C) 2014 Dominik Haumann Copyright (C) 2002 Christian Couder Copyright (C) 2001 Joseph Wenninger Copyright (C) 1999 Jochen Wilhelmy This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License 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 KTEXTEDITOR_CURSOR_H #define KTEXTEDITOR_CURSOR_H #include #include #include namespace KTextEditor { class Document; class Range; /** * \short The Cursor represents a position in a Document. * * \section kte_cursor_intro Introduction * A Cursor represents a position in a Document through a tuple * of two int%s, namely the line() and column(). A Cursor maintains * no affiliation with a particular Document, meaning that it remains * constant if not changed through the Cursor API. * * \section kte_cursor_notes Important Notes * * Working with a cursor, one should be aware of the following notes: * - Lines and columns start a 0. * - The Cursor class is designed to be passed by value (only 8 Bytes). * - Think of cursors as having their position at the start of a character, * not in the middle of one. * - invalid() Cursor%s are located at (-1, -1). In addition, a Cursor * is invalid(), if either its line() and/or its column() is arbitrarily * negative, i.e. < 0. * - All Cursor%s with line() >= 0 and column() >= 0 are valid. In this case * isValid() returns \e true. * - A Cursor has a non-virtual destructor. Hence, you cannot derive from Cursor. * * \section kte_cursor_properties Cursor Efficiency * * The Cursor consists of just two int%s, the line() and the column(). * Therefore, a Cursor instance takes 8 Bytes of memory. Further, a Cursor * is a non-virtual class, turning it into a primitive old data type (POD). * Thus, it can be moved and copied very efficiently. * * \section kte_cursor_more Additional Concepts * * In addition to the Cursor, the KTextEditor API provides advanced concepts: * - The DocumentCursor is a Cursor bound to a specific Document. In addition * to the Cursor API, it provides convenience functions like * DocumentCursor::isValidTextPosition() or DocumentCursor::move(). * The DocumentCursor does not maintain its position, though. * - The MovingCursor is also bound to a specific Document. In addition to the * DocumentCursor, the MovingCursor maintains its position, meaning that * whenever the Document changes, the MovingCursor moves, too. * - The Cursor forms the basis for the Range. * * \sa DocumentCursor, MovingCursor, Range */ class KTEXTEDITOR_EXPORT Cursor { public: /** * The default constructor creates a cursor at position (0, 0). */ Q_DECL_CONSTEXPR Cursor() Q_DECL_NOEXCEPT - : m_line(0) - , m_column(0) { + { } /** * This constructor creates a cursor initialized with \p line * and \p column. * \param line line for cursor * \param column column for cursor */ Q_DECL_CONSTEXPR Cursor(int line, int column) Q_DECL_NOEXCEPT : m_line(line) - , m_column(column) { + , m_column(column) + { } /** * Returns whether the current position of this cursor is a valid position * (line + column must both be >= 0). * * @note If you want to check, whether a cursor position is a valid * \e text-position, use DocumentCursor::isValidTextPosition(), * or Document::isValidTextPosition(). */ Q_DECL_CONSTEXPR inline bool isValid() const Q_DECL_NOEXCEPT { return m_line >= 0 && m_column >= 0; } /** * Returns an invalid cursor. * The returned cursor position is set to (-1, -1). * \see isValid() */ Q_DECL_CONSTEXPR static Cursor invalid() Q_DECL_NOEXCEPT { return Cursor(-1, -1); } /** * Returns a cursor representing the start of any document - i.e., line 0, column 0. */ Q_DECL_CONSTEXPR static Cursor start() Q_DECL_NOEXCEPT { return Cursor(); } /** * Returns the cursor position as string in the format "(line, column)". * \see fromString() */ QString toString() const { return QLatin1Char('(') + QString::number(m_line) + QStringLiteral(", ") + QString::number(m_column) + QLatin1Char(')'); } /** * Returns a Cursor created from the string \p str containing the format * "(line, column)". In case the string cannot be parsed, Cursor::invalid() * is returned. * \see toString() */ static Cursor fromString(const QString& str) Q_DECL_NOEXCEPT { return fromString(str.leftRef(-1)); } /** * Returns a Cursor created from the string \p str containing the format * "(line, column)". In case the string cannot be parsed, Cursor::invalid() * is returned. * \see toString() */ static Cursor fromString(const QStringRef& str) Q_DECL_NOEXCEPT; /** * \name Position * * The following functions provide access to, and manipulation of, the cursor's position. * \{ */ /** * Set the current cursor position to \e position. * * \param position new cursor position */ inline void setPosition(const Cursor &position) Q_DECL_NOEXCEPT { m_line = position.m_line; m_column = position.m_column; } /** * \overload * * Set the cursor position to \e line and \e column. * * \param line new cursor line * \param column new cursor column */ inline void setPosition(int line, int column) Q_DECL_NOEXCEPT { m_line = line; m_column = column; } /** * Retrieve the line on which this cursor is situated. * \return line number, where 0 is the first line. */ Q_DECL_CONSTEXPR inline int line() const Q_DECL_NOEXCEPT { return m_line; } /** * Set the cursor line to \e line. * \param line new cursor line */ inline void setLine(int line) Q_DECL_NOEXCEPT { m_line = line; } /** * Retrieve the column on which this cursor is situated. * \return column number, where 0 is the first column. */ Q_DECL_CONSTEXPR inline int column() const Q_DECL_NOEXCEPT { return m_column; } /** * Set the cursor column to \e column. * \param column new cursor column */ inline void setColumn(int column) Q_DECL_NOEXCEPT { m_column = column; } /** * Determine if this cursor is located at the start of a line (= at column 0). * \return \e true if the cursor is situated at the start of the line, \e false if it isn't. */ Q_DECL_CONSTEXPR inline bool atStartOfLine() const Q_DECL_NOEXCEPT { return m_column == 0; } /** * Determine if this cursor is located at the start of a document (= at position (0, 0)). * \return \e true if the cursor is situated at the start of the document, \e false if it isn't. */ Q_DECL_CONSTEXPR inline bool atStartOfDocument() const Q_DECL_NOEXCEPT { return m_line == 0 && m_column == 0; } /** * Get both the line and column of the cursor position. * \param line will be filled with current cursor line * \param column will be filled with current cursor column */ inline void position(int &line, int &column) const Q_DECL_NOEXCEPT { line = m_line; column = m_column; } //!\} /** * Addition operator. Takes two cursors and returns their summation. * \param c1 the first position * \param c2 the second position * \return a the summation of the two input cursors */ Q_DECL_CONSTEXPR inline friend Cursor operator+(const Cursor &c1, const Cursor &c2) Q_DECL_NOEXCEPT { return Cursor(c1.line() + c2.line(), c1.column() + c2.column()); } /** * Addition assignment operator. Adds \p c2 to this cursor. * \param c1 the cursor being added to * \param c2 the position to add * \return a reference to the cursor which has just been added to */ inline friend Cursor &operator+=(Cursor &c1, const Cursor &c2) Q_DECL_NOEXCEPT { c1.setPosition(c1.line() + c2.line(), c1.column() + c2.column()); return c1; } /** * Subtraction operator. Takes two cursors and returns the subtraction * of \p c2 from \p c1. * * \param c1 the first position * \param c2 the second position * \return a cursor representing the subtraction of \p c2 from \p c1 */ Q_DECL_CONSTEXPR inline friend Cursor operator-(const Cursor &c1, const Cursor &c2) Q_DECL_NOEXCEPT { return Cursor(c1.line() - c2.line(), c1.column() - c2.column()); } /** * Subtraction assignment operator. Subtracts \p c2 from \p c1. * \param c1 the cursor being subtracted from * \param c2 the position to subtract * \return a reference to the cursor which has just been subtracted from */ inline friend Cursor &operator-=(Cursor &c1, const Cursor &c2) Q_DECL_NOEXCEPT { c1.setPosition(c1.line() - c2.line(), c1.column() - c2.column()); return c1; } /** * Equality operator. * * \note comparison between two invalid cursors is undefined. * comparison between and invalid and a valid cursor will always be \e false. * * \param c1 first cursor to compare * \param c2 second cursor to compare * \return \e true, if c1's and c2's line and column are \e equal. */ Q_DECL_CONSTEXPR inline friend bool operator==(const Cursor &c1, const Cursor &c2) Q_DECL_NOEXCEPT { return c1.line() == c2.line() && c1.column() == c2.column(); } /** * Inequality operator. * \param c1 first cursor to compare * \param c2 second cursor to compare * \return \e true, if c1's and c2's line and column are \e not equal. */ Q_DECL_CONSTEXPR inline friend bool operator!=(const Cursor &c1, const Cursor &c2) Q_DECL_NOEXCEPT { return !(c1 == c2); } /** * Greater than operator. * \param c1 first cursor to compare * \param c2 second cursor to compare * \return \e true, if c1's position is greater than c2's position, * otherwise \e false. */ Q_DECL_CONSTEXPR inline friend bool operator>(const Cursor &c1, const Cursor &c2) Q_DECL_NOEXCEPT { return c1.line() > c2.line() || (c1.line() == c2.line() && c1.m_column > c2.m_column); } /** * Greater than or equal to operator. * \param c1 first cursor to compare * \param c2 second cursor to compare * \return \e true, if c1's position is greater than or equal to c2's * position, otherwise \e false. */ Q_DECL_CONSTEXPR inline friend bool operator>=(const Cursor &c1, const Cursor &c2) Q_DECL_NOEXCEPT{ return c1.line() > c2.line() || (c1.line() == c2.line() && c1.m_column >= c2.m_column); } /** * Less than operator. * \param c1 first cursor to compare * \param c2 second cursor to compare * \return \e true, if c1's position is greater than or equal to c2's * position, otherwise \e false. */ Q_DECL_CONSTEXPR inline friend bool operator<(const Cursor &c1, const Cursor &c2) Q_DECL_NOEXCEPT { return !(c1 >= c2); } /** * Less than or equal to operator. * \param c1 first cursor to compare * \param c2 second cursor to compare * \return \e true, if c1's position is lesser than or equal to c2's * position, otherwise \e false. */ Q_DECL_CONSTEXPR inline friend bool operator<=(const Cursor &c1, const Cursor &c2) Q_DECL_NOEXCEPT { return !(c1 > c2); } /** * qDebug() stream operator. Writes this cursor to the debug output in a nicely formatted way. */ inline friend QDebug operator<< (QDebug s, const Cursor &cursor) { s.nospace() << "(" << cursor.line() << ", " << cursor.column() << ")"; return s.space(); } private: /** * \internal * * Cursor line */ - int m_line; + int m_line = 0; /** * \internal * * Cursor column */ - int m_column; + int m_column = 0; }; } // namespace KTextEditor Q_DECLARE_TYPEINFO(KTextEditor::Cursor, Q_MOVABLE_TYPE); Q_DECLARE_METATYPE(KTextEditor::Cursor) /** * QHash function for KTextEditor::Cursor. * Returns the hash value for @p cursor. */ inline uint qHash(const KTextEditor::Cursor& cursor, uint seed = 0) Q_DECL_NOTHROW { return qHash(qMakePair(cursor.line(), cursor.column()), seed); } namespace QTest { // forward declaration of template in qtestcase.h template char* toString(const T&); /** * QTestLib integration to have nice output in e.g. QCOMPARE failures. */ template<> KTEXTEDITOR_EXPORT char *toString(const KTextEditor::Cursor &cursor); } #endif diff --git a/src/include/ktexteditor/markinterface.h b/src/include/ktexteditor/markinterface.h index 0c1d16f4..13780dc0 100644 --- a/src/include/ktexteditor/markinterface.h +++ b/src/include/ktexteditor/markinterface.h @@ -1,405 +1,405 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann (cullmann@kde.org) Copyright (C) 2005 Dominik Haumann (dhdev@gmx.de) (documentation) 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 KTEXTEDITOR_MARKINTERFACE_H #define KTEXTEDITOR_MARKINTERFACE_H #include #include #include class QPixmap; class QPoint; class QMenu; namespace KTextEditor { class Document; /** * \brief Mark class containing line and mark types. * * \section mark_intro Introduction * * The class Mark represents a mark in a Document. It contains the \e line * and \e type. A line can have multiple marks, like a \e bookmark and a * \e breakpoint, i.e. the \e type contains all marks combined with a logical * \e OR (|). There are several predefined mark types, look into the * MarkInterface for further details. * * \see KTextEditor::MarkInterface, KTextEditor::Document */ class Mark { public: /** The line that contains the mark. */ int line; /** The mark types in the line, combined with logical OR. */ uint type; }; /** * \brief Mark extension interface for the Document. * * \ingroup kte_group_doc_extensions * * \section markext_intro Introduction * * The MarkInterface provides methods to enable and disable marks in a * Document, a marked line can be visualized for example with a shaded * background color and/or a pixmap in the iconborder of the Document's View. * There are a number of predefined mark types, specified in * reservedMarkersCount(). Additionally it is possible to add custom marks * and set custom pixmaps. * * \section markext_access Accessing the Interface * * The MarkInterface is supposed to be an extension interface for a Document, * i.e. the Document inherits the interface \e provided that the * KTextEditor library in use implements the interface. Use qobject_cast to access * the interface: * \code * // doc is of type KTextEditor::Document* * KTextEditor::MarkInterface *iface = * qobject_cast( doc ); * * if( iface ) { * // the implementation supports the interface * // do stuff * } * \endcode * * \section markext_handling Handling Marks * * Get all marks in the document by calling marks(). Use clearMarks() to * remove all marks in the entire document. A single mark can be retrieved * with mark(). To remove all marks from a line call clearMark(). To add * and remove marks from a given line use addMark() and removeMark(). It is * also possible to replace all marks with setMark(), i.e. setMark() is the * same as a call of clearMark() followed by addMark(). The signals * marksChanged() and markChanged() are emitted whenever a line's marks * changed. * * \attention A mark type is represented as an \e uint. An \e uint can have * several mark types combined (see above: logical OR). That means for * all functions/signals with an \e uint parameter, e.g. setMark(), * removeMark(), etc, the \e uint may contain \e multiple marks, i.e. * you can add and remove multiple marks \e simultaneously. * * \section markext_userdefined User Defined Marks * * All marks that should be editable by the user can be specified with a mark * mask via setEditableMarks(). To set a description and pixmap of a mark type * call setMarkDescription() and setMarkPixmap(). * * \see KTextEditor::Document, KTextEditor::Mark * \author Christoph Cullmann \ */ class KTEXTEDITOR_EXPORT MarkInterface { public: MarkInterface(); /** * Virtual destructor. */ virtual ~MarkInterface(); // // slots !!! // public: /** * Get all marks set on the \p line. * \param line requested line * \return a \e uint representing of the marks set in \p line concatenated * by logical OR * \see addMark(), removeMark() */ virtual uint mark(int line) = 0; /** * Set the \p line's mark types to \p markType. * If \p line already contains a mark of the given type it has no effect. * All other marks are deleted before the mark is set. You can achieve * the same by calling * \code * clearMark(line); * addMark(line, markType); * \endcode * \param line line to set the mark * \param markType mark type * \see clearMark(), addMark(), mark() */ virtual void setMark(int line, uint markType) = 0; /** * Clear all marks set in the \p line. * \param line line to clear marks * \see clearMarks(), removeMark(), addMark() */ virtual void clearMark(int line) = 0; /** * Add marks of type \p markType to \p line. Existing marks on this line * are preserved. If the mark \p markType already is set, nothing * happens. * \param line line to set the mark * \param markType mark type * \see removeMark(), setMark() */ virtual void addMark(int line, uint markType) = 0; /** * Remove the mark mask of type \p markType from \p line. * \param line line to remove the mark * \param markType mark type to be removed * \see clearMark() */ virtual void removeMark(int line, uint markType) = 0; /** * Get a hash holding all marks in the document. * The hash key for a mark is its line. * \return a hash holding all marks in the document */ virtual const QHash &marks() = 0; /** * Clear all marks in the entire document. * \see clearMark(), removeMark() */ /// TODO: dominik: add argument unit mask = 0 virtual void clearMarks() = 0; /** * Get the number of predefined mark types we have so far. * \note FIXME: If you change this you have to make sure katepart * supports the new size! * \return number of reserved marker types */ static int reservedMarkersCount() { return 7; } /** * Predefined mark types. * * To add a new standard mark type, edit this interface and document * the type. */ enum MarkTypes { /** Bookmark */ markType01 = 0x1, /** Breakpoint active */ markType02 = 0x2, /** Breakpoint reached */ markType03 = 0x4, /** Breakpoint disabled */ markType04 = 0x8, /** Execution mark */ markType05 = 0x10, /** Warning */ markType06 = 0x20, /** Error */ markType07 = 0x40, markType08 = 0x80, markType09 = 0x100, markType10 = 0x200, markType11 = 0x400, markType12 = 0x800, markType13 = 0x1000, markType14 = 0x2000, markType15 = 0x4000, markType16 = 0x8000, markType17 = 0x10000, markType18 = 0x20000, markType19 = 0x40000, markType20 = 0x80000, markType21 = 0x100000, markType22 = 0x200000, markType23 = 0x400000, markType24 = 0x800000, markType25 = 0x1000000, markType26 = 0x2000000, markType27 = 0x4000000, markType28 = 0x8000000, markType29 = 0x10000000, markType30 = 0x20000000, markType31 = 0x40000000, markType32 = 0x80000000, /* reserved marks */ Bookmark = markType01, BreakpointActive = markType02, BreakpointReached = markType03, BreakpointDisabled = markType04, Execution = markType05, Warning = markType06, Error = markType07, SearchMatch = markType32, }; // // signals !!! // public: /** * The \p document emits this signal whenever a mark mask changed. * \param document document which emitted this signal * \see markChanged() */ virtual void marksChanged(KTextEditor::Document *document) = 0; /* * Methods to modify mark properties. */ public: /** * Set the \p mark's pixmap to \p pixmap. * \param mark mark to which the pixmap will be attached * \param pixmap new pixmap * \see setMarkDescription() */ virtual void setMarkPixmap(MarkTypes mark, const QPixmap &pixmap) = 0; /** * Get the \p mark's pixmap. * \param mark mark type. If the pixmap does not exist the resulting is null * (check with QPixmap::isNull()). * \see setMarkDescription() */ virtual QPixmap markPixmap(MarkTypes mark) const = 0; /** * Set the \p mark's description to \p text. * \param mark mark to set the description * \param text new descriptive text * \see markDescription(), setMarkPixmap() */ virtual void setMarkDescription(MarkTypes mark, const QString &text) = 0; /** * Get the \p mark's description to text. * \param mark mark to set the description * \return text of the given \p mark or QString(), if the entry does not * exist * \see setMarkDescription(), setMarkPixmap() */ virtual QString markDescription(MarkTypes mark) const = 0; /** * Set the mark mask the user is allowed to toggle to \p markMask. * I.e. concatenate all editable marks with a logical OR. If the user should * be able to add a bookmark and set a breakpoint with the context menu in * the icon pane, you have to call * \code * // iface is of Type KTextEditor::MarkInterface* * // only make bookmark and breakpoint editable * iface->setEditableMarks( MarkInterface::Bookmark | * MarkInterface::BreakpointActive ); * * // or preserve last settings, and add bookmark and breakpoint * iface->setEditableMarks( iface->editableMarks() | * MarkInterface::Bookmark | * MarkInterface::BreakpointActive ); * \endcode * \param markMask bitmap pattern * \see editableMarks(), setMarkPixmap(), setMarkDescription() */ virtual void setEditableMarks(uint markMask) = 0; /** * Get, which marks can be toggled by the user. * The returned value is a mark mask containing all editable marks combined * with a logical OR. * \return mark mask containing all editable marks * \see setEditableMarks() */ virtual uint editableMarks() const = 0; /** * Possible actions on a mark. * \see markChanged() */ enum MarkChangeAction { MarkAdded = 0, /**< action: a mark was added. */ MarkRemoved = 1 /**< action: a mark was removed. */ }; // // signals !!! // public: /** * The \p document emits this signal whenever the \p mark changes. * \param document the document which emitted the signal * \param mark changed mark * \param action action, either removed or added * \see marksChanged() */ virtual void markChanged(KTextEditor::Document *document, KTextEditor::Mark mark, KTextEditor::MarkInterface::MarkChangeAction action) = 0; Q_SIGNALS: /** * The \p document emits this signal whenever the \p mark is hovered using the mouse, * and the receiver may show a tooltip. * \param document the document which emitted the signal * \param mark mark that was hovered * \param position mouse position during the hovering * \param handled set this to 'true' if this event was handled externally */ void markToolTipRequested(KTextEditor::Document *document, KTextEditor::Mark mark, QPoint position, bool &handled); /** * The \p document emits this signal whenever the \p mark is right-clicked to show a context menu. * The receiver may show an own context menu instead of the kate internal one. * \param document the document which emitted the signal * \param mark mark that was right-clicked * \param pos position where the menu should be started * \param handled set this to 'true' if this event was handled externally, and kate should not create an own context menu. */ void markContextMenuRequested(KTextEditor::Document *document, KTextEditor::Mark mark, QPoint pos, bool &handled); /** * The \p document emits this signal whenever the \p mark is left-clicked. * \param document the document which emitted the signal * \param mark mark that was right-clicked * \param handled set this to 'true' if this event was handled externally, and kate should not do own handling of the left click. */ void markClicked(KTextEditor::Document *document, KTextEditor::Mark mark, bool &handled); private: - class MarkInterfacePrivate *const d; + class MarkInterfacePrivate *const d = nullptr; }; } Q_DECLARE_INTERFACE(KTextEditor::MarkInterface, "org.kde.KTextEditor.MarkInterface") #endif diff --git a/src/include/ktexteditor/modificationinterface.h b/src/include/ktexteditor/modificationinterface.h index 4f41265b..14f3f6b6 100644 --- a/src/include/ktexteditor/modificationinterface.h +++ b/src/include/ktexteditor/modificationinterface.h @@ -1,138 +1,138 @@ /* This file is part of the KDE project Copyright (C) 2005 Christoph Cullmann (cullmann@kde.org) Copyright (C) 2005 Dominik Haumann (dhdev@gmx.de) (documentation) 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 KTEXTEDITOR_MODIFICATIONINTERFACE_H #define KTEXTEDITOR_MODIFICATIONINTERFACE_H #include #include namespace KTextEditor { class Document; class View; /** * \brief External modification extension interface for the Document. * * \ingroup kte_group_doc_extensions * * \section modiface_intro Introduction * * The class ModificationInterface provides methods to handle modifications * of all opened files caused by external programs. Whenever the * modified-on-disk state changes the signal modifiedOnDisk() is emitted * along with a ModifiedOnDiskReason. Set the state by calling * setModifiedOnDisk(). Whether the Editor should show warning dialogs to * inform the user about external modified files can be controlled with * setModifiedOnDiskWarning(). The slot modifiedOnDisk() is called to ask * the user what to do whenever a file was modified. * * \section modiface_access Accessing the ModificationInterface * * The ModificationInterface is supposed to be an extension interface for a * Document, i.e. the Document inherits the interface \e provided that the * used KTextEditor library implements the interface. Use qobject_cast to * access the interface: * \code * // doc is of type KTextEditor::Document* * KTextEditor::ModificationInterface *iface = * qobject_cast( doc ); * * if( iface ) { * // the implementation supports the interface * // do stuff * } * \endcode * * \see KTextEditor::Document * \author Christoph Cullmann \ */ class KTEXTEDITOR_EXPORT ModificationInterface { public: ModificationInterface(); /** * Virtual destructor. */ virtual ~ModificationInterface(); public: /** * Reasons why a document is modified on disk. */ enum ModifiedOnDiskReason { OnDiskUnmodified = 0, ///< Not modified OnDiskModified = 1, ///< The file was modified by another program OnDiskCreated = 2, ///< The file was created by another program OnDiskDeleted = 3 ///< The file was deleted }; public: /** * Set the document's modified-on-disk state to \p reason. * KTextEditor implementations should emit the signal modifiedOnDisk() * along with the reason. When the document is in a clean state again the * reason should be ModifiedOnDiskReason::OnDiskUnmodified. * * \param reason the modified-on-disk reason. * \see ModifiedOnDiskReason, modifiedOnDisk() */ virtual void setModifiedOnDisk(ModifiedOnDiskReason reason) = 0; /** * Control, whether the editor should show a warning dialog whenever a file * was modified on disk. If \p on is \e true the editor will show warning * dialogs. * \param on controls, whether the editor should show a warning dialog for * files modified on disk */ virtual void setModifiedOnDiskWarning(bool on) = 0; /* * These stuff is implemented as SIGNALS in the real document */ public: /** * This signal is emitted whenever the \p document changed its * modified-on-disk state. * \param document the Document object that represents the file on disk * \param isModified if \e true, the file was modified rather than created * or deleted * \param reason the reason why the signal was emitted * \see setModifiedOnDisk() */ virtual void modifiedOnDisk(KTextEditor::Document *document, bool isModified, KTextEditor::ModificationInterface::ModifiedOnDiskReason reason) = 0; private: - class ModificationInterfacePrivate *const d; + class ModificationInterfacePrivate *const d = nullptr; }; } Q_DECLARE_INTERFACE(KTextEditor::ModificationInterface, "org.kde.KTextEditor.ModificationInterface") #endif diff --git a/src/include/ktexteditor/movinginterface.h b/src/include/ktexteditor/movinginterface.h index 2d334d83..be83ee74 100644 --- a/src/include/ktexteditor/movinginterface.h +++ b/src/include/ktexteditor/movinginterface.h @@ -1,184 +1,184 @@ /* This file is part of the KDE project * * Copyright (C) 2010 Christoph Cullmann * * Based on code of the SmartCursor/Range by: * Copyright (C) 2003-2005 Hamish Rodda * * 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 KTEXTEDITOR_MOVINGINTERFACE_H #define KTEXTEDITOR_MOVINGINTERFACE_H #include #include #include #include namespace KTextEditor { /** * \brief Document interface for MovingCursor%s and MovingRange%s. * * \ingroup kte_group_doc_extensions * \ingroup kte_group_moving_classes * * This class provides the interface for KTextEditor::Documents to create MovingCursors/Ranges. * * \author Christoph Cullmann \ * * \since 4.5 */ class KTEXTEDITOR_EXPORT MovingInterface { // // Constructor/Destructor // public: /** * Default constructor */ MovingInterface(); /** * Virtual destructor */ virtual ~MovingInterface(); // // Normal API // public: /** * Create a new moving cursor for this document. * @param position position of the moving cursor to create * @param insertBehavior insertion behavior * @return new moving cursor for the document */ virtual MovingCursor *newMovingCursor(const Cursor &position, MovingCursor::InsertBehavior insertBehavior = MovingCursor::MoveOnInsert) = 0; /** * Create a new moving range for this document. * @param range range of the moving range to create * @param insertBehaviors insertion behaviors * @param emptyBehavior behavior on becoming empty * @return new moving range for the document */ virtual MovingRange *newMovingRange(const Range &range, MovingRange::InsertBehaviors insertBehaviors = MovingRange::DoNotExpand, MovingRange::EmptyBehavior emptyBehavior = MovingRange::AllowEmpty) = 0; /** * Current revision * @return current revision */ virtual qint64 revision() const = 0; /** * Last revision the buffer got successful saved * @return last revision buffer got saved, -1 if none */ virtual qint64 lastSavedRevision() const = 0; /** * Lock a revision, this will keep it around until released again. * But all revisions will always be cleared on buffer clear() (and therefor load()) * @param revision revision to lock */ virtual void lockRevision(qint64 revision) = 0; /** * Release a revision. * @param revision revision to release */ virtual void unlockRevision(qint64 revision) = 0; /** * Transform a cursor from one revision to an other. * @param cursor cursor to transform * @param insertBehavior behavior of this cursor on insert of text at its position * @param fromRevision from this revision we want to transform * @param toRevision to this revision we want to transform, default of -1 is current revision */ virtual void transformCursor(KTextEditor::Cursor &cursor, KTextEditor::MovingCursor::InsertBehavior insertBehavior, qint64 fromRevision, qint64 toRevision = -1) = 0; /** * Transform a cursor from one revision to an other. * @param line line number of the cursor to transform * @param column column number of the cursor to transform * @param insertBehavior behavior of this cursor on insert of text at its position * @param fromRevision from this revision we want to transform * @param toRevision to this revision we want to transform, default of -1 is current revision */ virtual void transformCursor(int &line, int &column, KTextEditor::MovingCursor::InsertBehavior insertBehavior, qint64 fromRevision, qint64 toRevision = -1) = 0; /** * Transform a range from one revision to an other. * @param range range to transform * @param insertBehaviors behavior of this range on insert of text at its position * @param emptyBehavior behavior on becoming empty * @param fromRevision from this revision we want to transform * @param toRevision to this revision we want to transform, default of -1 is current revision */ virtual void transformRange(KTextEditor::Range &range, KTextEditor::MovingRange::InsertBehaviors insertBehaviors, MovingRange::EmptyBehavior emptyBehavior, qint64 fromRevision, qint64 toRevision = -1) = 0; // // Signals // public: /** * This signal is emitted before the cursors/ranges/revisions of a document * are destroyed as the document is deleted. * @param document the document which the interface belongs to which is in the process of being deleted */ void aboutToDeleteMovingInterfaceContent(KTextEditor::Document *document); /** * This signal is emitted before the ranges of a document are invalidated * and the revisions are deleted as the document is cleared (for example on * load/reload). While this signal is emitted, the old document content is * still valid and accessible before the clear. * @param document the document which the interface belongs to which will invalidate its data */ void aboutToInvalidateMovingInterfaceContent(KTextEditor::Document *document); private: /** * private d-pointer */ - class MovingInterfacePrivate *const d; + class MovingInterfacePrivate *const d = nullptr; }; } Q_DECLARE_INTERFACE(KTextEditor::MovingInterface, "org.kde.KTextEditor.MovingInterface") #endif diff --git a/src/include/ktexteditor/movingrangefeedback.h b/src/include/ktexteditor/movingrangefeedback.h index 14cffbaf..925257a5 100644 --- a/src/include/ktexteditor/movingrangefeedback.h +++ b/src/include/ktexteditor/movingrangefeedback.h @@ -1,120 +1,120 @@ /* This file is part of the KDE project * * Copyright (C) 2010 Christoph Cullmann * * Based on code of the SmartCursor/Range by: * Copyright (C) 2003-2005 Hamish Rodda * * 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 KTEXTEDITOR_MOVINGRANGEFEEDBACK_H #define KTEXTEDITOR_MOVINGRANGEFEEDBACK_H #include namespace KTextEditor { class View; class MovingRange; /** * \short A class which provides notifications of state changes to a MovingRange. * * \ingroup kte_group_moving_classes * * This class provides notifications of changes to the position or contents of a MovingRange. * * Before destruction, you must unregister the feedback class from any range using it. * * \author Christoph Cullmann \ * * \since 4.5 */ class KTEXTEDITOR_EXPORT MovingRangeFeedback { public: /** * Default constructor */ MovingRangeFeedback(); /** * Virtual destructor */ virtual ~MovingRangeFeedback(); /** * The range is now empty (ie. the start and end cursors are the same). * If the range has invalidateIfEmpty set, this will never be emitted, but instead rangeInvalid is triggered. * You may delete the range inside this method, but don't alter the range here (for example by using setRange). * * \param range pointer to the range which generated the notification. */ virtual void rangeEmpty(MovingRange *range); /** * The range is now invalid (ie. the start and end cursors are invalid). * You may delete the range inside this method, but don't alter the range here (for example by using setRange). * * \param range pointer to the range which generated the notification. */ virtual void rangeInvalid(MovingRange *range); /** * The mouse cursor on \a view entered \p range. * * \param range pointer to the range which generated the notification. * \param view view over which the mouse moved to generate the notification */ virtual void mouseEnteredRange(MovingRange *range, View *view); /** * The mouse cursor on \a view exited \p range. * * \param range pointer to the range which generated the notification. * \param view view over which the mouse moved to generate the notification */ virtual void mouseExitedRange(MovingRange *range, View *view); /** * The caret on \a view entered \p range. * * \param range pointer to the range which generated the notification. * \param view view over which the mouse moved to generate the notification */ virtual void caretEnteredRange(MovingRange *range, View *view); /** * The caret on \a view exited \p range. * * \param range pointer to the range which generated the notification. * \param view view over which the mouse moved to generate the notification */ virtual void caretExitedRange(MovingRange *range, View *view); private: /** * private d-pointer */ - class MovingRangeFeedbackPrivate *const d; + class MovingRangeFeedbackPrivate *const d = nullptr; }; } #endif diff --git a/src/include/ktexteditor/plugin.h b/src/include/ktexteditor/plugin.h index 2e84a35d..fb17011f 100644 --- a/src/include/ktexteditor/plugin.h +++ b/src/include/ktexteditor/plugin.h @@ -1,150 +1,150 @@ /* This file is part of the KDE libraries Copyright (C) 2001-2014 Christoph Cullmann Copyright (C) 2005-2014 Dominik Haumann (dhaumann@kde.org) 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 KTEXTEDITOR_PLUGIN_H #define KTEXTEDITOR_PLUGIN_H #include #include namespace KTextEditor { class ConfigPage; class MainWindow; /** * \brief KTextEditor Plugin interface. * * Topics: * - \ref plugin_intro * - \ref plugin_config_pages * - \ref plugin_sessions * * \section plugin_intro Introduction * * The Plugin class provides methods to create loadable plugins for the * KTextEditor framework. The Plugin class itself a function createView() * that is called for each MainWindow. In createView(), the plugin is * responsible to create tool views through MainWindow::createToolView(), * hook itself into the menus and toolbars through KXMLGuiClient, and attach * itself to View%s or Document%s. * * \section plugin_config_pages Plugin Config Pages * * If your plugin needs config pages, override the functions configPages() * and configPage() in your plugin as follows: * \code * class MyPlugin : public KTextEditor::Plugin * { * Q_OBJECT * public: * // ... - * int configPages() const Q_DECL_OVERRIDE; - * ConfigPage *configPage(int number, QWidget *parent) Q_DECL_OVERRIDE; + * int configPages() const override; + * ConfigPage *configPage(int number, QWidget *parent) override; * }; * \endcode * The host application will call configPage() for each config page. * * \section plugin_sessions Session Management * * As an extension a Plugin can implement the SessionConfigInterface. This * interface provides functions to read and write session related settings. * If you have session dependent data additionally derive your Plugin from * this interface and implement the session related functions, for example: * \code * class MyPlugin : public KTextEditor::Plugin, * public KTextEditor::SessionConfigInterface * { * Q_OBJECT * Q_INTERFACES(KTextEditor::SessionConfigInterface) * public: * // ... - * void readSessionConfig (const KConfigGroup& config) Q_DECL_OVERRIDE; - * void writeSessionConfig (KConfigGroup& config) Q_DECL_OVERRIDE; + * void readSessionConfig (const KConfigGroup& config) override; + * void writeSessionConfig (KConfigGroup& config) override; * }; * \endcode * * \see KTextEditor::SessionConfigInterface, KTextEditor::ConfigPage, * KTextEditor::MainWindow * \author Christoph Cullmann \ */ class KTEXTEDITOR_EXPORT Plugin : public QObject { Q_OBJECT public: /** * Constructor. * * Create a new application plugin. * \param parent parent object */ Plugin(QObject *parent); /** * Virtual destructor. */ virtual ~Plugin(); /** * Create a new View for this plugin for the given KTextEditor::MainWindow. * This may be called arbitrary often by the application to create as much * views as MainWindow%s exist. The application will take care to delete * a view whenever a MainWindow is closed, so you do not need to handle * deletion of the view yourself in the plugin. * * \note Session configuration: The host application will try to cast the * returned QObject with \p qobject_cast into the SessionConfigInterface. * This way, not only your Plugin, but also each Plugin view can have * session related settings. * * \param mainWindow the MainWindow for which a view should be created * \return the new created view or NULL * @see SessionConfigInterface */ virtual QObject *createView(KTextEditor::MainWindow *mainWindow) = 0; /** * Get the number of available config pages. * If a number < 1 is returned, it does not support config pages. * \return number of config pages, default implementation says 0 * \see configPage() */ virtual int configPages() const; /** * Get the config page with the \p number, config pages from 0 to * configPages()-1 are available if configPages() > 0. * \param number index of config page * \param parent parent widget for config page * \return created config page or NULL, if the number is out of bounds, default implementation returns NULL * \see configPages() */ virtual ConfigPage *configPage(int number, QWidget *parent); private: class PluginPrivate *const d; }; } #endif diff --git a/src/include/ktexteditor/sessionconfiginterface.h b/src/include/ktexteditor/sessionconfiginterface.h index f7de6047..cfe4dd91 100644 --- a/src/include/ktexteditor/sessionconfiginterface.h +++ b/src/include/ktexteditor/sessionconfiginterface.h @@ -1,123 +1,123 @@ /* This file is part of the KDE libraries Copyright (C) 2001-2014 Christoph Cullmann Copyright (C) 2005-2014 Dominik Haumann (dhaumann@kde.org) Copyright (C) 2009 Michel Ludwig (michel.ludwig@kdemail.net) 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 KTEXTEDITOR_SESSIONCONFIGINTERFACE_H #define KTEXTEDITOR_SESSIONCONFIGINTERFACE_H #include class KConfigGroup; #include namespace KTextEditor { /** * \brief Session config interface extension for the Plugin and Plugin views. * * \ingroup kte_group_plugin_extensions * * \section sessionconfig_intro Introduction * * The SessionConfigInterface is an extension for Plugin%s and Plugin views * to add support for session-specific configuration settings. * readSessionConfig() is called whenever session-specific settings are to be * read from the given KConfigGroup and writeSessionConfig() whenever they are to * be written, for example when a session changed or was closed. * * \note A \e session does not have anything to do with an X-session under Unix. * What is meant is rather a context, think of sessions in Kate or * projects in KDevelop for example. * * \section sessionconfig_support Adding Session Support * * To add support for sessions, your Plugin has to inherit the SessionConfigInterface * and reimplement readSessionConfig() and writeSessionConfig(). * * \section sessionconfig_access Accessing the SessionConfigInterface * * This secion is for application developers such as Kate, KDevelop, etc that * what to support session configuration for plugins. * * The SessionConfigInterface is an extension interface for a Plugin or a * Plugin view, i.e. Plugin/Plugin view inherits the interface * \e provided that it implements the interface. Use qobject_cast to * access the interface: * \code * // object is of type Plugin* or, in case of a plugin view, QObject* * KTextEditor::SessionConfigInterface *iface = * qobject_cast( object ); * * if( iface ) { * // interface is supported * // do stuff * } * \endcode * * \see KTextEditor::Plugin * \author Christoph Cullmann \ */ class KTEXTEDITOR_EXPORT SessionConfigInterface { public: SessionConfigInterface(); /** * Virtual destructor. */ virtual ~SessionConfigInterface(); public: /** * Read session settings from the given \p config. * * That means for example * - a Document should reload the file, restore all marks etc... * - a View should scroll to the last position and restore the cursor * position etc... * - a Plugin should restore session specific settings * - If no file is being loaded, because an empty new document is going to be displayed, * this function should emit ReadOnlyPart::completed * * \param config read the session settings from this KConfigGroup * \see writeSessionConfig() */ virtual void readSessionConfig(const KConfigGroup &config) = 0; /** * Write session settings to the \p config. * See readSessionConfig() for more details. * * \param config write the session settings to this KConfigGroup * \see readSessionConfig() */ virtual void writeSessionConfig(KConfigGroup &config) = 0; private: - class SessionConfigInterfacePrivate *const d; + class SessionConfigInterfacePrivate *const d = nullptr; }; } Q_DECLARE_INTERFACE(KTextEditor::SessionConfigInterface, "org.kde.KTextEditor.SessionConfigInterface") #endif diff --git a/src/include/ktexteditor/texthintinterface.h b/src/include/ktexteditor/texthintinterface.h index ee24c1c5..8eefc7cc 100644 --- a/src/include/ktexteditor/texthintinterface.h +++ b/src/include/ktexteditor/texthintinterface.h @@ -1,184 +1,184 @@ /* This file is part of the KDE libraries Copyright (C) 2001 Joseph Wenninger Copyright (C) 2013-2014 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. */ #ifndef KTEXTEDITOR_TEXTHINTINTERFACE_H #define KTEXTEDITOR_TEXTHINTINTERFACE_H #include #include #include namespace KTextEditor { class TextHintProvider; class View; /** * \brief Text hint interface showing tool tips under the mouse for the View. * * \ingroup kte_group_view_extensions * * \section texthint_introduction Introduction * * The text hint interface provides a way to show tool tips for text located * under the mouse. Possible applications include showing a value of a variable * when debugging an application, or showing a complete path of an include * directive. * * \image html texthint.png "Text hint showing the contents of a variable" * * To register as text hint provider, call registerTextHintProvider() with an * instance that inherits TextHintProvider. Finally, make sure you remove your * text hint provider by calling unregisterTextHintProvider(). * * Text hints are shown after the user hovers with the mouse for a delay of * textHintDelay() milliseconds over the same word. To change the delay, call * setTextHintDelay(). * * \section texthint_access Accessing the TextHintInterface * * The TextHintInterface is an extension interface for a * View, i.e. the View inherits the interface. Use qobject_cast to access the * interface: * \code * // view is of type KTextEditor::View* * KTextEditor::TextHintInterface *iface = * qobject_cast(view); * * if (iface) { * // the implementation supports the interface * // myProvider inherits KTextEditor::TextHintProvider * iface->registerTextHintProvider(myProvider); * } * \endcode * * \see TextHintProvider * \since 4.11 */ class KTEXTEDITOR_EXPORT TextHintInterface { public: TextHintInterface(); virtual ~TextHintInterface(); /** * Register the text hint provider \p provider. * * Whenever the user hovers over text, \p provider will be asked for * a text hint. When the provider is about to be destroyed, make * sure to call unregisterTextHintProvider() to avoid a dangling pointer. * * @param provider text hint provider * @see unregisterTextHintProvider(), TextHintProvider */ virtual void registerTextHintProvider(KTextEditor::TextHintProvider *provider) = 0; /** * Unregister the text hint provider \p provider. * * @param provider text hint provider to unregister * @see registerTextHintProvider(), TextHintProvider */ virtual void unregisterTextHintProvider(KTextEditor::TextHintProvider * provider) = 0; /** * Set the text hint delay to \p delay milliseconds. * * The delay specifies the time the user needs to hover over the text * before the tool tip is shown. Therefore, \p delay should not be * too large, a value of 500 milliseconds is recommended and set by * default. * * If \p delay is <= 0, the default delay will be set. * * \param delay tool tip delay in milliseconds */ virtual void setTextHintDelay(int delay) = 0; /** * Get the text hint delay in milliseconds. * By default, the text hint delay is set to 500 milliseconds. * It can be changed by calling \p setTextHintDelay(). */ virtual int textHintDelay() const = 0; private: - class TextHintInterfacePrivate * const d; + class TextHintInterfacePrivate * const d = nullptr; }; /** * \brief Class to provide text hints for a View. * * The class TextHintProvider is used in combination with TextHintInterface. * TextHintProvider allows to provide text hint information for text under * the mouse cursor. * * To use this class, derive your provider from TextHintProvider and register * it with TextHintInterface::registerTextHintProvider(). When not needed * anymore, make sure to remove your provider by calling * TextHintInterface::unregisterTextHintProvider(), otherwise the View will * contain a dangling pointer to your potentially deleted provider. * * Detailed information about how to use the TextHintInterface can be found * in the documentation about the TextHintInterface. * * \see TextHintInterface * \p since 5.0 */ class KTEXTEDITOR_EXPORT TextHintProvider { public: /** * Default constructor. */ TextHintProvider(); /** * Virtual destructor to allow inheritance. */ virtual ~TextHintProvider(); /** * This function is called whenever the users hovers over text such * that the text hint delay passes. Then, textHint() is called * for each registered TextHintProvider. * * Return the text hint (possibly Qt richtext) for @p view at @p position. * * If you do not have any contents to show, just return an empty QString(). * * \param view the view that requests the text hint * \param position text cursor under the mouse position * \return text tool tip to be displayed, may be Qt richtext */ virtual QString textHint(KTextEditor::View *view, const KTextEditor::Cursor &position) = 0; private: - class TextHintProviderPrivate * const d; + class TextHintProviderPrivate * const d = nullptr; }; } Q_DECLARE_INTERFACE(KTextEditor::TextHintInterface, "org.kde.KTextEditor.TextHintInterface") #endif diff --git a/src/inputmode/katenormalinputmode.h b/src/inputmode/katenormalinputmode.h index 2dff4df0..27e00bca 100644 --- a/src/inputmode/katenormalinputmode.h +++ b/src/inputmode/katenormalinputmode.h @@ -1,118 +1,118 @@ /* This file is part of the KDE libraries and the Kate part. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __KATE_NORMAL_INPUT_MODE_H__ #define __KATE_NORMAL_INPUT_MODE_H__ #include "kateabstractinputmode.h" class KateNormalInputModeFactory; class KateSearchBar; class KateCommandLineBar; class KateNormalInputMode : public KateAbstractInputMode { KateNormalInputMode(KateViewInternal *viewInternal); friend KateNormalInputModeFactory; public: - virtual ~KateNormalInputMode(); + ~KateNormalInputMode() override; - KTextEditor::View::ViewMode viewMode() const Q_DECL_OVERRIDE; - QString viewModeHuman() const Q_DECL_OVERRIDE; - KTextEditor::View::InputMode viewInputMode() const Q_DECL_OVERRIDE; - QString viewInputModeHuman() const Q_DECL_OVERRIDE; + KTextEditor::View::ViewMode viewMode() const override; + QString viewModeHuman() const override; + KTextEditor::View::InputMode viewInputMode() const override; + QString viewInputModeHuman() const override; - void activate() Q_DECL_OVERRIDE; - void deactivate() Q_DECL_OVERRIDE; - void reset() Q_DECL_OVERRIDE; + void activate() override; + void deactivate() override; + void reset() override; - bool overwrite() const Q_DECL_OVERRIDE; - void overwrittenChar(const QChar &) Q_DECL_OVERRIDE; + bool overwrite() const override; + void overwrittenChar(const QChar &) override; - void clearSelection() Q_DECL_OVERRIDE; - bool stealKey(QKeyEvent *) Q_DECL_OVERRIDE; + void clearSelection() override; + bool stealKey(QKeyEvent *) override; - void gotFocus() Q_DECL_OVERRIDE; - void lostFocus() Q_DECL_OVERRIDE; + void gotFocus() override; + void lostFocus() override; - void readSessionConfig(const KConfigGroup &config) Q_DECL_OVERRIDE; - void writeSessionConfig(KConfigGroup &config) Q_DECL_OVERRIDE; - void updateRendererConfig() Q_DECL_OVERRIDE; - void updateConfig() Q_DECL_OVERRIDE; - void readWriteChanged(bool rw) Q_DECL_OVERRIDE; + void readSessionConfig(const KConfigGroup &config) override; + void writeSessionConfig(KConfigGroup &config) override; + void updateRendererConfig() override; + void updateConfig() override; + void readWriteChanged(bool rw) override; - void find() Q_DECL_OVERRIDE; - void findSelectedForwards() Q_DECL_OVERRIDE; - void findSelectedBackwards() Q_DECL_OVERRIDE; - void findReplace() Q_DECL_OVERRIDE; - void findNext() Q_DECL_OVERRIDE; - void findPrevious() Q_DECL_OVERRIDE; + void find() override; + void findSelectedForwards() override; + void findSelectedBackwards() override; + void findReplace() override; + void findNext() override; + void findPrevious() override; - void activateCommandLine() Q_DECL_OVERRIDE; + void activateCommandLine() override; - bool keyPress(QKeyEvent *) Q_DECL_OVERRIDE; - bool blinkCaret() const Q_DECL_OVERRIDE; - KateRenderer::caretStyles caretStyle() const Q_DECL_OVERRIDE; + bool keyPress(QKeyEvent *) override; + bool blinkCaret() const override; + KateRenderer::caretStyles caretStyle() const override; - void toggleInsert() Q_DECL_OVERRIDE; - void launchInteractiveCommand(const QString &command) Q_DECL_OVERRIDE; + void toggleInsert() override; + void launchInteractiveCommand(const QString &command) override; - QString bookmarkLabel(int line) const Q_DECL_OVERRIDE; + QString bookmarkLabel(int line) const override; private: /** * Search bar mode: * - Setup Incremental mode, among other things: potential new search pattern * - Setup Power mode, aka find & replace: Also potential new search pattern * - Use current mode and current search pattern or if no Search bar exists, launch Incremental mode */ enum SearchBarMode { IncrementalSearchBar, PowerSearchBar, IncrementalSearchBarOrKeepMode }; /** * Get search bar, create it on demand. (with right mode) * @param mode wanted search bar mode * @return search bar widget */ KateSearchBar *searchBar(const SearchBarMode mode); /** * search bar around? * @return search bar around? */ bool hasSearchBar() const { return m_searchBar; } /** * Get command line bar, create it on demand. * @return command line bar, created if not already there */ KateCommandLineBar *cmdLineBar(); private: KateSearchBar *m_searchBar; KateCommandLineBar *m_cmdLine; }; #endif diff --git a/src/inputmode/katenormalinputmodefactory.h b/src/inputmode/katenormalinputmodefactory.h index 22492389..c0746388 100644 --- a/src/inputmode/katenormalinputmodefactory.h +++ b/src/inputmode/katenormalinputmodefactory.h @@ -1,38 +1,38 @@ /* This file is part of the KDE libraries and the Kate part. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __KATE_NORMAL_INPUT_MODE_FACTORY_H__ #define __KATE_NORMAL_INPUT_MODE_FACTORY_H__ #include "kateabstractinputmodefactory.h" class KateNormalInputModeFactory : public KateAbstractInputModeFactory { public: KateNormalInputModeFactory(); - virtual ~KateNormalInputModeFactory(); - KateAbstractInputMode *createInputMode(KateViewInternal *viewInternal) Q_DECL_OVERRIDE; + ~KateNormalInputModeFactory() override; + KateAbstractInputMode *createInputMode(KateViewInternal *viewInternal) override; - QString name() Q_DECL_OVERRIDE; - KTextEditor::View::InputMode inputMode() Q_DECL_OVERRIDE; + QString name() override; + KTextEditor::View::InputMode inputMode() override; - KateConfigPage *createConfigPage(QWidget *) Q_DECL_OVERRIDE; + KateConfigPage *createConfigPage(QWidget *) override; }; #endif diff --git a/src/inputmode/kateviinputmode.h b/src/inputmode/kateviinputmode.h index 330148f8..ec678237 100644 --- a/src/inputmode/kateviinputmode.h +++ b/src/inputmode/kateviinputmode.h @@ -1,102 +1,102 @@ /* This file is part of the KDE libraries and the Kate part. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __KATE_VI_INPUT_MODE_H__ #define __KATE_VI_INPUT_MODE_H__ #include "kateabstractinputmode.h" namespace KateVi { class GlobalState; class InputModeManager; class EmulatedCommandBar; } class KateViInputModeFactory; class KTEXTEDITOR_EXPORT KateViInputMode : public KateAbstractInputMode { explicit KateViInputMode(KateViewInternal *viewInternal, KateVi::GlobalState *global); friend KateViInputModeFactory; public: - virtual ~KateViInputMode(); + ~KateViInputMode() override; - KTextEditor::View::ViewMode viewMode() const Q_DECL_OVERRIDE; - QString viewModeHuman() const Q_DECL_OVERRIDE; - KTextEditor::View::InputMode viewInputMode() const Q_DECL_OVERRIDE; - QString viewInputModeHuman() const Q_DECL_OVERRIDE; + KTextEditor::View::ViewMode viewMode() const override; + QString viewModeHuman() const override; + KTextEditor::View::InputMode viewInputMode() const override; + QString viewInputModeHuman() const override; - void activate() Q_DECL_OVERRIDE; - void deactivate() Q_DECL_OVERRIDE; - void reset() Q_DECL_OVERRIDE; + void activate() override; + void deactivate() override; + void reset() override; - bool overwrite() const Q_DECL_OVERRIDE; - void overwrittenChar(const QChar &) Q_DECL_OVERRIDE; + bool overwrite() const override; + void overwrittenChar(const QChar &) override; - void clearSelection() Q_DECL_OVERRIDE; - bool stealKey(QKeyEvent *) Q_DECL_OVERRIDE; + void clearSelection() override; + bool stealKey(QKeyEvent *) override; - void gotFocus() Q_DECL_OVERRIDE; - void lostFocus() Q_DECL_OVERRIDE; + void gotFocus() override; + void lostFocus() override; - void readSessionConfig(const KConfigGroup &config) Q_DECL_OVERRIDE; - void writeSessionConfig(KConfigGroup &config) Q_DECL_OVERRIDE; - void updateRendererConfig() Q_DECL_OVERRIDE; - void updateConfig() Q_DECL_OVERRIDE; - void readWriteChanged(bool rw) Q_DECL_OVERRIDE; + void readSessionConfig(const KConfigGroup &config) override; + void writeSessionConfig(KConfigGroup &config) override; + void updateRendererConfig() override; + void updateConfig() override; + void readWriteChanged(bool rw) override; - void find() Q_DECL_OVERRIDE; - void findSelectedForwards() Q_DECL_OVERRIDE; - void findSelectedBackwards() Q_DECL_OVERRIDE; - void findReplace() Q_DECL_OVERRIDE; - void findNext() Q_DECL_OVERRIDE; - void findPrevious() Q_DECL_OVERRIDE; + void find() override; + void findSelectedForwards() override; + void findSelectedBackwards() override; + void findReplace() override; + void findNext() override; + void findPrevious() override; - void activateCommandLine() Q_DECL_OVERRIDE; + void activateCommandLine() override; - bool keyPress(QKeyEvent *) Q_DECL_OVERRIDE; - bool blinkCaret() const Q_DECL_OVERRIDE; - KateRenderer::caretStyles caretStyle() const Q_DECL_OVERRIDE; + bool keyPress(QKeyEvent *) override; + bool blinkCaret() const override; + KateRenderer::caretStyles caretStyle() const override; - void toggleInsert() Q_DECL_OVERRIDE; - void launchInteractiveCommand(const QString &command) Q_DECL_OVERRIDE; + void toggleInsert() override; + void launchInteractiveCommand(const QString &command) override; - QString bookmarkLabel(int line) const Q_DECL_OVERRIDE; + QString bookmarkLabel(int line) const override; public: void showViModeEmulatedCommandBar(); KateVi::EmulatedCommandBar *viModeEmulatedCommandBar(); inline KateVi::GlobalState *globalState() const { return m_viGlobal; } inline KateVi::InputModeManager *viInputModeManager() const { return m_viModeManager; } inline bool isActive() const { return m_activated; } void setCaretStyle(const KateRenderer::caretStyles caret); private: KateVi::InputModeManager *m_viModeManager; KateVi::EmulatedCommandBar *m_viModeEmulatedCommandBar; KateVi::GlobalState *m_viGlobal; KateRenderer::caretStyles m_caret; bool m_nextKeypressIsOverriddenShortCut; // configs bool m_relLineNumbers; bool m_activated; }; #endif diff --git a/src/inputmode/kateviinputmodefactory.h b/src/inputmode/kateviinputmodefactory.h index 8b0bf50c..7b2fc08f 100644 --- a/src/inputmode/kateviinputmodefactory.h +++ b/src/inputmode/kateviinputmodefactory.h @@ -1,44 +1,44 @@ /* This file is part of the KDE libraries and the Kate part. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __KATE_VI_INPUT_MODE_FACTORY_H__ #define __KATE_VI_INPUT_MODE_FACTORY_H__ #include "kateabstractinputmodefactory.h" namespace KateVi { class GlobalState; } class KateViInputMode; class KateViInputModeFactory : public KateAbstractInputModeFactory { friend KateViInputMode; public: KateViInputModeFactory(); - virtual ~KateViInputModeFactory(); - KateAbstractInputMode *createInputMode(KateViewInternal *viewInternal) Q_DECL_OVERRIDE; + ~KateViInputModeFactory() override; + KateAbstractInputMode *createInputMode(KateViewInternal *viewInternal) override; - QString name() Q_DECL_OVERRIDE; - KTextEditor::View::InputMode inputMode() Q_DECL_OVERRIDE; + QString name() override; + KTextEditor::View::InputMode inputMode() override; - KateConfigPage *createConfigPage(QWidget *) Q_DECL_OVERRIDE; + KateConfigPage *createConfigPage(QWidget *) override; private: KateVi::GlobalState *m_viGlobal; }; #endif diff --git a/src/mode/katemodeconfigpage.h b/src/mode/katemodeconfigpage.h index 2d26b843..47d65fc7 100644 --- a/src/mode/katemodeconfigpage.h +++ b/src/mode/katemodeconfigpage.h @@ -1,66 +1,66 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2001-2010 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_MODECONFIGPAGE_H__ #define KATE_MODECONFIGPAGE_H__ #include #include #include "katedialogs.h" #include "katemodemanager.h" namespace Ui { class FileTypeConfigWidget; } class ModeConfigPage : public KateConfigPage { Q_OBJECT public: explicit ModeConfigPage(QWidget *parent); - ~ModeConfigPage(); - QString name() const Q_DECL_OVERRIDE; + ~ModeConfigPage() override; + QString name() const override; public Q_SLOTS: - void apply() Q_DECL_OVERRIDE; - void reload() Q_DECL_OVERRIDE; - void reset() Q_DECL_OVERRIDE; - void defaults() Q_DECL_OVERRIDE; + void apply() override; + void reload() override; + void reset() override; + void defaults() override; private Q_SLOTS: void update(); void deleteType(); void newType(); void typeChanged(int type); void showMTDlg(); void save(); void hlDownload(); private: Ui::FileTypeConfigWidget *ui; QList m_types; int m_lastType; }; #endif diff --git a/src/mode/katemodemanager.h b/src/mode/katemodemanager.h index 4896976e..64237695 100644 --- a/src/mode/katemodemanager.h +++ b/src/mode/katemodemanager.h @@ -1,109 +1,109 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2001-2010 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_MODEMANAGER_H__ #define KATE_MODEMANAGER_H__ #include #include #include #include #include "katedialogs.h" namespace KTextEditor { class DocumentPrivate; } class KateFileType { public: - int number; + int number = -1; QString name; QString section; QStringList wildcards; QStringList mimetypes; - int priority; + int priority = 0; QString varLine; QString hl; - bool hlGenerated; + bool hlGenerated = false; QString version; QString indenter; QString translatedName; QString translatedSection; QString nameTranslated() const { return translatedName.isEmpty() ? name : translatedName; } QString sectionTranslated() const { return translatedSection.isEmpty() ? section : translatedSection; } KateFileType() - : number(-1), priority(0), hlGenerated(false) + {} }; class KateModeManager { public: KateModeManager(); ~KateModeManager(); /** * File Type Config changed, update all docs (which will take care of views/renderers) */ void update(); void save(const QList &v); /** * get the right fileType for the given document * -1 if none found ! */ QString fileType(KTextEditor::DocumentPrivate *doc, const QString &fileToReadFrom); /** * Don't store the pointer somewhere longer times, won't be valid after the next update() */ const KateFileType &fileType(const QString &name) const; /** * Don't modify */ const QList &list() const { return m_types; } private: QString wildcardsFind(const QString &fileName); private: QList m_types; QHash m_name2Type; }; #endif diff --git a/src/mode/katemodemenu.h b/src/mode/katemodemenu.h index 2335305d..f8311c72 100644 --- a/src/mode/katemodemenu.h +++ b/src/mode/katemodemenu.h @@ -1,65 +1,66 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2001-2010 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_MODEMENU_H__ #define KATE_MODEMENU_H__ #include #include #include #include "katedialogs.h" #include "katemodemanager.h" namespace KTextEditor { class DocumentPrivate; } class KateModeMenu : public KActionMenu { Q_OBJECT public: KateModeMenu(const QString &text, QObject *parent) : KActionMenu(text, parent) { init(); + setDelayed(false); } ~KateModeMenu(); void updateMenu(KTextEditor::Document *doc); private: void init(); QPointer m_doc; QStringList subMenusName; QStringList names; QList subMenus; QActionGroup *m_actionGroup; public Q_SLOTS: void slotAboutToShow(); private Q_SLOTS: void setType(QAction *); }; #endif diff --git a/src/part/katepart.cpp b/src/part/katepart.cpp index 6bf29eaf..a4e5fadd 100644 --- a/src/part/katepart.cpp +++ b/src/part/katepart.cpp @@ -1,71 +1,71 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2001-2010 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. */ #include "katedocument.h" #include /** * wrapper factory to be sure nobody external deletes our kateglobal object * each instance will just increment the reference counter of our internal * super private global instance ;) */ class KateFactory : public KPluginFactory { Q_OBJECT Q_PLUGIN_METADATA(IID KPluginFactory_iid FILE "katepart.json") Q_INTERFACES(KPluginFactory) public: /** * This function is called when the factory asked to create an Object. * * You may reimplement it to provide a very flexible factory. This is especially useful to * provide generic factories for plugins implemeted using a scripting language. * * \param iface The staticMetaObject::className() string identifying the plugin interface that * was requested. E.g. for KCModule plugins this string will be "KCModule". * \param parentWidget Only used if the requested plugin is a KPart. * \param parent The parent object for the plugin object. * \param args A plugin specific list of arbitrary arguments. * \param keyword A string that uniquely identifies the plugin. If a KService is used this * keyword is read from the X-KDE-PluginKeyword entry in the .desktop file. */ - QObject *create(const char *iface, QWidget *parentWidget, QObject *parent, const QVariantList &, const QString &) Q_DECL_OVERRIDE + QObject *create(const char *iface, QWidget *parentWidget, QObject *parent, const QVariantList &, const QString &) override { // iface == classname to construct const QByteArray classname (iface); // default to the kparts::* behavior of having one single widget() if the user don't requested a pure document const bool bWantSingleView = (classname != "KTextEditor::Document"); // should we be readonly? const bool bWantReadOnly = (classname == "KParts::ReadOnlyPart"); // construct right part variant KParts::ReadWritePart *part = new KTextEditor::DocumentPrivate (bWantSingleView, bWantReadOnly, parentWidget, parent); part->setReadWrite(!bWantReadOnly); return part; } }; #include "katepart.moc" diff --git a/src/part/katepart.desktop b/src/part/katepart.desktop index f34c4c84..69de3201 100644 --- a/src/part/katepart.desktop +++ b/src/part/katepart.desktop @@ -1,64 +1,65 @@ [Desktop Entry] Name=Embedded Advanced Text Editor Name[ar]=محرّر نصوص مضمّن متقدّم Name[ast]=Editor empotráu y avanzáu de testu Name[bg]=Вграден усъвършенстван текстов редактор Name[bs]=Ugrađeni napredni uređivač teksta Name[ca]=Editor de text avançat incrustat Name[ca@valencia]=Editor de text avançat incrustat Name[cs]=Zabudovaný rozšířený editor Name[da]=Indlejret avanceret teksteditor Name[de]=Einbettungsfähige erweiterte Editorkomponente Name[el]=Ενσωματωμένος προηγμένος επεξεργαστής κειμένου Name[en_GB]=Embedded Advanced Text Editor Name[es]=Editor de texto avanzado empotrado Name[et]=Põimitud võimas tekstiredaktor Name[eu]=Txertatutako testu-editore aurreratu Name[fi]=Kehittynyt upotettava tekstimuokkain Name[fr]=Éditeur de texte intégré avancé Name[ga]=Ardeagarthóir Leabaithe Téacs Name[gd]=Deasaiche teacsa adhartach leabaichte Name[gl]=Editor avanzado de textos integrado Name[hu]=Beágyazott szövegszerkesztő Name[ia]=Avantiate Editor Interne de Texto +Name[id]=Pengedit Teks Lanjutan Terbenam Name[is]=Ívefjanlegur þróaður textaritill Name[it]=Editor di testi avanzato integrato Name[ja]=埋め込みテキストエディタ Name[kk]=Ендірілетін үздік мәтін редакторы Name[km]=កម្មវិធី​និពន្ធ​អត្ថបទ​កម្រិត​ខ្ពស់​ដែល​បង្កប់​ Name[ko]=끼워넣은 고급 텍스트 편집기 Name[lt]=Vidinis sudėtingesnis teksto redaktorius Name[lv]=Iegultais paplašinātais teksta redaktors Name[mr]=अंतर्भूतीत प्रगत पाठ्य संपादक Name[nb]=Innebygget, avansert skriveprogram Name[nds]=Inbett verwiedert Texteditor Name[nl]=Ingebed geavanceerd tekstinvoercomponent Name[nn]=Innebyggbart avansert skriveprogram Name[pa]=ਇੰਬੈੱਡ ਮਾਹਰ ਟੈਕਸਟ ਐਡੀਟਰ Name[pl]=Zaawansowany osadzony edytor tekstu Name[pt]=Editor de Texto Avançado Incorporado Name[pt_BR]=Editor de texto avançado integrado Name[ro]=Redactor de text avansat înglobat Name[ru]=Встроенный расширенный текстовый редактор Name[si]=තිළැලි උසස් පෙළ සකසනය Name[sk]=Zabudovaný pokročilý textový editor Name[sl]=Vgrajen napreden urejevalnik besedil Name[sr]=Угнежђени напредни уређивач текста Name[sr@ijekavian]=Угнијежђени напредни уређивач текста Name[sr@ijekavianlatin]=Ugniježđeni napredni uređivač teksta Name[sr@latin]=Ugnežđeni napredni uređivač teksta Name[sv]=Inbäddningsbar avancerad texteditor Name[tg]=Таҳриргари матнии беҳтаршудаи дарунсохт Name[tr]=Gelişmiş Gömülü Metin Düzenleyici Name[ug]=سىڭدۈرمە KDE ئالىي تېكىست تەھرىرلىگۈچ Name[uk]=Вмонтований потужний текстовий редактор Name[wa]=Ravalé aspougneu di tecse avancî Name[x-test]=xxEmbedded Advanced Text Editorxx Name[zh_CN]=嵌入式高级文本编辑器 Name[zh_TW]=嵌入式進階文字編輯器 X-KDE-Library=kf5/parts/katepart Icon=accessories-text-editor X-KDE-ServiceTypes=KParts/ReadOnlyPart,KParts/ReadWritePart,KTextEditor/Document Type=Service InitialPreference=8 MimeType=text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-patch;text/x-adasrc;text/x-chdr;text/x-csrc;text/css;application/x-desktop;text/x-patch;text/x-fortran;text/html;text/x-java;text/x-tex;text/x-makefile;text/x-objcsrc;text/x-pascal;application/x-perl;application/x-perl;application/x-php;text/vnd.wap.wml;text/x-python;application/x-ruby;text/sgml;application/xml;model/vrml; diff --git a/src/printing/kateprinter.cpp b/src/printing/kateprinter.cpp index e78f29f9..9147afe4 100644 --- a/src/printing/kateprinter.cpp +++ b/src/printing/kateprinter.cpp @@ -1,184 +1,184 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2002-2010 Anders Lund * * Rewritten based on code of Copyright (c) 2002 Michael Goffioul * * 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 "kateprinter.h" #include "kateconfig.h" #include "katedocument.h" #include "kateview.h" #include #include #include -#include -#include -#include +#include +#include +#include #include "printconfigwidgets.h" #include "printpainter.h" using namespace KatePrinter; //BEGIN KatePrinterPrivate class KatePrinterPrivate : public QObject { Q_OBJECT public: KatePrinterPrivate(KTextEditor::DocumentPrivate *doc, KTextEditor::ViewPrivate *view = nullptr); ~KatePrinterPrivate(); bool print(QPrinter *printer); public Q_SLOTS: void paint(QPrinter *printer); private: KTextEditor::ViewPrivate *m_view; KTextEditor::DocumentPrivate *m_doc; PrintPainter *m_painter; }; KatePrinterPrivate::KatePrinterPrivate(KTextEditor::DocumentPrivate *doc, KTextEditor::ViewPrivate *view) : QObject() , m_view(view) , m_doc(doc) , m_painter(new PrintPainter(m_doc, m_view)) { } KatePrinterPrivate::~KatePrinterPrivate() { delete m_painter; } bool KatePrinterPrivate::print(QPrinter *printer) { // docname is now always there, including the right Untitled name printer->setDocName(m_doc->documentName()); KatePrintTextSettings *kpts = new KatePrintTextSettings; KatePrintHeaderFooter *kphf = new KatePrintHeaderFooter; KatePrintLayout *kpl = new KatePrintLayout; QList tabs; tabs << kpts; tabs << kphf; tabs << kpl; QWidget *parentWidget = m_doc->widget(); if (!parentWidget) { parentWidget = QApplication::activeWindow(); } QPointer printDialog(new QPrintDialog(printer, parentWidget)); printDialog->setOptionTabs(tabs); if (m_view && m_view->selection()) { printer->setPrintRange(QPrinter::Selection); printDialog->setOption(QAbstractPrintDialog::PrintSelection, true); } printDialog->setOption(QAbstractPrintDialog::PrintPageRange, true); const int dlgCode = printDialog->exec(); if (dlgCode != QDialog::Accepted || !printDialog) { delete printDialog; return false; } // configure the painter m_painter->setPrintGuide(kpts->printGuide()); m_painter->setPrintLineNumbers(kpts->printLineNumbers()); m_painter->setColorScheme(kpl->colorScheme()); m_painter->setUseBackground(kpl->useBackground()); m_painter->setUseBox(kpl->useBox()); m_painter->setBoxMargin(kpl->boxMargin()); m_painter->setBoxWidth(kpl->boxWidth()); m_painter->setBoxColor(kpl->boxColor()); m_painter->setHeadersFont(kphf->font()); m_painter->setUseHeader(kphf->useHeader()); m_painter->setHeaderBackground(kphf->headerBackground()); m_painter->setHeaderForeground(kphf->headerForeground()); m_painter->setUseHeaderBackground(kphf->useHeaderBackground()); m_painter->setHeaderFormat(kphf->headerFormat()); m_painter->setUseFooter(kphf->useFooter()); m_painter->setFooterBackground(kphf->footerBackground()); m_painter->setFooterForeground(kphf->footerForeground()); m_painter->setUseFooterBackground(kphf->useFooterBackground()); m_painter->setFooterFormat(kphf->footerFormat()); m_painter->paint(printer); delete printDialog; return true; } void KatePrinterPrivate::paint(QPrinter *printer) { m_painter->paint(printer); } //END KatePrinterPrivate //BEGIN KatePrinter bool KatePrinter::print(KTextEditor::ViewPrivate *view) { QPrinter printer; KatePrinterPrivate p(view->doc(), view); return p.print(&printer); } bool KatePrinter::printPreview(KTextEditor::ViewPrivate *view) { QPrinter printer; KatePrinterPrivate p(view->doc(), view); QPrintPreviewDialog preview(&printer); QObject::connect(&preview, SIGNAL(paintRequested(QPrinter*)), &p, SLOT(paint(QPrinter*))); return preview.exec(); } bool KatePrinter::print(KTextEditor::DocumentPrivate *doc) { QPrinter printer; KatePrinterPrivate p(doc); return p.print(&printer); } bool KatePrinter::printPreview(KTextEditor::DocumentPrivate *doc) { QPrinter printer; KatePrinterPrivate p(doc); QPrintPreviewDialog preview(&printer); QObject::connect(&preview, SIGNAL(paintRequested(QPrinter*)), &p, SLOT(paint(QPrinter*))); return preview.exec(); } //END KatePrinter #include "kateprinter.moc" diff --git a/src/printing/printpainter.cpp b/src/printing/printpainter.cpp index 34dbdaa7..225d0255 100644 --- a/src/printing/printpainter.cpp +++ b/src/printing/printpainter.cpp @@ -1,722 +1,710 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2002-2010 Anders Lund * * Rewritten based on code of Copyright (c) 2002 Michael Goffioul * * 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 "printpainter.h" #include "katetextfolding.h" #include "katedocument.h" #include "katebuffer.h" #include "kateview.h" #include "katehighlight.h" #include "katepartdebug.h" #include "katetextlayout.h" #include #include #include #include using namespace KatePrinter; class KatePrinter::PageLayout { public: PageLayout() - : pageWidth(0) - , pageHeight(0) - , headerWidth(0) - , maxWidth(0) - , maxHeight(0) - , xstart(0) - , innerMargin(0) - , selectionOnly(false) - , firstline(0) - , lastline(0) - , headerHeight(0) - , headerTagList() - , footerHeight(0) + : headerTagList() , footerTagList() , selectionRange() {} - uint pageWidth; - uint pageHeight; - uint headerWidth; - uint maxWidth; - uint maxHeight; - int xstart; // beginning point for painting lines - int innerMargin; + uint pageWidth = 0; + uint pageHeight = 0; + uint headerWidth = 0; + uint maxWidth = 0; + uint maxHeight = 0; + int xstart = 0; // beginning point for painting lines + int innerMargin = 0; - bool selectionOnly; + bool selectionOnly = false; - uint firstline; - uint lastline; + uint firstline = 0; + uint lastline = 0; // Header/Footer Page - uint headerHeight; + uint headerHeight = 0; QStringList headerTagList; - uint footerHeight; + uint footerHeight = 0; QStringList footerTagList; KTextEditor::Range selectionRange; }; PrintPainter::PrintPainter(KTextEditor::DocumentPrivate *doc, KTextEditor::ViewPrivate *view) : m_view(view) , m_doc(doc) , m_printGuide(false) , m_printLineNumbers(false) , m_useHeader(false) , m_useFooter(false) , m_useBackground(false) , m_useBox(false) , m_useHeaderBackground(false) , m_useFooterBackground(false) , m_boxMargin(0) , m_boxWidth(1) , m_boxColor(Qt::black) , m_headerBackground(Qt::lightGray) , m_headerForeground(Qt::black) , m_footerBackground(Qt::lightGray) , m_footerForeground(Qt::black) , m_fhFont() , m_headerFormat() , m_footerFormat() { m_folding = new Kate::TextFolding(m_doc->buffer()); m_renderer = new KateRenderer(m_doc, *m_folding, m_view); m_renderer->setPrinterFriendly(true); updateCache(); } PrintPainter::~PrintPainter() { delete m_renderer; delete m_folding; } void PrintPainter::setUseBox(const bool on) { m_useBox = on; setBoxWidth(m_boxWidth); // reset the width } void PrintPainter::setBoxWidth(const int width) { if (m_useBox) { m_boxWidth = width; if (width < 1) { m_boxWidth = 1; } } else { m_boxWidth = 0; } } void PrintPainter::setBoxColor(const QColor &color) { if (color.isValid()) { m_boxColor = color; } } void PrintPainter::setHeaderBackground(const QColor &color) { if (color.isValid()) { m_headerBackground = color; } } void PrintPainter::setHeaderForeground(const QColor &color) { if (color.isValid()) { m_headerForeground = color; } } void PrintPainter::setFooterBackground(const QColor &color) { if (color.isValid()) { m_footerBackground = color; } } void PrintPainter::setFooterForeground(const QColor &color) { if (color.isValid()) { m_footerForeground = color; } } void PrintPainter::setColorScheme(const QString &scheme) { // directly set that for the renderer m_renderer->config()->setSchema(scheme); // changed renderer requires cache updates updateCache(); } void PrintPainter::updateCache() { m_fontHeight = m_renderer->fontHeight(); // figure out the horiizontal space required QString s = QStringLiteral("%1 ").arg(m_doc->lines()); s.fill(QLatin1Char('5'), -1); // some non-fixed fonts haven't equally wide numbers // FIXME calculate which is actually the widest... m_lineNumberWidth = m_renderer->currentFontMetrics().width(s); } void PrintPainter::paint(QPrinter *printer) const { QPainter painter(printer); PageLayout pl; configure(printer, pl); uint lineCount = pl.firstline; uint y = 0; uint currentPage = (printer->fromPage() == 0) ? 1 : printer->fromPage(); bool pageStarted = true; // On to draw something :-) while (lineCount <= pl.lastline) { if (y + m_fontHeight > pl.maxHeight) { if ((int)currentPage == printer->toPage()) { // we've reached the page break of last page to be printed break; } printer->newPage(); painter.resetTransform(); currentPage++; pageStarted = true; y = 0; } if (pageStarted) { qCDebug(LOG_KTE) << "Starting new page," << lineCount << "lines up to now."; paintNewPage(painter, currentPage, y, pl); pageStarted = false; painter.translate(pl.xstart, y); } if (m_printLineNumbers /*&& ! startCol*/) { // don't repeat! paintLineNumber(painter, lineCount, pl); } uint remainder = 0; paintLine(painter, lineCount, y, remainder, pl); if (!remainder) { lineCount++; } } painter.end(); } void PrintPainter::configure(const QPrinter *printer, PageLayout &pl) const { pl.pageHeight = printer->height(); pl.pageWidth = printer->width(); pl.headerWidth = printer->width(); pl.innerMargin = m_useBox ? m_boxMargin : 6; pl.maxWidth = printer->width(); pl.maxHeight = (m_useBox ? printer->height() - pl.innerMargin : printer->height()); pl.selectionOnly = (printer->printRange() == QPrinter::Selection); pl.lastline = m_doc->lastLine(); if (m_view && pl.selectionOnly) { // set a line range from the first selected line to the last pl.selectionRange = m_view->selectionRange(); pl.firstline = pl.selectionRange.start().line(); pl.lastline = pl.selectionRange.end().line(); } if (m_printLineNumbers) { // a small space between the line numbers and the text int _adj = m_renderer->currentFontMetrics().width(QStringLiteral("5")); // adjust available width and set horizontal start point for data pl.maxWidth -= m_lineNumberWidth + _adj; pl.xstart += m_lineNumberWidth + _adj; } if (m_useHeader || m_useFooter) { // Set up a tag map // This retrieves all tags, ued or not, but // none of theese operations should be expensive, // and searcing each tag in the format strings is avoided. QDateTime dt = QDateTime::currentDateTime(); QMap tags; KUser u(KUser::UseRealUserID); tags[QStringLiteral("u")] = u.loginName(); tags[QStringLiteral("d")] = QLocale().toString(dt, QLocale::ShortFormat); tags[QStringLiteral("D")] = QLocale().toString(dt, QLocale::LongFormat); tags[QStringLiteral("h")] = QLocale().toString(dt.time(), QLocale::ShortFormat); tags[QStringLiteral("y")] = QLocale().toString(dt.date(), QLocale::ShortFormat); tags[QStringLiteral("Y")] = QLocale().toString(dt.date(), QLocale::LongFormat); tags[QStringLiteral("f")] = m_doc->url().fileName(); tags[QStringLiteral("U")] = m_doc->url().toString(); if (pl.selectionOnly) { QString s(i18n("(Selection of) ")); tags[QStringLiteral("f")].prepend(s); tags[QStringLiteral("U")].prepend(s); } QRegularExpression reTags(QStringLiteral("%([dDfUhuyY])")); // TODO check for "%%" if (m_useHeader) { pl.headerHeight = QFontMetrics(m_fhFont).height(); if (m_useBox || m_useHeaderBackground) { pl.headerHeight += pl.innerMargin * 2; } else { pl.headerHeight += 1 + QFontMetrics(m_fhFont).leading(); } pl.headerTagList = m_headerFormat; QMutableStringListIterator it(pl.headerTagList); while (it.hasNext()) { QString tag = it.next(); QRegularExpressionMatch match; int pos = tag.indexOf(reTags, 0, &match); QString rep; while (pos > -1) { rep = tags[match.captured(1)]; tag.replace((uint)pos, 2, rep); pos += rep.length(); pos = tag.indexOf(reTags, pos, &match); } it.setValue(tag); } } if (m_useFooter) { pl.footerHeight = QFontMetrics(m_fhFont).height(); if (m_useBox || m_useFooterBackground) { pl.footerHeight += 2 * pl.innerMargin; } else { pl.footerHeight += 1; // line only } pl.footerTagList = m_footerFormat; QMutableStringListIterator it(pl.footerTagList); while (it.hasNext()) { QString tag = it.next(); QRegularExpressionMatch match; int pos = tag.indexOf(reTags, 0, &match); QString rep; while (pos > -1) { rep = tags[match.captured(1)]; tag.replace((uint)pos, 2, rep); pos += rep.length(); pos = tag.indexOf(reTags, pos, &match); } it.setValue(tag); } // adjust maxheight, so we can know when/where to print footer pl.maxHeight -= pl.footerHeight; } } // if ( useHeader || useFooter ) if (m_useBackground) { if (!m_useBox) { pl.xstart += pl.innerMargin; pl.maxWidth -= pl.innerMargin * 2; } } if (m_useBox) { // set maxwidth to something sensible pl.maxWidth -= (m_boxWidth + pl.innerMargin) * 2; pl.xstart += m_boxWidth + pl.innerMargin; // maxheight too.. pl.maxHeight -= m_boxWidth; } int pageHeight = pl.maxHeight; if (m_useHeader) { pageHeight -= pl.headerHeight + pl.innerMargin; } if (m_useFooter) { pageHeight -= pl.innerMargin; } const int linesPerPage = pageHeight / m_fontHeight; if (printer->fromPage() > 0) { pl.firstline = (printer->fromPage() - 1) * linesPerPage; } // now that we know the vertical amount of space needed, // it is possible to calculate the total number of pages // if needed, that is if any header/footer tag contains "%P". if (!pl.headerTagList.filter(QStringLiteral("%P")).isEmpty() || !pl.footerTagList.filter(QStringLiteral("%P")).isEmpty()) { qCDebug(LOG_KTE) << "'%P' found! calculating number of pages..."; // calculate total layouted lines in the document int totalLines = 0; // TODO: right now ignores selection printing for (unsigned int i = pl.firstline; i <= pl.lastline; ++i) { KateLineLayoutPtr rangeptr(new KateLineLayout(*m_renderer)); rangeptr->setLine(i); m_renderer->layoutLine(rangeptr, (int)pl.maxWidth, false); totalLines += rangeptr->viewLineCount(); } const int totalPages = (totalLines / linesPerPage) + ((totalLines % linesPerPage) > 0 ? 1 : 0); // TODO: add space for guide if required // if ( useGuide ) // _lt += (guideHeight + (fontHeight /2)) / fontHeight; // substitute both tag lists QString re(QStringLiteral("%P")); QStringList::Iterator it; for (it = pl.headerTagList.begin(); it != pl.headerTagList.end(); ++it) { it->replace(re, QString::number(totalPages)); } for (it = pl.footerTagList.begin(); it != pl.footerTagList.end(); ++it) { (*it).replace(re, QString::number(totalPages)); } } } void PrintPainter::paintNewPage(QPainter &painter, const uint currentPage, uint &y, const PageLayout &pl) const { if (m_useHeader) { paintHeader(painter, currentPage, y, pl); } if (m_useFooter) { paintFooter(painter, currentPage, pl); } if (m_useBackground) { paintBackground(painter, y, pl); } if (m_useBox) { paintBox(painter, y, pl); } if (m_printGuide && currentPage == 1) { paintGuide(painter, y, pl); } } void PrintPainter::paintHeader(QPainter &painter, const uint currentPage, uint &y, const PageLayout &pl) const { painter.save(); painter.setPen(m_headerForeground); painter.setFont(m_fhFont); if (m_useHeaderBackground) { painter.fillRect(0, 0, pl.headerWidth, pl.headerHeight, m_headerBackground); } if (pl.headerTagList.count() == 3) { int valign = (m_useBox || m_useHeaderBackground || m_useBackground) ? Qt::AlignVCenter : Qt::AlignTop; int align = valign | Qt::AlignLeft; int marg = (m_useBox || m_useHeaderBackground) ? pl.innerMargin : 0; if (m_useBox) { marg += m_boxWidth; } QString s; for (int i = 0; i < 3; i++) { s = pl.headerTagList[i]; if (s.indexOf(QLatin1String("%p")) != -1) { s.replace(QLatin1String("%p"), QString::number(currentPage)); } painter.drawText(marg, 0, pl.headerWidth - (marg * 2), pl.headerHeight, align, s); align = valign | (i == 0 ? Qt::AlignHCenter : Qt::AlignRight); } } if (!(m_useHeaderBackground || m_useBox || m_useBackground)) { // draw a 1 px (!?) line to separate header from contents painter.drawLine(0, pl.headerHeight - 1, pl.headerWidth, pl.headerHeight - 1); //y += 1; now included in headerHeight } painter.restore(); y += pl.headerHeight + pl.innerMargin; } void PrintPainter::paintFooter(QPainter &painter, const uint currentPage, const PageLayout &pl) const { painter.save(); painter.setPen(m_footerForeground); if (!(m_useFooterBackground || m_useBox || m_useBackground)) {// draw a 1 px (!?) line to separate footer from contents painter.drawLine(0, pl.maxHeight + pl.innerMargin - 1, pl.headerWidth, pl.maxHeight + pl.innerMargin - 1); } if (m_useFooterBackground) { painter.fillRect(0, pl.maxHeight + pl.innerMargin + m_boxWidth, pl.headerWidth, pl.footerHeight, m_footerBackground); } if (pl.footerTagList.count() == 3) { int align = Qt::AlignVCenter | Qt::AlignLeft; int marg = (m_useBox || m_useFooterBackground) ? pl.innerMargin : 0; if (m_useBox) { marg += m_boxWidth; } QString s; for (int i = 0; i < 3; i++) { s = pl.footerTagList[i]; if (s.indexOf(QLatin1String("%p")) != -1) { s.replace(QLatin1String("%p"), QString::number(currentPage)); } painter.drawText(marg, pl.maxHeight + pl.innerMargin, pl.headerWidth - (marg * 2), pl.footerHeight, align, s); align = Qt::AlignVCenter | (i == 0 ? Qt::AlignHCenter : Qt::AlignRight); } } painter.restore(); } void PrintPainter::paintGuide(QPainter &painter, uint &y, const PageLayout &pl) const { // FIXME - this may span more pages... // draw a box unless we have boxes, in which case we end with a box line int _ystart = y; QString _hlName = m_doc->highlight()->name(); QList _attributes; // list of highlight attributes for the legend m_doc->highlight()->getKateExtendedAttributeList(m_renderer->config()->schema(), _attributes); KateAttributeList _defaultAttributes; KateHlManager::self()->getDefaults(m_renderer->config()->schema(), _defaultAttributes); QColor _defaultPen = _defaultAttributes.at(0)->foreground().color(); painter.save(); painter.setPen(_defaultPen); int _marg = 0; if (m_useBox) { _marg += (2 * m_boxWidth) + (2 * pl.innerMargin); } else { if (m_useBackground) { _marg += 2 * pl.innerMargin; } _marg += 1; y += 1 + pl.innerMargin; } // draw a title string QFont _titleFont = m_renderer->config()->font(); _titleFont.setBold(true); painter.setFont(_titleFont); QRect _r; painter.drawText(QRect(_marg, y, pl.pageWidth - (2 * _marg), pl.maxHeight - y), Qt::AlignTop | Qt::AlignHCenter, i18n("Typographical Conventions for %1", _hlName), &_r); const int _w = pl.pageWidth - (_marg * 2) - (pl.innerMargin * 2); const int _x = _marg + pl.innerMargin; y += _r.height() + pl.innerMargin; painter.drawLine(_x, y, _x + _w, y); y += 1 + pl.innerMargin; int _widest(0); foreach (const KTextEditor::Attribute::Ptr &attribute, _attributes) { _widest = qMax(QFontMetrics(attribute->font()).width(attribute->name().section(QLatin1Char(':'), 1, 1)), _widest); } const int _guideCols = _w / (_widest + pl.innerMargin); // draw attrib names using their styles const int _cw = _w / _guideCols; int _i = 0; _titleFont.setUnderline(true); QString _currentHlName; foreach (const KTextEditor::Attribute::Ptr &attribute, _attributes) { QString _hl = attribute->name().section(QLatin1Char(':'), 0, 0); QString _name = attribute->name().section(QLatin1Char(':'), 1, 1); if (_hl != _hlName && _hl != _currentHlName) { _currentHlName = _hl; if (_i % _guideCols) { y += m_fontHeight; } y += pl.innerMargin; painter.setFont(_titleFont); painter.setPen(_defaultPen); painter.drawText(_x, y, _w, m_fontHeight, Qt::AlignTop, _hl + QLatin1Char(' ') + i18n("text")); y += m_fontHeight; _i = 0; } KTextEditor::Attribute _attr = *_defaultAttributes[attribute->defaultStyle()]; _attr += *attribute; painter.setPen(_attr.foreground().color()); painter.setFont(_attr.font()); if (_attr.hasProperty(QTextFormat::BackgroundBrush)) { QRect _rect = QFontMetrics(_attr.font()).boundingRect(_name); _rect.moveTo(_x + ((_i % _guideCols)*_cw), y); painter.fillRect(_rect, _attr.background()); } painter.drawText((_x + ((_i % _guideCols) * _cw)), y, _cw, m_fontHeight, Qt::AlignTop, _name); _i++; if (_i && !(_i % _guideCols)) { y += m_fontHeight; } } if (_i % _guideCols) { y += m_fontHeight;// last row not full } // draw a box around the legend painter.setPen(_defaultPen); if (m_useBox) { painter.fillRect(0, y + pl.innerMargin, pl.headerWidth, m_boxWidth, m_boxColor); } else { _marg -= 1; painter.drawRect(_marg, _ystart, pl.pageWidth - (2 * _marg), y - _ystart + pl.innerMargin); } painter.restore(); y += (m_useBox ? m_boxWidth : 1) + (pl.innerMargin * 2); } void PrintPainter::paintBox(QPainter &painter, uint &y, const PageLayout &pl) const { painter.save(); painter.setPen(QPen(m_boxColor, m_boxWidth)); painter.drawRect(0, 0, pl.pageWidth, pl.pageHeight); if (m_useHeader) { painter.drawLine(0, pl.headerHeight, pl.headerWidth, pl.headerHeight); } else { y += pl.innerMargin; } if (m_useFooter) { // drawline is not trustable, grr. painter.fillRect(0, pl.maxHeight + pl.innerMargin, pl.headerWidth, m_boxWidth, m_boxColor); } painter.restore(); } void PrintPainter::paintBackground(QPainter &painter, const uint y, const PageLayout &pl) const { // If we have a box, or the header/footer has backgrounds, we want to paint // to the border of those. Otherwise just the contents area. int _y = y, _h = pl.maxHeight - y; if (m_useBox) { _y -= pl.innerMargin; _h += 2 * pl.innerMargin; } else { if (m_useHeaderBackground) { _y -= pl.innerMargin; _h += pl.innerMargin; } if (m_useFooterBackground) { _h += pl.innerMargin; } } painter.fillRect(0, _y, pl.pageWidth, _h, m_renderer->config()->backgroundColor()); } void PrintPainter::paintLine(QPainter &painter, const uint line, uint &y, uint &remainder, const PageLayout &pl) const { // HA! this is where we print [part of] a line ;]] KateLineLayoutPtr rangeptr(new KateLineLayout(*m_renderer)); rangeptr->setLine(line); m_renderer->layoutLine(rangeptr, (int)pl.maxWidth, false); // selectionOnly: clip non-selection parts and adjust painter position if needed int _xadjust = 0; if (pl.selectionOnly) { if (m_view && m_view->blockSelection()) { int _x = m_renderer->cursorToX(rangeptr->viewLine(0), pl.selectionRange.start()); int _x1 = m_renderer->cursorToX(rangeptr->viewLine(rangeptr->viewLineCount() - 1), pl.selectionRange.end()); _xadjust = _x; painter.translate(-_xadjust, 0); painter.setClipRegion(QRegion(_x, 0, _x1 - _x, rangeptr->viewLineCount() * m_fontHeight)); } else if (line == pl.firstline || line == pl.lastline) { QRegion region(0, 0, pl.maxWidth, rangeptr->viewLineCount() * m_fontHeight); if (line == pl.firstline) { region = region.subtracted(QRegion(0, 0, m_renderer->cursorToX(rangeptr->viewLine(0), pl.selectionRange.start()), m_fontHeight)); } if (line == pl.lastline) { int _x = m_renderer->cursorToX(rangeptr->viewLine(rangeptr->viewLineCount() - 1), pl.selectionRange.end()); region = region.subtracted(QRegion(_x, 0, pl.maxWidth - _x, m_fontHeight)); } painter.setClipRegion(region); } } // If the line is too long (too many 'viewlines') to fit the remaining vertical space, // clip and adjust the painter position as necessary int _lines = rangeptr->viewLineCount(); // number of "sublines" to paint. int proceedLines = _lines; if (remainder) { proceedLines = qMin((pl.maxHeight - y) / m_fontHeight, remainder); painter.translate(0, -(_lines - int(remainder)) * m_fontHeight + 1); painter.setClipRect(0, (_lines - int(remainder)) * m_fontHeight + 1, pl.maxWidth, proceedLines * m_fontHeight); //### drop the crosspatch in printerfriendly mode??? remainder -= proceedLines; } else if (y + m_fontHeight * _lines > pl.maxHeight) { remainder = _lines - ((pl.maxHeight - y) / m_fontHeight); painter.setClipRect(0, 0, pl.maxWidth, (_lines - int(remainder)) * m_fontHeight + 1); //### drop the crosspatch in printerfriendly mode??? } m_renderer->paintTextLine(painter, rangeptr, 0, (int)pl.maxWidth); painter.setClipping(false); painter.translate(_xadjust, (m_fontHeight * (_lines - remainder))); y += m_fontHeight * proceedLines; } void PrintPainter::paintLineNumber(QPainter &painter, const uint number, const PageLayout &pl) const { const int left = ((m_useBox || m_useBackground) ? pl.innerMargin : 0) - pl.xstart; painter.save(); painter.setFont(m_renderer->config()->font()); painter.setPen(m_renderer->config()->lineNumberColor()); painter.drawText(left, 0, m_lineNumberWidth, m_fontHeight, Qt::AlignRight, QString::number(number + 1)); painter.restore(); } //END PrintPainter diff --git a/src/render/katerenderer.cpp b/src/render/katerenderer.cpp index c7ae2d47..d1158518 100644 --- a/src/render/katerenderer.cpp +++ b/src/render/katerenderer.cpp @@ -1,1135 +1,1138 @@ /* This file is part of the KDE libraries Copyright (C) 2007 Mirko Stocker Copyright (C) 2003-2005 Hamish Rodda Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 1999 Jochen Wilhelmy 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 02110-1301, USA. */ #include "katerenderer.h" #include "katedocument.h" #include "kateconfig.h" #include "katehighlight.h" #include "kateview.h" #include "katerenderrange.h" #include "katetextlayout.h" #include "katebuffer.h" #include "katepartdebug.h" #include #include #include #include #include #include // qCeil static const QChar tabChar(QLatin1Char('\t')); static const QChar spaceChar(QLatin1Char(' ')); static const QChar nbSpaceChar(0xa0); // non-breaking space KateRenderer::KateRenderer(KTextEditor::DocumentPrivate *doc, Kate::TextFolding &folding, KTextEditor::ViewPrivate *view) : m_doc(doc) , m_folding(folding) , m_view(view) , m_tabWidth(m_doc->config()->tabWidth()) , m_indentWidth(m_doc->config()->indentationWidth()) , m_caretStyle(KateRenderer::Line) , m_drawCaret(true) , m_showSelections(true) , m_showTabs(true) , m_showSpaces(true) , m_showNonPrintableSpaces(false) , m_printerFriendly(false) , m_config(new KateRendererConfig(this)) { updateAttributes(); // initialize with a sane font height updateFontHeight(); // make the proper calculation for markerSize updateMarkerSize(); } KateRenderer::~KateRenderer() { delete m_config; } void KateRenderer::updateAttributes() { m_attributes = m_doc->highlight()->attributes(config()->schema()); } KTextEditor::Attribute::Ptr KateRenderer::attribute(uint pos) const { if (pos < (uint)m_attributes.count()) { return m_attributes[pos]; } return m_attributes[0]; } KTextEditor::Attribute::Ptr KateRenderer::specificAttribute(int context) const { if (context >= 0 && context < m_attributes.count()) { return m_attributes[context]; } return m_attributes[0]; } void KateRenderer::setDrawCaret(bool drawCaret) { m_drawCaret = drawCaret; } void KateRenderer::setCaretStyle(KateRenderer::caretStyles style) { m_caretStyle = style; } void KateRenderer::setShowTabs(bool showTabs) { m_showTabs = showTabs; } void KateRenderer::setShowTrailingSpaces(bool showSpaces) { m_showSpaces = showSpaces; } void KateRenderer::setShowNonPrintableSpaces(const bool on) { m_showNonPrintableSpaces = on; } void KateRenderer::setTabWidth(int tabWidth) { m_tabWidth = tabWidth; } bool KateRenderer::showIndentLines() const { return m_config->showIndentationLines(); } void KateRenderer::setShowIndentLines(bool showIndentLines) { m_config->setShowIndentationLines(showIndentLines); } void KateRenderer::setIndentWidth(int indentWidth) { m_indentWidth = indentWidth; } void KateRenderer::setShowSelections(bool showSelections) { m_showSelections = showSelections; } void KateRenderer::increaseFontSizes(qreal step) { QFont f(config()->font()); f.setPointSizeF(f.pointSizeF() + step); config()->setFont(f); } void KateRenderer::decreaseFontSizes(qreal step) { QFont f(config()->font()); if ((f.pointSizeF() - step) > 0) { f.setPointSizeF(f.pointSizeF() - step); } config()->setFont(f); } bool KateRenderer::isPrinterFriendly() const { return m_printerFriendly; } void KateRenderer::setPrinterFriendly(bool printerFriendly) { m_printerFriendly = printerFriendly; setShowTabs(false); setShowTrailingSpaces(false); setShowSelections(false); setDrawCaret(false); } void KateRenderer::paintTextLineBackground(QPainter &paint, KateLineLayoutPtr layout, int currentViewLine, int xStart, int xEnd) { if (isPrinterFriendly()) { return; } // Normal background color QColor backgroundColor(config()->backgroundColor()); // paint the current line background if we're on the current line QColor currentLineColor = config()->highlightedLineColor(); // Check for mark background int markRed = 0, markGreen = 0, markBlue = 0, markCount = 0; // Retrieve marks for this line uint mrk = m_doc->mark(layout->line()); if (mrk) { for (uint bit = 0; bit < 32; bit++) { KTextEditor::MarkInterface::MarkTypes markType = (KTextEditor::MarkInterface::MarkTypes)(1 << bit); if (mrk & markType) { QColor markColor = config()->lineMarkerColor(markType); if (markColor.isValid()) { markCount++; markRed += markColor.red(); markGreen += markColor.green(); markBlue += markColor.blue(); } } } // for } // Marks if (markCount) { markRed /= markCount; markGreen /= markCount; markBlue /= markCount; backgroundColor.setRgb( int((backgroundColor.red() * 0.9) + (markRed * 0.1)), int((backgroundColor.green() * 0.9) + (markGreen * 0.1)), int((backgroundColor.blue() * 0.9) + (markBlue * 0.1)) ); } // Draw line background paint.fillRect(0, 0, xEnd - xStart, lineHeight() * layout->viewLineCount(), backgroundColor); // paint the current line background if we're on the current line if (currentViewLine != -1) { if (markCount) { markRed /= markCount; markGreen /= markCount; markBlue /= markCount; currentLineColor.setRgb( int((currentLineColor.red() * 0.9) + (markRed * 0.1)), int((currentLineColor.green() * 0.9) + (markGreen * 0.1)), int((currentLineColor.blue() * 0.9) + (markBlue * 0.1)) ); } paint.fillRect(0, lineHeight() * currentViewLine, xEnd - xStart, lineHeight(), currentLineColor); } } void KateRenderer::paintTabstop(QPainter &paint, qreal x, qreal y) { QPen penBackup(paint.pen()); QPen pen(config()->tabMarkerColor()); pen.setWidthF(qMax(1.0, spaceWidth() / 10.0)); paint.setPen(pen); paint.setRenderHint(QPainter::Antialiasing, false); int dist = spaceWidth() * 0.3; QPoint points[8]; points[0] = QPoint(x - dist, y - dist); points[1] = QPoint(x, y); points[2] = QPoint(x, y); points[3] = QPoint(x - dist, y + dist); x += spaceWidth() / 3.0; points[4] = QPoint(x - dist, y - dist); points[5] = QPoint(x, y); points[6] = QPoint(x, y); points[7] = QPoint(x - dist, y + dist); paint.drawLines(points, 4); paint.setPen(penBackup); } void KateRenderer::paintTrailingSpace(QPainter &paint, qreal x, qreal y) { QPen penBackup(paint.pen()); QPen pen(config()->tabMarkerColor()); pen.setWidthF(m_markerSize); pen.setCapStyle(Qt::RoundCap); paint.setPen(pen); paint.setRenderHint(QPainter::Antialiasing, true); paint.drawPoint(QPointF(x, y)); paint.setPen(penBackup); } void KateRenderer::paintNonBreakSpace(QPainter &paint, qreal x, qreal y) { QPen penBackup(paint.pen()); QPen pen(config()->tabMarkerColor()); pen.setWidthF(qMax(1.0, spaceWidth() / 10.0)); paint.setPen(pen); paint.setRenderHint(QPainter::Antialiasing, false); const int height = fontHeight(); const int width = spaceWidth(); QPoint points[6]; points[0] = QPoint(x + width / 10, y + height / 4); points[1] = QPoint(x + width / 10, y + height / 3); points[2] = QPoint(x + width / 10, y + height / 3); points[3] = QPoint(x + width - width / 10, y + height / 3); points[4] = QPoint(x + width - width / 10, y + height / 3); points[5] = QPoint(x + width - width / 10, y + height / 4); paint.drawLines(points, 3); paint.setPen(penBackup); } void KateRenderer::paintNonPrintableSpaces(QPainter &paint, qreal x, qreal y, const QChar &chr) { paint.save(); QPen pen(config()->spellingMistakeLineColor()); pen.setWidthF(qMax(1.0, spaceWidth() * 0.1)); paint.setPen(pen); paint.setRenderHint(QPainter::Antialiasing, false); const int height = fontHeight(); const int width = config()->fontMetrics().width(chr); const int offset = spaceWidth() * 0.1; QPoint points[8]; points[0] = QPoint(x - offset, y + offset); points[1] = QPoint(x + width + offset, y + offset); points[2] = QPoint(x + width + offset, y + offset); points[3] = QPoint(x + width + offset, y - height - offset); points[4] = QPoint(x + width + offset, y - height - offset); points[5] = QPoint(x - offset, y - height - offset); points[6] = QPoint(x - offset, y - height - offset); points[7] = QPoint(x - offset, y + offset); paint.drawLines(points, 4); paint.restore(); } void KateRenderer::paintIndentMarker(QPainter &paint, uint x, uint y /*row*/) { QPen penBackup(paint.pen()); QPen myPen(config()->indentationLineColor()); static const QVector dashPattern = QVector() << 1 << 1; myPen.setDashPattern(dashPattern); if (y % 2) { myPen.setDashOffset(1); } paint.setPen(myPen); const int height = fontHeight(); const int top = 0; const int bottom = height - 1; QPainter::RenderHints renderHints = paint.renderHints(); paint.setRenderHints(renderHints, false); paint.drawLine(x + 2, top, x + 2, bottom); paint.setRenderHints(renderHints, true); paint.setPen(penBackup); } static bool rangeLessThanForRenderer(const Kate::TextRange *a, const Kate::TextRange *b) { // compare Z-Depth first // smaller Z-Depths should win! if (a->zDepth() > b->zDepth()) { return true; } else if (a->zDepth() < b->zDepth()) { return false; } // end of a > end of b? if (a->end().toCursor() > b->end().toCursor()) { return true; } // if ends are equal, start of a < start of b? if (a->end().toCursor() == b->end().toCursor()) { return a->start().toCursor() < b->start().toCursor(); } return false; } QList KateRenderer::decorationsForLine(const Kate::TextLine &textLine, int line, bool selectionsOnly, KateRenderRange *completionHighlight, bool completionSelected) const { QList newHighlight; // qDebug() << "paint line:" << line << "selection ranges:" << m_view->selections()->selections(); // Don't compute the highlighting if there isn't going to be any highlighting QList rangesWithAttributes = m_doc->buffer().rangesForLine(line, m_printerFriendly ? nullptr : m_view, true); if (selectionsOnly || !textLine->attributesList().isEmpty() || !rangesWithAttributes.isEmpty()) { RenderRangeList renderRanges; // Add the inbuilt highlighting to the list NormalRenderRange *inbuiltHighlight = new NormalRenderRange(); const QVector &al = textLine->attributesList(); for (int i = 0; i < al.count(); ++i) if (al[i].length > 0 && al[i].attributeValue > 0) { inbuiltHighlight->addRange(new KTextEditor::Range(KTextEditor::Cursor(line, al[i].offset), al[i].length), specificAttribute(al[i].attributeValue)); } renderRanges.append(inbuiltHighlight); if (!completionHighlight) { // check for dynamic hl stuff const QSet *rangesMouseIn = m_view ? m_view->rangesMouseIn() : nullptr; const QSet *rangesCaretIn = m_view ? m_view->rangesCaretIn() : nullptr; bool anyDynamicHlsActive = m_view && (!rangesMouseIn->empty() || !rangesCaretIn->empty()); // sort all ranges, we want that the most specific ranges win during rendering, multiple equal ranges are kind of random, still better than old smart rangs behavior ;) qSort(rangesWithAttributes.begin(), rangesWithAttributes.end(), rangeLessThanForRenderer); // loop over all ranges for (int i = 0; i < rangesWithAttributes.size(); ++i) { // real range Kate::TextRange *kateRange = rangesWithAttributes[i]; // calculate attribute, default: normal attribute KTextEditor::Attribute::Ptr attribute = kateRange->attribute(); if (anyDynamicHlsActive) { // check mouse in if (KTextEditor::Attribute::Ptr attributeMouseIn = attribute->dynamicAttribute(KTextEditor::Attribute::ActivateMouseIn)) { if (rangesMouseIn->contains(kateRange)) { attribute = attributeMouseIn; } } // check caret in if (KTextEditor::Attribute::Ptr attributeCaretIn = attribute->dynamicAttribute(KTextEditor::Attribute::ActivateCaretIn)) { if (rangesCaretIn->contains(kateRange)) { attribute = attributeCaretIn; } } } // span range NormalRenderRange *additionaHl = new NormalRenderRange(); additionaHl->addRange(new KTextEditor::Range(*kateRange), attribute); renderRanges.append(additionaHl); } } else { // Add the code completion arbitrary highlight to the list renderRanges.append(completionHighlight); } // Add selection highlighting if we're creating the selection decorations if ((m_view && selectionsOnly && showSelections() && m_view->selection()) || (completionHighlight && completionSelected) || (m_view && m_view->blockSelection())) { // Set up the selection background attribute TODO: move this elsewhere, eg. into the config? static KTextEditor::Attribute::Ptr backgroundAttribute; if (!backgroundAttribute) { backgroundAttribute = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute()); } backgroundAttribute->setBackground(config()->selectionColor()); backgroundAttribute->setForeground(attribute(KTextEditor::dsNormal)->selectedForeground().color()); // Create ranges for the current selection if (completionHighlight && completionSelected) { auto selectionHighlight = new NormalRenderRange(); selectionHighlight->addRange(new KTextEditor::Range(line, 0, line + 1, 0), backgroundAttribute); renderRanges.append(selectionHighlight); } else if (m_view->blockSelection() && m_view->selections()->overlapsLine(line)) { auto selectionHighlight = new NormalRenderRange(); selectionHighlight->addRange(new KTextEditor::Range(m_doc->rangeOnLine(m_view->selectionRange(), line)), backgroundAttribute); renderRanges.append(selectionHighlight); } else { auto s = m_view->selections()->selections(); Q_FOREACH ( const auto& range, s ) { if ( !range.overlapsLine(line) ) { continue; } auto start = qMax(range.start(), KTextEditor::Cursor(line, 0)); auto end = qMin(range.end(), KTextEditor::Cursor(line, textLine->length())); // qDebug() << "adding highlight range:" << start << end << "for line" << line; auto selectionHighlight = new NormalRenderRange(); selectionHighlight->addRange(new KTextEditor::Range(start, end), backgroundAttribute); renderRanges.append(selectionHighlight); } } } KTextEditor::Cursor currentPosition, endPosition; // Calculate the range which we need to iterate in order to get the highlighting for just this line if (m_view && selectionsOnly) { if (m_view->blockSelection()) { KTextEditor::Range subRange = m_doc->rangeOnLine(m_view->selectionRange(), line); currentPosition = subRange.start(); endPosition = subRange.end(); } else { KTextEditor::Range rangeNeeded = KTextEditor::Range(line, 0, line + 1, 0); currentPosition = rangeNeeded.start(); endPosition = rangeNeeded.end(); } } else { currentPosition = KTextEditor::Cursor(line, 0); endPosition = KTextEditor::Cursor(line + 1, 0); } // Main iterative loop. This walks through each set of highlighting ranges, and stops each // time the highlighting changes. It then creates the corresponding QTextLayout::FormatRanges. while (currentPosition < endPosition) { renderRanges.advanceTo(currentPosition); // qDebug() << "advanced to:" << currentPosition << renderRanges.hasAttribute(); if (!renderRanges.hasAttribute()) { // No attribute, don't need to create a FormatRange for this text range currentPosition = renderRanges.nextBoundary(); continue; } KTextEditor::Cursor nextPosition = renderRanges.nextBoundary(); // Create the format range and populate with the correct start, length and format info QTextLayout::FormatRange fr; fr.start = currentPosition.column(); if (nextPosition < endPosition || endPosition.line() <= line) { fr.length = nextPosition.column() - currentPosition.column(); } else { // +1 to force background drawing at the end of the line when it's warranted fr.length = textLine->length() - currentPosition.column() + 1; } KTextEditor::Attribute::Ptr a = renderRanges.generateAttribute(); if (a) { fr.format = *a; #warning fixme, no idea how this should work. halp // if (selectionsOnly) { // assignSelectionBrushesFromAttribute(fr, *a); // } } newHighlight.append(fr); currentPosition = nextPosition; } if (completionHighlight) // Don't delete external completion render range { renderRanges.removeAll(completionHighlight); } qDeleteAll(renderRanges); } return newHighlight; } void KateRenderer::assignSelectionBrushesFromAttribute(QTextLayout::FormatRange &target, const KTextEditor::Attribute &attribute) const { if (attribute.hasProperty(SelectedForeground)) { target.format.setForeground(attribute.selectedForeground()); } if (attribute.hasProperty(SelectedBackground)) { target.format.setBackground(attribute.selectedBackground()); } } /* The ultimate line painting function. Currently missing features: - draw indent lines */ void KateRenderer::paintTextLine(QPainter &paint, KateLineLayoutPtr range, int xStart, int xEnd, const KTextEditor::Cursor *cursor, PaintTextLineFlags flags) { Q_ASSERT(range->isValid()); // qCDebug(LOG_KTE)<<"KateRenderer::paintTextLine"; // font data const QFontMetricsF &fm = config()->fontMetrics(); int currentViewLine = -1; if (cursor && cursor->line() == range->line()) { currentViewLine = range->viewLineForColumn(cursor->column()); } paintTextLineBackground(paint, range, currentViewLine, xStart, xEnd); if (range->layout()) { bool drawSelection = m_view && m_view->selection() && showSelections() && m_view->selections()->overlapsLine(range->line()); // qDebug() << "draw selection:" << drawSelection << range->line(); // Draw selection in block selecton mode. We need past-end-of-line selection which QTextLayout::draw can't render if (drawSelection && m_view->blockSelection()) { int selectionStartColumn = m_doc->fromVirtualColumn(range->line(), m_doc->toVirtualColumn(m_view->selectionRange().start())); int selectionEndColumn = m_doc->fromVirtualColumn(range->line(), m_doc->toVirtualColumn(m_view->selectionRange().end())); QBrush selectionBrush = config()->selectionColor(); if (selectionStartColumn != selectionEndColumn) { KateTextLayout lastLine = range->viewLine(range->viewLineCount() - 1); if (selectionEndColumn > lastLine.startCol()) { int selectionStartX = (selectionStartColumn > lastLine.startCol()) ? cursorToX(lastLine, selectionStartColumn, true) : 0; int selectionEndX = cursorToX(lastLine, selectionEndColumn, true); paint.fillRect(QRect(selectionStartX - xStart, (int)lastLine.lineLayout().y(), selectionEndX - selectionStartX, lineHeight()), selectionBrush); } } } QVector additionalFormats; if (range->length() > 0) { // We may have changed the pen, be absolutely sure it gets set back to // normal foreground color before drawing text for text that does not // set the pen color paint.setPen(attribute(KTextEditor::dsNormal)->foreground().color()); // Draw the text :) if (drawSelection) { // FIXME toVector() may be a performance issue additionalFormats = decorationsForLine(range->textLine(), range->line(), true).toVector(); // qDebug() << "selection formats:" << additionalFormats.size(); // Q_FOREACH ( const auto& fmt, additionalFormats ) { // qDebug() << fmt.start << fmt.length << fmt.format; // } range->layout()->draw(&paint, QPoint(-xStart, 0), additionalFormats); } else { range->layout()->draw(&paint, QPoint(-xStart, 0)); } } QBrush backgroundBrush; bool backgroundBrushSet = false; // Loop each individual line for additional text decoration etc. QListIterator it = range->layout()->additionalFormats(); QVectorIterator it2 = additionalFormats; for (int i = 0; i < range->viewLineCount(); ++i) { KateTextLayout line = range->viewLine(i); bool haveBackground = false; // Determine the background to use, if any, for the end of this view line backgroundBrushSet = false; while (it2.hasNext()) { const QTextLayout::FormatRange &fr = it2.peekNext(); if (fr.start > line.endCol()) { break; } if (fr.start + fr.length > line.endCol()) { if (fr.format.hasProperty(QTextFormat::BackgroundBrush)) { backgroundBrushSet = true; backgroundBrush = fr.format.background(); } haveBackground = true; break; } it2.next(); } while (!haveBackground && it.hasNext()) { const QTextLayout::FormatRange &fr = it.peekNext(); if (fr.start > line.endCol()) { break; } if (fr.start + fr.length > line.endCol()) { if (fr.format.hasProperty(QTextFormat::BackgroundBrush)) { backgroundBrushSet = true; backgroundBrush = fr.format.background(); } break; } it.next(); } // Draw selection or background color outside of areas where text is rendered if (!m_printerFriendly) { bool draw = false; QBrush drawBrush; if (m_view && m_view->selection() && !m_view->blockSelection() && m_view->lineEndSelected(line.end(true))) { draw = true; drawBrush = config()->selectionColor(); } else if (backgroundBrushSet && !m_view->blockSelection()) { draw = true; drawBrush = backgroundBrush; } if (draw) { int fillStartX = line.endX() - line.startX() + line.xOffset() - xStart; int fillStartY = lineHeight() * i; int width = xEnd - xStart - fillStartX; int height = lineHeight(); // reverse X for right-aligned lines if (range->layout()->textOption().alignment() == Qt::AlignRight) { fillStartX = 0; } if (width > 0) { QRect area(fillStartX, fillStartY, width, height); paint.fillRect(area, drawBrush); } } } // Draw indent lines if (showIndentLines() && i == 0) { const qreal w = spaceWidth(); const int lastIndentColumn = range->textLine()->indentDepth(m_tabWidth); for (int x = m_indentWidth; x < lastIndentColumn; x += m_indentWidth) { paintIndentMarker(paint, x * w + 1 - xStart, range->line()); } } // draw an open box to mark non-breaking spaces const QString &text = range->textLine()->string(); int y = lineHeight() * i + fm.ascent() - fm.strikeOutPos(); int nbSpaceIndex = text.indexOf(nbSpaceChar, line.lineLayout().xToCursor(xStart)); while (nbSpaceIndex != -1 && nbSpaceIndex < line.endCol()) { int x = line.lineLayout().cursorToX(nbSpaceIndex); if (x > xEnd) { break; } paintNonBreakSpace(paint, x - xStart, y); nbSpaceIndex = text.indexOf(nbSpaceChar, nbSpaceIndex + 1); } // draw tab stop indicators if (showTabs()) { int tabIndex = text.indexOf(tabChar, line.lineLayout().xToCursor(xStart)); while (tabIndex != -1 && tabIndex < line.endCol()) { int x = line.lineLayout().cursorToX(tabIndex); if (x > xEnd) { break; } paintTabstop(paint, x - xStart + spaceWidth() / 2.0, y); tabIndex = text.indexOf(tabChar, tabIndex + 1); } } // draw trailing spaces if (showTrailingSpaces()) { int spaceIndex = line.endCol() - 1; int trailingPos = range->textLine()->lastChar(); if (trailingPos < 0) { trailingPos = 0; } if (spaceIndex >= trailingPos) { while (spaceIndex >= line.startCol() && text.at(spaceIndex).isSpace()) { if (text.at(spaceIndex) != QLatin1Char('\t') || !showTabs()) { if (range->layout()->textOption().alignment() == Qt::AlignRight) { // Draw on left for RTL lines paintTrailingSpace(paint, line.lineLayout().cursorToX(spaceIndex) - xStart - spaceWidth() / 2.0, y); } else { paintTrailingSpace(paint, line.lineLayout().cursorToX(spaceIndex) - xStart + spaceWidth() / 2.0, y); } } --spaceIndex; } } } if (showNonPrintableSpaces()) { const int y = lineHeight() * i + fm.ascent(); static const QRegularExpression nonPrintableSpacesRegExp(QStringLiteral("[\\x{2000}-\\x{200F}\\x{2028}-\\x{202F}\\x{205F}-\\x{2064}\\x{206A}-\\x{206F}]")); QRegularExpressionMatchIterator i = nonPrintableSpacesRegExp.globalMatch(text, line.lineLayout().xToCursor(xStart)); while (i.hasNext()) { const int charIndex = i.next().capturedStart(); const int x = line.lineLayout().cursorToX(charIndex); if (x > xEnd) { break; } paintNonPrintableSpaces(paint, x - xStart, y, text[charIndex]); } } } // draw word-wrap-honor-indent filling if ((range->viewLineCount() > 1) && range->shiftX() && (range->shiftX() > xStart)) { if (backgroundBrushSet) paint.fillRect(0, lineHeight(), range->shiftX() - xStart, lineHeight() * (range->viewLineCount() - 1), backgroundBrush); paint.fillRect(0, lineHeight(), range->shiftX() - xStart, lineHeight() * (range->viewLineCount() - 1), QBrush(config()->wordWrapMarkerColor(), Qt::Dense4Pattern)); } // Draw caret auto drawCaretAt = [this, &range, &paint, &xEnd, &xStart](const KTextEditor::Cursor* cursor, int alpha=255) { if (drawCaret() && cursor && range->includesCursor(*cursor)) { int caretWidth, lineWidth = 2; QColor color; QTextLine line = range->layout()->lineForTextPosition(qMin(cursor->column(), range->length())); // Determine the caret's style caretStyles style = caretStyle(); // Make the caret the desired width if (style == Line) { caretWidth = lineWidth; } else if (line.isValid() && cursor->column() < range->length()) { caretWidth = int(line.cursorToX(cursor->column() + 1) - line.cursorToX(cursor->column())); if (caretWidth < 0) { caretWidth = -caretWidth; } } else { caretWidth = spaceWidth(); } // Determine the color if (m_caretOverrideColor.isValid()) { // Could actually use the real highlighting system for this... // would be slower, but more accurate for corner cases color = m_caretOverrideColor; } else { // search for the FormatRange that includes the cursor foreach (const QTextLayout::FormatRange &r, range->layout()->additionalFormats()) { if ((r.start <= cursor->column()) && ((r.start + r.length) > cursor->column())) { // check for Qt::NoBrush, as the returned color is black() and no invalid QColor QBrush foregroundBrush = r.format.foreground(); if (foregroundBrush != Qt::NoBrush) { color = r.format.foreground().color(); } break; } } // still no color found, fall back to default style if (!color.isValid()) { color = attribute(KTextEditor::dsNormal)->foreground().color(); } } paint.save(); + paint.setRenderHint(QPainter::Antialiasing, false); switch (style) { case Line : paint.setPen(QPen(color, caretWidth)); break; case Block : // use a gray caret so it's possible to see the character color.setAlpha(128); paint.setPen(QPen(color, caretWidth)); break; case Underline : break; case Half : color.setAlpha(128); paint.setPen(QPen(color, caretWidth)); break; } if (cursor->column() <= range->length()) { range->layout()->drawCursor(&paint, QPoint(-xStart, 0), cursor->column(), caretWidth); } else { // Off the end of the line... must be block mode. Draw the caret ourselves. const KateTextLayout &lastLine = range->viewLine(range->viewLineCount() - 1); int x = cursorToX(lastLine, KTextEditor::Cursor(range->line(), cursor->column()), true); if ((x >= xStart) && (x <= xEnd)) { paint.fillRect(x - xStart, (int)lastLine.lineLayout().y(), caretWidth, lineHeight(), color); } } paint.restore(); } }; drawCaretAt(cursor); foreach ( const auto& secondary, view()->cursors()->cursors() ) { if ( cursor && secondary == *cursor ) { continue; } drawCaretAt(&secondary, 128); } } // Draws the dashed underline at the start of a folded block of text. if (!(flags & SkipDrawFirstInvisibleLineUnderlined) && range->startsInvisibleBlock()) { const QPainter::RenderHints backupRenderHints = paint.renderHints(); paint.setRenderHint(QPainter::Antialiasing, false); QPen pen(config()->wordWrapMarkerColor()); pen.setCosmetic(true); pen.setStyle(Qt::DashLine); pen.setDashOffset(xStart); paint.setPen(pen); paint.drawLine(0, (lineHeight() * range->viewLineCount()) - 1, xEnd - xStart, (lineHeight() * range->viewLineCount()) - 1); paint.setRenderHints(backupRenderHints); } // show word wrap marker if desirable if ((!isPrinterFriendly()) && config()->wordWrapMarker() && QFontInfo(config()->font()).fixedPitch()) { const QPainter::RenderHints backupRenderHints = paint.renderHints(); paint.setRenderHint(QPainter::Antialiasing, false); paint.setPen(config()->wordWrapMarkerColor()); int _x = qreal(m_doc->config()->wordWrapAt()) * fm.width(QLatin1Char('x')) - xStart; paint.drawLine(_x, 0, _x, lineHeight()); paint.setRenderHints(backupRenderHints); } } const QFont &KateRenderer::currentFont() const { return config()->font(); } const QFontMetricsF &KateRenderer::currentFontMetrics() const { return config()->fontMetrics(); } uint KateRenderer::fontHeight() const { return m_fontHeight; } uint KateRenderer::documentHeight() const { return m_doc->lines() * lineHeight(); } int KateRenderer::lineHeight() const { return fontHeight(); } void KateRenderer::updateConfig() { // update the attibute list pointer updateAttributes(); // update font height, do this before we update the view! updateFontHeight(); // trigger view update, if any! if (m_view) { m_view->updateRendererConfig(); } } void KateRenderer::updateFontHeight() { - // use height of font + round down, ensure we have at least one pixel - // we round down to avoid artifacts: line height too large vs. qt background rendering of text attributes - const qreal height = config()->fontMetrics().height(); - m_fontHeight = qMax(1, qFloor(height)); + // ensure minimal height of one pixel to not fall in the div by 0 trap somewhere + // use font line spacing, this includes the leading + m_fontHeight = qMax(1, qCeil(config()->fontMetrics().lineSpacing())); } void KateRenderer::updateMarkerSize() { // marker size will be calculated over the value defined // on dialog m_markerSize = spaceWidth() / (3.5 - (m_doc->config()->markerSize() * 0.5)); } qreal KateRenderer::spaceWidth() const { return config()->fontMetrics().width(spaceChar); } void KateRenderer::layoutLine(KateLineLayoutPtr lineLayout, int maxwidth, bool cacheLayout) const { // if maxwidth == -1 we have no wrap Kate::TextLine textLine = lineLayout->textLine(); Q_ASSERT(textLine); QTextLayout *l = lineLayout->layout(); if (!l) { l = new QTextLayout(textLine->string(), config()->font()); } else { l->setText(textLine->string()); l->setFont(config()->font()); } l->setCacheEnabled(cacheLayout); // Initial setup of the QTextLayout. // Tab width QTextOption opt; opt.setFlags(QTextOption::IncludeTrailingSpaces); opt.setTabStop(m_tabWidth * config()->fontMetrics().width(spaceChar)); opt.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); // Find the first strong character in the string. // If it is an RTL character, set the base layout direction of the string to RTL. // // See http://www.unicode.org/reports/tr9/#The_Paragraph_Level (Sections P2 & P3). // Qt's text renderer ("scribe") version 4.2 assumes a "higher-level protocol" // (such as KatePart) will specify the paragraph level, so it does not apply P2 & P3 // by itself. If this ever change in Qt, the next code block could be removed. if (isLineRightToLeft(lineLayout)) { opt.setAlignment(Qt::AlignRight); opt.setTextDirection(Qt::RightToLeft); } else { opt.setAlignment(Qt::AlignLeft); opt.setTextDirection(Qt::LeftToRight); } l->setTextOption(opt); // Syntax highlighting, inbuilt and arbitrary l->setAdditionalFormats(decorationsForLine(textLine, lineLayout->line())); // Begin layouting l->beginLayout(); int height = 0; int shiftX = 0; bool needShiftX = (maxwidth != -1) && m_view && (m_view->config()->dynWordWrapAlignIndent() > 0); forever { - QTextLine line = l->createLine(); + QTextLine line = l->createLine(); if (!line.isValid()) { break; } if (maxwidth > 0) { line.setLineWidth(maxwidth); } + // we include the leading, this must match the ::updateFontHeight code! + line.setLeadingIncluded(true); + line.setPosition(QPoint(line.lineNumber() ? shiftX : 0, height)); if (needShiftX && line.width() > 0) { needShiftX = false; // Determine x offset for subsequent-lines-of-paragraph indenting int pos = textLine->nextNonSpaceChar(0); if (pos > 0) { shiftX = (int)line.cursorToX(pos); } // check for too deep shift value and limit if necessary if (shiftX > ((double)maxwidth / 100 * m_view->config()->dynWordWrapAlignIndent())) { shiftX = 0; } // if shiftX > 0, the maxwidth has to adapted maxwidth -= shiftX; lineLayout->setShiftX(shiftX); } height += lineHeight(); } l->endLayout(); lineLayout->setLayout(l); } // 1) QString::isRightToLeft() sux // 2) QString::isRightToLeft() is marked as internal (WTF?) // 3) QString::isRightToLeft() does not seem to work on my setup // 4) isStringRightToLeft() should behave much better than QString::isRightToLeft() therefore: // 5) isStringRightToLeft() kicks ass bool KateRenderer::isLineRightToLeft(KateLineLayoutPtr lineLayout) const { QString s = lineLayout->textLine()->string(); int i = 0; // borrowed from QString::updateProperties() while (i != s.length()) { QChar c = s.at(i); switch (c.direction()) { case QChar::DirL: case QChar::DirLRO: case QChar::DirLRE: return false; case QChar::DirR: case QChar::DirAL: case QChar::DirRLO: case QChar::DirRLE: return true; default: break; } i ++; } return false; #if 0 // or should we use the direction of the widget? QWidget *display = qobject_cast(view()->parent()); if (!display) { return false; } return display->layoutDirection() == Qt::RightToLeft; #endif } int KateRenderer::cursorToX(const KateTextLayout &range, int col, bool returnPastLine) const { return cursorToX(range, KTextEditor::Cursor(range.line(), col), returnPastLine); } int KateRenderer::cursorToX(const KateTextLayout &range, const KTextEditor::Cursor &pos, bool returnPastLine) const { Q_ASSERT(range.isValid()); int x; if (range.lineLayout().width() > 0) { x = (int)range.lineLayout().cursorToX(pos.column()); } else { x = 0; } int over = pos.column() - range.endCol(); if (returnPastLine && over > 0) { x += over * spaceWidth(); } return x; } KTextEditor::Cursor KateRenderer::xToCursor(const KateTextLayout &range, int x, bool returnPastLine) const { Q_ASSERT(range.isValid()); KTextEditor::Cursor ret(range.line(), range.lineLayout().xToCursor(x)); // TODO wrong for RTL lines? if (returnPastLine && range.endCol(true) == -1 && x > range.width() + range.xOffset()) { ret.setColumn(ret.column() + ((x - (range.width() + range.xOffset())) / spaceWidth())); } return ret; } void KateRenderer::setCaretOverrideColor(const QColor &color) { m_caretOverrideColor = color; } diff --git a/src/render/katerenderrange.cpp b/src/render/katerenderrange.cpp index 4321d585..6f2b8fe0 100644 --- a/src/render/katerenderrange.cpp +++ b/src/render/katerenderrange.cpp @@ -1,215 +1,214 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2003-2005 Hamish Rodda * Copyright (C) 2007 Mirko Stocker * Copyright (C) 2008 David Nolden * * 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 "katerenderrange.h" #include void mergeAttributes(KTextEditor::Attribute::Ptr base, KTextEditor::Attribute::Ptr add) { if (!add) { return; } bool hadBg = base->hasProperty(KTextEditor::Attribute::BackgroundBrush); bool hasBg = add->hasProperty(KTextEditor::Attribute::BackgroundBrush); bool hadFg = base->hasProperty(KTextEditor::Attribute::ForegroundBrush); bool hasFg = add->hasProperty(KTextEditor::Attribute::ForegroundBrush); if (((!hadBg || !hasBg) && (!hadFg || !hasFg))) { //Nothing to blend *base += *add; return; } //We eventually have to blend QBrush baseBgBrush, baseFgBrush; if (hadBg) { baseBgBrush = base->background(); } if (hadFg) { baseFgBrush = base->foreground(); } *base += *add; if (hadBg && hasBg) { QBrush bg = add->background(); if (!bg.isOpaque()) { QColor mixWithColor = bg.color(); mixWithColor.setAlpha(255); bg.setColor(KColorUtils::mix(baseBgBrush.color(), mixWithColor, bg.color().alphaF())); base->setBackground(bg); } } if (hadFg && hasFg) { QBrush fg = add->foreground(); if (!fg.isOpaque()) { QColor mixWithColor = fg.color(); mixWithColor.setAlpha(255); fg.setColor(KColorUtils::mix(baseFgBrush.color(), mixWithColor, fg.color().alphaF())); base->setForeground(fg); } } } bool KateRenderRange::isReady() const { return false; } NormalRenderRange::NormalRenderRange() - : m_currentRange(0) { } NormalRenderRange::~NormalRenderRange() { QListIterator it = m_ranges; while (it.hasNext()) { delete it.next().first; } } void NormalRenderRange::addRange(KTextEditor::Range *range, KTextEditor::Attribute::Ptr attribute) { m_ranges.append(pairRA(range, attribute)); } KTextEditor::Cursor NormalRenderRange::nextBoundary() const { return m_nextBoundary; } bool NormalRenderRange::advanceTo(const KTextEditor::Cursor &pos) { int index = m_currentRange; while (index < m_ranges.count()) { const pairRA &p = m_ranges.at(index); KTextEditor::Range *r = p.first; if (r->end() <= pos) { ++index; } else { bool ret = index != m_currentRange; m_currentRange = index; if (r->start() > pos) { m_nextBoundary = r->start(); } else { m_nextBoundary = r->end(); } if (r->contains(pos)) { m_currentAttribute = p.second; } else { m_currentAttribute.reset(); } return ret; } } m_nextBoundary = KTextEditor::Cursor(INT_MAX, INT_MAX); m_currentAttribute.reset(); return false; } KTextEditor::Attribute::Ptr NormalRenderRange::currentAttribute() const { return m_currentAttribute; } KTextEditor::Cursor RenderRangeList::nextBoundary() const { KTextEditor::Cursor ret = m_currentPos; bool first = true; foreach (KateRenderRange *r, *this) { if (first) { ret = r->nextBoundary(); first = false; } else { KTextEditor::Cursor nb = r->nextBoundary(); if (ret > nb) { ret = nb; } } } return ret; } RenderRangeList::~RenderRangeList() { } void RenderRangeList::advanceTo(const KTextEditor::Cursor &pos) { foreach (KateRenderRange *r, *this) { r->advanceTo(pos); } //Delete lists that are ready, else the list may get too large due to temporaries for (int a = size() - 1; a >= 0; --a) { KateRenderRange *r = at(a); if (r->isReady()) { delete r; removeAt(a); } } } bool RenderRangeList::hasAttribute() const { foreach (KateRenderRange *r, *this) if (r->currentAttribute()) { return true; } return false; } KTextEditor::Attribute::Ptr RenderRangeList::generateAttribute() const { KTextEditor::Attribute::Ptr a; bool ownsAttribute = false; foreach (KateRenderRange *r, *this) { if (KTextEditor::Attribute::Ptr a2 = r->currentAttribute()) { if (!a) { a = a2; } else { if (!ownsAttribute) { //Make an own copy of the attribute.. ownsAttribute = true; a = new KTextEditor::Attribute(*a); } mergeAttributes(a, a2); } } } return a; } diff --git a/src/render/katerenderrange.h b/src/render/katerenderrange.h index bf9ef88a..5812fb06 100644 --- a/src/render/katerenderrange.h +++ b/src/render/katerenderrange.h @@ -1,75 +1,75 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2003-2005 Hamish Rodda * Copyright (C) 2008 David Nolden * * 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 KATERENDERRANGE_H #define KATERENDERRANGE_H #include #include #include #include class KateRenderRange { public: virtual ~KateRenderRange() {} virtual KTextEditor::Cursor nextBoundary() const = 0; virtual bool advanceTo(const KTextEditor::Cursor &pos) = 0; virtual KTextEditor::Attribute::Ptr currentAttribute() const = 0; virtual bool isReady() const; }; typedef QPair pairRA; class NormalRenderRange : public KateRenderRange { public: NormalRenderRange(); - virtual ~NormalRenderRange(); + ~NormalRenderRange() override; void addRange(KTextEditor::Range *range, KTextEditor::Attribute::Ptr attribute); - KTextEditor::Cursor nextBoundary() const Q_DECL_OVERRIDE; - bool advanceTo(const KTextEditor::Cursor &pos) Q_DECL_OVERRIDE; - KTextEditor::Attribute::Ptr currentAttribute() const Q_DECL_OVERRIDE; + KTextEditor::Cursor nextBoundary() const override; + bool advanceTo(const KTextEditor::Cursor &pos) override; + KTextEditor::Attribute::Ptr currentAttribute() const override; private: QList m_ranges; KTextEditor::Cursor m_nextBoundary; KTextEditor::Attribute::Ptr m_currentAttribute; - int m_currentRange; + int m_currentRange = 0; }; class RenderRangeList : public QList { public: ~RenderRangeList(); KTextEditor::Cursor nextBoundary() const; void advanceTo(const KTextEditor::Cursor &pos); bool hasAttribute() const; KTextEditor::Attribute::Ptr generateAttribute() const; private: KTextEditor::Cursor m_currentPos; }; #endif diff --git a/src/render/katetextlayout.cpp b/src/render/katetextlayout.cpp index 1b3aaf13..8b356bef 100644 --- a/src/render/katetextlayout.cpp +++ b/src/render/katetextlayout.cpp @@ -1,240 +1,239 @@ /* This file is part of the KDE libraries Copyright (C) 2002-2005 Hamish Rodda Copyright (C) 2003 Anakim Border * * 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 "katetextlayout.h" #include "katepartdebug.h" KateTextLayout::KateTextLayout(KateLineLayoutPtr line, int viewLine) : m_lineLayout(line) , m_viewLine(viewLine) , m_startX(m_viewLine ? -1 : 0) - , m_invalidDirty(true) { if (isValid()) { m_textLayout = m_lineLayout->layout()->lineAt(m_viewLine); } } bool KateTextLayout::isDirty() const { if (!isValid()) { return m_invalidDirty; } return m_lineLayout->isDirty(viewLine()); } bool KateTextLayout::setDirty(bool dirty) { if (!isValid()) { return (m_invalidDirty = dirty); } return m_lineLayout->setDirty(viewLine(), dirty); } bool KateTextLayout::includesCursor(const KTextEditor::Cursor &realCursor) const { return realCursor.line() == line() && realCursor.column() >= startCol() && (!wrap() || realCursor.column() < endCol()); } int KateTextLayout::xOffset() const { if (!isValid()) { return 0; } return startX() ? m_lineLayout->shiftX() : 0; } void KateTextLayout::debugOutput() const { qCDebug(LOG_KTE) << "KateTextLayout: " << m_lineLayout << " valid " << isValid() << " line " << m_lineLayout->line() << " (" << line() << ") cols [" << startCol() << " -> " << endCol() << "] x [" << startX() << " -> " << endX() << " off " << m_lineLayout->shiftX() << "] wrap " << wrap(); } bool operator> (const KateTextLayout &r, const KTextEditor::Cursor &c) { return r.line() > c.line() || r.endCol() > c.column(); } bool operator>= (const KateTextLayout &r, const KTextEditor::Cursor &c) { return r.line() > c.line() || r.endCol() >= c.column(); } bool operator< (const KateTextLayout &r, const KTextEditor::Cursor &c) { return r.line() < c.line() || r.startCol() < c.column(); } bool operator<= (const KateTextLayout &r, const KTextEditor::Cursor &c) { return r.line() < c.line() || r.startCol() <= c.column(); } bool KateTextLayout::isValid() const { return m_lineLayout && m_lineLayout->isValid() && m_viewLine >= 0 && m_viewLine < m_lineLayout->viewLineCount(); } int KateTextLayout::line() const { if (!isValid()) { return -1; } return m_lineLayout->line(); } int KateTextLayout::virtualLine() const { if (!isValid()) { return -1; } return m_lineLayout->virtualLine(); } int KateTextLayout::viewLine() const { if (!isValid()) { return 0; } return m_viewLine; } const QTextLine &KateTextLayout::lineLayout() const { return m_textLayout; } KateLineLayoutPtr KateTextLayout::kateLineLayout() const { return m_lineLayout; } int KateTextLayout::startCol() const { if (!isValid()) { return 0; } return lineLayout().textStart(); } KTextEditor::Cursor KateTextLayout::start() const { return KTextEditor::Cursor(line(), startCol()); } int KateTextLayout::endCol(bool indicateEOL) const { if (!isValid()) { return 0; } if (indicateEOL) if (viewLine() == kateLineLayout()->viewLineCount() - 1) { return -1; } return startCol() + m_textLayout.textLength(); } KTextEditor::Cursor KateTextLayout::end(bool indicateEOL) const { return KTextEditor::Cursor(line(), endCol(indicateEOL)); } int KateTextLayout::length() const { if (!isValid()) { return 0; } return m_textLayout.textLength(); } bool KateTextLayout::isEmpty() const { if (!isValid()) { return true; } return startCol() == 0 && endCol() == 0; } bool KateTextLayout::wrap() const { if (!isValid()) { return false; } return viewLine() < m_lineLayout->viewLineCount() - 1; } int KateTextLayout::startX() const { if (!isValid()) { return 0; } if (m_startX == -1) // viewLine is already > 0, from the constructor for (int i = 0; i < viewLine(); ++i) { m_startX += (int)m_lineLayout->layout()->lineAt(i).naturalTextWidth(); } return m_startX; } int KateTextLayout::endX() const { if (!isValid()) { return 0; } return startX() + (int)m_textLayout.naturalTextWidth(); } int KateTextLayout::width() const { if (!isValid()) { return 0; } return (int)m_textLayout.naturalTextWidth(); } KateTextLayout KateTextLayout::invalid() { return KateTextLayout(); } bool KateTextLayout::isRightToLeft() const { if (m_lineLayout) { return m_lineLayout->isRightToLeft(); } return false; } diff --git a/src/render/katetextlayout.h b/src/render/katetextlayout.h index 57164ab0..3160fa12 100644 --- a/src/render/katetextlayout.h +++ b/src/render/katetextlayout.h @@ -1,107 +1,107 @@ /* This file is part of the KDE libraries Copyright (C) 2002-2005 Hamish Rodda Copyright (C) 2003 Anakim Border * * 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_TEXTLAYOUT_H_ #define _KATE_TEXTLAYOUT_H_ #include #include "katelinelayout.h" /** * This class represents one visible line of text; with dynamic wrapping, * many KateTextLayouts can be needed to represent one actual line of text * (ie. one KateLineLayout) */ class KateTextLayout { friend class KateLineLayout; friend class KateLayoutCache; template friend class QVector; public: bool isValid() const; static KateTextLayout invalid(); int line() const; int virtualLine() const; /** Return the index of this visual line inside the document line (KateLineLayout). */ int viewLine() const; const QTextLine &lineLayout() const; KateLineLayoutPtr kateLineLayout() const; int startCol() const; KTextEditor::Cursor start() const; /** * Return the end column of this text line. * * \param indicateEOL set to true to return -1 if this layout is the * end of the line, otherwise false to return the end column number */ int endCol(bool indicateEOL = false) const; /** * Return the end position of this text line. * * \param indicateEOL set to true to return -1 if this layout is the * end of the line, otherwise false to return the end column number */ KTextEditor::Cursor end(bool indicateEOL = false) const; int length() const; bool isEmpty() const; bool wrap() const; bool isDirty() const; bool setDirty(bool dirty = true); int startX() const; int endX() const; int width() const; int xOffset() const; bool isRightToLeft() const; bool includesCursor(const KTextEditor::Cursor &realCursor) const; friend bool operator> (const KateLineLayout &r, const KTextEditor::Cursor &c); friend bool operator>= (const KateLineLayout &r, const KTextEditor::Cursor &c); friend bool operator< (const KateLineLayout &r, const KTextEditor::Cursor &c); friend bool operator<= (const KateLineLayout &r, const KTextEditor::Cursor &c); void debugOutput() const; private: explicit KateTextLayout(KateLineLayoutPtr line = KateLineLayoutPtr(), int viewLine = 0); KateLineLayoutPtr m_lineLayout; QTextLine m_textLayout; int m_viewLine; mutable int m_startX; - bool m_invalidDirty; + bool m_invalidDirty = true; }; #endif diff --git a/src/schema/katecategorydrawer.h b/src/schema/katecategorydrawer.h index 5cda1f3e..0b614112 100644 --- a/src/schema/katecategorydrawer.h +++ b/src/schema/katecategorydrawer.h @@ -1,48 +1,48 @@ /* * Copyright (C) 2009 by Rafael Fernández López * Copyright (C) 2013 by 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. */ #ifndef KATE_CATEGORYDRAWER_H #define KATE_CATEGORYDRAWER_H #include class QPainter; class QModelIndex; class QStyleOption; class KateCategoryDrawer : public KCategoryDrawer { public: KateCategoryDrawer(); virtual void drawCategory(const QModelIndex &index, int sortRole, const QStyleOption &option, - QPainter *painter) const Q_DECL_OVERRIDE; + QPainter *painter) const override; - int categoryHeight(const QModelIndex &index, const QStyleOption &option) const Q_DECL_OVERRIDE; + int categoryHeight(const QModelIndex &index, const QStyleOption &option) const override; - int leftMargin() const Q_DECL_OVERRIDE; + int leftMargin() const override; - int rightMargin() const Q_DECL_OVERRIDE; + int rightMargin() const override; }; #endif diff --git a/src/schema/katecolortreewidget.cpp b/src/schema/katecolortreewidget.cpp index b0fa8baf..3f8b0765 100644 --- a/src/schema/katecolortreewidget.cpp +++ b/src/schema/katecolortreewidget.cpp @@ -1,386 +1,386 @@ /* This file is part of the KDE libraries Copyright (C) 2012 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 "katecolortreewidget.h" #include "katecategorydrawer.h" #include #include #include #include #include #include "katepartdebug.h" #include #include #include #include #include //BEGIN KateColorTreeItem class KateColorTreeItem : public QTreeWidgetItem { public: KateColorTreeItem(const KateColorItem &colorItem, QTreeWidgetItem *parent = nullptr) : QTreeWidgetItem(parent) , m_colorItem(colorItem) { setText(0, m_colorItem.name); if (!colorItem.whatsThis.isEmpty()) { setData(1, Qt::WhatsThisRole, colorItem.whatsThis); } if (!colorItem.useDefault) { setData(2, Qt::ToolTipRole, i18n("Use default color from the KDE color scheme")); } } QColor color() const { return m_colorItem.color; } void setColor(const QColor &c) { m_colorItem.color = c; } QColor defaultColor() const { return m_colorItem.defaultColor; } bool useDefaultColor() const { return m_colorItem.useDefault; } void setUseDefaultColor(bool useDefault) { m_colorItem.useDefault = useDefault; QString tooltip = useDefault ? QString() : i18n("Use default color from the KDE color scheme"); setData(2, Qt::ToolTipRole, tooltip); } QString key() { return m_colorItem.key; } KateColorItem colorItem() const { return m_colorItem; } private: KateColorItem m_colorItem; }; //END KateColorTreeItem //BEGIN KateColorTreeDelegate class KateColorTreeDelegate : public QStyledItemDelegate { public: KateColorTreeDelegate(KateColorTreeWidget *widget) : QStyledItemDelegate(widget) , m_tree(widget) { } - QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override { QSize sh = QStyledItemDelegate::sizeHint(option, index); if (!index.parent().isValid()) { sh.rheight() += 2 * m_categoryDrawer.leftMargin(); } else { sh.rheight() += m_categoryDrawer.leftMargin(); } if (index.column() == 0) { sh.rwidth() += m_categoryDrawer.leftMargin(); } else if (index.column() == 1) { sh.rwidth() = 150; } else { sh.rwidth() += m_categoryDrawer.leftMargin(); } return sh; } QRect fullCategoryRect(const QStyleOptionViewItem &option, const QModelIndex &index) const { QModelIndex i = index; if (i.parent().isValid()) { i = i.parent(); } QTreeWidgetItem *item = m_tree->itemFromIndex(i); QRect r = m_tree->visualItemRect(item); // adapt width r.setLeft(m_categoryDrawer.leftMargin()); r.setWidth(m_tree->viewport()->width() - m_categoryDrawer.leftMargin() - m_categoryDrawer.rightMargin()); // adapt height if (item->isExpanded() && item->childCount() > 0) { const int childCount = item->childCount(); const int h = sizeHint(option, index.child(0, 0)).height(); r.setHeight(r.height() + childCount * h); } r.setTop(r.top() + m_categoryDrawer.leftMargin()); return r; } - void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override { Q_ASSERT(index.isValid()); Q_ASSERT(index.column() >= 0 && index.column() <= 2); //BEGIN: draw toplevel items if (!index.parent().isValid()) { QStyleOptionViewItem opt(option); const QRegion cl = painter->clipRegion(); painter->setClipRect(opt.rect); opt.rect = fullCategoryRect(option, index); m_categoryDrawer.drawCategory(index, 0, opt, painter); painter->setClipRegion(cl); return; } //END: draw toplevel items //BEGIN: draw background of category for all other items { QStyleOptionViewItem opt(option); opt.rect = fullCategoryRect(option, index); const QRegion cl = painter->clipRegion(); QRect cr = option.rect; if (index.column() == 0) { if (m_tree->layoutDirection() == Qt::LeftToRight) { cr.setLeft(5); } else { cr.setRight(opt.rect.right()); } } painter->setClipRect(cr); m_categoryDrawer.drawCategory(index, 0, opt, painter); painter->setClipRegion(cl); painter->setRenderHint(QPainter::Antialiasing, false); } //END: draw background of category for all other items // paint the text QStyledItemDelegate::paint(painter, option, index); if (index.column() == 0) { return; } painter->setClipRect(option.rect); KateColorTreeItem *item = dynamic_cast(m_tree->itemFromIndex(index)); //BEGIN: draw color button if (index.column() == 1) { QColor color = item->useDefaultColor() ? item->defaultColor() : item->color(); QStyleOptionButton opt; opt.rect = option.rect; opt.palette = m_tree->palette(); m_tree->style()->drawControl(QStyle::CE_PushButton, &opt, painter, m_tree); opt.rect = m_tree->style()->subElementRect(QStyle::SE_PushButtonContents, &opt, m_tree); opt.rect.adjust(1, 1, -1, -1); painter->fillRect(opt.rect, color); qDrawShadePanel(painter, opt.rect, opt.palette, true, 1, nullptr); } //END: draw color button //BEGIN: draw reset icon if (index.column() == 2 && !item->useDefaultColor()) { // get right pixmap const bool enabled = (option.state & QStyle::State_MouseOver || option.state & QStyle::State_HasFocus); const QPixmap p = QIcon::fromTheme(QStringLiteral("edit-undo")).pixmap(16, 16, enabled ? QIcon::Normal : QIcon::Disabled); // compute rect with scaled sizes const QRect rect(option.rect.left() + 10, option.rect.top() + (option.rect.height() - p.height() / p.devicePixelRatio() + 1) / 2, p.width() / p.devicePixelRatio(), p.height() / p.devicePixelRatio()); painter->drawPixmap(rect, p); } //END: draw reset icon } private: KateColorTreeWidget *m_tree; KateCategoryDrawer m_categoryDrawer; }; //END KateColorTreeDelegate KateColorTreeWidget::KateColorTreeWidget(QWidget *parent) : QTreeWidget(parent) { setItemDelegate(new KateColorTreeDelegate(this)); QStringList headers; headers << QString() // i18nc("@title:column the color name", "Color Role") << QString() // i18nc("@title:column a color button", "Color") << QString();// i18nc("@title:column use default color", "Reset") setHeaderLabels(headers); setHeaderHidden(true); setRootIsDecorated(false); setIndentation(25); } bool KateColorTreeWidget::edit(const QModelIndex &index, EditTrigger trigger, QEvent *event) { // accept edit only for color buttons in column 1 and reset in column 2 if (!index.parent().isValid() || index.column() < 1) { return QTreeWidget::edit(index, trigger, event); } bool accept = false; if (event && event->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(event); accept = (ke->key() == Qt::Key_Space); // allow Space to edit } switch (trigger) { case QAbstractItemView::DoubleClicked: case QAbstractItemView::SelectedClicked: case QAbstractItemView::EditKeyPressed: // = F2 accept = true; break; default: break; } if (accept) { KateColorTreeItem *item = dynamic_cast(itemFromIndex(index)); const QColor color = item->useDefaultColor() ? item->defaultColor() : item->color(); if (index.column() == 1) { const QColor selectedColor = QColorDialog::getColor(color, this); if (selectedColor.isValid()) { item->setUseDefaultColor(false); item->setColor(selectedColor); viewport()->update(); emit changed(); } } else if (index.column() == 2 && !item->useDefaultColor()) { item->setUseDefaultColor(true); viewport()->update(); emit changed(); } return false; } return QTreeWidget::edit(index, trigger, event); } void KateColorTreeWidget::drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const { Q_UNUSED(painter) Q_UNUSED(rect) Q_UNUSED(index) } void KateColorTreeWidget::selectDefaults() { bool somethingChanged = false; // use default colors for all selected items for (int a = 0; a < topLevelItemCount(); ++a) { QTreeWidgetItem *top = topLevelItem(a); for (int b = 0; b < top->childCount(); ++b) { KateColorTreeItem *it = dynamic_cast(top->child(b)); Q_ASSERT(it); if (!it->useDefaultColor()) { it->setUseDefaultColor(true); somethingChanged = true; } } } if (somethingChanged) { viewport()->update(); emit changed(); } } void KateColorTreeWidget::addColorItem(const KateColorItem &colorItem) { QTreeWidgetItem *categoryItem = nullptr; for (int i = 0; i < topLevelItemCount(); ++i) { if (topLevelItem(i)->text(0) == colorItem.category) { categoryItem = topLevelItem(i); break; } } if (!categoryItem) { categoryItem = new QTreeWidgetItem(); categoryItem->setText(0, colorItem.category); addTopLevelItem(categoryItem); expandItem(categoryItem); } new KateColorTreeItem(colorItem, categoryItem); resizeColumnToContents(0); } void KateColorTreeWidget::addColorItems(const QVector &colorItems) { foreach (const KateColorItem &item, colorItems) { addColorItem(item); } } QVector KateColorTreeWidget::colorItems() const { QVector items; for (int a = 0; a < topLevelItemCount(); ++a) { QTreeWidgetItem *top = topLevelItem(a); for (int b = 0; b < top->childCount(); ++b) { KateColorTreeItem *item = dynamic_cast(top->child(b)); Q_ASSERT(item); items.append(item->colorItem()); } } return items; } QColor KateColorTreeWidget::findColor(const QString &key) const { for (int a = 0; a < topLevelItemCount(); ++a) { QTreeWidgetItem *top = topLevelItem(a); for (int b = 0; b < top->childCount(); ++b) { KateColorTreeItem *item = dynamic_cast(top->child(b)); if (item->key() == key) { if (item->useDefaultColor()) { return item->defaultColor(); } else { return item->color(); } } } } return QColor(); } diff --git a/src/schema/katecolortreewidget.h b/src/schema/katecolortreewidget.h index c1e32791..e5a45c6a 100644 --- a/src/schema/katecolortreewidget.h +++ b/src/schema/katecolortreewidget.h @@ -1,71 +1,70 @@ /* This file is part of the KDE libraries Copyright (C) 2012 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. */ #ifndef KATE_COLOR_TREE_WIDGET_H #define KATE_COLOR_TREE_WIDGET_H #include class KateColorItem { public: KateColorItem() - : useDefault(true) { } QString name; // translated name QString category; // translated category for tree view hierarchy QString whatsThis; // what's this info QString key; // untranslated id, used as key to save/load from KConfig QColor color; // user visible color QColor defaultColor; // used when "Default" is clicked - bool useDefault; // flag whether to use the default color + bool useDefault = true; // flag whether to use the default color }; class KateColorTreeWidget : public QTreeWidget { Q_OBJECT friend class KateColorTreeItem; friend class KateColorTreeDelegate; public: explicit KateColorTreeWidget(QWidget *parent = nullptr); public: void addColorItem(const KateColorItem &colorItem); void addColorItems(const QVector &colorItems); QVector colorItems() const; QColor findColor(const QString &key) const; public Q_SLOTS: void selectDefaults(); Q_SIGNALS: void changed(); protected: - bool edit(const QModelIndex &index, EditTrigger trigger, QEvent *event) Q_DECL_OVERRIDE; - void drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const Q_DECL_OVERRIDE; + bool edit(const QModelIndex &index, EditTrigger trigger, QEvent *event) override; + void drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const override; }; #endif diff --git a/src/schema/kateschema.h b/src/schema/kateschema.h index 4a08143d..dbd5de2e 100644 --- a/src/schema/kateschema.h +++ b/src/schema/kateschema.h @@ -1,110 +1,111 @@ /* This file is part of the KDE libraries Copyright (C) 2001-2003 Christoph Cullmann Copyright (C) 2002, 2003 Anders Lund * * 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_SCHEMA_H__ #define __KATE_SCHEMA_H__ #include #include #include #include #include namespace KTextEditor { class ViewPrivate; } class QActionGroup; class KateSchema { public: QString rawName; int shippedDefaultSchema; /** * construct translated name for shipped schemas */ QString translatedName() const { return shippedDefaultSchema ? i18nc("Color Schema", rawName.toUtf8().data()) : rawName; } }; class KateSchemaManager { public: KateSchemaManager(); /** * Config */ KConfig &config() { return m_config; } /** * return kconfiggroup for the given schema */ KConfigGroup schema(const QString &name); /** * return schema data for on schema */ KateSchema schemaData(const QString &name); /** * Constructs list of schemas atm known in config object */ QList list(); private: KConfig m_config; }; class KateViewSchemaAction : public KActionMenu { Q_OBJECT public: KateViewSchemaAction(const QString &text, QObject *parent) : KActionMenu(text, parent) { init(); + setDelayed(false); } void updateMenu(KTextEditor::ViewPrivate *view); private: void init(); QPointer m_view; QStringList names; QActionGroup *m_group; int last; public Q_SLOTS: void slotAboutToShow(); private Q_SLOTS: void setSchema(); }; #endif diff --git a/src/schema/kateschemaconfig.h b/src/schema/kateschemaconfig.h index 25e58429..c3f987e5 100644 --- a/src/schema/kateschemaconfig.h +++ b/src/schema/kateschemaconfig.h @@ -1,216 +1,216 @@ /* This file is part of the KDE libraries Copyright (C) 2001-2003 Christoph Cullmann Copyright (C) 2002, 2003 Anders Lund Copyright (C) 2012 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. */ #ifndef __KATE_SCHEMA_CONFIG_H__ #define __KATE_SCHEMA_CONFIG_H__ #include "katedialogs.h" #include "katecolortreewidget.h" #include "kateextendedattribute.h" #include #include class KateStyleTreeWidget; class KComboBox; class KateSchemaConfigColorTab : public QWidget { Q_OBJECT public: KateSchemaConfigColorTab(); ~KateSchemaConfigColorTab(); QColor backgroundColor() const; QColor selectionColor() const; public Q_SLOTS: void apply(); void reload(); void schemaChanged(const QString &newSchema); void importSchema(KConfigGroup &config); void exportSchema(KConfigGroup &config); Q_SIGNALS: void changed(); private: QVector colorItemList() const; QVector readConfig(KConfigGroup &config); private: // multiple shemas may be edited. Hence, we need one ColorList for each schema QMap > m_schemas; QString m_currentSchema; KateColorTreeWidget *ui; }; class KateSchemaConfigFontTab : public QWidget { Q_OBJECT public: KateSchemaConfigFontTab(); ~KateSchemaConfigFontTab(); public: void readConfig(KConfig *config); void importSchema(KConfigGroup &config); void exportSchema(KConfigGroup &config); public Q_SLOTS: void apply(); void reload(); void schemaChanged(const QString &newSchema); Q_SIGNALS: void changed(); private: class KFontChooser *m_fontchooser; QMap m_fonts; QString m_currentSchema; private Q_SLOTS: void slotFontSelected(const QFont &font); }; class KateSchemaConfigDefaultStylesTab : public QWidget { Q_OBJECT public: KateSchemaConfigDefaultStylesTab(KateSchemaConfigColorTab *colorTab); - ~KateSchemaConfigDefaultStylesTab(); + ~KateSchemaConfigDefaultStylesTab() override; Q_SIGNALS: void changed(); public: void schemaChanged(const QString &schema); void reload(); void apply(); KateAttributeList *attributeList(const QString &schema); void exportSchema(const QString &schema, KConfig *cfg); void importSchema(const QString &schemaName, const QString &schema, KConfig *cfg); protected: - void showEvent(QShowEvent *event) Q_DECL_OVERRIDE; + void showEvent(QShowEvent *event) override; void updateColorPalette(const QColor &textColor); private: KateStyleTreeWidget *m_defaultStyles; QHash m_defaultStyleLists; KateSchemaConfigColorTab *m_colorTab; QString m_currentSchema; }; class KateSchemaConfigHighlightTab : public QWidget { Q_OBJECT public: explicit KateSchemaConfigHighlightTab(KateSchemaConfigDefaultStylesTab *page, KateSchemaConfigColorTab *colorTab); - ~KateSchemaConfigHighlightTab(); + ~KateSchemaConfigHighlightTab() override; void schemaChanged(const QString &schema); void reload(); void apply(); Q_SIGNALS: void changed(); protected Q_SLOTS: void hlChanged(int z); public Q_SLOTS: void exportHl(QString schema = QString(), int hl = -1, KConfig *cfg = nullptr); void importHl(const QString &fromSchemaName = QString(), QString schema = QString(), int hl = -1, KConfig *cfg = nullptr); protected: - void showEvent(QShowEvent *event) Q_DECL_OVERRIDE; + void showEvent(QShowEvent *event) override; void updateColorPalette(const QColor &textColor); private: KateSchemaConfigDefaultStylesTab *m_defaults; KateSchemaConfigColorTab *m_colorTab; KComboBox *hlCombo; KateStyleTreeWidget *m_styles; QString m_schema; int m_hl; QHash > > m_hlDict; public: QList hlsForSchema(const QString &schema); bool loadAllHlsForSchema(const QString &schema); }; class KateSchemaConfigPage : public KateConfigPage { Q_OBJECT public: explicit KateSchemaConfigPage(QWidget *parent); - virtual ~KateSchemaConfigPage(); - QString name() const Q_DECL_OVERRIDE; - QString fullName() const Q_DECL_OVERRIDE; - QIcon icon() const Q_DECL_OVERRIDE; + ~KateSchemaConfigPage() override; + QString name() const override; + QString fullName() const override; + QIcon icon() const override; public Q_SLOTS: - void apply() Q_DECL_OVERRIDE; - void reload() Q_DECL_OVERRIDE; - void reset() Q_DECL_OVERRIDE; - void defaults() Q_DECL_OVERRIDE; + void apply() override; + void reload() override; + void reset() override; + void defaults() override; void exportFullSchema(); void importFullSchema(); private Q_SLOTS: void deleteSchema(); bool newSchema(const QString &newName = QString()); void schemaChanged(const QString &schema); void comboBoxIndexChanged(int currentIndex); private: void refillCombos(const QString &schemaName, const QString &defaultSchemaName); QString requestSchemaName(const QString &suggestedName); private: QString m_currentSchema; class QPushButton *btndel; class KComboBox *defaultSchemaCombo; class KComboBox *schemaCombo; KateSchemaConfigColorTab *m_colorTab; KateSchemaConfigFontTab *m_fontTab; KateSchemaConfigDefaultStylesTab *m_defaultStylesTab; KateSchemaConfigHighlightTab *m_highlightTab; }; #endif diff --git a/src/schema/katestyletreewidget.cpp b/src/schema/katestyletreewidget.cpp index 699e6dda..1f4fec78 100644 --- a/src/schema/katestyletreewidget.cpp +++ b/src/schema/katestyletreewidget.cpp @@ -1,739 +1,739 @@ /* This file is part of the KDE libraries Copyright (C) 2001-2003 Christoph Cullmann Copyright (C) 2002, 2003 Anders Lund Copyright (C) 2005-2006 Hamish Rodda Copyright (C) 2007 Mirko Stocker * * 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 "katestyletreewidget.h" #include "kateconfig.h" #include "kateextendedattribute.h" #include "katedefaultcolors.h" #include "kateglobal.h" #include #include #include #include #include #include #include #include #include //BEGIN KateStyleTreeDelegate class KateStyleTreeDelegate : public QStyledItemDelegate { public: KateStyleTreeDelegate(KateStyleTreeWidget *widget); - void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE; + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; private: QBrush getBrushForColorColumn(const QModelIndex &index, int column) const; KateStyleTreeWidget *m_widget; }; //END //BEGIN KateStyleTreeWidgetItem decl /* QListViewItem subclass to display/edit a style, bold/italic is check boxes, normal and selected colors are boxes, which will display a color chooser when activated. The context name for the style will be drawn using the editor default font and the chosen colors. This widget id designed to handle the default as well as the individual hl style lists. This widget is designed to work with the KateStyleTreeWidget class exclusively. Added by anders, jan 23 2002. */ class KateStyleTreeWidgetItem : public QTreeWidgetItem { public: KateStyleTreeWidgetItem(QTreeWidgetItem *parent, const QString &styleName, KTextEditor::Attribute::Ptr defaultstyle, KTextEditor::Attribute::Ptr data = KTextEditor::Attribute::Ptr()); KateStyleTreeWidgetItem(QTreeWidget *parent, const QString &styleName, KTextEditor::Attribute::Ptr defaultstyle, KTextEditor::Attribute::Ptr data = KTextEditor::Attribute::Ptr()); - ~KateStyleTreeWidgetItem() {} + ~KateStyleTreeWidgetItem() override {} enum columns { Context = 0, Bold, Italic, Underline, StrikeOut, Foreground, SelectedForeground, Background, SelectedBackground, UseDefaultStyle, NumColumns }; /* initializes the style from the default and the hldata */ void initStyle(); /* updates the hldata's style */ void updateStyle(); /* For bool fields, toggles them, for color fields, display a color chooser */ void changeProperty(int p); /** unset a color. * c is 100 (BGColor) or 101 (SelectedBGColor) for now. */ void unsetColor(int c); /* style context name */ QString contextName() const { return text(0); } /* only true for a hl mode item using its default style */ bool defStyle() const; /* true for default styles */ bool isDefault() const; /* whichever style is active (currentStyle for hl mode styles not using the default style, defaultStyle otherwise) */ KTextEditor::Attribute::Ptr style() const { return currentStyle; } - QVariant data(int column, int role) const Q_DECL_OVERRIDE; + QVariant data(int column, int role) const override; KateStyleTreeWidget *treeWidget() const; private: /* private methods to change properties */ void toggleDefStyle(); void setColor(int); /* helper function to copy the default style into the KateExtendedAttribute, when a property is changed and we are using default style. */ KTextEditor::Attribute::Ptr currentStyle, // the style currently in use (was "is") defaultStyle; // default style for hl mode contexts and default styles (was "ds") KTextEditor::Attribute::Ptr actualStyle; // itemdata for hl mode contexts (was "st") }; //END //BEGIN KateStyleTreeWidget KateStyleTreeWidget::KateStyleTreeWidget(QWidget *parent, bool showUseDefaults) : QTreeWidget(parent) { setItemDelegate(new KateStyleTreeDelegate(this)); setRootIsDecorated(false); QStringList headers; headers << i18nc("@title:column Meaning of text in editor", "Context") << QString() << QString() << QString() << QString() << i18nc("@title:column Text style", "Normal") << i18nc("@title:column Text style", "Selected") << i18nc("@title:column Text style", "Background") << i18nc("@title:column Text style", "Background Selected"); if (showUseDefaults) { headers << i18n("Use Default Style"); } setHeaderLabels(headers); headerItem()->setIcon(1, QIcon::fromTheme(QStringLiteral("format-text-bold"))); headerItem()->setIcon(2, QIcon::fromTheme(QStringLiteral("format-text-italic"))); headerItem()->setIcon(3, QIcon::fromTheme(QStringLiteral("format-text-underline"))); headerItem()->setIcon(4, QIcon::fromTheme(QStringLiteral("format-text-strikethrough"))); // grap the bg color, selected color and default font const KColorScheme &colors(KTextEditor::EditorPrivate::self()->defaultColors().view()); normalcol = colors.foreground().color(); bgcol = KateRendererConfig::global()->backgroundColor(); selcol = KateRendererConfig::global()->selectionColor(); docfont = KateRendererConfig::global()->font(); QPalette pal = viewport()->palette(); pal.setColor(QPalette::Background, bgcol); viewport()->setPalette(pal); } QIcon brushIcon(const QColor &color) { QPixmap pm(16, 16); QRect all(0, 0, 15, 15); { QPainter p(&pm); p.fillRect(all, color); p.setPen(Qt::black); p.drawRect(all); } return QIcon(pm); } bool KateStyleTreeWidget::edit(const QModelIndex &index, EditTrigger trigger, QEvent *event) { if (index.column() == KateStyleTreeWidgetItem::Context) { return false; } KateStyleTreeWidgetItem *i = dynamic_cast(itemFromIndex(index)); if (!i) { return QTreeWidget::edit(index, trigger, event); } switch (trigger) { case QAbstractItemView::DoubleClicked: case QAbstractItemView::SelectedClicked: case QAbstractItemView::EditKeyPressed: i->changeProperty(index.column()); update(index); update(index.sibling(index.row(), KateStyleTreeWidgetItem::Context)); return false; default: return QTreeWidget::edit(index, trigger, event); } } void KateStyleTreeWidget::resizeColumns() { for (int i = 0; i < columnCount(); ++i) { resizeColumnToContents(i); } } void KateStyleTreeWidget::showEvent(QShowEvent *event) { QTreeWidget::showEvent(event); resizeColumns(); } void KateStyleTreeWidget::contextMenuEvent(QContextMenuEvent *event) { KateStyleTreeWidgetItem *i = dynamic_cast(itemAt(event->pos())); if (!i) { return; } QMenu m(this); KTextEditor::Attribute::Ptr currentStyle = i->style(); // the title is used, because the menu obscures the context name when // displayed on behalf of spacePressed(). QPainter p; p.setPen(Qt::black); const QIcon emptyColorIcon = brushIcon(viewport()->palette().base().color()); QIcon cl = brushIcon(i->style()->foreground().color()); QIcon scl = brushIcon(i->style()->selectedForeground().color()); QIcon bgcl = i->style()->hasProperty(QTextFormat::BackgroundBrush) ? brushIcon(i->style()->background().color()) : emptyColorIcon; QIcon sbgcl = i->style()->hasProperty(CustomProperties::SelectedBackground) ? brushIcon(i->style()->selectedBackground().color()) : emptyColorIcon; m.addSection(i->contextName()); QAction *a = m.addAction(i18n("&Bold"), this, SLOT(changeProperty())); a->setCheckable(true); a->setChecked(currentStyle->fontBold()); a->setData(KateStyleTreeWidgetItem::Bold); a = m.addAction(i18n("&Italic"), this, SLOT(changeProperty())); a->setCheckable(true); a->setChecked(currentStyle->fontItalic()); a->setData(KateStyleTreeWidgetItem::Italic); a = m.addAction(i18n("&Underline"), this, SLOT(changeProperty())); a->setCheckable(true); a->setChecked(currentStyle->fontUnderline()); a->setData(KateStyleTreeWidgetItem::Underline); a = m.addAction(i18n("S&trikeout"), this, SLOT(changeProperty())); a->setCheckable(true); a->setChecked(currentStyle->fontStrikeOut()); a->setData(KateStyleTreeWidgetItem::StrikeOut); m.addSeparator(); a = m.addAction(cl, i18n("Normal &Color..."), this, SLOT(changeProperty())); a->setData(KateStyleTreeWidgetItem::Foreground); a = m.addAction(scl, i18n("&Selected Color..."), this, SLOT(changeProperty())); a->setData(KateStyleTreeWidgetItem::SelectedForeground); a = m.addAction(bgcl, i18n("&Background Color..."), this, SLOT(changeProperty())); a->setData(KateStyleTreeWidgetItem::Background); a = m.addAction(sbgcl, i18n("S&elected Background Color..."), this, SLOT(changeProperty())); a->setData(KateStyleTreeWidgetItem::SelectedBackground); // defaulters m.addSeparator(); a = m.addAction(emptyColorIcon, i18n("Unset Normal Color"), this, SLOT(unsetColor())); a->setData(1); a = m.addAction(emptyColorIcon, i18n("Unset Selected Color"), this, SLOT(unsetColor())); a->setData(2); // unsetters KTextEditor::Attribute::Ptr style = i->style(); if (style->hasProperty(QTextFormat::BackgroundBrush)) { a = m.addAction(emptyColorIcon, i18n("Unset Background Color"), this, SLOT(unsetColor())); a->setData(3); } if (style->hasProperty(CustomProperties::SelectedBackground)) { a = m.addAction(emptyColorIcon, i18n("Unset Selected Background Color"), this, SLOT(unsetColor())); a->setData(4); } if (! i->isDefault() && ! i->defStyle()) { m.addSeparator(); a = m.addAction(i18n("Use &Default Style"), this, SLOT(changeProperty())); a->setCheckable(true); a->setChecked(i->defStyle()); a->setData(KateStyleTreeWidgetItem::UseDefaultStyle); } m.exec(event->globalPos()); } void KateStyleTreeWidget::changeProperty() { static_cast(currentItem())->changeProperty(static_cast(sender())->data().toInt()); } void KateStyleTreeWidget::unsetColor() { static_cast(currentItem())->unsetColor(static_cast(sender())->data().toInt()); } void KateStyleTreeWidget::updateGroupHeadings() { for (int i = 0; i < topLevelItemCount(); i++) { QTreeWidgetItem *currentTopLevelItem = topLevelItem(i); QTreeWidgetItem *firstChild = currentTopLevelItem->child(0); if (firstChild) { QColor foregroundColor = firstChild->data(KateStyleTreeWidgetItem::Foreground, Qt::DisplayRole).value(); QColor backgroundColor = firstChild->data(KateStyleTreeWidgetItem::Background, Qt::DisplayRole).value(); currentTopLevelItem->setForeground(KateStyleTreeWidgetItem::Context, foregroundColor); if (backgroundColor.isValid()) { currentTopLevelItem->setBackground(KateStyleTreeWidgetItem::Context, backgroundColor); } } } } void KateStyleTreeWidget::emitChanged() { updateGroupHeadings(); emit changed(); } void KateStyleTreeWidget::addItem(const QString &styleName, KTextEditor::Attribute::Ptr defaultstyle, KTextEditor::Attribute::Ptr data) { new KateStyleTreeWidgetItem(this, styleName, defaultstyle, data); } void KateStyleTreeWidget::addItem(QTreeWidgetItem *parent, const QString &styleName, KTextEditor::Attribute::Ptr defaultstyle, KTextEditor::Attribute::Ptr data) { new KateStyleTreeWidgetItem(parent, styleName, defaultstyle, data); updateGroupHeadings(); } //END //BEGIN KateStyleTreeWidgetItem KateStyleTreeDelegate::KateStyleTreeDelegate(KateStyleTreeWidget *widget) : m_widget(widget) { } QBrush KateStyleTreeDelegate::getBrushForColorColumn(const QModelIndex &index, int column) const { QModelIndex colorIndex = index.sibling(index.row(), column); QVariant displayData = colorIndex.model()->data(colorIndex); return displayData.value(); } void KateStyleTreeDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { static QSet columns; if (columns.isEmpty()) { columns << KateStyleTreeWidgetItem::Foreground << KateStyleTreeWidgetItem::SelectedForeground << KateStyleTreeWidgetItem::Background << KateStyleTreeWidgetItem::SelectedBackground; } if (index.column() == KateStyleTreeWidgetItem::Context) { QStyleOptionViewItem styleContextItem(option); QBrush brush = getBrushForColorColumn(index, KateStyleTreeWidgetItem::SelectedBackground); if (brush != QBrush()) { styleContextItem.palette.setBrush(QPalette::Highlight, brush); } brush = getBrushForColorColumn(index, KateStyleTreeWidgetItem::SelectedForeground); if (brush != QBrush()) { styleContextItem.palette.setBrush(QPalette::HighlightedText, brush); } return QStyledItemDelegate::paint(painter, styleContextItem, index); } QStyledItemDelegate::paint(painter, option, index); if (!columns.contains(index.column())) { return; } QVariant displayData = index.model()->data(index); if (displayData.type() != QVariant::Brush) { return; } QBrush brush = displayData.value(); QStyleOptionButton opt; opt.rect = option.rect; opt.palette = m_widget->palette(); bool set = brush != QBrush(); if (!set) { opt.text = i18nc("No text or background color set", "None set"); brush = Qt::white; } m_widget->style()->drawControl(QStyle::CE_PushButton, &opt, painter, m_widget); if (set) { painter->fillRect(m_widget->style()->subElementRect(QStyle::SE_PushButtonContents, &opt, m_widget), brush); } } KateStyleTreeWidgetItem::KateStyleTreeWidgetItem(QTreeWidgetItem *parent, const QString &stylename, KTextEditor::Attribute::Ptr defaultAttribute, KTextEditor::Attribute::Ptr actualAttribute) : QTreeWidgetItem(parent), currentStyle(nullptr), defaultStyle(defaultAttribute), actualStyle(actualAttribute) { initStyle(); setText(0, stylename); } KateStyleTreeWidgetItem::KateStyleTreeWidgetItem(QTreeWidget *parent, const QString &stylename, KTextEditor::Attribute::Ptr defaultAttribute, KTextEditor::Attribute::Ptr actualAttribute) : QTreeWidgetItem(parent), currentStyle(nullptr), defaultStyle(defaultAttribute), actualStyle(actualAttribute) { initStyle(); setText(0, stylename); } void KateStyleTreeWidgetItem::initStyle() { if (!actualStyle) { currentStyle = defaultStyle; } else { currentStyle = new KTextEditor::Attribute(*defaultStyle); if (actualStyle->hasAnyProperty()) { *currentStyle += *actualStyle; } } setFlags(Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); } static Qt::CheckState toCheckState(bool b) { return b ? Qt::Checked : Qt::Unchecked; } QVariant KateStyleTreeWidgetItem::data(int column, int role) const { if (column == Context) { switch (role) { case Qt::ForegroundRole: if (style()->hasProperty(QTextFormat::ForegroundBrush)) { return style()->foreground().color(); } break; case Qt::BackgroundRole: if (style()->hasProperty(QTextFormat::BackgroundBrush)) { return style()->background().color(); } break; case Qt::FontRole: return style()->font(); break; } } if (role == Qt::CheckStateRole) { switch (column) { case Bold: return toCheckState(style()->fontBold()); case Italic: return toCheckState(style()->fontItalic()); case Underline: return toCheckState(style()->fontUnderline()); case StrikeOut: return toCheckState(style()->fontStrikeOut()); case UseDefaultStyle: /* can't compare all attributes, currentStyle has always more than defaultStyle (e.g. the item's name), * so we just compare the important ones:*/ return toCheckState( currentStyle->foreground() == defaultStyle->foreground() && currentStyle->background() == defaultStyle->background() && currentStyle->selectedForeground() == defaultStyle->selectedForeground() && currentStyle->selectedBackground() == defaultStyle->selectedBackground() && currentStyle->fontBold() == defaultStyle->fontBold() && currentStyle->fontItalic() == defaultStyle->fontItalic() && currentStyle->fontUnderline() == defaultStyle->fontUnderline() && currentStyle->fontStrikeOut() == defaultStyle->fontStrikeOut()); } } if (role == Qt::DisplayRole) { switch (column) { case Foreground: return style()->foreground(); case SelectedForeground: return style()->selectedForeground(); case Background: return style()->background(); case SelectedBackground: return style()->selectedBackground(); } } return QTreeWidgetItem::data(column, role); } void KateStyleTreeWidgetItem::updateStyle() { // nothing there, not update it, will crash if (!actualStyle) { return; } if (currentStyle->hasProperty(QTextFormat::FontWeight)) { if (currentStyle->fontWeight() != actualStyle->fontWeight()) { actualStyle->setFontWeight(currentStyle->fontWeight()); } } else { actualStyle->clearProperty(QTextFormat::FontWeight); } if (currentStyle->hasProperty(QTextFormat::FontItalic)) { if (currentStyle->fontItalic() != actualStyle->fontItalic()) { actualStyle->setFontItalic(currentStyle->fontItalic()); } } else { actualStyle->clearProperty(QTextFormat::FontItalic); } if (currentStyle->hasProperty(QTextFormat::FontStrikeOut)) { if (currentStyle->fontStrikeOut() != actualStyle->fontStrikeOut()) { actualStyle->setFontStrikeOut(currentStyle->fontStrikeOut()); } } else { actualStyle->clearProperty(QTextFormat::FontStrikeOut); } if (currentStyle->hasProperty(QTextFormat::FontUnderline)) { if (currentStyle->fontUnderline() != actualStyle->fontUnderline()) { actualStyle->setFontUnderline(currentStyle->fontUnderline()); } } else { actualStyle->clearProperty(QTextFormat::FontUnderline); } if (currentStyle->hasProperty(CustomProperties::Outline)) { if (currentStyle->outline() != actualStyle->outline()) { actualStyle->setOutline(currentStyle->outline()); } } else { actualStyle->clearProperty(CustomProperties::Outline); } if (currentStyle->hasProperty(QTextFormat::ForegroundBrush)) { if (currentStyle->foreground() != actualStyle->foreground()) { actualStyle->setForeground(currentStyle->foreground()); } } else { actualStyle->clearProperty(QTextFormat::ForegroundBrush); } if (currentStyle->hasProperty(CustomProperties::SelectedForeground)) { if (currentStyle->selectedForeground() != actualStyle->selectedForeground()) { actualStyle->setSelectedForeground(currentStyle->selectedForeground()); } } else { actualStyle->clearProperty(CustomProperties::SelectedForeground); } if (currentStyle->hasProperty(QTextFormat::BackgroundBrush)) { if (currentStyle->background() != actualStyle->background()) { actualStyle->setBackground(currentStyle->background()); } } else { actualStyle->clearProperty(QTextFormat::BackgroundBrush); } if (currentStyle->hasProperty(CustomProperties::SelectedBackground)) { if (currentStyle->selectedBackground() != actualStyle->selectedBackground()) { actualStyle->setSelectedBackground(currentStyle->selectedBackground()); } } else { actualStyle->clearProperty(CustomProperties::SelectedBackground); } } /* only true for a hl mode item using its default style */ bool KateStyleTreeWidgetItem::defStyle() const { return actualStyle && actualStyle->properties() != defaultStyle->properties(); } /* true for default styles */ bool KateStyleTreeWidgetItem::isDefault() const { return actualStyle ? false : true; } void KateStyleTreeWidgetItem::changeProperty(int p) { if (p == Bold) { currentStyle->setFontBold(! currentStyle->fontBold()); } else if (p == Italic) { currentStyle->setFontItalic(! currentStyle->fontItalic()); } else if (p == Underline) { currentStyle->setFontUnderline(! currentStyle->fontUnderline()); } else if (p == StrikeOut) { currentStyle->setFontStrikeOut(! currentStyle->fontStrikeOut()); } else if (p == UseDefaultStyle) { toggleDefStyle(); } else { setColor(p); } updateStyle(); treeWidget()->emitChanged(); } void KateStyleTreeWidgetItem::toggleDefStyle() { if (*currentStyle == *defaultStyle) { KMessageBox::information(treeWidget(), i18n("\"Use Default Style\" will be automatically unset when you change any style properties."), i18n("Kate Styles"), QStringLiteral("Kate hl config use defaults")); } else { currentStyle = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute(*defaultStyle)); updateStyle(); QModelIndex currentIndex = treeWidget()->currentIndex(); while (currentIndex.isValid()) { treeWidget()->update(currentIndex); currentIndex = currentIndex.sibling(currentIndex.row(), currentIndex.column() - 1); } } } void KateStyleTreeWidgetItem::setColor(int column) { QColor c; // use this QColor d; // default color if (column == Foreground) { c = currentStyle->foreground().color(); d = defaultStyle->foreground().color(); } else if (column == SelectedForeground) { c = currentStyle->selectedForeground().color(); d = defaultStyle->selectedForeground().color(); } else if (column == Background) { c = currentStyle->background().color(); d = defaultStyle->background().color(); } else if (column == SelectedBackground) { c = currentStyle->selectedBackground().color(); d = defaultStyle->selectedBackground().color(); } if (!c.isValid()) { c = d; } const QColor selectedColor = QColorDialog::getColor(c, treeWidget()); if (!selectedColor.isValid()) { return; } // if set default, and the attrib is set in the default style use it // else if set default, unset it // else set the selected color switch (column) { case Foreground: currentStyle->setForeground(selectedColor); break; case SelectedForeground: currentStyle->setSelectedForeground(selectedColor); break; case Background: currentStyle->setBackground(selectedColor); break; case SelectedBackground: currentStyle->setSelectedBackground(selectedColor); break; } //FIXME //repaint(); } void KateStyleTreeWidgetItem::unsetColor(int colorId) { switch (colorId) { case 1: if (defaultStyle->hasProperty(QTextFormat::ForegroundBrush)) { currentStyle->setForeground(defaultStyle->foreground()); } else { currentStyle->clearProperty(QTextFormat::ForegroundBrush); } break; case 2: if (defaultStyle->hasProperty(CustomProperties::SelectedForeground)) { currentStyle->setSelectedForeground(defaultStyle->selectedForeground()); } else { currentStyle->clearProperty(CustomProperties::SelectedForeground); } break; case 3: if (currentStyle->hasProperty(QTextFormat::BackgroundBrush)) { currentStyle->clearProperty(QTextFormat::BackgroundBrush); } break; case 4: if (currentStyle->hasProperty(CustomProperties::SelectedBackground)) { currentStyle->clearProperty(CustomProperties::SelectedBackground); } break; } updateStyle(); treeWidget()->emitChanged(); } KateStyleTreeWidget *KateStyleTreeWidgetItem::treeWidget() const { return static_cast(QTreeWidgetItem::treeWidget()); } //END diff --git a/src/schema/katestyletreewidget.h b/src/schema/katestyletreewidget.h index a067993f..8cc78058 100644 --- a/src/schema/katestyletreewidget.h +++ b/src/schema/katestyletreewidget.h @@ -1,82 +1,82 @@ /* This file is part of the KDE libraries Copyright (C) 2001-2003 Christoph Cullmann Copyright (C) 2002, 2003 Anders Lund Copyright (C) 2005-2006 Hamish Rodda Copyright (C) 2007 Mirko Stocker * * 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 KATESTYLETREEWIDGET_H #define KATESTYLETREEWIDGET_H #include #include "kateextendedattribute.h" /** * QTreeWidget that automatically adds columns for KateStyleListItems and provides a * popup menu and a slot to edit a style using the keyboard. * Added by anders, jan 23 2002. */ class KateStyleTreeWidget : public QTreeWidget { Q_OBJECT friend class KateStyleListItem; public: explicit KateStyleTreeWidget(QWidget *parent = nullptr, bool showUseDefaults = false); void emitChanged(); void setBgCol(const QColor &c) { bgcol = c; } void setSelCol(const QColor &c) { selcol = c; } void setNormalCol(const QColor &c) { normalcol = c; } void addItem(QTreeWidgetItem *parent, const QString &styleName, KTextEditor::Attribute::Ptr defaultstyle, KTextEditor::Attribute::Ptr data = KTextEditor::Attribute::Ptr()); void addItem(const QString &styleName, KTextEditor::Attribute::Ptr defaultstyle, KTextEditor::Attribute::Ptr data = KTextEditor::Attribute::Ptr()); void resizeColumns(); Q_SIGNALS: void changed(); protected: - void contextMenuEvent(QContextMenuEvent *event) Q_DECL_OVERRIDE; - void showEvent(QShowEvent *event) Q_DECL_OVERRIDE; - bool edit(const QModelIndex &index, EditTrigger trigger, QEvent *event) Q_DECL_OVERRIDE; + void contextMenuEvent(QContextMenuEvent *event) override; + void showEvent(QShowEvent *event) override; + bool edit(const QModelIndex &index, EditTrigger trigger, QEvent *event) override; private Q_SLOTS: void changeProperty(); void unsetColor(); void updateGroupHeadings(); private: QColor bgcol, selcol, normalcol; QFont docfont; }; #endif diff --git a/src/script/data/indentation/cppstyle.js b/src/script/data/indentation/cppstyle.js index 8a4e3203..de83cb24 100644 --- a/src/script/data/indentation/cppstyle.js +++ b/src/script/data/indentation/cppstyle.js @@ -1,2798 +1,2817 @@ var katescript = { "name": "C++/boost Style", "author": "Alex Turbov ", "license": "LGPL", "revision": 32, "kate-version": "5.1", "required-syntax-style": "C++", "indent-languages": ["C++", "C++/Qt4", "ISO C++"], "priority": 10 }; // kate-script-header, must be at the start of the file without comments, pure json /** * This file is part of the Kate Project. * * 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. */ /** * \warning This indenter designed to be used with my C++ style! It consists * of mix of boost and STL styles + some my, unfortunately (still) * undocumented additions I've found useful after ~15 years of C++ coding. * So \b LOT of things here are \b HARDCODED and I \b DON'T care about other * styles!!! * * Ok, you've been warned :-) * * More info available here: http://zaufi.github.io/programming/2013/11/29/kate-cppstyle-indenter/ * * Some settings it assumes being in effect: * - indent-width 4; * - space-indent true; * - auto-brackets true; <-- TODO REALLY? * - replace-tabs true; * - replace-tabs-save true; * * \todo Better to check (assert) some of that modelines... */ // required katepart js libraries require ("range.js"); require ("string.js"); require ("utils.js") // specifies the characters which should trigger indent, beside the default '\n' // ':' is for `case'/`default' and class access specifiers: public, protected, private // '/' is for single line comments // ',' for parameter list // '<' and '>' is for templates // '#' is for preprocessor directives // ')' is for align dangling close bracket // ';' is for align `for' parts // ' ' is to add a '()' after `if', `while', `for', ... // TBD triggerCharacters = "{}()[]<>/:;,#\\?!|&/%.@ '\"=*^"; var debugMode = false; //BEGIN global variables and functions var gIndentWidth = 4; var gSameLineCommentStartAt = 60; ///< Position for same-line-comments (inline comments) var gBraceMap = { '(': ')', ')': '(' , '<': '>', '>': '<' , '{': '}', '}': '{' , '[': ']', ']': '[' }; //END global variables and functions /** * Try to (re)align (to 60th position) inline comment if present * \return \c true if comment line was moved above */ function alignInlineComment(line) { // Check is there any comment on the current line var sc = splitByComment(line); // Did we found smth and if so, make sure it is not a string or comment... if (sc.hasComment && !isStringOrComment(line, sc.before.length - 1)) { var rbefore = sc.before.rtrim(); var cursor = view.cursorPosition(); /// \attention Kate has a BUG: even if everything is Ok and no realign /// required, document gets modified anyway! So condition below /// designed to prevent document modification w/o real actions won't /// help anyway :-( Need to fix Kate before! if (rbefore.length < gSameLineCommentStartAt && sc.before.length != gSameLineCommentStartAt) { // Ok, test on the line is shorter than needed. // But what about current padding? if (sc.before.length < gSameLineCommentStartAt) // Need to add some padding document.insertText( line , sc.before.length , String().fill(' ', gSameLineCommentStartAt - sc.before.length) ); else // Need to remove a redundant padding document.removeText(line, gSameLineCommentStartAt, line, sc.before.length); // Keep cursor at the place we've found it before view.setCursorPosition(cursor); } else if (gSameLineCommentStartAt < rbefore.length) { // Move inline comment before the current line var startPos = document.firstColumn(line); var currentLineText = String().fill(' ', startPos) + "//" + sc.after.rtrim() + "\n"; document.removeText(line, rbefore.length, line, document.lineLength(line)); document.insertText(line, 0, currentLineText); // Keep cursor at the place we've found it before view.setCursorPosition(new Cursor(line + 1, cursor.column)); return true; } } return false; } function tryIndentRelativePrevNonCommentLine(line) { var current_line = line - 1; while (0 <= current_line && isStringOrComment(current_line, document.firstColumn(current_line))) --current_line; if (current_line == -1) return -2; var prevLineFirstChar = document.firstChar(current_line); var needHalfUnindent = !( prevLineFirstChar == ',' || prevLineFirstChar == ':' || prevLineFirstChar == '?' || prevLineFirstChar == '<' || prevLineFirstChar == '>' || prevLineFirstChar == '&' ); return document.firstColumn(current_line) - (needHalfUnindent ? 2 : 0); } /** * Try to keep same-line comment. * I.e. if \c ENTER was hit on a line w/ inline comment and before it, * try to keep it on a previous line... */ function tryToKeepInlineComment(line) { // Make sure that there is some text still present on a prev line // i.e. it was just splitted and same-line-comment must be moved back to it if (document.line(line - 1).trim().length == 0) return; // Check is there any comment on the current (non empty) line var sc = splitByComment(line); dbg("sc.hasComment="+sc.hasComment); dbg("sc.before.rtrim().length="+sc.before.rtrim().length); if (sc.hasComment) { // Ok, here is few cases possible when ENTER pressed in different positions // | |smth|was here; | |// comment // // If sc.before has some text, it means that cursor was in the middle of some // non-commented text, and part of it left on a prev line, so we have to move // the comment back to that line... if (sc.before.trim().length > 0) // Is there some text before comment? { var lastPos = document.lastColumn(line - 1); // Get last position of non space char @ prev line // Put the comment text to the prev line w/ padding document.insertText( line - 1 , lastPos + 1 , String().fill(' ', gSameLineCommentStartAt - lastPos - 1) + "//" + sc.after.rtrim() ); // Remove it from current line starting from current position // 'till the line end document.removeText(line, sc.before.rtrim().length, line, document.lineLength(line)); } else { // No text before comment. Need to remove possible spaces from prev line... var prevLine = line - 1; document.removeText( prevLine , document.lastColumn(prevLine) + 1 , prevLine , document.lineLength(prevLine) ); } } } /** * Return a current preprocessor indentation level * \note preprocessor indentation means how deep the current line * inside of \c #if directives. * \warning Negative result means that smth wrong w/ a source code */ function getPreprocessorLevelAt(line) { // Just follow towards start and count #if/#endif directives var currentLine = line; var result = 0; while (currentLine >= 0) { currentLine--; var currentLineText = document.line(currentLine); if (currentLineText.search(/^\s*#\s*(if|ifdef|ifndef)\s+.*$/) != -1) result++; else if (currentLineText.search(/^\s*#\s*endif.*$/) != -1) result--; } return result; } /** * Check if \c ENTER was hit between ()/{}/[]/<> * \todo Match closing brace forward, put content between * braces on a separate line and align a closing brace. */ function tryBraceSplit_ch(line) { var result = -1; // Get last char from previous line (opener) and a first from the current (closer) var firstCharPos = document.lastColumn(line - 1); var firstChar = document.charAt(line - 1, firstCharPos); var lastCharPos = document.firstColumn(line); var lastChar = document.charAt(line, lastCharPos); var isCurveBracketsMatched = (firstChar == '{' && lastChar == '}'); var isBracketsMatched = isCurveBracketsMatched || (firstChar == '[' && lastChar == ']') || (firstChar == '(' && lastChar == ')') || (firstChar == '<' && lastChar == '>') ; if (isBracketsMatched) { var currentIndentation = document.firstVirtualColumn(line - 1); result = currentIndentation + gIndentWidth; document.insertText(line, document.firstColumn(line), "\n"); document.indent(new Range(line + 1, 0, line + 1, 1), currentIndentation / gIndentWidth); // Add half-tab (2 spaces) if matched not a curve bracket or // open character isn't the only one on the line var isOpenCharTheOnlyOnLine = (document.firstColumn(line - 1) == firstCharPos); if (!(isCurveBracketsMatched || isOpenCharTheOnlyOnLine)) document.insertText(line + 1, document.firstColumn(line + 1), " "); view.setCursorPosition(line, result); } if (result != -1) { dbg("tryBraceSplit_ch result="+result); tryToKeepInlineComment(line); } return result; } /** * Even if counterpart brace not found (\sa \c tryBraceSplit_ch), align the current line * to one level deeper if last char on a previous line is one of open braces. * \code * foo(|blah); * // or * {| * // or * smth<|blah, blah> * // or * array[|idx] = blah; * \endcode */ function tryToAlignAfterOpenBrace_ch(line) { var result = -1; var pos = document.lastColumn(line - 1); var ch = document.charAt(line - 1, pos); if (ch == '(' || ch == '[') { result = document.firstColumn(line - 1) + gIndentWidth; } else if (ch == '{') { if (document.startsWith(line - 1, "namespace", true)) result = 0; else result = document.firstColumn(line - 1) + gIndentWidth; } else if (ch == '<') { // Does it looks like 'operator<<'? if (document.charAt(line - 1, pos - 1) != '<') result = document.firstColumn(line - 1) + gIndentWidth; else result = document.firstColumn(line - 1) + (gIndentWidth / 2); } if (result != -1) { tryToKeepInlineComment(line); dbg("tryToAlignOpenBrace_ch result="+result); } return result; } function tryToAlignBeforeCloseBrace_ch(line) { var result = -1; var pos = document.firstColumn(line); var ch = document.charAt(line, pos); if (ch == '}' || ch == ')' || ch == ']') { var openBracePos = document.anchor(line, pos, ch); dbg("Found open brace @", openBracePos); if (openBracePos.isValid()) result = document.firstColumn(openBracePos.line) + (ch == '}' ? 0 : (gIndentWidth / 2)); } else if (ch == '>') { // TBD } if (result != -1) { tryToKeepInlineComment(line); dbg("tryToAlignBeforeCloseBrace_ch result="+result); } return result; } function tryToAlignBeforeComma_ch(line) { var result = -1; var pos = document.firstColumn(line); var ch = document.charAt(line, pos); if (line > 0 && (ch == ',' || ch == ';')) { var openBracePos = document.anchor(line, pos, '('); if (!openBracePos.isValid()) openBracePos = document.anchor(line, pos, '['); if (openBracePos.isValid()) result = document.firstColumn(openBracePos.line) + 2; } if (result != -1) { tryToKeepInlineComment(line); dbg("tryToAlignBeforeComma_ch result="+result); } return result; } /// Check if a multiline comment introduced on a previous line function tryMultilineCommentStart_ch(line) { var result = -1; // Check if multiline comment was started on the line // and ENTER wan't pressed right after a /*C-style comment*/ if (document.startsWith(line - 1, "/*", true) && !document.endsWith(line - 1, "*/", true)) { var filler = String().fill(' ', document.firstVirtualColumn(line - 1) + 1); var padding = filler + "* "; // If next line (if present) doesn't looks like a continue of the current comment, // then append a comment closer also... if ((line + 1) < document.lines()) { // Maybe user wants to extend a multiline C-style/Doxygen comment // by pressing ENTER at start of it? if (!document.startsWith(line + 1, "*", true)) { // ... doesn't looks like a multiline comment padding += "\n" + filler; // Maybe user just splits a C-style comment? if (!document.startsWith(line, "*/", true)) padding += document.endsWith(line, "*/", true) ? "* " : "*/"; else document.removeText(line, 0, line, document.firstColumn(line)) } // else, no need to append a closing */ } else // There is no a next line... { padding += "\n" + filler; if (!document.startsWith(line, "*/", true)) padding += document.endsWith(line, "*/", true) ? "* " : "*/"; else document.removeText(line, 0, line, document.firstColumn(line)) } document.insertText(line, 0, padding); view.setCursorPosition(line, filler.length + 2); result = -2; } if (result != -1) { dbg("tryMultilineCommentStart_ch result="+result); } return result; } /// Check if \c ENTER was hit inside or at last line of a multiline comment function tryMultilineCommentCont_ch(line) { var result = -1; // Check if multiline comment continued on the line: // 0) it starts w/ a start // 1) and followed by a space (i.e. it doesn't looks like a dereference) or nothing var firstCharPos = document.firstColumn(line - 1); var prevLineFirstChar = document.charAt(line - 1, firstCharPos); var prevLineSecondChar = document.charAt(line - 1, firstCharPos + 1); if (prevLineFirstChar == '*' && (prevLineSecondChar == ' ' || prevLineSecondChar == -1)) { if (document.charAt(line - 1, firstCharPos + 1) == '/') // ENTER pressed after multiline comment: unindent 1 space! result = firstCharPos - 1; else { // Ok, ENTER pressed inside of the multiline comment: // just append one more line... var filler = String().fill(' ', document.firstColumn(line - 1)); // Try to continue a C-style comment document.insertText(line, 0, filler + "* "); result = filler.length; } } if (result != -1) { dbg("tryMultilineCommentCont_ch result="+result); } return result; } function tryAfterCloseMultilineComment_ch(line) { var result = -1; if (document.startsWith(line - 1, "*/", true)) { result = document.firstColumn(line - 1) - 1; } if (result != -1) { dbg("tryAfterCloseMultilineComment_ch result="+result); } return result; } /** * Check if a current line has a text after cursor position * and a previous one has a comment, then append a "// " * before cursor and realign if latter was inline comment... */ function trySplitComment_ch(line) { var result = -1; if (document.lastColumn(line) != -1) { // Ok, current line has some text after... // NOTE There is should be at least one space between // the text and the comment var match = /^(.*\s)(\/\/)(.*)$/.exec(document.line(line - 1)); if (match != null && 0 < match[3].trim().length) // If matched and there is some text in a comment { if (0 < match[1].trim().length) // Is there some text before the comment? { // Align comment to gSameLineCommentStartAt result = gSameLineCommentStartAt; } else { result = match[1].length; } var leadMatch = /^([^\s]*\s+).*$/.exec(match[3]); var lead = ""; if (leadMatch != null) lead = leadMatch[1]; else lead = " "; document.insertText(line, 0, "//" + lead); } } if (result != -1) { dbg("trySplitComment_ch result="+result); } return result; } /** * \brief Indent a next line after some keywords. * * Incrase indent after the following keywords: * - \c if * - \c else * - \c for * - \c while * - \c do * - \c case * - \c default * - \c return * - and access modifiers \c public, \c protected and \c private */ function tryIndentAfterSomeKeywords_ch(line) { var result = -1; // Check if ENTER was pressed after some keywords... var sr = splitByComment(line - 1); var prevString = sr.before; dbg("tryIndentAfterSomeKeywords_ch prevString='"+prevString+"'"); var r = /^(\s*)((if|for|while)\s*\(|\bdo\b|\breturn\b|(((public|protected|private)(\s+(slots|Q_SLOTS))?)|default|case\s+.*)\s*:).*$/ .exec(prevString); if (r != null) { dbg("r=",r); if (!r[2].startsWith("return") || !prevString.rtrim().endsWith(';')) result = r[1].length + gIndentWidth; } else { r = /^\s*\belse\b.*$/.exec(prevString) if (r != null) { var prevPrevString = stripComment(line - 2); dbg("tryIndentAfterSomeKeywords_ch prevPrevString='"+prevPrevString+"'"); if (prevPrevString.endsWith('}')) result = document.firstColumn(line - 2); else if (prevPrevString.match(/^\s*[\])>]/)) result = document.firstColumn(line - 2) - gIndentWidth - (gIndentWidth / 2); else result = document.firstColumn(line - 2) - gIndentWidth; // Realign 'else' statement if needed var pp = document.firstColumn(line - 1); if (pp < result) document.insertText(line - 1, 0, String().fill(' ', result - pp)); else if (result < pp) document.removeText(line - 1, 0, line - 1, pp - result); result += gIndentWidth; } } if (result != -1) { tryToKeepInlineComment(line); dbg("tryIndentAfterSomeKeywords_ch result="+result); } return result; } /** * Try to indent a line right after a dangling semicolon * (possible w/ leading close braces and comment after) * \code * foo( * blah * );| * \endcode */ function tryAfterDanglingSemicolon_ch(line) { var result = -1; var prevString = document.line(line - 1); var r = /^(\s*)(([\)\]}]?\s*)*([\)\]]\s*))?;/.exec(prevString); if (r != null) { result = Math.floor(r[1].length / 4) * 4; } else { // Does it looks like a template tail? // i.e. smth like this: // typedef boost::mpl::blah< // params // > type;| r = /^(\s*)([>]+).*;/.exec(prevString); if (r != null) result = Math.floor(r[1].length / 4) * 4; } if (result != -1) { tryToKeepInlineComment(line); dbg("tryDanglingSemicolon_ch result="+result); } return result; } /** * Check if \c ENTER pressed after equal sign * \code * blah = * |blah * \endcode */ function tryAfterEqualChar_ch(line) { var result = -1; var pos = document.lastColumn(line - 1); if (document.charAt(line - 1, pos) == '=') result = document.firstColumn(line - 1) + gIndentWidth; if (result != -1) { tryToKeepInlineComment(line); dbg("tryAfterEqualChar_ch result="+result); } return result; } /// Check if \c ENTER hits after \c #define w/ a backslash function tryMacroDefinition_ch(line) { var result = -1; var prevString = document.line(line - 1); if (prevString.search(/^\s*#\s*define\s+.*\\$/) != -1) result = gIndentWidth; if (result != -1) { dbg("tryMacroDefinition_ch result="+result); } return result; } /** * Do not incrase indent if ENTER pressed before access * specifier (i.e. public/private/protected) */ function tryBeforeAccessSpecifier_ch(line) { var result = -1; if (document.line(line).match(/(public|protected|private):/)) { var openPos = document.anchor(line, 0, '{'); if (openPos.isValid()) result = document.firstColumn(openPos.line); } if (result != -1) { tryToKeepInlineComment(line); dbg("tryBeforeAccessSpecifier_ch result="+result); } return result; } /** * Try to align a line w/ a leading (word) delimiter symbol * (i.e. not an identifier and a brace) */ function tryBeforeDanglingDelimiter_ch(line) { var result = -1; var halfTabNeeded = // current line do not starts w/ a comment !document.line(line).ltrim().startsWith("//") // if a previous line starts w/ an identifier && (document.line(line - 1).search(/^\s*[A-Za-z_][A-Za-z0-9_]*/) != -1) // but the current one starts w/ a delimiter (which is looks like operator) && (document.line(line).search(/^\s*[,%&<=:\|\-\?\/\+\*\.]/) != -1) ; // check if we r at function call or array index var insideBraces = document.anchor(line, document.firstColumn(line), '(').isValid() || document.anchor(line, document.firstColumn(line), '[').isValid() ; if (halfTabNeeded) result = document.firstVirtualColumn(line - 1) + (insideBraces ? -2 : 2); if (result != -1) { tryToKeepInlineComment(line); dbg("tryBeforeDanglingDelimiter_ch result="+result); } return result; } function tryPreprocessor_ch(line) { var result = -1; if (document.firstChar(line) == '#') { result = 0; var text = document.line(line); // Get current depth level var currentLevel = getPreprocessorLevelAt(line); if (currentLevel > 0) { // How much spaces we have after hash? var spacesCnt = 0; var column = document.firstColumn(line) + 1; var i = column; for (; i < text.length; i++) { if (text[i] != ' ') break; spacesCnt++; } var wordAfterHash = document.wordAt(line, i); dbg("wordAfterHash='"+wordAfterHash+"'"); if (wordAfterHash[0] == '#') wordAfterHash = wordAfterHash.substring(1, wordAfterHash.length); if (wordAfterHash == "else" || wordAfterHash == "elif" || wordAfterHash == "endif") currentLevel--; var paddingLen = (currentLevel == 0) ? 0 : (currentLevel - 1) * 2 + 1; if (spacesCnt < paddingLen) { var padding = String().fill(' ', paddingLen - spacesCnt); document.insertText(line, column, padding); } else if (paddingLen < spacesCnt) { document.removeText(line, column, line, column + spacesCnt - paddingLen); } } } if (result != -1) { dbg("tryPreprocessor_ch result="+result); } return result; } /** * Check if \c ENTER was pressed on a start of line and * after a block comment. */ function tryAfterBlockComment_ch(line) { var result = -1; if (0 < line) { var prev_non_empty_line = document.prevNonEmptyLine(line - 1); if (prev_non_empty_line != -1 && document.line(prev_non_empty_line).trim().startsWith("*/")) { var p = document.firstColumn(prev_non_empty_line); if ((p % gIndentWidth) != 0) result = Math.floor(p / gIndentWidth) * gIndentWidth; } } if (result != -1) { dbg("tryAfterBlockComment_ch result="+result); } return result; } /** * Check if \c ENTER was pressed after \c break or \c continue statements * and if so, unindent the current line. */ function tryAfterBreakContinue_ch(line) { var result = -1; var currentLineText = document.line(line - 1).ltrim(); var should_proceed = currentLineText.startsWith("break;") || currentLineText.startsWith("continue;") if (should_proceed) { result = document.firstColumn(line - 1) - gIndentWidth; } if (result != -1) { dbg("tryAfterBreakContinue_ch result="+result); } return result; } /// \internal function getStringAligmentAfterSplit(line) { var prevLineFirstChar = document.firstChar(line - 1); var halfIndent = prevLineFirstChar == ',' || prevLineFirstChar == ':' || prevLineFirstChar == '?' || prevLineFirstChar == '<' || prevLineFirstChar == '>' || prevLineFirstChar == '&' ; return document.firstColumn(line - 1) + ( prevLineFirstChar != '"' ? (halfIndent ? (gIndentWidth / 2) : gIndentWidth) : 0 ); } /** * Handle the case when \c ENTER has pressed in the middle of a string. * Find a string begin (a quote char) and analyze if it is a C++11 raw * string literal. If it is not, add a "closing" quote to a previous line * and to the current one. Align a 2nd part (the moved down one) of a string * according a previous line. If latter is a pure string, then give the same * indentation level, otherwise incrase it to one \c TAB. * * Here is few cases possible: * - \c ENTER has pressed in a line auto some = ""|, so a new * line just have an empty string or some text which is doesn't matter now; * - \c ENTER has pressed in a line auto some = "possible some text here| and here", * then a new line will have and here" text * * In both cases attribute at (line-1, lastColumn-1) will be \c String */ function trySplitString_ch(line) { var result = -1; var column = document.lastColumn(line - 1); if (isComment(line - 1, column)) return result; // Do nothing for comments // Check if last char on a prev line has string attribute var lastColumnIsString = isString(line - 1, column); var firstColumnIsString = isString(line, 0); var firstChar = (document.charAt(line, 0) == '"'); if (!lastColumnIsString) // If it is not, { // TODO TBD if (firstColumnIsString && firstChar == '"') result = getStringAligmentAfterSplit(line); return result; // then nothing to do... } var lastChar = (document.charAt(line - 1, column) == '"'); var prevLastColumnIsString = isString(line - 1, column - 1); var prevLastChar = (document.charAt(line - 1, column - 1) == '"'); dbg("trySplitString_ch: lastColumnIsString="+lastColumnIsString); dbg("trySplitString_ch: lastChar="+lastChar); dbg("trySplitString_ch: prevLastColumnIsString="+prevLastColumnIsString); dbg("trySplitString_ch: prevLastChar="+prevLastChar); dbg("trySplitString_ch: isString(line,0)="+firstColumnIsString); dbg("trySplitString_ch: firstChar="+firstChar); var startOfString = firstColumnIsString && firstChar; var endOfString = !(firstColumnIsString || firstChar); var should_proceed = !lastChar && prevLastColumnIsString && (endOfString || !prevLastChar && startOfString) || lastChar && !prevLastColumnIsString && !prevLastChar && (endOfString || startOfString) ; dbg("trySplitString_ch: ------ should_proceed="+should_proceed); if (should_proceed) { // Add closing quote to the previous line document.insertText(line - 1, document.lineLength(line - 1), '"'); // and open quote to the current one document.insertText(line, 0, '"'); // NOTE If AutoBrace plugin is used, it won't add a quote // char, if cursor positioned right before another quote char // (which was moved from a line above in this case)... // So, lets force it! if (startOfString && document.charAt(line, 1) != '"') { document.insertText(line, 1, '"'); // Add one more! view.setCursorPosition(line, 1); // Step back inside of string } result = getStringAligmentAfterSplit(line); } if (result != -1) { dbg("trySplitString_ch result="+result); tryToKeepInlineComment(line); } return result; } /** * Here is few cases possible: * \code * // set some var to lambda function * auto some = [foo](bar)| * * // lambda as a parameter (possible the first one, * // i.e. w/o a leading comma) * std::foreach( * begin(container) * , end(container) * , [](const value_type& v)| * ); * \endcode */ function tryAfterLambda_ch(line) { var result = -1; var column = document.lastColumn(line - 1); if (isComment(line - 1, column)) return result; // Do nothing for comments var sr = splitByComment(line - 1); if (sr.before.match(/\[[^\]]*\]\([^{]*\)[^{}]*$/)) { var align = document.firstColumn(line - 1); var before = sr.before.ltrim(); if (before.startsWith(',')) align += 2; var padding = String().fill(' ', align); var tail = before.startsWith('auto ') ? "};" : "}"; document.insertText( line , 0 , padding + "{\n" + padding + String().fill(' ', gIndentWidth) + "\n" + padding + tail ); view.setCursorPosition(line + 1, align + gIndentWidth); result = -2; } if (result != -1) { dbg("tryAfterLambda_ch result="+result); } return result; } /// Wrap \c tryToKeepInlineComment as \e caret-handler function tryToKeepInlineComment_ch(line) { tryToKeepInlineComment(line); return -1; } /** * \brief Handle \c ENTER key */ function caretPressed(cursor) { var result = -1; var line = cursor.line; // Dunno what to do if previous line isn't available if (line - 1 < 0) return result; // Nothing (dunno) to do if no previous line... // Register all indent functions var handlers = [ tryBraceSplit_ch // Handle ENTER between braces , tryMultilineCommentStart_ch , tryMultilineCommentCont_ch , tryAfterCloseMultilineComment_ch , trySplitComment_ch , tryToAlignAfterOpenBrace_ch // Handle {,[,(,< on a previous line , tryToAlignBeforeCloseBrace_ch // Handle },],),> on a current line before cursor , tryToAlignBeforeComma_ch // Handle ENTER pressed before comma or semicolon , tryIndentAfterSomeKeywords_ch // NOTE It must follow after trySplitComment_ch! , tryAfterDanglingSemicolon_ch , tryMacroDefinition_ch , tryBeforeDanglingDelimiter_ch , tryBeforeAccessSpecifier_ch , tryAfterEqualChar_ch , tryPreprocessor_ch , tryAfterBlockComment_ch , tryAfterBreakContinue_ch , trySplitString_ch // Handle ENTER pressed in the middle of a string , tryAfterLambda_ch // Handle ENTER after lambda prototype and before body , tryToKeepInlineComment_ch // NOTE This must be a last checker! ]; // Apply all all functions until result gets changed for ( var i = 0 ; i < handlers.length && result == -1 ; result = handlers[i++](line) ); return result; } /** * \brief Handle \c '/' key pressed * * Check if is it start of a comment. Here is few cases possible: * \li very first \c '/' -- do nothing * \li just entered \c '/' is a second in a sequence. If no text before or some present after, * do nothing, otherwise align a \e same-line-comment to \c gSameLineCommentStartAt * position. * \li just entered \c '/' is a 3rd in a sequence. If there is some text before and no after, * it looks like inlined doxygen comment, so append \c '<' char after. Do nothing otherwise. * \li if there is a '// ' string right before just entered \c '/', form a * doxygen comment '///' or '///<' depending on presence of some text * on a line before the comment. * * \todo Due the BUG #316809 in a current version of Kate, this code doesn't work as expected! * It always returns a "NormalText"! * \code * var cm = document.attributeName(cursor); * if (cm.indexOf("String") != -1) * return; * \endcode * * \bug This code doesn't work properly in the following case: * \code * std::string bug = "some text// * \endcode * */ function trySameLineComment(cursor) { var line = cursor.line; var column = cursor.column; // First of all check that we are not withing a string if (document.isString(line, column)) return; var sc = splitByComment(line); if (sc.hasComment) // Is there any comment on a line? { // Make sure we r not in a comment already -- it can be a multiline one... var fc = document.firstColumn(line); var text = document.line(line).ltrim(); var nothing_to_do = (fc < (column - 1)) && document.isComment(line, fc); // Also check that line has smth that needs to be "fixed"... if (nothing_to_do && text != "//" && text != "// /" && text != "///" && text != "/// /") return; // If no text after the comment and it still not aligned var text_len = sc.before.rtrim().length; if (text_len != 0 && sc.after.length == 0 && text_len < gSameLineCommentStartAt) { // Align it! document.insertText( line , column - 2 , String().fill(' ', gSameLineCommentStartAt - sc.before.length) ); document.insertText(line, gSameLineCommentStartAt + 2, ' '); } // If text in a comment equals to '/' or ' /' -- it looks like a 3rd '/' pressed else if (sc.after == " /" || sc.after == "/") { // Form a Doxygen comment! document.removeText(line, column - sc.after.length, line, column); document.insertText(line, column - sc.after.length, text_len != 0 ? "/< " : "/ "); } // If right trimmed text in a comment equals to '/' -- it seems user moves cursor // one char left (through space) to add one more '/' else if (sc.after.rtrim() == "/") { // Form a Doxygen comment! document.removeText(line, column, line, column + sc.after.length); document.insertText(line, column, text_len != 0 ? "< " : " "); } else if (sc.after == "/ /") { // Looks like user wants to "draw a fence" w/ '/'s document.removeText(line, column - 2, line, column - 1); } else if (text_len == 0 && sc.after.length == 0) { document.insertText(line, column, ' '); } } } /** * \brief Maybe '>' needs to be added? * * Here is a few cases possible: * \li user entered "template > * \li user entered smth like std::map> * \li user wants to output smth to C++ I/O stream by typing >> * possible at the line start, so it must be half indented * \li shortcut: some(<|) transformed into some()<| * * But, do not add '>' if there some text after cursor. */ function tryTemplate(cursor) { var line = cursor.line; var column = cursor.column; var result = -2; if (isStringOrComment(line, column)) return result; // Do nothing for comments and strings // Check for 'template' keyword at line start var currentString = document.line(line); var prevWord = document.wordAt(line, column - 1); dbg("tryTemplate: prevWord='"+prevWord+"'"); dbg("tryTemplate: prevWord.match="+prevWord.match(/\b[A-Za-z_][A-Za-z0-9_]*/)); // Add a closing angle bracket if a prev word is not a 'operator' // and it looks like an identifier or current line starts w/ 'template' keyword var isCloseAngleBracketNeeded = (prevWord != "operator") && (currentString.match(/^\s*template\s*<$/) || prevWord.match(/\b[A-Za-z_][A-Za-z0-9_]*/)) && (column == document.lineLength(line) || document.charAt(cursor).match(/\W/)) ; if (isCloseAngleBracketNeeded) { document.insertText(cursor, ">"); view.setCursorPosition(cursor); } else if (justEnteredCharIsFirstOnLine(line, column, '<')) { result = tryIndentRelativePrevNonCommentLine(line); } // Add a space after 2nd '<' if a word before is not a 'operator' else if (document.charAt(line, column - 2) == '<') { if (document.wordAt(line, column - 3) != "operator") { // Looks like case 3... // 0) try to remove '>' if user typed 'some<' before // (and closing '>' was added by tryTemplate) if (column < document.lineLength(line) && document.charAt(line, column) == '>') { document.removeText(line, column, line, column + 1); addCharOrJumpOverIt(line, column - 2, ' '); view.setCursorPosition(line, column + 1); column = column + 1; } // add a space after operator<< document.insertText(line, column, " "); } else { document.insertText(line, column, "()"); view.setCursorPosition(line, column + 1); } } else { cursor = tryJumpOverParenthesis(cursor); // Try to jump out of parenthesis tryAddSpaceAfterClosedBracketOrQuote(cursor); } return result; } /** * This function called for some characters and trying to do the following: * if the cursor (right after a trigger character is entered) is positioned withing * a parenthesis, move the entered character out of parenthesis. * * For example: * \code * auto a = two_params_func(get_first(,|)) * // ... transformed into * auto a = two_params_func(get_first(),|) * \endcode * * because entering comma right after ( definitely incorrect, but * we can help the user (programmer) to avoid 3 key presses ;-) * (RightArrow, ',', space) * * except comma here are other "impossible" characters: * \c ., \c ?, \c :, \c %, \c |, \c /, \c =, \c <, \c >, \c ], \c } * * But \c ; will be handled separately to be able to jump over all closing \c ). * * \sa \c trySemicolon() * * \note This valid if we r not inside a comment or a string literal, * and the char out of the parenthesis is not the same as just entered ;-) * * \param cursor initial cursor position * \param es edit session instance * \return new (possible modified) cursor position */ function tryJumpOverParenthesis(cursor) { var line = cursor.line; var column = cursor.column; if (2 < column && isStringOrComment(line, column)) return cursor; // Do nothing for comments of string literals // Check that we r inside of parenthesis and some symbol between var pc = document.charAt(line, column - 2); var cc = document.charAt(cursor); if ((pc == '(' && cc == ')') || (pc == '{' && cc == '}')) { var c = document.charAt(line, column - 1); switch (c) { case '.': if (pc == '(' && cc == ')') { // Make sure this is not a `catch (...)` if (document.startsWith(line, "catch (.)", true)) { document.insertText(line, column, ".."); view.setCursorPosition(line, column + 3); break; } } case ',': case '?': case ':': case '%': case '^': case '|': case '/': // TODO ORLY? case '=': case '<': case '>': case '}': case ')': case ']': // NOTE '[' could be a part of lambda { // Ok, move character out of parenthesis document.removeText(line, column - 1, line, column); // Check is a character after the closing brace the same as just entered one addCharOrJumpOverIt(line, column, c); return view.cursorPosition(); } default: break; } } return cursor; } /** * Handle the case when some character was entered after a some closing bracket. * Here is few close brackets possible: * \li \c ) -- ordinal function call * \li \c } -- C++11 constructor call * \li \c ] -- array access * \li \c " -- end of a string literal * \li \c ' -- and of a char literal * * This function try to add a space between a closing quote/bracket and operator char. * * \note This valid if we r not inside a comment or a string literal. */ function tryAddSpaceAfterClosedBracketOrQuote(cursor) { var line = cursor.line; var column = cursor.column; if (isStringOrComment(line, column - 1)) return cursor; // Do nothing for comments of string literals // Check if we have a closing bracket before a last entered char var b = document.charAt(line, column - 2); if (!(b == ']' || b == '}' || b == ')' || b == '"' || b == "'")) return cursor; // Ok, lets check what we've got as a last char var c = document.charAt(line, column - 1); dbg("tryAddSpaceAfterClosedBracketOrQuote: c='"+c+"', @"+new Cursor(line, column-1)); switch (c) { case '*': case '/': case '%': case '&': case '|': case '=': case '^': case '?': case ':': case '<': document.insertText(line, column - 1, " "); view.setCursorPosition(line, column + 1); return view.cursorPosition(); case '>': // Close angle bracket may be a part of template instantiation // w/ some function type parameter... Otherwise, add a space after. if (b != ')') { document.insertText(line, column - 1, " "); view.setCursorPosition(line, column + 1); return view.cursorPosition(); } break; default: break; } return cursor; } /** * \brief Try to align parameters list * * If (just entered) comma is a first symbol on a line, * just move it on a half-tab left relative to a previous line * (if latter doesn't starts w/ comma or ':'). * Do nothing otherwise. A space would be added after it anyway. */ function tryComma(cursor) { var result = -2; var line = cursor.line; var column = cursor.column; // Check is comma a very first character on a line... if (justEnteredCharIsFirstOnLine(line, column, ',')) { result = tryIndentRelativePrevNonCommentLine(line); } else { // Try to stick a comma to a previous non-space char var lastWordPos = document.text(line, 0, line, column - 1).rtrim().length; if (lastWordPos < column) { document.removeText(line, column - 1, line, column); document.insertText(line, lastWordPos, ","); cursor = new Cursor(line, lastWordPos + 1); view.setCursorPosition(cursor); } } cursor = tryJumpOverParenthesis(cursor); // Try to jump out of parenthesis addCharOrJumpOverIt(cursor.line, cursor.column, ' '); return result; } /** * \brief Move towards a document start and look for control flow keywords * * \note Keyword must be at the line start * * \return found line's indent, otherwise \c -2 if nothing found */ function tryBreakContinue(line, is_break) { var result = -2; // Ok, look backward and find a loop/switch statement for (; 0 <= line; --line) { var text = document.line(line).ltrim(); var is_loop_or_switch = text.startsWith("for ") || text.startsWith("do ") || text.startsWith("while ") || text.startsWith("if ") || text.startsWith("else if ") ; if (is_break) is_loop_or_switch = is_loop_or_switch || text.startsWith("case ") || text.startsWith("default:") ; if (is_loop_or_switch) break; } if (line != -1) // Found smth? result = document.firstColumn(line) + gIndentWidth; return result; } /** * \brief Handle \c ; character. * * Here is few cases possible (handled): * \li semicolon is a first char on a line -- then, it looks like \c for statement * splitted accross the lines * \li semicolon entered after some keywords: \c break or \c continue, then we * need to align this line taking in account a previous one * \li and finally here is a trick: when auto brackets extension enabled, and user types * a function call like this: * \code * auto var = some_call(arg1, arg2|) * \endcode * (\c '|' shows a cursor position). Note there is no final semicolon in this expression, * cuz pressing '(' leads to the following snippet: some_call(|), so to * add a semicolon you have to move cursor out of parenthesis. The trick is to allow to press * ';' at position shown in the code snippet, so indenter will transform it into this: * \code * auto var = some_call(arg1, arg2);| * \endcode * same works even there is no arguments... * * All the time, when simicolon is not a first non-space symbol (and not a part of a comment * or string) it will be stiked to the last non-space character on the line. */ function trySemicolon(cursor) { var result = -2; var line = cursor.line; var column = cursor.column; if (isStringOrComment(line, column)) return result; // Do nothing for comments and strings // If ';' is a first char on a line? if (justEnteredCharIsFirstOnLine(line, column, ';')) { // Check if we are inside a `for' statement var openBracePos = document.anchor(line, column, '('); if (openBracePos.isValid()) { // Add a half-tab relative '(' result = document.firstColumn(openBracePos.line) + 2; document.insertText(cursor, " "); } } else { // Stick ';' to the last "word" var lcsc = document.prevNonSpaceColumn(line, column - 2); if (2 < column && lcsc < (column - 2)) { document.removeText(line, column - 1, line, column); if (document.charAt(line, lcsc) != ';') { document.insertText(line, lcsc + 1, ";"); view.setCursorPosition(line, lcsc + 2); } else view.setCursorPosition(line, lcsc + 1); cursor = view.cursorPosition(); column = cursor.column; } var text = document.line(line).ltrim(); var is_break = text.startsWith("break;"); var should_proceed = is_break || text.startsWith("continue;") if (should_proceed) { result = tryBreakContinue(line - 1, is_break); if (result == -2) result = -1; } // Make sure we r not inside of `for' statement /// \todo Make sure cursor is really inside of \c for and /// not smth like this: for (blah; blah; blah) some(arg,;|) else if (!text.startsWith("for ")) { // Check if next character(s) is ')' and nothing after should_proceed = true; var lineLength = document.lineLength(line); for (var i = column; i < lineLength; ++i) { var c = document.charAt(line, i); if (!(c == ')' || c == ']')) { should_proceed = false; break; } } // Ok, lets move ';' out of "a(b(c(;)))" of any level... if (should_proceed) { // Remove ';' from column - 1 document.removeText(line, column - 1, line, column); // Append ';' to the end of line document.insertText(line, lineLength - 1, ";"); view.setCursorPosition(line, lineLength); cursor = view.cursorPosition(); column = cursor.column; } } // In C++ there is no need to have more than one semicolon. // So remove a redundant one! if (document.charAt(line, column - 2) == ';') { // Remove just entered ';' document.removeText(line, column - 1, line, column); } } return result; } /** * Handle possible dangling operators (moved from a previous line) * * \c ?, \c |, \c ^, \c %, \c . * * Add spaces around ternary operator. */ function tryOperator(cursor, ch) { var result = -2; var line = cursor.line; var column = cursor.column; if (isStringOrComment(line, column)) return result; // Do nothing for comments and strings var halfTabNeeded = justEnteredCharIsFirstOnLine(line, column, ch) && document.line(line - 1).search(/^\s*[A-Za-z_][A-Za-z0-9_]*/) != -1 ; dbg("tryOperator: halfTabNeeded =", halfTabNeeded); if (halfTabNeeded) { // check if we r at function call or array index var insideBraces = document.anchor(line, document.firstColumn(line), '(').isValid() || document.anchor(line, document.firstColumn(line), '[').isValid() || document.anchor(line, document.firstColumn(line), '{').isValid() ; dbg("tryOperator: insideBraces =",insideBraces); result = document.firstColumn(line - 1) + (insideBraces && ch != '.' ? -2 : 2); } var prev_pos = cursor; cursor = tryJumpOverParenthesis(cursor); // Try to jump out of parenthesis cursor = tryAddSpaceAfterClosedBracketOrQuote(cursor); // Check if a space before '?' still needed if (prev_pos == cursor && ch == '?' && document.charAt(line, cursor.column - 1) != ' ') document.insertText(line, cursor.column - 1, " "); // Add it! cursor = view.cursorPosition(); // Update cursor position line = cursor.line; column = cursor.column; if (ch == '?') { addCharOrJumpOverIt(line, column, ' '); } // Handle operator| and/or operator|| else if (ch == '|') { /** * Here is 6+3 cases possible (the last bar is just entered): * 0) ??? -- add a space before bar and after if needed * 1) ?? -- add a space after if needed * 2) ??| -- add a space before 1st bar and after the 2nd if needed * 3) ? | -- add a space after the 2nd bar if needed * 4) ?| -- add a space before 1st bar, remove the mid one, add a space after 2nd bar * 5) | -- remove the mid space, add one after 2nd bar * and finally, * 6a) || -- add a space before 1st bar if needed, remove the last bar * 6b) || -- remove the last bar and add a space after 2nd bar if needed * 6c) || -- add a space after if needed */ var prev = document.text(line, column - 4, line, column - 1); dbg("tryOperator: checking @Cursor("+line+","+(column - 4)+"), prev='"+prev+"'"); var space_offset = 0; if (prev.endsWith(" | ")) { // case 5: remove the mid space document.removeText(line, column - 2, line, column - 1); space_offset = -1; } else if (prev.endsWith("|| ")) { // case 6a: add a space before 1st bar if needed, remove the last bar document.removeText(line, column - 1, line, column); var space_has_added = addCharOrJumpOverIt(line, column - 4, ' '); space_offset = (space_has_added ? 1 : 0) - 2; } else if (prev.endsWith(" ||")) { // case 6b: remove the last bar document.removeText(line, column - 1, line, column); space_offset = -1; } else if (prev.endsWith("||")) { // case 6a: add a space before and remove the last bar document.removeText(line, column - 1, line, column); document.insertText(line, column - 3, " "); } else if (prev.endsWith("| ")) { // case 4: add a space before 1st bar, remove the mid one document.removeText(line, column - 2, line, column - 1); document.insertText(line, column - 3, " "); } else if (prev.endsWith(" |") || prev.endsWith(" ")) { // case 3 and 1 } else { // case 2: add a space before 1st bar if (prev.endsWith('|')) space_offset = 1; // case 0: add a space before bar document.insertText(line, column - 1 - space_offset, " "); space_offset = 1; } addCharOrJumpOverIt(line, column + space_offset, ' '); } // Handle operator% and/or operator^ else if (ch == '%' || ch == '^') { var prev = document.text(line, column - 4, line, column - 1); dbg("tryOperator: checking2 @Cursor("+line+","+(column - 4)+"), prev='"+prev+"'"); var patterns = [" % ", "% ", " %", "%", " "]; for ( var i = 0 ; i < patterns.length ; i++ ) patterns[i] = patterns[i].replace('%', ch); var space_offset = 0; if (prev.endsWith(patterns[0])) { // case 0: remove just entered char document.removeText(line, column - 1, line, column); space_offset = -2; } else if (prev.endsWith(patterns[1])) { // case 1: remove just entered char, add a space before document.removeText(line, column - 1, line, column); document.insertText(line, column - 3, " "); space_offset = -1; } else if (prev.endsWith(patterns[2])) { // case 2: remove just entered char document.removeText(line, column - 1, line, column); space_offset = -1; } else if (prev.endsWith(patterns[3])) { // case 3: add a space before document.removeText(line, column - 1, line, column); document.insertText(line, column - 2, " "); space_offset = 0; } else if (prev.endsWith(patterns[4])) { // case 4: no space needed before space_offset = 0; } else { // case everything else: surround operator w/ spaces document.insertText(line, column - 1, " "); space_offset = 1; } addCharOrJumpOverIt(line, column + space_offset, ' '); } else if (ch == '.') // Replace '..' w/ '...' { var prev = document.text(line, column - 3, line, column); dbg("tryOperator: checking3 @Cursor("+line+","+(column - 4)+"), prev='"+prev+"'"); if (prev == "...") // If there is already 3 dots { // Remove just entered (redundant) one document.removeText(line, column - 1, line, column); } // Append one more if only two here and we are in 'Normal Mode' else if (prev[1] == '.' && prev[2] == '.' && document.defStyleNum(line, column) == 0) { addCharOrJumpOverIt(line, column, '.'); } // Otherwise, do nothing... } if (result != -2) { dbg("tryOperator result="+result); } return result; } /** * \brief Try to align a given close bracket */ function tryCloseBracket(cursor, ch) { var result = -2; var line = cursor.line; var column = cursor.column; var braceCursor = Cursor.invalid(); if (ch != '>') { // TODO Make sure a given `ch` in the gBraceMap braceCursor = document.anchor(line, column - 1, gBraceMap[ch]); // make sure we are actually on the character we are expecting // if that's not the case, a brace was probably inserted behind the cursor by auto brackets realCharacter = document.charAt(line, column - 1); if (realCharacter != ch) { braceCursor = document.anchor(line, column, gBraceMap[ch]); } // TODO Otherwise, it seems we have a template parameters list... } // unindent when starting new block and previous line has lower indentation level // prevents over-indentation when inserting condition or loop headers with their opening brace in the next line var firstPos = document.firstColumn(line); var prevFirstPos = document.firstColumn(line - 1); if (firstPos == column - 1 && ch == '}' && firstPos > prevFirstPos) { result = prevFirstPos; } // Check if a given closing brace is a first char on a line // (i.e. it is 'dangling' brace)... if (justEnteredCharIsFirstOnLine(line, column, ch) && braceCursor.isValid()) { // Move to one half-TAB right, if anything but not closing '}', else // align to the corresponding open char result = document.firstColumn(braceCursor.line) + (ch != '}' ? 2 : 0); dbg("tryCloseBracket: setting result="+result); } // Check if ';' required after closing '}', but make sure it was inserted by the user // and not automatically by auto brackets (behind the cursor) if (ch == '}' && braceCursor.isValid()) { var is_check_needed = false; // Check if corresponding anchor is a class/struct/union/enum, // (possible keyword located on same or prev line) // and check for trailing ';'... var anchoredString = document.line(braceCursor.line); dbg("tryCloseBracket: anchoredString='"+anchoredString+"'"); var regex = /^(\s*)(class|struct|union|enum).*$/; var r = regex.exec(anchoredString); if (r != null) { dbg("tryCloseBracket: same line"); is_check_needed = true; } else (!is_check_needed && 0 < braceCursor.line) // Is there any line before? { dbg("tryCloseBracket: checking prev line"); // Ok, lets check it! anchoredString = document.line(braceCursor.line - 1); dbg("tryCloseBracket: anchoredString-1='"+anchoredString+"'"); r = regex.exec(anchoredString); if (r != null) { is_check_needed = true; dbg("tryCloseBracket: prev line"); } } dbg("tryCloseBracket: is_check_needed="+is_check_needed); if (is_check_needed) { var is_ok = document.line(line) .substring(column, document.lineLength(line)) .ltrim() .startsWith(';') ; if (!is_ok) { var pos = column; var newCursorPos = pos + 1; if (document.charAt(line, column - 1) != '}' && document.charAt(line, column) == '}') { ++pos; --newCursorPos; } document.insertText(line, pos, ';'); view.setCursorPosition(line, newCursorPos); } } } else if (ch == '>') { // If user typed 'some' + '<' + '>', jump over the '>' // (which was added by the tryTemplate) if (document.charAt(line, column) == '>') { document.removeText(line, column, line, column + 1); } } tryJumpOverParenthesis(view.cursorPosition()); return result; } /** * \brief Indent a new scope block * * ... try to unindent to be precise... First of all check that open * \c '{' is a first symbol on a line, and if it doesn't, * add space (if absent at previous position) after ')' or \c '=' * or if line stats w/ some keywords: \c enum, \c class, \c struct or \c union. * Otherwise, look at the previous line for dangling ')' or * a line started w/ one of flow control keywords. * */ function tryBlock(cursor) { var result = -2; var line = cursor.line; var column = cursor.column; // Make sure we r not in a comment or string dbg("tryBlock: isStringOrComment(line, column - 2)="+isStringOrComment(line, column - 2)) if (isStringOrComment(line, column - 2)) return result; if (justEnteredCharIsFirstOnLine(line, column, '{')) { // Check for a dangling close brace on a previous line // (this may mean that `for' or `if' or `while' w/ looong parameters list on it) if (document.firstChar(line - 1) == ')') result = Math.floor(document.firstColumn(line - 1) / gIndentWidth) * gIndentWidth; else { // Otherwise, check for a keyword on the previous line and // indent the started block to it... var prevString = document.line(line - 1); var r = /^(\s*)((catch|if|for|while)\s*\(|do|else|try|(default|case\s+.*)\s*:).*$/.exec(prevString); if (r != null) result = r[1].length; } } else { // '{' is not a first char. Check for a previous one... if (1 < column) { var prevChar = document.charAt(line, column - 2); dbg("tryBlock: prevChar='"+prevChar+"'"); if (prevChar == ')' || prevChar == '=') document.insertText(line, column - 1, ' '); else if (prevChar != ' ') { var currentLine = document.line(line).ltrim(); var starts_with_keyword = currentLine.startsWith('struct ') || currentLine.startsWith('class ') || currentLine.startsWith('union ') || currentLine.startsWith('enum ') ; if (starts_with_keyword) document.insertText(line, column - 1, ' '); } } } return result; } /** * \brief Align preprocessor directives */ function tryPreprocessor(cursor) { var result = -2; var line = cursor.line; var column = cursor.column; // Check if just entered '#' is a first on a line if (justEnteredCharIsFirstOnLine(line, column, '#')) { // Get current indentation level var currentLevel = getPreprocessorLevelAt(line); if (currentLevel > 0) { var padding = String().fill(' ', (currentLevel - 1) * 2 + 1); document.insertText(cursor, padding); } result = 0; } return result; } /** * \brief Try to align access modifiers or class initialization list * * Here is few cases possible: * \li \c ':' pressed after a keyword \c public, \c protected or \c private. * Then align a current line to corresponding class/struct definition. * Check a previous line and if it is not starts w/ \c '{' add a new line before. * \li \c ':' is a first char on the line, then it looks like a class initialization * list or 2nd line of ternary operator. * \li \c ':' is pressed on a line started w/ \c for statement and after a space * \li \c ':' after '>' looks like an access to template's member * \li shortcut: transform some(:|) into some() :| * * \todo Should it be done only for non strings and comments? */ function tryColon(cursor) { var result = -2; var line = cursor.line; var column = cursor.column; if (isStringOrComment(line, column)) return result; // Do nothing for comments and strings // Check if just entered ':' is a first on a line if (justEnteredCharIsFirstOnLine(line, column, ':')) { // Check if there a dangling ')' or '?' (ternary operator) on a previous line var ch = document.firstChar(line - 1); if (ch == ')' || ch == '?') result = document.firstVirtualColumn(line - 1); else result = document.firstVirtualColumn(line - 1) + 2; document.insertText(cursor, " "); } else { var currentLine = document.line(line); if (currentLine.search(/^\s*((public|protected|private)\s*(slots|Q_SLOTS)?|(signals|Q_SIGNALS)\s*):\s*$/) != -1) { var definitionCursor = document.anchor(line, 0, '{'); if (definitionCursor.isValid()) { result = document.firstVirtualColumn(definitionCursor.line); dbg("tryColon: result="+result); if (0 < line) // Is there any line before? { // Check if previous line is not empty and not starts w/ '{' var prevLine = document.line(line - 1).trim() if (prevLine.length && !prevLine.startsWith("{")) { // Cuz a new line will be added in place of current, returning // result will not affect indentation. So do it manually. var firstColumn = document.firstColumn(line); var padding = ""; if (firstColumn < result) padding = String().fill(' ', result - firstColumn); else if (result < firstColumn) document.removeText(line, 0, line, firstColumn - result); // Add an empty line before the current document.insertText(line, 0, "\n" + padding); result = 0; } } } } else if (document.charAt(line, column - 2) == ' ') { // Is it looks like a range based `for' or class/struct/enum? var add_space = currentLine.ltrim().startsWith("for (") || currentLine.ltrim().startsWith("class ") || currentLine.ltrim().startsWith("struct ") || currentLine.ltrim().startsWith("enum ") ; if (add_space) { // Add a space after ':' document.insertText(line, column, " "); } else if (document.charAt(line, column - 3) == ':') { // Transform ': :' -> '::' document.removeText(line, column - 2, line, column - 1); } } else if (document.charAt(line, column - 2) == ':') { // A char before is (already) a one more colon. // Make sure there is no more than two colons... // NOTE In C++ it is not possible to have more than two of them adjacent! if (document.charAt(line, column - 3) == ':') { // Remove the current (just entered) one... document.removeText(line, column - 1, line, column); } } else { // Check that it is not a 'case' and not a magic sequence. // NOTE "Magic sequence" means support for dynamic expand functions. // http://zaufi.github.io/programming/2014/02/13/kate-c++-stuff/ var is_magic_sequence = document.charAt( line , document.wordRangeAt(line, column - 1).start.column - 1 ) == ';'; if (!currentLine.ltrim().startsWith("case ") && !is_magic_sequence) { // Add one more ':' // Example some: --> some:: or std: --> std:: document.insertText(line, column, ":"); } else { // Try to jump out of parenthesis cursor = tryJumpOverParenthesis(cursor); // Try add a space after close bracket tryAddSpaceAfterClosedBracketOrQuote(cursor); } } } return result; } /** * \brief Try to add one space after keywords and before an open brace */ function tryOpenBrace(cursor) { var line = cursor.line; var column = cursor.column; var wordBefore = document.wordAt(line, column - 1); dbg("word before: '"+wordBefore+"'"); if (wordBefore.search(/\b(catch|for|if|switch|while|return)\b/) != -1) document.insertText(line, column - 1, " "); } function getMacroRange(line) { function stripLastCharAndRTrim(str) { return str.substring(0, str.length - 1).rtrim(); } var maxLength = 0; var macroStartLine = -1; // Look up towards begining of a document for (var i = line; i >= 0; --i) { var currentLineText = document.line(i); dbg("up: '"+currentLineText+"'"); if (currentLineText.search(/^\s*#\s*define\s+.*\\$/) != -1) { macroStartLine = i; maxLength = Math.max(maxLength, stripLastCharAndRTrim(currentLineText).length); break; // Ok, we've found the macro start! } else if (currentLineText.search(/\\$/) == -1) break; // Oops! No backslash found and #define still not reached! maxLength = Math.max(maxLength, stripLastCharAndRTrim(currentLineText).length); } if (macroStartLine == -1) return null; // Look down towards end of the document var macroEndLine = -1; for (var i = line; i < document.lines(); ++i) { var currentLineText = document.line(i); dbg("dw: '"+currentLineText+"'"); if (currentLineText.search(/\\$/) != -1) // Make sure the current line have a '\' at the end { macroEndLine = i; maxLength = Math.max(maxLength, stripLastCharAndRTrim(currentLineText).length); } else break; // No backslash at the end --> end of macro! } if (macroEndLine == -1) return null; macroEndLine++; return { range: new Range(macroStartLine, 0, macroEndLine, 0) , max: maxLength }; } /** * \brief Try to align a backslashes in macro definition * * \note It is \b illegal to have smth after a backslash in source code! */ function tryBackslash(cursor) { var line = cursor.line; var result = getMacroRange(line); // Look up and down for macro definition range if (result != null) { dbg("macroRange:",result.range); dbg("maxLength:",result.max); // Iterate over macro definition, strip backslash // and add a padding string up to result.max length + backslash for (var i = result.range.start.line; i < result.range.end.line; ++i) { var currentLineText = document.line(i); var originalTextLength = currentLineText.length; currentLineText = currentLineText.substring(0, currentLineText.length - 1).rtrim(); var textLength = currentLineText.length; document.removeText(i, textLength, i, originalTextLength); document.insertText(i, textLength, String().fill(' ', result.max - textLength + 1) + "\\"); } } } /** * \brief Handle a @ symbol * * Possible user wants to add a Doxygen group */ function tryDoxygenGrouping(cursor) { var line = cursor.line; var column = cursor.column; var firstColumn = document.firstColumn(line); // Check the symbol before the just entered var looks_like_doxgorup = isStringOrComment(line, column - 2) && firstColumn == (column - 4) && document.line(line).ltrim().startsWith("// ") ; if (looks_like_doxgorup) { document.removeText(line, column - 2, line, column - 1); var padding = String().fill(' ', firstColumn); document.insertText(line, column - 1, "{\n" + padding + "\n" + padding + "//@}"); view.setCursorPosition(line + 1, document.lineLength(line + 1)); } } /** * \brief Handle quote character * * Look back for \c 'R' char right before \c '"' and if * the next one (after \c '"') is not an alphanumeric, * then add a delimiters. * * \attention Effect of AutoBrace extension has already neutralized at this point :) */ function tryStringLiteral(cursor, ch) { var line = cursor.line; var column = cursor.column; - if (isComment(line, column - 2)) // Do nothing for comments + // Do nothing for comments + if (isComment(line, column - 2)) return; // First of all we have to determinate where we are: // 0) new string literal just started, or ... // 1) string literal just ends // Check if the '"' is a very first char on a line var new_string_just_started; - if (column < 2) + var raw_string; + if (column < 2) { // Yes, then we have to look to the last char of the previous line new_string_just_started = !(line != 0 && isString(line - 1, document.lastColumn(line - 1))); - else - // Ok, just check attribute of the char right before '"' + } else { + // Ok, just check attribute of the char right before '"' (special case for R"" raw string literals) new_string_just_started = !isString(line, column - 2); + if (!new_string_just_started && ch == '"') { + // we need to scan backwards before R", [uUL]R" or u8R" + var stringEnd; + if (column >= 4 && document.charAt(line, column - 2) == 'R' && document.charAt(line, column - 3) == '8' && document.charAt(line, column - 4) == 'u') { + raw_string = true; + stringEnd = 5; + } else if (column >= 3 && document.charAt(line, column - 2) == 'R' && (document.charAt(line, column - 3) == 'u' || document.charAt(line, column - 3) == 'U' || document.charAt(line, column - 3) == 'L')) { + raw_string = true; + stringEnd = 4; + } else if (column >= 2 && document.charAt(line, column - 2) == 'R') { + raw_string = true; + stringEnd = 3; + } + if (raw_string) + new_string_just_started = (column >= stringEnd) ? !isString(line, column - stringEnd) : !(line != 0 && isString(line - 1, document.lastColumn(line - 1))); + } + } // TODO Add a space after possible operator right before just // started string literal... if (new_string_just_started) { // Is there anything after just entered '"'? var nc = document.charAt(line, column); var need_closing_quote = column == document.lineLength(line) || document.isSpace(nc) || nc == ',' // user tries to add new string param, || nc == ')' // ... or one more param to the end of some call || nc == ']' // ... or string literal as subscript index || nc == ';' // ... or one more string before end of expression || nc == '<' // ... or `some << "|<<` ; if (need_closing_quote) { // Check for 'R' right before '"' - if (ch == '"' && document.charAt(line, column - 2) == 'R') + if (raw_string) { // Yeah, looks like a raw string literal /// \todo Make delimiter configurable... HOW? /// It would be nice if indenters can have a configuration page somehow... document.insertText(cursor, "~()~\""); view.setCursorPosition(line, column + 2); } else { document.insertText(cursor, ch); view.setCursorPosition(line, column); } } } } /** * \brief Handle \c '!' char * * Exclamation symbol can be a part of \c operator!= or unary operator. * in both cases, a space required before it! Except few cases: * - when it is at the line start * - when a char before it \c '(' -- i.e. argument of a control flow keyword (\c if, \c while) * or a function call parameter * - when a char before it \c '[' (array subscript) * - when a char before it \c '<' -- here is two cases possible: * - it is a first non-type template parameter (w/ type \c bool obviously) * - it is a part of less or shift operators. * To distinct last case, it is enough to check that a word before \c '<' (w/o space) * is an identifier. * \note Yep, operators supposed to be separated from around text. */ function tryExclamation(cursor) { var line = cursor.line; var column = cursor.column; if (column == 0) // Do nothing for very first char return; if (isStringOrComment(line, column - 1)) // Do nothing for comments and stings return; if (document.firstColumn(line) == column - 1) // Make sure '!' is not a first char on a line return; if (column < 2) // Do nothing if there is less than 2 chars before return; var pc = document.charAt(line, column - 2); // Do nothing if one of 'stop' chars: if (pc == ' ' || pc == '(' || pc == '[' || pc == '{') return; // And finally make sure it is not a part of 'relation operator' if (pc == '<' && column >= 3) { // Make sure a char before is not a space or another '<' var ppc = document.charAt(line, column - 3); if (ppc != ' ' && ppc != '<') return; } // Ok, if we r here, just insert a space ;) document.insertText(line, column - 1, " "); } /** * \brief Handle a space * * - add '()' pair after some keywords like: \c if, \c while, \c for, \c switch * - add ';' if space pressed right after \c return, and no text after it * - if space pressed inside of angle brackets 'some<|>' transform into 'some < |' */ function tryKeywordsWithBrackets(cursor) { var line = cursor.line; var column = cursor.column; var text = document.line(line).ltrim(); var need_brackets = text == "if " || text == "else if " || text == "while " || text == "for " || text == "switch " || text == "catch " ; if (need_brackets) { document.insertText(cursor, "()"); view.setCursorPosition(line, column + 1); } else if (text == "return ") { document.insertText(cursor, ";"); view.setCursorPosition(line, column); } else if (document.charAt(line, column - 2) == '<' && document.charAt(cursor) == '>') { document.removeText(line, column, line, column + 1); document.insertText(line, column - 2, " "); } } /** * Try to add space before and after some equal operators. */ function tryEqualOperator(cursor) { var line = cursor.line; var column = cursor.column; // Do nothing for comments or string literals or lines shorter than 2 if (2 < column && isStringOrComment(line, column)) return cursor; var c = document.charAt(line, column - 2); dbg("tryEqualOperator: checking @Cursor("+line+","+(column - 2)+"), c='"+c+"'"); switch (c) { // Two chars operators: !=, ==, ... case '*': case '%': case '/': case '^': case '|': case '&': case '!': case '=': addCharOrJumpOverIt(line, column, ' '); // Make sure there is a space after it! // Make sure there is a space before it! if (column >= 3 && document.charAt(line, column - 3) != ' ') document.insertText(line, column - 2, " "); break; case '(': // some(=|) --> some() =| cursor = tryJumpOverParenthesis(cursor); tryEqualOperator(cursor); // Call self again to handle "some()=|" break; case ')': // "some()=" or "(expr)=" --> ") =|" case '}': // It can be a ctor of some proxy object // Add a space between closing bracket and just entered '=' document.insertText(line, column - 1, " "); addCharOrJumpOverIt(line, column + 1, ' '); // Make sure there is a space after it! break; case '<': // Shortcut: transfrom "some<=|>" -> "some <= |" if (document.charAt(cursor) == '>') document.removeText(line, column, line, column + 1); case '>': // This could be '<<=', '>>=', '<=', '>=' // Make sure there is a space after it! addCharOrJumpOverIt(line, column, ' '); // Make sure there is a space after it! // Check if this is one of >>= or <<= if (column >= 3) { if (document.charAt(line, column - 3) == c) { if (column >= 4 && document.charAt(line, column - 4) != ' ') document.insertText(line, column - 3, " "); } else if (document.charAt(line, column - 3) != ' ') { // <= or >= document.insertText(line, column - 2, " "); } } break; case '[': // This could be a part of lambda capture [=] break; case ' ': // Lookup one more character towards left if (column >= 3) { var pc = document.charAt(line, column - 3); dbg("tryEqualOperator: checking @Cursor("+line+","+(column - 3)+"), pc='"+pc+"'"); switch (pc) { case '=': // Stick the current '=' to the previous char case '|': case '&': case '^': case '<': case '>': case '*': case '/': case '%': document.removeText(line, column - 1, line, column); document.insertText(line, column - 2, '='); break; default: break; } } break; case '+': case '-': addCharOrJumpOverIt(line, column, ' '); // Make sure there is a space after it! // Here is few things possible: // some+=| --> some += | // some++=| --> some++ = | // some+++=| --> some++ += | var space_offset = -1; if (column >= 3) { if (document.charAt(line, column - 3) == c) { if (column >= 4) { if (document.charAt(line, column - 4) == c) space_offset = 2; else if (document.charAt(line, column - 4) != ' ') space_offset = 1; } } else if (document.charAt(line, column - 3) != ' ') space_offset = 2; } if (space_offset != -1) document.insertText(line, column - space_offset, " "); break; default: dbg("tryEqualOperator: default"); // '=' always surrounded by spaces! addCharOrJumpOverIt(line, column, ' '); // Make sure there is a space after it! document.insertText(line, column - 1, " "); // Make sure there is a space before it! break; } } /** * \brief Process one character * * NOTE Cursor positioned right after just entered character and has +1 in column. * * \attention This function will roll back the effect of \b AutoBrace extension * for quote chars. So this indenter can handle that chars withing predictable * surround... * */ function processChar(line, ch) { var result = -2; // By default, do nothing... var cursor = view.cursorPosition(); if (!cursor) return result; // TODO Is there any `assert' in JS? if (line != cursor.line) { dbg("ASSERTION FAILURE: line != cursor.line"); return result; } document.editBegin(); switch (ch) { case '\n': result = caretPressed(cursor); break; case '/': trySameLineComment(cursor); // Possible user wants to start a comment break; case '<': result = tryTemplate(cursor); // Possible need to add closing '>' after template break; case ',': result = tryComma(cursor); // Possible need to align parameters list break; case ';': result = trySemicolon(cursor); // Possible `for ()` loop spread over lines break; case '?': case '|': case '^': case '%': case '.': result = tryOperator(cursor, ch); // Possible need to align some operator break; case '}': case ')': case ']': case '>': result = tryCloseBracket(cursor, ch); // Try to align a given close bracket break; case '{': result = tryBlock(cursor); break; case '#': result = tryPreprocessor(cursor); break; case ':': result = tryColon(cursor); break; case '(': tryOpenBrace(cursor); // Try to add a space after some keywords break; case '\\': tryBackslash(cursor); break; case '@': tryDoxygenGrouping(cursor); break; case '"': case '\'': tryStringLiteral(cursor, ch); break; case '!': // Almost all the time there should be a space before! tryExclamation(cursor); break; case ' ': tryKeywordsWithBrackets(cursor); break; case '=': tryEqualOperator(cursor); break; case '*': case '&': tryAddSpaceAfterClosedBracketOrQuote(cursor); break; default: break; // Nothing to do... } // Make sure it is not a pure comment line var currentLineText = document.line(cursor.line).ltrim(); if (ch != '\n' && !currentLineText.startsWith("//")) { // Ok, try to keep an inline comment aligned (if any)... // BUG If '=' was inserted (and a space added) in a code line w/ inline comment, // it seems kate do not update highlighting, so position, where comment was before, // still counted as a 'Comment' attribute, but actually it should be 'Normal Text'... // It is why adding '=' will not realign an inline comment... if (alignInlineComment(cursor.line) && ch == ' ') document.insertText(view.cursorPosition(), ' '); } document.editEnd(); return result; } function alignPreprocessor(line) { if (tryPreprocessor_ch(line) == -1) // Is smth happened? return -2; // No! Signal to upper level to try next aligner... return 0; // NOTE preprocessor directives always aligned to 0! } /** * Try to find a next non comment line assuming that a given * one is a start or middle of a multi-line comment. * * \attention This function would ignore anything else than * a simple comments like this one... I.e. if \b right after * star+slash starts anything (non comment, or even maybe after * that another one comment begins), it will be \b IGNORED. * (Just because this is a damn ugly style!) * * \return line number or \c 0 if not found * \note \c 0 is impossible value, so suitable to indicate an error! * * \sa \c alignInsideBraces() */ function findMultiLineCommentBlockEnd(line) { for (; line < document.lines(); line++) { var text = document.line(line).rtrim(); if (text.endsWith("*/")) break; } line++; // Move to *next* line if (line < document.lines()) { // Make sure it is not another one comment, and if so, // going to find it's end as well... var currentLineText = document.line(line).ltrim(); if (currentLineText.startsWith("//")) line = findSingleLineCommentBlockEnd(line); else if (currentLineText.startsWith("/*")) line = findMultiLineCommentBlockEnd(line); } else line = 0; // EOF found return line; } /** * Try to find a next non comment line assuming that a given * one is a single-line one * * \return line number or \c 0 if not found * \note \c 0 is impossible value, so suitable to indicate an error! * * \sa \c alignInsideBraces() */ function findSingleLineCommentBlockEnd(line) { while (++line < document.lines()) { var text = document.line(line).ltrim(); if (text.length == 0) continue; // Skip empty lines... if (!text.startsWith("//")) break; // Yeah! Smth was found finally. } if (line < document.lines()) { var currentLineText = document.line(line).ltrim(); // Get text of the found line while (currentLineText.length == 0) // Skip empty lines if any currentLineText = document.line(++line).ltrim(); // Make sure it is not another one multiline comment, and if so, // going to find it's end as well... if (currentLineText.startsWith("/*")) line = findMultiLineCommentBlockEnd(line); } else line = 0; // EOF found return line; } /** * Almost anything in a code is placed withing some brackets. * So the ideas is simple: * \li find nearest open bracket of any kind * \li depending on its type and presence of leading delimiters (non identifier characters) * add one or half TAB relative a first non-space char of a line w/ found bracket. * * But here is some details: * \li do nothing on empty lines * \li do nothing if first position is a \e string * \li align comments according next non-comment and non-preprocessor line * (i.e. it's desired indent cuz it maybe still unaligned) * * \attention Current Kate version has a BUG: \c anchor() unable to find smth * in a multiline macro definition (i.e. where every line ends w/ a backslash)! */ function alignInsideBraces(line) { // Make sure there is a text on a line, otherwise nothing to align here... var thisLineIndent = document.firstColumn(line); if (thisLineIndent == -1 || document.isString(line, 0)) return 0; // Check for comment on the current line var currentLineText = document.line(line).ltrim(); var nextNonCommentLine = -1; var middleOfMultilineBlock = false; var isSingleLineComment = false; if (currentLineText.startsWith('//')) // Is single line comment on this line? { dbg("found a single-line comment"); // Yep, go to find a next non-comment line... nextNonCommentLine = findSingleLineCommentBlockEnd(line); isSingleLineComment = true; } else if (currentLineText.startsWith('/*')) // Is multiline comment starts on this line? { // Yep, go to find a next non-comment line... dbg("found start of a multiline comment"); nextNonCommentLine = findMultiLineCommentBlockEnd(line); } // Are we already inside of a multiline comment? // NOTE To be sure that we are not inside of #if0/#endif block, // lets check that current line starts w/ '*' also! // NOTE Yep, it is expected (hardcoded) that multiline comment has // all lines started w/ a star symbol! // TODO BUG Kate has a bug: when multiline code snippet gets inserted into // a multiline comment block (like Doxygen's @code/@endcode) // document.isComment() returns true *only& for the first line of it! // So some other way needs to be found to indent comments properly... // TODO DAMN... it doesn't work that way also... for snippets longer than 2 lines. // I suppose kate first insert text, then indent it, and after that highlight it // So indenters based on a highlighting info will not work! BUT THEY DEFINITELY SHOULD! else if (currentLineText.startsWith("*") && document.isComment(line, 0)) { dbg("found middle of a multiline comment"); // Yep, go to find a next non-comment line... nextNonCommentLine = findMultiLineCommentBlockEnd(line); middleOfMultilineBlock = true; } dbg("line="+line); dbg("document.isComment(line, 0)="+document.isComment(line, 0)); //dbg("document.defStyleNum(line, 0)="+document.defStyleNum(line-1, 0)); dbg("currentLineText='"+currentLineText+"'"); dbg("middleOfMultilineBlock="+middleOfMultilineBlock); if (nextNonCommentLine == 0) // End of comment not found? // ... possible due temporary invalid code... // anyway, dunno how to align it! return -2; // So, are we inside a comment? (and we know where it ends) if (nextNonCommentLine != -1) { // Yep, lets try to get desired indent for next non-comment line var desiredIndent = indentLine(nextNonCommentLine); if (desiredIndent < 0) { // Have no idea how to indent this comment! So try to align it // as found line: desiredIndent = document.firstColumn(nextNonCommentLine); } // TODO Make sure that next non-comment line do not starts // w/ 'special' chars... return desiredIndent + (middleOfMultilineBlock|0); } var brackets = [ document.anchor(line, document.firstColumn(line), '(') , document.anchor(line, document.firstColumn(line), '{') , document.anchor(line, document.firstColumn(line), '[') ].sort(); dbg("Found open brackets @ "+brackets); // Check if we are at some brackets, otherwise do nothing var nearestBracket = brackets[brackets.length - 1]; if (!nearestBracket.isValid()) return 0; // Make sure it is not a `namespace' level // NOTE '{' brace should be at the same line w/ a 'namespace' keyword // (yep, according my style... :-) var bracketChar = document.charAt(nearestBracket); var parentLineText = document.line(nearestBracket.line).ltrim(); if (bracketChar == '{' && parentLineText.startsWith("namespace")) return 0; // Ok, (re)align it! var result = -2; switch (bracketChar) { case '{': case '(': case '[': // If current line has some leading delimiter, i.e. non alphanumeric character // add a half-TAB, otherwise add a one TAB... if needed! var parentIndent = document.firstColumn(nearestBracket.line); var openBraceIsFirst = parentIndent == nearestBracket.column; var firstChar = document.charAt(line, thisLineIndent); var isCloseBraceFirst = firstChar == ')' || firstChar == ']' || firstChar == '}'; var doNotAddAnything = openBraceIsFirst && isCloseBraceFirst; var mustAddHalfTab = (!openBraceIsFirst && isCloseBraceFirst) || firstChar == ',' || firstChar == '?' || firstChar == ':' || firstChar == ';' ; var desiredIndent = parentIndent + ( mustAddHalfTab ? (gIndentWidth / 2) : (doNotAddAnything ? 0 : gIndentWidth) ); result = desiredIndent; // Reassign a result w/ desired value! //BEGIN SPAM dbg("parentIndent="+parentIndent); dbg("openBraceIsFirst="+openBraceIsFirst); dbg("firstChar="+firstChar); dbg("isCloseBraceFirst="+isCloseBraceFirst); dbg("doNotAddAnything="+doNotAddAnything); dbg("mustAddHalfTab="+mustAddHalfTab); dbg("desiredIndent="+desiredIndent); //END SPAM break; default: dbg("Dunno how to align this line..."); break; } return result; } function alignAccessSpecifier(line) { var result = -2; var currentLineText = document.line(line).ltrim(); var match = currentLineText.search( /^\s*((public|protected|private)\s*(slots|Q_SLOTS)?|(signals|Q_SIGNALS)\s*):\s*$/ ); if (match != -1) { // Ok, lets find an open brace of the `class'/`struct' var openBracePos = document.anchor(line, document.firstColumn(line), '{'); if (openBracePos.isValid()) result = document.firstColumn(openBracePos.line); } return result; } /** * Try to align \c case statements in a \c switch */ function alignCase(line) { var result = -2; var currentLineText = document.line(line).ltrim(); if (currentLineText.startsWith("case ") || currentLineText.startsWith("default:")) { // Ok, lets find an open brace of the `switch' var openBracePos = document.anchor(line, document.firstColumn(line), '{'); if (openBracePos.isValid()) result = document.firstColumn(openBracePos.line) + gIndentWidth; } return result; } /** * Try to align \c break or \c continue statements in a loop or \c switch. * * Also it take care about the following case: * \code * for (blah-blah) * { * if (smth) * break; * } * \endcode */ function alignBreakContinue(line) { var result = -2; var currentLineText = document.line(line).ltrim(); var is_break = currentLineText.startsWith("break;"); var should_proceed = is_break || currentLineText.startsWith("continue;") if (should_proceed) result = tryBreakContinue(line - 1, is_break); return result; } /** * Try to align a given line * \todo More actions */ function indentLine(line) { dbg(">> Going to indent line "+line); var result = alignPreprocessor(line); // Try to align a preprocessor directive if (result == -2) // Nothing has changed? result = alignAccessSpecifier(line); // Try to align access specifiers in a class if (result == -2) // Nothing has changed? result = alignCase(line); // Try to align `case' statements in a `switch' if (result == -2) // Nothing has changed? result = alignBreakContinue(line); // Try to align `break' or `continue' statements if (result == -2) // Nothing has changed? result = alignInsideBraces(line); // Try to align a generic line alignInlineComment(line); // Always try to align inline comments dbg("indentLine result="+result); if (result == -2) // Still dunno what to do? result = -1; // ... just align according a previous non empty line return result; } /** * \brief Process a newline or one of \c triggerCharacters character. * * This function is called whenever the user hits \c ENTER key. * * It gets three arguments: \c line, \c indentwidth in spaces and typed character * * Called for each newline (ch == \n) and all characters specified in * the global variable \c triggerCharacters. When calling \e Tools->Align * the variable \c ch is empty, i.e. ch == ''. */ function indent(line, indentWidth, ch) { // NOTE Update some global variables gIndentWidth = indentWidth; var crsr = view.cursorPosition(); dbg("indentWidth: " + indentWidth); dbg(" Mode: " + document.highlightingModeAt(crsr)); dbg(" Attribute: " + document.attributeName(crsr)); dbg(" line: " + line); dbg(" char: " + crsr + " -> '" + ch + "'"); if (ch != "") return processChar(line, ch); return indentLine(line); } /** * \todo Better to use \c defStyleNum() instead of \c attributeName() and string comparison * * \todo Prevent second '//' on a line... ? Fix the current way anyway... */ // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/src/script/katecommandlinescript.h b/src/script/katecommandlinescript.h index c432ee7f..f631d6db 100644 --- a/src/script/katecommandlinescript.h +++ b/src/script/katecommandlinescript.h @@ -1,86 +1,86 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2009 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. */ #ifndef KATE_COMMANDLINE_SCRIPT_H #define KATE_COMMANDLINE_SCRIPT_H #include "katescript.h" #include "kateview.h" #include #include class KateCommandLineScriptHeader { public: KateCommandLineScriptHeader() {} inline void setFunctions(const QStringList &functions) { m_functions = functions; } inline const QStringList &functions() const { return m_functions; } inline void setActions(const QJsonArray &actions) { m_actions = actions; } inline const QJsonArray &actions() const { return m_actions; } private: QStringList m_functions; ///< the functions the script contains QJsonArray m_actions; ///< the action for this script }; /** * A specialized class for scripts that are of type * KateScriptInformation::IndentationScript */ class KateCommandLineScript : public KateScript, public KTextEditor::Command { public: KateCommandLineScript(const QString &url, const KateCommandLineScriptHeader &header); - virtual ~KateCommandLineScript(); + ~KateCommandLineScript() override; const KateCommandLineScriptHeader &commandHeader(); bool callFunction(const QString &cmd, const QStringList args, QString &errorMessage); // // KTextEditor::Command interface // public: - bool help(KTextEditor::View *view, const QString &cmd, QString &msg) Q_DECL_OVERRIDE; - bool exec(KTextEditor::View *view, const QString &cmd, QString &msg, const KTextEditor::Range &range = KTextEditor::Range::invalid()) Q_DECL_OVERRIDE; - bool supportsRange(const QString &cmd) Q_DECL_OVERRIDE; + bool help(KTextEditor::View *view, const QString &cmd, QString &msg) override; + bool exec(KTextEditor::View *view, const QString &cmd, QString &msg, const KTextEditor::Range &range = KTextEditor::Range::invalid()) override; + bool supportsRange(const QString &cmd) override; private: KateCommandLineScriptHeader m_commandHeader; }; #endif diff --git a/src/script/kateindentscript.h b/src/script/kateindentscript.h index 3818049e..a2c5ebdb 100644 --- a/src/script/kateindentscript.h +++ b/src/script/kateindentscript.h @@ -1,132 +1,132 @@ // This file is part of the KDE libraries // Copyright (C) 2008 Paul Giannaros // Copyright (C) 2009 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) version 3. // // 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_INDENT_SCRIPT_H #define KATE_INDENT_SCRIPT_H #include "katescript.h" #include "kateview.h" class KateIndentScriptHeader { public: - KateIndentScriptHeader() : m_priority(0) + KateIndentScriptHeader() {} inline void setName(const QString &name) { m_name = name; } inline const QString &name() const { return m_name; } inline void setRequiredStyle(const QString &requiredStyle) { m_requiredStyle = requiredStyle; } inline const QString &requiredStyle() const { return m_requiredStyle; } inline void setIndentLanguages(const QStringList &indentLanguages) { m_indentLanguages = indentLanguages; } inline const QStringList &indentLanguages() const { return m_indentLanguages; } inline void setPriority(int priority) { m_priority = priority; } inline int priority() const { return m_priority; } inline void setBaseName(const QString &baseName) { m_baseName = baseName; } inline const QString &baseName() const { return m_baseName; } private: QString m_name; ///< indenter name, e.g. Python /** * If this is an indenter, then this specifies the required syntax * highlighting style that must be used for this indenter to work properly. * If this property is empty, the indenter doesn't require a specific style. */ QString m_requiredStyle; /** * If this script is an indenter, then the indentLanguages member specifies * which languages this is an indenter for. The values must correspond with * the name of a programming language given in a highlighting file (e.g "TI Basic") */ QStringList m_indentLanguages; /** * If this script is an indenter, this value controls the priority it will take * when an indenter for one of the supported languages is requested and multiple * indenters are found */ - int m_priority; + int m_priority = 0; /** * basename of script */ QString m_baseName; }; /** * A specialized class for scripts that are of type * KateScriptInformation::IndentationScript */ class KateIndentScript : public KateScript { public: KateIndentScript(const QString &url, const KateIndentScriptHeader &header); const QString &triggerCharacters(); const KateIndentScriptHeader &indentHeader() const; /** * Returns a pair where the first value is the indent amount, and the second * value is the alignment. */ QPair indent(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &position, QChar typedCharacter, int indentWidth); private: QString m_triggerCharacters; bool m_triggerCharactersSet; KateIndentScriptHeader m_indentHeader; }; #endif diff --git a/src/script/katescript.cpp b/src/script/katescript.cpp index b8fa16ad..09d36c2e 100644 --- a/src/script/katescript.cpp +++ b/src/script/katescript.cpp @@ -1,255 +1,253 @@ // This file is part of the KDE libraries // Copyright (C) 2008 Paul Giannaros // Copyright (C) 2009, 2010 Dominik Haumann // Copyright (C) 2010 Joseph Wenninger // // 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) version 3. // // 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 "katescript.h" +#include "katescripteditor.h" #include "katescriptdocument.h" #include "katescriptview.h" #include "katescripthelpers.h" #include "kateview.h" #include "katedocument.h" #include "katepartdebug.h" #include #include #include #include #include #include #include #include KateScript::KateScript(const QString &urlOrScript, enum InputType inputType) - : m_loaded(false) - , m_loadSuccessful(false) - , m_url(inputType == InputURL ? urlOrScript : QString()) - , m_engine(nullptr) - , m_document(nullptr) - , m_view(nullptr) + : m_url(inputType == InputURL ? urlOrScript : QString()) , m_inputType(inputType) , m_script(inputType == InputSCRIPT ? urlOrScript : QString()) { } KateScript::~KateScript() { if (m_loadSuccessful) { // remove data... + delete m_editor; delete m_document; delete m_view; delete m_engine; } } QString KateScript::backtrace(const QJSValue &error, const QString &header) { QString bt; if (!header.isNull()) { bt += header + QLatin1String(":\n"); } if (error.isError()) { bt += error.toString() + QLatin1Char('\n'); } return bt; } void KateScript::displayBacktrace(const QJSValue &error, const QString &header) { if (!m_engine) { std::cerr << "KateScript::displayBacktrace: no engine, cannot display error\n"; return; } std::cerr << "\033[31m" << qPrintable(backtrace(error, header)) << "\033[0m" << '\n'; } void KateScript::clearExceptions() { if (!load()) { return; } } QJSValue KateScript::global(const QString &name) { // load the script if necessary if (!load()) { return QJSValue::UndefinedValue; } return m_engine->globalObject().property(name); } QJSValue KateScript::function(const QString &name) { QJSValue value = global(name); if (!value.isCallable()) { return QJSValue::UndefinedValue; } return value; } bool KateScript::load() { if (m_loaded) { return m_loadSuccessful; } m_loaded = true; m_loadSuccessful = false; // here set to false, and at end of function to true // read the script file into memory QString source; if (m_inputType == InputURL) { if (!Kate::Script::readFile(m_url, source)) { return false; } } else { source = m_script; } // create script engine, register meta types m_engine = new QJSEngine(); // export read & require function and add the require guard object QJSValue functions = m_engine->newQObject(new Kate::ScriptHelper(m_engine)); m_engine->globalObject().setProperty(QStringLiteral("functions"), functions); m_engine->globalObject().setProperty(QStringLiteral("read"), functions.property(QStringLiteral("read"))); m_engine->globalObject().setProperty(QStringLiteral("require"), functions.property(QStringLiteral("require"))); m_engine->globalObject().setProperty(QStringLiteral("require_guard"), m_engine->newObject()); // export debug function m_engine->globalObject().setProperty(QStringLiteral("debug"), functions.property(QStringLiteral("debug"))); // export translation functions m_engine->globalObject().setProperty(QStringLiteral("i18n"), functions.property(QStringLiteral("_i18n"))); m_engine->globalObject().setProperty(QStringLiteral("i18nc"), functions.property(QStringLiteral("_i18nc"))); m_engine->globalObject().setProperty(QStringLiteral("i18np"), functions.property(QStringLiteral("_i18np"))); m_engine->globalObject().setProperty(QStringLiteral("i18ncp"), functions.property(QStringLiteral("_i18ncp"))); // register default styles as ds* global properties m_engine->globalObject().setProperty(QStringLiteral("dsNormal"), KTextEditor::dsNormal); m_engine->globalObject().setProperty(QStringLiteral("dsKeyword"), KTextEditor::dsKeyword); m_engine->globalObject().setProperty(QStringLiteral("dsFunction"), KTextEditor::dsFunction); m_engine->globalObject().setProperty(QStringLiteral("dsVariable"), KTextEditor::dsVariable); m_engine->globalObject().setProperty(QStringLiteral("dsControlFlow"), KTextEditor::dsControlFlow); m_engine->globalObject().setProperty(QStringLiteral("dsOperator"), KTextEditor::dsOperator); m_engine->globalObject().setProperty(QStringLiteral("dsBuiltIn"), KTextEditor::dsBuiltIn); m_engine->globalObject().setProperty(QStringLiteral("dsExtension"), KTextEditor::dsExtension); m_engine->globalObject().setProperty(QStringLiteral("dsPreprocessor"), KTextEditor::dsPreprocessor); m_engine->globalObject().setProperty(QStringLiteral("dsAttribute"), KTextEditor::dsAttribute); m_engine->globalObject().setProperty(QStringLiteral("dsChar"), KTextEditor::dsChar); m_engine->globalObject().setProperty(QStringLiteral("dsSpecialChar"), KTextEditor::dsSpecialChar); m_engine->globalObject().setProperty(QStringLiteral("dsString"), KTextEditor::dsString); m_engine->globalObject().setProperty(QStringLiteral("dsVerbatimString"), KTextEditor::dsVerbatimString); m_engine->globalObject().setProperty(QStringLiteral("dsSpecialString"), KTextEditor::dsSpecialString); m_engine->globalObject().setProperty(QStringLiteral("dsImport"), KTextEditor::dsImport); m_engine->globalObject().setProperty(QStringLiteral("dsDataType"), KTextEditor::dsDataType); m_engine->globalObject().setProperty(QStringLiteral("dsDecVal"), KTextEditor::dsDecVal); m_engine->globalObject().setProperty(QStringLiteral("dsBaseN"), KTextEditor::dsBaseN); m_engine->globalObject().setProperty(QStringLiteral("dsFloat"), KTextEditor::dsFloat); m_engine->globalObject().setProperty(QStringLiteral("dsConstant"), KTextEditor::dsConstant); m_engine->globalObject().setProperty(QStringLiteral("dsComment"), KTextEditor::dsComment); m_engine->globalObject().setProperty(QStringLiteral("dsDocumentation"), KTextEditor::dsDocumentation); m_engine->globalObject().setProperty(QStringLiteral("dsAnnotation"), KTextEditor::dsAnnotation); m_engine->globalObject().setProperty(QStringLiteral("dsCommentVar"), KTextEditor::dsCommentVar); m_engine->globalObject().setProperty(QStringLiteral("dsRegionMarker"), KTextEditor::dsRegionMarker); m_engine->globalObject().setProperty(QStringLiteral("dsInformation"), KTextEditor::dsInformation); m_engine->globalObject().setProperty(QStringLiteral("dsWarning"), KTextEditor::dsWarning); m_engine->globalObject().setProperty(QStringLiteral("dsAlert"), KTextEditor::dsAlert); m_engine->globalObject().setProperty(QStringLiteral("dsOthers"), KTextEditor::dsOthers); m_engine->globalObject().setProperty(QStringLiteral("dsError"), KTextEditor::dsError); // register scripts itself QJSValue result = m_engine->evaluate(source, m_url); if (hasException(result, m_url)) { return false; } // AFTER SCRIPT: set the view/document objects as necessary + m_engine->globalObject().setProperty(QStringLiteral("editor"), m_engine->newQObject(m_editor = new KateScriptEditor(m_engine))); m_engine->globalObject().setProperty(QStringLiteral("document"), m_engine->newQObject(m_document = new KateScriptDocument(m_engine))); m_engine->globalObject().setProperty(QStringLiteral("view"), m_engine->newQObject(m_view = new KateScriptView(m_engine))); // yip yip! m_loadSuccessful = true; return true; } QJSValue KateScript::evaluate(const QString& program, const FieldMap& env) { if ( !load() ) { qWarning() << "load of script failed:" << program; return QJSValue(); } // Wrap the arguments in a function to avoid poluting the global object QString programWithContext = QStringLiteral("function(") + QStringList(env.keys()).join(QLatin1Char(',')) + QStringLiteral(") { return ") + program + QStringLiteral("}"); QJSValue programFunction = m_engine->evaluate(programWithContext); Q_ASSERT(programFunction.isCallable()); QJSValueList args; for ( auto it = env.begin(); it != env.end(); it++ ) { args << it.value(); } QJSValue result = programFunction.call(args); if (result.isError()) qWarning() << "Error evaluating script: " << result.toString(); return result; } bool KateScript::hasException(const QJSValue &object, const QString &file) { if (object.isError()) { displayBacktrace(object, i18n("Error loading script %1\n", file)); m_errorMessage = i18n("Error loading script %1", file); delete m_engine; m_engine = nullptr; m_loadSuccessful = false; return true; } return false; } bool KateScript::setView(KTextEditor::ViewPrivate *view) { if (!load()) { return false; } // setup the stuff m_document->setDocument(view->doc()); m_view->setView(view); return true; } void KateScript::setGeneralHeader(const KateScriptHeader &generalHeader) { m_generalHeader = generalHeader; } KateScriptHeader &KateScript::generalHeader() { return m_generalHeader; } diff --git a/src/script/katescript.h b/src/script/katescript.h index a9679a5e..6844cb8b 100644 --- a/src/script/katescript.h +++ b/src/script/katescript.h @@ -1,229 +1,231 @@ // This file is part of the KDE libraries // Copyright (C) 2008 Paul Giannaros // Copyright (C) 2009 Dominik Haumann // Copyright (C) 2010 Joseph Wenninger // // 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) version 3. // // 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_SCRIPT_H #define KATE_SCRIPT_H -#include -#include -#include +#include +#include +#include class QJSEngine; namespace KTextEditor { class ViewPrivate; } +class KateScriptEditor; class KateScriptDocument; class KateScriptView; namespace Kate { enum ScriptType { /** The script is an indenter */ IndentationScript, /** The script contains command line commands */ CommandLineScript, /** Don't know what kind of script this is */ UnknownScript }; } //BEGIN KateScriptHeader class KateScriptHeader { public: - KateScriptHeader() : m_revision(0), m_scriptType(Kate::UnknownScript) + KateScriptHeader() {} virtual ~KateScriptHeader() {} inline void setLicense(const QString &license) { m_license = license; } inline const QString &license() const { return m_license; } inline void setAuthor(const QString &author) { m_author = author; } inline const QString &author() const { return m_author; } inline void setRevision(int revision) { m_revision = revision; } inline int revision() const { return m_revision; } inline void setKateVersion(const QString &kateVersion) { m_kateVersion = kateVersion; } inline const QString &kateVersion() const { return m_kateVersion; } inline void setScriptType(Kate::ScriptType scriptType) { m_scriptType = scriptType; } inline Kate::ScriptType scriptType() const { return m_scriptType; } private: QString m_license; ///< the script's license, e.g. LGPL QString m_author; ///< the script author, e.g. "John Smith " - int m_revision; ///< script revision, a simple number, e.g. 1, 2, 3, ... + int m_revision = 0; ///< script revision, a simple number, e.g. 1, 2, 3, ... QString m_kateVersion; ///< required katepart version - Kate::ScriptType m_scriptType; ///< the script type + Kate::ScriptType m_scriptType = Kate::UnknownScript; ///< the script type }; //END //BEGIN KateScript /** * KateScript objects represent a script that can be executed and inspected. */ class KateScript { public: enum InputType { InputURL, InputSCRIPT }; typedef QMap FieldMap; /** * Create a new script representation, passing either a file or the script * content @p urlOrScript to it. * In case of a file, loading of the script will happen lazily. */ KateScript(const QString &urlOrScript, enum InputType inputType = InputURL); virtual ~KateScript(); /** The script's URL */ const QString &url() { return m_url; } /** * Load the script. If loading is successful, returns true. Otherwise, returns * returns false and an error message will be set (see errorMessage()). * Note that you don't have to call this -- it is called as necessary by the * functions that require it. * Subsequent calls to load will return the value it returned the first time. */ bool load(); /** * set view for this script for the execution * will trigger load! */ bool setView(KTextEditor::ViewPrivate *view); /** * Get a QJSValue for a global item in the script given its name, or an * invalid QJSValue if no such global item exists. */ QJSValue global(const QString &name); /** * Return a function in the script of the given name, or an invalid QJSValue * if no such function exists. */ QJSValue function(const QString &name); /** Return a context-specific error message */ const QString &errorMessage() { return m_errorMessage; } /** Returns the backtrace when a script has errored out */ QString backtrace(const QJSValue &error, const QString &header = QString()); /** Execute a piece of code **/ QJSValue evaluate(const QString& program, const FieldMap& env = FieldMap()); /** Displays the backtrace when a script has errored out */ void displayBacktrace(const QJSValue &error, const QString &header = QString()); /** Clears any uncaught exceptions in the script engine. */ void clearExceptions(); /** set the general header after construction of the script */ void setGeneralHeader(const KateScriptHeader &generalHeader); /** Return the general header */ KateScriptHeader &generalHeader(); protected: /** Checks for exception and gives feedback on the console. */ bool hasException(const QJSValue &object, const QString &file); private: /** Whether or not there has been a call to load */ - bool m_loaded; + bool m_loaded = false; /** Whether or not the script loaded successfully into memory */ - bool m_loadSuccessful; + bool m_loadSuccessful = false; /** The script's URL */ QString m_url; /** An error message set when an error occurs */ QString m_errorMessage; protected: /** The Qt interpreter for this script */ - QJSEngine *m_engine; + QJSEngine *m_engine = nullptr; private: /** general header data */ KateScriptHeader m_generalHeader; - /** document/view wrapper objects */ - KateScriptDocument *m_document; - KateScriptView *m_view; + /** wrapper objects */ + KateScriptEditor *m_editor = nullptr; + KateScriptDocument *m_document = nullptr; + KateScriptView *m_view = nullptr; private: /** if input is script or url**/ enum InputType m_inputType; QString m_script; }; //END #endif diff --git a/src/script/katescriptaction.cpp b/src/script/katescriptaction.cpp index 1eae2950..63b65052 100644 --- a/src/script/katescriptaction.cpp +++ b/src/script/katescriptaction.cpp @@ -1,164 +1,165 @@ /* 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 "katescriptaction.h" #include "katecommandlinescript.h" #include "katescriptmanager.h" #include "kateview.h" #include "katedocument.h" #include "kateglobal.h" #include "kateviewhelpers.h" #include "katepartdebug.h" #include "katecmd.h" #include "kateabstractinputmode.h" #include #include #include #include //BEGIN KateScriptAction KateScriptAction::KateScriptAction(const QString &cmd, const QJsonObject &action, KTextEditor::ViewPrivate *view) : QAction(i18nc("Script command name", action.value(QStringLiteral("name")).toString().toUtf8().data()), view) , m_view(view) , m_command(cmd) , m_interactive(action.value(QStringLiteral("interactive")).toBool()) { const QString icon = action.value(QStringLiteral("icon")).toString(); if (!icon.isEmpty()) { setIcon(QIcon::fromTheme(icon)); } connect(this, SIGNAL(triggered(bool)), this, SLOT(exec())); } KateScriptAction::~KateScriptAction() { } void KateScriptAction::exec() { if (m_interactive) { m_view->currentInputMode()->launchInteractiveCommand(m_command + QLatin1Char(' ')); } else { KTextEditor::Command *p = KateCmd::self()->queryCommand(m_command); if (p) { QString msg; p->exec(m_view, m_command, msg); } } } //END KateScriptAction //BEGIN KateScriptActionMenu KateScriptActionMenu::KateScriptActionMenu(KTextEditor::ViewPrivate *view, const QString &text) : KActionMenu(QIcon::fromTheme(QStringLiteral("code-context")), text, view) , m_view(view) { repopulate(); + setDelayed(false); // on script-reload signal, repopulate script menu connect(KTextEditor::EditorPrivate::self()->scriptManager(), SIGNAL(reloaded()), this, SLOT(repopulate())); } KateScriptActionMenu::~KateScriptActionMenu() { cleanup(); } void KateScriptActionMenu::cleanup() { // delete menus and actions for real qDeleteAll(m_menus); m_menus.clear(); qDeleteAll(m_actions); m_actions.clear(); } void KateScriptActionMenu::repopulate() { // if the view is already hooked into the GUI, first remove it // now and add it later, so that the changes we do here take effect KXMLGUIFactory *viewFactory = m_view->factory(); if (viewFactory) { viewFactory->removeClient(m_view); } // remove existing menu actions cleanup(); // now add all command line script commands QVector scripts = KTextEditor::EditorPrivate::self()->scriptManager()->commandLineScripts(); QHash menus; foreach (KateCommandLineScript *script, scripts) { /** * traverse actions */ const QJsonArray &actions = script->commandHeader().actions(); Q_FOREACH (const QJsonValue value, actions) { /** * action is a value */ const QJsonObject action = value.toObject(); /** * get command */ const QString cmd = action.value(QStringLiteral("function")).toString(); // show in a category submenu? QMenu *m = menu(); QString category = action.value(QStringLiteral("category")).toString(); if (!category.isEmpty()) { category = i18nc("Script command category", category.toUtf8().data()); m = menus[category]; if (!m) { m = menu()->addMenu(category); menus.insert(category, m); m_menus.append(m); } } // create action + add to menu QAction *a = new KateScriptAction(cmd, action, m_view); m->addAction(a); m_view->actionCollection()->addAction(QLatin1String("tools_scripts_") + cmd, a); const QString shortcut = action.value(QStringLiteral("shortcut")).toString(); if (!shortcut.isEmpty()) { m_view->actionCollection()->setDefaultShortcut(a, shortcut); } m_actions.append(a); } } // finally add the view to the xml factory again, if it initially was there if (viewFactory) { viewFactory->addClient(m_view); } } //END KateScriptActionMenu diff --git a/src/script/katescriptdocument.h b/src/script/katescriptdocument.h index d4801b58..7cf49853 100644 --- a/src/script/katescriptdocument.h +++ b/src/script/katescriptdocument.h @@ -1,197 +1,197 @@ // This file is part of the KDE libraries // Copyright (C) 2008 Paul Giannaros // Copyright (C) 2009 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) version 3. // // 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_SCRIPT_DOCUMENT_H #define KATE_SCRIPT_DOCUMENT_H #include #include #include -#include +#include #include #include namespace KTextEditor { class DocumentPrivate; } /** * Thinish wrapping around KTextEditor::DocumentPrivate, exposing the methods we want exposed * and adding some helper methods. * * setDocument _must_ be called before using any other method. This is not checked * for the sake of speed. */ class KTEXTEDITOR_EXPORT KateScriptDocument : public QObject { Q_OBJECT // Note: we have no Q_PROPERTIES due to consistency: everything is a function. public: KateScriptDocument(QJSEngine *, QObject *parent = nullptr); void setDocument(KTextEditor::DocumentPrivate *document); KTextEditor::DocumentPrivate *document(); //BEGIN Q_INVOKABLE QString fileName(); Q_INVOKABLE QString url(); Q_INVOKABLE QString mimeType(); Q_INVOKABLE QString encoding(); Q_INVOKABLE QString highlightingMode(); Q_INVOKABLE QStringList embeddedHighlightingModes(); Q_INVOKABLE QString highlightingModeAt(const QJSValue &pos); Q_INVOKABLE bool isModified(); Q_INVOKABLE QString text(); Q_INVOKABLE QString text(int fromLine, int fromColumn, int toLine, int toColumn); Q_INVOKABLE QString text(const QJSValue &jsfrom, const QJSValue &jsto); Q_INVOKABLE QString text(const QJSValue &jsrange); Q_INVOKABLE QString line(int line); Q_INVOKABLE QString wordAt(int line, int column); Q_INVOKABLE QString wordAt(const QJSValue &jscursor); Q_INVOKABLE QJSValue wordRangeAt(int line, int column); Q_INVOKABLE QJSValue wordRangeAt(const QJSValue &jscursor); Q_INVOKABLE QString charAt(int line, int column); Q_INVOKABLE QString charAt(const QJSValue &jscursor); Q_INVOKABLE QString firstChar(int line); Q_INVOKABLE QString lastChar(int line); Q_INVOKABLE bool isSpace(int line, int column); Q_INVOKABLE bool isSpace(const QJSValue &jscursor); Q_INVOKABLE bool matchesAt(int line, int column, const QString &s); Q_INVOKABLE bool matchesAt(const QJSValue &cursor, const QString &s); Q_INVOKABLE bool setText(const QString &s); Q_INVOKABLE bool clear(); Q_INVOKABLE bool truncate(int line, int column); Q_INVOKABLE bool truncate(const QJSValue &jscursor); Q_INVOKABLE bool insertText(int line, int column, const QString &s); Q_INVOKABLE bool insertText(const QJSValue &jscursor, const QString &s); Q_INVOKABLE bool removeText(int fromLine, int fromColumn, int toLine, int toColumn); Q_INVOKABLE bool removeText(const QJSValue &range); Q_INVOKABLE bool removeText(const QJSValue &jsfrom, const QJSValue &jsto); Q_INVOKABLE bool insertLine(int line, const QString &s); Q_INVOKABLE bool removeLine(int line); Q_INVOKABLE bool wrapLine(int line, int column); Q_INVOKABLE bool wrapLine(const QJSValue &cursor); Q_INVOKABLE void joinLines(int startLine, int endLine); Q_INVOKABLE int lines(); Q_INVOKABLE bool isLineModified(int line); Q_INVOKABLE bool isLineSaved(int line); Q_INVOKABLE bool isLineTouched(int line); Q_INVOKABLE int findTouchedLine(int startLine, bool down); Q_INVOKABLE int length(); Q_INVOKABLE int lineLength(int line); Q_INVOKABLE void editBegin(); Q_INVOKABLE void editEnd(); Q_INVOKABLE int firstColumn(int line); Q_INVOKABLE int lastColumn(int line); Q_INVOKABLE int prevNonSpaceColumn(int line, int column); Q_INVOKABLE int prevNonSpaceColumn(const QJSValue &jscursor); Q_INVOKABLE int nextNonSpaceColumn(int line, int column); Q_INVOKABLE int nextNonSpaceColumn(const QJSValue &jscursor); Q_INVOKABLE int prevNonEmptyLine(int line); Q_INVOKABLE int nextNonEmptyLine(int line); Q_INVOKABLE bool isInWord(const QString &character, int attribute); Q_INVOKABLE bool canBreakAt(const QString &character, int attribute); Q_INVOKABLE bool canComment(int startAttribute, int endAttribute); Q_INVOKABLE QString commentMarker(int attribute); Q_INVOKABLE QString commentStart(int attribute); Q_INVOKABLE QString commentEnd(int attribute); Q_INVOKABLE QJSValue documentRange(); Q_INVOKABLE QJSValue documentEnd(); Q_INVOKABLE bool isValidTextPosition(int line, int column); Q_INVOKABLE bool isValidTextPosition(const QJSValue& cursor); /** * Get the syntax highlighting attribute at a given position in the document. */ Q_INVOKABLE int attribute(int line, int column); Q_INVOKABLE int attribute(const QJSValue &jscursor); /** * Return true if the highlight attribute equals @p attr. */ Q_INVOKABLE bool isAttribute(int line, int column, int attr); Q_INVOKABLE bool isAttribute(const QJSValue &jscursor, int attr); /** * Get the name of the syntax highlighting attribute at the given position. */ Q_INVOKABLE QString attributeName(int line, int column); Q_INVOKABLE QString attributeName(const QJSValue &jscursor); /** * Return true is the name of the syntax attribute equals @p name. */ Q_INVOKABLE bool isAttributeName(int line, int column, const QString &name); Q_INVOKABLE bool isAttributeName(const QJSValue &cursor, const QString &name); Q_INVOKABLE QString variable(const QString &s); Q_INVOKABLE void setVariable(const QString &s, const QString &v); //END Q_INVOKABLE int firstVirtualColumn(int line); Q_INVOKABLE int lastVirtualColumn(int line); Q_INVOKABLE int toVirtualColumn(int line, int column); Q_INVOKABLE int toVirtualColumn(const QJSValue &cursor); Q_INVOKABLE QJSValue toVirtualCursor(int line, int column); Q_INVOKABLE QJSValue toVirtualCursor(const QJSValue &jscursor); Q_INVOKABLE int fromVirtualColumn(int line, int virtualColumn); Q_INVOKABLE int fromVirtualColumn(const QJSValue &jscursor); Q_INVOKABLE QJSValue fromVirtualCursor(int line, int column); Q_INVOKABLE QJSValue fromVirtualCursor(const QJSValue &jscursor); KTextEditor::Cursor anchorInternal(int line, int column, QChar character); KTextEditor::Cursor anchor(const KTextEditor::Cursor &cursor, QChar character); Q_INVOKABLE QJSValue anchor(int line, int column, QChar character); Q_INVOKABLE QJSValue anchor(const QJSValue &cursor, QChar character); KTextEditor::Cursor rfindInternal(int line, int column, const QString &text, int attribute = -1); KTextEditor::Cursor rfind(const KTextEditor::Cursor &cursor, const QString &text, int attribute = -1); Q_INVOKABLE QJSValue rfind(int line, int column, const QString &text, int attribute = -1); Q_INVOKABLE QJSValue rfind(const QJSValue &cursor, const QString &text, int attribute = -1); Q_INVOKABLE int defStyleNum(int line, int column); Q_INVOKABLE int defStyleNum(const QJSValue &cursor); Q_INVOKABLE bool isCode(int line, int column); Q_INVOKABLE bool isCode(const QJSValue &cursor); Q_INVOKABLE bool isComment(int line, int column); Q_INVOKABLE bool isComment(const QJSValue &cursor); Q_INVOKABLE bool isString(int line, int column); Q_INVOKABLE bool isString(const QJSValue &cursor); Q_INVOKABLE bool isRegionMarker(int line, int column); Q_INVOKABLE bool isRegionMarker(const QJSValue &cursor); Q_INVOKABLE bool isChar(int line, int column); Q_INVOKABLE bool isChar(const QJSValue &cursor); Q_INVOKABLE bool isOthers(int line, int column); Q_INVOKABLE bool isOthers(const QJSValue &cursor); Q_INVOKABLE bool startsWith(int line, const QString &pattern, bool skipWhiteSpaces); Q_INVOKABLE bool endsWith(int line, const QString &pattern, bool skipWhiteSpaces); Q_INVOKABLE void indent(const QJSValue &jsrange, int change); private: bool _isCode(int defaultStyle); KTextEditor::DocumentPrivate *m_document; QJSEngine *m_engine; }; #endif diff --git a/src/script/katescripteditor.cpp b/src/script/katescripteditor.cpp new file mode 100644 index 00000000..92694b27 --- /dev/null +++ b/src/script/katescripteditor.cpp @@ -0,0 +1,54 @@ +/* This file is part of the KDE libraries. + * + * Copyright (C) 2017 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 "katescripteditor.h" + +#include "kateglobal.h" + +#include +#include +#include + +using KTextEditor::EditorPrivate; + +KateScriptEditor::KateScriptEditor(QJSEngine *engine, QObject *parent) + : QObject(parent) + , m_engine(engine) +{ +} + +QString KateScriptEditor::clipboardText() const +{ + return QApplication::clipboard()->text(); +} + +QStringList KateScriptEditor::clipboardHistory() const +{ + QStringList ret; + for ( const auto& entry: KTextEditor::EditorPrivate::self()->clipboardHistory() ) { + ret.append(entry.isEmpty() ? QString() : entry.first()); + } + return ret; +} + +void KateScriptEditor::setClipboardText(const QString &text) +{ + QApplication::clipboard()->setText(text, QClipboard::Clipboard); +} diff --git a/src/script/katescripteditor.h b/src/script/katescripteditor.h new file mode 100644 index 00000000..311a1571 --- /dev/null +++ b/src/script/katescripteditor.h @@ -0,0 +1,50 @@ +/* This file is part of the KDE libraries. + * + * Copyright (C) 2017 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. + */ +#ifndef KATE_SCRIPT_EDITOR_H +#define KATE_SCRIPT_EDITOR_H + +#include +#include + +#include + +namespace KTextEditor { class ViewPrivate; } +class QJSEngine; + +/** + * This class wraps the global editor instance KateGlobal, exposing some + * helper methods such as the clipboard history etc. + */ +class KTEXTEDITOR_EXPORT KateScriptEditor : public QObject +{ + Q_OBJECT + +public: + KateScriptEditor(QJSEngine *engine, QObject *parent = nullptr); + + Q_INVOKABLE QString clipboardText() const; + Q_INVOKABLE QStringList clipboardHistory() const; + Q_INVOKABLE void setClipboardText(const QString &text); + +private: + QJSEngine *m_engine = nullptr; +}; + +#endif diff --git a/src/script/katescripthelpers.h b/src/script/katescripthelpers.h index 53a99bb4..330e1e3f 100644 --- a/src/script/katescripthelpers.h +++ b/src/script/katescripthelpers.h @@ -1,59 +1,59 @@ /* 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. */ #ifndef KATE_SCRIPTHELPERS_H #define KATE_SCRIPTHELPERS_H -#include -#include +#include +#include #include class QJSEngine; namespace Kate { /** Top-level script functions */ namespace Script { /** read complete file contents, helper */ KTEXTEDITOR_EXPORT bool readFile(const QString &sourceUrl, QString &sourceCode); } // namespace Script class KTEXTEDITOR_EXPORT ScriptHelper : public QObject { Q_OBJECT QJSEngine *m_engine; public: - ScriptHelper(QJSEngine *engine) : m_engine(engine) {} + explicit ScriptHelper(QJSEngine *engine) : m_engine(engine) {} Q_INVOKABLE QString read(const QString &file); Q_INVOKABLE void require(const QString &file); Q_INVOKABLE void debug(const QString &msg); Q_INVOKABLE QString _i18n(const QString &msg); Q_INVOKABLE QString _i18nc(const QString &textContext, const QString &text); Q_INVOKABLE QString _i18np(const QString &trSingular, const QString &trPlural, int number); Q_INVOKABLE QString _i18ncp(const QString &trContext, const QString &trSingular, const QString &trPlural, int number = 0); }; } // namespace Kate #endif diff --git a/src/script/katescriptmanager.cpp b/src/script/katescriptmanager.cpp index 5bf0ca1c..bdc1eb83 100644 --- a/src/script/katescriptmanager.cpp +++ b/src/script/katescriptmanager.cpp @@ -1,341 +1,341 @@ // This file is part of the KDE libraries // Copyright (C) 2005 Christoph Cullmann // Copyright (C) 2005 Joseph Wenninger // Copyright (C) 2006, 2009 Dominik Haumann // Copyright (C) 2008 Paul Giannaros // Copyright (C) 2010 Joseph Wenninger // // 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 "katescriptmanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kateglobal.h" #include "katecmd.h" #include "katepartdebug.h" KateScriptManager *KateScriptManager::m_instance = nullptr; KateScriptManager::KateScriptManager() : KTextEditor::Command({ QStringLiteral("reload-scripts") }) { // use cached info collect(); } KateScriptManager::~KateScriptManager() { qDeleteAll(m_indentationScripts); qDeleteAll(m_commandLineScripts); m_instance = nullptr; } KateIndentScript *KateScriptManager::indenter(const QString &language) { KateIndentScript *highestPriorityIndenter = nullptr; foreach (KateIndentScript *indenter, m_languageToIndenters.value(language.toLower())) { // don't overwrite if there is already a result with a higher priority if (highestPriorityIndenter && indenter->indentHeader().priority() < highestPriorityIndenter->indentHeader().priority()) { #ifdef DEBUG_SCRIPTMANAGER qCDebug(LOG_KTE) << "Not overwriting indenter for" << language << "as the priority isn't big enough (" << indenter->indentHeader().priority() << '<' << highestPriorityIndenter->indentHeader().priority() << ')'; #endif } else { highestPriorityIndenter = indenter; } } #ifdef DEBUG_SCRIPTMANAGER if (highestPriorityIndenter) { qCDebug(LOG_KTE) << "Found indenter" << highestPriorityIndenter->url() << "for" << language; } else { qCDebug(LOG_KTE) << "No indenter for" << language; } #endif return highestPriorityIndenter; } /** * Small helper: QJsonValue to QStringList */ static QStringList jsonToStringList (const QJsonValue &value) { QStringList list; Q_FOREACH (const QJsonValue &value, value.toArray()) { if (value.isString()) { list.append(value.toString()); } } return list; } void KateScriptManager::collect() { // clear out the old scripts and reserve enough space qDeleteAll(m_indentationScripts); qDeleteAll(m_commandLineScripts); m_indentationScripts.clear(); m_commandLineScripts.clear(); m_languageToIndenters.clear(); m_indentationScriptMap.clear(); /** * now, we search all kinds of known scripts */ - for (const QString type : { QLatin1String("indentation"), QLatin1String("commands") }) { + for (const auto &type : { QLatin1String("indentation"), QLatin1String("commands") }) { // basedir for filesystem lookup const QString basedir = QLatin1String("/katepart5/script/") + type; QStringList dirs; // first writable locations, e.g. stuff the user has provided dirs += QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + basedir; // then resources, e.g. the stuff we ship with us dirs.append(QLatin1String(":/ktexteditor/script/") + type); // then all other locations, this includes global stuff installed by other applications // this will not allow global stuff to overwrite the stuff we ship in our resources to allow to install a more up-to-date ktexteditor lib locally! foreach (const QString &dir, QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation)) { dirs.append(dir + basedir); } QStringList list; foreach (const QString &dir, dirs) { const QStringList fileNames = QDir(dir).entryList({ QStringLiteral("*.js") }); foreach (const QString &file, fileNames) { list.append(dir + QLatin1Char('/') + file); } } // iterate through the files and read info out of cache or file, no double loading of same scripts QSet unique; foreach (const QString &fileName, list) { /** * get file basename */ const QString baseName = QFileInfo(fileName).baseName(); /** * only load scripts once, even if multiple installed variants found! */ if (unique.contains(baseName)) continue; /** * remember the script */ unique.insert (baseName); /** * open file or skip it */ QFile file(fileName); if (!file.open(QIODevice::ReadOnly)) { qCDebug(LOG_KTE) << "Script parse error: Cannot open file " << qPrintable(fileName) << '\n'; continue; } /** * search json header or skip this file */ QByteArray fileContent = file.readAll(); int startOfJson = fileContent.indexOf ('{'); if (startOfJson < 0) { qCDebug(LOG_KTE) << "Script parse error: Cannot find start of json header at start of file " << qPrintable(fileName) << '\n'; continue; } int endOfJson = fileContent.indexOf("\n};", startOfJson); if (endOfJson < 0) { // as fallback, check also mac os line ending endOfJson = fileContent.indexOf("\r};", startOfJson); } if (endOfJson < 0) { qCDebug(LOG_KTE) << "Script parse error: Cannot find end of json header at start of file " << qPrintable(fileName) << '\n'; continue; } endOfJson += 2; //we want the end including the } but not the ; /** * parse json header or skip this file */ QJsonParseError error; const QJsonDocument metaInfo (QJsonDocument::fromJson(fileContent.mid(startOfJson, endOfJson-startOfJson), &error)); if (error.error || !metaInfo.isObject()) { qCDebug(LOG_KTE) << "Script parse error: Cannot parse json header at start of file " << qPrintable(fileName) << error.errorString() << endOfJson << fileContent.mid(endOfJson-25, 25).replace('\n', ' '); continue; } /** * remember type */ KateScriptHeader generalHeader; if (type == QLatin1String("indentation")) { generalHeader.setScriptType(Kate::IndentationScript); } else if (type == QLatin1String("commands")) { generalHeader.setScriptType(Kate::CommandLineScript); } else { // should never happen, we dictate type by directory Q_ASSERT(false); } const QJsonObject metaInfoObject = metaInfo.object(); generalHeader.setLicense(metaInfoObject.value(QStringLiteral("license")).toString()); generalHeader.setAuthor(metaInfoObject.value(QStringLiteral("author")).toString()); generalHeader.setRevision(metaInfoObject.value(QStringLiteral("revision")).toInt()); generalHeader.setKateVersion(metaInfoObject.value(QStringLiteral("kate-version")).toString()); // now, cast accordingly based on type switch (generalHeader.scriptType()) { case Kate::IndentationScript: { KateIndentScriptHeader indentHeader; indentHeader.setName(metaInfoObject.value(QStringLiteral("name")).toString()); indentHeader.setBaseName(baseName); if (indentHeader.name().isNull()) { qCDebug(LOG_KTE) << "Script value error: No name specified in script meta data: " << qPrintable(fileName) << '\n' << "-> skipping indenter" << '\n'; continue; } // required style? indentHeader.setRequiredStyle(metaInfoObject.value(QStringLiteral("required-syntax-style")).toString()); // which languages does this support? QStringList indentLanguages = jsonToStringList(metaInfoObject.value(QStringLiteral("indent-languages"))); if (!indentLanguages.isEmpty()) { indentHeader.setIndentLanguages(indentLanguages); } else { indentHeader.setIndentLanguages(QStringList() << indentHeader.name()); #ifdef DEBUG_SCRIPTMANAGER qCDebug(LOG_KTE) << "Script value warning: No indent-languages specified for indent " << "script " << qPrintable(fileName) << ". Using the name (" << qPrintable(indentHeader.name()) << ")\n"; #endif } // priority indentHeader.setPriority(metaInfoObject.value(QStringLiteral("priority")).toInt()); KateIndentScript *script = new KateIndentScript(fileName, indentHeader); script->setGeneralHeader(generalHeader); foreach (const QString &language, indentHeader.indentLanguages()) { m_languageToIndenters[language.toLower()].push_back(script); } m_indentationScriptMap.insert(indentHeader.baseName(), script); m_indentationScripts.append(script); break; } case Kate::CommandLineScript: { KateCommandLineScriptHeader commandHeader; commandHeader.setFunctions(jsonToStringList(metaInfoObject.value(QStringLiteral("functions")))); commandHeader.setActions(metaInfoObject.value(QStringLiteral("actions")).toArray()); if (commandHeader.functions().isEmpty()) { qCDebug(LOG_KTE) << "Script value error: No functions specified in script meta data: " << qPrintable(fileName) << '\n' << "-> skipping script" << '\n'; continue; } KateCommandLineScript *script = new KateCommandLineScript(fileName, commandHeader); script->setGeneralHeader(generalHeader); m_commandLineScripts.push_back(script); break; } case Kate::UnknownScript: default: qCDebug(LOG_KTE) << "Script value warning: Unknown type ('" << qPrintable(type) << "'): " << qPrintable(fileName) << '\n'; } } } #ifdef DEBUG_SCRIPTMANAGER // XX Test if (indenter("Python")) { qCDebug(LOG_KTE) << "Python: " << indenter("Python")->global("triggerCharacters").isValid() << "\n"; qCDebug(LOG_KTE) << "Python: " << indenter("Python")->function("triggerCharacters").isValid() << "\n"; qCDebug(LOG_KTE) << "Python: " << indenter("Python")->global("blafldsjfklas").isValid() << "\n"; qCDebug(LOG_KTE) << "Python: " << indenter("Python")->function("indent").isValid() << "\n"; } if (indenter("C")) { qCDebug(LOG_KTE) << "C: " << qPrintable(indenter("C")->url()) << "\n"; } if (indenter("lisp")) { qCDebug(LOG_KTE) << "LISP: " << qPrintable(indenter("Lisp")->url()) << "\n"; } #endif } void KateScriptManager::reload() { collect(); emit reloaded(); } /// Kate::Command stuff bool KateScriptManager::exec(KTextEditor::View *view, const QString &_cmd, QString &errorMsg, const KTextEditor::Range &) { QStringList args(_cmd.split(QRegExp(QLatin1String("\\s+")), QString::SkipEmptyParts)); QString cmd(args.first()); args.removeFirst(); if (!view) { errorMsg = i18n("Could not access view"); return false; } if (cmd == QLatin1String("reload-scripts")) { reload(); return true; } else { errorMsg = i18n("Command not found: %1", cmd); return false; } } bool KateScriptManager::help(KTextEditor::View *view, const QString &cmd, QString &msg) { Q_UNUSED(view) if (cmd == QLatin1String("reload-scripts")) { msg = i18n("Reload all JavaScript files (indenters, command line scripts, etc)."); return true; } else { msg = i18n("Command not found: %1", cmd); return false; } } diff --git a/src/script/katescriptmanager.h b/src/script/katescriptmanager.h index d7df0e0e..a9e24ad9 100644 --- a/src/script/katescriptmanager.h +++ b/src/script/katescriptmanager.h @@ -1,140 +1,140 @@ // This file is part of the KDE libraries // Copyright (C) 2008 Paul Giannaros // Copyright (C) 2009 Dominik Haumann // Copyright (C) 2010 Joseph Wenninger // // 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) version 3. // // 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_SCRIPT_MANAGER_H #define KATE_SCRIPT_MANAGER_H #include #include #include #include "katescript.h" #include "kateindentscript.h" #include "katecommandlinescript.h" class QString; /** * Manage the scripts on disks -- find them and query them. * Provides access to loaded scripts too. */ class KateScriptManager : public KTextEditor::Command { Q_OBJECT KateScriptManager(); static KateScriptManager *m_instance; public: - virtual ~KateScriptManager(); + ~KateScriptManager() override; /** Get all scripts available in the command line */ const QVector &commandLineScripts() { return m_commandLineScripts; } /** * Get an indentation script for the given language -- if there is more than * one result, this will return the script with the highest priority. If * both have the same priority, an arbitrary script will be returned. * If no scripts are found, 0 is returned. */ KateIndentScript *indenter(const QString &language); // // KTextEditor::Command // public: /** * execute command * @param view view to use for execution * @param cmd cmd string * @param errorMsg error to return if no success * @return success */ - bool exec(KTextEditor::View *view, const QString &cmd, QString &errorMsg, const KTextEditor::Range &) Q_DECL_OVERRIDE; + bool exec(KTextEditor::View *view, const QString &cmd, QString &errorMsg, const KTextEditor::Range &) override; /** * get help for a command * @param view view to use * @param cmd cmd name * @param msg help message * @return help available or not */ - bool help(KTextEditor::View *view, const QString &cmd, QString &msg) Q_DECL_OVERRIDE; + bool help(KTextEditor::View *view, const QString &cmd, QString &msg) override; static KateScriptManager *self() { if (m_instance == nullptr) { m_instance = new KateScriptManager(); } return m_instance; } // // Helper methods // public: /** * Collect all scripts. */ void collect(); public: KateIndentScript *indentationScript(const QString &scriptname) { return m_indentationScriptMap.value(scriptname); } int indentationScriptCount() { return m_indentationScripts.size(); } KateIndentScript *indentationScriptByIndex(int index) { return m_indentationScripts[index]; } public: /** explicitly reload all scripts */ void reload(); Q_SIGNALS: /** this signal is emitted when all scripts are _deleted_ and reloaded again. */ void reloaded(); private: /** List of all command line scripts */ QVector m_commandLineScripts; /** list of all indentation scripts */ QList m_indentationScripts; /** hash of all existing indenter scripts, hashes basename -> script */ QHash m_indentationScriptMap; /** Map of language to indent scripts */ QHash > m_languageToIndenters; }; #endif diff --git a/src/script/scriptcursor.h b/src/script/scriptcursor.h index 2116afd9..3d719cc8 100644 --- a/src/script/scriptcursor.h +++ b/src/script/scriptcursor.h @@ -1,46 +1,46 @@ /* This file is part of the KDE project Copyright (C) 2017 Allan Sandfeld Jensen 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 KTEXTEDITOR_SCRIPTCURSOR_H #define KTEXTEDITOR_SCRIPTCURSOR_H -#include -#include +#include +#include #include "ktexteditor/cursor.h" inline QJSValue cursorToScriptValue(QJSEngine *engine, const KTextEditor::Cursor &cursor) { QString code = QStringLiteral("new Cursor(%1, %2);").arg(cursor.line()).arg(cursor.column()); QJSValue result = engine->evaluate(code); Q_ASSERT(!result.isError()); return result; } inline KTextEditor::Cursor cursorFromScriptValue(const QJSValue &obj) { KTextEditor::Cursor cursor; QJSValue line = obj.property(QStringLiteral("line")); QJSValue column = obj.property(QStringLiteral("column")); Q_ASSERT(!line.isError() && !column.isError()); cursor.setPosition(line.toInt(), column.toInt()); return cursor; } #endif diff --git a/src/script/scriptrange.h b/src/script/scriptrange.h index 098f816b..5b56b392 100644 --- a/src/script/scriptrange.h +++ b/src/script/scriptrange.h @@ -1,49 +1,49 @@ /* This file is part of the KDE project Copyright (C) 2017 Allan Sandfeld Jensen 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 KTEXTEDITOR_SCRIPTRANGE_H #define KTEXTEDITOR_SCRIPTRANGE_H -#include -#include +#include +#include #include "ktexteditor/range.h" inline QJSValue rangeToScriptValue(QJSEngine *engine, const KTextEditor::Range &range) { QString code = QStringLiteral("new Range(%1, %2, %3, %4);").arg(range.start().line()) .arg(range.start().column()) .arg(range.end().line()) .arg(range.end().column()); QJSValue result = engine->evaluate(code); Q_ASSERT(!result.isError()); return result; } inline KTextEditor::Range rangeFromScriptValue(const QJSValue &obj) { KTextEditor::Range range; range.setRange(KTextEditor::Cursor(obj.property(QStringLiteral("start")).property(QStringLiteral("line")).toInt(), obj.property(QStringLiteral("start")).property(QStringLiteral("column")).toInt()), KTextEditor::Cursor(obj.property(QStringLiteral("end")).property(QStringLiteral("line")).toInt(), obj.property(QStringLiteral("end")).property(QStringLiteral("column")).toInt())); return range; } #endif diff --git a/src/search/kateregexp.cpp b/src/search/kateregexp.cpp index 7399c3e6..1894b71c 100644 --- a/src/search/kateregexp.cpp +++ b/src/search/kateregexp.cpp @@ -1,305 +1,301 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2009 Bernhard Beschow * Copyright (C) 2007 Sebastian Pipping * * 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 "kateregexp.h" KateRegExp::KateRegExp(const QString &pattern, Qt::CaseSensitivity cs, QRegExp::PatternSyntax syntax) : m_regExp(pattern, cs, syntax) { } // these things can besides '.' and '\s' make apptern multi-line: // \n, \x000A, \x????-\x????, \0012, \0???-\0??? // a multi-line pattern must not pass as single-line, the other // way around will just result in slower searches and is therefore // not as critical int KateRegExp::repairPattern(bool &stillMultiLine) { const QString &text = pattern(); // read-only input for parsing // get input const int inputLen = text.length(); int input = 0; // walker index // prepare output QString output; output.reserve(2 * inputLen + 1); // twice should be enough for the average case // parser state stillMultiLine = false; int replaceCount = 0; bool insideClass = false; while (input < inputLen) { if (insideClass) { // wait for closing, unescaped ']' switch (text[input].unicode()) { case L'\\': switch (text[input + 1].unicode()) { case L'x': if (input + 5 < inputLen) { // copy "\x????" unmodified output.append(text.midRef(input, 6)); input += 6; } else { // copy "\x" unmodified output.append(text.midRef(input, 2)); input += 2; } stillMultiLine = true; break; case L'0': if (input + 4 < inputLen) { // copy "\0???" unmodified output.append(text.midRef(input, 5)); input += 5; } else { // copy "\0" unmodified output.append(text.midRef(input, 2)); input += 2; } stillMultiLine = true; break; case L's': // replace "\s" with "[ \t]" output.append(QLatin1String(" \\t")); input += 2; replaceCount++; break; case L'n': stillMultiLine = true; // FALLTROUGH -#if QT_VERSION >= QT_VERSION_CHECK(5,8,0) Q_FALLTHROUGH(); -#endif default: // copy "\?" unmodified output.append(text.midRef(input, 2)); input += 2; } break; case L']': // copy "]" unmodified insideClass = false; output.append(text[input]); input++; break; default: // copy "?" unmodified output.append(text[input]); input++; } } else { // search for real dots and \S switch (text[input].unicode()) { case L'\\': switch (text[input + 1].unicode()) { case L'x': if (input + 5 < inputLen) { // copy "\x????" unmodified output.append(text.midRef(input, 6)); input += 6; } else { // copy "\x" unmodified output.append(text.midRef(input, 2)); input += 2; } stillMultiLine = true; break; case L'0': if (input + 4 < inputLen) { // copy "\0???" unmodified output.append(text.midRef(input, 5)); input += 5; } else { // copy "\0" unmodified output.append(text.midRef(input, 2)); input += 2; } stillMultiLine = true; break; case L's': // replace "\s" with "[ \t]" output.append(QLatin1String("[ \\t]")); input += 2; replaceCount++; break; case L'n': stillMultiLine = true; // FALLTROUGH -#if QT_VERSION >= QT_VERSION_CHECK(5,8,0) Q_FALLTHROUGH(); -#endif default: // copy "\?" unmodified output.append(text.midRef(input, 2)); input += 2; } break; case L'.': // replace " with "[^\n]" output.append(QLatin1String("[^\\n]")); input++; replaceCount++; break; case L'[': // copy "]" unmodified insideClass = true; output.append(text[input]); input++; break; default: // copy "?" unmodified output.append(text[input]); input++; } } } // Overwrite with repaired pattern m_regExp.setPattern(output); return replaceCount; } bool KateRegExp::isMultiLine() const { const QString &text = pattern(); // parser state bool insideClass = false; for (int input = 0; input < text.length(); /*empty*/) { if (insideClass) { // wait for closing, unescaped ']' switch (text[input].unicode()) { case L'\\': switch (text[input + 1].unicode()) { case L'x': return true; case L'0': return true; case L's': // replace "\s" with "[ \t]" input += 2; break; case L'n': return true; // FALLTROUGH default: // copy "\?" unmodified input += 2; } break; case L']': // copy "]" unmodified insideClass = false; input++; break; default: // copy "?" unmodified input++; } } else { // search for real dots and \S switch (text[input].unicode()) { case L'\\': switch (text[input + 1].unicode()) { case L'x': return true; case L'0': return true; case L's': // replace "\s" with "[ \t]" input += 2; break; case L'n': return true; default: // copy "\?" unmodified input += 2; } break; case L'.': // replace " with "[^\n]" input++; break; case L'[': // copy "]" unmodified insideClass = true; input++; break; default: // copy "?" unmodified input++; } } } return false; } int KateRegExp::indexIn(const QString &str, int start, int end) const { return m_regExp.indexIn(str.left(end), start, QRegExp::CaretAtZero); } int KateRegExp::lastIndexIn(const QString &str, int start, int end) const { const int index = m_regExp.lastIndexIn(str.mid(start, end - start), -1, QRegExp::CaretAtZero); if (index == -1) { return -1; } const int index2 = m_regExp.indexIn(str.left(end), start + index, QRegExp::CaretAtZero); return index2; } diff --git a/src/search/katesearchbar.cpp b/src/search/katesearchbar.cpp index 45ee13c4..b2fe961c 100644 --- a/src/search/katesearchbar.cpp +++ b/src/search/katesearchbar.cpp @@ -1,1656 +1,1653 @@ /* This file is part of the KDE libraries Copyright (C) 2009-2010 Bernhard Beschow Copyright (C) 2007 Sebastian Pipping Copyright (C) 2007 Matthew Woehlke Copyright (C) 2007 Thomas Friedrichsmeier This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "katesearchbar.h" #include "kateregexp.h" #include "katematch.h" #include "kateview.h" #include "katedocument.h" #include "kateundomanager.h" #include "kateconfig.h" #include "katerenderer.h" #include "kateglobal.h" #include #include #include #include "ui_searchbarincremental.h" #include "ui_searchbarpower.h" #include #include #include #include #include #include #include #include #include #include // Turn debug messages on/off here // #define FAST_DEBUG_ENABLE #ifdef FAST_DEBUG_ENABLE # define FAST_DEBUG(x) qCDebug(LOG_KTE) << x #else # define FAST_DEBUG(x) #endif using namespace KTextEditor; namespace { class AddMenuManager { private: QVector m_insertBefore; QVector m_insertAfter; QSet m_actionPointers; uint m_indexWalker; QMenu *m_menu; public: AddMenuManager(QMenu *parent, int expectedItemCount) : m_insertBefore(QVector(expectedItemCount)), m_insertAfter(QVector(expectedItemCount)), m_indexWalker(0), m_menu(nullptr) { Q_ASSERT(parent != nullptr); m_menu = parent->addMenu(i18n("Add...")); if (m_menu == nullptr) { return; } m_menu->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); } void enableMenu(bool enabled) { if (m_menu == nullptr) { return; } m_menu->setEnabled(enabled); } void addEntry(const QString &before, const QString after, const QString description, const QString &realBefore = QString(), const QString &realAfter = QString()) { if (m_menu == nullptr) { return; } QAction *const action = m_menu->addAction(before + after + QLatin1Char('\t') + description); m_insertBefore[m_indexWalker] = QString(realBefore.isEmpty() ? before : realBefore); m_insertAfter[m_indexWalker] = QString(realAfter.isEmpty() ? after : realAfter); action->setData(QVariant(m_indexWalker++)); m_actionPointers.insert(action); } void addSeparator() { if (m_menu == nullptr) { return; } m_menu->addSeparator(); } void handle(QAction *action, QLineEdit *lineEdit) { if (!m_actionPointers.contains(action)) { return; } const int cursorPos = lineEdit->cursorPosition(); const int index = action->data().toUInt(); const QString &before = m_insertBefore[index]; const QString &after = m_insertAfter[index]; lineEdit->insert(before + after); lineEdit->setCursorPosition(cursorPos + before.count()); lineEdit->setFocus(); } }; } // anon namespace KateSearchBar::KateSearchBar(bool initAsPower, KTextEditor::ViewPrivate *view, KateViewConfig *config) : KateViewBarWidget(true, view), m_view(view), m_config(config), m_layout(new QVBoxLayout()), m_widget(nullptr), m_incUi(nullptr), m_incInitCursor(view->cursorPosition()), m_powerUi(nullptr), highlightMatchAttribute(new Attribute()), highlightReplacementAttribute(new Attribute()), m_incHighlightAll(false), m_incFromCursor(true), m_incMatchCase(false), m_powerMatchCase(true), m_powerFromCursor(false), m_powerHighlightAll(false), m_powerMode(0) { connect(view, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(updateIncInitCursor())); // init match attribute Attribute::Ptr mouseInAttribute(new Attribute()); mouseInAttribute->setFontBold(true); highlightMatchAttribute->setDynamicAttribute(Attribute::ActivateMouseIn, mouseInAttribute); Attribute::Ptr caretInAttribute(new Attribute()); caretInAttribute->setFontItalic(true); highlightMatchAttribute->setDynamicAttribute(Attribute::ActivateCaretIn, caretInAttribute); updateHighlightColors(); // Modify parent QWidget *const widget = centralWidget(); widget->setLayout(m_layout); m_layout->setMargin(0); // allow to have small size, for e.g. Kile setMinimumWidth(100); // Copy global to local config backup const long searchFlags = m_config->searchFlags(); m_incHighlightAll = (searchFlags & KateViewConfig::IncHighlightAll) != 0; m_incFromCursor = (searchFlags & KateViewConfig::IncFromCursor) != 0; m_incMatchCase = (searchFlags & KateViewConfig::IncMatchCase) != 0; m_powerMatchCase = (searchFlags & KateViewConfig::PowerMatchCase) != 0; m_powerFromCursor = (searchFlags & KateViewConfig::PowerFromCursor) != 0; m_powerHighlightAll = (searchFlags & KateViewConfig::PowerHighlightAll) != 0; m_powerMode = ((searchFlags & KateViewConfig::PowerModeRegularExpression) != 0) ? MODE_REGEX : (((searchFlags & KateViewConfig::PowerModeEscapeSequences) != 0) ? MODE_ESCAPE_SEQUENCES : (((searchFlags & KateViewConfig::PowerModeWholeWords) != 0) ? MODE_WHOLE_WORDS : MODE_PLAIN_TEXT)); // Load one of either dialogs if (initAsPower) { enterPowerMode(); } else { enterIncrementalMode(); } updateSelectionOnly(); connect(view, SIGNAL(selectionChanged(KTextEditor::View*)), this, SLOT(updateSelectionOnly())); } KateSearchBar::~KateSearchBar() { clearHighlights(); delete m_layout; delete m_widget; delete m_incUi; delete m_powerUi; } void KateSearchBar::closed() { // remove search from the view bar, because it vertically bloats up the // stacked layout in KateViewBar. if (viewBar()) { viewBar()->removeBarWidget(this); } clearHighlights(); } void KateSearchBar::setReplacementPattern(const QString &replacementPattern) { Q_ASSERT(isPower()); if (this->replacementPattern() == replacementPattern) { return; } m_powerUi->replacement->setEditText(replacementPattern); } QString KateSearchBar::replacementPattern() const { Q_ASSERT(isPower()); return m_powerUi->replacement->currentText(); } void KateSearchBar::setSearchMode(KateSearchBar::SearchMode mode) { Q_ASSERT(isPower()); m_powerUi->searchMode->setCurrentIndex(mode); } void KateSearchBar::findNext() { const bool found = find(); if (found) { QComboBox *combo = m_powerUi != nullptr ? m_powerUi->pattern : m_incUi->pattern; // Add to search history addCurrentTextToHistory(combo); } } void KateSearchBar::findPrevious() { const bool found = find(SearchBackward); if (found) { QComboBox *combo = m_powerUi != nullptr ? m_powerUi->pattern : m_incUi->pattern; // Add to search history addCurrentTextToHistory(combo); } } void KateSearchBar::showInfoMessage(const QString &text) { delete m_infoMessage; m_infoMessage = new KTextEditor::Message(text, KTextEditor::Message::Positive); m_infoMessage->setPosition(KTextEditor::Message::BottomInView); m_infoMessage->setAutoHide(3000); // 3 seconds m_infoMessage->setView(m_view); m_view->doc()->postMessage(m_infoMessage); } void KateSearchBar::highlightMatch(const Range &range) { KTextEditor::MovingRange *const highlight = m_view->doc()->newMovingRange(range, Kate::TextRange::DoNotExpand); highlight->setView(m_view); // show only in this view highlight->setAttributeOnlyForViews(true); // use z depth defined in moving ranges interface highlight->setZDepth(-10000.0); highlight->setAttribute(highlightMatchAttribute); m_hlRanges.append(highlight); } void KateSearchBar::highlightReplacement(const Range &range) { KTextEditor::MovingRange *const highlight = m_view->doc()->newMovingRange(range, Kate::TextRange::DoNotExpand); highlight->setView(m_view); // show only in this view highlight->setAttributeOnlyForViews(true); // use z depth defined in moving ranges interface highlight->setZDepth(-10000.0); highlight->setAttribute(highlightReplacementAttribute); m_hlRanges.append(highlight); } void KateSearchBar::indicateMatch(MatchResult matchResult) { QLineEdit *const lineEdit = isPower() ? m_powerUi->pattern->lineEdit() : m_incUi->pattern->lineEdit(); QPalette background(lineEdit->palette()); switch (matchResult) { case MatchFound: // FALLTHROUGH case MatchWrappedForward: case MatchWrappedBackward: // Green background for line edit KColorScheme::adjustBackground(background, KColorScheme::PositiveBackground); break; case MatchMismatch: // Red background for line edit KColorScheme::adjustBackground(background, KColorScheme::NegativeBackground); break; case MatchNothing: // Reset background of line edit background = QPalette(); break; case MatchNeutral: KColorScheme::adjustBackground(background, KColorScheme::NeutralBackground); break; } // Update status label if (m_incUi != nullptr) { QPalette foreground(m_incUi->status->palette()); switch (matchResult) { case MatchFound: // FALLTHROUGH case MatchNothing: KColorScheme::adjustForeground(foreground, KColorScheme::NormalText, QPalette::WindowText, KColorScheme::Window); m_incUi->status->clear(); break; case MatchWrappedForward: case MatchWrappedBackward: KColorScheme::adjustForeground(foreground, KColorScheme::NormalText, QPalette::WindowText, KColorScheme::Window); if (matchResult == MatchWrappedBackward) { m_incUi->status->setText(i18n("Reached top, continued from bottom")); } else { m_incUi->status->setText(i18n("Reached bottom, continued from top")); } break; case MatchMismatch: KColorScheme::adjustForeground(foreground, KColorScheme::NegativeText, QPalette::WindowText, KColorScheme::Window); m_incUi->status->setText(i18n("Not found")); break; case MatchNeutral: /* do nothing */ break; } m_incUi->status->setPalette(foreground); } lineEdit->setPalette(background); } /*static*/ void KateSearchBar::selectRange(KTextEditor::ViewPrivate *view, const KTextEditor::Range &range) { view->setCursorPositionInternal(range.end()); view->setSelection(range); } void KateSearchBar::selectRange2(const KTextEditor::Range &range) { disconnect(m_view, SIGNAL(selectionChanged(KTextEditor::View*)), this, SLOT(updateSelectionOnly())); selectRange(m_view, range); connect(m_view, SIGNAL(selectionChanged(KTextEditor::View*)), this, SLOT(updateSelectionOnly())); } void KateSearchBar::onIncPatternChanged(const QString &pattern) { if (!m_incUi) { return; } // clear prior highlightings (deletes info message if present) clearHighlights(); m_incUi->next->setDisabled(pattern.isEmpty()); m_incUi->prev->setDisabled(pattern.isEmpty()); KateMatch match(m_view->doc(), searchOptions()); if (!pattern.isEmpty()) { // Find, first try const Range inputRange = KTextEditor::Range(m_incInitCursor, m_view->document()->documentEnd()); match.searchText(inputRange, pattern); } const bool wrap = !match.isValid() && !pattern.isEmpty(); if (wrap) { // Find, second try const KTextEditor::Range inputRange = m_view->document()->documentRange(); match.searchText(inputRange, pattern); } const MatchResult matchResult = match.isValid() ? (wrap ? MatchWrappedForward : MatchFound) : pattern.isEmpty() ? MatchNothing : MatchMismatch; const Range selectionRange = pattern.isEmpty() ? Range(m_incInitCursor, m_incInitCursor) : match.isValid() ? match.range() : Range::invalid(); // don't update m_incInitCursor when we move the cursor disconnect(m_view, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(updateIncInitCursor())); selectRange2(selectionRange); connect(m_view, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(updateIncInitCursor())); indicateMatch(matchResult); } void KateSearchBar::setMatchCase(bool matchCase) { if (this->matchCase() == matchCase) { return; } if (isPower()) { m_powerUi->matchCase->setChecked(matchCase); } else { m_incUi->matchCase->setChecked(matchCase); } } void KateSearchBar::onMatchCaseToggled(bool /*matchCase*/) { sendConfig(); if (m_incUi != nullptr) { // Re-search with new settings const QString pattern = m_incUi->pattern->currentText(); onIncPatternChanged(pattern); } else { indicateMatch(MatchNothing); } } bool KateSearchBar::matchCase() const { return isPower() ? m_powerUi->matchCase->isChecked() : m_incUi->matchCase->isChecked(); } void KateSearchBar::fixForSingleLine(Range &range, SearchDirection searchDirection) { FAST_DEBUG("Single-line workaround checking BEFORE" << range); if (searchDirection == SearchForward) { const int line = range.start().line(); const int col = range.start().column(); const int maxColWithNewline = m_view->document()->lineLength(line) + 1; if (col == maxColWithNewline) { FAST_DEBUG("Starting on a newline" << range); const int maxLine = m_view->document()->lines() - 1; if (line < maxLine) { range.setRange(Cursor(line + 1, 0), range.end()); FAST_DEBUG("Search range fixed to " << range); } else { FAST_DEBUG("Already at last line"); range = Range::invalid(); } } } else { const int col = range.end().column(); if (col == 0) { FAST_DEBUG("Ending after a newline" << range); const int line = range.end().line(); if (line > 0) { const int maxColWithNewline = m_view->document()->lineLength(line - 1); range.setRange(range.start(), Cursor(line - 1, maxColWithNewline)); FAST_DEBUG("Search range fixed to " << range); } else { FAST_DEBUG("Already at first line"); range = Range::invalid(); } } } FAST_DEBUG("Single-line workaround checking AFTER" << range); } void KateSearchBar::onReturnPressed() { const Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers(); const bool shiftDown = (modifiers & Qt::ShiftModifier) != 0; const bool controlDown = (modifiers & Qt::ControlModifier) != 0; if (shiftDown) { // Shift down, search backwards findPrevious(); } else { // Shift up, search forwards findNext(); } if (controlDown) { emit hideMe(); } } bool KateSearchBar::find(SearchDirection searchDirection, const QString *replacement) { // What to find? if (searchPattern().isEmpty()) { return false; // == Pattern error } // don't let selectionChanged signal mess around in this routine disconnect(m_view, SIGNAL(selectionChanged(KTextEditor::View*)), this, SLOT(updateSelectionOnly())); // clear previous highlights if there are any clearHighlights(); const SearchOptions enabledOptions = searchOptions(searchDirection); // Where to find? Range inputRange; const Range selection = m_view->selection() ? m_view->selectionRange() : Range::invalid(); if (selection.isValid()) { if (selectionOnly()) { // First match in selection inputRange = selection; } else { // Next match after/before selection if a match was selected before if (searchDirection == SearchForward) { inputRange.setRange(selection.start(), m_view->document()->documentEnd()); } else { inputRange.setRange(Cursor(0, 0), selection.end()); } } } else { // No selection const Cursor cursorPos = m_view->cursorPosition(); if (searchDirection == SearchForward) { inputRange.setRange(cursorPos, m_view->document()->documentEnd()); } else { inputRange.setRange(Cursor(0, 0), cursorPos); } } FAST_DEBUG("Search range is" << inputRange); { const bool regexMode = enabledOptions.testFlag(Regex); const bool multiLinePattern = regexMode ? KateRegExp(searchPattern()).isMultiLine() : false; // Single-line pattern workaround if (regexMode && !multiLinePattern) { fixForSingleLine(inputRange, searchDirection); } } KateMatch match(m_view->doc(), enabledOptions); Range afterReplace = Range::invalid(); // Find, first try match.searchText(inputRange, searchPattern()); if (match.isValid() && match.range() == selection) { // Same match again if (replacement != nullptr) { // Selection is match -> replace KTextEditor::MovingRange *smartInputRange = m_view->doc()->newMovingRange(inputRange, KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight); afterReplace = match.replace(*replacement, m_view->blockSelection()); inputRange = *smartInputRange; delete smartInputRange; } if (!selectionOnly()) { // Find, second try after old selection if (searchDirection == SearchForward) { const Cursor start = (replacement != nullptr) ? afterReplace.end() : selection.end(); inputRange.setRange(start, inputRange.end()); } else { const Cursor end = (replacement != nullptr) ? afterReplace.start() : selection.start(); inputRange.setRange(inputRange.start(), end); } } // Single-line pattern workaround fixForSingleLine(inputRange, searchDirection); match.searchText(inputRange, searchPattern()); } bool askWrap = !match.isValid() && (!selection.isValid() || !selectionOnly()); bool wrap = false; if (askWrap) { askWrap = false; wrap = true; } if (askWrap) { QString question = searchDirection == SearchForward ? i18n("Bottom of file reached. Continue from top?") : i18n("Top of file reached. Continue from bottom?"); wrap = (KMessageBox::questionYesNo(nullptr, question, i18n("Continue search?"), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("DoNotShowAgainContinueSearchDialog")) == KMessageBox::Yes); } if (wrap) { - // show message widget when wrapping (if not already present) - if (searchDirection == SearchForward && !m_wrappedTopMessage) { - const QString msg = i18n("Continuing search from top"); - m_wrappedTopMessage = new KTextEditor::Message(msg, KTextEditor::Message::Information); - m_wrappedTopMessage->setPosition(KTextEditor::Message::TopInView); - m_wrappedTopMessage->setAutoHide(2000); - m_wrappedTopMessage->setAutoHideMode(KTextEditor::Message::Immediate); - m_wrappedTopMessage->setView(m_view); - m_view->doc()->postMessage(m_wrappedTopMessage); - } else if (searchDirection == SearchBackward && !m_wrappedBottomMessage) { - const QString msg = i18n("Continuing search from bottom"); - m_wrappedBottomMessage = new KTextEditor::Message(msg, KTextEditor::Message::Information); - m_wrappedBottomMessage->setPosition(KTextEditor::Message::BottomInView); - m_wrappedBottomMessage->setAutoHide(2000); - m_wrappedBottomMessage->setAutoHideMode(KTextEditor::Message::Immediate); - m_wrappedBottomMessage->setView(m_view); - m_view->doc()->postMessage(m_wrappedBottomMessage); + // show message widget when wrapping + const QIcon icon = searchDirection == SearchForward + ? QIcon::fromTheme(QStringLiteral("go-down-search")) + : QIcon::fromTheme(QStringLiteral("go-up-search")); + + if (!m_wrappedMessage || m_lastSearchDirection != searchDirection) { + m_lastSearchDirection = searchDirection; + m_wrappedMessage = new KTextEditor::Message(i18n("Search wrapped"), KTextEditor::Message::Positive); + m_wrappedMessage->setIcon(icon); + m_wrappedMessage->setPosition(KTextEditor::Message::CenterInView); + m_wrappedMessage->setAutoHide(2000); + m_wrappedMessage->setAutoHideMode(KTextEditor::Message::Immediate); + m_wrappedMessage->setView(m_view); + m_view->doc()->postMessage(m_wrappedMessage); } inputRange = m_view->document()->documentRange(); match.searchText(inputRange, searchPattern()); } if (match.isValid()) { selectRange2(match.range()); } const MatchResult matchResult = !match.isValid() ? MatchMismatch : !wrap ? MatchFound : searchDirection == SearchForward ? MatchWrappedForward : MatchWrappedBackward; indicateMatch(matchResult); // highlight replacements if applicable if (afterReplace.isValid()) { highlightReplacement(afterReplace); } // restore connection connect(m_view, SIGNAL(selectionChanged(KTextEditor::View*)), this, SLOT(updateSelectionOnly())); return true; // == No pattern error } void KateSearchBar::findAll() { // clear highlightings of prior search&replace action clearHighlights(); Range inputRange = (m_view->selection() && selectionOnly()) ? m_view->selectionRange() : m_view->document()->documentRange(); const int occurrences = findAll(inputRange, nullptr); // send passive notification to view showInfoMessage(i18ncp("short translation", "1 match found", "%1 matches found", occurrences)); indicateMatch(occurrences > 0 ? MatchFound : MatchMismatch); } void KateSearchBar::onPowerPatternChanged(const QString & /*pattern*/) { givePatternFeedback(); indicateMatch(MatchNothing); } bool KateSearchBar::isPatternValid() const { if (searchPattern().isEmpty()) { return false; } return searchOptions().testFlag(WholeWords) ? searchPattern().trimmed() == searchPattern() : searchOptions().testFlag(Regex) ? QRegExp(searchPattern()).isValid() : true; } void KateSearchBar::givePatternFeedback() { // Enable/disable next/prev and replace next/all m_powerUi->findNext->setEnabled(isPatternValid()); m_powerUi->findPrev->setEnabled(isPatternValid()); m_powerUi->replaceNext->setEnabled(isPatternValid()); m_powerUi->replaceAll->setEnabled(isPatternValid()); m_powerUi->findAll->setEnabled(isPatternValid()); } void KateSearchBar::addCurrentTextToHistory(QComboBox *combo) { const QString text = combo->currentText(); const int index = combo->findText(text); if (index > 0) { combo->removeItem(index); } if (index != 0) { combo->insertItem(0, text); combo->setCurrentIndex(0); } // sync to application config KTextEditor::EditorPrivate::self()->saveSearchReplaceHistoryModels(); } void KateSearchBar::backupConfig(bool ofPower) { if (ofPower) { m_powerMatchCase = m_powerUi->matchCase->isChecked(); m_powerMode = m_powerUi->searchMode->currentIndex(); } else { m_incMatchCase = m_incUi->matchCase->isChecked(); } } void KateSearchBar::sendConfig() { const long pastFlags = m_config->searchFlags(); long futureFlags = pastFlags; if (m_powerUi != nullptr) { const bool OF_POWER = true; backupConfig(OF_POWER); // Update power search flags only const long incFlagsOnly = pastFlags & (KateViewConfig::IncHighlightAll | KateViewConfig::IncFromCursor | KateViewConfig::IncMatchCase); futureFlags = incFlagsOnly | (m_powerMatchCase ? KateViewConfig::PowerMatchCase : 0) | (m_powerFromCursor ? KateViewConfig::PowerFromCursor : 0) | (m_powerHighlightAll ? KateViewConfig::PowerHighlightAll : 0) | ((m_powerMode == MODE_REGEX) ? KateViewConfig::PowerModeRegularExpression : ((m_powerMode == MODE_ESCAPE_SEQUENCES) ? KateViewConfig::PowerModeEscapeSequences : ((m_powerMode == MODE_WHOLE_WORDS) ? KateViewConfig::PowerModeWholeWords : KateViewConfig::PowerModePlainText))); } else if (m_incUi != nullptr) { const bool OF_INCREMENTAL = false; backupConfig(OF_INCREMENTAL); // Update incremental search flags only const long powerFlagsOnly = pastFlags & (KateViewConfig::PowerMatchCase | KateViewConfig::PowerFromCursor | KateViewConfig::PowerHighlightAll | KateViewConfig::PowerModeRegularExpression | KateViewConfig::PowerModeEscapeSequences | KateViewConfig::PowerModeWholeWords | KateViewConfig::PowerModePlainText); futureFlags = powerFlagsOnly | (m_incHighlightAll ? KateViewConfig::IncHighlightAll : 0) | (m_incFromCursor ? KateViewConfig::IncFromCursor : 0) | (m_incMatchCase ? KateViewConfig::IncMatchCase : 0); } // Adjust global config m_config->setSearchFlags(futureFlags); } void KateSearchBar::replaceNext() { const QString replacement = m_powerUi->replacement->currentText(); if (find(SearchForward, &replacement)) { // Never merge replace actions with other replace actions/user actions m_view->doc()->undoManager()->undoSafePoint(); // Add to search history addCurrentTextToHistory(m_powerUi->pattern); // Add to replace history addCurrentTextToHistory(m_powerUi->replacement); } } // replacement == NULL --> Highlight all matches // replacement != NULL --> Replace and highlight all matches int KateSearchBar::findAll(Range inputRange, const QString *replacement) { // don't let selectionChanged signal mess around in this routine disconnect(m_view, SIGNAL(selectionChanged(KTextEditor::View*)), this, SLOT(updateSelectionOnly())); const SearchOptions enabledOptions = searchOptions(SearchForward); const bool regexMode = enabledOptions.testFlag(Regex); const bool multiLinePattern = regexMode ? KateRegExp(searchPattern()).isMultiLine() : false; KTextEditor::MovingRange *workingRange = m_view->doc()->newMovingRange(inputRange); QVector highlightRanges; int matchCounter = 0; bool block = m_view->selection() && m_view->blockSelection(); int line = inputRange.start().line(); do { if (block) { workingRange = m_view->doc()->newMovingRange(m_view->doc()->rangeOnLine(inputRange, line)); } for (;;) { KateMatch match(m_view->doc(), enabledOptions); match.searchText(*workingRange, searchPattern()); if (!match.isValid()) { break; } bool const originalMatchEmpty = match.isEmpty(); // Work with the match if (replacement != nullptr) { if (matchCounter == 0) { static_cast(m_view->document())->startEditing(); } // Replace const Range afterReplace = match.replace(*replacement, false, ++matchCounter); // Highlight and continue after adjusted match //highlightReplacement(*afterReplace); highlightRanges << afterReplace; } else { // Highlight and continue after original match //highlightMatch(match); highlightRanges << match.range(); matchCounter++; } // Continue after match if (highlightRanges.last().end() >= workingRange->end()) { break; } KTextEditor::DocumentCursor workingStart(m_view->doc(), highlightRanges.last().end()); if (originalMatchEmpty) { // Can happen for regex patterns like "^". // If we don't advance here we will loop forever... workingStart.move(1); } else if (regexMode && !multiLinePattern && workingStart.atEndOfLine()) { // single-line regexps might match the naked line end // therefore we better advance to the next line workingStart.move(1); } workingRange->setRange(workingStart.toCursor(), workingRange->end()); // Are we done? if (!workingRange->toRange().isValid() || workingStart.atEndOfDocument()) { break; } } } while (block && ++line <= inputRange.end().line()); // After last match if (matchCounter > 0) { if (replacement != nullptr) { static_cast(m_view->document())->finishEditing(); } } // Add ScrollBarMarks KTextEditor::MarkInterface* iface = qobject_cast(m_view->document()); if (iface) { iface->setMarkDescription(KTextEditor::MarkInterface::SearchMatch, i18n("SearchHighLight")); iface->setMarkPixmap(KTextEditor::MarkInterface::SearchMatch, QIcon().pixmap(0,0)); foreach (Range r, highlightRanges) { iface->addMark(r.start().line(), KTextEditor::MarkInterface::SearchMatch); } } // Add highlights if (replacement == nullptr) { QVector cursors; foreach (Range r, highlightRanges) { cursors << r.end(); } m_view->setSelections(highlightRanges, cursors); m_view->setFocus(); } else { foreach (Range r, highlightRanges) { highlightReplacement(r); } } delete workingRange; // restore connection connect(m_view, SIGNAL(selectionChanged(KTextEditor::View*)), this, SLOT(updateSelectionOnly())); return matchCounter; } void KateSearchBar::replaceAll() { // clear prior highlightings (deletes info message if present) clearHighlights(); // What to find/replace? const QString replacement = m_powerUi->replacement->currentText(); // Where to replace? Range selection; const bool selected = m_view->selection(); Range inputRange = (selected && selectionOnly()) ? m_view->selectionRange() : m_view->document()->documentRange(); // Pass on the hard work int replacementsDone = findAll(inputRange, &replacement); // send passive notification to view showInfoMessage(i18ncp("short translation", "1 replacement made", "%1 replacements made", replacementsDone)); // Never merge replace actions with other replace actions/user actions m_view->doc()->undoManager()->undoSafePoint(); // Add to search history addCurrentTextToHistory(m_powerUi->pattern); // Add to replace history addCurrentTextToHistory(m_powerUi->replacement); } void KateSearchBar::setSearchPattern(const QString &searchPattern) { if (searchPattern == this->searchPattern()) { return; } if (isPower()) { m_powerUi->pattern->setEditText(searchPattern); } else { m_incUi->pattern->setEditText(searchPattern); } } QString KateSearchBar::searchPattern() const { return (m_powerUi != nullptr) ? m_powerUi->pattern->currentText() : m_incUi->pattern->currentText(); } void KateSearchBar::setSelectionOnly(bool selectionOnly) { if (this->selectionOnly() == selectionOnly) { return; } if (isPower()) { m_powerUi->selectionOnly->setChecked(selectionOnly); } } bool KateSearchBar::selectionOnly() const { return isPower() ? m_powerUi->selectionOnly->isChecked() : false; } KTextEditor::SearchOptions KateSearchBar::searchOptions(SearchDirection searchDirection) const { SearchOptions enabledOptions = KTextEditor::Default; if (!matchCase()) { enabledOptions |= CaseInsensitive; } if (searchDirection == SearchBackward) { enabledOptions |= Backwards; } if (m_powerUi != nullptr) { switch (m_powerUi->searchMode->currentIndex()) { case MODE_WHOLE_WORDS: enabledOptions |= WholeWords; break; case MODE_ESCAPE_SEQUENCES: enabledOptions |= EscapeSequences; break; case MODE_REGEX: enabledOptions |= Regex; break; case MODE_PLAIN_TEXT: // FALLTHROUGH default: break; } } return enabledOptions; } struct ParInfo { int openIndex; bool capturing; int captureNumber; // 1..9 }; QVector KateSearchBar::getCapturePatterns(const QString &pattern) const { QVector capturePatterns; capturePatterns.reserve(9); QStack parInfos; const int inputLen = pattern.length(); int input = 0; // walker index bool insideClass = false; int captureCount = 0; while (input < inputLen) { if (insideClass) { // Wait for closing, unescaped ']' if (pattern[input].unicode() == L']') { insideClass = false; } input++; } else { switch (pattern[input].unicode()) { case L'\\': // Skip this and any next character input += 2; break; case L'(': ParInfo curInfo; curInfo.openIndex = input; curInfo.capturing = (input + 1 >= inputLen) || (pattern[input + 1].unicode() != '?'); if (curInfo.capturing) { captureCount++; } curInfo.captureNumber = captureCount; parInfos.push(curInfo); input++; break; case L')': if (!parInfos.empty()) { ParInfo &top = parInfos.top(); if (top.capturing && (top.captureNumber <= 9)) { const int start = top.openIndex + 1; const int len = input - start; if (capturePatterns.size() < top.captureNumber) { capturePatterns.resize(top.captureNumber); } capturePatterns[top.captureNumber - 1] = pattern.mid(start, len); } parInfos.pop(); } input++; break; case L'[': input++; insideClass = true; break; default: input++; break; } } } return capturePatterns; } void KateSearchBar::showExtendedContextMenu(bool forPattern, const QPoint &pos) { // Make original menu QComboBox *comboBox = forPattern ? m_powerUi->pattern : m_powerUi->replacement; QMenu *const contextMenu = comboBox->lineEdit()->createStandardContextMenu(); if (contextMenu == nullptr) { return; } bool extendMenu = false; bool regexMode = false; switch (m_powerUi->searchMode->currentIndex()) { case MODE_REGEX: regexMode = true; // FALLTHROUGH case MODE_ESCAPE_SEQUENCES: extendMenu = true; break; default: break; } AddMenuManager addMenuManager(contextMenu, 37); if (!extendMenu) { addMenuManager.enableMenu(extendMenu); } else { // Build menu if (forPattern) { if (regexMode) { addMenuManager.addEntry(QStringLiteral("^"), QString(), i18n("Beginning of line")); addMenuManager.addEntry(QStringLiteral("$"), QString(), i18n("End of line")); addMenuManager.addSeparator(); addMenuManager.addEntry(QStringLiteral("."), QString(), i18n("Any single character (excluding line breaks)")); addMenuManager.addSeparator(); addMenuManager.addEntry(QStringLiteral("+"), QString(), i18n("One or more occurrences")); addMenuManager.addEntry(QStringLiteral("*"), QString(), i18n("Zero or more occurrences")); addMenuManager.addEntry(QStringLiteral("?"), QString(), i18n("Zero or one occurrences")); addMenuManager.addEntry(QStringLiteral("{a"), QStringLiteral(",b}"), i18n(" through occurrences"), QStringLiteral("{"), QStringLiteral(",}")); addMenuManager.addSeparator(); addMenuManager.addEntry(QStringLiteral("("), QStringLiteral(")"), i18n("Group, capturing")); addMenuManager.addEntry(QStringLiteral("|"), QString(), i18n("Or")); addMenuManager.addEntry(QStringLiteral("["), QStringLiteral("]"), i18n("Set of characters")); addMenuManager.addEntry(QStringLiteral("[^"), QStringLiteral("]"), i18n("Negative set of characters")); addMenuManager.addSeparator(); } } else { addMenuManager.addEntry(QStringLiteral("\\0"), QString(), i18n("Whole match reference")); addMenuManager.addSeparator(); if (regexMode) { const QString pattern = m_powerUi->pattern->currentText(); const QVector capturePatterns = getCapturePatterns(pattern); const int captureCount = capturePatterns.count(); for (int i = 1; i <= 9; i++) { const QString number = QString::number(i); const QString &captureDetails = (i <= captureCount) ? (QString::fromLatin1(" = (") + capturePatterns[i - 1].left(30)) + QLatin1String(")") : QString(); addMenuManager.addEntry(QLatin1String("\\") + number, QString(), i18n("Reference") + QLatin1Char(' ') + number + captureDetails); } addMenuManager.addSeparator(); } } addMenuManager.addEntry(QStringLiteral("\\n"), QString(), i18n("Line break")); addMenuManager.addEntry(QStringLiteral("\\t"), QString(), i18n("Tab")); if (forPattern && regexMode) { addMenuManager.addEntry(QStringLiteral("\\b"), QString(), i18n("Word boundary")); addMenuManager.addEntry(QStringLiteral("\\B"), QString(), i18n("Not word boundary")); addMenuManager.addEntry(QStringLiteral("\\d"), QString(), i18n("Digit")); addMenuManager.addEntry(QStringLiteral("\\D"), QString(), i18n("Non-digit")); addMenuManager.addEntry(QStringLiteral("\\s"), QString(), i18n("Whitespace (excluding line breaks)")); addMenuManager.addEntry(QStringLiteral("\\S"), QString(), i18n("Non-whitespace (excluding line breaks)")); addMenuManager.addEntry(QStringLiteral("\\w"), QString(), i18n("Word character (alphanumerics plus '_')")); addMenuManager.addEntry(QStringLiteral("\\W"), QString(), i18n("Non-word character")); } addMenuManager.addEntry(QStringLiteral("\\0???"), QString(), i18n("Octal character 000 to 377 (2^8-1)"), QStringLiteral("\\0")); addMenuManager.addEntry(QStringLiteral("\\x????"), QString(), i18n("Hex character 0000 to FFFF (2^16-1)"), QStringLiteral("\\x")); addMenuManager.addEntry(QStringLiteral("\\\\"), QString(), i18n("Backslash")); if (forPattern && regexMode) { addMenuManager.addSeparator(); addMenuManager.addEntry(QStringLiteral("(?:E"), QStringLiteral(")"), i18n("Group, non-capturing"), QStringLiteral("(?:")); addMenuManager.addEntry(QStringLiteral("(?=E"), QStringLiteral(")"), i18n("Lookahead"), QStringLiteral("(?=")); addMenuManager.addEntry(QStringLiteral("(?!E"), QStringLiteral(")"), i18n("Negative lookahead"), QStringLiteral("(?!")); } if (!forPattern) { addMenuManager.addSeparator(); addMenuManager.addEntry(QStringLiteral("\\L"), QString(), i18n("Begin lowercase conversion")); addMenuManager.addEntry(QStringLiteral("\\U"), QString(), i18n("Begin uppercase conversion")); addMenuManager.addEntry(QStringLiteral("\\E"), QString(), i18n("End case conversion")); addMenuManager.addEntry(QStringLiteral("\\l"), QString(), i18n("Lowercase first character conversion")); addMenuManager.addEntry(QStringLiteral("\\u"), QString(), i18n("Uppercase first character conversion")); addMenuManager.addEntry(QStringLiteral("\\#[#..]"), QString(), i18n("Replacement counter (for Replace All)"), QStringLiteral("\\#")); } } // Show menu QAction *const result = contextMenu->exec(comboBox->mapToGlobal(pos)); if (result != nullptr) { addMenuManager.handle(result, comboBox->lineEdit()); } } void KateSearchBar::onPowerModeChanged(int /*index*/) { if (m_powerUi->searchMode->currentIndex() == MODE_REGEX) { m_powerUi->matchCase->setChecked(true); } sendConfig(); indicateMatch(MatchNothing); givePatternFeedback(); } /*static*/ void KateSearchBar::nextMatchForSelection(KTextEditor::ViewPrivate *view, SearchDirection searchDirection) { const bool selected = view->selection(); if (selected) { const QString pattern = view->selectionText(); // How to find? SearchOptions enabledOptions(KTextEditor::Default); if (searchDirection == SearchBackward) { enabledOptions |= Backwards; } // Where to find? const Range selRange = view->selectionRange(); Range inputRange; if (searchDirection == SearchForward) { inputRange.setRange(selRange.end(), view->doc()->documentEnd()); } else { inputRange.setRange(Cursor(0, 0), selRange.start()); } // Find, first try KateMatch match(view->doc(), enabledOptions); match.searchText(inputRange, pattern); if (match.isValid()) { selectRange(view, match.range()); } else { // Find, second try if (searchDirection == SearchForward) { inputRange.setRange(Cursor(0, 0), selRange.start()); } else { inputRange.setRange(selRange.end(), view->doc()->documentEnd()); } KateMatch match2(view->doc(), enabledOptions); match2.searchText(inputRange, pattern); if (match2.isValid()) { selectRange(view, match2.range()); } } } else { // Select current word so we can search for that the next time const Cursor cursorPos = view->cursorPosition(); KTextEditor::Range wordRange = view->document()->wordRangeAt(cursorPos); if (wordRange.isValid()) { selectRange(view, wordRange); } } } void KateSearchBar::enterPowerMode() { QString initialPattern; bool selectionOnly = false; // Guess settings from context: init pattern with current selection const bool selected = m_view->selection(); if (selected) { const Range &selection = m_view->selectionRange(); if (selection.onSingleLine()) { // ... with current selection initialPattern = m_view->selectionText(); } else { // Enable selection only selectionOnly = true; } } // If there's no new selection, we'll use the existing pattern if (initialPattern.isNull()) { // Coming from power search? const bool fromReplace = (m_powerUi != nullptr) && (m_widget->isVisible()); if (fromReplace) { QLineEdit *const patternLineEdit = m_powerUi->pattern->lineEdit(); Q_ASSERT(patternLineEdit != nullptr); patternLineEdit->selectAll(); m_powerUi->pattern->setFocus(Qt::MouseFocusReason); return; } // Coming from incremental search? const bool fromIncremental = (m_incUi != nullptr) && (m_widget->isVisible()); if (fromIncremental) { initialPattern = m_incUi->pattern->currentText(); } } // Create dialog const bool create = (m_powerUi == nullptr); if (create) { // Kill incremental widget if (m_incUi != nullptr) { // Backup current settings const bool OF_INCREMENTAL = false; backupConfig(OF_INCREMENTAL); // Kill widget delete m_incUi; m_incUi = nullptr; m_layout->removeWidget(m_widget); m_widget->deleteLater(); // I didn't get a crash here but for symmetrie to the other mutate slot^ } // Add power widget m_widget = new QWidget(this); m_powerUi = new Ui::PowerSearchBar; m_powerUi->setupUi(m_widget); m_layout->addWidget(m_widget); // Bind to shared history models m_powerUi->pattern->setDuplicatesEnabled(false); m_powerUi->pattern->setInsertPolicy(QComboBox::InsertAtTop); m_powerUi->pattern->setMaxCount(m_config->maxHistorySize()); m_powerUi->pattern->setModel(KTextEditor::EditorPrivate::self()->searchHistoryModel()); m_powerUi->pattern->lineEdit()->setClearButtonEnabled(true); m_powerUi->pattern->setCompleter(nullptr); m_powerUi->replacement->setDuplicatesEnabled(false); m_powerUi->replacement->setInsertPolicy(QComboBox::InsertAtTop); m_powerUi->replacement->setMaxCount(m_config->maxHistorySize()); m_powerUi->replacement->setModel(KTextEditor::EditorPrivate::self()->replaceHistoryModel()); m_powerUi->replacement->lineEdit()->setClearButtonEnabled(true); m_powerUi->replacement->setCompleter(nullptr); // Icons m_powerUi->mutate->setIcon(QIcon::fromTheme(QStringLiteral("games-config-options"))); m_powerUi->mutate->setChecked(true); m_powerUi->findNext->setIcon(QIcon::fromTheme(QStringLiteral("go-down-search"))); m_powerUi->findPrev->setIcon(QIcon::fromTheme(QStringLiteral("go-up-search"))); m_powerUi->findAll->setIcon(QIcon::fromTheme(QStringLiteral("edit-find"))); m_powerUi->matchCase->setIcon(QIcon::fromTheme(QStringLiteral("format-text-superscript"))); m_powerUi->selectionOnly->setIcon(QIcon::fromTheme(QStringLiteral("edit-select-all"))); // Focus proxy centralWidget()->setFocusProxy(m_powerUi->pattern); } m_powerUi->selectionOnly->setChecked(selectionOnly); // Restore previous settings if (create) { m_powerUi->matchCase->setChecked(m_powerMatchCase); m_powerUi->searchMode->setCurrentIndex(m_powerMode); } // force current index of -1 --> shows 1st completion entry instead of 2nd m_powerUi->pattern->setCurrentIndex(-1); m_powerUi->replacement->setCurrentIndex(-1); // Set initial search pattern QLineEdit *const patternLineEdit = m_powerUi->pattern->lineEdit(); Q_ASSERT(patternLineEdit != nullptr); patternLineEdit->setText(initialPattern); patternLineEdit->selectAll(); // Set initial replacement text QLineEdit *const replacementLineEdit = m_powerUi->replacement->lineEdit(); Q_ASSERT(replacementLineEdit != nullptr); replacementLineEdit->setText(QString()); // Propagate settings (slots are still inactive on purpose) onPowerPatternChanged(initialPattern); givePatternFeedback(); if (create) { // Slots connect(m_powerUi->mutate, SIGNAL(clicked()), this, SLOT(enterIncrementalMode())); connect(patternLineEdit, SIGNAL(textChanged(QString)), this, SLOT(onPowerPatternChanged(QString))); connect(m_powerUi->findNext, SIGNAL(clicked()), this, SLOT(findNext())); connect(m_powerUi->findPrev, SIGNAL(clicked()), this, SLOT(findPrevious())); connect(m_powerUi->replaceNext, SIGNAL(clicked()), this, SLOT(replaceNext())); connect(m_powerUi->replaceAll, SIGNAL(clicked()), this, SLOT(replaceAll())); connect(m_powerUi->searchMode, SIGNAL(currentIndexChanged(int)), this, SLOT(onPowerModeChanged(int))); connect(m_powerUi->matchCase, SIGNAL(toggled(bool)), this, SLOT(onMatchCaseToggled(bool))); connect(m_powerUi->findAll, SIGNAL(clicked()), this, SLOT(findAll())); // Make [return] in pattern line edit trigger action connect(patternLineEdit, SIGNAL(returnPressed()), this, SLOT(onReturnPressed())); connect(replacementLineEdit, SIGNAL(returnPressed()), this, SLOT(replaceNext())); // Hook into line edit context menus m_powerUi->pattern->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_powerUi->pattern, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onPowerPatternContextMenuRequest(QPoint))); m_powerUi->replacement->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_powerUi->replacement, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onPowerReplacmentContextMenuRequest(QPoint))); } // Focus if (m_widget->isVisible()) { m_powerUi->pattern->setFocus(Qt::MouseFocusReason); } } void KateSearchBar::enterIncrementalMode() { QString initialPattern; // Guess settings from context: init pattern with current selection const bool selected = m_view->selection(); if (selected) { const Range &selection = m_view->selectionRange(); if (selection.onSingleLine()) { // ... with current selection initialPattern = m_view->selectionText(); } } // If there's no new selection, we'll use the existing pattern if (initialPattern.isNull()) { // Coming from incremental search? const bool fromIncremental = (m_incUi != nullptr) && (m_widget->isVisible()); if (fromIncremental) { m_incUi->pattern->lineEdit()->selectAll(); m_incUi->pattern->setFocus(Qt::MouseFocusReason); return; } // Coming from power search? const bool fromReplace = (m_powerUi != nullptr) && (m_widget->isVisible()); if (fromReplace) { initialPattern = m_powerUi->pattern->currentText(); } } // Still no search pattern? Use the word under the cursor if (initialPattern.isNull()) { const KTextEditor::Cursor cursorPosition = m_view->cursorPosition(); initialPattern = m_view->doc()->wordAt(cursorPosition); } // Create dialog const bool create = (m_incUi == nullptr); if (create) { // Kill power widget if (m_powerUi != nullptr) { // Backup current settings const bool OF_POWER = true; backupConfig(OF_POWER); // Kill widget delete m_powerUi; m_powerUi = nullptr; m_layout->removeWidget(m_widget); m_widget->deleteLater(); //deleteLater, because it's not a good idea too delete the widget and there for the button triggering this slot } // Add incremental widget m_widget = new QWidget(this); m_incUi = new Ui::IncrementalSearchBar; m_incUi->setupUi(m_widget); m_layout->addWidget(m_widget); // new QShortcut(KStandardShortcut::paste().primary(), m_incUi->pattern, SLOT(paste()), 0, Qt::WidgetWithChildrenShortcut); // if (!KStandardShortcut::paste().alternate().isEmpty()) // new QShortcut(KStandardShortcut::paste().alternate(), m_incUi->pattern, SLOT(paste()), 0, Qt::WidgetWithChildrenShortcut); // Icons m_incUi->mutate->setIcon(QIcon::fromTheme(QStringLiteral("games-config-options"))); m_incUi->next->setIcon(QIcon::fromTheme(QStringLiteral("go-down-search"))); m_incUi->prev->setIcon(QIcon::fromTheme(QStringLiteral("go-up-search"))); m_incUi->matchCase->setIcon(QIcon::fromTheme(QStringLiteral("format-text-superscript"))); // Ensure minimum size m_incUi->pattern->setMinimumWidth(12 * m_incUi->pattern->fontMetrics().height()); // Customize status area m_incUi->status->setTextElideMode(Qt::ElideLeft); // Focus proxy centralWidget()->setFocusProxy(m_incUi->pattern); m_incUi->pattern->setDuplicatesEnabled(false); m_incUi->pattern->setInsertPolicy(QComboBox::InsertAtTop); m_incUi->pattern->setMaxCount(m_config->maxHistorySize()); m_incUi->pattern->setModel(KTextEditor::EditorPrivate::self()->searchHistoryModel()); m_incUi->pattern->lineEdit()->setClearButtonEnabled(true); m_incUi->pattern->setCompleter(nullptr); } // Restore previous settings if (create) { m_incUi->matchCase->setChecked(m_incMatchCase); } // force current index of -1 --> shows 1st completion entry instead of 2nd m_incUi->pattern->setCurrentIndex(-1); // Set initial search pattern if (!create) { disconnect(m_incUi->pattern, SIGNAL(editTextChanged(QString)), this, SLOT(onIncPatternChanged(QString))); } m_incUi->pattern->setEditText(initialPattern); connect(m_incUi->pattern, SIGNAL(editTextChanged(QString)), this, SLOT(onIncPatternChanged(QString))); m_incUi->pattern->lineEdit()->selectAll(); // Propagate settings (slots are still inactive on purpose) if (initialPattern.isEmpty()) { // Reset edit color indicateMatch(MatchNothing); } // Enable/disable next/prev m_incUi->next->setDisabled(initialPattern.isEmpty()); m_incUi->prev->setDisabled(initialPattern.isEmpty()); if (create) { // Slots connect(m_incUi->mutate, SIGNAL(clicked()), this, SLOT(enterPowerMode())); connect(m_incUi->pattern->lineEdit(), SIGNAL(returnPressed()), this, SLOT(onReturnPressed())); connect(m_incUi->next, SIGNAL(clicked()), this, SLOT(findNext())); connect(m_incUi->prev, SIGNAL(clicked()), this, SLOT(findPrevious())); connect(m_incUi->matchCase, SIGNAL(toggled(bool)), this, SLOT(onMatchCaseToggled(bool))); } // Focus if (m_widget->isVisible()) { m_incUi->pattern->setFocus(Qt::MouseFocusReason); } } bool KateSearchBar::clearHighlights() { // Remove ScrollBarMarks KTextEditor::MarkInterface* iface = qobject_cast(m_view->document()); if (iface) { const QHash marks = iface->marks(); QHashIterator i(marks); while (i.hasNext()) { i.next(); if (i.value()->type & KTextEditor::MarkInterface::SearchMatch) { iface->removeMark(i.value()->line, KTextEditor::MarkInterface::SearchMatch); } } } if (m_infoMessage) { delete m_infoMessage; } if (m_hlRanges.isEmpty()) { return false; } qDeleteAll(m_hlRanges); m_hlRanges.clear(); return true; } void KateSearchBar::updateHighlightColors() { const QColor foregroundColor = m_view->defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color(); const QColor &searchColor = m_view->renderer()->config()->searchHighlightColor(); const QColor &replaceColor = m_view->renderer()->config()->replaceHighlightColor(); // init match attribute highlightMatchAttribute->setForeground(foregroundColor); highlightMatchAttribute->setBackground(searchColor); highlightMatchAttribute->dynamicAttribute(Attribute::ActivateMouseIn)->setBackground(searchColor); highlightMatchAttribute->dynamicAttribute(Attribute::ActivateMouseIn)->setForeground(foregroundColor); highlightMatchAttribute->dynamicAttribute(Attribute::ActivateCaretIn)->setBackground(searchColor); highlightMatchAttribute->dynamicAttribute(Attribute::ActivateCaretIn)->setForeground(foregroundColor); // init replacement attribute highlightReplacementAttribute->setBackground(replaceColor); highlightReplacementAttribute->setForeground(foregroundColor); } void KateSearchBar::showEvent(QShowEvent *event) { // Update init cursor if (m_incUi != nullptr) { m_incInitCursor = m_view->cursorPosition(); } updateSelectionOnly(); KateViewBarWidget::showEvent(event); } void KateSearchBar::updateSelectionOnly() { if (m_powerUi == nullptr) { return; } // Re-init "Selection only" checkbox if power search bar open const bool selected = m_view->selection(); bool selectionOnly = selected; if (selected) { Range const &selection = m_view->selectionRange(); selectionOnly = !selection.onSingleLine(); } m_powerUi->selectionOnly->setChecked(selectionOnly); } void KateSearchBar::updateIncInitCursor() { if (m_incUi == nullptr) { return; } // Update init cursor m_incInitCursor = m_view->cursorPosition(); } void KateSearchBar::onPowerPatternContextMenuRequest(const QPoint &pos) { const bool FOR_PATTERN = true; showExtendedContextMenu(FOR_PATTERN, pos); } void KateSearchBar::onPowerPatternContextMenuRequest() { onPowerPatternContextMenuRequest(m_powerUi->pattern->mapFromGlobal(QCursor::pos())); } void KateSearchBar::onPowerReplacmentContextMenuRequest(const QPoint &pos) { const bool FOR_REPLACEMENT = false; showExtendedContextMenu(FOR_REPLACEMENT, pos); } void KateSearchBar::onPowerReplacmentContextMenuRequest() { onPowerReplacmentContextMenuRequest(m_powerUi->replacement->mapFromGlobal(QCursor::pos())); } bool KateSearchBar::isPower() const { return m_powerUi != nullptr; } void KateSearchBar::slotReadWriteChanged() { if (!KateSearchBar::isPower()) { return; } // perhaps disable/enable m_powerUi->replaceNext->setEnabled(m_view->doc()->isReadWrite() && isPatternValid()); m_powerUi->replaceAll->setEnabled(m_view->doc()->isReadWrite() && isPatternValid()); } diff --git a/src/search/katesearchbar.h b/src/search/katesearchbar.h index e70f9957..d885098e 100644 --- a/src/search/katesearchbar.h +++ b/src/search/katesearchbar.h @@ -1,210 +1,210 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2009-2010 Bernhard Beschow * Copyright (C) 2007 Sebastian Pipping * * 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_SEARCH_BAR_H #define KATE_SEARCH_BAR_H 1 #include "kateviewhelpers.h" #include #include #include namespace KTextEditor { class ViewPrivate; } class KateViewConfig; class QVBoxLayout; class QComboBox; namespace Ui { class IncrementalSearchBar; class PowerSearchBar; } namespace KTextEditor { class MovingRange; class Message; } class KTEXTEDITOR_EXPORT KateSearchBar : public KateViewBarWidget { Q_OBJECT friend class SearchBarTest; public: enum SearchMode { // NOTE: Concrete values are important here // to work with the combobox index! MODE_PLAIN_TEXT = 0, MODE_WHOLE_WORDS = 1, MODE_ESCAPE_SEQUENCES = 2, MODE_REGEX = 3 }; enum MatchResult { MatchFound, MatchWrappedForward, MatchWrappedBackward, MatchMismatch, MatchNothing, MatchNeutral }; enum SearchDirection { SearchForward, SearchBackward }; public: explicit KateSearchBar(bool initAsPower, KTextEditor::ViewPrivate *view, KateViewConfig *config); - ~KateSearchBar(); + ~KateSearchBar() override; - void closed() Q_DECL_OVERRIDE; + void closed() override; bool isPower() const; QString searchPattern() const; QString replacementPattern() const; bool selectionOnly() const; bool matchCase() const; // Only used by KTextEditor::ViewPrivate static void nextMatchForSelection(KTextEditor::ViewPrivate *view, SearchDirection searchDirection); public Q_SLOTS: /** * Set the current search pattern. * @param searchPattern the search pattern */ void setSearchPattern(const QString &searchPattern); /** * Set the current replacement pattern. * @param replacementPattern the replacement pattern */ void setReplacementPattern(const QString &replacementPattern); void setSearchMode(SearchMode mode); void setSelectionOnly(bool selectionOnly); void setMatchCase(bool matchCase); // Called for and + void findNext(); void findPrevious(); void findAll(); void replaceNext(); void replaceAll(); // Also used by KTextEditor::ViewPrivate void enterPowerMode(); void enterIncrementalMode(); bool clearHighlights(); void updateHighlightColors(); // read write status of document changed void slotReadWriteChanged(); protected: // Overridden - void showEvent(QShowEvent *event) Q_DECL_OVERRIDE; + void showEvent(QShowEvent *event) override; private Q_SLOTS: void onIncPatternChanged(const QString &pattern); void onMatchCaseToggled(bool matchCase); void onReturnPressed(); void updateSelectionOnly(); void updateIncInitCursor(); void onPowerPatternChanged(const QString &pattern); void onPowerModeChanged(int index); void onPowerPatternContextMenuRequest(); void onPowerPatternContextMenuRequest(const QPoint &); void onPowerReplacmentContextMenuRequest(); void onPowerReplacmentContextMenuRequest(const QPoint &); private: // Helpers bool find(SearchDirection searchDirection = SearchForward, const QString *replacement = nullptr); int findAll(KTextEditor::Range inputRange, const QString *replacement); bool isPatternValid() const; KTextEditor::SearchOptions searchOptions(SearchDirection searchDirection = SearchForward) const; void highlightMatch(const KTextEditor::Range &range); void highlightReplacement(const KTextEditor::Range &range); void indicateMatch(MatchResult matchResult); static void selectRange(KTextEditor::ViewPrivate *view, const KTextEditor::Range &range); void selectRange2(const KTextEditor::Range &range); QVector getCapturePatterns(const QString &pattern) const; void showExtendedContextMenu(bool forPattern, const QPoint &pos); void givePatternFeedback(); void addCurrentTextToHistory(QComboBox *combo); void backupConfig(bool ofPower); void sendConfig(); void fixForSingleLine(KTextEditor::Range &range, SearchDirection searchDirection); void showInfoMessage(const QString &text); private: KTextEditor::ViewPrivate *const m_view; KateViewConfig *const m_config; QList m_hlRanges; QPointer m_infoMessage; - QPointer m_wrappedTopMessage; - QPointer m_wrappedBottomMessage; + QPointer m_wrappedMessage; + SearchDirection m_lastSearchDirection = SearchForward; // Shared by both dialogs QVBoxLayout *const m_layout; QWidget *m_widget; // Incremental search related Ui::IncrementalSearchBar *m_incUi; KTextEditor::Cursor m_incInitCursor; // Power search related Ui::PowerSearchBar *m_powerUi; // attribute to highlight matches with KTextEditor::Attribute::Ptr highlightMatchAttribute; KTextEditor::Attribute::Ptr highlightReplacementAttribute; // Status backup bool m_incHighlightAll : 1; bool m_incFromCursor : 1; bool m_incMatchCase : 1; bool m_powerMatchCase : 1; bool m_powerFromCursor : 1; bool m_powerHighlightAll : 1; unsigned int m_powerMode : 2; }; #endif // KATE_SEARCH_BAR_H diff --git a/src/spellcheck/ontheflycheck.h b/src/spellcheck/ontheflycheck.h index dd2ab1ae..2fd2d384 100644 --- a/src/spellcheck/ontheflycheck.h +++ b/src/spellcheck/ontheflycheck.h @@ -1,141 +1,141 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2008-2010 by Michel Ludwig * Copyright (C) 2009 by Joseph Wenninger * * 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 ONTHEFLYCHECK_H #define ONTHEFLYCHECK_H #include #include #include #include #include #include #include #include "katedocument.h" namespace Sonnet { class BackgroundChecker; } class KateOnTheFlyChecker : public QObject, private KTextEditor::MovingRangeFeedback { Q_OBJECT enum ModificationType {TEXT_INSERTED = 0, TEXT_REMOVED}; typedef QPair SpellCheckItem; typedef QList MovingRangeList; typedef QPair MisspelledItem; typedef QList MisspelledList; typedef QPair ModificationItem; typedef QList ModificationList; public: - KateOnTheFlyChecker(KTextEditor::DocumentPrivate *document); - ~KateOnTheFlyChecker(); + explicit KateOnTheFlyChecker(KTextEditor::DocumentPrivate *document); + ~KateOnTheFlyChecker() override; QPair getMisspelledItem(const KTextEditor::Cursor &cursor) const; QString dictionaryForMisspelledRange(const KTextEditor::Range &range) const; void clearMisspellingForWord(const QString &word); public Q_SLOTS: void textInserted(KTextEditor::Document *document, const KTextEditor::Range &range); void textRemoved(KTextEditor::Document *document, const KTextEditor::Range &range); void updateConfig(); void refreshSpellCheck(const KTextEditor::Range &range = KTextEditor::Range::invalid()); void updateInstalledMovingRanges(KTextEditor::ViewPrivate *view); protected: KTextEditor::DocumentPrivate *const m_document; Sonnet::Speller m_speller; QList m_spellCheckQueue; Sonnet::BackgroundChecker *m_backgroundChecker; SpellCheckItem m_currentlyCheckedItem; MisspelledList m_misspelledList; ModificationList m_modificationList; KTextEditor::DocumentPrivate::OffsetList m_currentDecToEncOffsetList; QMap m_displayRangeMap; void freeDocument(); MovingRangeList installedMovingRanges(const KTextEditor::Range &range); void queueLineSpellCheck(KTextEditor::DocumentPrivate *document, int line); /** * 'range' must be on a single line **/ void queueLineSpellCheck(const KTextEditor::Range &range, const QString &dictionary); void queueSpellCheckVisibleRange(const KTextEditor::Range &range); void queueSpellCheckVisibleRange(KTextEditor::ViewPrivate *view, const KTextEditor::Range &range); void addToSpellCheckQueue(const KTextEditor::Range &range, const QString &dictionary); void addToSpellCheckQueue(KTextEditor::MovingRange *range, const QString &dictionary); QTimer *m_viewRefreshTimer; QPointer m_refreshView; virtual void removeRangeFromEverything(KTextEditor::MovingRange *range); bool removeRangeFromCurrentSpellCheck(KTextEditor::MovingRange *range); bool removeRangeFromSpellCheckQueue(KTextEditor::MovingRange *range); - void rangeEmpty(KTextEditor::MovingRange *range) Q_DECL_OVERRIDE; - void rangeInvalid(KTextEditor::MovingRange *range) Q_DECL_OVERRIDE; - void mouseEnteredRange(KTextEditor::MovingRange *range, KTextEditor::View *view) Q_DECL_OVERRIDE; - void mouseExitedRange(KTextEditor::MovingRange *range, KTextEditor::View *view) Q_DECL_OVERRIDE; - void caretEnteredRange(KTextEditor::MovingRange *range, KTextEditor::View *view) Q_DECL_OVERRIDE; - void caretExitedRange(KTextEditor::MovingRange *range, KTextEditor::View *view) Q_DECL_OVERRIDE; + void rangeEmpty(KTextEditor::MovingRange *range) override; + void rangeInvalid(KTextEditor::MovingRange *range) override; + void mouseEnteredRange(KTextEditor::MovingRange *range, KTextEditor::View *view) override; + void mouseExitedRange(KTextEditor::MovingRange *range, KTextEditor::View *view) override; + void caretEnteredRange(KTextEditor::MovingRange *range, KTextEditor::View *view) override; + void caretExitedRange(KTextEditor::MovingRange *range, KTextEditor::View *view) override; KTextEditor::Range findWordBoundaries(const KTextEditor::Cursor &begin, const KTextEditor::Cursor &end); void deleteMovingRange(KTextEditor::MovingRange *range); void deleteMovingRanges(const QList &list); void deleteMovingRangeQuickly(KTextEditor::MovingRange *range); void stopCurrentSpellCheck(); protected Q_SLOTS: void performSpellCheck(); void misspelling(const QString &word, int start); void spellCheckDone(); void viewDestroyed(QObject *obj); void addView(KTextEditor::Document *document, KTextEditor::View *view); void removeView(KTextEditor::View *view); void restartViewRefreshTimer(KTextEditor::ViewPrivate *view); void viewRefreshTimeout(); void handleModifiedRanges(); void handleInsertedText(const KTextEditor::Range &range); void handleRemovedText(const KTextEditor::Range &range); void handleRespellCheckBlock(int start, int end); bool removeRangeFromModificationList(KTextEditor::MovingRange *range); void clearModificationList(); }; #endif diff --git a/src/spellcheck/prefixstore.cpp b/src/spellcheck/prefixstore.cpp index 53f8ff88..02d6a345 100644 --- a/src/spellcheck/prefixstore.cpp +++ b/src/spellcheck/prefixstore.cpp @@ -1,189 +1,187 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2009 by Michel Ludwig * * 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 "prefixstore.h" #include "katepartdebug.h" #include "katetextline.h" KatePrefixStore::KatePrefixStore() - : m_longestPrefixLength(0), - m_lastAssignedState(0) { } KatePrefixStore::~KatePrefixStore() { } void KatePrefixStore::addPrefix(const QString &prefix) { if (prefix.isEmpty()) { return; } if (m_prefixSet.contains(prefix)) { return; } unsigned long long state = 0; for (int i = 0; i < prefix.length(); ++i) { QChar c = prefix.at(i); CharToOccurrenceStateHash &hash = m_transitionFunction[state]; CharToOccurrenceStateHash::iterator it = hash.find(c.unicode()); if (it == hash.end()) { state = nextFreeState(); hash[c.unicode()] = QPair(1, state); continue; } ++(*it).first; state = (*it).second; } // add the last state as accepting state m_acceptingStates.insert(state); m_prefixSet.insert(prefix); if (prefix.length() > m_longestPrefixLength) { m_longestPrefixLength = prefix.length(); } } void KatePrefixStore::removePrefix(const QString &prefix) { if (prefix.isEmpty()) { return; } if (!m_prefixSet.contains(prefix)) { return; } m_prefixSet.remove(prefix); unsigned long long state = 0; for (int i = 0; i < prefix.length(); ++i) { QChar c = prefix.at(i); CharToOccurrenceStateHash &hash = m_transitionFunction[state]; CharToOccurrenceStateHash::iterator it = hash.find(c.unicode()); if (it == hash.end()) { continue; } state = (*it).second; if (m_acceptingStates.contains(state) && i == prefix.length() - 1) { m_acceptingStates.remove(state); } if ((*it).first <= 1) { hash.erase(it); m_stateFreeList.push_back(state); } else { --(*it).first; } } if (prefix.length() == m_longestPrefixLength) { m_longestPrefixLength = computeLongestPrefixLength(); } } void KatePrefixStore::dump() { for (unsigned long long i = 0; i < m_lastAssignedState; ++i) { CharToOccurrenceStateHash &hash = m_transitionFunction[i]; for (CharToOccurrenceStateHash::iterator it = hash.begin(); it != hash.end(); ++it) { qCDebug(LOG_KTE) << i << "x" << QChar(it.key()) << "->" << it.value().first << "x" << it.value().second; } } qCDebug(LOG_KTE) << "Accepting states" << m_acceptingStates; } QString KatePrefixStore::findPrefix(const QString &s, int start) const { unsigned long long state = 0; for (int i = start; i < s.length(); ++i) { QChar c = s.at(i); const CharToOccurrenceStateHash &hash = m_transitionFunction[state]; CharToOccurrenceStateHash::const_iterator it = hash.find(c.unicode()); if (it == hash.end()) { return QString(); } state = (*it).second; if (m_acceptingStates.contains(state)) { return s.mid(start, i + 1 - start); } } return QString(); } QString KatePrefixStore::findPrefix(const Kate::TextLine &line, int start) const { unsigned long long state = 0; for (int i = start; i < line->length(); ++i) { QChar c = line->at(i); const CharToOccurrenceStateHash &hash = m_transitionFunction[state]; CharToOccurrenceStateHash::const_iterator it = hash.find(c.unicode()); if (it == hash.end()) { return QString(); } state = (*it).second; if (m_acceptingStates.contains(state)) { return line->string(start, i + 1 - start); } } return QString(); } int KatePrefixStore::longestPrefixLength() const { return m_longestPrefixLength; } void KatePrefixStore::clear() { m_longestPrefixLength = 0; m_prefixSet.clear(); m_transitionFunction.clear(); m_acceptingStates.clear(); m_stateFreeList.clear(); m_lastAssignedState = 0; } int KatePrefixStore::computeLongestPrefixLength() { int toReturn = 0; for (QSet::iterator i = m_prefixSet.begin(); i != m_prefixSet.end(); ++i) { qCDebug(LOG_KTE) << "length" << (*i).length(); toReturn = qMax(toReturn, (*i).length()); } return toReturn; } unsigned long long KatePrefixStore::nextFreeState() { if (!m_stateFreeList.isEmpty()) { return m_stateFreeList.takeFirst(); } return ++m_lastAssignedState; } diff --git a/src/spellcheck/prefixstore.h b/src/spellcheck/prefixstore.h index 8ef0e799..b214a626 100644 --- a/src/spellcheck/prefixstore.h +++ b/src/spellcheck/prefixstore.h @@ -1,88 +1,88 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2008-2009 by Michel Ludwig * * 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 PREFIXSTORE_H #define PREFIXSTORE_H #include #include #include #include #include #include #include "katetextline.h" /** * This class can be used to efficiently search for occurrences of strings in * a given string. Theoretically speaking, a finite deterministic automaton is * constructed which exactly accepts the strings that are to be recognized. In * order to check whether a given string contains one of the strings that are being * searched for the constructed automaton has to applied on each position in the * given string. **/ class KatePrefixStore { public: typedef QPair BooleanPair; KatePrefixStore(); virtual ~KatePrefixStore(); void addPrefix(const QString &prefix); void removePrefix(const QString &prefix); /** * Returns the shortest prefix of the given string that is contained in * this prefix store starting at position 'start'. **/ QString findPrefix(const QString &s, int start = 0) const; /** * Returns the shortest prefix of the given string that is contained in * this prefix store starting at position 'start'. **/ QString findPrefix(const Kate::TextLine &line, int start = 0) const; int longestPrefixLength() const; void clear(); void dump(); protected: - int m_longestPrefixLength; + int m_longestPrefixLength = 0; QSet m_prefixSet; // State x Char -> Nr. of char occurrences in prefixes x State typedef QHash > CharToOccurrenceStateHash; typedef QHash TransitionFunction; TransitionFunction m_transitionFunction; QSet m_acceptingStates; QList m_stateFreeList; - unsigned long long m_lastAssignedState; + unsigned long long m_lastAssignedState = 0; int computeLongestPrefixLength(); unsigned long long nextFreeState(); // bool containsPrefixOfLengthEndingWith(int length, const QChar& c); }; #endif diff --git a/src/spellcheck/spellcheck.cpp b/src/spellcheck/spellcheck.cpp index 07bc13ec..9e04d788 100644 --- a/src/spellcheck/spellcheck.cpp +++ b/src/spellcheck/spellcheck.cpp @@ -1,292 +1,291 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2009 by Michel Ludwig * * 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. */ /* If ever threads should be used again, thread communication and * synchronization ought to be done with blocking queued signal connections. */ #include "spellcheck.h" #include #include #include #include #include #include #include "katedocument.h" #include "katehighlight.h" KateSpellCheckManager::KateSpellCheckManager(QObject *parent) : QObject(parent) { } KateSpellCheckManager::~KateSpellCheckManager() { } QStringList KateSpellCheckManager::suggestions(const QString &word, const QString &dictionary) { Sonnet::Speller speller; speller.setLanguage(dictionary); return speller.suggest(word); } void KateSpellCheckManager::ignoreWord(const QString &word, const QString &dictionary) { Sonnet::Speller speller; speller.setLanguage(dictionary); speller.addToSession(word); } void KateSpellCheckManager::addToDictionary(const QString &word, const QString &dictionary) { Sonnet::Speller speller; speller.setLanguage(dictionary); speller.addToPersonal(word); } QList KateSpellCheckManager::rangeDifference(const KTextEditor::Range &r1, const KTextEditor::Range &r2) { Q_ASSERT(r1.contains(r2)); QList toReturn; KTextEditor::Range before(r1.start(), r2.start()); KTextEditor::Range after(r2.end(), r1.end()); if (!before.isEmpty()) { toReturn.push_back(before); } if (!after.isEmpty()) { toReturn.push_back(after); } return toReturn; } namespace { bool lessThanRangeDictionaryPair(const QPair &s1, const QPair &s2) { return s1.first.end() <= s2.first.start(); } } QList > KateSpellCheckManager::spellCheckLanguageRanges(KTextEditor::DocumentPrivate *doc, const KTextEditor::Range &range) { QString defaultDict = doc->defaultDictionary(); QList toReturn; QList > dictionaryRanges = doc->dictionaryRanges(); if (dictionaryRanges.isEmpty()) { toReturn.push_back(RangeDictionaryPair(range, defaultDict)); return toReturn; } QList splitQueue; splitQueue.push_back(range); while (!splitQueue.isEmpty()) { bool handled = false; KTextEditor::Range consideredRange = splitQueue.takeFirst(); for (QList >::iterator i = dictionaryRanges.begin(); i != dictionaryRanges.end(); ++i) { KTextEditor::Range languageRange = *((*i).first); KTextEditor::Range intersection = languageRange.intersect(consideredRange); if (intersection.isEmpty()) { continue; } toReturn.push_back(RangeDictionaryPair(intersection, (*i).second)); splitQueue += rangeDifference(consideredRange, intersection); handled = true; break; } if (!handled) { // 'consideredRange' did not intersect with any dictionary range, so we add it with the default dictionary toReturn.push_back(RangeDictionaryPair(consideredRange, defaultDict)); } } // finally, we still have to sort the list qStableSort(toReturn.begin(), toReturn.end(), lessThanRangeDictionaryPair); return toReturn; } QList > KateSpellCheckManager::spellCheckWrtHighlightingRanges(KTextEditor::DocumentPrivate *document, const KTextEditor::Range &range, const QString &dictionary, bool singleLine, bool returnSingleRange) { QList > toReturn; if (range.isEmpty()) { return toReturn; } KateHighlighting *highlighting = document->highlight(); QList rangesToSplit; if (!singleLine || range.onSingleLine()) { rangesToSplit.push_back(range); } else { const int startLine = range.start().line(); const int startColumn = range.start().column(); const int endLine = range.end().line(); const int endColumn = range.end().column(); for (int line = startLine; line <= endLine; ++line) { const int start = (line == startLine) ? startColumn : 0; const int end = (line == endLine) ? endColumn : document->lineLength(line); KTextEditor::Range toAdd(line, start, line, end); if (!toAdd.isEmpty()) { rangesToSplit.push_back(toAdd); } } } for (QList::iterator i = rangesToSplit.begin(); i != rangesToSplit.end(); ++i) { KTextEditor::Range rangeToSplit = *i; KTextEditor::Cursor begin = KTextEditor::Cursor::invalid(); const int startLine = rangeToSplit.start().line(); const int startColumn = rangeToSplit.start().column(); const int endLine = rangeToSplit.end().line(); const int endColumn = rangeToSplit.end().column(); bool inSpellCheckArea = false; for (int line = startLine; line <= endLine; ++line) { Kate::TextLine kateTextLine = document->kateTextLine(line); if (!kateTextLine) { continue; // bug #303496 } const int start = (line == startLine) ? startColumn : 0; const int end = (line == endLine) ? endColumn : kateTextLine->length(); for (int i = start; i < end;) { // WARNING: 'i' has to be incremented manually! int attr = kateTextLine->attribute(i); const KatePrefixStore &prefixStore = highlighting->getCharacterEncodingsPrefixStore(attr); QString prefixFound = prefixStore.findPrefix(kateTextLine, i); - unsigned int attribute = kateTextLine->attribute(i); - if (!document->highlight()->attributeRequiresSpellchecking(attribute) + if (!document->highlight()->attributeRequiresSpellchecking(static_cast(attr)) && prefixFound.isEmpty()) { if (i == start) { ++i; continue; } else if (inSpellCheckArea) { KTextEditor::Range spellCheckRange(begin, KTextEditor::Cursor(line, i)); // work around Qt bug 6498 trimRange(document, spellCheckRange); if (!spellCheckRange.isEmpty()) { toReturn.push_back(RangeDictionaryPair(spellCheckRange, dictionary)); if (returnSingleRange) { return toReturn; } } begin = KTextEditor::Cursor::invalid(); inSpellCheckArea = false; } } else if (!inSpellCheckArea) { begin = KTextEditor::Cursor(line, i); inSpellCheckArea = true; } if (!prefixFound.isEmpty()) { i += prefixFound.length(); } else { ++i; } } } if (inSpellCheckArea) { KTextEditor::Range spellCheckRange(begin, rangeToSplit.end()); // work around Qt bug 6498 trimRange(document, spellCheckRange); if (!spellCheckRange.isEmpty()) { toReturn.push_back(RangeDictionaryPair(spellCheckRange, dictionary)); if (returnSingleRange) { return toReturn; } } } } return toReturn; } QList > KateSpellCheckManager::spellCheckRanges(KTextEditor::DocumentPrivate *doc, const KTextEditor::Range &range, bool singleLine) { QList toReturn; QList languageRangeList = spellCheckLanguageRanges(doc, range); for (QList::iterator i = languageRangeList.begin(); i != languageRangeList.end(); ++i) { const RangeDictionaryPair &p = *i; toReturn += spellCheckWrtHighlightingRanges(doc, p.first, p.second, singleLine); } return toReturn; } void KateSpellCheckManager::replaceCharactersEncodedIfNecessary(const QString &newWord, KTextEditor::DocumentPrivate *doc, const KTextEditor::Range &replacementRange) { const QString replacedString = doc->text(replacementRange); int attr = doc->kateTextLine(replacementRange.start().line())->attribute(replacementRange.start().column()); int p = doc->highlight()->getEncodedCharactersInsertionPolicy(attr); if ((p == KTextEditor::DocumentPrivate::EncodeAlways) || (p == KTextEditor::DocumentPrivate::EncodeWhenPresent && doc->containsCharacterEncoding(replacementRange))) { doc->replaceText(replacementRange, newWord); doc->replaceCharactersByEncoding(KTextEditor::Range(replacementRange.start(), replacementRange.start() + KTextEditor::Cursor(0, newWord.length()))); } else { doc->replaceText(replacementRange, newWord); } } void KateSpellCheckManager::trimRange(KTextEditor::DocumentPrivate *doc, KTextEditor::Range &r) { if (r.isEmpty()) { return; } KTextEditor::Cursor cursor = r.start(); while (cursor < r.end()) { if (doc->lineLength(cursor.line()) > 0 && !doc->characterAt(cursor).isSpace() && doc->characterAt(cursor).category() != QChar::Other_Control) { break; } cursor.setColumn(cursor.column() + 1); if (cursor.column() >= doc->lineLength(cursor.line())) { cursor.setPosition(cursor.line() + 1, 0); } } r.setStart(cursor); if (r.isEmpty()) { return; } cursor = r.end(); KTextEditor::Cursor prevCursor = cursor; // the range cannot be empty now do { prevCursor = cursor; if (cursor.column() <= 0) { cursor.setPosition(cursor.line() - 1, doc->lineLength(cursor.line() - 1)); } else { cursor.setColumn(cursor.column() - 1); } if (cursor.column() < doc->lineLength(cursor.line()) && !doc->characterAt(cursor).isSpace() && doc->characterAt(cursor).category() != QChar::Other_Control) { break; } } while (cursor > r.start()); r.setEnd(prevCursor); } diff --git a/src/spellcheck/spellcheckbar.cpp b/src/spellcheck/spellcheckbar.cpp index 988c7446..20132632 100644 --- a/src/spellcheck/spellcheckbar.cpp +++ b/src/spellcheck/spellcheckbar.cpp @@ -1,453 +1,453 @@ /** * Copyright (C) 2003 Zack Rusin * Copyright (C) 2009-2010 Michel Ludwig * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "spellcheckbar.h" #include "ui_spellcheckbar.h" #include #include "sonnet/backgroundchecker.h" #include "sonnet/speller.h" /* #include "sonnet/filter_p.h" #include "sonnet/settings_p.h" */ #include #include #include #include #include #include #include -#include +#include #include //to initially disable sorting in the suggestions listview #define NONSORTINGCOLUMN 2 class ReadOnlyStringListModel: public QStringListModel { public: ReadOnlyStringListModel(QObject *parent): QStringListModel(parent) {} - Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE + Qt::ItemFlags flags(const QModelIndex &index) const override { Q_UNUSED(index); return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } }; /** * Structure abstracts the word and its position in the * parent text. * * @author Zack Rusin * @short struct represents word */ struct Word { - Word() : start(0), end(true) + Word() {} Word(const QString &w, int st, bool e = false) : word(w), start(st), end(e) {} Word(const Word &other) : word(other.word), start(other.start), end(other.end) {} QString word; - int start; - bool end; + int start = 0; + bool end = true; }; class SpellCheckBar::Private { public: Ui_SonnetUi ui; ReadOnlyStringListModel *suggestionsModel; QWidget *wdg; QDialogButtonBox *buttonBox; QProgressDialog *progressDialog; QString originalBuffer; Sonnet::BackgroundChecker *checker; Word currentWord; QMap replaceAllMap; bool restart;//used when text is distributed across several qtextedits, eg in KAider QMap dictsMap; int progressDialogTimeout; bool showCompletionMessageBox; bool spellCheckContinuedAfterReplacement; bool canceled; void deleteProgressDialog(bool directly) { if (progressDialog) { progressDialog->hide(); if (directly) { delete progressDialog; } else { progressDialog->deleteLater(); } progressDialog = nullptr; } } }; SpellCheckBar::SpellCheckBar(Sonnet::BackgroundChecker *checker, QWidget *parent) : KateViewBarWidget(true, parent), d(new Private) { d->checker = checker; d->canceled = false; d->showCompletionMessageBox = false; d->spellCheckContinuedAfterReplacement = true; d->progressDialogTimeout = -1; d->progressDialog = nullptr; initGui(); initConnections(); } SpellCheckBar::~SpellCheckBar() { delete d; } void SpellCheckBar::closed() { if (viewBar()) { viewBar()->removeBarWidget(this); } // called from hideMe, so don't call it again! d->canceled = true; d->deleteProgressDialog(false); // this method can be called in response to d->replaceAllMap.clear(); // pressing 'Cancel' on the dialog emit cancel(); emit spellCheckStatus(i18n("Spell check canceled.")); } void SpellCheckBar::initConnections() { connect(d->ui.m_addBtn, SIGNAL(clicked()), SLOT(slotAddWord())); connect(d->ui.m_replaceBtn, SIGNAL(clicked()), SLOT(slotReplaceWord())); connect(d->ui.m_replaceAllBtn, SIGNAL(clicked()), SLOT(slotReplaceAll())); connect(d->ui.m_skipBtn, SIGNAL(clicked()), SLOT(slotSkip())); connect(d->ui.m_skipAllBtn, SIGNAL(clicked()), SLOT(slotSkipAll())); connect(d->ui.m_suggestBtn, SIGNAL(clicked()), SLOT(slotSuggest())); connect(d->ui.m_language, SIGNAL(activated(QString)), SLOT(slotChangeLanguage(QString))); connect(d->checker, SIGNAL(misspelling(QString,int)), SLOT(slotMisspelling(QString,int))); connect(d->checker, SIGNAL(done()), SLOT(slotDone())); /* connect(d->ui.m_suggestions, SIGNAL(doubleClicked(QModelIndex)), SLOT(slotReplaceWord())); */ connect(d->ui.cmbReplacement, SIGNAL(returnPressed()), this, SLOT(slotReplaceWord())); connect(d->ui.m_autoCorrect, SIGNAL(clicked()), SLOT(slotAutocorrect())); // button use by kword/kpresenter // hide by default d->ui.m_autoCorrect->hide(); } void SpellCheckBar::initGui() { QVBoxLayout *layout = new QVBoxLayout; layout->setMargin(0); centralWidget()->setLayout(layout); d->wdg = new QWidget(this); d->ui.setupUi(d->wdg); layout->addWidget(d->wdg); setGuiEnabled(false); /* d->buttonBox = new QDialogButtonBox(this); d->buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); layout->addWidget(d->buttonBox); */ //d->ui.m_suggestions->setSorting( NONSORTINGCOLUMN ); fillDictionaryComboBox(); d->restart = false; d->suggestionsModel = new ReadOnlyStringListModel(this); d->ui.cmbReplacement->setModel(d->suggestionsModel); } void SpellCheckBar::activeAutoCorrect(bool _active) { if (_active) { d->ui.m_autoCorrect->show(); } else { d->ui.m_autoCorrect->hide(); } } void SpellCheckBar::showProgressDialog(int timeout) { d->progressDialogTimeout = timeout; } void SpellCheckBar::showSpellCheckCompletionMessage(bool b) { d->showCompletionMessageBox = b; } void SpellCheckBar::setSpellCheckContinuedAfterReplacement(bool b) { d->spellCheckContinuedAfterReplacement = b; } void SpellCheckBar::slotAutocorrect() { setGuiEnabled(false); setProgressDialogVisible(true); emit autoCorrect(d->currentWord.word, d->ui.cmbReplacement->lineEdit()->text()); slotReplaceWord(); } void SpellCheckBar::setGuiEnabled(bool b) { d->wdg->setEnabled(b); } void SpellCheckBar::setProgressDialogVisible(bool b) { if (!b) { d->deleteProgressDialog(true); } else if (d->progressDialogTimeout >= 0) { if (d->progressDialog) { return; } d->progressDialog = new QProgressDialog(this); d->progressDialog->setLabelText(i18nc("progress label", "Spell checking in progress...")); d->progressDialog->setWindowTitle(i18nc("@title:window", "Check Spelling")); d->progressDialog->setModal(true); d->progressDialog->setAutoClose(false); d->progressDialog->setAutoReset(false); // create an 'indefinite' progress box as we currently cannot get progress feedback from // the speller d->progressDialog->reset(); d->progressDialog->setRange(0, 0); d->progressDialog->setValue(0); connect(d->progressDialog, SIGNAL(canceled()), this, SLOT(slotCancel())); d->progressDialog->setMinimumDuration(d->progressDialogTimeout); } } void SpellCheckBar::slotCancel() { hideMe(); } QString SpellCheckBar::originalBuffer() const { return d->originalBuffer; } QString SpellCheckBar::buffer() const { return d->checker->text(); } void SpellCheckBar::setBuffer(const QString &buf) { d->originalBuffer = buf; //it is possible to change buffer inside slot connected to done() signal d->restart = true; } void SpellCheckBar::fillDictionaryComboBox() { Sonnet::Speller speller = d->checker->speller(); d->dictsMap = speller.availableDictionaries(); QStringList langs = d->dictsMap.keys(); d->ui.m_language->clear(); d->ui.m_language->addItems(langs); updateDictionaryComboBox(); } void SpellCheckBar::updateDictionaryComboBox() { Sonnet::Speller speller = d->checker->speller(); d->ui.m_language->setCurrentIndex(d->dictsMap.values().indexOf(speller.language())); } void SpellCheckBar::updateDialog(const QString &word) { d->ui.m_unknownWord->setText(word); //d->ui.m_contextLabel->setText(d->checker->currentContext()); const QStringList suggs = d->checker->suggest(word); if (suggs.isEmpty()) { d->ui.cmbReplacement->lineEdit()->clear(); } else { d->ui.cmbReplacement->lineEdit()->setText(suggs.first()); } fillSuggestions(suggs); } void SpellCheckBar::show() { d->canceled = false; fillDictionaryComboBox(); updateDictionaryComboBox(); if (d->originalBuffer.isEmpty()) { d->checker->start(); } else { d->checker->setText(d->originalBuffer); } setProgressDialogVisible(true); } void SpellCheckBar::slotAddWord() { setGuiEnabled(false); setProgressDialogVisible(true); d->checker->addWordToPersonal(d->currentWord.word); d->checker->continueChecking(); } void SpellCheckBar::slotReplaceWord() { setGuiEnabled(false); setProgressDialogVisible(true); const QString replacementText = d->ui.cmbReplacement->lineEdit()->text(); emit replace(d->currentWord.word, d->currentWord.start, replacementText); if (d->spellCheckContinuedAfterReplacement) { d->checker->replace(d->currentWord.start, d->currentWord.word, replacementText); d->checker->continueChecking(); } else { setProgressDialogVisible(false); d->checker->stop(); } } void SpellCheckBar::slotReplaceAll() { setGuiEnabled(false); setProgressDialogVisible(true); d->replaceAllMap.insert(d->currentWord.word, d->ui.cmbReplacement->lineEdit()->text()); slotReplaceWord(); } void SpellCheckBar::slotSkip() { setGuiEnabled(false); setProgressDialogVisible(true); d->checker->continueChecking(); } void SpellCheckBar::slotSkipAll() { setGuiEnabled(false); setProgressDialogVisible(true); //### do we want that or should we have a d->ignoreAll list? Sonnet::Speller speller = d->checker->speller(); speller.addToPersonal(d->currentWord.word); d->checker->setSpeller(speller); d->checker->continueChecking(); } void SpellCheckBar::slotSuggest() { QStringList suggs = d->checker->suggest(d->ui.cmbReplacement->lineEdit()->text()); fillSuggestions(suggs); } void SpellCheckBar::slotChangeLanguage(const QString &lang) { Sonnet::Speller speller = d->checker->speller(); QString languageCode = d->dictsMap[lang]; if (!languageCode.isEmpty()) { d->checker->changeLanguage(languageCode); slotSuggest(); emit languageChanged(languageCode); } } void SpellCheckBar::fillSuggestions(const QStringList &suggs) { d->suggestionsModel->setStringList(suggs); if (!suggs.isEmpty()) { d->ui.cmbReplacement->setCurrentIndex(0); } } void SpellCheckBar::slotMisspelling(const QString &word, int start) { setGuiEnabled(true); setProgressDialogVisible(false); emit misspelling(word, start); //NOTE this is HACK I had to introduce because BackgroundChecker lacks 'virtual' marks on methods //this dramatically reduces spellchecking time in Lokalize //as this doesn't fetch suggestions for words that are present in msgid if (!updatesEnabled()) { return; } d->currentWord = Word(word, start); if (d->replaceAllMap.contains(word)) { d->ui.cmbReplacement->lineEdit()->setText(d->replaceAllMap[ word ]); slotReplaceWord(); } else { updateDialog(word); } } void SpellCheckBar::slotDone() { d->restart = false; emit done(d->checker->text()); if (d->restart) { updateDictionaryComboBox(); d->checker->setText(d->originalBuffer); d->restart = false; } else { setProgressDialogVisible(false); emit spellCheckStatus(i18n("Spell check complete.")); hideMe(); if (!d->canceled && d->showCompletionMessageBox) { QMessageBox::information(this, i18n("Spell check complete."), i18nc("@title:window", "Check Spelling")); } } } diff --git a/src/spellcheck/spellcheckbar.h b/src/spellcheck/spellcheckbar.h index 375fd8dc..94afc268 100644 --- a/src/spellcheck/spellcheckbar.h +++ b/src/spellcheck/spellcheckbar.h @@ -1,160 +1,160 @@ /* * Copyright (C) 2003 Zack Rusin * Copyright (C) 2009-2010 Michel Ludwig * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef SONNET_DIALOG_H #define SONNET_DIALOG_H #include "kateviewhelpers.h" class QListWidgetItem; class QModelIndex; namespace Sonnet { class BackgroundChecker; } /** * @short Spellcheck dialog * * \code * Sonnet::SpellCheckBar dlg = new Sonnet::SpellCheckBar( * new Sonnet::BackgroundChecker(this), this); * //connect signals * ... * dlg->setBuffer( someText ); * dlg->show(); * \endcode * * You can change buffer inside a slot connected to done() signal * and spellcheck will continue with new data automatically. */ class SpellCheckBar : public KateViewBarWidget { Q_OBJECT public: SpellCheckBar(Sonnet::BackgroundChecker *checker, QWidget *parent); - ~SpellCheckBar(); + ~SpellCheckBar() override; QString originalBuffer() const; QString buffer() const; - void closed() Q_DECL_OVERRIDE; + void closed() override; void show(); void activeAutoCorrect(bool _active); /** * Controls whether an (indefinite) progress dialog is shown when the spell * checking takes longer than the given time to complete. By default no * progress dialog is shown. If the progress dialog is set to be shown, no * time consuming operation (for example, showing a notification message) should * be performed in a slot connected to the 'done' signal as this might trigger * the progress dialog unnecessarily. * * @param timeout time after which the progress dialog should appear; a negative * value can be used to hide it * @since 4.4 */ void showProgressDialog(int timeout = 500); /** * Controls whether a message box indicating the completion of the spell checking * is shown or not. By default it is not shown. * * @since 4.4 */ void showSpellCheckCompletionMessage(bool b = true); /** * Controls whether the spell checking is continued after the replacement of a * misspelled word has been performed. By default it is continued. * * @since 4.4 */ void setSpellCheckContinuedAfterReplacement(bool b); public Q_SLOTS: void setBuffer(const QString &); Q_SIGNALS: /** * The dialog won't be closed if you setBuffer() in slot connected to this signal * * Also emitted after stop() signal */ void done(const QString &newBuffer); void misspelling(const QString &word, int start); void replace(const QString &oldWord, int start, const QString &newWord); void stop(); void cancel(); void autoCorrect(const QString ¤tWord, const QString &replaceWord); /** * Signal sends when spell checking is finished/stopped/completed * @since 4.1 */ void spellCheckStatus(const QString &); /** * Emitted when the user changes the language used for spellchecking, * which is shown in a combobox of this dialog. * * @param language the new language the user selected * @since 4.1 */ void languageChanged(const QString &language); private Q_SLOTS: void slotMisspelling(const QString &word, int start); void slotDone(); void slotCancel(); void slotAddWord(); void slotReplaceWord(); void slotReplaceAll(); void slotSkip(); void slotSkipAll(); void slotSuggest(); void slotChangeLanguage(const QString &); void slotAutocorrect(); void setGuiEnabled(bool b); void setProgressDialogVisible(bool b); private: void updateDialog(const QString &word); void fillDictionaryComboBox(); void updateDictionaryComboBox(); void fillSuggestions(const QStringList &suggs); void initConnections(); void initGui(); void continueChecking(); private: class Private; Private *const d; Q_DISABLE_COPY(SpellCheckBar) }; #endif diff --git a/src/syntax/katehighlight.h b/src/syntax/katehighlight.h index 381bfe19..7de51139 100644 --- a/src/syntax/katehighlight.h +++ b/src/syntax/katehighlight.h @@ -1,469 +1,469 @@ /* This file is part of the KDE libraries Copyright (C) 2001,2002 Joseph Wenninger Copyright (C) 2001 Christoph Cullmann 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 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_HIGHLIGHT_H__ #define __KATE_HIGHLIGHT_H__ #include "katetextline.h" #include "kateextendedattribute.h" #include "katesyntaxmanager.h" #include "spellcheck/prefixstore.h" #include "range.h" #include #include #include #include #include #include #include #include #include #include class KConfig; class KateHlContext; class KateHlItem; class KateHlIncludeRule; class KateSyntaxModeListItem; class KateSyntaxContextData; namespace KTextEditor { class DocumentPrivate; } // same as in kmimemagic, no need to feed more data #define KATE_HL_HOWMANY 1024 // min. x seconds between two dynamic contexts reset #define KATE_DYNAMIC_CONTEXTS_RESET_DELAY (30 * 1000) /** * describe a modification of the context stack */ class KateHlContextModification { public: enum modType { doNothing = 0, doPush = 1, doPops = 2, doPopsAndPush = 3 }; /** * Constructor * @param _newContext new context to push on stack * @param _pops number of contexts to remove from stack in advance */ - KateHlContextModification(int _newContext = -1, int _pops = 0) : type(doNothing), newContext(_newContext), pops(_pops) //krazy:exclude=explicit + KateHlContextModification(int _newContext = -1, int _pops = 0) : newContext(_newContext), pops(_pops) //krazy:exclude=explicit { if (newContext >= 0 && pops == 0) { type = doPush; } else if (newContext < 0 && pops > 0) { type = doPops; } else if (newContext >= 0 && pops > 0) { type = doPopsAndPush; } else { type = doNothing; } } public: /** * indicates what this modification does, for speed */ - char type; + char type = doNothing; /** * new context to push on the stack * if this is < 0, push nothing on the stack */ int newContext; /** * number of contexts to pop from the stack * before pushing a new context on it */ int pops; }; class KateEmbeddedHlInfo { public: KateEmbeddedHlInfo() { loaded = false; context0 = -1; } KateEmbeddedHlInfo(bool l, int ctx0) { loaded = l; context0 = ctx0; } public: bool loaded; int context0; }; // some typedefs typedef QList KateHlIncludeRules; typedef QMap KateEmbeddedHlInfos; typedef QMap KateHlUnresolvedCtxRefs; class KateHighlighting { public: KateHighlighting(const KSyntaxHighlighting::Definition &def); ~KateHighlighting(); private: /** * this method frees mem ;) * used by the destructor and done(), therefor * not only delete elements but also reset the array * sizes, as we will reuse this object later and refill ;) */ void cleanup(); public: struct ContextChange { KateHlContext* toContext; int pos; }; /** * Parse the text and fill in the context array and folding list array * * @param prevLine The previous line, the context array is picked up from that if present. * @param textLine The text line to parse * @param nextLine The next line, to check if indentation changed for indentation based folding. * @param ctxChanged will be set to reflect if the context changed * @param tabWidth tab width for indentation based folding, if wanted, else 0 */ void doHighlight(const Kate::TextLineData *prevLine, Kate::TextLineData *textLine, const Kate::TextLineData *nextLine, bool &ctxChanged, int tabWidth = 0, QVector* contextChanges = nullptr); /** * Saves the attribute definitions to the config file. * * @param schema The id of the schema group to save * @param list QList containing the data to be used */ void setKateExtendedAttributeList(const QString &schema, QList &list, KConfig *cfg = nullptr /*if 0 standard kate config*/, bool writeDefaultsToo = false); const QString &name() const { return iName; } const QString &nameTranslated() const { return iNameTranslated; } const QString §ion() const { return iSection; } bool hidden() const { return iHidden; } const QString &version() const { return iVersion; } const QString &style() const { return iStyle; } const QString &author() const { return iAuthor; } const QString &license() const { return iLicense; } const QString &getIdentifier() const { return identifier; } void use(); void reload(); /** * @return true if the character @p c is not a deliminator character * for the corresponding highlight. */ bool isInWord(QChar c, int attrib = 0) const; /** * @return true if the character @p c is a wordwrap deliminator as specified * in the general keyword section of the xml file. */ bool canBreakAt(QChar c, int attrib = 0) const; /** * */ QLinkedList emptyLines(int attribute = 0) const; bool isEmptyLine(const Kate::TextLineData *textline) const; /** * @return true if @p beginAttr and @p endAttr are members of the same * highlight, and there are comment markers of either type in that. */ bool canComment(int startAttr, int endAttr) const; /** * @return 0 if highlighting which attr is a member of does not * define a comment region, otherwise the region is returned */ signed char commentRegion(int attr) const; /** * @return the mulitiline comment start marker for the highlight * corresponding to @p attrib. */ QString getCommentStart(int attrib = 0) const; /** * @return the muiltiline comment end marker for the highlight corresponding * to @p attrib. */ QString getCommentEnd(int attrib = 0) const; /** * @return the single comment marker for the highlight corresponding * to @p attrib. */ QString getCommentSingleLineStart(int attrib = 0) const; const QHash &characterEncodings(int attrib = 0) const; /** * This enum is used for storing the information where a single line comment marker should be inserted */ enum CSLPos { CSLPosColumn0 = 0, CSLPosAfterWhitespace = 1}; /** * @return the single comment marker position for the highlight corresponding * to @p attrib. */ CSLPos getCommentSingleLinePosition(int attrib = 0) const; /** * @return the attribute for @p context. */ int attribute(int context) const; bool attributeRequiresSpellchecking(int attr); /** * map attribute to its highlighting file. * the returned string is used as key for m_additionalData. */ QString hlKeyForAttrib(int attrib) const; QString hlKeyForContext(int attrib) const; KateHlContext* contextForLocation(KTextEditor::DocumentPrivate* doc, const KTextEditor::Cursor& cursor); KTextEditor::DefaultStyle defaultStyleForAttribute(int attr) const; void clearAttributeArrays(); QList attributes(const QString &schema); inline bool noHighlighting() const { return noHl; } // be carefull: all documents hl should be invalidated after calling this method! void dropDynamicContexts(); QString indentation() { return m_indentation; } void getKateExtendedAttributeList(const QString &schema, QList &, KConfig *cfg = nullptr); void getKateExtendedAttributeListCopy(const QString &schema, QList &, KConfig *cfg = nullptr); const QHash &getCharacterEncodings(int attrib) const; const KatePrefixStore &getCharacterEncodingsPrefixStore(int attrib) const; const QHash &getReverseCharacterEncodings(int attrib) const; int getEncodedCharactersInsertionPolicy(int attrib) const; /** * Returns a list of names of embedded modes. */ QStringList getEmbeddedHighlightingModes() const; KateHlContext *contextNum(int n) const; private: /** * 'encoding' must not contain new line characters, i.e. '\n' or '\r'! **/ void addCharacterEncoding(const QString &key, const QString &encoding, const QChar &c); private: void init(); void makeContextList(); int makeDynamicContext(KateHlContext *model, const QStringList *args); void handleKateHlIncludeRules(); void handleKateHlIncludeRulesRecursive(int index, KateHlIncludeRules *list); int addToContextList(const QString &ident, int ctx0); void addToKateExtendedAttributeList(); void createKateExtendedAttribute(QList &list); void readGlobalKeywordConfig(); void readWordWrapConfig(); void readCommentConfig(); void readEmptyLineConfig(); void readIndentationConfig(); void readFoldingConfig(); void readSpellCheckingConfig(); /** * update given context stack * @param contextStack context stack to manipulate * @param modification description of the modification of the stack to execute * @param indexLastContextPreviousLine index of the last context from the previous line which still is in the stack * @return current active context, last one of the stack or default context 0 for empty stack */ KateHlContext *generateContextStack(Kate::TextLineData::ContextStack &contextStack, KateHlContextModification modification, int &indexLastContextPreviousLine); KateHlItem *createKateHlItem(KateSyntaxContextData *data, QList &iDl, QStringList *RegionList, QStringList *ContextList); int lookupAttrName(const QString &name, QList &iDl); void createContextNameList(QStringList *ContextNameList, int ctx0); KateHlContextModification getContextModificationFromString(QStringList *ContextNameList, QString tmpLineEndContext,/*NO CONST*/ QString &unres); QList internalIDList; QVector m_contexts; QMap< QPair, short> dynamicCtxs; // make them pointers perhaps // NOTE: gets cleaned once makeContextList() finishes KateEmbeddedHlInfos embeddedHls; // valid once makeContextList() finishes QStringList embeddedHighlightingModes; KateHlUnresolvedCtxRefs unresolvedContextReferences; QStringList RegionList; QStringList ContextNameList; bool noHl; bool folding; bool casesensitive; QString weakDeliminator; QString deliminator; QString iName; QString iNameTranslated; QString iSection; bool iHidden; QString identifier; QString iVersion; QString iStyle; QString iAuthor; QString iLicense; QString m_indentation; int refCount; int startctx, base_startctx; QString errorsAndWarnings; QString buildIdentifier; QString buildPrefix; bool building; uint buildContext0Offset; KateHlIncludeRules includeRules; bool m_foldingIndentationSensitive; // map schema name to attributes... QHash< QString, QList > m_attributeArrays; // list of all created items to delete them later QList m_hlItemCleanupList; /** * This class holds the additional properties for one highlight * definition, such as comment strings, deliminators etc. * * When a highlight is added, a instance of this class is appended to * m_additionalData, and the current position in the attrib and context * arrays are stored in the indexes for look up. You can then use * hlKeyForAttrib or hlKeyForContext to find the relevant instance of this * class from m_additionalData. * * If you need to add a property to a highlight, add it here. */ class HighlightPropertyBag { public: QString singleLineCommentMarker; QString multiLineCommentStart; QString multiLineCommentEnd; QString multiLineRegion; CSLPos singleLineCommentPosition; QString deliminator; QString wordWrapDeliminator; QLinkedList emptyLines; QHash characterEncodings; KatePrefixStore characterEncodingsPrefixStore; QHash reverseCharacterEncodings; int encodedCharactersInsertionPolicy; }; /** * Highlight properties for each included highlight definition. * The key is the identifier */ QHash m_additionalData; /** * Fast lookup of hl properties, based on attribute index * The key is the starting index in the attribute array for each file. * @see hlKeyForAttrib */ QMap m_hlIndex; QMap m_ctxIndex; public: inline bool foldingIndentationSensitive() { return m_foldingIndentationSensitive; } inline bool allowsFolding() { return folding; } }; #endif diff --git a/src/syntax/katehighlighthelpers.h b/src/syntax/katehighlighthelpers.h index 029adff9..27688bca 100644 --- a/src/syntax/katehighlighthelpers.h +++ b/src/syntax/katehighlighthelpers.h @@ -1,378 +1,378 @@ /* This file is part of the KDE libraries Copyright (C) 2001,2002 Joseph Wenninger Copyright (C) 2001 Christoph Cullmann 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 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_HIGHLIGHTHELPERS_H__ #define __KATE_HIGHLIGHTHELPERS_H__ #include "katehighlight.h" #include class KateHlItem { public: KateHlItem(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2); virtual ~KateHlItem(); public: // caller must keep in mind: LEN > 0 is a must !!!!!!!!!!!!!!!!!!!!!1 // Now, the function returns the offset detected, or 0 if no match is found. // bool linestart isn't needed, this is equivalent to offset == 0. virtual int checkHgl(const QString &text, int offset, int len) = 0; virtual bool lineContinue() { return false; } virtual void capturedTexts(QStringList &) { } virtual KateHlItem *clone(const QStringList *) { return this; } static void dynamicSubstitute(QString &str, const QStringList *args); QVector subItems; int attr; KateHlContextModification ctx; signed char region; signed char region2; bool lookAhead; bool dynamic; bool dynamicChild; bool firstNonSpace; bool onlyConsume; int column; // start enable flags, nicer than the virtual methodes // saves function calls bool alwaysStartEnable; bool customStartEnable; // set to true when you cached something bool haveCache; // internal for doHighlight, don't set it in the items bool cachingHandled; }; class KateHlContext { public: KateHlContext(const QString &_hlId, int attribute, KateHlContextModification _lineEndContext, bool _fallthrough, KateHlContextModification _fallthroughContext, bool _dynamic, bool _noIndentationBasedFolding, bool _emptyLineContex, KateHlContextModification _emptyLineContextModification ); virtual ~KateHlContext(); KateHlContext *clone(const QStringList *args); QVector items; QString hlId; ///< A unique highlight identifier. Used to look up correct properties. int attr; KateHlContextModification lineEndContext; /** @internal anders: possible escape if no rules matches. false unless 'fallthrough="1|true"' (insensitive) if true, go to ftcxt w/o eating of string. ftctx is "fallthroughContext" in xml files, valid values are int or #pop[..] see in KateHighlighting::doHighlight */ bool fallthrough; KateHlContextModification ftctx; // where to go after no rules matched bool dynamic; bool dynamicChild; bool noIndentationBasedFolding; bool emptyLineContext; KateHlContextModification emptyLineContextModification; }; class KateHlIncludeRule { public: explicit KateHlIncludeRule(int ctx_ = 0, uint pos_ = 0, const QString &incCtxN_ = QString(), bool incAttrib = false) : ctx(ctx_) , pos(pos_) , incCtxN(incCtxN_) , includeAttrib(incAttrib) { incCtx = -1; } //KateHlIncludeRule(int ctx_, uint pos_, bool incAttrib) {ctx=ctx_;pos=pos_;incCtx=-1;incCtxN="";includeAttrib=incAttrib} public: int ctx; uint pos; KateHlContextModification incCtx; QString incCtxN; bool includeAttrib; }; class KateHlCharDetect : public KateHlItem { public: KateHlCharDetect(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2, QChar); - int checkHgl(const QString &text, int offset, int len) Q_DECL_OVERRIDE; - KateHlItem *clone(const QStringList *args) Q_DECL_OVERRIDE; + int checkHgl(const QString &text, int offset, int len) override; + KateHlItem *clone(const QStringList *args) override; private: QChar sChar; }; class KateHl2CharDetect : public KateHlItem { public: KateHl2CharDetect(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2, QChar ch1, QChar ch2); KateHl2CharDetect(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2, const QChar *ch); - int checkHgl(const QString &text, int offset, int len) Q_DECL_OVERRIDE; - KateHlItem *clone(const QStringList *args) Q_DECL_OVERRIDE; + int checkHgl(const QString &text, int offset, int len) override; + KateHlItem *clone(const QStringList *args) override; private: QChar sChar1; QChar sChar2; }; class KateHlStringDetect : public KateHlItem { public: KateHlStringDetect(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2, const QString &, bool inSensitive = false); - int checkHgl(const QString &text, int offset, int len) Q_DECL_OVERRIDE; - KateHlItem *clone(const QStringList *args) Q_DECL_OVERRIDE; + int checkHgl(const QString &text, int offset, int len) override; + KateHlItem *clone(const QStringList *args) override; protected: const QString str; const int strLen; const bool _inSensitive; }; class KateHlWordDetect : public KateHlStringDetect { public: KateHlWordDetect(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2, const QString &, bool inSensitive = false); - int checkHgl(const QString &text, int offset, int len) Q_DECL_OVERRIDE; - KateHlItem *clone(const QStringList *args) Q_DECL_OVERRIDE; + int checkHgl(const QString &text, int offset, int len) override; + KateHlItem *clone(const QStringList *args) override; }; class KateHlRangeDetect : public KateHlItem { public: KateHlRangeDetect(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2, QChar ch1, QChar ch2); - int checkHgl(const QString &text, int offset, int len) Q_DECL_OVERRIDE; + int checkHgl(const QString &text, int offset, int len) override; private: QChar sChar1; QChar sChar2; }; class KateHlKeyword : public KateHlItem { public: KateHlKeyword(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2, bool insensitive, const QString &delims); - virtual ~KateHlKeyword(); + ~KateHlKeyword() override; QSet allKeywords() const; void addList(const QStringList &); - int checkHgl(const QString &text, int offset, int len) Q_DECL_OVERRIDE; + int checkHgl(const QString &text, int offset, int len) override; private: QVector< QSet* > dict; bool _insensitive; QSet deliminators; int minLen; int maxLen; }; class KateHlInt : public KateHlItem { public: KateHlInt(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2); - int checkHgl(const QString &text, int offset, int len) Q_DECL_OVERRIDE; + int checkHgl(const QString &text, int offset, int len) override; }; class KateHlFloat : public KateHlItem { public: KateHlFloat(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2); - virtual ~KateHlFloat() {} + ~KateHlFloat() override {} - int checkHgl(const QString &text, int offset, int len) Q_DECL_OVERRIDE; + int checkHgl(const QString &text, int offset, int len) override; }; class KateHlCFloat : public KateHlFloat { public: KateHlCFloat(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2); - int checkHgl(const QString &text, int offset, int len) Q_DECL_OVERRIDE; + int checkHgl(const QString &text, int offset, int len) override; int checkIntHgl(const QString &text, int offset, int len); }; class KateHlCOct : public KateHlItem { public: KateHlCOct(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2); - int checkHgl(const QString &text, int offset, int len) Q_DECL_OVERRIDE; + int checkHgl(const QString &text, int offset, int len) override; }; class KateHlCHex : public KateHlItem { public: KateHlCHex(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2); - int checkHgl(const QString &text, int offset, int len) Q_DECL_OVERRIDE; + int checkHgl(const QString &text, int offset, int len) override; }; class KateHlLineContinue : public KateHlItem { public: KateHlLineContinue(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2, QChar c); virtual bool endEnable(QChar c) { return c == QLatin1Char('\0'); } - int checkHgl(const QString &text, int offset, int len) Q_DECL_OVERRIDE; - bool lineContinue() Q_DECL_OVERRIDE + int checkHgl(const QString &text, int offset, int len) override; + bool lineContinue() override { return true; } private: QChar m_trailer; }; class KateHlCStringChar : public KateHlItem { public: KateHlCStringChar(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2); - int checkHgl(const QString &text, int offset, int len) Q_DECL_OVERRIDE; + int checkHgl(const QString &text, int offset, int len) override; }; class KateHlCChar : public KateHlItem { public: KateHlCChar(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2); - int checkHgl(const QString &text, int offset, int len) Q_DECL_OVERRIDE; + int checkHgl(const QString &text, int offset, int len) override; }; class KateHlAnyChar : public KateHlItem { public: KateHlAnyChar(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2, const QString &charList); - int checkHgl(const QString &text, int offset, int len) Q_DECL_OVERRIDE; + int checkHgl(const QString &text, int offset, int len) override; private: const QString _charList; }; class KateHlRegExpr : public KateHlItem { public: KateHlRegExpr(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2, const QString &expr, bool insensitive, bool minimal); - int checkHgl(const QString &text, int offset, int len) Q_DECL_OVERRIDE; + int checkHgl(const QString &text, int offset, int len) override; - void capturedTexts(QStringList &) Q_DECL_OVERRIDE; + void capturedTexts(QStringList &) override; - KateHlItem *clone(const QStringList *args) Q_DECL_OVERRIDE; + KateHlItem *clone(const QStringList *args) override; private: /** * regular expression to match */ const QRegularExpression m_regularExpression; /** * does the regular expression start with ^? * allows to skip for any offset > 0 */ const bool m_handlesLineStart; /** * last match, if any */ QRegularExpressionMatch m_lastMatch; }; class KateHlDetectSpaces : public KateHlItem { public: KateHlDetectSpaces(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2) : KateHlItem(attribute, context, regionId, regionId2) {} - int checkHgl(const QString &text, int offset, int len) Q_DECL_OVERRIDE + int checkHgl(const QString &text, int offset, int len) override { int len2 = offset + len; while ((offset < len2) && text[offset].isSpace()) { offset++; } return offset; } }; class KateHlDetectIdentifier : public KateHlItem { public: KateHlDetectIdentifier(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2) : KateHlItem(attribute, context, regionId, regionId2) { alwaysStartEnable = false; } - int checkHgl(const QString &text, int offset, int len) Q_DECL_OVERRIDE + int checkHgl(const QString &text, int offset, int len) override { // first char should be a letter or underscore if (text[offset].isLetter() || text[offset] == QLatin1Char('_')) { // memorize length int len2 = offset + len; // one char seen offset++; // now loop for all other thingies while ( (offset < len2) && (text[offset].isLetterOrNumber() || (text[offset] == QLatin1Char('_'))) ) { offset++; } return offset; } return 0; } }; //END #endif diff --git a/src/syntax/katehighlightingcmds.h b/src/syntax/katehighlightingcmds.h index a0031e98..811e8eaf 100644 --- a/src/syntax/katehighlightingcmds.h +++ b/src/syntax/katehighlightingcmds.h @@ -1,68 +1,68 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2014 Christoph Rüßler * * 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_HIGHLIGHT_RELOAD_H #define KATE_HIGHLIGHT_RELOAD_H #include namespace KateCommands { class Highlighting : public KTextEditor::Command { Highlighting() : KTextEditor::Command({ QStringLiteral("reload-highlighting"), QStringLiteral("edit-highlighting") }) { } static Highlighting* m_instance; public: - ~Highlighting() + ~Highlighting() override { m_instance = nullptr; } static Highlighting *self() { if (m_instance == nullptr) { m_instance = new Highlighting(); } return m_instance; } /** * execute command * @param view view to use for execution * @param cmd cmd string * @param errorMsg error to return if no success * @return success */ bool exec(class KTextEditor::View *view, const QString &cmd, QString &errorMsg, - const KTextEditor::Range &range = KTextEditor::Range::invalid()) Q_DECL_OVERRIDE; + const KTextEditor::Range &range = KTextEditor::Range::invalid()) override; /** This command does not have help. @see KTextEditor::Command::help */ - bool help(class KTextEditor::View *, const QString &, QString &) Q_DECL_OVERRIDE; + bool help(class KTextEditor::View *, const QString &, QString &) override; }; } #endif diff --git a/src/syntax/katehighlightmenu.h b/src/syntax/katehighlightmenu.h index 064419d4..a52c14af 100644 --- a/src/syntax/katehighlightmenu.h +++ b/src/syntax/katehighlightmenu.h @@ -1,64 +1,65 @@ /* This file is part of the KDE libraries Copyright (C) 2001-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_HIGHLIGHTMENU_H__ #define KATE_HIGHLIGHTMENU_H__ #include #include #include #include namespace KTextEditor { class DocumentPrivate; } class KateHighlightingMenu : public KActionMenu { Q_OBJECT public: KateHighlightingMenu(const QString &text, QObject *parent) : KActionMenu(text, parent) { init(); + setDelayed(false); } ~KateHighlightingMenu(); void updateMenu(KTextEditor::DocumentPrivate *doc); private: void init(); QPointer m_doc; QStringList subMenusName; QStringList names; QList subMenus; QList subActions; QActionGroup *m_actionGroup; public Q_SLOTS: void slotAboutToShow(); private Q_SLOTS: void setHl(); }; #endif diff --git a/src/syntax/katesyntaxmanager.cpp b/src/syntax/katesyntaxmanager.cpp index 0488a0b9..e38a9306 100644 --- a/src/syntax/katesyntaxmanager.cpp +++ b/src/syntax/katesyntaxmanager.cpp @@ -1,720 +1,718 @@ /* This file is part of the KDE libraries Copyright (C) 2007 Matthew Woehlke Copyright (C) 2003, 2004 Anders Lund Copyright (C) 2003 Hamish Rodda Copyright (C) 2001,2002 Joseph Wenninger Copyright (C) 2001 Christoph Cullmann 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 as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ //BEGIN INCLUDES #include "katesyntaxmanager.h" #include "katetextline.h" #include "katedocument.h" #include "katesyntaxdocument.h" #include "katerenderer.h" #include "kateglobal.h" #include "kateschema.h" #include "kateconfig.h" #include "kateextendedattribute.h" #include "katehighlight.h" #include "katepartdebug.h" #include "katedefaultcolors.h" #include #include #include #include #include #include #include #include #include #include #include #include //END using namespace KTextEditor; //BEGIN KateHlManager KateHlManager::KateHlManager() : QObject() , m_config(KTextEditor::EditorPrivate::unitTestMode() ? QString() :QStringLiteral("katesyntaxhighlightingrc") - , KTextEditor::EditorPrivate::unitTestMode() ? KConfig::SimpleConfig : KConfig::NoGlobals) // skip config for unit tests! + , KTextEditor::EditorPrivate::unitTestMode() ? KConfig::SimpleConfig : KConfig::NoGlobals) // skip config for unit tests! , commonSuffixes({QStringLiteral(".orig"), QStringLiteral(".new"), QStringLiteral("~"), QStringLiteral(".bak"), QStringLiteral(".BAK")}) - , dynamicCtxsCount(0) - , forceNoDCReset(false) { // Let's build the Mode List setupModeList(); lastCtxsReset.start(); } KateHlManager::~KateHlManager() { qDeleteAll(hlList); } void KateHlManager::setupModeList() { const auto defs = m_repository.definitions(); hlList.reserve(defs.size() + 1); hlDict.reserve(defs.size() + 1); for (int i = 0; i < defs.count(); i++) { KateHighlighting *hl = new KateHighlighting(defs.at(i)); hlList.push_back(hl); hlDict.insert(hl->name(), hl); } // Normal HL KateHighlighting *hl = new KateHighlighting(KSyntaxHighlighting::Definition()); hlList.prepend(hl); hlDict.insert(hl->name(), hl); } KateHlManager *KateHlManager::self() { return KTextEditor::EditorPrivate::self()->hlManager(); } KateHighlighting *KateHlManager::getHl(int n) { if (n < 0 || n >= hlList.count()) { n = 0; } return hlList.at(n); } int KateHlManager::nameFind(const QString &name) { for (int i = 0; i < hlList.count(); ++i) { if (hlList.at(i)->name().compare(name, Qt::CaseInsensitive) == 0) { return i; } } return -1; } int KateHlManager::defaultStyleCount() { return KTextEditor::dsError + 1; } QString KateHlManager::defaultStyleName(int n, bool translateNames) { static QStringList names; static QStringList translatedNames; if (names.isEmpty()) { names << QStringLiteral("Normal"); names << QStringLiteral("Keyword"); names << QStringLiteral("Function"); names << QStringLiteral("Variable"); names << QStringLiteral("Control Flow"); names << QStringLiteral("Operator"); names << QStringLiteral("Built-in"); names << QStringLiteral("Extension"); names << QStringLiteral("Preprocessor"); names << QStringLiteral("Attribute"); names << QStringLiteral("Character"); names << QStringLiteral("Special Character"); names << QStringLiteral("String"); names << QStringLiteral("Verbatim String"); names << QStringLiteral("Special String"); names << QStringLiteral("Import"); names << QStringLiteral("Data Type"); names << QStringLiteral("Decimal/Value"); names << QStringLiteral("Base-N Integer"); names << QStringLiteral("Floating Point"); names << QStringLiteral("Constant"); names << QStringLiteral("Comment"); names << QStringLiteral("Documentation"); names << QStringLiteral("Annotation"); names << QStringLiteral("Comment Variable"); // this next one is for denoting the beginning/end of a user defined folding region names << QStringLiteral("Region Marker"); names << QStringLiteral("Information"); names << QStringLiteral("Warning"); names << QStringLiteral("Alert"); names << QStringLiteral("Others"); // this one is for marking invalid input names << QStringLiteral("Error"); translatedNames << i18nc("@item:intable Text context", "Normal"); translatedNames << i18nc("@item:intable Text context", "Keyword"); translatedNames << i18nc("@item:intable Text context", "Function"); translatedNames << i18nc("@item:intable Text context", "Variable"); translatedNames << i18nc("@item:intable Text context", "Control Flow"); translatedNames << i18nc("@item:intable Text context", "Operator"); translatedNames << i18nc("@item:intable Text context", "Built-in"); translatedNames << i18nc("@item:intable Text context", "Extension"); translatedNames << i18nc("@item:intable Text context", "Preprocessor"); translatedNames << i18nc("@item:intable Text context", "Attribute"); translatedNames << i18nc("@item:intable Text context", "Character"); translatedNames << i18nc("@item:intable Text context", "Special Character"); translatedNames << i18nc("@item:intable Text context", "String"); translatedNames << i18nc("@item:intable Text context", "Verbatim String"); translatedNames << i18nc("@item:intable Text context", "Special String"); translatedNames << i18nc("@item:intable Text context", "Imports, Modules, Includes"); translatedNames << i18nc("@item:intable Text context", "Data Type"); translatedNames << i18nc("@item:intable Text context", "Decimal/Value"); translatedNames << i18nc("@item:intable Text context", "Base-N Integer"); translatedNames << i18nc("@item:intable Text context", "Floating Point"); translatedNames << i18nc("@item:intable Text context", "Constant"); translatedNames << i18nc("@item:intable Text context", "Comment"); translatedNames << i18nc("@item:intable Text context", "Documentation"); translatedNames << i18nc("@item:intable Text context", "Annotation"); translatedNames << i18nc("@item:intable Text context", "Comment Variable"); // this next one is for denoting the beginning/end of a user defined folding region translatedNames << i18nc("@item:intable Text context", "Region Marker"); translatedNames << i18nc("@item:intable Text context", "Information"); translatedNames << i18nc("@item:intable Text context", "Warning"); translatedNames << i18nc("@item:intable Text context", "Alert"); translatedNames << i18nc("@item:intable Text context", "Others"); // this one is for marking invalid input translatedNames << i18nc("@item:intable Text context", "Error"); } // sanity checks Q_ASSERT(n >= 0); Q_ASSERT(n < names.size()); return translateNames ? translatedNames[n] : names[n]; } int KateHlManager::defaultStyleNameToIndex(const QString &name) { // // Normal text and source code // if (name == QLatin1String("dsNormal")) { return KTextEditor::dsNormal; } else if (name == QLatin1String("dsKeyword")) { return KTextEditor::dsKeyword; } else if (name == QLatin1String("dsFunction")) { return KTextEditor::dsFunction; } else if (name == QLatin1String("dsVariable")) { return KTextEditor::dsVariable; } else if (name == QLatin1String("dsControlFlow")) { return KTextEditor::dsControlFlow; } else if (name == QLatin1String("dsOperator")) { return KTextEditor::dsOperator; } else if (name == QLatin1String("dsBuiltIn")) { return KTextEditor::dsBuiltIn; } else if (name == QLatin1String("dsExtension")) { return KTextEditor::dsExtension; } else if (name == QLatin1String("dsPreprocessor")) { return KTextEditor::dsPreprocessor; } else if (name == QLatin1String("dsAttribute")) { return KTextEditor::dsAttribute; } // // Strings & Characters // if (name == QLatin1String("dsChar")) { return KTextEditor::dsChar; } else if (name == QLatin1String("dsSpecialChar")) { return KTextEditor::dsSpecialChar; } else if (name == QLatin1String("dsString")) { return KTextEditor::dsString; } else if (name == QLatin1String("dsVerbatimString")) { return KTextEditor::dsVerbatimString; } else if (name == QLatin1String("dsSpecialString")) { return KTextEditor::dsSpecialString; } else if (name == QLatin1String("dsImport")) { return KTextEditor::dsImport; } // // Numbers, Types & Constants // if (name == QLatin1String("dsDataType")) { return KTextEditor::dsDataType; } else if (name == QLatin1String("dsDecVal")) { return KTextEditor::dsDecVal; } else if (name == QLatin1String("dsBaseN")) { return KTextEditor::dsBaseN; } else if (name == QLatin1String("dsFloat")) { return KTextEditor::dsFloat; } else if (name == QLatin1String("dsConstant")) { return KTextEditor::dsConstant; } // // Comments & Documentation // if (name == QLatin1String("dsComment")) { return KTextEditor::dsComment; } else if (name == QLatin1String("dsDocumentation")) { return KTextEditor::dsDocumentation; } else if (name == QLatin1String("dsAnnotation")) { return KTextEditor::dsAnnotation; } else if (name == QLatin1String("dsCommentVar")) { return KTextEditor::dsCommentVar; } else if (name == QLatin1String("dsRegionMarker")) { return KTextEditor::dsRegionMarker; } else if (name == QLatin1String("dsInformation")) { return KTextEditor::dsInformation; } else if (name == QLatin1String("dsWarning")) { return KTextEditor::dsWarning; } else if (name == QLatin1String("dsAlert")) { return KTextEditor::dsAlert; } // // Misc // if (name == QLatin1String("dsOthers")) { return KTextEditor::dsOthers; } else if (name == QLatin1String("dsError")) { return KTextEditor::dsError; } return KTextEditor::dsNormal; } void KateHlManager::getDefaults(const QString &schema, KateAttributeList &list, KConfig *cfg) { const KColorScheme &scheme(KTextEditor::EditorPrivate::self()->defaultColors().view()); const KColorScheme &schemeSelected(KTextEditor::EditorPrivate::self()->defaultColors().selection()); ///NOTE: it's important to append in the order of the KTextEditor::DefaultStyle /// enum, to make KTextEditor::DocumentPrivate::defaultStyle() work properly. { // dsNormal Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground().color()); attrib->setSelectedForeground(schemeSelected.foreground().color()); list.append(attrib); } { // dsKeyword Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground().color()); attrib->setSelectedForeground(schemeSelected.foreground().color()); attrib->setFontBold(true); list.append(attrib); } { // dsFunction Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::NeutralText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::NeutralText).color()); list.append(attrib); } { // dsVariable Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::LinkText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::LinkText).color()); list.append(attrib); } { // dsControlFlow Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground().color()); attrib->setSelectedForeground(schemeSelected.foreground().color()); attrib->setFontBold(true); list.append(attrib); } { // dsOperator Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground().color()); attrib->setSelectedForeground(schemeSelected.foreground().color()); list.append(attrib); } { // dsBuiltIn Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::VisitedText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::VisitedText).color()); list.append(attrib); } { // dsExtension Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(QColor(0, 149, 255)); attrib->setSelectedForeground(schemeSelected.foreground().color()); attrib->setFontBold(true); list.append(attrib); } { // dsPreprocessor Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::PositiveText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::PositiveText).color()); list.append(attrib); } { // dsAttribute Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::LinkText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::LinkText).color()); list.append(attrib); } { // dsChar Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::ActiveText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::ActiveText).color()); list.append(attrib); } { // dsSpecialChar Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::ActiveText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::ActiveText).color()); list.append(attrib); } { // dsString Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::NegativeText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::NegativeText).color()); list.append(attrib); } { // dsVerbatimString Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::NegativeText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::NegativeText).color()); list.append(attrib); } { // dsSpecialString Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::NegativeText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::NegativeText).color()); list.append(attrib); } { // dsImport Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::VisitedText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::VisitedText).color()); list.append(attrib); } { // dsDataType Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::LinkText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::LinkText).color()); list.append(attrib); } { // dsDecVal Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::NeutralText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::NeutralText).color()); list.append(attrib); } { // dsBaseN Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::NeutralText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::NeutralText).color()); list.append(attrib); } { // dsFloat Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::NeutralText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::NeutralText).color()); list.append(attrib); } { // dsConstant Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground().color()); attrib->setSelectedForeground(schemeSelected.foreground().color()); attrib->setFontBold(true); list.append(attrib); } { // dsComment Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::InactiveText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::InactiveText).color()); list.append(attrib); } { // dsDocumentation Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::NegativeText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::NegativeText).color()); list.append(attrib); } { // dsAnnotation Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::VisitedText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::VisitedText).color()); list.append(attrib); } { // dsCommentVar Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::VisitedText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::VisitedText).color()); list.append(attrib); } { // dsRegionMarker Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::LinkText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::LinkText).color()); attrib->setBackground(scheme.background(KColorScheme::LinkBackground).color()); list.append(attrib); } { // dsInformation Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::NeutralText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::NeutralText).color()); list.append(attrib); } { // dsWarning Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::NegativeText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::NegativeText).color()); list.append(attrib); } { // dsAlert Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::NegativeText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::NegativeText).color()); attrib->setFontBold(true); attrib->setBackground(scheme.background(KColorScheme::NegativeBackground).color()); list.append(attrib); } { // dsOthers Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::PositiveText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::PositiveText).color()); list.append(attrib); } { // dsError Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::NegativeText)); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::NegativeText).color()); attrib->setFontUnderline(true); list.append(attrib); } KConfigGroup config(cfg ? cfg : KateHlManager::self()->self()->getKConfig(), QLatin1String("Default Item Styles - Schema ") + schema); for (int z = 0; z < defaultStyleCount(); z++) { KTextEditor::Attribute::Ptr i = list.at(z); QStringList s = config.readEntry(defaultStyleName(z), QStringList()); if (!s.isEmpty()) { while (s.count() < 9) { s << QString(); } QString tmp; QRgb col; tmp = s[0]; if (!tmp.isEmpty()) { col = tmp.toUInt(nullptr, 16); i->setForeground(QColor(col)); } tmp = s[1]; if (!tmp.isEmpty()) { col = tmp.toUInt(nullptr, 16); i->setSelectedForeground(QColor(col)); } tmp = s[2]; if (!tmp.isEmpty()) { i->setFontBold(tmp != QLatin1String("0")); } tmp = s[3]; if (!tmp.isEmpty()) { i->setFontItalic(tmp != QLatin1String("0")); } tmp = s[4]; if (!tmp.isEmpty()) { i->setFontStrikeOut(tmp != QLatin1String("0")); } tmp = s[5]; if (!tmp.isEmpty()) { i->setFontUnderline(tmp != QLatin1String("0")); } tmp = s[6]; if (!tmp.isEmpty()) { if (tmp != QLatin1String("-")) { col = tmp.toUInt(nullptr, 16); i->setBackground(QColor(col)); } else { i->clearBackground(); } } tmp = s[7]; if (!tmp.isEmpty()) { if (tmp != QLatin1String("-")) { col = tmp.toUInt(nullptr, 16); i->setSelectedBackground(QColor(col)); } else { i->clearProperty(SelectedBackground); } } tmp = s[8]; if (!tmp.isEmpty() && tmp != QLatin1String("---")) { i->setFontFamily(tmp); } } } } void KateHlManager::setDefaults(const QString &schema, KateAttributeList &list, KConfig *cfg) { cfg = cfg ? cfg : KateHlManager::self()->self()->getKConfig(); KConfigGroup config(cfg, QLatin1String("Default Item Styles - Schema ") + schema); const QString zero = QStringLiteral("0"); const QString one = QStringLiteral("1"); const QString dash = QStringLiteral("-"); for (int z = 0; z < defaultStyleCount(); z++) { QStringList settings; KTextEditor::Attribute::Ptr p = list.at(z); settings << (p->hasProperty(QTextFormat::ForegroundBrush) ? QString::number(p->foreground().color().rgb(), 16) : QString()); settings << (p->hasProperty(SelectedForeground) ? QString::number(p->selectedForeground().color().rgb(), 16) : QString()); settings << (p->hasProperty(QTextFormat::FontWeight) ? (p->fontBold() ? one : zero) : QString()); settings << (p->hasProperty(QTextFormat::FontItalic) ? (p->fontItalic() ? one : zero) : QString()); settings << (p->hasProperty(QTextFormat::FontStrikeOut) ? (p->fontStrikeOut() ? one : zero) : QString()); settings << (p->hasProperty(QTextFormat::FontUnderline) ? (p->fontUnderline() ? one : zero) : QString()); settings << (p->hasProperty(QTextFormat::BackgroundBrush) ? QString::number(p->background().color().rgb(), 16) : dash); settings << (p->hasProperty(SelectedBackground) ? QString::number(p->selectedBackground().color().rgb(), 16) : dash); settings << (p->hasProperty(QTextFormat::FontFamily) ? (p->fontFamily()) : QString()); settings << QStringLiteral("---"); config.writeEntry(defaultStyleName(z), settings); } emit changed(); } int KateHlManager::highlights() { return (int) hlList.count(); } QString KateHlManager::hlName(int n) { return hlList.at(n)->name(); } QString KateHlManager::hlNameTranslated(int n) { return hlList.at(n)->nameTranslated(); } QString KateHlManager::hlSection(int n) { return hlList.at(n)->section(); } bool KateHlManager::hlHidden(int n) { return hlList.at(n)->hidden(); } QString KateHlManager::identifierForName(const QString &name) { if (hlDict.contains(name)) { return hlDict[name]->getIdentifier(); } return QString(); } QString KateHlManager::nameForIdentifier(const QString &identifier) { for (QHash::iterator it = hlDict.begin(); it != hlDict.end(); ++it) { if ((*it)->getIdentifier() == identifier) { return it.key(); } } return QString(); } bool KateHlManager::resetDynamicCtxs() { if (forceNoDCReset) { return false; } if (lastCtxsReset.elapsed() < KATE_DYNAMIC_CONTEXTS_RESET_DELAY) { return false; } foreach (KateHighlighting *hl, hlList) { hl->dropDynamicContexts(); } dynamicCtxsCount = 0; lastCtxsReset.start(); return true; } void KateHlManager::reload() { // clear syntax document cache syntax.clearCache(); resetDynamicCtxs(); for(int i = 0; i < highlights(); i++) { getHl(i)->reload(); } foreach(KTextEditor::DocumentPrivate* doc, KTextEditor::EditorPrivate::self()->kateDocuments()) { doc->makeAttribs(); } } //END diff --git a/src/syntax/katesyntaxmanager.h b/src/syntax/katesyntaxmanager.h index a4fcc8f4..4a477d15 100644 --- a/src/syntax/katesyntaxmanager.h +++ b/src/syntax/katesyntaxmanager.h @@ -1,167 +1,167 @@ /* This file is part of the KDE libraries Copyright (C) 2001,2002 Joseph Wenninger Copyright (C) 2001 Christoph Cullmann 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 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_SYNTAXMANAGER_H__ #define __KATE_SYNTAXMANAGER_H__ #include "katetextline.h" #include "kateextendedattribute.h" #include "katesyntaxdocument.h" #include #include #include #include #include #include #include #include #include #include #include #include #include class KateHighlighting; class KateHlManager : public QObject { Q_OBJECT public: KateHlManager(); ~KateHlManager(); static KateHlManager *self(); KateSyntaxDocument *syntaxDocument() { return &syntax; } inline KConfig *getKConfig() { return &m_config; } KateHighlighting *getHl(int n); int nameFind(const QString &name); QString identifierForName(const QString &); /** * Returns the mode name for a given identifier, as e.g. * returned by KateHighlighting::hlKeyForAttrib(). */ QString nameForIdentifier(const QString &); void getDefaults(const QString &schema, KateAttributeList &, KConfig *cfg = nullptr); void setDefaults(const QString &schema, KateAttributeList &, KConfig *cfg = nullptr); int highlights(); QString hlName(int n); QString hlNameTranslated(int n); QString hlSection(int n); bool hlHidden(int n); void incDynamicCtxs() { ++dynamicCtxsCount; } int countDynamicCtxs() { return dynamicCtxsCount; } void setForceNoDCReset(bool b) { forceNoDCReset = b; } // be carefull: all documents hl should be invalidated after having successfully called this method! bool resetDynamicCtxs(); void reload(); Q_SIGNALS: void changed(); // // methodes to get the default style count + names // public: /** * Return the number of default styles. */ static int defaultStyleCount(); /** * Return the name of default style @p n. If @p translateNames is @e true, * the default style name is translated. */ static QString defaultStyleName(int n, bool translateNames = false); /** * Return the index for the default style @p name. * @param name @e untranslated default style * @see KTextEditor::DefaultStyles) */ static int defaultStyleNameToIndex(const QString &name); /** * Get the mode list * @return mode list */ QVector modeList() const { return m_repository.definitions(); } private: friend class KateHighlighting; /** * Generate the list of hl modes, store them in myModeList */ void setupModeList(); /** * Syntax highlighting definitions. */ KSyntaxHighlighting::Repository m_repository; // This list owns objects it holds, thus they should be deleted when the object is removed QList hlList; // This hash does not own the objects it holds, thus they should not be deleted QHash hlDict; KConfig m_config; QStringList commonSuffixes; KateSyntaxDocument syntax; - int dynamicCtxsCount; + int dynamicCtxsCount = 0; QTime lastCtxsReset; - bool forceNoDCReset; + bool forceNoDCReset = false; }; #endif diff --git a/src/undo/katemodifiedundo.h b/src/undo/katemodifiedundo.h index 270d03ce..24b27d6c 100644 --- a/src/undo/katemodifiedundo.h +++ b/src/undo/katemodifiedundo.h @@ -1,139 +1,139 @@ /* This file is part of the Kate project. * * Copyright (C) 2011 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. */ #ifndef KATE_MODIFIED_UNDO_H #define KATE_MODIFIED_UNDO_H #include "kateundo.h" class KateModifiedInsertText : public KateEditInsertTextUndo { public: KateModifiedInsertText(KTextEditor::DocumentPrivate *document, int line, int col, const QString &text); /** * @copydoc KateUndo::undo() */ - void undo() Q_DECL_OVERRIDE; + void undo() override; /** * @copydoc KateUndo::redo() */ - void redo() Q_DECL_OVERRIDE; + void redo() override; - void updateUndoSavedOnDiskFlag(QBitArray &lines) Q_DECL_OVERRIDE; - void updateRedoSavedOnDiskFlag(QBitArray &lines) Q_DECL_OVERRIDE; + void updateUndoSavedOnDiskFlag(QBitArray &lines) override; + void updateRedoSavedOnDiskFlag(QBitArray &lines) override; }; class KateModifiedRemoveText : public KateEditRemoveTextUndo { public: KateModifiedRemoveText(KTextEditor::DocumentPrivate *document, int line, int col, const QString &text); /** * @copydoc KateUndo::undo() */ - void undo() Q_DECL_OVERRIDE; + void undo() override; /** * @copydoc KateUndo::redo() */ - void redo() Q_DECL_OVERRIDE; + void redo() override; - void updateUndoSavedOnDiskFlag(QBitArray &lines) Q_DECL_OVERRIDE; - void updateRedoSavedOnDiskFlag(QBitArray &lines) Q_DECL_OVERRIDE; + void updateUndoSavedOnDiskFlag(QBitArray &lines) override; + void updateRedoSavedOnDiskFlag(QBitArray &lines) override; }; class KateModifiedWrapLine : public KateEditWrapLineUndo { public: KateModifiedWrapLine(KTextEditor::DocumentPrivate *document, int line, int col, int len, bool newLine); /** * @copydoc KateUndo::undo() */ - void undo() Q_DECL_OVERRIDE; + void undo() override; /** * @copydoc KateUndo::redo() */ - void redo() Q_DECL_OVERRIDE; + void redo() override; - void updateUndoSavedOnDiskFlag(QBitArray &lines) Q_DECL_OVERRIDE; - void updateRedoSavedOnDiskFlag(QBitArray &lines) Q_DECL_OVERRIDE; + void updateUndoSavedOnDiskFlag(QBitArray &lines) override; + void updateRedoSavedOnDiskFlag(QBitArray &lines) override; }; class KateModifiedUnWrapLine : public KateEditUnWrapLineUndo { public: KateModifiedUnWrapLine(KTextEditor::DocumentPrivate *document, int line, int col, int len, bool removeLine); /** * @copydoc KateUndo::undo() */ - void undo() Q_DECL_OVERRIDE; + void undo() override; /** * @copydoc KateUndo::redo() */ - void redo() Q_DECL_OVERRIDE; + void redo() override; - void updateUndoSavedOnDiskFlag(QBitArray &lines) Q_DECL_OVERRIDE; - void updateRedoSavedOnDiskFlag(QBitArray &lines) Q_DECL_OVERRIDE; + void updateUndoSavedOnDiskFlag(QBitArray &lines) override; + void updateRedoSavedOnDiskFlag(QBitArray &lines) override; }; class KateModifiedInsertLine : public KateEditInsertLineUndo { public: KateModifiedInsertLine(KTextEditor::DocumentPrivate *document, int line, const QString &text); /** * @copydoc KateUndo::undo() */ - void undo() Q_DECL_OVERRIDE; + void undo() override; /** * @copydoc KateUndo::redo() */ - void redo() Q_DECL_OVERRIDE; + void redo() override; - void updateRedoSavedOnDiskFlag(QBitArray &lines) Q_DECL_OVERRIDE; + void updateRedoSavedOnDiskFlag(QBitArray &lines) override; }; class KateModifiedRemoveLine : public KateEditRemoveLineUndo { public: KateModifiedRemoveLine(KTextEditor::DocumentPrivate *document, int line, const QString &text); /** * @copydoc KateUndo::undo() */ - void undo() Q_DECL_OVERRIDE; + void undo() override; /** * @copydoc KateUndo::redo() */ - void redo() Q_DECL_OVERRIDE; + void redo() override; - void updateUndoSavedOnDiskFlag(QBitArray &lines) Q_DECL_OVERRIDE; + void updateUndoSavedOnDiskFlag(QBitArray &lines) override; }; #endif // KATE_MODIFIED_UNDO_H diff --git a/src/undo/kateundo.h b/src/undo/kateundo.h index ae0751ef..1f48d664 100644 --- a/src/undo/kateundo.h +++ b/src/undo/kateundo.h @@ -1,573 +1,573 @@ /* This file is part of the KDE libraries Copyright (C) 2011 Dominik Haumann Copyright (C) 2009-2010 Bernhard Beschow Copyright (C) 2002 John Firebaugh Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2016 Sven Brauch 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_undo_h #define kate_undo_h #include #include #include class KateUndoManager; namespace KTextEditor { class DocumentPrivate; } namespace KTextEditor { class View; } /** * Base class for Kate undo commands. */ class KateUndo { public: /** * Constructor * @param document the document the undo item belongs to */ KateUndo(KTextEditor::DocumentPrivate *document); /** * Destructor */ virtual ~KateUndo(); public: /** * Types for undo items */ enum UndoType { editInsertText, editRemoveText, editWrapLine, editUnWrapLine, editInsertLine, editRemoveLine, editMarkLineAutoWrapped, editInvalid }; public: /** * Check whether the item is empty. * * @return whether the item is empty */ virtual bool isEmpty() const; /** * merge an undo item * Saves a bit of memory and potentially many calls when undo/redoing. * @param undo undo item to merge * @return success */ virtual bool mergeWith(const KateUndo *undo); /** * undo this item */ virtual void undo() = 0; /** * redo this item */ virtual void redo() = 0; /** * type of item * @return type */ virtual KateUndo::UndoType type() const = 0; protected: /** * Return the document the undo item belongs to. * @return the document the undo item belongs to */ inline KTextEditor::DocumentPrivate *document() { return m_document; } private: /** * the document the undo item belongs to */ KTextEditor::DocumentPrivate *m_document; // // Line modification system // public: enum ModificationFlag { UndoLine1Modified = 1, UndoLine2Modified = 2, UndoLine1Saved = 4, UndoLine2Saved = 8, RedoLine1Modified = 16, RedoLine2Modified = 32, RedoLine1Saved = 64, RedoLine2Saved = 128 }; inline void setFlag(ModificationFlag flag) { m_lineModFlags |= flag; } inline void unsetFlag(ModificationFlag flag) { m_lineModFlags &= (~flag); } inline bool isFlagSet(ModificationFlag flag) const { return m_lineModFlags & flag; } virtual void updateUndoSavedOnDiskFlag(QBitArray &lines) { Q_UNUSED(lines) } virtual void updateRedoSavedOnDiskFlag(QBitArray &lines) { Q_UNUSED(lines) } private: uchar m_lineModFlags; }; class KateEditInsertTextUndo : public KateUndo { public: KateEditInsertTextUndo(KTextEditor::DocumentPrivate *document, int line, int col, const QString &text); /** * @copydoc KateUndo::isEmpty() */ - bool isEmpty() const Q_DECL_OVERRIDE; + bool isEmpty() const override; /** * @copydoc KateUndo::undo() */ - void undo() Q_DECL_OVERRIDE; + void undo() override; /** * @copydoc KateUndo::redo() */ - void redo() Q_DECL_OVERRIDE; + void redo() override; /** * @copydoc KateUndo::mergeWith(const KateUndo) */ - bool mergeWith(const KateUndo *undo) Q_DECL_OVERRIDE; + bool mergeWith(const KateUndo *undo) override; /** * @copydoc KateUndo::type() */ - KateUndo::UndoType type() const Q_DECL_OVERRIDE + KateUndo::UndoType type() const override { return KateUndo::editInsertText; } protected: inline int len() const { return m_text.length(); } inline int line() const { return m_line; } private: const int m_line; const int m_col; QString m_text; }; class KateEditRemoveTextUndo : public KateUndo { public: KateEditRemoveTextUndo(KTextEditor::DocumentPrivate *document, int line, int col, const QString &text); /** * @copydoc KateUndo::isEmpty() */ - bool isEmpty() const Q_DECL_OVERRIDE; + bool isEmpty() const override; /** * @copydoc KateUndo::undo() */ - void undo() Q_DECL_OVERRIDE; + void undo() override; /** * @copydoc KateUndo::redo() */ - void redo() Q_DECL_OVERRIDE; + void redo() override; /** * @copydoc KateUndo::mergeWith(const KateUndo) */ - bool mergeWith(const KateUndo *undo) Q_DECL_OVERRIDE; + bool mergeWith(const KateUndo *undo) override; /** * @copydoc KateUndo::type() */ - KateUndo::UndoType type() const Q_DECL_OVERRIDE + KateUndo::UndoType type() const override { return KateUndo::editRemoveText; } protected: inline int len() const { return m_text.length(); } inline int line() const { return m_line; } private: const int m_line; int m_col; QString m_text; }; class KateEditMarkLineAutoWrappedUndo : public KateUndo { public: KateEditMarkLineAutoWrappedUndo(KTextEditor::DocumentPrivate *document, int line, bool autowrapped) : KateUndo(document) , m_line(line) , m_autowrapped(autowrapped) {} /** * @copydoc KateUndo::undo() */ - void undo() Q_DECL_OVERRIDE; + void undo() override; /** * @copydoc KateUndo::redo() */ - void redo() Q_DECL_OVERRIDE; + void redo() override; /** * @copydoc KateUndo::type() */ - KateUndo::UndoType type() const Q_DECL_OVERRIDE + KateUndo::UndoType type() const override { return KateUndo::editMarkLineAutoWrapped; } private: const int m_line; const bool m_autowrapped; }; class KateEditWrapLineUndo : public KateUndo { public: KateEditWrapLineUndo(KTextEditor::DocumentPrivate *document, int line, int col, int len, bool newLine); /** * @copydoc KateUndo::undo() */ - void undo() Q_DECL_OVERRIDE; + void undo() override; /** * @copydoc KateUndo::redo() */ - void redo() Q_DECL_OVERRIDE; + void redo() override; /** * @copydoc KateUndo::type() */ - KateUndo::UndoType type() const Q_DECL_OVERRIDE + KateUndo::UndoType type() const override { return KateUndo::editWrapLine; } protected: inline int line() const { return m_line; } private: const int m_line; const int m_col; const int m_len; const bool m_newLine; }; class KateEditUnWrapLineUndo : public KateUndo { public: KateEditUnWrapLineUndo(KTextEditor::DocumentPrivate *document, int line, int col, int len, bool removeLine); /** * @copydoc KateUndo::undo() */ - void undo() Q_DECL_OVERRIDE; + void undo() override; /** * @copydoc KateUndo::redo() */ - void redo() Q_DECL_OVERRIDE; + void redo() override; /** * @copydoc KateUndo::type() */ - KateUndo::UndoType type() const Q_DECL_OVERRIDE + KateUndo::UndoType type() const override { return KateUndo::editUnWrapLine; } protected: inline int line() const { return m_line; } private: const int m_line; const int m_col; const int m_len; const bool m_removeLine; }; class KateEditInsertLineUndo : public KateUndo { public: KateEditInsertLineUndo(KTextEditor::DocumentPrivate *document, int line, const QString &text); /** * @copydoc KateUndo::undo() */ - void undo() Q_DECL_OVERRIDE; + void undo() override; /** * @copydoc KateUndo::redo() */ - void redo() Q_DECL_OVERRIDE; + void redo() override; /** * @copydoc KateUndo::type() */ - KateUndo::UndoType type() const Q_DECL_OVERRIDE + KateUndo::UndoType type() const override { return KateUndo::editInsertLine; } protected: inline int line() const { return m_line; } private: const int m_line; const QString m_text; }; class KateEditRemoveLineUndo : public KateUndo { public: KateEditRemoveLineUndo(KTextEditor::DocumentPrivate *document, int line, const QString &text); /** * @copydoc KateUndo::undo() */ - void undo() Q_DECL_OVERRIDE; + void undo() override; /** * @copydoc KateUndo::redo() */ - void redo() Q_DECL_OVERRIDE; + void redo() override; /** * @copydoc KateUndo::type() */ - KateUndo::UndoType type() const Q_DECL_OVERRIDE + KateUndo::UndoType type() const override { return KateUndo::editRemoveLine; } protected: inline int line() const { return m_line; } private: const int m_line; const QString m_text; }; /** * Class to manage a group of undo items */ class KateUndoGroup { public: /** * Constructor * @param manager KateUndoManager this undo group will belong to */ explicit KateUndoGroup(KateUndoManager *manager, const QVector &cursorPosition, const QVector &selectionRange); /** * Destructor */ ~KateUndoGroup(); public: /** * Undo the contained undo items */ void undo(KTextEditor::View *view); /** * Redo the contained undo items */ void redo(KTextEditor::View *view); void editEnd(const QVector &cursorPosition, const QVector &selectionRange); /** * merge this group with an other * @param newGroup group to merge into this one * @param complex set if a complex undo * @return success */ bool merge(KateUndoGroup *newGroup, bool complex); /** * set group as as savepoint. the next group will not merge with this one */ void safePoint(bool safePoint = true); /** * is this undogroup empty? */ bool isEmpty() const { return m_items.isEmpty(); } /** * Change all LineSaved flags to LineModified of the line modification system. */ void flagSavedAsModified(); void markUndoAsSaved(QBitArray &lines); void markRedoAsSaved(QBitArray &lines); /** * Set the undo cursor to @p cursor. */ inline void setUndoCursor(const QVector &cursor) { m_undoCursor = cursor; } /** * Set the redo cursor to @p cursor. */ inline void setRedoCursor(const QVector &cursor) { m_redoCursor = cursor; } inline const QVector &redoCursor() const { return m_redoCursor; } private: KTextEditor::Document *document(); /** * singleType * @return the type if it's only one type, or editInvalid if it contains multiple types. */ KateUndo::UndoType singleType() const; /** * are we only of this type ? * @param type type to query * @return we contain only the given type */ bool isOnlyType(KateUndo::UndoType type) const; public: /** * add an undo item * @param u item to add */ void addItem(KateUndo *u); private: KateUndoManager *const m_manager; /** * list of items contained */ QList m_items; /** * prohibit merging with the next group */ bool m_safePoint; /** * the text selection of the active view before the edit step */ const QVector m_undoSelection; /** * the text selection of the active view after the edit step */ QVector m_redoSelection; /** * the cursor position of the active view before the edit step */ QVector m_undoCursor; /** * the cursor position of the active view after the edit step */ QVector m_redoCursor; }; #endif diff --git a/src/utils/codecompletioninterface.cpp b/src/utils/codecompletioninterface.cpp index 9836c036..a5e5a102 100644 --- a/src/utils/codecompletioninterface.cpp +++ b/src/utils/codecompletioninterface.cpp @@ -1,26 +1,26 @@ /* This file is part of the KDE libraries Copyright (C) 2005-2006 Hamish Rodda * * 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 "codecompletioninterface.h" using namespace KTextEditor; -CodeCompletionInterface::~ CodeCompletionInterface() +CodeCompletionInterface::~CodeCompletionInterface() { } diff --git a/src/utils/codecompletionmodel.cpp b/src/utils/codecompletionmodel.cpp index 81f068a7..59f324fd 100644 --- a/src/utils/codecompletionmodel.cpp +++ b/src/utils/codecompletionmodel.cpp @@ -1,119 +1,118 @@ /* This file is part of the KDE libraries Copyright (C) 2005-2006 Hamish Rodda * * 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 "codecompletionmodel.h" #include "document.h" #include "view.h" using namespace KTextEditor; class KTextEditor::CodeCompletionModelPrivate { public: CodeCompletionModelPrivate() - : rowCount(0), hasGroups(false) {} - int rowCount; - bool hasGroups; + int rowCount = 0; + bool hasGroups = false; }; CodeCompletionModel::CodeCompletionModel(QObject *parent) : QAbstractItemModel(parent) , d(new CodeCompletionModelPrivate) { } CodeCompletionModel::~ CodeCompletionModel() { delete d; } int CodeCompletionModel::columnCount(const QModelIndex &) const { return ColumnCount; } QModelIndex CodeCompletionModel::index(int row, int column, const QModelIndex &parent) const { if (row < 0 || row >= d->rowCount || column < 0 || column >= ColumnCount || parent.isValid()) { return QModelIndex(); } return createIndex(row, column, (void *)nullptr); } QMap< int, QVariant > CodeCompletionModel::itemData(const QModelIndex &index) const { QMap ret = QAbstractItemModel::itemData(index); for (int i = CompletionRole; i <= AccessibilityAccept; ++i) { QVariant v = data(index, i); if (v.isValid()) { ret.insert(i, v); } } return ret; } QModelIndex CodeCompletionModel::parent(const QModelIndex &) const { return QModelIndex(); } void CodeCompletionModel::setRowCount(int rowCount) { d->rowCount = rowCount; } int CodeCompletionModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return d->rowCount; } void CodeCompletionModel::completionInvoked(KTextEditor::View *view, const Range &range, InvocationType invocationType) { Q_UNUSED(view) Q_UNUSED(range) Q_UNUSED(invocationType) } void CodeCompletionModel::executeCompletionItem (KTextEditor::View *view, const Range &word, const QModelIndex &index) const { view->document()->replaceText(word, data(index.sibling(index.row(), Name)).toString()); } bool CodeCompletionModel::hasGroups() const { return d->hasGroups; } void CodeCompletionModel::setHasGroups(bool hasGroups) { if (d->hasGroups != hasGroups) { d->hasGroups = hasGroups; emit hasGroupsChanged(this, hasGroups); } } diff --git a/src/utils/configinterface.cpp b/src/utils/configinterface.cpp index 01c25814..b5b929d8 100644 --- a/src/utils/configinterface.cpp +++ b/src/utils/configinterface.cpp @@ -1,31 +1,30 @@ /* This file is part of the KDE project Copyright (C) 2006 Matt Broadstone (mbroadst@gmail.com) 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 using namespace KTextEditor; ConfigInterface::ConfigInterface() - : d(nullptr) { } ConfigInterface::~ConfigInterface() { } diff --git a/src/utils/kateautoindent.cpp b/src/utils/kateautoindent.cpp index 344f01d4..78a0209e 100644 --- a/src/utils/kateautoindent.cpp +++ b/src/utils/kateautoindent.cpp @@ -1,498 +1,499 @@ /* This file is part of the KDE libraries Copyright (C) 2003 Jesse Yurkovich Copyright (C) 2004 >Anders Lund (KateVarIndent class) Copyright (C) 2005 Dominik Haumann (basic support for config page) 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 "kateautoindent.h" #include "kateconfig.h" #include "katehighlight.h" #include "kateglobal.h" #include "kateindentscript.h" #include "katescriptmanager.h" #include "kateview.h" #include "kateextendedattribute.h" #include "katedocument.h" #include "katepartdebug.h" #include #include namespace { inline const QString MODE_NONE() { return QStringLiteral("none"); } inline const QString MODE_NORMAL() { return QStringLiteral("normal"); } } //BEGIN KateAutoIndent QStringList KateAutoIndent::listModes() { QStringList l; for (int i = 0; i < modeCount(); ++i) { l << modeDescription(i); } return l; } QStringList KateAutoIndent::listIdentifiers() { QStringList l; for (int i = 0; i < modeCount(); ++i) { l << modeName(i); } return l; } int KateAutoIndent::modeCount() { // inbuild modes + scripts return 2 + KTextEditor::EditorPrivate::self()->scriptManager()->indentationScriptCount(); } QString KateAutoIndent::modeName(int mode) { if (mode == 0 || mode >= modeCount()) { return MODE_NONE(); } if (mode == 1) { return MODE_NORMAL(); } return KTextEditor::EditorPrivate::self()->scriptManager()->indentationScriptByIndex(mode - 2)->indentHeader().baseName(); } QString KateAutoIndent::modeDescription(int mode) { if (mode == 0 || mode >= modeCount()) { return i18nc("Autoindent mode", "None"); } if (mode == 1) { return i18nc("Autoindent mode", "Normal"); } const QString &name = KTextEditor::EditorPrivate::self()->scriptManager()->indentationScriptByIndex(mode - 2)->indentHeader().name(); return i18nc("Autoindent mode", name.toUtf8().constData()); } QString KateAutoIndent::modeRequiredStyle(int mode) { if (mode == 0 || mode == 1 || mode >= modeCount()) { return QString(); } return KTextEditor::EditorPrivate::self()->scriptManager()->indentationScriptByIndex(mode - 2)->indentHeader().requiredStyle(); } uint KateAutoIndent::modeNumber(const QString &name) { for (int i = 0; i < modeCount(); ++i) if (modeName(i) == name) { return i; } return 0; } KateAutoIndent::KateAutoIndent(KTextEditor::DocumentPrivate *_doc) : QObject(_doc), doc(_doc), m_script(nullptr) { // don't call updateConfig() here, document might is not ready for that.... // on script reload, the script pointer is invalid -> force reload connect(KTextEditor::EditorPrivate::self()->scriptManager(), SIGNAL(reloaded()), this, SLOT(reloadScript())); } KateAutoIndent::~KateAutoIndent() { } QString KateAutoIndent::tabString(int length, int align) const { QString s; length = qMin(length, 256); // sanity check for large values of pos int spaces = qBound(0, align - length, 256); if (!useSpaces) { s.append(QString(length / tabWidth, QLatin1Char('\t'))); length = length % tabWidth; } s.append(QString(length + spaces, QLatin1Char(' '))); return s; } bool KateAutoIndent::doIndent(int line, int indentDepth, int align) { Kate::TextLine textline = doc->plainKateTextLine(line); // textline not found, cu if (!textline) { return false; } // sanity check if (indentDepth < 0) { indentDepth = 0; } const QString oldIndentation = textline->leadingWhitespace(); // Preserve existing "tabs then spaces" alignment if and only if: // - no alignment was passed to doIndent and // - we aren't using spaces for indentation and // - we aren't rounding indentation up to the next multiple of the indentation width and // - we aren't using a combination to tabs and spaces for alignment, or in other words // the indent width is a multiple of the tab width. bool preserveAlignment = !useSpaces && keepExtra && indentWidth % tabWidth == 0; if (align == 0 && preserveAlignment) { // Count the number of consecutive spaces at the end of the existing indentation int i = oldIndentation.size() - 1; while (i >= 0 && oldIndentation.at(i) == QLatin1Char(' ')) { --i; } // Use the passed indentDepth as the alignment, and set the indentDepth to // that value minus the number of spaces found (but don't let it get negative). align = indentDepth; indentDepth = qMax(0, align - (oldIndentation.size() - 1 - i)); } QString indentString = tabString(indentDepth, align); // Modify the document *ONLY* if smth has really changed! if (oldIndentation != indentString) { // remove leading whitespace, then insert the leading indentation doc->editStart(); doc->editRemoveText(line, 0, oldIndentation.length()); doc->editInsertText(line, 0, indentString); doc->editEnd(); } return true; } bool KateAutoIndent::doIndentRelative(int line, int change) { Kate::TextLine textline = doc->plainKateTextLine(line); // get indent width of current line int indentDepth = textline->indentDepth(tabWidth); int extraSpaces = indentDepth % indentWidth; // add change indentDepth += change; // if keepExtra is off, snap to a multiple of the indentWidth if (!keepExtra && extraSpaces > 0) { if (change < 0) { indentDepth += indentWidth - extraSpaces; } else { indentDepth -= extraSpaces; } } // do indent return doIndent(line, indentDepth); } void KateAutoIndent::keepIndent(int line) { // no line in front, no work... if (line <= 0) { return; } // keep indentation: find line with content int nonEmptyLine = line - 1; while (nonEmptyLine >= 0) { if (doc->lineLength(nonEmptyLine) > 0) { break; } --nonEmptyLine; } Kate::TextLine prevTextLine = doc->plainKateTextLine(nonEmptyLine); Kate::TextLine textLine = doc->plainKateTextLine(line); // textline not found, cu if (!prevTextLine || !textLine) { return; } const QString previousWhitespace = prevTextLine->leadingWhitespace(); // remove leading whitespace, then insert the leading indentation doc->editStart(); if (!keepExtra) { const QString currentWhitespace = textLine->leadingWhitespace(); doc->editRemoveText(line, 0, currentWhitespace.length()); } doc->editInsertText(line, 0, previousWhitespace); doc->editEnd(); } void KateAutoIndent::reloadScript() { // small trick to force reload m_script = nullptr; // prevent dangling pointer QString currentMode = m_mode; m_mode = QString(); setMode(currentMode); } void KateAutoIndent::scriptIndent(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &position, QChar typedChar) { // start edit doc->pushEditState(); doc->editStart(); QPair result = m_script->indent(view, position, typedChar, indentWidth); int newIndentInChars = result.first; // handle negative values special if (newIndentInChars < -1) { // do nothing atm } // reuse indentation of the previous line, just like the "normal" indenter else if (newIndentInChars == -1) { // keep indent of previous line keepIndent(position.line()); } // get align else { // we got a positive or zero indent to use... doIndent(position.line(), newIndentInChars, result.second); } // end edit in all cases doc->editEnd(); doc->popEditState(); } bool KateAutoIndent::isStyleProvided(const KateIndentScript *script, const KateHighlighting *highlight) { QString requiredStyle = script->indentHeader().requiredStyle(); return (requiredStyle.isEmpty() || requiredStyle == highlight->style()); } void KateAutoIndent::setMode(const QString &name) { // bail out, already set correct mode... if (m_mode == name) { return; } // cleanup m_script = nullptr; // first, catch easy stuff... normal mode and none, easy... if (name.isEmpty() || name == MODE_NONE()) { m_mode = MODE_NONE(); return; } if (name == MODE_NORMAL()) { m_mode = MODE_NORMAL(); return; } // handle script indenters, if any for this name... KateIndentScript *script = KTextEditor::EditorPrivate::self()->scriptManager()->indentationScript(name); if (script) { if (isStyleProvided(script, doc->highlight())) { m_script = script; m_mode = name; return; } else { qCWarning(LOG_KTE) << "mode" << name << "requires a different highlight style: highlighting '" << doc->highlight()->name() << "' (" << doc->highlight()->version() << "), style '" << doc->highlight()->style() << "'" ", but script require '" << script->indentHeader().requiredStyle() << "'" ; } } else { qCWarning(LOG_KTE) << "mode" << name << "does not exist"; } // Fall back to normal m_mode = MODE_NORMAL(); } void KateAutoIndent::checkRequiredStyle() { if (m_script) { if (!isStyleProvided(m_script, doc->highlight())) { qCDebug(LOG_KTE) << "mode" << m_mode << "requires a different highlight style: highlighting '" << doc->highlight()->name() << "' (" << doc->highlight()->version() << "), style '" << doc->highlight()->style() << "'" ", but script require '" << m_script->indentHeader().requiredStyle() << "'" ; doc->config()->setIndentationMode(MODE_NORMAL()); } } } void KateAutoIndent::updateConfig() { KateDocumentConfig *config = doc->config(); useSpaces = config->replaceTabsDyn(); keepExtra = config->keepExtraSpaces(); tabWidth = config->tabWidth(); indentWidth = config->indentationWidth(); } bool KateAutoIndent::changeIndent(const KTextEditor::Range &range, int change) { QList skippedLines; // loop over all lines given... for (int line = range.start().line() < 0 ? 0 : range.start().line(); line <= qMin(range.end().line(), doc->lines() - 1); ++line) { // don't indent empty lines if (doc->line(line).isEmpty()) { skippedLines.append(line); continue; } // don't indent the last line when the cursor is on the first column if (line == range.end().line() && range.end().column() == 0) { skippedLines.append(line); continue; } doIndentRelative(line, change * indentWidth); } if (skippedLines.count() > range.numberOfLines()) { // all lines were empty, so indent them nevertheless foreach (int line, skippedLines) { doIndentRelative(line, change * indentWidth); } } return true; } void KateAutoIndent::indent(KTextEditor::ViewPrivate *view, const KTextEditor::Range &range) { // no script, do nothing... if (!m_script) { return; } // we want one undo action >= START doc->setUndoMergeAllEdits(true); // loop over all lines given... for (int line = range.start().line() < 0 ? 0 : range.start().line(); line <= qMin(range.end().line(), doc->lines() - 1); ++line) { // let the script indent for us... scriptIndent(view, KTextEditor::Cursor(line, 0), QChar()); } // we want one undo action => END doc->setUndoMergeAllEdits(false); } void KateAutoIndent::userTypedChar(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &position, QChar typedChar) { // normal mode if (m_mode == MODE_NORMAL()) { // only indent on new line, per default if (typedChar != QLatin1Char('\n')) { return; } // keep indent of previous line keepIndent(position.line()); return; } // no script, do nothing... if (!m_script) { return; } // does the script allow this char as trigger? if (typedChar != QLatin1Char('\n') && !m_script->triggerCharacters().contains(typedChar)) { return; } // let the script indent for us... scriptIndent(view, position, typedChar); } //END KateAutoIndent //BEGIN KateViewIndentAction KateViewIndentationAction::KateViewIndentationAction(KTextEditor::DocumentPrivate *_doc, const QString &text, QObject *parent) : KActionMenu(text, parent), doc(_doc) { + setDelayed(false); connect(menu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShow())); actionGroup = new QActionGroup(menu()); } void KateViewIndentationAction::slotAboutToShow() { QStringList modes = KateAutoIndent::listModes(); menu()->clear(); foreach (QAction *action, actionGroup->actions()) { actionGroup->removeAction(action); } for (int z = 0; z < modes.size(); ++z) { QAction *action = menu()->addAction(QLatin1Char('&') + KateAutoIndent::modeDescription(z).replace(QLatin1Char('&'), QLatin1String("&&"))); actionGroup->addAction(action); action->setCheckable(true); action->setData(z); QString requiredStyle = KateAutoIndent::modeRequiredStyle(z); action->setEnabled(requiredStyle.isEmpty() || requiredStyle == doc->highlight()->style()); if (doc->config()->indentationMode() == KateAutoIndent::modeName(z)) { action->setChecked(true); } } disconnect(menu(), SIGNAL(triggered(QAction*)), this, SLOT(setMode(QAction*))); connect(menu(), SIGNAL(triggered(QAction*)), this, SLOT(setMode(QAction*))); } void KateViewIndentationAction::setMode(QAction *action) { // set new mode doc->config()->setIndentationMode(KateAutoIndent::modeName(action->data().toInt())); doc->rememberUserDidSetIndentationMode(); } //END KateViewIndentationAction diff --git a/src/utils/katebookmarks.cpp b/src/utils/katebookmarks.cpp index 47e1ace5..a900fcbd 100644 --- a/src/utils/katebookmarks.cpp +++ b/src/utils/katebookmarks.cpp @@ -1,299 +1,301 @@ /* This file is part of the KDE libraries Copyright (C) 2002, 2003, 2004 Anders Lund Copyright (C) 2002 John Firebaugh 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 "katebookmarks.h" #include "katedocument.h" #include "kateview.h" #include "kateabstractinputmode.h" #include #include #include #include #include #include #include #include #include #include #include namespace KTextEditor { class Document; } KateBookmarks::KateBookmarks(KTextEditor::ViewPrivate *view, Sorting sort) : QObject(view) , m_view(view) , m_bookmarkClear(nullptr) , m_sorting(sort) { setObjectName(QStringLiteral("kate bookmarks")); connect(view->doc(), SIGNAL(marksChanged(KTextEditor::Document*)), this, SLOT(marksChanged())); _tries = 0; m_bookmarksMenu = nullptr; } KateBookmarks::~KateBookmarks() { } void KateBookmarks::createActions(KActionCollection *ac) { m_bookmarkToggle = new KToggleAction(i18n("Set &Bookmark"), this); ac->addAction(QStringLiteral("bookmarks_toggle"), m_bookmarkToggle); m_bookmarkToggle->setIcon(QIcon::fromTheme(QStringLiteral("bookmark-new"))); ac->setDefaultShortcut(m_bookmarkToggle, Qt::CTRL + Qt::Key_B); m_bookmarkToggle->setWhatsThis(i18n("If a line has no bookmark then add one, otherwise remove it.")); connect(m_bookmarkToggle, SIGNAL(triggered()), this, SLOT(toggleBookmark())); m_bookmarkClear = new QAction(i18n("Clear &All Bookmarks"), this); ac->addAction(QStringLiteral("bookmarks_clear"), m_bookmarkClear); + m_bookmarkClear->setIcon(QIcon::fromTheme(QStringLiteral("bookmark-remove"))); m_bookmarkClear->setWhatsThis(i18n("Remove all bookmarks of the current document.")); connect(m_bookmarkClear, SIGNAL(triggered()), this, SLOT(clearBookmarks())); m_goNext = new QAction(i18n("Next Bookmark"), this); ac->addAction(QStringLiteral("bookmarks_next"), m_goNext); m_goNext->setIcon(QIcon::fromTheme(QStringLiteral("go-down-search"))); ac->setDefaultShortcut(m_goNext, Qt::ALT + Qt::Key_PageDown); m_goNext->setWhatsThis(i18n("Go to the next bookmark.")); connect(m_goNext, SIGNAL(triggered()), this, SLOT(goNext())); m_goPrevious = new QAction(i18n("Previous Bookmark"), this); ac->addAction(QStringLiteral("bookmarks_previous"), m_goPrevious); m_goPrevious->setIcon(QIcon::fromTheme(QStringLiteral("go-up-search"))); ac->setDefaultShortcut(m_goPrevious, Qt::ALT + Qt::Key_PageUp); m_goPrevious->setWhatsThis(i18n("Go to the previous bookmark.")); connect(m_goPrevious, SIGNAL(triggered()), this, SLOT(goPrevious())); KActionMenu *actionMenu = new KActionMenu(i18n("&Bookmarks"), this); ac->addAction(QStringLiteral("bookmarks"), actionMenu); + actionMenu->setDelayed(false); m_bookmarksMenu = actionMenu->menu(); connect(m_bookmarksMenu, SIGNAL(aboutToShow()), this, SLOT(bookmarkMenuAboutToShow())); marksChanged(); // Always want the actions with shortcuts plugged into something so their shortcuts can work m_view->addAction(m_bookmarkToggle); m_view->addAction(m_bookmarkClear); m_view->addAction(m_goNext); m_view->addAction(m_goPrevious); } void KateBookmarks::toggleBookmark() { uint mark = m_view->doc()->mark(m_view->cursorPosition().line()); if (mark & KTextEditor::MarkInterface::markType01) m_view->doc()->removeMark(m_view->cursorPosition().line(), KTextEditor::MarkInterface::markType01); else m_view->doc()->addMark(m_view->cursorPosition().line(), KTextEditor::MarkInterface::markType01); } void KateBookmarks::clearBookmarks() { QHash m = m_view->doc()->marks(); for (QHash::const_iterator i = m.constBegin(); i != m.constEnd(); ++i) { m_view->doc()->removeMark(i.value()->line, KTextEditor::MarkInterface::markType01); } // just to be sure ;) // dominik: the following line can be deleted afaics, as Document::removeMark emits this signal. marksChanged(); } void KateBookmarks::insertBookmarks(QMenu &menu) { int line = m_view->cursorPosition().line(); const QRegExp re(QLatin1String("&(?!&)")); int next = -1; // -1 means next bookmark doesn't exist int prev = -1; // -1 means previous bookmark doesn't exist const QHash &m = m_view->doc()->marks(); QVector bookmarkLineArray; // Array of line numbers which have bookmarks if (m.isEmpty()) { return; } // Find line numbers where bookmarks are set & store those line numbers in bookmarkLineArray for (QHash::const_iterator it = m.constBegin(); it != m.constEnd(); ++it) { if (it.value()->type & KTextEditor::MarkInterface::markType01) { bookmarkLineArray.append(it.value()->line); } } if (m_sorting == Position) { qSort(bookmarkLineArray.begin(), bookmarkLineArray.end()); } QAction *firstNewAction = menu.addSeparator(); // Consider each line with a bookmark one at a time for (int i = 0; i < bookmarkLineArray.size(); ++i) { // Get text in this particular line in a QString QString bText = menu.fontMetrics().elidedText (m_view->doc()->line(bookmarkLineArray.at(i)), Qt::ElideRight, menu.fontMetrics().maxWidth() * 32); bText.replace(re, QStringLiteral("&&")); // kill undesired accellerators! bText.replace(QLatin1Char('\t'), QLatin1Char(' ')); // kill tabs, as they are interpreted as shortcuts QAction *before = nullptr; if (m_sorting == Position) { // 3 actions already present if (menu.actions().size() <= i + 3) { before = nullptr; } else { before = menu.actions().at(i + 3); } } // Adding action for this bookmark in menu if (before) { QAction *a = new QAction(QStringLiteral("%1 %3 - \"%2\"") .arg(bookmarkLineArray.at(i) + 1).arg(bText) .arg(m_view->currentInputMode()->bookmarkLabel(bookmarkLineArray.at(i))), &menu); menu.insertAction(before, a); connect(a, SIGNAL(activated()), this, SLOT(gotoLine())); a->setData(bookmarkLineArray.at(i)); if (!firstNewAction) { firstNewAction = a; } } else { QAction *a = menu.addAction(QStringLiteral("%1 %3 - \"%2\"") .arg(bookmarkLineArray.at(i) + 1).arg(bText) .arg(m_view->currentInputMode()->bookmarkLabel(bookmarkLineArray.at(i))), this, SLOT(gotoLine())); a->setData(bookmarkLineArray.at(i)); } // Find the line number of previous & next bookmark (if present) in relation to the cursor if (bookmarkLineArray.at(i) < line) { if ((prev == -1) || prev < (bookmarkLineArray.at(i))) { prev = bookmarkLineArray.at(i); } } else if (bookmarkLineArray.at(i) > line) { if ((next == -1) || next > (bookmarkLineArray.at(i))) { next = bookmarkLineArray.at(i); } } } if (next != -1) { // Insert action for next bookmark m_goNext->setText(i18n("&Next: %1 - \"%2\"", next + 1, KStringHandler::rsqueeze(m_view->doc()->line(next), 24))); menu.insertAction(firstNewAction, m_goNext); firstNewAction = m_goNext; } if (prev != -1) { // Insert action for previous bookmark m_goPrevious->setText(i18n("&Previous: %1 - \"%2\"", prev + 1, KStringHandler::rsqueeze(m_view->doc()->line(prev), 24))); menu.insertAction(firstNewAction, m_goPrevious); firstNewAction = m_goPrevious; } if (next != -1 || prev != -1) { menu.insertSeparator(firstNewAction); } } void KateBookmarks::gotoLine() { if (!sender()) { return; } gotoLine(((QAction *)(sender()))->data().toInt()); } void KateBookmarks::gotoLine(int line) { m_view->setCursorPosition(KTextEditor::Cursor(line, 0)); } void KateBookmarks::bookmarkMenuAboutToShow() { m_bookmarksMenu->clear(); m_bookmarkToggle->setChecked(m_view->doc()->mark(m_view->cursorPosition().line()) & KTextEditor::MarkInterface::markType01); m_bookmarksMenu->addAction(m_bookmarkToggle); m_bookmarksMenu->addAction(m_bookmarkClear); m_goNext->setText(i18n("Next Bookmark")); m_goPrevious->setText(i18n("Previous Bookmark")); insertBookmarks(*m_bookmarksMenu); } void KateBookmarks::goNext() { const QHash &m = m_view->doc()->marks(); if (m.isEmpty()) { return; } int line = m_view->cursorPosition().line(); int found = -1; for (QHash::const_iterator it = m.constBegin(); it != m.constEnd(); ++it) { if ((it.value()->line > line) && ((found == -1) || (found > it.value()->line))) { found = it.value()->line; } } if (found != -1) { gotoLine(found); } } void KateBookmarks::goPrevious() { const QHash &m = m_view->doc()->marks(); if (m.isEmpty()) { return; } int line = m_view->cursorPosition().line(); int found = -1; for (QHash::const_iterator it = m.constBegin(); it != m.constEnd(); ++it) { if ((it.value()->line < line) && ((found == -1) || (found < it.value()->line))) { found = it.value()->line; } } if (found != -1) { gotoLine(found); } } void KateBookmarks::marksChanged() { if (m_bookmarkClear) { m_bookmarkClear->setEnabled(!m_view->doc()->marks().isEmpty()); } } diff --git a/src/utils/katecmd.h b/src/utils/katecmd.h index 09c0691b..f51e002d 100644 --- a/src/utils/katecmd.h +++ b/src/utils/katecmd.h @@ -1,109 +1,109 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2001-2010 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_CMD_H #define _KATE_CMD_H #include #include #include #include #include class KTEXTEDITOR_EXPORT KateCmd { public: KateCmd(); ~KateCmd(); static KateCmd *self(); bool registerCommand(KTextEditor::Command *cmd); bool unregisterCommand(KTextEditor::Command *cmd); KTextEditor::Command *queryCommand(const QString &cmd) const; QList commands() const; QStringList commandList() const; QStringList cmds(); void appendHistory(const QString &cmd); const QString fromHistory(int i) const; uint historyLength() const { return m_history.count(); } KCompletion *commandCompletionObject(); private: QHash m_dict; QStringList m_cmds; QStringList m_history; KCompletion m_cmdCompletion; // shared completion object for all KateCmdLineEdits in each KTE::View }; /** * A KCompletion object that completes last ?unquoted? word in the string * passed. Do not mistake "shell" for anything related to quoting, this * simply mimics shell tab completion by completing the last word in the * provided text. */ class KateCmdShellCompletion : public KCompletion { public: KateCmdShellCompletion(); /** * Finds completions to the given text. * The first match is returned and emitted in the signal match(). * @param text the text to complete * @return the first match, or QString() if not found */ - QString makeCompletion(const QString &text) Q_DECL_OVERRIDE; + QString makeCompletion(const QString &text) override; protected: // Called by KCompletion - void postProcessMatch(QString *match) const Q_DECL_OVERRIDE; - void postProcessMatches(QStringList *matches) const Q_DECL_OVERRIDE; - void postProcessMatches(KCompletionMatches *matches) const Q_DECL_OVERRIDE; + void postProcessMatch(QString *match) const override; + void postProcessMatches(QStringList *matches) const override; + void postProcessMatches(KCompletionMatches *matches) const override; private: /** * Split text at the last unquoted space * * @param text_start will be set to the text at the left, including the space * @param text_compl Will be set to the text at the right. This is the text to complete. */ void splitText(const QString &text, QString &text_start, QString &text_compl) const; QChar m_word_break_char; QChar m_quote_char1; QChar m_quote_char2; QChar m_escape_char; QString m_text_start; QString m_text_compl; }; #endif diff --git a/src/utils/katecmds.h b/src/utils/katecmds.h index b7000bc2..27ce31aa 100644 --- a/src/utils/katecmds.h +++ b/src/utils/katecmds.h @@ -1,212 +1,212 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2003-2005 Anders Lund * Copyright (C) 2001-2010 Christoph Cullmann * Copyright (C) 2001 Charles Samuels * * 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_CMDS_H__ #define __KATE_CMDS_H__ #include #include class KCompletion; /** * The KateCommands namespace collects subclasses of KTextEditor::Command * for specific use in kate. */ namespace KateCommands { /** * This KTextEditor::Command provides access to a lot of the core functionality * of kate part, settings, utilities, navigation etc. * it needs to get a kateview pointer, it will cast the kate::view pointer * hard to kateview */ class CoreCommands : public KTextEditor::Command { CoreCommands() : KTextEditor::Command({ QStringLiteral("indent") , QStringLiteral("unindent") , QStringLiteral("cleanindent") , QStringLiteral("fold") , QStringLiteral("tfold") , QStringLiteral("unfold") , QStringLiteral("comment") , QStringLiteral("uncomment") , QStringLiteral("goto") , QStringLiteral("kill-line") , QStringLiteral("set-tab-width") , QStringLiteral("set-replace-tabs") , QStringLiteral("set-show-tabs") , QStringLiteral("set-indent-width") , QStringLiteral("set-indent-mode") , QStringLiteral("set-auto-indent") , QStringLiteral("set-line-numbers") , QStringLiteral("set-folding-markers") , QStringLiteral("set-icon-border") , QStringLiteral("set-indent-pasted-text") , QStringLiteral("set-word-wrap") , QStringLiteral("set-word-wrap-column") , QStringLiteral("set-replace-tabs-save") , QStringLiteral("set-remove-trailing-spaces") , QStringLiteral("set-highlight") , QStringLiteral("set-mode") , QStringLiteral("set-show-indent") , QStringLiteral("print") }) { } static CoreCommands *m_instance; public: - ~CoreCommands() + ~CoreCommands() override { m_instance = nullptr; } /** * execute command * @param view view to use for execution * @param cmd cmd string * @param errorMsg error to return if no success * @return success */ bool exec(class KTextEditor::View *view, const QString &cmd, QString &errorMsg); /** * execute command on given range * @param view view to use for execution * @param cmd cmd string * @param errorMsg error to return if no success * @param range range to execute command on * @return success */ bool exec(class KTextEditor::View *view, const QString &cmd, QString &errorMsg, - const KTextEditor::Range &range = KTextEditor::Range(-1, -0, -1, 0)) Q_DECL_OVERRIDE; + const KTextEditor::Range &range = KTextEditor::Range(-1, -0, -1, 0)) override; - bool supportsRange(const QString &range) Q_DECL_OVERRIDE; + bool supportsRange(const QString &range) override; /** This command does not have help. @see KTextEditor::Command::help */ - bool help(class KTextEditor::View *, const QString &, QString &) Q_DECL_OVERRIDE; + bool help(class KTextEditor::View *, const QString &, QString &) override; /** override from KTextEditor::Command */ - KCompletion *completionObject(KTextEditor::View *, const QString &) Q_DECL_OVERRIDE; + KCompletion *completionObject(KTextEditor::View *, const QString &) override; static CoreCommands *self() { if (m_instance == nullptr) { m_instance = new CoreCommands(); } return m_instance; } }; /** * insert a unicode or ascii character * base 9+1: 1234 * hex: 0x1234 or x1234 * octal: 01231 * * prefixed with "char:" **/ class Character : public KTextEditor::Command { Character() : KTextEditor::Command({ QStringLiteral("char") }) { } static Character *m_instance; public: - ~Character() + ~Character() override { m_instance = nullptr; } /** * execute command * @param view view to use for execution * @param cmd cmd string * @param errorMsg error to return if no success * @return success */ bool exec(class KTextEditor::View *view, const QString &cmd, QString &errorMsg, - const KTextEditor::Range &range = KTextEditor::Range(-1, -0, -1, 0)) Q_DECL_OVERRIDE; + const KTextEditor::Range &range = KTextEditor::Range(-1, -0, -1, 0)) override; /** This command does not have help. @see KTextEditor::Command::help */ - bool help(class KTextEditor::View *, const QString &, QString &) Q_DECL_OVERRIDE; + bool help(class KTextEditor::View *, const QString &, QString &) override; static Character *self() { if (m_instance == nullptr) { m_instance = new Character(); } return m_instance; } }; /** * insert the current date/time in the given format */ class Date : public KTextEditor::Command { Date() : KTextEditor::Command({ QStringLiteral("date") }) { } static Date *m_instance; public: - ~Date() + ~Date() override { m_instance = nullptr; } /** * execute command * @param view view to use for execution * @param cmd cmd string * @param errorMsg error to return if no success * @return success */ bool exec(class KTextEditor::View *view, const QString &cmd, QString &errorMsg, - const KTextEditor::Range &range = KTextEditor::Range(-1, -0, -1, 0)) Q_DECL_OVERRIDE; + const KTextEditor::Range &range = KTextEditor::Range(-1, -0, -1, 0)) override; /** This command does not have help. @see KTextEditor::Command::help */ - bool help(class KTextEditor::View *, const QString &, QString &) Q_DECL_OVERRIDE; + bool help(class KTextEditor::View *, const QString &, QString &) override; static Date *self() { if (m_instance == nullptr) { m_instance = new Date(); } return m_instance; } }; } // namespace KateCommands #endif diff --git a/src/utils/kateconfig.cpp b/src/utils/kateconfig.cpp index f488f8d1..37fe8a8a 100644 --- a/src/utils/kateconfig.cpp +++ b/src/utils/kateconfig.cpp @@ -1,3106 +1,3134 @@ /* 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_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) + m_lineLengthLimitSet(false) + { 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_markerSize(1), 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_markerSize(1), 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_showWordCountSet(false), + m_showLinesCountSet(false), m_autoBracketsSet(false), - m_backspaceRemoveComposedSet(false), - m_view(nullptr) + m_backspaceRemoveComposedSet(false) + { 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_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_LINES_COUNT[] = "Show Lines Count"; const char KEY_SHOW_WORD_COUNT[] = "Show Word Count"; const char KEY_AUTO_BRACKETS[] = "Auto Brackets"; const char KEY_BACKSPACE_REMOVE_COMPOSED[] = "Backspace Remove Composed Characters"; } void KateViewConfig::readConfig(const KConfigGroup &config) { configStart(); // 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)); + setShowLinesCount(config.readEntry(KEY_SHOW_LINES_COUNT, false)); setShowWordCount(config.readEntry(KEY_SHOW_WORD_COUNT, false)); setAutoBrackets(config.readEntry(KEY_AUTO_BRACKETS, false)); setBackspaceRemoveComposed(config.readEntry(KEY_BACKSPACE_REMOVE_COMPOSED, false)); configEnd(); } void KateViewConfig::writeConfig(KConfigGroup &config) { 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_LINES_COUNT, showLinesCount()); 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() +bool KateViewConfig::showWordCount() const { - return m_showWordCount; + if (m_showWordCountSet || isGlobal()) { + return m_showWordCount; + } + + return s_global->showWordCount(); } void KateViewConfig::setShowWordCount(bool on) { - if (m_showWordCount == on) { + if (m_showWordCountSet && m_showWordCount == on) { return; } configStart(); + m_showWordCountSet = true; m_showWordCount = on; configEnd(); } +bool KateViewConfig::showLinesCount() const +{ + if (m_showLinesCountSet || isGlobal()) { + return m_showLinesCount; + } + + return s_global->showLinesCount(); +} + +void KateViewConfig::setShowLinesCount(bool on) +{ + if (m_showLinesCountSet && m_showLinesCount == on) { + return; + } + + configStart(); + m_showLinesCountSet = true; + m_showLinesCount = 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) + m_lineMarkerColorSet(m_lineMarkerColor.size()) + { // init bitarray m_lineMarkerColorSet.fill(true); s_global = this; // init with defaults from config or really hardcoded ones KConfigGroup config(KTextEditor::EditorPrivate::config(), "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; + setFontWithDroppedStyleName(config.readEntry("Font", QFontDatabase::systemFont(QFontDatabase::FixedFont))); m_templateBackgroundColor = config.readEntry(QStringLiteral("Color Template Background"), colors.color(Kate::TemplateBackground)); m_templateFocusedEditablePlaceholderColor = config.readEntry(QStringLiteral("Color Template Focused Editable Placeholder"), colors.color(Kate::TemplateFocusedEditablePlaceholder)); m_templateEditablePlaceholderColor = config.readEntry(QStringLiteral("Color Template Editable Placeholder"), colors.color(Kate::TemplateEditablePlaceholder)); m_templateNotEditablePlaceholderColor = config.readEntry(QStringLiteral("Color Template Not Editable Placeholder"), colors.color(Kate::TemplateNotEditablePlaceholder)); m_templateColorsSet = true; } const QFont &KateRendererConfig::font() const { if (m_fontSet || isGlobal()) { return m_font; } return s_global->font(); } const QFontMetricsF &KateRendererConfig::fontMetrics() const { if (m_fontSet || isGlobal()) { return m_fontMetrics; } return s_global->fontMetrics(); } void KateRendererConfig::setFont(const QFont &font) { if (m_fontSet && m_font == font) { return; } configStart(); + setFontWithDroppedStyleName(font); + configEnd(); +} - m_fontSet = true; +void KateRendererConfig::setFontWithDroppedStyleName(const QFont &font) +{ + /** + * Drop styleName, otherwise stuff like bold/italic/... won't work as style! + */ m_font = font; + m_font.setStyleName(QString()); m_fontMetrics = QFontMetricsF(m_font); - - configEnd(); + m_fontSet = true; } bool KateRendererConfig::wordWrapMarker() const { if (m_wordWrapMarkerSet || isGlobal()) { return m_wordWrapMarker; } return s_global->wordWrapMarker(); } void KateRendererConfig::setWordWrapMarker(bool on) { if (m_wordWrapMarkerSet && m_wordWrapMarker == on) { return; } configStart(); m_wordWrapMarkerSet = true; m_wordWrapMarker = on; configEnd(); } const QColor &KateRendererConfig::backgroundColor() const { if (m_backgroundColorSet || isGlobal()) { return m_backgroundColor; } return s_global->backgroundColor(); } void KateRendererConfig::setBackgroundColor(const QColor &col) { if (m_backgroundColorSet && m_backgroundColor == col) { return; } configStart(); m_backgroundColorSet = true; m_backgroundColor = col; configEnd(); } const QColor &KateRendererConfig::selectionColor() const { if (m_selectionColorSet || isGlobal()) { return m_selectionColor; } return s_global->selectionColor(); } void KateRendererConfig::setSelectionColor(const QColor &col) { if (m_selectionColorSet && m_selectionColor == col) { return; } configStart(); m_selectionColorSet = true; m_selectionColor = col; configEnd(); } const QColor &KateRendererConfig::highlightedLineColor() const { if (m_highlightedLineColorSet || isGlobal()) { return m_highlightedLineColor; } return s_global->highlightedLineColor(); } void KateRendererConfig::setHighlightedLineColor(const QColor &col) { if (m_highlightedLineColorSet && m_highlightedLineColor == col) { return; } configStart(); m_highlightedLineColorSet = true; m_highlightedLineColor = col; configEnd(); } const QColor &KateRendererConfig::lineMarkerColor(KTextEditor::MarkInterface::MarkTypes type) const { int index = 0; if (type > 0) { while ((type >> index++) ^ 1) {} } index -= 1; if (index < 0 || index >= KTextEditor::MarkInterface::reservedMarkersCount()) { static QColor dummy; return dummy; } if (m_lineMarkerColorSet[index] || isGlobal()) { return m_lineMarkerColor[index]; } return s_global->lineMarkerColor(type); } void KateRendererConfig::setLineMarkerColor(const QColor &col, KTextEditor::MarkInterface::MarkTypes type) { int index = static_cast(log(static_cast(type)) / log(2.0)); Q_ASSERT(index >= 0 && index < KTextEditor::MarkInterface::reservedMarkersCount()); if (m_lineMarkerColorSet[index] && m_lineMarkerColor[index] == col) { return; } configStart(); m_lineMarkerColorSet[index] = true; m_lineMarkerColor[index] = col; configEnd(); } const QColor &KateRendererConfig::highlightedBracketColor() const { if (m_highlightedBracketColorSet || isGlobal()) { return m_highlightedBracketColor; } return s_global->highlightedBracketColor(); } void KateRendererConfig::setHighlightedBracketColor(const QColor &col) { if (m_highlightedBracketColorSet && m_highlightedBracketColor == col) { return; } configStart(); m_highlightedBracketColorSet = true; m_highlightedBracketColor = col; configEnd(); } const QColor &KateRendererConfig::wordWrapMarkerColor() const { if (m_wordWrapMarkerColorSet || isGlobal()) { return m_wordWrapMarkerColor; } return s_global->wordWrapMarkerColor(); } void KateRendererConfig::setWordWrapMarkerColor(const QColor &col) { if (m_wordWrapMarkerColorSet && m_wordWrapMarkerColor == col) { return; } configStart(); m_wordWrapMarkerColorSet = true; m_wordWrapMarkerColor = col; configEnd(); } const QColor &KateRendererConfig::tabMarkerColor() const { if (m_tabMarkerColorSet || isGlobal()) { return m_tabMarkerColor; } return s_global->tabMarkerColor(); } void KateRendererConfig::setTabMarkerColor(const QColor &col) { if (m_tabMarkerColorSet && m_tabMarkerColor == col) { return; } configStart(); m_tabMarkerColorSet = true; m_tabMarkerColor = col; configEnd(); } const QColor &KateRendererConfig::indentationLineColor() const { if (m_indentationLineColorSet || isGlobal()) { return m_indentationLineColor; } return s_global->indentationLineColor(); } void KateRendererConfig::setIndentationLineColor(const QColor &col) { if (m_indentationLineColorSet && m_indentationLineColor == col) { return; } configStart(); m_indentationLineColorSet = true; m_indentationLineColor = col; configEnd(); } const QColor &KateRendererConfig::iconBarColor() const { if (m_iconBarColorSet || isGlobal()) { return m_iconBarColor; } return s_global->iconBarColor(); } void KateRendererConfig::setIconBarColor(const QColor &col) { if (m_iconBarColorSet && m_iconBarColor == col) { return; } configStart(); m_iconBarColorSet = true; m_iconBarColor = col; configEnd(); } const QColor &KateRendererConfig::foldingColor() const { if (m_foldingColorSet || isGlobal()) { return m_foldingColor; } return s_global->foldingColor(); } void KateRendererConfig::setFoldingColor(const QColor &col) { if (m_foldingColorSet && m_foldingColor == col) { return; } configStart(); m_foldingColorSet = true; m_foldingColor = col; configEnd(); } const QColor &KateRendererConfig::templateBackgroundColor() const { if (m_templateColorsSet || isGlobal()) { return m_templateBackgroundColor; } return s_global->templateBackgroundColor(); } const QColor &KateRendererConfig::templateEditablePlaceholderColor() const { if (m_templateColorsSet || isGlobal()) { return m_templateEditablePlaceholderColor; } return s_global->templateEditablePlaceholderColor(); } const QColor &KateRendererConfig::templateFocusedEditablePlaceholderColor() const { if (m_templateColorsSet || isGlobal()) { return m_templateFocusedEditablePlaceholderColor; } return s_global->templateFocusedEditablePlaceholderColor(); } const QColor &KateRendererConfig::templateNotEditablePlaceholderColor() const { if (m_templateColorsSet || isGlobal()) { return m_templateNotEditablePlaceholderColor; } return s_global->templateNotEditablePlaceholderColor(); } const QColor &KateRendererConfig::lineNumberColor() const { if (m_lineNumberColorSet || isGlobal()) { return m_lineNumberColor; } return s_global->lineNumberColor(); } void KateRendererConfig::setLineNumberColor(const QColor &col) { if (m_lineNumberColorSet && m_lineNumberColor == col) { return; } configStart(); m_lineNumberColorSet = true; m_lineNumberColor = col; configEnd(); } const QColor &KateRendererConfig::currentLineNumberColor() const { if (m_currentLineNumberColorSet || isGlobal()) { return m_currentLineNumberColor; } return s_global->currentLineNumberColor(); } void KateRendererConfig::setCurrentLineNumberColor(const QColor &col) { if (m_currentLineNumberColorSet && m_currentLineNumberColor == col) { return; } configStart(); m_currentLineNumberColorSet = true; m_currentLineNumberColor = col; configEnd(); } const QColor &KateRendererConfig::separatorColor() const { if (m_separatorColorSet || isGlobal()) { return m_separatorColor; } return s_global->separatorColor(); } void KateRendererConfig::setSeparatorColor(const QColor &col) { if (m_separatorColorSet && m_separatorColor == col) { return; } configStart(); m_separatorColorSet = true; m_separatorColor = col; configEnd(); } const QColor &KateRendererConfig::spellingMistakeLineColor() const { if (m_spellingMistakeLineColorSet || isGlobal()) { return m_spellingMistakeLineColor; } return s_global->spellingMistakeLineColor(); } void KateRendererConfig::setSpellingMistakeLineColor(const QColor &col) { if (m_spellingMistakeLineColorSet && m_spellingMistakeLineColor == col) { return; } configStart(); m_spellingMistakeLineColorSet = true; m_spellingMistakeLineColor = col; configEnd(); } const QColor &KateRendererConfig::modifiedLineColor() const { if (m_modifiedLineColorSet || isGlobal()) { return m_modifiedLineColor; } return s_global->modifiedLineColor(); } void KateRendererConfig::setModifiedLineColor(const QColor &col) { if (m_modifiedLineColorSet && m_modifiedLineColor == col) { return; } configStart(); m_modifiedLineColorSet = true; m_modifiedLineColor = col; configEnd(); } const QColor &KateRendererConfig::savedLineColor() const { if (m_savedLineColorSet || isGlobal()) { return m_savedLineColor; } return s_global->savedLineColor(); } void KateRendererConfig::setSavedLineColor(const QColor &col) { if (m_savedLineColorSet && m_savedLineColor == col) { return; } configStart(); m_savedLineColorSet = true; m_savedLineColor = col; configEnd(); } const QColor &KateRendererConfig::searchHighlightColor() const { if (m_searchHighlightColorSet || isGlobal()) { return m_searchHighlightColor; } return s_global->searchHighlightColor(); } void KateRendererConfig::setSearchHighlightColor(const QColor &col) { if (m_searchHighlightColorSet && m_searchHighlightColor == col) { return; } configStart(); m_searchHighlightColorSet = true; m_searchHighlightColor = col; configEnd(); } const QColor &KateRendererConfig::replaceHighlightColor() const { if (m_replaceHighlightColorSet || isGlobal()) { return m_replaceHighlightColor; } return s_global->replaceHighlightColor(); } void KateRendererConfig::setReplaceHighlightColor(const QColor &col) { if (m_replaceHighlightColorSet && m_replaceHighlightColor == col) { return; } configStart(); m_replaceHighlightColorSet = true; m_replaceHighlightColor = col; configEnd(); } bool KateRendererConfig::showIndentationLines() const { if (m_showIndentationLinesSet || isGlobal()) { return m_showIndentationLines; } return s_global->showIndentationLines(); } void KateRendererConfig::setShowIndentationLines(bool on) { if (m_showIndentationLinesSet && m_showIndentationLines == on) { return; } configStart(); m_showIndentationLinesSet = true; m_showIndentationLines = on; configEnd(); } bool KateRendererConfig::showWholeBracketExpression() const { if (m_showWholeBracketExpressionSet || isGlobal()) { return m_showWholeBracketExpression; } return s_global->showWholeBracketExpression(); } void KateRendererConfig::setShowWholeBracketExpression(bool on) { if (m_showWholeBracketExpressionSet && m_showWholeBracketExpression == on) { return; } configStart(); m_showWholeBracketExpressionSet = true; m_showWholeBracketExpression = on; configEnd(); } bool KateRendererConfig::animateBracketMatching() const { return s_global->m_animateBracketMatching; } void KateRendererConfig::setAnimateBracketMatching(bool on) { if (!isGlobal()) { s_global->setAnimateBracketMatching(on); } else if (on != m_animateBracketMatching) { configStart(); m_animateBracketMatching = on; configEnd(); } } //END diff --git a/src/utils/kateconfig.h b/src/utils/kateconfig.h index 23645018..fc6c525c 100644 --- a/src/utils/kateconfig.h +++ b/src/utils/kateconfig.h @@ -1,858 +1,870 @@ /* 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; + uint configSessionNumber = 0; /** * is a config session running */ - bool configIsRunning; + bool configIsRunning = false; }; class KTEXTEDITOR_EXPORT KateGlobalConfig : public KateConfig { private: friend class KTextEditor::EditorPrivate; /** * only used in KTextEditor::EditorPrivate for the static global fallback !!! */ KateGlobalConfig(); /** * Destructor */ - ~KateGlobalConfig(); + ~KateGlobalConfig() override; 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; + void updateConfig() 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(); + ~KateDocumentConfig() override; 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; + void updateConfig() 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; + int m_indentationWidth = 2; + int m_tabWidth = 4; + uint m_tabHandling = tabSmart; + uint m_configFlags = 0; + int m_wordWrapAt = 80; 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; + uint m_markerSize = 1; 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; + KTextEditor::DocumentPrivate *m_doc = nullptr; }; class KTEXTEDITOR_EXPORT KateViewConfig : public KateConfig { private: friend class KTextEditor::EditorPrivate; /** * only used in KTextEditor::EditorPrivate for the static global fallback !!! */ KateViewConfig(); public: /** * Construct a DocumentConfig */ explicit KateViewConfig(KTextEditor::ViewPrivate *view); /** * Cu DocumentConfig */ - ~KateViewConfig(); + ~KateViewConfig() override; 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; + void updateConfig() 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(); + bool showWordCount() const; void setShowWordCount(bool on); + bool showLinesCount() const; + void setShowLinesCount(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_showWordCount = false; + bool m_showLinesCount = false; 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_showWordCountSet : 1; + bool m_showLinesCountSet : 1; bool m_autoBracketsSet : 1; bool m_backspaceRemoveComposedSet : 1; private: static KateViewConfig *s_global; - KTextEditor::ViewPrivate *m_view; + KTextEditor::ViewPrivate *m_view = nullptr; }; class KTEXTEDITOR_EXPORT KateRendererConfig : public KateConfig { private: friend class KTextEditor::EditorPrivate; /** * only used in KTextEditor::EditorPrivate for the static global fallback !!! */ KateRendererConfig(); public: /** * Construct a DocumentConfig */ KateRendererConfig(KateRenderer *renderer); /** * Cu DocumentConfig */ - ~KateRendererConfig(); + ~KateRendererConfig() override; 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; + void updateConfig() override; public: const QString &schema() const; void setSchema(const QString &schema); /** * Reload the schema from the schema manager. * For the global instance, have all other instances reload. * Used by the schema config page to apply changes. */ void reloadSchema(); const QFont &font() const; const QFontMetricsF &fontMetrics() const; void setFont(const QFont &font); bool wordWrapMarker() const; void setWordWrapMarker(bool on); const QColor &backgroundColor() const; void setBackgroundColor(const QColor &col); const QColor &selectionColor() const; void setSelectionColor(const QColor &col); const QColor &highlightedLineColor() const; void setHighlightedLineColor(const QColor &col); const QColor &lineMarkerColor(KTextEditor::MarkInterface::MarkTypes type = KTextEditor::MarkInterface::markType01) const; // markType01 == Bookmark void setLineMarkerColor(const QColor &col, KTextEditor::MarkInterface::MarkTypes type = KTextEditor::MarkInterface::markType01); const QColor &highlightedBracketColor() const; void setHighlightedBracketColor(const QColor &col); const QColor &wordWrapMarkerColor() const; void setWordWrapMarkerColor(const QColor &col); const QColor &tabMarkerColor() const; void setTabMarkerColor(const QColor &col); const QColor &indentationLineColor() const; void setIndentationLineColor(const QColor &col); const QColor &iconBarColor() const; void setIconBarColor(const QColor &col); const QColor &foldingColor() const; void setFoldingColor(const QColor &col); // the line number color is used for the line numbers on the left bar const QColor &lineNumberColor() const; void setLineNumberColor(const QColor &col); const QColor ¤tLineNumberColor() const; void setCurrentLineNumberColor(const QColor &col); // the color of the separator between line numbers and icon bar const QColor &separatorColor() const; void setSeparatorColor(const QColor &col); const QColor &spellingMistakeLineColor() const; void setSpellingMistakeLineColor(const QColor &col); bool showIndentationLines() const; void setShowIndentationLines(bool on); bool showWholeBracketExpression() const; void setShowWholeBracketExpression(bool on); bool animateBracketMatching() const; void setAnimateBracketMatching(bool on); const QColor &templateBackgroundColor() const; const QColor &templateEditablePlaceholderColor() const; const QColor &templateFocusedEditablePlaceholderColor() const; const QColor &templateNotEditablePlaceholderColor() const; const QColor &modifiedLineColor() const; void setModifiedLineColor(const QColor &col); const QColor &savedLineColor() const; void setSavedLineColor(const QColor &col); const QColor &searchHighlightColor() const; void setSearchHighlightColor(const QColor &col); const QColor &replaceHighlightColor() const; void setReplaceHighlightColor(const QColor &col); private: /** * Read the schema properties from the config file. */ void setSchemaInternal(const QString &schema); + /** + * Set the font but drop style name before that. + * Otherwise e.g. styles like bold/italic/... will not work + */ + void setFontWithDroppedStyleName(const QFont &font); + QString m_schema; QFont m_font; QFontMetricsF m_fontMetrics; QColor m_backgroundColor; QColor m_selectionColor; QColor m_highlightedLineColor; QColor m_highlightedBracketColor; QColor m_wordWrapMarkerColor; QColor m_tabMarkerColor; QColor m_indentationLineColor; QColor m_iconBarColor; QColor m_foldingColor; QColor m_lineNumberColor; QColor m_currentLineNumberColor; QColor m_separatorColor; QColor m_spellingMistakeLineColor; QVector m_lineMarkerColor; QColor m_templateBackgroundColor; QColor m_templateEditablePlaceholderColor; QColor m_templateFocusedEditablePlaceholderColor; QColor m_templateNotEditablePlaceholderColor; QColor m_modifiedLineColor; QColor m_savedLineColor; QColor m_searchHighlightColor; QColor m_replaceHighlightColor; - bool m_wordWrapMarker; - bool m_showIndentationLines; - bool m_showWholeBracketExpression; - bool m_animateBracketMatching; + bool m_wordWrapMarker = false; + bool m_showIndentationLines = false; + bool m_showWholeBracketExpression = false; + bool m_animateBracketMatching = false; bool m_schemaSet : 1; bool m_fontSet : 1; bool m_wordWrapMarkerSet : 1; bool m_showIndentationLinesSet : 1; bool m_showWholeBracketExpressionSet : 1; bool m_backgroundColorSet : 1; bool m_selectionColorSet : 1; bool m_highlightedLineColorSet : 1; bool m_highlightedBracketColorSet : 1; bool m_wordWrapMarkerColorSet : 1; bool m_tabMarkerColorSet : 1; bool m_indentationLineColorSet : 1; bool m_iconBarColorSet : 1; bool m_foldingColorSet : 1; bool m_lineNumberColorSet : 1; bool m_currentLineNumberColorSet : 1; bool m_separatorColorSet : 1; bool m_spellingMistakeLineColorSet : 1; bool m_templateColorsSet : 1; bool m_modifiedLineColorSet : 1; bool m_savedLineColorSet : 1; bool m_searchHighlightColorSet : 1; bool m_replaceHighlightColorSet : 1; QBitArray m_lineMarkerColorSet; private: static KateRendererConfig *s_global; - KateRenderer *m_renderer; + KateRenderer *m_renderer = nullptr; }; #endif diff --git a/src/utils/kateglobal.cpp b/src/utils/kateglobal.cpp index d1ed531d..8be64f65 100644 --- a/src/utils/kateglobal.cpp +++ b/src/utils/kateglobal.cpp @@ -1,527 +1,533 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2001-2010 Christoph Cullmann * Copyright (C) 2009 Erlend Hamberg * * 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 "kateglobal.h" #include "config.h" #include #include "katedocument.h" #include "kateview.h" #include "katerenderer.h" #include "katecmd.h" #include "katecmds.h" #include "katesedcmd.h" #include "katehighlightingcmds.h" #include "katemodemanager.h" #include "kateschema.h" #include "kateschemaconfig.h" #include "kateconfig.h" #include "katescriptmanager.h" #include "katebuffer.h" #include "katewordcompletion.h" #include "katekeywordcompletion.h" #include "spellcheck/spellcheck.h" #include "katepartdebug.h" #include "katedefaultcolors.h" #include "katenormalinputmodefactory.h" #include "kateviinputmodefactory.h" #include #include #include #include #include #include #include #include #include #include #include #if LIBGIT2_FOUND #include #endif // logging category for this framework, default: log stuff >= warning Q_LOGGING_CATEGORY(LOG_KTE, "org.kde.ktexteditor", QtWarningMsg) //BEGIN unit test mode static bool kateUnitTestMode = false; void KTextEditor::EditorPrivate::enableUnitTestMode() { kateUnitTestMode = true; } bool KTextEditor::EditorPrivate::unitTestMode() { return kateUnitTestMode; } //END unit test mode KTextEditor::EditorPrivate::EditorPrivate(QPointer &staticInstance) : KTextEditor::Editor (this) , m_aboutData(QStringLiteral("katepart"), i18n("Kate Part"), QStringLiteral(KTEXTEDITOR_VERSION_STRING), i18n("Embeddable editor component"), KAboutLicense::LGPL_V2, i18n("(c) 2000-2017 The Kate Authors"), QString(), QStringLiteral("http://kate-editor.org")) , m_dummyApplication(nullptr) , m_application(&m_dummyApplication) , m_dummyMainWindow(nullptr) , m_defaultColors(new KateDefaultColors()) , m_searchHistoryModel(nullptr) , m_replaceHistoryModel(nullptr) { // remember this staticInstance = this; #if QT_VERSION < QT_VERSION_CHECK(5, 9, 1) // disable the QML JIT compiler as a protection against an unknown bug // in Qt's V4 engine which can provoke a crash in certain of our scripts. // See https://bugreports.qt.io/browse/QTBUG-63045 // https://bugs.kde.org/show_bug.cgi?id=384404 // and https://bugs.kde.org/show_bug.cgi?id=385413 qputenv("QV4_FORCE_INTERPRETER", QByteArrayLiteral("1")); qCDebug(LOG_KTE) << "QV4_FORCE_INTERPRETER set to 1"; #endif // init libgit2, we require at least 0.22 which has this function! #if LIBGIT2_FOUND git_libgit2_init(); #endif /** * register some datatypes */ qRegisterMetaType("KTextEditor::Cursor"); qRegisterMetaType("KTextEditor::Document*"); qRegisterMetaType("KTextEditor::View*"); // // fill about data // m_aboutData.addAuthor(i18n("Christoph Cullmann"), i18n("Maintainer"), QStringLiteral("cullmann@kde.org"), QStringLiteral("http://www.cullmann.io")); m_aboutData.addAuthor(i18n("Dominik Haumann"), i18n("Core Developer"), QStringLiteral("dhaumann@kde.org")); m_aboutData.addAuthor(i18n("Milian Wolff"), i18n("Core Developer"), QStringLiteral("mail@milianw.de"), QStringLiteral("http://milianw.de")); m_aboutData.addAuthor(i18n("Joseph Wenninger"), i18n("Core Developer"), QStringLiteral("jowenn@kde.org"), QStringLiteral("http://stud3.tuwien.ac.at/~e9925371")); m_aboutData.addAuthor(i18n("Erlend Hamberg"), i18n("Vi Input Mode"), QStringLiteral("ehamberg@gmail.com"), QStringLiteral("http://hamberg.no/erlend")); m_aboutData.addAuthor(i18n("Bernhard Beschow"), i18n("Developer"), QStringLiteral("bbeschow@cs.tu-berlin.de"), QStringLiteral("https://user.cs.tu-berlin.de/~bbeschow")); m_aboutData.addAuthor(i18n("Anders Lund"), i18n("Core Developer"), QStringLiteral("anders@alweb.dk"), QStringLiteral("http://www.alweb.dk")); m_aboutData.addAuthor(i18n("Michel Ludwig"), i18n("On-the-fly spell checking"), QStringLiteral("michel.ludwig@kdemail.net")); m_aboutData.addAuthor(i18n("Pascal Létourneau"), i18n("Large scale bug fixing"), QStringLiteral("pascal.letourneau@gmail.com")); m_aboutData.addAuthor(i18n("Hamish Rodda"), i18n("Core Developer"), QStringLiteral("rodda@kde.org")); m_aboutData.addAuthor(i18n("Waldo Bastian"), i18n("The cool buffersystem"), QStringLiteral("bastian@kde.org")); m_aboutData.addAuthor(i18n("Charles Samuels"), i18n("The Editing Commands"), QStringLiteral("charles@kde.org")); m_aboutData.addAuthor(i18n("Matt Newell"), i18n("Testing, ..."), QStringLiteral("newellm@proaxis.com")); m_aboutData.addAuthor(i18n("Michael Bartl"), i18n("Former Core Developer"), QStringLiteral("michael.bartl1@chello.at")); m_aboutData.addAuthor(i18n("Michael McCallum"), i18n("Core Developer"), QStringLiteral("gholam@xtra.co.nz")); m_aboutData.addAuthor(i18n("Michael Koch"), i18n("KWrite port to KParts"), QStringLiteral("koch@kde.org")); m_aboutData.addAuthor(i18n("Christian Gebauer"), QString(), QStringLiteral("gebauer@kde.org")); m_aboutData.addAuthor(i18n("Simon Hausmann"), QString(), QStringLiteral("hausmann@kde.org")); m_aboutData.addAuthor(i18n("Glen Parker"), i18n("KWrite Undo History, Kspell integration"), QStringLiteral("glenebob@nwlink.com")); m_aboutData.addAuthor(i18n("Scott Manson"), i18n("KWrite XML Syntax highlighting support"), QStringLiteral("sdmanson@alltel.net")); m_aboutData.addAuthor(i18n("John Firebaugh"), i18n("Patches and more"), QStringLiteral("jfirebaugh@kde.org")); m_aboutData.addAuthor(i18n("Andreas Kling"), i18n("Developer"), QStringLiteral("kling@impul.se")); m_aboutData.addAuthor(i18n("Mirko Stocker"), i18n("Various bugfixes"), QStringLiteral("me@misto.ch"), QStringLiteral("http://misto.ch/")); m_aboutData.addAuthor(i18n("Matthew Woehlke"), i18n("Selection, KColorScheme integration"), QStringLiteral("mw_triad@users.sourceforge.net")); m_aboutData.addAuthor(i18n("Sebastian Pipping"), i18n("Search bar back- and front-end"), QStringLiteral("webmaster@hartwork.org"), QStringLiteral("http://www.hartwork.org/")); m_aboutData.addAuthor(i18n("Jochen Wilhelmy"), i18n("Original KWrite Author"), QStringLiteral("digisnap@cs.tu-berlin.de")); m_aboutData.addAuthor(i18n("Gerald Senarclens de Grancy"), i18n("QA and Scripting"), QStringLiteral("oss@senarclens.eu"), QStringLiteral("http://find-santa.eu/")); m_aboutData.addCredit(i18n("Matteo Merli"), i18n("Highlighting for RPM Spec-Files, Perl, Diff and more"), QStringLiteral("merlim@libero.it")); m_aboutData.addCredit(i18n("Rocky Scaletta"), i18n("Highlighting for VHDL"), QStringLiteral("rocky@purdue.edu")); m_aboutData.addCredit(i18n("Yury Lebedev"), i18n("Highlighting for SQL"), QString()); m_aboutData.addCredit(i18n("Chris Ross"), i18n("Highlighting for Ferite"), QString()); m_aboutData.addCredit(i18n("Nick Roux"), i18n("Highlighting for ILERPG"), QString()); m_aboutData.addCredit(i18n("Carsten Niehaus"), i18n("Highlighting for LaTeX"), QString()); m_aboutData.addCredit(i18n("Per Wigren"), i18n("Highlighting for Makefiles, Python"), QString()); m_aboutData.addCredit(i18n("Jan Fritz"), i18n("Highlighting for Python"), QString()); m_aboutData.addCredit(i18n("Daniel Naber")); m_aboutData.addCredit(i18n("Roland Pabel"), i18n("Highlighting for Scheme"), QString()); m_aboutData.addCredit(i18n("Cristi Dumitrescu"), i18n("PHP Keyword/Datatype list"), QString()); m_aboutData.addCredit(i18n("Carsten Pfeiffer"), i18n("Very nice help"), QString()); m_aboutData.addCredit(i18n("Bruno Massa"), i18n("Highlighting for Lua"), QStringLiteral("brmassa@gmail.com")); m_aboutData.addCredit(i18n("All people who have contributed and I have forgotten to mention")); m_aboutData.setTranslator(i18nc("NAME OF TRANSLATORS", "Your names"), i18nc("EMAIL OF TRANSLATORS", "Your emails")); /** * set the new Kate mascot */ m_aboutData.setProgramLogo (QImage(QStringLiteral(":/ktexteditor/mascot.png"))); // // dir watch // m_dirWatch = new KDirWatch(); // // command manager // m_cmdManager = new KateCmd(); // // hl manager // m_hlManager = new KateHlManager(); // // mode man // m_modeManager = new KateModeManager(); // // schema man // m_schemaManager = new KateSchemaManager(); // // input mode factories // KateAbstractInputModeFactory *fact; fact = new KateNormalInputModeFactory(); m_inputModeFactories.insert(KTextEditor::View::NormalInputMode, fact); #if BUILD_VIMODE fact = new KateViInputModeFactory(); m_inputModeFactories.insert(KTextEditor::View::ViInputMode, fact); #endif // // spell check manager // m_spellCheckManager = new KateSpellCheckManager(); // config objects m_globalConfig = new KateGlobalConfig(); m_documentConfig = new KateDocumentConfig(); m_viewConfig = new KateViewConfig(); m_rendererConfig = new KateRendererConfig(); // create script manager (search scripts) m_scriptManager = KateScriptManager::self(); // // init the cmds // m_cmds.push_back(KateCommands::CoreCommands::self()); m_cmds.push_back(KateCommands::Character::self()); m_cmds.push_back(KateCommands::Date::self()); m_cmds.push_back(KateCommands::SedReplace::self()); m_cmds.push_back(KateCommands::Highlighting::self()); // global word completion model m_wordCompletionModel = new KateWordCompletionModel(this); // global keyword completion model m_keywordCompletionModel = new KateKeywordCompletionModel (this); // tap to QApplication object for color palette changes qApp->installEventFilter(this); } KTextEditor::EditorPrivate::~EditorPrivate() { delete m_globalConfig; delete m_documentConfig; delete m_viewConfig; delete m_rendererConfig; delete m_modeManager; delete m_schemaManager; delete m_dirWatch; // cu managers delete m_scriptManager; delete m_hlManager; delete m_spellCheckManager; // cu model delete m_wordCompletionModel; // delete the commands before we delete the cmd manager qDeleteAll(m_cmds); delete m_cmdManager; qDeleteAll(m_inputModeFactories); // shutdown libgit2, we require at least 0.22 which has this function! #if LIBGIT2_FOUND git_libgit2_shutdown(); #endif } KTextEditor::Document *KTextEditor::EditorPrivate::createDocument(QObject *parent) { KTextEditor::DocumentPrivate *doc = new KTextEditor::DocumentPrivate(false, false, nullptr, parent); emit documentCreated(this, doc); return doc; } //END KTextEditor::Editor config stuff void KTextEditor::EditorPrivate::configDialog(QWidget *parent) { QPointer kd = new KPageDialog(parent); kd->setWindowTitle(i18n("Configure")); kd->setFaceType(KPageDialog::List); kd->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Apply | QDialogButtonBox::Help); QList editorPages; for (int i = 0; i < configPages(); ++i) { QFrame *page = new QFrame(); KTextEditor::ConfigPage *cp = configPage(i, page); KPageWidgetItem *item = kd->addPage(page, cp->name()); item->setHeader(cp->fullName()); item->setIcon(cp->icon()); QVBoxLayout *topLayout = new QVBoxLayout(page); topLayout->setMargin(0); connect(kd->button(QDialogButtonBox::Apply), SIGNAL(clicked()), cp, SLOT(apply())); topLayout->addWidget(cp); editorPages.append(cp); } if (kd->exec() && kd) { KateGlobalConfig::global()->configStart(); KateDocumentConfig::global()->configStart(); KateViewConfig::global()->configStart(); KateRendererConfig::global()->configStart(); for (int i = 0; i < editorPages.count(); ++i) { editorPages.at(i)->apply(); } KateGlobalConfig::global()->configEnd(); KateDocumentConfig::global()->configEnd(); KateViewConfig::global()->configEnd(); KateRendererConfig::global()->configEnd(); } delete kd; } int KTextEditor::EditorPrivate::configPages() const { return 4; } KTextEditor::ConfigPage *KTextEditor::EditorPrivate::configPage(int number, QWidget *parent) { switch (number) { case 0: return new KateViewDefaultsConfig(parent); case 1: return new KateSchemaConfigPage(parent); case 2: return new KateEditConfigTab(parent); case 3: return new KateSaveConfigTab(parent); default: break; } return nullptr; } /** * Cleanup the KTextEditor::EditorPrivate during QCoreApplication shutdown */ static void cleanupGlobal() { /** * delete if there */ delete KTextEditor::EditorPrivate::self(); } KTextEditor::EditorPrivate *KTextEditor::EditorPrivate::self() { /** * remember the static instance in a QPointer */ static bool inited = false; static QPointer staticInstance; /** * just return it, if already inited */ if (inited) { return staticInstance.data(); } /** * start init process */ inited = true; /** * now create the object and store it */ new KTextEditor::EditorPrivate(staticInstance); /** * register cleanup * let use be deleted during QCoreApplication shutdown */ qAddPostRoutine(cleanupGlobal); /** * return instance */ return staticInstance.data(); } void KTextEditor::EditorPrivate::registerDocument(KTextEditor::DocumentPrivate *doc) { Q_ASSERT (!m_documents.contains(doc)); m_documents.insert(doc, doc); } void KTextEditor::EditorPrivate::deregisterDocument(KTextEditor::DocumentPrivate *doc) { Q_ASSERT (m_documents.contains(doc)); m_documents.remove(doc); } void KTextEditor::EditorPrivate::registerView(KTextEditor::ViewPrivate *view) { Q_ASSERT (!m_views.contains(view)); m_views.insert(view); } void KTextEditor::EditorPrivate::deregisterView(KTextEditor::ViewPrivate *view) { Q_ASSERT (m_views.contains(view)); m_views.remove(view); } KTextEditor::Command *KTextEditor::EditorPrivate::queryCommand(const QString &cmd) const { return m_cmdManager->queryCommand(cmd); } QList KTextEditor::EditorPrivate::commands() const { return m_cmdManager->commands(); } QStringList KTextEditor::EditorPrivate::commandList() const { return m_cmdManager->commandList(); } void KTextEditor::EditorPrivate::updateColorPalette() { // update default color cache m_defaultColors.reset(new KateDefaultColors()); // reload the global schema (triggers reload for every view as well) m_rendererConfig->reloadSchema(); // force full update of all view caches and colors m_rendererConfig->updateConfig(); } void KTextEditor::EditorPrivate::updateClipboardHistory(const QVector &text) { /** * empty => nop */ if (text.isEmpty()) { return; } /** * remember in history + */ + QApplication::clipboard()->setText(text.first(), QClipboard::Clipboard); + + /** + * LRU, kill potential duplicated, move new entry to top * cut after 10 entries */ + m_clipboardHistory.removeOne(text); m_clipboardHistory.prepend(text); if (m_clipboardHistory.size() > 10) { m_clipboardHistory.removeLast(); } /** * notify about change */ emit clipboardHistoryChanged(); } bool KTextEditor::EditorPrivate::eventFilter(QObject *obj, QEvent *event) { if (obj == qApp && event->type() == QEvent::ApplicationPaletteChange) { // only update the color once for the event that belongs to the qApp updateColorPalette(); } return false; // always continue processing } QList< KateAbstractInputModeFactory *> KTextEditor::EditorPrivate::inputModeFactories() { return m_inputModeFactories.values(); } QStringListModel *KTextEditor::EditorPrivate::searchHistoryModel() { if (!m_searchHistoryModel) { KConfigGroup cg(KSharedConfig::openConfig(), "KTextEditor::Search"); const QStringList history = cg.readEntry(QStringLiteral("Search History"), QStringList()); m_searchHistoryModel = new QStringListModel(history, this); } return m_searchHistoryModel; } QStringListModel *KTextEditor::EditorPrivate::replaceHistoryModel() { if (!m_replaceHistoryModel) { KConfigGroup cg(KSharedConfig::openConfig(), "KTextEditor::Search"); const QStringList history = cg.readEntry(QStringLiteral("Replace History"), QStringList()); m_replaceHistoryModel = new QStringListModel(history, this); } return m_replaceHistoryModel; } void KTextEditor::EditorPrivate::saveSearchReplaceHistoryModels() { KConfigGroup cg(KSharedConfig::openConfig(), "KTextEditor::Search"); if (m_searchHistoryModel) { cg.writeEntry(QStringLiteral("Search History"), m_searchHistoryModel->stringList()); } if (m_replaceHistoryModel) { cg.writeEntry(QStringLiteral("Replace History"), m_replaceHistoryModel->stringList()); } } diff --git a/src/utils/kateglobal.h b/src/utils/kateglobal.h index c8224575..4f1416a6 100644 --- a/src/utils/kateglobal.h +++ b/src/utils/kateglobal.h @@ -1,568 +1,568 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2001-2010 Christoph Cullmann * Copyright (C) 2009 Erlend Hamberg * * 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_GLOBAL_H__ #define __KATE_GLOBAL_H__ #include #include "katescript.h" #include #include "ktexteditor/view.h" #include #include #include #include #include #include #include #include #include class QStringListModel; class KateCmd; class KateModeManager; class KateSchemaManager; class KateGlobalConfig; class KateDocumentConfig; class KateViewConfig; class KateRendererConfig; namespace KTextEditor { class DocumentPrivate; } namespace KTextEditor { class ViewPrivate; } class KateScriptManager; class KDirWatch; class KateHlManager; class KateSpellCheckManager; class KateWordCompletionModel; class KateAbstractInputModeFactory; class KateKeywordCompletionModel; class KateDefaultColors; namespace KTextEditor { /** * KTextEditor::EditorPrivate * One instance of this class is hold alive during * a kate part session, as long as any factory, document * or view stay around, here is the place to put things * which are needed and shared by all this objects ;) */ class KTEXTEDITOR_EXPORT EditorPrivate : public KTextEditor::Editor { Q_OBJECT friend class KTextEditor::Editor; // unit testing support public: /** * Calling this function internally sets a flag such that unitTestMode() * returns \p true. */ static void enableUnitTestMode(); /** * Returns \p true, if the unit test mode was enabled through a call of * enableUnitTestMode(), otherwise \p false. */ static bool unitTestMode(); // for setDefaultEncoding friend class KateDocumentConfig; private: /** * Default constructor, private, as singleton * @param staticInstance pointer to fill with content of this */ EditorPrivate(QPointer &staticInstance); public: /** * Destructor */ ~EditorPrivate(); /** * Create a new document object * @param parent parent object * @return created KTextEditor::Document */ - KTextEditor::Document *createDocument(QObject *parent) Q_DECL_OVERRIDE; + KTextEditor::Document *createDocument(QObject *parent) override; /** * Returns a list of all documents of this editor. * @return list of all existing documents */ - QList documents() Q_DECL_OVERRIDE + QList documents() override { return m_documents.keys(); } /** * Set the global application object. * This will allow the editor component to access * the hosting application. * @param application application object */ - void setApplication(KTextEditor::Application *application) Q_DECL_OVERRIDE + void setApplication(KTextEditor::Application *application) override { // switch back to dummy application? m_application = application ? application : &m_dummyApplication; } /** * Current hosting application, if any set. * @return current application object or nullptr */ - KTextEditor::Application *application() const Q_DECL_OVERRIDE + KTextEditor::Application *application() const override { return m_application; } /** * General Information about this editor */ public: /** * return the about data * @return about data of this editor part */ - const KAboutData &aboutData() const Q_DECL_OVERRIDE + const KAboutData &aboutData() const override { return m_aboutData; } /** * Configuration management */ public: /** * Shows a config dialog for the part, changes will be applied * to the editor, but not saved anywhere automagically, call * writeConfig to save them */ - void configDialog(QWidget *parent) Q_DECL_OVERRIDE; + void configDialog(QWidget *parent) override; /** * Number of available config pages * If the editor returns a number < 1, it doesn't support this * and the embedding app should use the configDialog () instead * @return number of config pages */ - int configPages() const Q_DECL_OVERRIDE; + int configPages() const override; /** * returns config page with the given number, * config pages from 0 to configPages()-1 are available * if configPages() > 0 */ - KTextEditor::ConfigPage *configPage(int number, QWidget *parent) Q_DECL_OVERRIDE; + KTextEditor::ConfigPage *configPage(int number, QWidget *parent) override; /** * Kate Part Internal stuff ;) */ public: /** * singleton accessor * @return instance of the factory */ static KTextEditor::EditorPrivate *self(); /** * register document at the factory * this allows us to loop over all docs for example on config changes * @param doc document to register */ void registerDocument(KTextEditor::DocumentPrivate *doc); /** * unregister document at the factory * @param doc document to register */ void deregisterDocument(KTextEditor::DocumentPrivate *doc); /** * register view at the factory * this allows us to loop over all views for example on config changes * @param view view to register */ void registerView(KTextEditor::ViewPrivate *view); /** * unregister view at the factory * @param view view to unregister */ void deregisterView(KTextEditor::ViewPrivate *view); /** * return a list of all registered views * @return all known views */ QList views() { return m_views.toList(); } /** * global dirwatch * @return dirwatch instance */ KDirWatch *dirWatch() { return m_dirWatch; } /** * The global configuration of katepart, e.g. katepartrc * @return global shared access to katepartrc config */ static KSharedConfigPtr config() { // use dummy config for unit tests! return KTextEditor::EditorPrivate::unitTestMode() ? KSharedConfig::openConfig(QStringLiteral("katepartrc-unittest"), KConfig::SimpleConfig, QStandardPaths::TempLocation) : KSharedConfig::openConfig(QStringLiteral("katepartrc")); } /** * global mode manager * used to manage the modes centrally * @return mode manager */ KateModeManager *modeManager() { return m_modeManager; } /** * manager for the katepart schemas * @return schema manager */ KateSchemaManager *schemaManager() { return m_schemaManager; } /** * fallback document config * @return default config for all documents */ KateDocumentConfig *documentConfig() { return m_documentConfig; } /** * fallback view config * @return default config for all views */ KateViewConfig *viewConfig() { return m_viewConfig; } /** * fallback renderer config * @return default config for all renderers */ KateRendererConfig *rendererConfig() { return m_rendererConfig; } /** * Global script collection */ KateScriptManager *scriptManager() { return m_scriptManager; } /** * hl manager * @return hl manager */ KateHlManager *hlManager() { return m_hlManager; } /** * command manager * @return command manager */ KateCmd *cmdManager() { return m_cmdManager; } /** * spell check manager * @return spell check manager */ KateSpellCheckManager *spellCheckManager() { return m_spellCheckManager; } /** * global instance of the simple word completion mode * @return global instance of the simple word completion mode */ KateWordCompletionModel *wordCompletionModel() { return m_wordCompletionModel; } /** * Global instance of the language-aware keyword completion model * @return global instance of the keyword completion model */ KateKeywordCompletionModel *keywordCompletionModel () { return m_keywordCompletionModel; } /** * query for command * @param cmd name of command to query for * @return found command or 0 */ - KTextEditor::Command *queryCommand(const QString &cmd) const Q_DECL_OVERRIDE; + KTextEditor::Command *queryCommand(const QString &cmd) const override; /** * Get a list of all registered commands. * \return list of all commands */ - QList commands() const Q_DECL_OVERRIDE; + QList commands() const override; /** * Get a list of available commandline strings. * \return commandline strings */ - QStringList commandList() const Q_DECL_OVERRIDE; + QStringList commandList() const override; /** * Remember text in the clipboard history * @param text text to remember, does nothing if empty! */ void updateClipboardHistory(const QVector &text); /** * Clipboard history, filled with text we ever copied * to clipboard via updateClipboardHistory. */ const QVector> &clipboardHistory() const { return m_clipboardHistory; } /** * return a list of all registered docs * @return all known documents */ QList kateDocuments() { return m_documents.values(); } /** * Dummy main window to be null safe. * @return dummy main window */ KTextEditor::MainWindow *dummyMainWindow() { return &m_dummyMainWindow; } /** * @return list of available input mode factories */ QList inputModeFactories(); /** * Default colors, once constructed, as expensive * @return default colors */ const KateDefaultColors &defaultColors() const { return *m_defaultColors; } /** * Search pattern history shared among simple/power search instances. */ QStringListModel *searchHistoryModel(); /** * Replace pattern history shared among simple/power search instances. */ QStringListModel *replaceHistoryModel(); /** * Call this function to store the history models to the application config. */ void saveSearchReplaceHistoryModels(); Q_SIGNALS: /** * Emitted if the history of clipboard changes via copyToClipboard */ void clipboardHistoryChanged(); protected: - bool eventFilter(QObject *, QEvent *) Q_DECL_OVERRIDE; + bool eventFilter(QObject *, QEvent *) override; private Q_SLOTS: void updateColorPalette(); private: /** * about data (authors and more) */ KAboutData m_aboutData; /** * registered docs, map from general to specialized pointer */ QHash m_documents; /** * registered views */ QSet m_views; /** * global dirwatch object */ KDirWatch *m_dirWatch; /** * mode manager */ KateModeManager *m_modeManager; /** * schema manager */ KateSchemaManager *m_schemaManager; /** * global config */ KateGlobalConfig *m_globalConfig; /** * fallback document config */ KateDocumentConfig *m_documentConfig; /** * fallback view config */ KateViewConfig *m_viewConfig; /** * fallback renderer config */ KateRendererConfig *m_rendererConfig; /** * internal commands */ QList m_cmds; /** * script manager */ KateScriptManager *m_scriptManager; /** * hl manager */ KateHlManager *m_hlManager; /** * command manager */ KateCmd *m_cmdManager; /** * spell check manager */ KateSpellCheckManager *m_spellCheckManager; /** * global instance of the simple word completion mode */ KateWordCompletionModel *m_wordCompletionModel; /** * global instance of the language-specific keyword completion model */ KateKeywordCompletionModel *m_keywordCompletionModel; /** * clipboard history */ QVector> m_clipboardHistory; /** * Dummy application object to be null safe */ KTextEditor::Application m_dummyApplication; /** * access to application */ QPointer m_application; /** * Dummy main window to be null safe */ KTextEditor::MainWindow m_dummyMainWindow; /** * input modes map */ QMap m_inputModeFactories; /** * default colors */ QScopedPointer m_defaultColors; /** * Shared history models for search & replace. */ QStringListModel *m_searchHistoryModel; QStringListModel *m_replaceHistoryModel; }; } #endif diff --git a/src/utils/katesedcmd.h b/src/utils/katesedcmd.h index a2075178..647ba0d1 100644 --- a/src/utils/katesedcmd.h +++ b/src/utils/katesedcmd.h @@ -1,145 +1,145 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2003-2005 Anders Lund * Copyright (C) 2001-2010 Christoph Cullmann * Copyright (C) 2001 Charles Samuels * * 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_SED_CMD_H__ #define __KATE_SED_CMD_H__ #include #include "kateregexpsearch.h" #include #include namespace KTextEditor { class DocumentPrivate; class ViewPrivate; } /** * The KateCommands namespace collects subclasses of KTextEditor::Command * for specific use in kate. */ namespace KateCommands { /** * Support vim/sed style search and replace * @author Charles Samuels **/ class SedReplace : public KTextEditor::Command { static SedReplace *m_instance; protected: SedReplace() : KTextEditor::Command({ QStringLiteral("s"), QStringLiteral("%s"), QStringLiteral("$s") }) { } public: - ~SedReplace() + ~SedReplace() override { m_instance = nullptr; } /** * Execute command. Valid command strings are: * - s/search/replace/ find @c search, replace it with @c replace * on this line * - \%s/search/replace/ do the same to the whole file * - s/search/replace/i do the search and replace case insensitively * - $s/search/replace/ do the search are replacement to the * selection only * * @note $s/// is currently unsupported * @param view view to use for execution * @param cmd cmd string * @param errorMsg error to return if no success * @return success */ bool exec(class KTextEditor::View *view, const QString &cmd, QString &errorMsg, - const KTextEditor::Range &r) Q_DECL_OVERRIDE; + const KTextEditor::Range &r) override; - bool supportsRange(const QString &) Q_DECL_OVERRIDE + bool supportsRange(const QString &) override { return true; } /** This command does not have help. @see KTextEditor::Command::help */ - bool help(class KTextEditor::View *, const QString &, QString &) Q_DECL_OVERRIDE + bool help(class KTextEditor::View *, const QString &, QString &) override { return false; } static SedReplace *self() { if (m_instance == nullptr) { m_instance = new SedReplace(); } return m_instance; } /** * Parses @c sedReplaceString to see if it is a valid sed replace expression (e.g. "s/find/replace/gi"). * If it is, returns true and sets @c delimiter to the delimiter used in the expression; * @c destFindBeginPos and @c destFindEndPos to the positions in * @c sedReplaceString of the * begin and end of the "find" term; and @c destReplaceBeginPos and @c destReplaceEndPos to the begin * and end positions of the "replace" term. */ static bool parse(const QString &sedReplaceString, QString &destDelim, int &destFindBeginPos, int &destFindEndPos, int &destReplaceBeginPos, int &destReplaceEndPos); class InteractiveSedReplacer { public: InteractiveSedReplacer(KTextEditor::DocumentPrivate *doc, const QString &findPattern, const QString &replacePattern, bool caseSensitive, bool onlyOnePerLine, int startLine, int endLine); /** * Will return invalid Range if there are no further matches. */ KTextEditor::Range currentMatch(); void skipCurrentMatch(); void replaceCurrentMatch(); void replaceAllRemaining(); QString currentMatchReplacementConfirmationMessage(); QString finalStatusReportMessage(); private: const QString m_findPattern; const QString m_replacePattern; bool m_onlyOnePerLine; int m_endLine; KTextEditor::DocumentPrivate *m_doc; KateRegExpSearch m_regExpSearch; int m_numReplacementsDone; int m_numLinesTouched; int m_lastChangedLineNum; KTextEditor::Cursor m_currentSearchPos; const QVector fullCurrentMatch(); QString replacementTextForCurrentMatch(); }; protected: virtual bool interactiveSedReplace(KTextEditor::ViewPrivate *kateView, QSharedPointer interactiveSedReplace); }; } // namespace KateCommands #endif diff --git a/src/utils/katetemplatehandler.h b/src/utils/katetemplatehandler.h index 887c506b..a4a6e821 100644 --- a/src/utils/katetemplatehandler.h +++ b/src/utils/katetemplatehandler.h @@ -1,252 +1,252 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2004,2010 Joseph Wenninger * Copyright (C) 2009 Milian Wolff * Copyright (C) 2014 Sven Brauch * * 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_TEMPLATE_HANDLER_H_ #define _KATE_TEMPLATE_HANDLER_H_ #include #include #include #include #include #include class KateUndoManager; namespace KTextEditor { class DocumentPrivate; class ViewPrivate; class MovingCursor; class MovingRange; } /** * \brief Inserts a template and offers advanced snippet features, like navigation and mirroring. * * For each template inserted a new KateTemplateHandler will be created. * * The handler has the following features: * * \li It inserts the template string into the document at the requested position. * \li When the template contains at least one variable, the cursor will be placed * at the start of the first variable and its range gets selected. * \li When more than one variable exists,TAB and SHIFT TAB can be used to navigate * to the next/previous variable. * \li When a variable occurs more than once in the template, edits to any of the * occurrences will be mirroed to the other ones. * \li When ESC is pressed, the template handler closes. * \li When ALT + RETURN is pressed and a \c ${cursor} variable * exists in the template,the cursor will be placed there. Else the cursor will * be placed at the end of the template. * * \author Milian Wolff */ class KateTemplateHandler: public QObject { Q_OBJECT public: /** * Setup the template handler, insert the template string. * * NOTE: The handler deletes itself when required, you do not need to * keep track of it. */ KateTemplateHandler(KTextEditor::ViewPrivate *view, KTextEditor::Cursor position, const QString &templateString, const QString& script, KateUndoManager *undoManager); - virtual ~KateTemplateHandler(); + ~KateTemplateHandler() override; protected: /** * \brief Provide keyboard interaction for the template handler. * * The event filter handles the following shortcuts: * * TAB: jump to next editable (i.e. not mirrored) range. * NOTE: this prevents indenting via TAB. * SHIFT + TAB: jump to previous editable (i.e. not mirrored) range. * NOTE: this prevents un-indenting via SHIFT + TAB. * ESC: terminate template handler (only when no completion is active). * ALT + RETURN: accept template and jump to the end-cursor. * if %{cursor} was given in the template, that will be the * end-cursor. * else just jump to the end of the inserted text. */ - bool eventFilter(QObject *object, QEvent *event) Q_DECL_OVERRIDE; + bool eventFilter(QObject *object, QEvent *event) override; private: /** * Inserts the @p text template at @p position and performs * all necessary initializations, such as populating default values * and placing the cursor. */ void initializeTemplate(); /** * Parse @p templateText and populate m_fields. */ void parseFields(const QString& templateText); /** * Set necessary attributes (esp. background colour) on all moving * ranges for the fields in m_fields. */ void setupFieldRanges(); /** * Evaluate default values for all fields in m_fields and * store them in the fields. This updates the @property defaultValue property * of the TemplateField instances in m_fields from the raw, user-entered * default value to its evaluated equivalent (e.g. "func()" -> result of function call) * * @sa TemplateField */ void setupDefaultValues(); /** * Install an event filter on the filter proxy of \p view for * navigation between the ranges and terminating the KateTemplateHandler. * * \see eventFilter() */ void setupEventHandler(KTextEditor::View *view); /** * Jumps to the previous editable range. If there is none, wrap and jump to the first range. * * \see jumpToNextRange() */ void jumpToPreviousRange(); /** * Jumps to the next editable range. If there is none, wrap and jump to the last range. * * \see jumpToPreviousRange() */ void jumpToNextRange(); /** * Helper function for jumpTo{Next,Previous} * if initial is set to true, assumes the cursor is before the snippet * and selects the first field */ void jump(int by, bool initial = false); /** * Jumps to the final cursor position. This is either \p m_finalCursorPosition, or * if that is not set, the end of \p m_templateRange. */ void jumpToFinalCursorPosition(); /** * Go through all template fields and decide if their moving ranges expand * when edited at the corners. Expansion is turned off if two fields are * directly adjacent to avoid overlaps when characters are inserted between * them. */ void updateRangeBehaviours(); /** * Sort all template fields in m_fields by tab order, which means, * by range; except for ${cursor} which is always sorted last. */ void sortFields(); private Q_SLOTS: /** * Saves the range of the inserted template. This is required since * tabs could get expanded on insert. While we are at it, we can * use it to auto-indent the code after insert. */ void slotTemplateInserted(KTextEditor::Document *document, const KTextEditor::Range &range); /** * Install event filter on new views. */ void slotViewCreated(KTextEditor::Document *document, KTextEditor::View *view); /** * Update content of all dependent fields, i.e. mirror or script fields. */ void updateDependentFields(KTextEditor::Document *document, const KTextEditor::Range &oldRange); public: KTextEditor::ViewPrivate* view() const; KTextEditor::DocumentPrivate* doc() const; private: /// The view we operate on KTextEditor::ViewPrivate* m_view; /// The undo manager associated with our document KateUndoManager* const m_undoManager; // Describes a single template field, e.g. ${foo}. struct TemplateField { // up-to-date range for the field QSharedPointer range; // contents of the field, i.e. identifier or function to call QString identifier; // default value, if applicable; else empty QString defaultValue; enum Kind { Invalid, // not an actual field Editable, // normal, user-editable field (green by default) [non-dependent field] Mirror, // field mirroring contents of another field [dependent field] FunctionCall, // field containing the up-to-date result of a function call [dependent field] FinalCursorPosition // field marking the final cursor position }; Kind kind = Invalid; // true if this field was edited by the user before bool touched = false; bool operator==(const TemplateField& other) { return range == other.range; } }; // List of all template fields in the inserted snippet. @see sortFields() QVector m_fields; // Get the template field which contains @p range. const TemplateField fieldForRange(const KTextEditor::Range& range) const; /// Construct a map of master fields and their current value, for use in scripts. KateScript::FieldMap fieldMap() const; /// A range that occupies the whole range of the inserted template. /// When the an edit happens outside it, the template handler gets closed. QSharedPointer m_wholeTemplateRange; /// Set to true when currently updating dependent fields, to prevent recursion. bool m_internalEdit; /// template script (i.e. javascript stuff), which can be used by the current template KateScript m_templateScript; }; #endif diff --git a/src/utils/ktexteditor.cpp b/src/utils/ktexteditor.cpp index 65c047bd..0fa3b51e 100644 --- a/src/utils/ktexteditor.cpp +++ b/src/utils/ktexteditor.cpp @@ -1,281 +1,276 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann (cullmann@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kateview.h" #include "cursor.h" #include "configpage.h" #include "editor.h" #include "document.h" #include "view.h" #include "plugin.h" #include "command.h" #include "markinterface.h" #include "modificationinterface.h" #include "sessionconfiginterface.h" #include "texthintinterface.h" #include "annotationinterface.h" #include "kateglobal.h" #include "kateconfig.h" #include "katecmd.h" using namespace KTextEditor; Cursor Cursor::fromString(const QStringRef& str) Q_DECL_NOEXCEPT { // parse format "(line, column)" const int startIndex = str.indexOf(QLatin1Char('(')); const int endIndex = str.indexOf(QLatin1Char(')')); const int commaIndex = str.indexOf(QLatin1Char(',')); if (startIndex < 0 || endIndex < 0 || commaIndex < 0 || commaIndex < startIndex || endIndex < commaIndex || endIndex < startIndex) { return invalid(); } bool ok1 = false; bool ok2 = false; const int line = str.mid(startIndex + 1, commaIndex - startIndex - 1).toInt(&ok1); const int column = str.mid(commaIndex + 1, endIndex - commaIndex - 1).toInt(&ok2); if (!ok1 || !ok2) { return invalid(); } return {line, column}; } Editor::Editor(EditorPrivate *impl) : QObject () , d (impl) { } Editor::~Editor() { } Editor *KTextEditor::Editor::instance() { /** * Just use internal KTextEditor::EditorPrivate::self() */ return KTextEditor::EditorPrivate::self(); } QString Editor::defaultEncoding () const { /** * return default encoding in global config object */ return d->documentConfig()->encoding (); } bool View::insertText(const QString &text) { KTextEditor::Document *doc = document(); if (!doc) { return false; } return doc->insertText(cursorPosition(), text, blockSelection()); } bool View::isStatusBarEnabled() const { /** * is the status bar around? */ return !!d->statusBar(); } void View::setStatusBarEnabled(bool enable) { /** * no state change, do nothing */ if (enable == !!d->statusBar()) return; /** * else toggle it */ d->toggleStatusBar (); } bool View::insertTemplate(const KTextEditor::Cursor& insertPosition, const QString& templateString, const QString& script) { return d->insertTemplateInternal(insertPosition, templateString, script); } ConfigPage::ConfigPage(QWidget *parent) : QWidget(parent) , d(nullptr) {} ConfigPage::~ConfigPage() {} QString ConfigPage::fullName() const { return name(); } QIcon ConfigPage::icon() const { return QIcon::fromTheme(QStringLiteral("document-properties")); } View::View (ViewPrivate *impl, QWidget *parent) : QWidget (parent), KXMLGUIClient() , d(impl) {} View::~View() {} Plugin::Plugin(QObject *parent) : QObject(parent) , d(nullptr) {} Plugin::~Plugin() {} int Plugin::configPages() const { return 0; } ConfigPage *Plugin::configPage(int, QWidget *) { return nullptr; } MarkInterface::MarkInterface() - : d(nullptr) {} MarkInterface::~MarkInterface() {} ModificationInterface::ModificationInterface() - : d(nullptr) {} ModificationInterface::~ModificationInterface() {} SessionConfigInterface::SessionConfigInterface() - : d(nullptr) {} SessionConfigInterface::~SessionConfigInterface() {} TextHintInterface::TextHintInterface() - : d(nullptr) {} TextHintInterface::~TextHintInterface() {} TextHintProvider::TextHintProvider() - : d(nullptr) {} TextHintProvider::~TextHintProvider() {} Command::Command(const QStringList &cmds, QObject *parent) : QObject(parent) , m_cmds (cmds) , d(nullptr) { // register this command static_cast (KTextEditor::Editor::instance())->cmdManager()->registerCommand (this); } Command::~Command() { // unregister this command, if instance is still there! if (KTextEditor::Editor::instance()) static_cast (KTextEditor::Editor::instance())->cmdManager()->unregisterCommand (this); } bool Command::supportsRange(const QString &) { return false; } KCompletion *Command::completionObject(KTextEditor::View *, const QString &) { return nullptr; } bool Command::wantsToProcessText(const QString &) { return false; } void Command::processText(KTextEditor::View *, const QString &) { } void View::setScrollPosition(KTextEditor::Cursor &cursor) { d->setScrollPositionInternal(cursor); } void View::setHorizontalScrollPosition(int x) { d->setHorizontalScrollPositionInternal(x); } KTextEditor::Cursor View::maxScrollPosition() const { return d->maxScrollPositionInternal(); } int View::firstDisplayedLine(LineType lineType) const { return d->firstDisplayedLineInternal(lineType); } int View::lastDisplayedLine(LineType lineType) const { return d->lastDisplayedLineInternal(lineType); } QRect View::textAreaRect() const { return d->textAreaRectInternal(); } diff --git a/src/utils/movinginterface.cpp b/src/utils/movinginterface.cpp index 42682909..bdbf9d43 100644 --- a/src/utils/movinginterface.cpp +++ b/src/utils/movinginterface.cpp @@ -1,36 +1,35 @@ /* This file is part of the KDE project * * Copyright (C) 2010 Christoph Cullmann * * Based on code of the SmartCursor/Range by: * Copyright (C) 2003-2005 Hamish Rodda * * 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 "movinginterface.h" using namespace KTextEditor; MovingInterface::MovingInterface() - : d(nullptr) { } MovingInterface::~MovingInterface() { } diff --git a/src/utils/movingrangefeedback.cpp b/src/utils/movingrangefeedback.cpp index b0c757e9..a81cf86c 100644 --- a/src/utils/movingrangefeedback.cpp +++ b/src/utils/movingrangefeedback.cpp @@ -1,60 +1,59 @@ /* This file is part of the KDE project * * Copyright (C) 2010 Christoph Cullmann * * Based on code of the SmartCursor/Range by: * Copyright (C) 2003-2005 Hamish Rodda * * 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 "movingrangefeedback.h" using namespace KTextEditor; MovingRangeFeedback::MovingRangeFeedback() - : d(nullptr) { } MovingRangeFeedback::~MovingRangeFeedback() { } void MovingRangeFeedback::rangeEmpty(MovingRange *) { } void MovingRangeFeedback::rangeInvalid(MovingRange *) { } void MovingRangeFeedback::mouseEnteredRange(MovingRange *, View *) { } void MovingRangeFeedback::mouseExitedRange(MovingRange *, View *) { } void MovingRangeFeedback::caretEnteredRange(MovingRange *, View *) { } void MovingRangeFeedback::caretExitedRange(MovingRange *, View *) { } diff --git a/src/variableeditor/variableeditor.h b/src/variableeditor/variableeditor.h index 46ba1346..3a5a15b8 100644 --- a/src/variableeditor/variableeditor.h +++ b/src/variableeditor/variableeditor.h @@ -1,187 +1,187 @@ /* This file is part of the KDE project Copyright (C) 2011 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. */ #ifndef VARIABLE_EDITOR_H #define VARIABLE_EDITOR_H #include class KateHelpButton; class VariableBoolItem; class VariableColorItem; class VariableFontItem; class VariableItem; class VariableStringListItem; class VariableIntItem; class VariableStringItem; class VariableSpellCheckItem; class VariableRemoveSpacesItem; class KColorCombo; class QFontComboBox; class QCheckBox; class QComboBox; class QLabel; class QLineEdit; class QSpinBox; namespace Sonnet { class DictionaryComboBox; } class VariableEditor : public QWidget { Q_OBJECT public: VariableEditor(VariableItem *item, QWidget *parent = nullptr); - virtual ~VariableEditor(); + ~VariableEditor() override; VariableItem *item() const; Q_SIGNALS: void valueChanged(); protected Q_SLOTS: void itemEnabled(bool enabled); void activateItem(); protected: - void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE; - void enterEvent(QEvent *event) Q_DECL_OVERRIDE; - void leaveEvent(QEvent *event) Q_DECL_OVERRIDE; + void paintEvent(QPaintEvent *event) override; + void enterEvent(QEvent *event) override; + void leaveEvent(QEvent *event) override; private: VariableItem *m_item; QCheckBox *m_checkBox; QLabel *m_variable; QLabel *m_helpText; KateHelpButton *m_btnHelp; }; class VariableIntEditor : public VariableEditor { Q_OBJECT public: VariableIntEditor(VariableIntItem *item, QWidget *parent); protected Q_SLOTS: void setItemValue(int newValue); private: QSpinBox *m_spinBox; }; class VariableBoolEditor : public VariableEditor { Q_OBJECT public: VariableBoolEditor(VariableBoolItem *item, QWidget *parent); protected Q_SLOTS: void setItemValue(int enabled); private: QComboBox *m_comboBox; }; class VariableStringListEditor : public VariableEditor { Q_OBJECT public: VariableStringListEditor(VariableStringListItem *item, QWidget *parent); protected Q_SLOTS: void setItemValue(const QString &newValue); private: QComboBox *m_comboBox; }; class VariableColorEditor : public VariableEditor { Q_OBJECT public: VariableColorEditor(VariableColorItem *item, QWidget *parent); protected Q_SLOTS: void setItemValue(const QColor &newValue); private: KColorCombo *m_comboBox; }; class VariableFontEditor : public VariableEditor { Q_OBJECT public: VariableFontEditor(VariableFontItem *item, QWidget *parent); protected Q_SLOTS: void setItemValue(const QFont &newValue); private: QFontComboBox *m_comboBox; }; class VariableStringEditor : public VariableEditor { Q_OBJECT public: VariableStringEditor(VariableStringItem *item, QWidget *parent); protected Q_SLOTS: void setItemValue(const QString &newValue); private: QLineEdit *m_lineEdit; }; class VariableSpellCheckEditor : public VariableEditor { Q_OBJECT public: VariableSpellCheckEditor(VariableSpellCheckItem *item, QWidget *parent); protected Q_SLOTS: void setItemValue(const QString &newValue); private: Sonnet::DictionaryComboBox *m_dictionaryCombo; }; class VariableRemoveSpacesEditor : public VariableEditor { Q_OBJECT public: VariableRemoveSpacesEditor(VariableRemoveSpacesItem *item, QWidget *parent); protected Q_SLOTS: void setItemValue(int enabled); private: QComboBox *m_comboBox; }; #endif diff --git a/src/variableeditor/variableitem.h b/src/variableeditor/variableitem.h index 4771ac7e..efbc7c5d 100644 --- a/src/variableeditor/variableitem.h +++ b/src/variableeditor/variableitem.h @@ -1,228 +1,228 @@ /* This file is part of the KDE project Copyright (C) 2011 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. */ #ifndef VARIABLE_ITEM_H #define VARIABLE_ITEM_H #include #include #include #include class VariableEditor; //BEGIN class VariableItem class VariableItem { public: VariableItem(const QString &variable); virtual ~VariableItem(); QString variable() const; QString helpText() const; void setHelpText(const QString &text); bool isActive() const; void setActive(bool active); virtual void setValueByString(const QString &value) = 0; virtual QString valueAsString() const = 0; virtual VariableEditor *createEditor(QWidget *parent) = 0; private: // not implemented: copy constructor and operator= VariableItem(const VariableItem ©); VariableItem &operator=(const VariableItem &other); QString m_variable; QString m_helpText; bool m_active; }; //END class VariableItem // // DERIVE A CLASS FOR EACH TYPE // //BEGIN class VariableIntItem class VariableIntItem : public VariableItem { public: VariableIntItem(const QString &variable, int value); int value() const; void setValue(int newValue); void setRange(int minValue, int maxValue); int minValue() const; int maxValue() const; public: - void setValueByString(const QString &value) Q_DECL_OVERRIDE; - QString valueAsString() const Q_DECL_OVERRIDE; - VariableEditor *createEditor(QWidget *parent) Q_DECL_OVERRIDE; + void setValueByString(const QString &value) override; + QString valueAsString() const override; + VariableEditor *createEditor(QWidget *parent) override; private: int m_value; int m_minValue; int m_maxValue; }; //END class VariableIntItem //BEGIN class VariableStringListItem class VariableStringListItem : public VariableItem { public: VariableStringListItem(const QString &variable, const QStringList &slist, const QString &value); QStringList stringList() const; QString value() const; void setValue(const QString &newValue); public: - void setValueByString(const QString &value) Q_DECL_OVERRIDE; - QString valueAsString() const Q_DECL_OVERRIDE; - VariableEditor *createEditor(QWidget *parent) Q_DECL_OVERRIDE; + void setValueByString(const QString &value) override; + QString valueAsString() const override; + VariableEditor *createEditor(QWidget *parent) override; private: QStringList m_list; QString m_value; }; //END class VariableStringListItem //BEGIN class VariableBoolItem class VariableBoolItem : public VariableItem { public: VariableBoolItem(const QString &variable, bool value); bool value() const; void setValue(bool enabled); public: - void setValueByString(const QString &value) Q_DECL_OVERRIDE; - QString valueAsString() const Q_DECL_OVERRIDE; - VariableEditor *createEditor(QWidget *parent) Q_DECL_OVERRIDE; + void setValueByString(const QString &value) override; + QString valueAsString() const override; + VariableEditor *createEditor(QWidget *parent) override; private: bool m_value; }; //END class VariableBoolItem //BEGIN class VariableColorItem class VariableColorItem : public VariableItem { public: VariableColorItem(const QString &variable, const QColor &value); QColor value() const; void setValue(const QColor &color); public: - void setValueByString(const QString &value) Q_DECL_OVERRIDE; - QString valueAsString() const Q_DECL_OVERRIDE; - VariableEditor *createEditor(QWidget *parent) Q_DECL_OVERRIDE; + void setValueByString(const QString &value) override; + QString valueAsString() const override; + VariableEditor *createEditor(QWidget *parent) override; private: QColor m_value; }; //END class VariableColorItem //BEGIN class VariableFontItem class VariableFontItem : public VariableItem { public: VariableFontItem(const QString &variable, const QFont &value); QFont value() const; void setValue(const QFont &value); public: - void setValueByString(const QString &value) Q_DECL_OVERRIDE; - QString valueAsString() const Q_DECL_OVERRIDE; - VariableEditor *createEditor(QWidget *parent) Q_DECL_OVERRIDE; + void setValueByString(const QString &value) override; + QString valueAsString() const override; + VariableEditor *createEditor(QWidget *parent) override; private: QFont m_value; }; //END class VariableFontItem //BEGIN class VariableStringItem class VariableStringItem : public VariableItem { public: VariableStringItem(const QString &variable, const QString &value); QString value() const; void setValue(const QString &value); public: - void setValueByString(const QString &value) Q_DECL_OVERRIDE; // Same as setValue() in this case, implemented for uniformity - QString valueAsString() const Q_DECL_OVERRIDE; // Same as value() in this case, implemented for uniformity - VariableEditor *createEditor(QWidget *parent) Q_DECL_OVERRIDE; + void setValueByString(const QString &value) override; // Same as setValue() in this case, implemented for uniformity + QString valueAsString() const override; // Same as value() in this case, implemented for uniformity + VariableEditor *createEditor(QWidget *parent) override; private: QString m_value; }; //END class VariableStringItem //BEGIN class VariableSpellCheckItem class VariableSpellCheckItem : public VariableItem { public: VariableSpellCheckItem(const QString &variable, const QString &value); QString value() const; void setValue(const QString &value); public: - void setValueByString(const QString &value) Q_DECL_OVERRIDE; // Same as setValue() in this case, implemented for uniformity - QString valueAsString() const Q_DECL_OVERRIDE; // Same as value() in this case, implemented for uniformity - VariableEditor *createEditor(QWidget *parent) Q_DECL_OVERRIDE; + void setValueByString(const QString &value) override; // Same as setValue() in this case, implemented for uniformity + QString valueAsString() const override; // Same as value() in this case, implemented for uniformity + VariableEditor *createEditor(QWidget *parent) override; private: QString m_value; }; //END class VariableSpellCheckItem //BEGIN class VariableRemoveSpacesItem class VariableRemoveSpacesItem : public VariableItem { public: VariableRemoveSpacesItem(const QString &variable, int value); int value() const; void setValue(int value); public: - void setValueByString(const QString &value) Q_DECL_OVERRIDE; // Same as setValue() in this case, implemented for uniformity - QString valueAsString() const Q_DECL_OVERRIDE; // Same as value() in this case, implemented for uniformity - VariableEditor *createEditor(QWidget *parent) Q_DECL_OVERRIDE; + void setValueByString(const QString &value) override; // Same as setValue() in this case, implemented for uniformity + QString valueAsString() const override; // Same as value() in this case, implemented for uniformity + VariableEditor *createEditor(QWidget *parent) override; private: int m_value; }; //END class VariableRemoveSpacesItem #endif diff --git a/src/variableeditor/variablelineedit.h b/src/variableeditor/variablelineedit.h index 6afb2b21..4ea31649 100644 --- a/src/variableeditor/variablelineedit.h +++ b/src/variableeditor/variablelineedit.h @@ -1,59 +1,59 @@ /* This file is part of the KDE project Copyright (C) 2011 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. */ #ifndef VARIABLE_LINE_EDIT_H #define VARIABLE_LINE_EDIT_H #include class QFrame; class QLineEdit; class QToolButton; class VariableListView; class VariableLineEdit : public QWidget { Q_OBJECT public: - VariableLineEdit(QWidget *parent = nullptr); + explicit VariableLineEdit(QWidget *parent = nullptr); virtual ~VariableLineEdit(); void addKateItems(VariableListView *listview); QString text(); public Q_SLOTS: void editVariables(); void setText(const QString &text); void clear(); void updateVariableLine(); Q_SIGNALS: void textChanged(const QString &); private: QFrame *m_popup; QLineEdit *m_lineedit; QToolButton *m_button; VariableListView *m_listview; }; #endif diff --git a/src/variableeditor/variablelistview.h b/src/variableeditor/variablelistview.h index a4cd9064..f30f9a00 100644 --- a/src/variableeditor/variablelistview.h +++ b/src/variableeditor/variablelistview.h @@ -1,60 +1,60 @@ /* This file is part of the KDE project Copyright (C) 2011 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. */ #ifndef VARIABLE_LIST_VIEW_H #define VARIABLE_LIST_VIEW_H #include #include class VariableItem; class VariableEditor; class VariableListView : public QScrollArea { Q_OBJECT public: VariableListView(const QString &variableLine, QWidget *parent = nullptr); - virtual ~VariableListView(); + ~VariableListView() override; void addItem(VariableItem *item); /// always returns the up-to-date variables line QString variableLine(); Q_SIGNALS: void aboutToHide(); void changed(); // unused right now protected: - void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE; - void hideEvent(QHideEvent *event) Q_DECL_OVERRIDE; + void resizeEvent(QResizeEvent *event) override; + void hideEvent(QHideEvent *event) override; void parseVariables(const QString &line); QVector m_items; QVector m_editors; QMap m_variables; }; #endif diff --git a/src/view/katefadeeffect.h b/src/view/katefadeeffect.h index 4243220a..e1dc2dba 100644 --- a/src/view/katefadeeffect.h +++ b/src/view/katefadeeffect.h @@ -1,109 +1,109 @@ /* This file is part of the KDE and the Kate project * * Copyright (C) 2013 Dominik Haumann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KATE_FADE_EFFECT_H #define KATE_FADE_EFFECT_H #include #include class QWidget; class QTimeLine; class QGraphicsOpacityEffect; /** * This class provides a fade in/out effect for arbitrary QWidget%s. * Example: * \code * KateFadeEffect* fadeEffect = new KateFadeEffect(someWidget); * fadeEffect->fadeIn(); * //... * fadeEffect->fadeOut(); * \endcode */ class KateFadeEffect : public QObject { Q_OBJECT public: /** * Constructor. * By default, the widget is fully opaque (opacity = 1.0). */ - KateFadeEffect(QWidget *widget = nullptr); + explicit KateFadeEffect(QWidget *widget = nullptr); /** * Check whether the hide animation started by calling fadeOut() * is still running. If animations are disabled, this function always * returns @e false. */ bool isHideAnimationRunning() const; /** * Check whether the show animation started by calling fadeIn() * is still running. If animations are disabled, this function always * returns @e false. */ bool isShowAnimationRunning() const; public Q_SLOTS: /** * Call to fade out and hide the widget. */ void fadeOut(); /** * Call to show and fade in the widget */ void fadeIn(); Q_SIGNALS: /** * This signal is emitted when the fadeOut animation is finished, started by * calling fadeOut(). If animations are disabled, this signal is * emitted immediately. */ void hideAnimationFinished(); /** * This signal is emitted when the fadeIn animation is finished, started by * calling fadeIn(). If animations are disabled, this signal is * emitted immediately. */ void showAnimationFinished(); protected Q_SLOTS: /** * Helper to update opacity value */ void opacityChanged(qreal value); /** * When the animation is finished, hide the widget if fading out. */ void animationFinished(); private: QPointer m_widget; ///< the fading widget QTimeLine *m_timeLine; ///< update time line QPointer m_effect; ///< graphics opacity effect }; #endif diff --git a/src/view/katestatusbar.cpp b/src/view/katestatusbar.cpp index 87577ccf..ea7f9ce3 100644 --- a/src/view/katestatusbar.cpp +++ b/src/view/katestatusbar.cpp @@ -1,451 +1,483 @@ /* This file is part of the KDE and the Kate project * * Copyright (C) 2013 Dominik Haumann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "katestatusbar.h" #include "katemodemenu.h" #include "kateglobal.h" #include "katemodemanager.h" #include "katedocument.h" #include "kateconfig.h" #include "kateabstractinputmode.h" #include "wordcounter.h" #include #include #include #include #include //BEGIN menu KateStatusBarOpenUpMenu::KateStatusBarOpenUpMenu(QWidget *parent) : QMenu(parent) {} KateStatusBarOpenUpMenu::~KateStatusBarOpenUpMenu(){} void KateStatusBarOpenUpMenu::setVisible(bool visibility) { if (visibility) { QRect geo=geometry(); QPoint pos=((QPushButton*)parent())->mapToGlobal(QPoint(0,0)); geo.moveTopLeft(QPoint(pos.x(),pos.y()-geo.height())); if (geo.top()<0) geo.moveTop(0); setGeometry(geo); } QMenu::setVisible(visibility); } //END menu KateStatusBar::KateStatusBar(KTextEditor::ViewPrivate *view) : KateViewBarWidget(false) , m_view(view) , m_insertModeLabel(nullptr) , m_modifiedStatus (-1) , m_selectionMode (-1) , m_wordCounter(nullptr) { KAcceleratorManager::setNoAccel(this); setFocusProxy(m_view); /** * just add our status bar to central widget, full sized */ QHBoxLayout *topLayout = new QHBoxLayout(centralWidget()); topLayout->setMargin(0); topLayout->setSpacing(4); /** * add a bit space */ topLayout->addSpacing (4); /** * show Line XXX, Column XXX */ m_lineColLabel = new QLabel( this ); m_lineColLabel->installEventFilter(this); // register for doubleclick topLayout->addWidget( m_lineColLabel, 0 ); m_lineColLabel->setAlignment( Qt::AlignVCenter | Qt::AlignLeft ); m_lineColLabel->setFocusProxy(m_view); m_lineColLabel->setWhatsThis(i18n("Current cursor position. Doubleclick to go to specific line.")); /** * show word count */ m_wordCountLabel = new QLabel(this); topLayout->addWidget(m_wordCountLabel, 0); m_wordCountLabel->setAlignment(Qt::AlignVCenter | Qt::AlignLeft); m_wordCountLabel->setFocusProxy(m_view); m_wordCountLabel->setWhatsThis(i18n("Words and Chars count in document/selection.")); m_wordCountLabel->hide(); /** * show the current mode, like INSERT, OVERWRITE, VI + modifiers like [BLOCK] */ m_insertModeLabel = new QLabel( this ); m_insertModeLabel->installEventFilter(this); // register for doubleclick topLayout->addWidget( m_insertModeLabel, 1000 /* this one should strech */ ); m_insertModeLabel->setAlignment( Qt::AlignVCenter | Qt::AlignRight ); m_insertModeLabel->setFocusProxy(m_view); m_insertModeLabel->setWhatsThis(i18n("Insert mode and VI input mode indicator")); /** * allow to change indentation configuration */ m_spacesOnly = ki18n("Soft Tabs: %1"); m_spacesOnlyShowTabs = ki18n("Soft Tabs: %1 (%2)"); m_tabsOnly = ki18n("Tab Size: %1"); m_tabSpacesMixed = ki18n("Indent/Tab: %1/%2"); QAction *action; m_tabGroup = new QActionGroup(this); m_indentGroup = new QActionGroup(this); m_tabsIndent = new QPushButton(QString(), this); m_tabsIndent->setFlat(true); topLayout->addWidget(m_tabsIndent, 0); m_tabsIndent->setFocusProxy(m_view); m_indentSettingsMenu = new KateStatusBarOpenUpMenu(m_tabsIndent); m_indentSettingsMenu->addSection(i18n("Tab Width")); addNumberAction(m_tabGroup, m_indentSettingsMenu, -1); addNumberAction(m_tabGroup, m_indentSettingsMenu, 8); addNumberAction(m_tabGroup, m_indentSettingsMenu, 4); addNumberAction(m_tabGroup, m_indentSettingsMenu, 2); m_indentSettingsMenu->addSection(i18n("Indentation Width")); addNumberAction(m_indentGroup, m_indentSettingsMenu, -1); addNumberAction(m_indentGroup, m_indentSettingsMenu, 8); addNumberAction(m_indentGroup, m_indentSettingsMenu, 4); addNumberAction(m_indentGroup, m_indentSettingsMenu, 2); m_indentSettingsMenu->addSection(i18n("Indentation Mode")); QActionGroup *radioGroup = new QActionGroup(m_indentSettingsMenu); action = m_indentSettingsMenu->addAction(i18n("Tabulators && Spaces")); action->setCheckable(true); action->setActionGroup(radioGroup); m_mixedAction = action; action = m_indentSettingsMenu->addAction(i18n("Tabulators")); action->setCheckable(true); action->setActionGroup(radioGroup); m_hardAction = action; action = m_indentSettingsMenu->addAction(i18n("Spaces")); action->setCheckable(true); action->setActionGroup(radioGroup); m_softAction = action; m_tabsIndent->setMenu(m_indentSettingsMenu); /** * add encoding button which allows user to switch encoding of document * this will reuse the encoding action menu of the view */ m_encoding = new QPushButton( QString(), this ); m_encoding->setFlat(true); topLayout->addWidget( m_encoding, 0 ); m_encoding->setMenu(m_view->encodingAction()->menu()); m_encoding->setFocusProxy(m_view); m_encoding->setWhatsThis(i18n("Encoding")); /** * add mode button which allows user to switch mode of document * this will reuse the mode action menu of the view */ m_mode = new QPushButton( QString(), this ); m_mode->setFlat(true); topLayout->addWidget( m_mode, 0 ); m_mode->setMenu(m_view->modeAction()->menu()); m_mode->setFocusProxy(m_view); m_mode->setWhatsThis(i18n("Syntax highlighting")); /** * show modification state of the document */ m_modifiedLabel = new QToolButton( this ); m_modifiedLabel->setAutoRaise(true); m_modifiedLabel->setEnabled(false); topLayout->addWidget( m_modifiedLabel, 0 ); m_modifiedLabel->setFocusProxy(m_view); /** * add a bit space */ topLayout->addSpacing (4); // signals for the statusbar connect(m_view, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(cursorPositionChanged())); connect(m_view, SIGNAL(viewModeChanged(KTextEditor::View*,KTextEditor::View::ViewMode)), this, SLOT(viewModeChanged())); connect(m_view, SIGNAL(selectionChanged(KTextEditor::View*)), this, SLOT(selectionChanged())); connect(m_view->document(), SIGNAL(modifiedChanged(KTextEditor::Document*)), this, SLOT(modifiedChanged())); connect(m_view->document(), SIGNAL(modifiedOnDisk(KTextEditor::Document*,bool,KTextEditor::ModificationInterface::ModifiedOnDiskReason)), this, SLOT(modifiedChanged()) ); connect(m_view->document(), SIGNAL(configChanged()), this, SLOT(documentConfigChanged())); connect(m_view->document(), SIGNAL(modeChanged(KTextEditor::Document*)), this, SLOT(modeChanged())); connect(m_view, &KTextEditor::ViewPrivate::configChanged, this, &KateStatusBar::configChanged); connect(m_tabGroup,SIGNAL(triggered(QAction*)),this,SLOT(slotTabGroup(QAction*))); connect(m_indentGroup,SIGNAL(triggered(QAction*)),this,SLOT(slotIndentGroup(QAction*))); connect(radioGroup,SIGNAL(triggered(QAction*)),this,SLOT(slotIndentTabMode(QAction*))); updateStatus (); wordCountChanged(0, 0, 0, 0); } bool KateStatusBar::eventFilter(QObject *obj, QEvent *event) { if (obj == m_insertModeLabel) { if (event->type() == QEvent::MouseButtonDblClick) { m_view->currentInputMode()->toggleInsert(); return true; } } else if (obj == m_lineColLabel) { if (event->type() == QEvent::MouseButtonDblClick) { m_view->gotoLine(); return true; } } return KateViewBarWidget::eventFilter(obj, event); } +void KateStatusBar::contextMenuEvent(QContextMenuEvent *event) +{ + QMenu menu(this); + QAction *showLines = menu.addAction(QStringLiteral("Show Lines Count"), this, &KateStatusBar::toggleShowLines); + showLines->setCheckable(true); + showLines->setChecked(KateViewConfig::global()->showLinesCount()); + QAction *showWords = menu.addAction(QStringLiteral("Show word count"), this, &KateStatusBar::toggleShowWords); + showWords->setCheckable(true); + showWords->setChecked(KateViewConfig::global()->showWordCount()); + menu.exec(event->globalPos()); +} + +void KateStatusBar::toggleShowLines(bool checked) +{ + KateViewConfig::global()->setShowLinesCount(checked); +} + +void KateStatusBar::toggleShowWords(bool checked) +{ + KateViewConfig::global()->setShowWordCount(checked); +} + void KateStatusBar::updateStatus () { selectionChanged (); viewModeChanged (); cursorPositionChanged (); modifiedChanged (); documentConfigChanged(); modeChanged(); } void KateStatusBar::selectionChanged () { const unsigned int newSelectionMode = m_view->blockSelection(); if (newSelectionMode == m_selectionMode) { return; } // remember new mode and update info m_selectionMode = newSelectionMode; viewModeChanged(); } void KateStatusBar::viewModeChanged () { // prepend BLOCK for block selection mode QString text = m_view->viewModeHuman(); if (m_view->blockSelection()) text = i18n ("[BLOCK] %1", text); m_insertModeLabel->setText(text); } void KateStatusBar::cursorPositionChanged () { KTextEditor::Cursor position (m_view->cursorPositionVirtual()); + if( KateViewConfig::global()->showLinesCount() ) m_lineColLabel->setText( + i18n("Line %1 of %2, Column %3" + , QLocale().toString(position.line() + 1) + , QLocale().toString(m_view->doc()->lines()) + , QLocale().toString(position.column() + 1) + ) + ); + + else + m_lineColLabel->setText( i18n("Line %1, Column %2" , QLocale().toString(position.line() + 1) , QLocale().toString(position.column() + 1) ) ); } void KateStatusBar::modifiedChanged() { const bool mod = m_view->doc()->isModified(); const bool modOnHD = m_view->doc()->isModifiedOnDisc(); /** * combine to modified status, update only if changed */ unsigned int newStatus = (unsigned int)mod | ((unsigned int)modOnHD << 1); if (m_modifiedStatus == newStatus) return; m_modifiedStatus = newStatus; switch (m_modifiedStatus) { case 0x1: m_modifiedLabel->setIcon (QIcon::fromTheme(QStringLiteral("document-save"))); m_modifiedLabel->setWhatsThis(i18n("Meaning of current icon: Document was modified since it was loaded")); break; case 0x2: m_modifiedLabel->setIcon (QIcon::fromTheme(QStringLiteral("dialog-warning"))); m_modifiedLabel->setWhatsThis(i18n("Meaning of current icon: Document was modified or deleted by another program")); break; case 0x3: m_modifiedLabel->setIcon (QIcon(KIconUtils::addOverlay(QIcon::fromTheme(QStringLiteral("document-save")), QIcon(QStringLiteral("emblem-important")), Qt::TopLeftCorner))); m_modifiedLabel->setWhatsThis(QString()); break; default: m_modifiedLabel->setIcon (QIcon::fromTheme(QStringLiteral("text-plain"))); m_modifiedLabel->setWhatsThis(i18n("Meaning of current icon: Document was not modified since it was loaded")); break; } } void KateStatusBar::documentConfigChanged () { m_encoding->setText( m_view->document()->encoding() ); KateDocumentConfig *config=((KTextEditor::DocumentPrivate*)m_view->document())->config(); int tabWidth=config->tabWidth(); int indentationWidth=config->indentationWidth(); bool replaceTabsDyn=config->replaceTabsDyn(); if (!replaceTabsDyn) { if (tabWidth==indentationWidth) { m_tabsIndent->setText(m_tabsOnly.subs(tabWidth).toString()); m_tabGroup->setEnabled(false); m_hardAction->setChecked(true); } else { m_tabsIndent->setText(m_tabSpacesMixed.subs(indentationWidth).subs(tabWidth).toString()); m_tabGroup->setEnabled(true); m_mixedAction->setChecked(true); } } else { if (tabWidth==indentationWidth) { m_tabsIndent->setText(m_spacesOnly.subs(indentationWidth).toString()); m_tabGroup->setEnabled(true); m_softAction->setChecked(true); } else { m_tabsIndent->setText(m_spacesOnlyShowTabs.subs(indentationWidth).subs(tabWidth).toString()); m_tabGroup->setEnabled(true); m_softAction->setChecked(true); } } updateGroup(m_tabGroup,tabWidth); updateGroup(m_indentGroup,indentationWidth); } void KateStatusBar::modeChanged () { m_mode->setText( KTextEditor::EditorPrivate::self()->modeManager()->fileType(m_view->document()->mode()).nameTranslated() ); } void KateStatusBar::addNumberAction(QActionGroup *group, QMenu *menu, int data) { QAction *a; if (data != -1) { a = menu->addAction(QStringLiteral("%1").arg(data)); } else { a = menu->addAction(i18n("Other...")); } a->setData(data); a->setCheckable(true); a->setActionGroup(group); } void KateStatusBar::updateGroup(QActionGroup *group, int w) { QAction *m1=nullptr; bool found=false; //linear search should be fast enough here, no additional hash Q_FOREACH(QAction *action, group->actions()) { int val=action->data().toInt(); if (val==-1) m1=action; if (val==w) { found=true; action->setChecked(true); } } if (found) { m1->setText(i18n("Other...")); } else { m1->setText(i18np("Other (%1)", "Other (%1)", w)); m1->setChecked(true); } } void KateStatusBar::slotTabGroup(QAction* a) { int val=a->data().toInt(); bool ok; KateDocumentConfig *config=((KTextEditor::DocumentPrivate*)m_view->document())->config(); if (val==-1) { val=QInputDialog::getInt(this, i18n("Tab Width"), i18n("Please specify the wanted tab width:"), config->tabWidth(), 1, 16, 1, &ok); if (!ok) val=config->tabWidth(); } config->setTabWidth(val); } void KateStatusBar::slotIndentGroup(QAction* a) { int val=a->data().toInt(); bool ok; KateDocumentConfig *config=((KTextEditor::DocumentPrivate*)m_view->document())->config(); if (val==-1) { val=QInputDialog::getInt(this, i18n("Indentation Width"), i18n("Please specify the wanted indentation width:"), config->indentationWidth(), 1, 16, 1, &ok); if (!ok) val=config->indentationWidth(); } config->configStart(); config->setIndentationWidth(val); if (m_hardAction->isChecked()) config->setTabWidth(val); config->configEnd(); } void KateStatusBar::slotIndentTabMode(QAction* a) { KateDocumentConfig *config=((KTextEditor::DocumentPrivate*)m_view->document())->config(); if (a==m_softAction) { config->setReplaceTabsDyn(true); } else if (a==m_mixedAction) { if (config->replaceTabsDyn()) config->setReplaceTabsDyn(false); m_tabGroup->setEnabled(true); } else if (a==m_hardAction) { if (config->replaceTabsDyn()) { config->configStart(); config->setReplaceTabsDyn(false); config->setTabWidth(config->indentationWidth()); config->configEnd(); } else { config->setTabWidth(config->indentationWidth()); } m_tabGroup->setEnabled(false); } } void KateStatusBar::toggleWordCount(bool on) { if ((m_wordCounter != nullptr) == on) { return; } if (on) { m_wordCounter = new WordCounter(m_view); connect(m_wordCounter, &WordCounter::changed, this, &KateStatusBar::wordCountChanged); } else { delete m_wordCounter; m_wordCounter = nullptr; wordCountChanged(0, 0, 0, 0); } m_wordCountLabel->setVisible(on); } void KateStatusBar::wordCountChanged(int wordsInDocument, int wordsInSelection, int charsInDocument, int charsInSelection) { m_wordCountLabel->setText(i18n("Words %1/%2, Chars %3/%4", wordsInDocument, wordsInSelection, charsInDocument, charsInSelection)); } void KateStatusBar::configChanged() { toggleWordCount(m_view->config()->showWordCount()); } diff --git a/src/view/katestatusbar.h b/src/view/katestatusbar.h index 1c399bb1..c8aedda2 100644 --- a/src/view/katestatusbar.h +++ b/src/view/katestatusbar.h @@ -1,109 +1,112 @@ /* This file is part of the KDE and the Kate project * * Copyright (C) 2013 Dominik Haumann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KATE_STATUS_BAR_H #define KATE_STATUS_BAR_H #include "kateview.h" #include "kateviewhelpers.h" #include #include #include #include #include class WordCounter; class KateStatusBarOpenUpMenu: public QMenu { Q_OBJECT public: KateStatusBarOpenUpMenu(QWidget *parent); - virtual ~KateStatusBarOpenUpMenu(); - void setVisible(bool) Q_DECL_OVERRIDE; + ~KateStatusBarOpenUpMenu() override; + void setVisible(bool) override; }; class KateStatusBar : public KateViewBarWidget { Q_OBJECT public: explicit KateStatusBar(KTextEditor::ViewPrivate *view); public Q_SLOTS: void updateStatus (); void viewModeChanged (); void cursorPositionChanged (); void selectionChanged (); void modifiedChanged(); void documentConfigChanged (); void modeChanged (); void wordCountChanged(int, int, int, int); void toggleWordCount(bool on); void configChanged(); protected: - bool eventFilter(QObject *obj, QEvent *event) Q_DECL_OVERRIDE; + bool eventFilter(QObject *obj, QEvent *event) override; + void contextMenuEvent(QContextMenuEvent *event) override; private: KTextEditor::ViewPrivate *const m_view; QLabel* m_lineColLabel; QLabel* m_wordCountLabel; QToolButton* m_modifiedLabel; QLabel* m_insertModeLabel; QPushButton* m_mode; QPushButton* m_encoding; QPushButton* m_tabsIndent; KLocalizedString m_spacesOnly; KLocalizedString m_tabsOnly; KLocalizedString m_tabSpacesMixed; KLocalizedString m_spacesOnlyShowTabs; QMenu *m_indentSettingsMenu; unsigned int m_modifiedStatus; unsigned int m_selectionMode; QActionGroup *m_tabGroup; QActionGroup *m_indentGroup; QAction *m_mixedAction; QAction *m_hardAction; QAction *m_softAction; WordCounter *m_wordCounter; private: void addNumberAction(QActionGroup *group, QMenu *menu, int data); void updateGroup(QActionGroup *group, int w); public Q_SLOTS: void slotTabGroup(QAction*); void slotIndentGroup(QAction*); void slotIndentTabMode(QAction*); + void toggleShowLines(bool checked); + void toggleShowWords(bool checked); }; #endif diff --git a/src/view/katetextpreview.h b/src/view/katetextpreview.h index fb456a9c..8e6b2f3e 100644 --- a/src/view/katetextpreview.h +++ b/src/view/katetextpreview.h @@ -1,106 +1,106 @@ /* This file is part of the KDE libraries Copyright (C) 2016 Dominik Haumann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef KATE_TEXT_PREVIEW_H #define KATE_TEXT_PREVIEW_H #include //namespace KTextEditor { class DocumentPrivate; } namespace KTextEditor { class ViewPrivate; } /** * TODO */ class KateTextPreview : public QFrame { Q_OBJECT Q_PROPERTY(qreal line READ line WRITE setLine) Q_PROPERTY(bool showFoldedLines READ showFoldedLines WRITE setShowFoldedLines) Q_PROPERTY(bool centerView READ centerView WRITE setCenterView) Q_PROPERTY(qreal scaleFactor READ scaleFactor WRITE setScaleFactor) public: KateTextPreview(KTextEditor::ViewPrivate *view); - virtual ~KateTextPreview(); + ~KateTextPreview() override; KTextEditor::ViewPrivate *view() const; /** * Sets @p line as preview line. */ void setLine(qreal line); /** * Returns the line set with setLine(). */ qreal line() const; /** * Enabled/disable centering the view on the line set with setLine(). * If @p center is false, the first visible line is the once specified in * setLine(). If @p center is true, the specified line is vertically * centered. By default, centering the preview is set to true. */ void setCenterView(bool center); /** * Returns whether view centering is enabled. */ bool centerView() const; /** * Sets the scale factor. * The default scale factor is 1.0. For text previews, you may want a scale * factor of e.g. 0.8 or 0.9. Negative scale factors are not allowed. */ void setScaleFactor(qreal factor); /** * Returns the scale factor set with setScale(). * The default value is 1.0. */ qreal scaleFactor() const; /** * Sets whether folded lines are hidden or not. * By default, folded liens are not visible. */ void setShowFoldedLines(bool on); /** * Returns whether folded lines are hidden. */ bool showFoldedLines() const; protected: - void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE; + void paintEvent(QPaintEvent *event) override; private: KTextEditor::ViewPrivate *m_view; qreal m_line; bool m_showFoldedLines; bool m_center; qreal m_scale; }; #endif diff --git a/src/view/kateview.cpp b/src/view/kateview.cpp index aeaa6889..ee452518 100644 --- a/src/view/kateview.cpp +++ b/src/view/kateview.cpp @@ -1,3751 +1,3754 @@ /* This file is part of the KDE libraries Copyright (C) 2009 Michel Ludwig Copyright (C) 2007 Mirko Stocker Copyright (C) 2003 Hamish Rodda Copyright (C) 2002 John Firebaugh Copyright (C) 2001-2004 Christoph Cullmann Copyright (C) 2001-2010 Joseph Wenninger Copyright (C) 1999 Jochen Wilhelmy This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ //BEGIN includes #include "kateview.h" #include "kateviewinternal.h" #include "kateviewhelpers.h" #include "katerenderer.h" #include "katedocument.h" #include "kateundomanager.h" #include "kateglobal.h" #include "katehighlight.h" #include "katehighlightmenu.h" #include "katedialogs.h" #include "katetextline.h" #include "kateschema.h" #include "katebookmarks.h" #include "kateconfig.h" #include "katemodemenu.h" #include "kateautoindent.h" #include "katecompletionwidget.h" #include "katewordcompletion.h" #include "katekeywordcompletion.h" #include "katelayoutcache.h" #include "spellcheck/spellcheck.h" #include "spellcheck/spellcheckdialog.h" #include "spellcheck/spellingmenu.h" #include "katebuffer.h" #include "script/katescriptmanager.h" #include "script/katescriptaction.h" #include "export/exporter.h" #include "katemessagewidget.h" #include "katetemplatehandler.h" #include "katepartdebug.h" #include "printing/kateprinter.h" #include "katestatusbar.h" #include "kateabstractinputmode.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#define VIEW_RANGE_DEBUG //END includes namespace { bool hasCommentInFirstLine(KTextEditor::DocumentPrivate* doc) { const Kate::TextLine& line = doc->kateTextLine(0); Q_ASSERT(line); return doc->isComment(0, line->firstChar()); } } void KTextEditor::ViewPrivate::blockFix(KTextEditor::Range &range) { if (range.start().column() > range.end().column()) { int tmp = range.start().column(); range.setStart(KTextEditor::Cursor(range.start().line(), range.end().column())); range.setEnd(KTextEditor::Cursor(range.end().line(), tmp)); } } KTextEditor::ViewPrivate::ViewPrivate(KTextEditor::DocumentPrivate *doc, QWidget *parent, KTextEditor::MainWindow *mainWindow) : KTextEditor::View (this, parent) , m_completionWidget(nullptr) , m_annotationModel(nullptr) , m_hasWrap(false) , m_doc(doc) , m_textFolding(doc->buffer()) , m_config(new KateViewConfig(this)) , m_renderer(new KateRenderer(doc, m_textFolding, this)) , m_viewInternal(new KateViewInternal(this)) , m_spell(new KateSpellCheckDialog(this)) , m_bookmarks(new KateBookmarks(this)) , m_topSpacer(new QSpacerItem(0,0)) , m_leftSpacer(new QSpacerItem(0,0)) , m_rightSpacer(new QSpacerItem(0,0)) , m_bottomSpacer(new QSpacerItem(0,0)) , m_startingUp(true) , m_updatingDocumentConfig(false) , m_bottomViewBar(nullptr) , m_gotoBar(nullptr) , m_dictionaryBar(nullptr) , m_spellingMenu(new KateSpellingMenu(this)) , m_userContextMenuSet(false) , m_delayedUpdateTriggered(false) , m_lineToUpdateMin(-1) , m_lineToUpdateMax(-1) , m_mainWindow(mainWindow ? mainWindow : KTextEditor::EditorPrivate::self()->dummyMainWindow()) // use dummy window if no window there! , m_statusBar(nullptr) , m_temporaryAutomaticInvocationDisabled(false) , m_autoFoldedFirstLine(false) , m_clipboard(cursors()) { // queued connect to collapse view updates for range changes, INIT THIS EARLY ENOUGH! connect(this, SIGNAL(delayedUpdateOfView()), this, SLOT(slotDelayedUpdateOfView()), Qt::QueuedConnection); KXMLGUIClient::setComponentName(KTextEditor::EditorPrivate::self()->aboutData().componentName(), KTextEditor::EditorPrivate::self()->aboutData().displayName()); KTextEditor::EditorPrivate::self()->registerView(this); /** * try to let the main window, if any, create a view bar for this view */ QWidget *bottomBarParent = m_mainWindow->createViewBar(this); m_bottomViewBar = new KateViewBar(bottomBarParent != nullptr, bottomBarParent ? bottomBarParent : this, this); // ugly workaround: // Force the layout to be left-to-right even on RTL deskstop, as discussed // on the mailing list. This will cause the lines and icons panel to be on // the left, even for Arabic/Hebrew/Farsi/whatever users. setLayoutDirection(Qt::LeftToRight); m_bottomViewBar->installEventFilter(m_viewInternal); // add KateMessageWidget for KTE::MessageInterface immediately above view m_messageWidgets[KTextEditor::Message::AboveView] = new KateMessageWidget(this); m_messageWidgets[KTextEditor::Message::AboveView]->hide(); // add KateMessageWidget for KTE::MessageInterface immediately above view m_messageWidgets[KTextEditor::Message::BelowView] = new KateMessageWidget(this); m_messageWidgets[KTextEditor::Message::BelowView]->hide(); // add bottom viewbar... if (bottomBarParent) { m_mainWindow->addWidgetToViewBar(this, m_bottomViewBar); } // add layout for floating message widgets to KateViewInternal m_notificationLayout = new KateMessageLayout(m_viewInternal); m_notificationLayout->setContentsMargins(20, 20, 20, 20); m_viewInternal->setLayout(m_notificationLayout); // this really is needed :) m_viewInternal->updateView(); doc->addView(this); setFocusProxy(m_viewInternal); setFocusPolicy(Qt::StrongFocus); setXMLFile(QStringLiteral("katepart5ui.rc")); setupConnections(); setupActions(); // auto word completion new KateWordCompletionView(this, actionCollection()); // update the enabled state of the undo/redo actions... slotUpdateUndo(); /** * create the status bar of this view * do this after action creation, we use some of them! */ toggleStatusBar(); m_startingUp = false; updateConfig(); slotHlChanged(); KCursor::setAutoHideCursor(m_viewInternal, true); for (auto messageWidget : m_messageWidgets) { if (messageWidget) { // user interaction (scrolling) starts notification auto-hide timer connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), messageWidget, SLOT(startAutoHideTimer())); // user interaction (cursor navigation) starts notification auto-hide timer connect(this, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), messageWidget, SLOT(startAutoHideTimer())); } } // folding restoration on reload connect(m_doc, SIGNAL(aboutToReload(KTextEditor::Document*)), SLOT(saveFoldingState())); connect(m_doc, SIGNAL(reloaded(KTextEditor::Document*)), SLOT(applyFoldingState())); // update highlights on scrolling and co connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), this, SLOT(createHighlights())); // clear highlights on reload connect(m_doc, SIGNAL(aboutToReload(KTextEditor::Document*)), SLOT(clearHighlights())); // setup layout setupLayout(); } KTextEditor::ViewPrivate::~ViewPrivate() { // invalidate update signal m_delayedUpdateTriggered = false; // remove from xmlgui factory, to be safe if (factory()) { factory()->removeClient(this); } // delete internal view before view bar! delete m_viewInternal; /** * remove view bar again, if needed */ m_mainWindow->deleteViewBar(this); m_bottomViewBar = nullptr; m_doc->removeView(this); delete m_renderer; delete m_config; KTextEditor::EditorPrivate::self()->deregisterView(this); } void KTextEditor::ViewPrivate::toggleStatusBar() { /** * if there, delete it */ if (m_statusBar) { bottomViewBar()->removePermanentBarWidget(m_statusBar); delete m_statusBar; m_statusBar = nullptr; emit statusBarEnabledChanged(this, false); return; } /** * else: create it */ m_statusBar = new KateStatusBar(this); bottomViewBar()->addPermanentBarWidget(m_statusBar); emit statusBarEnabledChanged(this, true); } void KTextEditor::ViewPrivate::setupLayout() { // delete old layout if any if (layout()) { delete layout(); /** * need to recreate spacers because they are deleted with * their belonging layout */ m_topSpacer = new QSpacerItem(0,0); m_leftSpacer = new QSpacerItem(0,0); m_rightSpacer = new QSpacerItem(0,0); m_bottomSpacer = new QSpacerItem(0,0); } // set margins QStyleOptionFrame opt; opt.initFrom(this); opt.frameShape = QFrame::StyledPanel; opt.state |= QStyle::State_Sunken; const int margin = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, this); m_topSpacer->changeSize(0, margin, QSizePolicy::Minimum, QSizePolicy::Fixed); m_leftSpacer->changeSize(margin, 0, QSizePolicy::Fixed, QSizePolicy::Minimum); m_rightSpacer->changeSize(margin, 0, QSizePolicy::Fixed, QSizePolicy::Minimum); m_bottomSpacer->changeSize(0, margin, QSizePolicy::Minimum, QSizePolicy::Fixed); // define layout QGridLayout *layout=new QGridLayout(this); layout->setMargin(0); layout->setSpacing(0); const bool frameAroundContents = style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, &opt, this); if (frameAroundContents) { // top message widget layout->addWidget(m_messageWidgets[KTextEditor::Message::AboveView], 0, 0, 1, 5); // top spacer layout->addItem(m_topSpacer, 1, 0, 1, 4); // left spacer layout->addItem(m_leftSpacer, 2, 0, 1, 1); // left border layout->addWidget(m_viewInternal->m_leftBorder, 2, 1, 1, 1); // view layout->addWidget(m_viewInternal, 2, 2, 1, 1); // right spacer layout->addItem(m_rightSpacer, 2, 3, 1, 1); // bottom spacer layout->addItem(m_bottomSpacer, 3, 0, 1, 4); // vertical scrollbar layout->addWidget(m_viewInternal->m_lineScroll, 1, 4, 3, 1); // horizontal scrollbar layout->addWidget(m_viewInternal->m_columnScroll, 4, 0, 1, 4); // dummy layout->addWidget(m_viewInternal->m_dummy, 4, 4, 1, 1); // bottom message layout->addWidget(m_messageWidgets[KTextEditor::Message::BelowView], 5, 0, 1, 5); // bottom viewbar if (m_bottomViewBar->parentWidget() == this) { layout->addWidget(m_bottomViewBar, 6, 0, 1, 5); } // stretch layout->setColumnStretch(2, 1); layout->setRowStretch(2, 1); // adjust scrollbar background m_viewInternal->m_lineScroll->setBackgroundRole(QPalette::Window); m_viewInternal->m_lineScroll->setAutoFillBackground(false); m_viewInternal->m_columnScroll->setBackgroundRole(QPalette::Window); m_viewInternal->m_columnScroll->setAutoFillBackground(false); } else { // top message widget layout->addWidget(m_messageWidgets[KTextEditor::Message::AboveView], 0, 0, 1, 5); // top spacer layout->addItem(m_topSpacer, 1, 0, 1, 5); // left spacer layout->addItem(m_leftSpacer, 2, 0, 1, 1); // left border layout->addWidget(m_viewInternal->m_leftBorder, 2, 1, 1, 1); // view layout->addWidget(m_viewInternal, 2, 2, 1, 1); // vertical scrollbar layout->addWidget(m_viewInternal->m_lineScroll, 2, 3, 1, 1); // right spacer layout->addItem(m_rightSpacer, 2, 4, 1, 1); // horizontal scrollbar layout->addWidget(m_viewInternal->m_columnScroll, 3, 1, 1, 2); // dummy layout->addWidget(m_viewInternal->m_dummy, 3, 3, 1, 1); // bottom spacer layout->addItem(m_bottomSpacer, 4, 0, 1, 5); // bottom message layout->addWidget(m_messageWidgets[KTextEditor::Message::BelowView], 5, 0, 1, 5); // bottom viewbar if (m_bottomViewBar->parentWidget() == this) { layout->addWidget(m_bottomViewBar, 6, 0, 1, 5); } // stretch layout->setColumnStretch(2, 1); layout->setRowStretch(2, 1); // adjust scrollbar background m_viewInternal->m_lineScroll->setBackgroundRole(QPalette::Base); m_viewInternal->m_lineScroll->setAutoFillBackground(true); m_viewInternal->m_columnScroll->setBackgroundRole(QPalette::Base); m_viewInternal->m_columnScroll->setAutoFillBackground(true); } } void KTextEditor::ViewPrivate::setupConnections() { connect(m_doc, SIGNAL(undoChanged()), this, SLOT(slotUpdateUndo())); connect(m_doc, SIGNAL(highlightingModeChanged(KTextEditor::Document*)), this, SLOT(slotHlChanged())); connect(m_doc, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); connect(m_viewInternal, SIGNAL(dropEventPass(QDropEvent*)), this, SIGNAL(dropEventPass(QDropEvent*))); connect(m_doc, SIGNAL(annotationModelChanged(KTextEditor::AnnotationModel*,KTextEditor::AnnotationModel*)), m_viewInternal->m_leftBorder, SLOT(annotationModelChanged(KTextEditor::AnnotationModel*,KTextEditor::AnnotationModel*))); } void KTextEditor::ViewPrivate::goToPreviousEditingPosition() { auto c = doc()->lastEditingPosition(KTextEditor::DocumentPrivate::Previous, cursorPosition()); if(c.isValid()){ setCursorPosition(c); } } void KTextEditor::ViewPrivate::goToNextEditingPosition() { auto c = doc()->lastEditingPosition(KTextEditor::DocumentPrivate::Next, cursorPosition()); if(c.isValid()){ setCursorPosition(c); } } void KTextEditor::ViewPrivate::setupActions() { KActionCollection *ac = actionCollection(); QAction *a; m_toggleWriteLock = nullptr; m_cut = a = ac->addAction(KStandardAction::Cut, this, SLOT(cut())); a->setWhatsThis(i18n("Cut the selected text and move it to the clipboard")); m_paste = a = ac->addAction(KStandardAction::Paste, this, SLOT(paste())); a->setWhatsThis(i18n("Paste previously copied or cut clipboard contents")); m_copy = a = ac->addAction(KStandardAction::Copy, this, SLOT(copy())); a->setWhatsThis(i18n("Use this command to copy the currently selected text to the system clipboard.")); m_pasteMenu = ac->addAction(QStringLiteral("edit_paste_menu"), new KatePasteMenu(i18n("Clipboard &History"), this)); connect(KTextEditor::EditorPrivate::self(), SIGNAL(clipboardHistoryChanged()), this, SLOT(slotClipboardHistoryChanged())); if (!m_doc->readOnly()) { a = ac->addAction(KStandardAction::Save, m_doc, SLOT(documentSave())); a->setWhatsThis(i18n("Save the current document")); a = m_editUndo = ac->addAction(KStandardAction::Undo, m_doc, SLOT(undo())); a->setWhatsThis(i18n("Revert the most recent editing actions")); a = m_editRedo = ac->addAction(KStandardAction::Redo, m_doc, SLOT(redo())); a->setWhatsThis(i18n("Revert the most recent undo operation")); // Tools > Scripts // stored inside scoped pointer to ensure we destruct it early enough as it does internal cleanups of other child objects m_scriptActionMenu.reset(new KateScriptActionMenu(this, i18n("&Scripts"))); ac->addAction(QStringLiteral("tools_scripts"), m_scriptActionMenu.data()); a = ac->addAction(QStringLiteral("tools_apply_wordwrap")); a->setText(i18n("Apply &Word Wrap")); a->setWhatsThis(i18n("Use this command to wrap all lines of the current document which are longer than the width of the" " current view, to fit into this view.

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

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

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

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

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

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

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

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

" "You can configure whether tabs should be honored and used or replaced with spaces, in the configuration dialog.")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_I)); connect(a, SIGNAL(triggered(bool)), SLOT(indent())); a = ac->addAction(QStringLiteral("tools_unindent")); a->setIcon(QIcon::fromTheme(QStringLiteral("format-indent-less"))); a->setText(i18n("&Unindent")); a->setWhatsThis(i18n("Use this to unindent a selected block of text.")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_I)); connect(a, SIGNAL(triggered(bool)), SLOT(unIndent())); } if (hasFocus()) { slotGotFocus(); } else { slotLostFocus(); } } void KTextEditor::ViewPrivate::setSecondaryCursorsFrozen(bool freeze) { cursors()->setSecondaryFrozen(freeze); auto a = actionCollection()->action(QStringLiteral("freeze_secondary_cursors")); Q_ASSERT(a); if ( !a ) return; if ( a->isChecked() != freeze ) { a->blockSignals(true); a->setChecked(freeze); a->blockSignals(false); } } void KTextEditor::ViewPrivate::placeSecondaryCursor() { cursors()->toggleSecondaryCursorAt(cursors()->primaryCursor()); setSecondaryCursorsFrozen(true); } void KTextEditor::ViewPrivate::setupCodeFolding() { KActionCollection *ac = this->actionCollection(); QAction *a; a = ac->addAction(QStringLiteral("folding_toplevel")); a->setText(i18n("Fold Toplevel Nodes")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Minus)); connect(a, SIGNAL(triggered(bool)), SLOT(slotFoldToplevelNodes())); a = ac->addAction(QLatin1String("folding_expandtoplevel")); a->setText(i18n("Unfold Toplevel Nodes")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_Plus)); connect(a, SIGNAL(triggered(bool)), SLOT(slotExpandToplevelNodes())); /*a = ac->addAction(QLatin1String("folding_expandall")); a->setText(i18n("Unfold All Nodes")); connect(a, SIGNAL(triggered(bool)), m_doc->foldingTree(), SLOT(expandAll())); a = ac->addAction(QLatin1String("folding_collapse_dsComment")); a->setText(i18n("Fold Multiline Comments")); connect(a, SIGNAL(triggered(bool)), m_doc->foldingTree(), SLOT(collapseAll_dsComments())); */ a = ac->addAction(QStringLiteral("folding_collapselocal")); a->setText(i18n("Fold Current Node")); connect(a, SIGNAL(triggered(bool)), SLOT(slotCollapseLocal())); a = ac->addAction(QStringLiteral("folding_expandlocal")); a->setText(i18n("Unfold Current Node")); connect(a, SIGNAL(triggered(bool)), SLOT(slotExpandLocal())); } void KTextEditor::ViewPrivate::slotFoldToplevelNodes() { for (int line = 0; line < doc()->lines(); ++line) { if (textFolding().isLineVisible(line)) { foldLine(line); } } } void KTextEditor::ViewPrivate::slotExpandToplevelNodes() { const auto topLevelRanges(textFolding().foldingRangesForParentRange()); for (const auto &range : topLevelRanges) { textFolding().unfoldRange(range.first); } } void KTextEditor::ViewPrivate::slotCollapseLocal() { foldLine(cursorPosition().line()); } void KTextEditor::ViewPrivate::slotExpandLocal() { unfoldLine(cursorPosition().line()); } void KTextEditor::ViewPrivate::foldLine(int startLine) { // only for valid lines if (startLine < 0 || startLine >= doc()->buffer().lines()) { return; } // try to fold all known ranges QVector > startingRanges = textFolding().foldingRangesStartingOnLine(startLine); for (int i = 0; i < startingRanges.size(); ++i) { textFolding().foldRange(startingRanges[i].first); } // try if the highlighting can help us and create a fold textFolding().newFoldingRange(doc()->buffer().computeFoldingRangeForStartLine(startLine), Kate::TextFolding::Folded); } void KTextEditor::ViewPrivate::unfoldLine(int startLine) { // only for valid lines if (startLine < 0 || startLine >= doc()->buffer().lines()) { return; } // try to unfold all known ranges QVector > startingRanges = textFolding().foldingRangesStartingOnLine(startLine); for (int i = 0; i < startingRanges.size(); ++i) { textFolding().unfoldRange(startingRanges[i].first); } } KTextEditor::View::ViewMode KTextEditor::ViewPrivate::viewMode() const { return currentInputMode()->viewMode(); } QString KTextEditor::ViewPrivate::viewModeHuman() const { QString currentMode = currentInputMode()->viewModeHuman(); /** * append read-only if needed */ if (!m_doc->isReadWrite()) { currentMode = i18n("(R/O) %1", currentMode); } /** * return full mode */ return currentMode; } KTextEditor::View::InputMode KTextEditor::ViewPrivate::viewInputMode() const { return currentInputMode()->viewInputMode(); } QString KTextEditor::ViewPrivate::viewInputModeHuman() const { return currentInputMode()->viewInputModeHuman(); } void KTextEditor::ViewPrivate::setInputMode(KTextEditor::View::InputMode mode) { if (currentInputMode()->viewInputMode() == mode) { return; } if (!m_viewInternal->m_inputModes.contains(mode)) { return; } m_viewInternal->m_currentInputMode->deactivate(); m_viewInternal->m_currentInputMode = m_viewInternal->m_inputModes[mode]; m_viewInternal->m_currentInputMode->activate(); config()->setInputMode(mode); // TODO: this could be called from read config procedure, so it's not a good idea to set a specific view mode here /* small duplication, but need to do this if not toggled by action */ Q_FOREACH(QAction *action, m_inputModeActions->actions()) { if (static_cast(action->data().toInt()) == mode) { action->setChecked(true); break; } } /* inform the rest of the system about the change */ emit viewInputModeChanged(this, mode); emit viewModeChanged(this, viewMode()); } void KTextEditor::ViewPrivate::slotGotFocus() { //qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::slotGotFocus"; currentInputMode()->gotFocus(); /** * update current view and scrollbars * it is needed for styles that implement different frame and scrollbar * rendering when focused */ update(); if (m_viewInternal->m_lineScroll->isVisible()) { m_viewInternal->m_lineScroll->update(); } if (m_viewInternal->m_columnScroll->isVisible()) { m_viewInternal->m_columnScroll->update(); } emit focusIn(this); } void KTextEditor::ViewPrivate::slotLostFocus() { //qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::slotLostFocus"; currentInputMode()->lostFocus(); /** * update current view and scrollbars * it is needed for styles that implement different frame and scrollbar * rendering when focused */ update(); if (m_viewInternal->m_lineScroll->isVisible()) { m_viewInternal->m_lineScroll->update(); } if (m_viewInternal->m_columnScroll->isVisible()) { m_viewInternal->m_columnScroll->update(); } emit focusOut(this); } void KTextEditor::ViewPrivate::setDynWrapIndicators(int mode) { config()->setDynWordWrapIndicators(mode); } bool KTextEditor::ViewPrivate::isOverwriteMode() const { return m_doc->config()->ovr(); } void KTextEditor::ViewPrivate::reloadFile() { // bookmarks and cursor positions are temporarily saved by the document m_doc->documentReload(); } void KTextEditor::ViewPrivate::slotReadWriteChanged() { if (m_toggleWriteLock) { m_toggleWriteLock->setChecked(! m_doc->isReadWrite()); } m_cut->setEnabled(m_doc->isReadWrite() && (selection() || m_config->smartCopyCut())); m_paste->setEnabled(m_doc->isReadWrite()); m_pasteMenu->setEnabled(m_doc->isReadWrite() && !KTextEditor::EditorPrivate::self()->clipboardHistory().isEmpty()); m_setEndOfLine->setEnabled(m_doc->isReadWrite()); - static const QStringList l { + static const auto l = { QStringLiteral("edit_replace") , QStringLiteral("tools_spelling") , QStringLiteral("tools_indent") , QStringLiteral("tools_unindent") , QStringLiteral("tools_cleanIndent") , QStringLiteral("tools_align") , QStringLiteral("tools_comment") , QStringLiteral("tools_uncomment") , QStringLiteral("tools_toggle_comment") , QStringLiteral("tools_uppercase") , QStringLiteral("tools_lowercase") , QStringLiteral("tools_capitalize") , QStringLiteral("tools_join_lines") , QStringLiteral("tools_apply_wordwrap") , QStringLiteral("tools_spelling_from_cursor") , QStringLiteral("tools_spelling_selection") }; - foreach (const QString &action, l) { + foreach (const auto &action, l) { QAction *a = actionCollection()->action(action); if (a) { a->setEnabled(m_doc->isReadWrite()); } } slotUpdateUndo(); currentInputMode()->readWriteChanged(m_doc->isReadWrite()); // => view mode changed emit viewModeChanged(this, viewMode()); emit viewInputModeChanged(this, viewInputMode()); } void KTextEditor::ViewPrivate::slotClipboardHistoryChanged() { m_pasteMenu->setEnabled(m_doc->isReadWrite() && !KTextEditor::EditorPrivate::self()->clipboardHistory().isEmpty()); } void KTextEditor::ViewPrivate::slotUpdateUndo() { if (m_doc->readOnly()) { return; } m_editUndo->setEnabled(m_doc->isReadWrite() && m_doc->undoCount() > 0); m_editRedo->setEnabled(m_doc->isReadWrite() && m_doc->redoCount() > 0); } bool KTextEditor::ViewPrivate::setCursorPositionInternal(const KTextEditor::Cursor &position, uint tabwidth, bool calledExternally) { Kate::TextLine l = m_doc->kateTextLine(position.line()); if (!l) { return false; } QString line_str = m_doc->line(position.line()); int x = 0; int z = 0; for (; z < line_str.length() && z < position.column(); z++) { if (line_str[z] == QLatin1Char('\t')) { x += tabwidth - (x % tabwidth); } else { x++; } } if (blockSelection()) { if (z < position.column()) { x += position.column() - z; } } auto cur = KTextEditor::Cursor(position.line(), x); m_viewInternal->cursors()->setPrimaryCursor(cur, false); m_viewInternal->notifyPrimaryCursorChanged(cur, false, true, calledExternally); return true; } void KTextEditor::ViewPrivate::toggleInsert() { m_doc->config()->setOvr(!m_doc->config()->ovr()); m_toggleInsert->setChecked(isOverwriteMode()); emit viewModeChanged(this, viewMode()); emit viewInputModeChanged(this, viewInputMode()); } void KTextEditor::ViewPrivate::slotSaveCanceled(const QString &error) { if (!error.isEmpty()) { // happens when canceling a job KMessageBox::error(this, error); } } void KTextEditor::ViewPrivate::gotoLine() { gotoBar()->updateData(); bottomViewBar()->showBarWidget(gotoBar()); } void KTextEditor::ViewPrivate::changeDictionary() { dictionaryBar()->updateData(); bottomViewBar()->showBarWidget(dictionaryBar()); } void KTextEditor::ViewPrivate::joinLines() { int first = selectionRange().start().line(); int last = selectionRange().end().line(); //int left = m_doc->line( last ).length() - m_doc->selEndCol(); if (first == last) { first = cursorPosition().line(); last = first + 1; } m_doc->joinLines(first, last); } void KTextEditor::ViewPrivate::readSessionConfig(const KConfigGroup &config, const QSet &flags) { Q_UNUSED(flags) // cursor position setCursorPositionInternal(KTextEditor::Cursor(config.readEntry("CursorLine", 0), config.readEntry("CursorColumn", 0))); // restore dyn word wrap if set for this view if (config.hasKey("Dynamic Word Wrap")) { m_config->setDynWordWrap(config.readEntry("Dynamic Word Wrap", false)); } // restore text folding m_savedFoldingState = QJsonDocument::fromJson(config.readEntry("TextFolding", QByteArray())); applyFoldingState(); Q_FOREACH(KateAbstractInputMode *mode, m_viewInternal->m_inputModes) { mode->readSessionConfig(config); } } void KTextEditor::ViewPrivate::writeSessionConfig(KConfigGroup &config, const QSet &flags) { Q_UNUSED(flags) // cursor position #warning TODO multicursor config.writeEntry("CursorLine", m_viewInternal->primaryCursor().line()); config.writeEntry("CursorColumn", m_viewInternal->primaryCursor().column()); // save dyn word wrap if set for this view if (m_config->dynWordWrapSet()) { config.writeEntry("Dynamic Word Wrap", m_config->dynWordWrap()); } // save text folding state saveFoldingState(); config.writeEntry("TextFolding", m_savedFoldingState.toJson(QJsonDocument::Compact)); m_savedFoldingState = QJsonDocument(); Q_FOREACH(KateAbstractInputMode *mode, m_viewInternal->m_inputModes) { mode->writeSessionConfig(config); } } int KTextEditor::ViewPrivate::getEol() const { return m_doc->config()->eol(); } void KTextEditor::ViewPrivate::setEol(int eol) { if (!doc()->isReadWrite()) { return; } if (m_updatingDocumentConfig) { return; } if (eol != m_doc->config()->eol()) { m_doc->setModified(true); // mark modified (bug #143120) m_doc->config()->setEol(eol); } } void KTextEditor::ViewPrivate::setAddBom(bool enabled) { if (!doc()->isReadWrite()) { return; } if (m_updatingDocumentConfig) { return; } m_doc->config()->setBom(enabled); m_doc->bomSetByUser(); } void KTextEditor::ViewPrivate::setIconBorder(bool enable) { config()->setIconBar(enable); } void KTextEditor::ViewPrivate::toggleIconBorder() { config()->setIconBar(!config()->iconBar()); } void KTextEditor::ViewPrivate::setLineNumbersOn(bool enable) { config()->setLineNumbers(enable); } void KTextEditor::ViewPrivate::toggleLineNumbersOn() { config()->setLineNumbers(!config()->lineNumbers()); } void KTextEditor::ViewPrivate::setScrollBarMarks(bool enable) { config()->setScrollBarMarks(enable); } void KTextEditor::ViewPrivate::toggleScrollBarMarks() { config()->setScrollBarMarks(!config()->scrollBarMarks()); } void KTextEditor::ViewPrivate::setScrollBarMiniMap(bool enable) { config()->setScrollBarMiniMap(enable); } void KTextEditor::ViewPrivate::toggleScrollBarMiniMap() { config()->setScrollBarMiniMap(!config()->scrollBarMiniMap()); } void KTextEditor::ViewPrivate::setScrollBarMiniMapAll(bool enable) { config()->setScrollBarMiniMapAll(enable); } void KTextEditor::ViewPrivate::toggleScrollBarMiniMapAll() { config()->setScrollBarMiniMapAll(!config()->scrollBarMiniMapAll()); } void KTextEditor::ViewPrivate::setScrollBarMiniMapWidth(int width) { config()->setScrollBarMiniMapWidth(width); } void KTextEditor::ViewPrivate::toggleDynWordWrap() { config()->setDynWordWrap(!config()->dynWordWrap()); } void KTextEditor::ViewPrivate::toggleWWMarker() { m_renderer->config()->setWordWrapMarker(!m_renderer->config()->wordWrapMarker()); } void KTextEditor::ViewPrivate::toggleNPSpaces() { m_renderer->setShowNonPrintableSpaces(!m_renderer->showNonPrintableSpaces()); m_viewInternal->update(); // force redraw } void KTextEditor::ViewPrivate::toggleWordCount(bool on) { config()->setShowWordCount(on); } void KTextEditor::ViewPrivate::setFoldingMarkersOn(bool enable) { config()->setFoldingBar(enable); } void KTextEditor::ViewPrivate::toggleFoldingMarkers() { config()->setFoldingBar(!config()->foldingBar()); } bool KTextEditor::ViewPrivate::iconBorder() { return m_viewInternal->m_leftBorder->iconBorderOn(); } bool KTextEditor::ViewPrivate::lineNumbersOn() { return m_viewInternal->m_leftBorder->lineNumbersOn(); } bool KTextEditor::ViewPrivate::scrollBarMarks() { return m_viewInternal->m_lineScroll->showMarks(); } bool KTextEditor::ViewPrivate::scrollBarMiniMap() { return m_viewInternal->m_lineScroll->showMiniMap(); } int KTextEditor::ViewPrivate::dynWrapIndicators() { return m_viewInternal->m_leftBorder->dynWrapIndicators(); } bool KTextEditor::ViewPrivate::foldingMarkersOn() { return m_viewInternal->m_leftBorder->foldingMarkersOn(); } void KTextEditor::ViewPrivate::toggleWriteLock() { m_doc->setReadWrite(! m_doc->isReadWrite()); } void KTextEditor::ViewPrivate::registerTextHintProvider(KTextEditor::TextHintProvider *provider) { m_viewInternal->registerTextHintProvider(provider); } void KTextEditor::ViewPrivate::unregisterTextHintProvider(KTextEditor::TextHintProvider *provider) { m_viewInternal->unregisterTextHintProvider(provider); } void KTextEditor::ViewPrivate::setTextHintDelay(int delay) { m_viewInternal->setTextHintDelay(delay); } int KTextEditor::ViewPrivate::textHintDelay() const { return m_viewInternal->textHintDelay(); } void KTextEditor::ViewPrivate::find() { currentInputMode()->find(); } void KTextEditor::ViewPrivate::findSelectedForwards() { currentInputMode()->findSelectedForwards(); } void KTextEditor::ViewPrivate::findSelectedBackwards() { currentInputMode()->findSelectedBackwards(); } void KTextEditor::ViewPrivate::replace() { currentInputMode()->findReplace(); } void KTextEditor::ViewPrivate::findNext() { currentInputMode()->findNext(); } void KTextEditor::ViewPrivate::findPrevious() { currentInputMode()->findPrevious(); } void KTextEditor::ViewPrivate::slotSelectionChanged() { m_copy->setEnabled(selection() || m_config->smartCopyCut()); m_deSelect->setEnabled(selection()); m_copyHtmlAction->setEnabled (selection()); // update highlighting of current selected word selectionChangedForHighlights (); if (m_doc->readOnly()) { return; } m_cut->setEnabled(selection() || m_config->smartCopyCut()); m_spell->updateActions(); } void KTextEditor::ViewPrivate::switchToCmdLine() { currentInputMode()->activateCommandLine(); } KateRenderer *KTextEditor::ViewPrivate::renderer() { return m_renderer; } void KTextEditor::ViewPrivate::updateConfig() { if (m_startingUp) { return; } // dyn. word wrap & markers if (m_hasWrap != config()->dynWordWrap()) { m_viewInternal->prepareForDynWrapChange(); m_hasWrap = config()->dynWordWrap(); m_viewInternal->dynWrapChanged(); m_setDynWrapIndicators->setEnabled(config()->dynWordWrap()); m_toggleDynWrap->setChecked(config()->dynWordWrap()); } m_viewInternal->m_leftBorder->setDynWrapIndicators(config()->dynWordWrapIndicators()); m_setDynWrapIndicators->setCurrentItem(config()->dynWordWrapIndicators()); // line numbers m_viewInternal->m_leftBorder->setLineNumbersOn(config()->lineNumbers()); m_toggleLineNumbers->setChecked(config()->lineNumbers()); // icon bar m_viewInternal->m_leftBorder->setIconBorderOn(config()->iconBar()); m_toggleIconBar->setChecked(config()->iconBar()); // scrollbar marks m_viewInternal->m_lineScroll->setShowMarks(config()->scrollBarMarks()); m_toggleScrollBarMarks->setChecked(config()->scrollBarMarks()); // scrollbar mini-map m_viewInternal->m_lineScroll->setShowMiniMap(config()->scrollBarMiniMap()); m_toggleScrollBarMiniMap->setChecked(config()->scrollBarMiniMap()); // scrollbar mini-map - (whole document) m_viewInternal->m_lineScroll->setMiniMapAll(config()->scrollBarMiniMapAll()); //m_toggleScrollBarMiniMapAll->setChecked( config()->scrollBarMiniMapAll() ); // scrollbar mini-map.width m_viewInternal->m_lineScroll->setMiniMapWidth(config()->scrollBarMiniMapWidth()); // misc edit m_toggleBlockSelection->setChecked(blockSelection()); m_toggleInsert->setChecked(isOverwriteMode()); updateFoldingConfig(); // bookmark m_bookmarks->setSorting((KateBookmarks::Sorting) config()->bookmarkSort()); m_viewInternal->setAutoCenterLines(config()->autoCenterLines()); Q_FOREACH(KateAbstractInputMode *input, m_viewInternal->m_inputModes) { input->updateConfig(); } setInputMode(config()->inputMode()); reflectOnTheFlySpellCheckStatus(m_doc->isOnTheFlySpellCheckingEnabled()); // register/unregister word completion... bool wc = config()->wordCompletion(); if (wc != isCompletionModelRegistered(KTextEditor::EditorPrivate::self()->wordCompletionModel())) { if (wc) registerCompletionModel(KTextEditor::EditorPrivate::self()->wordCompletionModel()); else unregisterCompletionModel(KTextEditor::EditorPrivate::self()->wordCompletionModel()); } bool kc = config()->keywordCompletion(); if (kc != isCompletionModelRegistered(KTextEditor::EditorPrivate::self()->keywordCompletionModel())) { if (kc) registerCompletionModel(KTextEditor::EditorPrivate::self()->keywordCompletionModel()); else unregisterCompletionModel (KTextEditor::EditorPrivate::self()->keywordCompletionModel()); } m_cut->setEnabled(m_doc->isReadWrite() && (selection() || m_config->smartCopyCut())); m_copy->setEnabled(selection() || m_config->smartCopyCut()); + // if not disabled, update status bar + if (m_statusBar) { + m_statusBar->updateStatus(); + } + // now redraw... m_viewInternal->cache()->clear(); tagAll(); updateView(true); emit configChanged(); } void KTextEditor::ViewPrivate::updateDocumentConfig() { if (m_startingUp) { return; } m_updatingDocumentConfig = true; m_setEndOfLine->setCurrentItem(m_doc->config()->eol()); m_addBom->setChecked(m_doc->config()->bom()); m_updatingDocumentConfig = false; // maybe block selection or wrap-cursor mode changed ensureCursorColumnValid(); // first change this m_renderer->setTabWidth(m_doc->config()->tabWidth()); m_renderer->setIndentWidth(m_doc->config()->indentationWidth()); // now redraw... m_viewInternal->cache()->clear(); tagAll(); updateView(true); } void KTextEditor::ViewPrivate::updateRendererConfig() { if (m_startingUp) { return; } m_toggleWWMarker->setChecked(m_renderer->config()->wordWrapMarker()); m_viewInternal->updateBracketMarkAttributes(); m_viewInternal->updateBracketMarks(); // now redraw... m_viewInternal->cache()->clear(); tagAll(); m_viewInternal->updateView(true); // update the left border right, for example linenumbers m_viewInternal->m_leftBorder->updateFont(); m_viewInternal->m_leftBorder->repaint(); m_viewInternal->m_lineScroll->queuePixmapUpdate(); currentInputMode()->updateRendererConfig(); // @@ showIndentLines is not cached anymore. // m_renderer->setShowIndentLines (m_renderer->config()->showIndentationLines()); emit configChanged(); } void KTextEditor::ViewPrivate::updateFoldingConfig() { // folding bar m_viewInternal->m_leftBorder->setFoldingMarkersOn(config()->foldingBar()); m_toggleFoldingMarkers->setChecked(config()->foldingBar()); if (hasCommentInFirstLine(m_doc)) { if (config()->foldFirstLine() && !m_autoFoldedFirstLine) { foldLine(0); m_autoFoldedFirstLine = true; } else if (!config()->foldFirstLine() && m_autoFoldedFirstLine) { unfoldLine(0); m_autoFoldedFirstLine = false; } } else { m_autoFoldedFirstLine = false; } #if 0 // FIXME: FOLDING - const QStringList l { + const QStringList l = { QStringLiteral("folding_toplevel") , QStringLiteral("folding_expandtoplevel") , QStringLiteral("folding_collapselocal") , QStringLiteral("folding_expandlocal") }; QAction *a = 0; for (int z = 0; z < l.size(); z++) if ((a = actionCollection()->action(l[z].toAscii().constData()))) { a->setEnabled(m_doc->highlight() && m_doc->highlight()->allowsFolding()); } #endif } void KTextEditor::ViewPrivate::ensureCursorColumnValid() { #warning TODO multicursor // KTextEditor::Cursor c = m_viewInternal->getCursor(); // // // make sure the cursor is valid: // // - in block selection mode or if wrap cursor is off, the column is arbitrary // // - otherwise: it's bounded by the line length // if (!blockSelection() && wrapCursor() // && (!c.isValid() || c.column() > m_doc->lineLength(c.line()))) { // c.setColumn(m_doc->kateTextLine(cursorPosition().line())->length()); // setCursorPosition(c); // } } //BEGIN EDIT STUFF void KTextEditor::ViewPrivate::editStart() { m_viewInternal->editStart(); } void KTextEditor::ViewPrivate::editEnd(int editTagLineStart, int editTagLineEnd, bool tagFrom) { m_viewInternal->editEnd(editTagLineStart, editTagLineEnd, tagFrom); } void KTextEditor::ViewPrivate::editSetCursor(const KTextEditor::Cursor &cursor) { m_viewInternal->editSetCursor(cursor); } //END //BEGIN TAG & CLEAR bool KTextEditor::ViewPrivate::tagLine(const KTextEditor::Cursor &virtualCursor) { return m_viewInternal->tagLine(virtualCursor); } bool KTextEditor::ViewPrivate::tagRange(const KTextEditor::Range &range, bool realLines) { return m_viewInternal->tagRange(range, realLines); } bool KTextEditor::ViewPrivate::tagLines(int start, int end, bool realLines) { return m_viewInternal->tagLines(start, end, realLines); } bool KTextEditor::ViewPrivate::tagLines(KTextEditor::Cursor start, KTextEditor::Cursor end, bool realCursors) { return m_viewInternal->tagLines(start, end, realCursors); } void KTextEditor::ViewPrivate::tagAll() { m_viewInternal->tagAll(); } void KTextEditor::ViewPrivate::clear() { m_viewInternal->clear(); } void KTextEditor::ViewPrivate::repaintText(bool paintOnlyDirty) { if (paintOnlyDirty) { m_viewInternal->updateDirty(); } else { m_viewInternal->update(); } } void KTextEditor::ViewPrivate::updateView(bool changed) { //qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::updateView"; m_viewInternal->updateView(changed); m_viewInternal->m_leftBorder->update(); } //END void KTextEditor::ViewPrivate::slotHlChanged() { KateHighlighting *hl = m_doc->highlight(); bool ok(!hl->getCommentStart(0).isEmpty() || !hl->getCommentSingleLineStart(0).isEmpty()); if (actionCollection()->action(QStringLiteral("tools_comment"))) { actionCollection()->action(QStringLiteral("tools_comment"))->setEnabled(ok); } if (actionCollection()->action(QStringLiteral("tools_uncomment"))) { actionCollection()->action(QStringLiteral("tools_uncomment"))->setEnabled(ok); } if (actionCollection()->action(QStringLiteral("tools_toggle_comment"))) { actionCollection()->action(QStringLiteral("tools_toggle_comment"))->setEnabled(ok); } // show folding bar if "view defaults" says so, otherwise enable/disable only the menu entry updateFoldingConfig(); } int KTextEditor::ViewPrivate::virtualCursorColumn() const { return m_doc->toVirtualColumn(m_viewInternal->primaryCursor()); } void KTextEditor::ViewPrivate::notifyMousePositionChanged(const KTextEditor::Cursor &newPosition) { emit mousePositionChanged(this, newPosition); } //BEGIN KTextEditor::SelectionInterface stuff bool KTextEditor::ViewPrivate::setSelection(const KTextEditor::Range &selection) { qDebug() << "called" << selection; /** * anything to do? */ if ( !selections()->hasMultipleSelections() && selection == primarySelection()) { return false; } /** * set new range; repainting is done by the selection manager * this also emits the selectionChanged() signal */ selections()->setSelection(selection); /** * be done */ return true; } bool KTextEditor::ViewPrivate::clearSelection() { return clearSelection(true); } bool KTextEditor::ViewPrivate::clearSelection(bool redraw, bool finishedChangingSelection) { /** * no selection, nothing to do... */ if (!selection()) { return false; } /** * do clear; this also emits the selectionChanged signal */ selections()->clearSelection(); /** * be done */ return true; } bool KTextEditor::ViewPrivate::selection() const { return selections()->hasSelections(); } QString KTextEditor::ViewPrivate::selectionText() const { return m_doc->text(primarySelection()); } bool KTextEditor::ViewPrivate::removeSelectedText() { if (!selection()) { return false; } m_doc->editStart(); auto sels = selections()->selections(); std::sort(sels.begin(), sels.end(), [](const Range& a, const Range& b) { return a > b; }); Q_FOREACH ( const auto& range, sels ) { m_doc->removeText(range); } // don't redraw the cleared selection - that's done in editEnd(). clearSelection(false); m_doc->editEnd(); return true; } bool KTextEditor::ViewPrivate::selectAll() { setBlockSelection(false); top(); shiftBottom(); return true; } bool KTextEditor::ViewPrivate::cursorSelected(const KTextEditor::Cursor &cursor) { return selections()->positionSelected(cursor); } bool KTextEditor::ViewPrivate::lineSelected(int line) { return selections()->lineSelected(line); } bool KTextEditor::ViewPrivate::lineEndSelected(const KTextEditor::Cursor &lineEndPos) { return selections()->lineEndSelected(lineEndPos); } bool KTextEditor::ViewPrivate::lineHasSelected(int line) { return selections()->lineHasSelection(line); } bool KTextEditor::ViewPrivate::lineIsSelection(int line) { #warning TODO fix this return ( line == primarySelection().start().line() && line == primarySelection().end().line()); } void KTextEditor::ViewPrivate::selectWord(const KTextEditor::Cursor &cursor) { setSelection(m_doc->wordRangeAt(cursor)); } void KTextEditor::ViewPrivate::selectLine(const KTextEditor::Cursor &cursor) { int line = cursor.line(); if (line + 1 >= m_doc->lines()) { setSelection(KTextEditor::Range(line, 0, line, m_doc->lineLength(line))); } else { setSelection(KTextEditor::Range(line, 0, line + 1, 0)); } } void KTextEditor::ViewPrivate::cut() { if (!selection() && !m_config->smartCopyCut()) { return; } copy(); #warning fixme: smart copy cut // if (!selection()) { // selectLine(m_viewInternal->primaryCursor()); // } removeSelectedText(); } void KTextEditor::ViewPrivate::copy() const { #warning fixme: smart copy cut // if (!selection()) { // if (!m_config->smartCopyCut()) { // return; // } // text = m_doc->line(m_viewInternal->primaryCursor().line()) + QLatin1Char('\n'); // m_viewInternal->cursors()->moveCursorsStartOfLine(); // } m_clipboard.copyToClipboard(); } void KTextEditor::ViewPrivate::applyWordWrap() { if (selection()) { m_doc->wrapText(selectionRange().start().line(), selectionRange().end().line()); } else { m_doc->wrapText(0, m_doc->lastLine()); } } //END //BEGIN KTextEditor::BlockSelectionInterface stuff bool KTextEditor::ViewPrivate::blockSelection() const { return false; } bool KTextEditor::ViewPrivate::setBlockSelection(bool /*on*/) { return toAlignedBlock(false); } bool KTextEditor::ViewPrivate::toggleBlockSelection() { auto blockSelect = selections()->hasMultipleSelections(); m_toggleBlockSelection->setChecked(!blockSelect); return setBlockSelection(!blockSelect); } bool KTextEditor::ViewPrivate::toAlignedBlock(bool fill) { auto blockSelect = !selections()->hasMultipleSelections(); if (selections()->hasSelections()) { auto s = selections()->selections(); auto blockStart = std::min_element(s.begin(), s.end(), [](const KTextEditor::Range& r1, const KTextEditor::Range& r2) { return r1.start() < r2.start(); } )->start(); auto blockEnd = std::max_element(s.begin(), s.end(), [](const KTextEditor::Range& r1, const KTextEditor::Range& r2) { return r1.end() < r2.end(); } )->end(); if (blockSelect) { if (fill) { Document::EditingTransaction tr(doc()); auto cursorColumn = blockEnd.column(); for ( int i = blockStart.line(); i <= blockEnd.line(); i++ ) { auto missing = cursorColumn - doc()->lineLength(i); if (missing > 0) { doc()->insertText({i, doc()->lineLength(i)}, QStringLiteral(" ").repeated(missing)); } } } selections()->setSelectionBlock({blockStart, blockEnd}, KateMultiCursor::Right); } else { selections()->setSelection({blockStart, blockEnd}); } } return true; } bool KTextEditor::ViewPrivate::wrapCursor() const { return !blockSelection(); } //END void KTextEditor::ViewPrivate::slotTextInserted(KTextEditor::View *view, const KTextEditor::Cursor &position, const QString &text) { emit textInserted(view, position, text); } bool KTextEditor::ViewPrivate::insertTemplateInternal(const KTextEditor::Cursor& c, const QString& templateString, const QString& script) { /** * no empty templates */ if (templateString.isEmpty()) { return false; } /** * not for read-only docs */ if (!m_doc->isReadWrite()) { return false; } /** * only one handler maybe active at a time; store it in the document. * Clear it first to make sure at no time two handlers are active at once */ doc()->setActiveTemplateHandler(nullptr); doc()->setActiveTemplateHandler(new KateTemplateHandler(this, c, templateString, script, m_doc->undoManager())); return true; } bool KTextEditor::ViewPrivate::tagLines(KTextEditor::Range range, bool realRange) { return tagLines(range.start(), range.end(), realRange); } void KTextEditor::ViewPrivate::deactivateEditActions() { foreach (QAction *action, m_editActions) { action->setEnabled(false); } } void KTextEditor::ViewPrivate::activateEditActions() { foreach (QAction *action, m_editActions) { action->setEnabled(true); } } bool KTextEditor::ViewPrivate::mouseTrackingEnabled() const { // FIXME support return true; } bool KTextEditor::ViewPrivate::setMouseTrackingEnabled(bool) { // FIXME support return true; } bool KTextEditor::ViewPrivate::isCompletionActive() const { return completionWidget()->isCompletionActive(); } KateCompletionWidget *KTextEditor::ViewPrivate::completionWidget() const { if (!m_completionWidget) { m_completionWidget = new KateCompletionWidget(const_cast(this)); } return m_completionWidget; } void KTextEditor::ViewPrivate::startCompletion(const KTextEditor::Range &word, KTextEditor::CodeCompletionModel *model) { completionWidget()->startCompletion(word, model); } void KTextEditor::ViewPrivate::abortCompletion() { completionWidget()->abortCompletion(); } void KTextEditor::ViewPrivate::forceCompletion() { completionWidget()->execute(); } void KTextEditor::ViewPrivate::registerCompletionModel(KTextEditor::CodeCompletionModel *model) { completionWidget()->registerCompletionModel(model); } void KTextEditor::ViewPrivate::unregisterCompletionModel(KTextEditor::CodeCompletionModel *model) { completionWidget()->unregisterCompletionModel(model); } bool KTextEditor::ViewPrivate::isCompletionModelRegistered(KTextEditor::CodeCompletionModel *model) const { return completionWidget()->isCompletionModelRegistered(model); } bool KTextEditor::ViewPrivate::isAutomaticInvocationEnabled() const { return !m_temporaryAutomaticInvocationDisabled && m_config->automaticCompletionInvocation(); } void KTextEditor::ViewPrivate::setAutomaticInvocationEnabled(bool enabled) { config()->setAutomaticCompletionInvocation(enabled); } void KTextEditor::ViewPrivate::sendCompletionExecuted(const KTextEditor::Cursor &position, KTextEditor::CodeCompletionModel *model, const QModelIndex &index) { emit completionExecuted(this, position, model, index); } void KTextEditor::ViewPrivate::sendCompletionAborted() { emit completionAborted(this); } void KTextEditor::ViewPrivate::paste() { m_temporaryAutomaticInvocationDisabled = true; m_clipboard.pasteFromClipboard(QClipboard::Clipboard); m_temporaryAutomaticInvocationDisabled = false; } void KTextEditor::ViewPrivate::pasteInternal(const QVector& texts) { m_clipboard.pasteVector(texts); } bool KTextEditor::ViewPrivate::setCursorPosition(KTextEditor::Cursor position) { return setCursorPositionInternal(position, 1, true); } KateMultiSelection* KTextEditor::ViewPrivate::selections() { return m_viewInternal->selections(); } const KateMultiSelection* KTextEditor::ViewPrivate::selections() const { return m_viewInternal->selections(); } bool KTextEditor::ViewPrivate::setSelections(const QVector& newSelections, const QVector& newCursors) { if ( !newCursors.isEmpty() && (newSelections.size() != newCursors.size()) ) { Q_ASSERT(false); qWarning() << "mismatching cursor/selection size"; return false; } if ( std::any_of(newCursors.begin(), newCursors.end(), [](const KTextEditor::Cursor& c) { return !c.isValid(); }) ) { return false; } if ( newSelections.isEmpty() ) { bool ret = selections()->hasSelections(); selections()->clearSelection(); return ret; } selections()->setSelection(newSelections, newCursors); return true; } bool KTextEditor::ViewPrivate::setCursorPositions(const QVector& positions) { if ( std::any_of(positions.begin(), positions.end(), [](const KTextEditor::Cursor& c) { return !c.isValid(); }) ) { return false; } if ( positions.isEmpty() ) { return false; } auto s = QVector(); s.resize(positions.size()); selections()->setSelection(s, positions); return true; } KTextEditor::Cursor KTextEditor::ViewPrivate::cursorPosition() const { return m_viewInternal->primaryCursor(); } QVector KTextEditor::ViewPrivate::cursorPositions() const { return cursors()->cursors(); } const KateMultiCursor* KTextEditor::ViewPrivate::cursors() const { return m_viewInternal->cursors(); } KTextEditor::Cursor KTextEditor::ViewPrivate::cursorPositionVirtual() const { return KTextEditor::Cursor(m_viewInternal->primaryCursor().line(), virtualCursorColumn()); } QPoint KTextEditor::ViewPrivate::cursorToCoordinate(const KTextEditor::Cursor &cursor) const { // map from ViewInternal to View coordinates const QPoint pt = m_viewInternal->cursorToCoordinate(cursor, true, false); return pt == QPoint(-1, -1) ? pt : m_viewInternal->mapToParent(pt); } KTextEditor::Cursor KTextEditor::ViewPrivate::coordinatesToCursor(const QPoint &coords) const { // map from View to ViewInternal coordinates return m_viewInternal->coordinatesToCursor(m_viewInternal->mapFromParent(coords), false); } QPoint KTextEditor::ViewPrivate::cursorPositionCoordinates() const { // map from ViewInternal to View coordinates const QPoint pt = m_viewInternal->cursorCoordinates(false); return pt == QPoint(-1, -1) ? pt : m_viewInternal->mapToParent(pt); } void KTextEditor::ViewPrivate::setScrollPositionInternal(KTextEditor::Cursor &cursor) { m_viewInternal->scrollPos(cursor, false, true, false); } void KTextEditor::ViewPrivate::setHorizontalScrollPositionInternal(int x) { m_viewInternal->scrollColumns(x); } KTextEditor::Cursor KTextEditor::ViewPrivate::maxScrollPositionInternal() const { return m_viewInternal->maxStartPos(true); } int KTextEditor::ViewPrivate::firstDisplayedLineInternal(LineType lineType) const { if (lineType == RealLine) { return m_textFolding.visibleLineToLine(m_viewInternal->startLine()); } else { return m_viewInternal->startLine(); } } int KTextEditor::ViewPrivate::lastDisplayedLineInternal(LineType lineType) const { if (lineType == RealLine) { return m_textFolding.visibleLineToLine(m_viewInternal->endLine()); } else { return m_viewInternal->endLine(); } } QRect KTextEditor::ViewPrivate::textAreaRectInternal() const { const auto sourceRect = m_viewInternal->rect(); const auto topLeft = m_viewInternal->mapTo(this, sourceRect.topLeft()); const auto bottomRight = m_viewInternal->mapTo(this, sourceRect.bottomRight()); return {topLeft, bottomRight}; } bool KTextEditor::ViewPrivate::setCursorPositionVisual(const KTextEditor::Cursor &position) { return setCursorPositionInternal(position, m_doc->config()->tabWidth(), true); } QString KTextEditor::ViewPrivate::currentTextLine() { return m_doc->line(cursorPosition().line()); } QTextLayout * KTextEditor::ViewPrivate::textLayout(int line) const { KateLineLayoutPtr thisLine = m_viewInternal->cache()->line(line); return thisLine->isValid() ? thisLine->layout() : nullptr; } QTextLayout * KTextEditor::ViewPrivate::textLayout(const KTextEditor::Cursor &pos) const { KateLineLayoutPtr thisLine = m_viewInternal->cache()->line(pos); return thisLine->isValid() ? thisLine->layout() : nullptr; } void KTextEditor::ViewPrivate::indent() { KTextEditor::Cursor c(cursorPosition().line(), 0); KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c); m_doc->indent(r, 1); } void KTextEditor::ViewPrivate::unIndent() { KTextEditor::Cursor c(cursorPosition().line(), 0); KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c); m_doc->indent(r, -1); } void KTextEditor::ViewPrivate::cleanIndent() { KTextEditor::Cursor c(cursorPosition().line(), 0); KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c); m_doc->indent(r, 0); } void KTextEditor::ViewPrivate::align() { // no selection: align current line; selection: use selection range const int line = cursorPosition().line(); KTextEditor::Range alignRange(KTextEditor::Cursor(line, 0), KTextEditor::Cursor(line, 0)); if (selection()) { alignRange = selectionRange(); } m_doc->align(this, alignRange); } void KTextEditor::ViewPrivate::comment() { #warning fixme // m_selection.setInsertBehaviors(Kate::TextRange::ExpandLeft | Kate::TextRange::ExpandRight); m_doc->comment(this, cursorPosition().line(), cursorPosition().column(), 1); // m_selection.setInsertBehaviors(Kate::TextRange::ExpandRight); } void KTextEditor::ViewPrivate::uncomment() { m_doc->comment(this, cursorPosition().line(), cursorPosition().column(), -1); } void KTextEditor::ViewPrivate::toggleComment() { #warning fixme // m_selection.setInsertBehaviors(Kate::TextRange::ExpandLeft | Kate::TextRange::ExpandRight); m_doc->comment(this, cursorPosition().line(), cursorPosition().column(), 0); // m_selection.setInsertBehaviors(Kate::TextRange::ExpandRight); } void KTextEditor::ViewPrivate::uppercase() { m_doc->transform(this, m_viewInternal->primaryCursor(), KTextEditor::DocumentPrivate::Uppercase); } void KTextEditor::ViewPrivate::killLine() { QSet lines; for ( const auto& cursor : cursors()->cursors() ) { auto selection = selections()->selectionForCursor(cursor)->toRange(); if ( selection.isValid() && !selection.isEmpty() ) { for ( int i = selection.start().line(); i <= selection.end().line(); i++ ) { lines.insert(i); } } else { lines.insert(cursor.line()); } } auto linesVector = lines.toList(); std::sort(linesVector.rbegin(), linesVector.rend()); m_doc->editStart(); for ( auto line : linesVector ) { m_doc->removeLine(line); } m_doc->editEnd(); } void KTextEditor::ViewPrivate::lowercase() { m_doc->transform(this, m_viewInternal->primaryCursor(), KTextEditor::DocumentPrivate::Lowercase); } void KTextEditor::ViewPrivate::capitalize() { m_doc->editStart(); m_doc->transform(this, m_viewInternal->primaryCursor(), KTextEditor::DocumentPrivate::Lowercase); m_doc->transform(this, m_viewInternal->primaryCursor(), KTextEditor::DocumentPrivate::Capitalize); m_doc->editEnd(); } void KTextEditor::ViewPrivate::keyReturn() { m_viewInternal->doReturn(); } void KTextEditor::ViewPrivate::smartNewline() { m_viewInternal->doSmartNewline(); } void KTextEditor::ViewPrivate::backspace() { m_viewInternal->doBackspace(); } void KTextEditor::ViewPrivate::insertTab() { m_viewInternal->doTabulator(); } void KTextEditor::ViewPrivate::deleteWordLeft() { m_viewInternal->doDeletePrevWord(); } void KTextEditor::ViewPrivate::keyDelete() { m_viewInternal->doDelete(); } void KTextEditor::ViewPrivate::deleteWordRight() { m_viewInternal->doDeleteNextWord(); } void KTextEditor::ViewPrivate::transpose() { m_viewInternal->doTranspose(); } void KTextEditor::ViewPrivate::cursorLeft() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->cursorNextChar(); } else { m_viewInternal->cursorPrevChar(); } } void KTextEditor::ViewPrivate::shiftCursorLeft() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->cursorNextChar(true); } else { m_viewInternal->cursorPrevChar(true); } } void KTextEditor::ViewPrivate::cursorRight() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->cursorPrevChar(); } else { m_viewInternal->cursorNextChar(); } } void KTextEditor::ViewPrivate::shiftCursorRight() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->cursorPrevChar(true); } else { m_viewInternal->cursorNextChar(true); } } void KTextEditor::ViewPrivate::wordLeft() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->wordNext(); } else { m_viewInternal->wordPrev(); } } void KTextEditor::ViewPrivate::shiftWordLeft() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->wordNext(true); } else { m_viewInternal->wordPrev(true); } } void KTextEditor::ViewPrivate::wordRight() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->wordPrev(); } else { m_viewInternal->wordNext(); } } void KTextEditor::ViewPrivate::shiftWordRight() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->wordPrev(true); } else { m_viewInternal->wordNext(true); } } void KTextEditor::ViewPrivate::home() { m_viewInternal->home(); } void KTextEditor::ViewPrivate::shiftHome() { m_viewInternal->home(true); } void KTextEditor::ViewPrivate::end() { m_viewInternal->end(); } void KTextEditor::ViewPrivate::shiftEnd() { m_viewInternal->end(true); } void KTextEditor::ViewPrivate::up() { m_viewInternal->cursorUp(); } void KTextEditor::ViewPrivate::shiftUp() { m_viewInternal->cursorUp(true); } void KTextEditor::ViewPrivate::down() { m_viewInternal->cursorDown(); } void KTextEditor::ViewPrivate::shiftDown() { m_viewInternal->cursorDown(true); } void KTextEditor::ViewPrivate::scrollUp() { m_viewInternal->scrollUp(); } void KTextEditor::ViewPrivate::scrollDown() { m_viewInternal->scrollDown(); } void KTextEditor::ViewPrivate::topOfView() { m_viewInternal->topOfView(); } void KTextEditor::ViewPrivate::shiftTopOfView() { m_viewInternal->topOfView(true); } void KTextEditor::ViewPrivate::bottomOfView() { m_viewInternal->bottomOfView(); } void KTextEditor::ViewPrivate::shiftBottomOfView() { m_viewInternal->bottomOfView(true); } void KTextEditor::ViewPrivate::pageUp() { m_viewInternal->pageUp(); } void KTextEditor::ViewPrivate::shiftPageUp() { m_viewInternal->pageUp(true); } void KTextEditor::ViewPrivate::pageDown() { m_viewInternal->pageDown(); } void KTextEditor::ViewPrivate::shiftPageDown() { m_viewInternal->pageDown(true); } void KTextEditor::ViewPrivate::top() { m_viewInternal->top_home(); } void KTextEditor::ViewPrivate::shiftTop() { m_viewInternal->top_home(true); } void KTextEditor::ViewPrivate::bottom() { m_viewInternal->bottom_end(); } void KTextEditor::ViewPrivate::shiftBottom() { m_viewInternal->bottom_end(true); } void KTextEditor::ViewPrivate::toMatchingBracket() { cursors()->clearSecondaryCursors(); m_viewInternal->cursorToMatchingBracket(); } void KTextEditor::ViewPrivate::shiftToMatchingBracket() { m_viewInternal->cursorToMatchingBracket(true); } void KTextEditor::ViewPrivate::toPrevModifiedLine() { const int startLine = m_viewInternal->primaryCursor().line() - 1; const int line = m_doc->findTouchedLine(startLine, false); if (line >= 0) { KTextEditor::Cursor c(line, 0); m_viewInternal->updateSelection(c, false); m_viewInternal->cursors()->setPrimaryCursor(c); } } void KTextEditor::ViewPrivate::toNextModifiedLine() { const int startLine = m_viewInternal->primaryCursor().line() + 1; const int line = m_doc->findTouchedLine(startLine, true); if (line >= 0) { KTextEditor::Cursor c(line, 0); m_viewInternal->updateSelection(c, false); m_viewInternal->cursors()->setPrimaryCursor(c); } } KTextEditor::Range KTextEditor::ViewPrivate::selectionRange() const { return primarySelection(); } QVector KTextEditor::ViewPrivate::selectionRanges() const { return selections()->selections(); } KTextEditor::Document *KTextEditor::ViewPrivate::document() const { return m_doc; } void KTextEditor::ViewPrivate::setContextMenu(QMenu *menu) { if (m_contextMenu) { disconnect(m_contextMenu, SIGNAL(aboutToShow()), this, SLOT(aboutToShowContextMenu())); disconnect(m_contextMenu, SIGNAL(aboutToHide()), this, SLOT(aboutToHideContextMenu())); } m_contextMenu = menu; m_userContextMenuSet = true; if (m_contextMenu) { connect(m_contextMenu, SIGNAL(aboutToShow()), this, SLOT(aboutToShowContextMenu())); connect(m_contextMenu, SIGNAL(aboutToHide()), this, SLOT(aboutToHideContextMenu())); } } QMenu *KTextEditor::ViewPrivate::contextMenu() const { if (m_userContextMenuSet) { return m_contextMenu; } else { KXMLGUIClient *client = const_cast(this); while (client->parentClient()) { client = client->parentClient(); } //qCDebug(LOG_KTE) << "looking up all menu containers"; if (client->factory()) { QList conts = client->factory()->containers(QStringLiteral("menu")); foreach (QWidget *w, conts) { if (w->objectName() == QLatin1String("ktexteditor_popup")) { //perhaps optimize this block QMenu *menu = (QMenu *)w; disconnect(menu, SIGNAL(aboutToShow()), this, SLOT(aboutToShowContextMenu())); disconnect(menu, SIGNAL(aboutToHide()), this, SLOT(aboutToHideContextMenu())); connect(menu, SIGNAL(aboutToShow()), this, SLOT(aboutToShowContextMenu())); connect(menu, SIGNAL(aboutToHide()), this, SLOT(aboutToHideContextMenu())); return menu; } } } } return nullptr; } QMenu *KTextEditor::ViewPrivate::defaultContextMenu(QMenu *menu) const { if (!menu) { menu = new QMenu(const_cast(this)); } menu->addAction(m_editUndo); menu->addAction(m_editRedo); menu->addSeparator(); menu->addAction(m_cut); menu->addAction(m_copy); menu->addAction(m_paste); menu->addSeparator(); menu->addAction(m_selectAll); menu->addAction(m_deSelect); if (QAction *spellingSuggestions = actionCollection()->action(QStringLiteral("spelling_suggestions"))) { menu->addSeparator(); menu->addAction(spellingSuggestions); } if (QAction *bookmark = actionCollection()->action(QStringLiteral("bookmarks"))) { menu->addSeparator(); menu->addAction(bookmark); } return menu; } void KTextEditor::ViewPrivate::aboutToShowContextMenu() { QMenu *menu = qobject_cast(sender()); if (menu) { emit contextMenuAboutToShow(this, menu); } } void KTextEditor::ViewPrivate::aboutToHideContextMenu() { m_spellingMenu->setUseMouseForMisspelledRange(false); } // BEGIN ConfigInterface stff QStringList KTextEditor::ViewPrivate::configKeys() const { static const QStringList keys = { QStringLiteral("icon-bar"), QStringLiteral("line-numbers"), QStringLiteral("dynamic-word-wrap"), QStringLiteral("background-color"), QStringLiteral("selection-color"), QStringLiteral("search-highlight-color"), QStringLiteral("replace-highlight-color"), QStringLiteral("default-mark-type"), QStringLiteral("allow-mark-menu"), QStringLiteral("folding-bar"), QStringLiteral("folding-preview"), QStringLiteral("icon-border-color"), QStringLiteral("folding-marker-color"), QStringLiteral("line-number-color"), QStringLiteral("current-line-number-color"), QStringLiteral("modification-markers"), QStringLiteral("keyword-completion"), QStringLiteral("word-count"), QStringLiteral("scrollbar-minimap"), QStringLiteral("scrollbar-preview"), QStringLiteral("font") }; return keys; } QVariant KTextEditor::ViewPrivate::configValue(const QString &key) { if (key == QLatin1String("icon-bar")) { return config()->iconBar(); } else if (key == QLatin1String("line-numbers")) { return config()->lineNumbers(); } else if (key == QLatin1String("dynamic-word-wrap")) { return config()->dynWordWrap(); } else if (key == QLatin1String("background-color")) { return renderer()->config()->backgroundColor(); } else if (key == QLatin1String("selection-color")) { return renderer()->config()->selectionColor(); } else if (key == QLatin1String("search-highlight-color")) { return renderer()->config()->searchHighlightColor(); } else if (key == QLatin1String("replace-highlight-color")) { return renderer()->config()->replaceHighlightColor(); } else if (key == QLatin1String("default-mark-type")) { return config()->defaultMarkType(); } else if (key == QLatin1String("allow-mark-menu")) { return config()->allowMarkMenu(); } else if (key == QLatin1String("folding-bar")) { return config()->foldingBar(); } else if (key == QLatin1String("folding-preview")) { return config()->foldingPreview(); } else if (key == QLatin1String("icon-border-color")) { return renderer()->config()->iconBarColor(); } else if (key == QLatin1String("folding-marker-color")) { return renderer()->config()->foldingColor(); } else if (key == QLatin1String("line-number-color")) { return renderer()->config()->lineNumberColor(); } else if (key == QLatin1String("current-line-number-color")) { return renderer()->config()->currentLineNumberColor(); } else if (key == QLatin1String("modification-markers")) { return config()->lineModification(); } else if (key == QLatin1String("keyword-completion")) { return config()->keywordCompletion(); } else if (key == QLatin1String("word-count")) { return config()->showWordCount(); } else if (key == QLatin1String("scrollbar-minimap")) { return config()->scrollBarMiniMap(); } else if (key == QLatin1String("scrollbar-preview")) { return config()->scrollBarPreview(); } else if (key == QLatin1String("font")) { return renderer()->config()->font(); } // return invalid variant return QVariant(); } void KTextEditor::ViewPrivate::setConfigValue(const QString &key, const QVariant &value) { if (value.canConvert(QVariant::Color)) { if (key == QLatin1String("background-color")) { renderer()->config()->setBackgroundColor(value.value()); } else if (key == QLatin1String("selection-color")) { renderer()->config()->setSelectionColor(value.value()); } else if (key == QLatin1String("search-highlight-color")) { renderer()->config()->setSearchHighlightColor(value.value()); } else if (key == QLatin1String("replace-highlight-color")) { renderer()->config()->setReplaceHighlightColor(value.value()); } else if (key == QLatin1String("icon-border-color")) { renderer()->config()->setIconBarColor(value.value()); } else if (key == QLatin1String("folding-marker-color")) { renderer()->config()->setFoldingColor(value.value()); } else if (key == QLatin1String("line-number-color")) { renderer()->config()->setLineNumberColor(value.value()); } else if (key == QLatin1String("current-line-number-color")) { renderer()->config()->setCurrentLineNumberColor(value.value()); } } else if (value.type() == QVariant::Bool) { // Note explicit type check above. If we used canConvert, then // values of type UInt will be trapped here. if (key == QLatin1String("icon-bar")) { config()->setIconBar(value.toBool()); } else if (key == QLatin1String("line-numbers")) { config()->setLineNumbers(value.toBool()); } else if (key == QLatin1String("dynamic-word-wrap")) { config()->setDynWordWrap(value.toBool()); } else if (key == QLatin1String("allow-mark-menu")) { config()->setAllowMarkMenu(value.toBool()); } else if (key == QLatin1String("folding-bar")) { config()->setFoldingBar(value.toBool()); } else if (key == QLatin1String("folding-preview")) { config()->setFoldingPreview(value.toBool()); } else if (key == QLatin1String("modification-markers")) { config()->setLineModification(value.toBool()); } else if (key == QLatin1String("keyword-completion")) { config()->setKeywordCompletion(value.toBool()); } else if (key == QLatin1String("word-count")) { config()->setShowWordCount(value.toBool()); } else if (key == QLatin1String("scrollbar-minimap")) { config()->setScrollBarMiniMap(value.toBool()); } else if (key == QLatin1String("scrollbar-preview")) { config()->setScrollBarPreview(value.toBool()); } } else if (value.canConvert(QVariant::UInt)) { if (key == QLatin1String("default-mark-type")) { config()->setDefaultMarkType(value.toUInt()); } } else if (value.canConvert(QVariant::Font)) { if (key == QLatin1String("font")) { renderer()->config()->setFont(value.value()); } } } // END ConfigInterface void KTextEditor::ViewPrivate::userInvokedCompletion() { completionWidget()->userInvokedCompletion(); } KateViewBar *KTextEditor::ViewPrivate::bottomViewBar() const { return m_bottomViewBar; } KateGotoBar *KTextEditor::ViewPrivate::gotoBar() { if (!m_gotoBar) { m_gotoBar = new KateGotoBar(this); bottomViewBar()->addBarWidget(m_gotoBar); } return m_gotoBar; } KateDictionaryBar *KTextEditor::ViewPrivate::dictionaryBar() { if (!m_dictionaryBar) { m_dictionaryBar = new KateDictionaryBar(this); bottomViewBar()->addBarWidget(m_dictionaryBar); } return m_dictionaryBar; } void KTextEditor::ViewPrivate::setAnnotationModel(KTextEditor::AnnotationModel *model) { KTextEditor::AnnotationModel *oldmodel = m_annotationModel; m_annotationModel = model; m_viewInternal->m_leftBorder->annotationModelChanged(oldmodel, m_annotationModel); } KTextEditor::AnnotationModel *KTextEditor::ViewPrivate::annotationModel() const { return m_annotationModel; } void KTextEditor::ViewPrivate::setAnnotationBorderVisible(bool visible) { m_viewInternal->m_leftBorder->setAnnotationBorderOn(visible); if ( !visible ) { // make sure the tooltip is hidden QToolTip::hideText(); } } bool KTextEditor::ViewPrivate::isAnnotationBorderVisible() const { return m_viewInternal->m_leftBorder->annotationBorderOn(); } KTextEditor::Range KTextEditor::ViewPrivate::visibleRange() { //ensure that the view is up-to-date, otherwise 'endPos()' might fail! m_viewInternal->updateView(); return KTextEditor::Range(m_viewInternal->toRealCursor(m_viewInternal->startPos()), m_viewInternal->toRealCursor(m_viewInternal->endPos())); } bool KTextEditor::ViewPrivate::event(QEvent *e) { switch (e->type()) { case QEvent::StyleChange: setupLayout(); return true; default: return KTextEditor::View::event(e); } } void KTextEditor::ViewPrivate::paintEvent(QPaintEvent *e) { //base class KTextEditor::View::paintEvent(e); const QRect contentsRect = m_topSpacer->geometry()| m_bottomSpacer->geometry()| m_leftSpacer->geometry()| m_rightSpacer->geometry(); if (contentsRect.isValid()) { QStyleOptionFrame opt; opt.initFrom(this); opt.frameShape = QFrame::StyledPanel; opt.state |= QStyle::State_Sunken; // clear mouseOver and focus state // update from relevant widgets opt.state &= ~(QStyle::State_HasFocus|QStyle::State_MouseOver); const QList widgets = QList() << m_viewInternal << m_viewInternal->m_leftBorder << m_viewInternal->m_lineScroll << m_viewInternal->m_columnScroll; foreach (const QWidget *w, widgets) { if (w->hasFocus()) opt.state |= QStyle::State_HasFocus; if (w->underMouse()) opt.state |= QStyle::State_MouseOver; } // update rect opt.rect=contentsRect; // render QPainter paint(this); paint.setClipRegion(e->region()); paint.setRenderHints(QPainter::Antialiasing); style()->drawControl(QStyle::CE_ShapedFrame, &opt, &paint, this); } } void KTextEditor::ViewPrivate::toggleOnTheFlySpellCheck(bool b) { m_doc->onTheFlySpellCheckingEnabled(b); } void KTextEditor::ViewPrivate::reflectOnTheFlySpellCheckStatus(bool enabled) { m_spellingMenu->setVisible(enabled); m_toggleOnTheFlySpellCheck->setChecked(enabled); } KateSpellingMenu *KTextEditor::ViewPrivate::spellingMenu() { return m_spellingMenu; } void KTextEditor::ViewPrivate::notifyAboutRangeChange(int startLine, int endLine, bool rangeWithAttribute) { #ifdef VIEW_RANGE_DEBUG // output args qCDebug(LOG_KTE) << "trigger attribute changed from" << startLine << "to" << endLine << "rangeWithAttribute" << rangeWithAttribute; #endif // first call: if (!m_delayedUpdateTriggered) { m_delayedUpdateTriggered = true; m_lineToUpdateMin = -1; m_lineToUpdateMax = -1; // only set initial line range, if range with attribute! if (rangeWithAttribute) { m_lineToUpdateMin = startLine; m_lineToUpdateMax = endLine; } // emit queued signal and be done emit delayedUpdateOfView(); return; } // ignore lines if no attribute if (!rangeWithAttribute) { return; } // update line range if (startLine != -1 && (m_lineToUpdateMin == -1 || startLine < m_lineToUpdateMin)) { m_lineToUpdateMin = startLine; } if (endLine != -1 && endLine > m_lineToUpdateMax) { m_lineToUpdateMax = endLine; } } void KTextEditor::ViewPrivate::slotDelayedUpdateOfView() { if (!m_delayedUpdateTriggered) { return; } #ifdef VIEW_RANGE_DEBUG // output args qCDebug(LOG_KTE) << "delayed attribute changed from" << m_lineToUpdateMin << "to" << m_lineToUpdateMax; #endif // update ranges in updateRangesIn(KTextEditor::Attribute::ActivateMouseIn); updateRangesIn(KTextEditor::Attribute::ActivateCaretIn); // update view, if valid line range, else only feedback update wanted anyway if (m_lineToUpdateMin != -1 && m_lineToUpdateMax != -1) { tagLines(m_lineToUpdateMin, m_lineToUpdateMax, true); updateView(true); } // reset flags m_delayedUpdateTriggered = false; m_lineToUpdateMin = -1; m_lineToUpdateMax = -1; } void KTextEditor::ViewPrivate::updateRangesIn(KTextEditor::Attribute::ActivationType activationType) { // new ranges with cursor in, default none QSet newRangesIn; // on which range set we work? QSet &oldSet = (activationType == KTextEditor::Attribute::ActivateMouseIn) ? m_rangesMouseIn : m_rangesCaretIn; // which cursor position to honor? KTextEditor::Cursor currentCursor = (activationType == KTextEditor::Attribute::ActivateMouseIn) ? m_viewInternal->getMouse() : m_viewInternal->primaryCursor(); // first: validate the remembered ranges QSet validRanges; foreach (Kate::TextRange *range, oldSet) if (m_doc->buffer().rangePointerValid(range)) { validRanges.insert(range); } // cursor valid? else no new ranges can be found if (currentCursor.isValid() && currentCursor.line() < m_doc->buffer().lines()) { // now: get current ranges for the line of cursor with an attribute QList rangesForCurrentCursor = m_doc->buffer().rangesForLine(currentCursor.line(), this, false); // match which ranges really fit the given cursor foreach (Kate::TextRange *range, rangesForCurrentCursor) { // range has no dynamic attribute of right type and no feedback object if ((!range->attribute() || !range->attribute()->dynamicAttribute(activationType)) && !range->feedback()) { continue; } // range doesn't contain cursor, not interesting if ((range->start().insertBehavior() == KTextEditor::MovingCursor::StayOnInsert) ? (currentCursor < range->start().toCursor()) : (currentCursor <= range->start().toCursor())) { continue; } if ((range->end().insertBehavior() == KTextEditor::MovingCursor::StayOnInsert) ? (range->end().toCursor() <= currentCursor) : (range->end().toCursor() < currentCursor)) { continue; } // range contains cursor, was it already in old set? if (validRanges.contains(range)) { // insert in new, remove from old, be done with it newRangesIn.insert(range); validRanges.remove(range); continue; } // oh, new range, trigger update and insert into new set newRangesIn.insert(range); if (range->attribute() && range->attribute()->dynamicAttribute(activationType)) { notifyAboutRangeChange(range->start().line(), range->end().line(), true); } // feedback if (range->feedback()) { if (activationType == KTextEditor::Attribute::ActivateMouseIn) { range->feedback()->mouseEnteredRange(range, this); } else { range->feedback()->caretEnteredRange(range, this); } } #ifdef VIEW_RANGE_DEBUG // found new range for activation qCDebug(LOG_KTE) << "activated new range" << range << "by" << activationType; #endif } } // now: notify for left ranges! foreach (Kate::TextRange *range, validRanges) { // range valid + right dynamic attribute, trigger update if (range->toRange().isValid() && range->attribute() && range->attribute()->dynamicAttribute(activationType)) { notifyAboutRangeChange(range->start().line(), range->end().line(), true); } // feedback if (range->feedback()) { if (activationType == KTextEditor::Attribute::ActivateMouseIn) { range->feedback()->mouseExitedRange(range, this); } else { range->feedback()->caretExitedRange(range, this); } } } // set new ranges oldSet = newRangesIn; } void KTextEditor::ViewPrivate::postMessage(KTextEditor::Message *message, QList > actions) { // just forward to KateMessageWidget :-) auto messageWidget = m_messageWidgets[message->position()]; if (!messageWidget) { // this branch is used for: TopInView, CenterInView, and BottomInView messageWidget = new KateMessageWidget(m_viewInternal, true); m_messageWidgets[message->position()] = messageWidget; m_notificationLayout->addWidget(messageWidget, message->position()); connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), messageWidget, SLOT(startAutoHideTimer())); connect(this, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), messageWidget, SLOT(startAutoHideTimer())); } messageWidget->postMessage(message, actions); } KateMessageWidget *KTextEditor::ViewPrivate::messageWidget() { return m_messageWidgets[KTextEditor::Message::TopInView]; } void KTextEditor::ViewPrivate::saveFoldingState() { m_savedFoldingState = m_textFolding.exportFoldingRanges(); } void KTextEditor::ViewPrivate::applyFoldingState() { m_textFolding.importFoldingRanges(m_savedFoldingState); m_savedFoldingState = QJsonDocument(); } void KTextEditor::ViewPrivate::exportHtmlToFile(const QString &file) { KateExporter(this).exportToFile(file); } void KTextEditor::ViewPrivate::exportHtmlToClipboard () { KateExporter(this).exportToClipboard(); } void KTextEditor::ViewPrivate::exportHtmlToFile () { const QString file = QFileDialog::getSaveFileName(this, i18n("Export File as HTML"), m_doc->documentName()); if (!file.isEmpty()) { KateExporter(this).exportToFile(file); } } void KTextEditor::ViewPrivate::clearHighlights() { qDeleteAll(m_rangesForHighlights); m_rangesForHighlights.clear(); m_currentTextForHighlights.clear(); } void KTextEditor::ViewPrivate::selectionChangedForHighlights() { QString text; // if text of selection is still the same, abort if (selection() && selectionRange().onSingleLine()) { text = selectionText(); if (text == m_currentTextForHighlights) return; } // text changed: remove all highlights + create new ones // (do not call clearHighlights(), since this also resets the m_currentTextForHighlights qDeleteAll(m_rangesForHighlights); m_rangesForHighlights.clear(); // do not highlight strings with leading and trailing spaces if (!text.isEmpty() && (text.at(0).isSpace() || text.at(text.length()-1).isSpace())) return; // trigger creation of ranges for current view range m_currentTextForHighlights = text; createHighlights(); } void KTextEditor::ViewPrivate::createHighlights() { // do nothing if no text to highlight if (m_currentTextForHighlights.isEmpty()) { return; } KTextEditor::Attribute::Ptr attr(new KTextEditor::Attribute()); attr->setBackground(Qt::yellow); // set correct highlight color from Kate's color schema QColor fgColor = defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color(); QColor bgColor = renderer()->config()->searchHighlightColor(); attr->setForeground(fgColor); attr->setBackground(bgColor); KTextEditor::Cursor start(visibleRange().start()); KTextEditor::Range searchRange; /** * only add word boundary if we can find the text then * fixes $lala hl */ QString regex = QRegExp::escape (m_currentTextForHighlights); if (QRegExp (QStringLiteral("\\b%1").arg(regex)).indexIn (QStringLiteral(" %1 ").arg(m_currentTextForHighlights)) != -1) regex = QStringLiteral("\\b%1").arg(regex); if (QRegExp (QStringLiteral("%1\\b").arg(regex)).indexIn (QStringLiteral(" %1 ").arg(m_currentTextForHighlights)) != -1) regex = QStringLiteral("%1\\b").arg(regex); QVector matches; do { searchRange.setRange(start, visibleRange().end()); matches = m_doc->searchText(searchRange, regex, KTextEditor::Regex); if (matches.first().isValid()) { KTextEditor::MovingRange* mr = m_doc->newMovingRange(matches.first()); mr->setAttribute(attr); mr->setView(this); mr->setZDepth(-90000.0); // Set the z-depth to slightly worse than the selection mr->setAttributeOnlyForViews(true); m_rangesForHighlights.append(mr); start = matches.first().end(); } } while (matches.first().isValid()); } KateAbstractInputMode *KTextEditor::ViewPrivate::currentInputMode() const { return m_viewInternal->m_currentInputMode; } void KTextEditor::ViewPrivate::toggleInputMode() { if (QAction *a = dynamic_cast(sender())) { setInputMode(static_cast(a->data().toInt())); } } void KTextEditor::ViewPrivate::cycleInputMode() { InputMode current = currentInputMode()->viewInputMode(); InputMode to = (current == KTextEditor::View::NormalInputMode) ? KTextEditor::View::ViInputMode : KTextEditor::View::NormalInputMode; setInputMode(to); } //BEGIN KTextEditor::PrintInterface stuff bool KTextEditor::ViewPrivate::print() { return KatePrinter::print(this); } void KTextEditor::ViewPrivate::printPreview() { KatePrinter::printPreview(this); } //END // void KTextEditor::ViewPrivate::moveSecondaryCursors(int chars) // { // Q_FOREACH ( auto c, m_secondaryCursors ) { // if ( blockSelection() ) { // c->setPosition(c->line(), qMax(0, c->column() + chars)); // } // else { // c->move(chars, KTextEditor::MovingCursor::Wrap); // } // Q_ASSERT(c->toCursor().isValid()); // } // } // // void KTextEditor::ViewPrivate::moveSecondaryCursorsVertically(int lines) // { // Q_FOREACH ( auto c, m_secondaryCursors ) { // const auto oldColumn = c->column(); // c->setLine(qMin(qMax(0, c->line() + lines), doc()->lines())); // auto newColumn = blockSelection() ? oldColumn : qMin(c->document()->lineLength(c->line()), oldColumn); // c->setColumn(newColumn); // Q_ASSERT(c->toCursor().isValid()); // } // } // // void KTextEditor::ViewPrivate::updateSecondaryCursorsPositions(std::function update) // { // Q_FOREACH ( const auto& c, m_secondaryCursors ) { // c->setPosition(update(c->toCursor())); // Q_ASSERT(c->toCursor().isValid()); // } // } KTextEditor::Attribute::Ptr KTextEditor::ViewPrivate::defaultStyleAttribute(KTextEditor::DefaultStyle defaultStyle) const { KateRendererConfig * renderConfig = const_cast(this)->renderer()->config(); KTextEditor::Attribute::Ptr style = m_doc->highlight()->attributes(renderConfig->schema()).at(defaultStyle); if (!style->hasProperty(QTextFormat::BackgroundBrush)) { // make sure the returned style has the default background color set style = new KTextEditor::Attribute(*style); style->setBackground(QBrush(renderConfig->backgroundColor())); } return style; } QList KTextEditor::ViewPrivate::lineAttributes(int line) { QList attribs; if (line < 0 || line >= m_doc->lines()) return attribs; Kate::TextLine kateLine = m_doc->kateTextLine(line); if (!kateLine) { return attribs; } const QVector &intAttrs = kateLine->attributesList(); for (int i = 0; i < intAttrs.size(); ++i) { if (intAttrs[i].length > 0 && intAttrs[i].attributeValue > 0) { attribs << KTextEditor::AttributeBlock( intAttrs.at(i).offset, intAttrs.at(i).length, renderer()->attribute(intAttrs.at(i).attributeValue) ); } } return attribs; } KateMultiCursor * KTextEditor::ViewPrivate::cursors() { return m_viewInternal->cursors(); } Cursors KTextEditor::ViewPrivate::allCursors() { return cursors()->cursors(); } diff --git a/src/view/kateview.h b/src/view/kateview.h index 04342736..d07cbb6d 100644 --- a/src/view/kateview.h +++ b/src/view/kateview.h @@ -1,1033 +1,1033 @@ /* This file is part of the KDE libraries Copyright (C) 2002 John Firebaugh Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001-2010 Joseph Wenninger Copyright (C) 1999 Jochen Wilhelmy This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef kate_view_h #define kate_view_h #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "katetextrange.h" #include "katetextfolding.h" #include "katerenderer.h" #include "katemessagewidget.h" #include "katemulticursor.h" #include "katemulticlipboard.h" namespace KTextEditor { class AnnotationModel; class Message; } namespace KTextEditor { class DocumentPrivate; } class KateBookmarks; class KateViewConfig; class KateRenderer; class KateSpellCheckDialog; class KateCompletionWidget; class KateViewInternal; class KateViewBar; class KateTextPreview; class KateGotoBar; class KateDictionaryBar; class KateSpellingMenu; class KateMessageWidget; class KateIconBorder; class KateStatusBar; class KateViewEncodingAction; class KateModeMenu; class KateAbstractInputMode; class KateScriptActionMenu; class KateMessageLayout; class KToggleAction; class KSelectAction; class QAction; namespace KTextEditor { // // Kate KTextEditor::View class ;) // class KTEXTEDITOR_EXPORT ViewPrivate : public KTextEditor::View, public KTextEditor::TextHintInterface, public KTextEditor::CodeCompletionInterface, public KTextEditor::ConfigInterface, public KTextEditor::AnnotationViewInterface { Q_OBJECT Q_INTERFACES(KTextEditor::TextHintInterface) Q_INTERFACES(KTextEditor::ConfigInterface) Q_INTERFACES(KTextEditor::CodeCompletionInterface) Q_INTERFACES(KTextEditor::AnnotationViewInterface) friend class KTextEditor::View; friend class ::KateViewInternal; friend class ::KateIconBorder; friend class CalculatingCursor; friend class ::KateTextPreview; public: ViewPrivate (KTextEditor::DocumentPrivate *doc, QWidget *parent, KTextEditor::MainWindow *mainWindow = nullptr); - ~ViewPrivate (); + ~ViewPrivate() override; /** * Get the view's main window, if any * \return the view's main window */ - KTextEditor::MainWindow *mainWindow() const Q_DECL_OVERRIDE + KTextEditor::MainWindow *mainWindow() const override { return m_mainWindow; } - KTextEditor::Document *document() const Q_DECL_OVERRIDE; + KTextEditor::Document *document() const override; - ViewMode viewMode() const Q_DECL_OVERRIDE; - QString viewModeHuman() const Q_DECL_OVERRIDE; - InputMode viewInputMode() const Q_DECL_OVERRIDE; - QString viewInputModeHuman() const Q_DECL_OVERRIDE; + ViewMode viewMode() const override; + QString viewModeHuman() const override; + InputMode viewInputMode() const override; + QString viewInputModeHuman() const override; void setInputMode(InputMode mode); const KateMultiCursor* cursors() const; const KateMultiSelection* selections() const; KateMultiCursor* cursors(); KateMultiSelection* selections(); Cursors allCursors(); // // KTextEditor::ClipboardInterface // public Q_SLOTS: void paste(); void pasteInternal(const QVector& texts); // void paste(const QString *textToPaste = nullptr); void cut(); void copy() const; private Q_SLOTS: /** * internal use, apply word wrap */ void applyWordWrap(); // // KTextEditor::PopupMenuInterface // public: - void setContextMenu(QMenu *menu) Q_DECL_OVERRIDE; - QMenu *contextMenu() const Q_DECL_OVERRIDE; - QMenu *defaultContextMenu(QMenu *menu = nullptr) const Q_DECL_OVERRIDE; + void setContextMenu(QMenu *menu) override; + QMenu *contextMenu() const override; + QMenu *defaultContextMenu(QMenu *menu = nullptr) const override; private Q_SLOTS: void aboutToShowContextMenu(); void aboutToHideContextMenu(); private: QPointer m_contextMenu; // // KTextEditor::ViewCursorInterface // public: - bool setCursorPosition(KTextEditor::Cursor position) Q_DECL_OVERRIDE; - bool setCursorPositions(const QVector &positions) Q_DECL_OVERRIDE; + bool setCursorPosition(KTextEditor::Cursor position) override; + bool setCursorPositions(const QVector &positions) override; - KTextEditor::Cursor cursorPosition() const Q_DECL_OVERRIDE; - QVector cursorPositions() const Q_DECL_OVERRIDE; + KTextEditor::Cursor cursorPosition() const override; + QVector cursorPositions() const override; - KTextEditor::Cursor cursorPositionVirtual() const Q_DECL_OVERRIDE; + KTextEditor::Cursor cursorPositionVirtual() const override; - QPoint cursorToCoordinate(const KTextEditor::Cursor &cursor) const Q_DECL_OVERRIDE; + QPoint cursorToCoordinate(const KTextEditor::Cursor &cursor) const override; - KTextEditor::Cursor coordinatesToCursor(const QPoint &coord) const Q_DECL_OVERRIDE; + KTextEditor::Cursor coordinatesToCursor(const QPoint &coord) const override; - QPoint cursorPositionCoordinates() const Q_DECL_OVERRIDE; + QPoint cursorPositionCoordinates() const override; bool setCursorPositionVisual(const KTextEditor::Cursor &position); /** * Return the virtual cursor column, each tab is expanded into the * document's tabWidth characters. If word wrap is off, the cursor may be * behind the line's length. */ int virtualCursorColumn() const; - bool mouseTrackingEnabled() const Q_DECL_OVERRIDE; - bool setMouseTrackingEnabled(bool enable) Q_DECL_OVERRIDE; + bool mouseTrackingEnabled() const override; + bool setMouseTrackingEnabled(bool enable) override; private: void notifyMousePositionChanged(const KTextEditor::Cursor &newPosition); // Internal public: bool setCursorPositionInternal(const KTextEditor::Cursor &position, uint tabwidth = 1, bool calledExternally = false); // // KTextEditor::ConfigInterface // public: - QStringList configKeys() const Q_DECL_OVERRIDE; - QVariant configValue(const QString &key) Q_DECL_OVERRIDE; - void setConfigValue(const QString &key, const QVariant &value) Q_DECL_OVERRIDE; + QStringList configKeys() const override; + QVariant configValue(const QString &key) override; + void setConfigValue(const QString &key, const QVariant &value) override; Q_SIGNALS: void configChanged(); public: /** * Try to fold starting at the given line. * This will both try to fold existing folding ranges of this line and to query the highlighting what to fold. * @param startLine start line to fold at */ void foldLine(int startLine); /** * Try to unfold all foldings starting at the given line. * @param startLine start line to unfold at */ void unfoldLine(int startLine); // // KTextEditor::CodeCompletionInterface2 // public: - bool isCompletionActive() const Q_DECL_OVERRIDE; - void startCompletion(const KTextEditor::Range &word, KTextEditor::CodeCompletionModel *model) Q_DECL_OVERRIDE; - void abortCompletion() Q_DECL_OVERRIDE; - void forceCompletion() Q_DECL_OVERRIDE; - void registerCompletionModel(KTextEditor::CodeCompletionModel *model) Q_DECL_OVERRIDE; - void unregisterCompletionModel(KTextEditor::CodeCompletionModel *model) Q_DECL_OVERRIDE; + bool isCompletionActive() const override; + void startCompletion(const KTextEditor::Range &word, KTextEditor::CodeCompletionModel *model) override; + void abortCompletion() override; + void forceCompletion() override; + void registerCompletionModel(KTextEditor::CodeCompletionModel *model) override; + void unregisterCompletionModel(KTextEditor::CodeCompletionModel *model) override; bool isCompletionModelRegistered(KTextEditor::CodeCompletionModel *model) const; - bool isAutomaticInvocationEnabled() const Q_DECL_OVERRIDE; - void setAutomaticInvocationEnabled(bool enabled = true) Q_DECL_OVERRIDE; + bool isAutomaticInvocationEnabled() const override; + void setAutomaticInvocationEnabled(bool enabled = true) override; Q_SIGNALS: void completionExecuted(KTextEditor::View *view, const KTextEditor::Cursor &position, KTextEditor::CodeCompletionModel *model, const QModelIndex &); void completionAborted(KTextEditor::View *view); public Q_SLOTS: void userInvokedCompletion(); public: KateCompletionWidget *completionWidget() const; mutable KateCompletionWidget *m_completionWidget; void sendCompletionExecuted(const KTextEditor::Cursor &position, KTextEditor::CodeCompletionModel *model, const QModelIndex &index); void sendCompletionAborted(); // // KTextEditor::TextHintInterface // public: - void registerTextHintProvider(KTextEditor::TextHintProvider *provider) Q_DECL_OVERRIDE; - void unregisterTextHintProvider(KTextEditor::TextHintProvider *provider) Q_DECL_OVERRIDE; - void setTextHintDelay(int delay) Q_DECL_OVERRIDE; - int textHintDelay() const Q_DECL_OVERRIDE; + void registerTextHintProvider(KTextEditor::TextHintProvider *provider) override; + void unregisterTextHintProvider(KTextEditor::TextHintProvider *provider) override; + void setTextHintDelay(int delay) override; + int textHintDelay() const override; public: bool dynWordWrap() const { return m_hasWrap; } // // KTextEditor::SelectionInterface stuff // public Q_SLOTS: /** * @brief Get the primary selection. * * The primary selection is the selection range containing the primary * cursor. If no such selection exists, as might be the case * in "persistent selection" mode, the persistent selection is returned instead. */ KTextEditor::Range primarySelection() const { return selections()->primarySelection(); }; /** * @brief Set the primary selection. */ - bool setSelection(const KTextEditor::Range &selection) Q_DECL_OVERRIDE; + bool setSelection(const KTextEditor::Range &selection) override; bool setPrimarySelection(const KTextEditor::Range &selection) { return setSelection(selection); }; bool setSelections(const QVector &selections, - const QVector &cursors) Q_DECL_OVERRIDE; + const QVector &cursors) override; - bool removeSelection() Q_DECL_OVERRIDE + bool removeSelection() override { return clearSelection(); } - bool removeSelectionText() Q_DECL_OVERRIDE + bool removeSelectionText() override { return removeSelectedText(); } - bool setBlockSelection(bool on) Q_DECL_OVERRIDE; + bool setBlockSelection(bool on) override; bool toggleBlockSelection(); bool toAlignedBlock(bool fill); bool clearSelection(); bool clearSelection(bool redraw, bool finishedChangingSelection = true); bool removeSelectedText(); bool selectAll(); public: - bool selection() const Q_DECL_OVERRIDE; - QString selectionText() const Q_DECL_OVERRIDE; - bool blockSelection() const Q_DECL_OVERRIDE; - KTextEditor::Range selectionRange() const Q_DECL_OVERRIDE; - QVector selectionRanges() const Q_DECL_OVERRIDE; + bool selection() const override; + QString selectionText() const override; + bool blockSelection() const override; + KTextEditor::Range selectionRange() const override; + QVector selectionRanges() const override; static void blockFix(KTextEditor::Range &range); // // Arbitrary Syntax HL + Action extensions // public: // Action association extension void deactivateEditActions(); void activateEditActions(); // // internal helper stuff, for katerenderer and so on // public: // should cursor be wrapped ? take config + blockselection state in account bool wrapCursor() const; // some internal functions to get selection state of a line/col bool cursorSelected(const KTextEditor::Cursor &cursor); bool lineSelected(int line); bool lineEndSelected(const KTextEditor::Cursor &lineEndPos); bool lineHasSelected(int line); bool lineIsSelection(int line); void ensureCursorColumnValid(); void selectWord(const KTextEditor::Cursor &cursor); void selectLine(const KTextEditor::Cursor &cursor); //BEGIN EDIT STUFF public: void editStart(); void editEnd(int editTagLineStart, int editTagLineEnd, bool tagFrom); void editSetCursor(const KTextEditor::Cursor &cursor); //END //BEGIN TAG & CLEAR public: bool tagLine(const KTextEditor::Cursor &virtualCursor); bool tagRange(const KTextEditor::Range &range, bool realLines = false); bool tagLines(int start, int end, bool realLines = false); bool tagLines(KTextEditor::Cursor start, KTextEditor::Cursor end, bool realCursors = false); bool tagLines(KTextEditor::Range range, bool realRange = false); void tagAll(); void clear(); void repaintText(bool paintOnlyDirty = false); void updateView(bool changed = false); //END // // KTextEditor::AnnotationView // public: - void setAnnotationModel(KTextEditor::AnnotationModel *model) Q_DECL_OVERRIDE; - KTextEditor::AnnotationModel *annotationModel() const Q_DECL_OVERRIDE; - void setAnnotationBorderVisible(bool visible) Q_DECL_OVERRIDE; - bool isAnnotationBorderVisible() const Q_DECL_OVERRIDE; + void setAnnotationModel(KTextEditor::AnnotationModel *model) override; + KTextEditor::AnnotationModel *annotationModel() const override; + void setAnnotationBorderVisible(bool visible) override; + bool isAnnotationBorderVisible() const override; Q_SIGNALS: - void annotationContextMenuAboutToShow(KTextEditor::View *view, QMenu *menu, int line) Q_DECL_OVERRIDE; - void annotationActivated(KTextEditor::View *view, int line) Q_DECL_OVERRIDE; + void annotationContextMenuAboutToShow(KTextEditor::View *view, QMenu *menu, int line) override; + void annotationActivated(KTextEditor::View *view, int line) override; // KF6: fix View -> KTextEditor::View - void annotationBorderVisibilityChanged(View *view, bool visible) Q_DECL_OVERRIDE; + void annotationBorderVisibilityChanged(View *view, bool visible) override; void navigateLeft(); void navigateRight(); void navigateUp(); void navigateDown(); void navigateAccept(); void navigateBack(); private: KTextEditor::AnnotationModel *m_annotationModel; // // KTextEditor::View // public: void emitNavigateLeft() { emit navigateLeft(); } void emitNavigateRight() { emit navigateRight(); } void emitNavigateUp() { emit navigateUp(); } void emitNavigateDown() { emit navigateDown(); } void emitNavigateAccept() { emit navigateAccept(); } void emitNavigateBack() { emit navigateBack(); } /** Return values for "save" related commands. */ bool isOverwriteMode() const; QString currentTextLine(); QTextLayout * textLayout(int line) const; QTextLayout * textLayout(const KTextEditor::Cursor &pos) const; public Q_SLOTS: void indent(); void unIndent(); void cleanIndent(); void align(); void comment(); void uncomment(); void toggleComment(); void killLine(); /** * Sets the cursor to the previous editing position in this document */ void goToPreviousEditingPosition(); /** * Sets the cursor to the next editing position in this document */ void goToNextEditingPosition(); /** Uppercases selected text, or an alphabetic character next to the cursor. */ void uppercase(); /** Lowercases selected text, or an alphabetic character next to the cursor. */ void lowercase(); /** Capitalizes the selection (makes each word start with an uppercase) or the word under the cursor. */ void capitalize(); /** Joins lines touched by the selection */ void joinLines(); // Note - the following functions simply forward to KateViewInternal void keyReturn(); void smartNewline(); void backspace(); void deleteWordLeft(); void keyDelete(); void deleteWordRight(); void transpose(); void cursorLeft(); void shiftCursorLeft(); void cursorRight(); void shiftCursorRight(); void wordLeft(); void shiftWordLeft(); void wordRight(); void shiftWordRight(); void home(); void shiftHome(); void end(); void shiftEnd(); void up(); void shiftUp(); void down(); void shiftDown(); void scrollUp(); void scrollDown(); void topOfView(); void shiftTopOfView(); void bottomOfView(); void shiftBottomOfView(); void pageUp(); void shiftPageUp(); void pageDown(); void shiftPageDown(); void top(); void shiftTop(); void bottom(); void shiftBottom(); void toMatchingBracket(); void shiftToMatchingBracket(); void toPrevModifiedLine(); void toNextModifiedLine(); void insertTab(); void gotoLine(); // config file / session management functions public: /** * Read session settings from the given \p config. * * Known flags: * "SkipUrl" => don't save/restore the file * "SkipMode" => don't save/restore the mode * "SkipHighlighting" => don't save/restore the highlighting * "SkipEncoding" => don't save/restore the encoding * * \param config read the session settings from this KConfigGroup * \param flags additional flags * \see writeSessionConfig() */ - void readSessionConfig(const KConfigGroup &config, const QSet &flags = QSet()) Q_DECL_OVERRIDE; + void readSessionConfig(const KConfigGroup &config, const QSet &flags = QSet()) override; /** * Write session settings to the \p config. * See readSessionConfig() for more details. * * \param config write the session settings to this KConfigGroup * \param flags additional flags * \see readSessionConfig() */ - void writeSessionConfig(KConfigGroup &config, const QSet &flags = QSet()) Q_DECL_OVERRIDE; + void writeSessionConfig(KConfigGroup &config, const QSet &flags = QSet()) override; public Q_SLOTS: void setEol(int eol); void setAddBom(bool enabled); void find(); void findSelectedForwards(); void findSelectedBackwards(); void replace(); void findNext(); void findPrevious(); void setFoldingMarkersOn(bool enable); // Not in KTextEditor::View, but should be void setIconBorder(bool enable); void setLineNumbersOn(bool enable); void setScrollBarMarks(bool enable); void setScrollBarMiniMap(bool enable); void setScrollBarMiniMapAll(bool enable); void setScrollBarMiniMapWidth(int width); void toggleFoldingMarkers(); void toggleIconBorder(); void toggleLineNumbersOn(); void toggleScrollBarMarks(); void toggleScrollBarMiniMap(); void toggleScrollBarMiniMapAll(); void toggleDynWordWrap(); void setDynWrapIndicators(int mode); public Q_SLOTS: void setSecondaryCursorsFrozen(bool freeze); void placeSecondaryCursor(); public: int getEol() const; public: KateRenderer *renderer(); bool iconBorder(); bool lineNumbersOn(); bool scrollBarMarks(); bool scrollBarMiniMap(); bool scrollBarMiniMapAll(); int dynWrapIndicators(); bool foldingMarkersOn(); private Q_SLOTS: /** * used to update actions after selection changed */ void slotSelectionChanged(); void toggleInputMode(); void cycleInputMode(); public: /** * accessor to katedocument pointer * @return pointer to document */ KTextEditor::DocumentPrivate *doc() { return m_doc; } const KTextEditor::DocumentPrivate *doc() const { return m_doc; } public Q_SLOTS: void slotUpdateUndo(); void toggleInsert(); void reloadFile(); void toggleWWMarker(); void toggleNPSpaces(); void toggleWordCount(bool on); void toggleWriteLock(); void switchToCmdLine(); void slotReadWriteChanged(); void slotClipboardHistoryChanged(); Q_SIGNALS: void dropEventPass(QDropEvent *); public: /** * Folding handler for this view. * @return folding handler */ Kate::TextFolding &textFolding() { return m_textFolding; } public: void slotTextInserted(KTextEditor::View *view, const KTextEditor::Cursor &position, const QString &text); void exportHtmlToFile(const QString &file); private Q_SLOTS: void slotGotFocus(); void slotLostFocus(); void slotSaveCanceled(const QString &error); void slotConfigDialog(); void exportHtmlToClipboard (); void exportHtmlToFile (); public Q_SLOTS: void slotFoldToplevelNodes(); void slotExpandToplevelNodes(); void slotCollapseLocal(); void slotExpandLocal(); private: void setupLayout(); void setupConnections(); void setupActions(); void setupEditActions(); void setupCodeFolding(); QList m_editActions; QAction *m_editUndo; QAction *m_editRedo; QAction *m_pasteMenu; KToggleAction *m_toggleFoldingMarkers; KToggleAction *m_toggleIconBar; KToggleAction *m_toggleLineNumbers; KToggleAction *m_toggleScrollBarMarks; KToggleAction *m_toggleScrollBarMiniMap; KToggleAction *m_toggleScrollBarMiniMapAll; KToggleAction *m_toggleDynWrap; KSelectAction *m_setDynWrapIndicators; KToggleAction *m_toggleWWMarker; KToggleAction *m_toggleNPSpaces; KToggleAction *m_toggleWordCount; QAction *m_switchCmdLine; KToggleAction *m_viInputModeAction; KSelectAction *m_setEndOfLine; KToggleAction *m_addBom; QAction *m_cut; QAction *m_copy; QAction *m_copyHtmlAction; QAction *m_paste; QAction *m_selectAll; QAction *m_deSelect; QActionGroup *m_inputModeActions; KToggleAction *m_toggleBlockSelection; KToggleAction *m_toggleInsert; KToggleAction *m_toggleWriteLock; bool m_hasWrap; KTextEditor::DocumentPrivate *const m_doc; Kate::TextFolding m_textFolding; KateViewConfig *const m_config; KateRenderer *const m_renderer; KateViewInternal *const m_viewInternal; KateSpellCheckDialog *m_spell; KateBookmarks *const m_bookmarks; //* margins QSpacerItem *m_topSpacer; QSpacerItem *m_leftSpacer; QSpacerItem *m_rightSpacer; QSpacerItem *m_bottomSpacer; private Q_SLOTS: void slotHlChanged(); /** * Configuration */ public: inline KateViewConfig *config() { return m_config; } void updateConfig(); void updateDocumentConfig(); void updateRendererConfig(); private Q_SLOTS: void updateFoldingConfig(); private: bool m_startingUp; bool m_updatingDocumentConfig; // templates public: bool insertTemplateInternal(const KTextEditor::Cursor& insertPosition, const QString& templateString, const QString& script = QString()); /** * Accessors to the bars... */ public: KateViewBar *bottomViewBar() const; KateDictionaryBar *dictionaryBar(); private: KateGotoBar *gotoBar(); /** * viewbar + its widgets * they are created on demand... */ private: // created in constructor of the view KateViewBar *m_bottomViewBar; // created on demand..., only access them through the above accessors.... KateGotoBar *m_gotoBar; KateDictionaryBar *m_dictionaryBar; // input modes public: KateAbstractInputMode *currentInputMode() const; public: KTextEditor::Range visibleRange(); Q_SIGNALS: void displayRangeChanged(KTextEditor::ViewPrivate *view); protected: - bool event(QEvent *e) Q_DECL_OVERRIDE; - void paintEvent(QPaintEvent *e) Q_DECL_OVERRIDE; + bool event(QEvent *e) override; + void paintEvent(QPaintEvent *e) override; KToggleAction *m_toggleOnTheFlySpellCheck; KateSpellingMenu *m_spellingMenu; protected Q_SLOTS: void toggleOnTheFlySpellCheck(bool b); public Q_SLOTS: void changeDictionary(); void reflectOnTheFlySpellCheckStatus(bool enabled); public: KateSpellingMenu *spellingMenu(); private: bool m_userContextMenuSet; private Q_SLOTS: /** * save folding state before document reload */ void saveFoldingState(); /** * restore folding state after document reload */ void applyFoldingState(); void clearHighlights(); void createHighlights(); private: void selectionChangedForHighlights(); /** * saved folding state */ QJsonDocument m_savedFoldingState; QString m_currentTextForHighlights; QList m_rangesForHighlights; public: /** * Attribute of a range changed or range with attribute changed in given line range. * @param startLine start line of change * @param endLine end line of change * @param rangeWithAttribute attribute changed or is active, this will perhaps lead to repaints */ void notifyAboutRangeChange(int startLine, int endLine, bool rangeWithAttribute); private Q_SLOTS: /** * Delayed update for view after text ranges changed */ void slotDelayedUpdateOfView(); Q_SIGNALS: /** * Delayed update for view after text ranges changed */ void delayedUpdateOfView(); public: /** * set of ranges which had the mouse inside last time, used for rendering * @return set of ranges which had the mouse inside last time checked */ const QSet *rangesMouseIn() const { return &m_rangesMouseIn; } /** * set of ranges which had the caret inside last time, used for rendering * @return set of ranges which had the caret inside last time checked */ const QSet *rangesCaretIn() const { return &m_rangesCaretIn; } /** * check if ranges changed for mouse in and caret in * @param activationType type of activation to check */ void updateRangesIn(KTextEditor::Attribute::ActivationType activationType); // // helpers for delayed view update after ranges changes // private: /** * update already inited? */ bool m_delayedUpdateTriggered; /** * minimal line to update */ int m_lineToUpdateMin; /** * maximal line to update */ int m_lineToUpdateMax; /** * set of ranges which had the mouse inside last time */ QSet m_rangesMouseIn; /** * set of ranges which had the caret inside last time */ QSet m_rangesCaretIn; // // forward impl for KTextEditor::MessageInterface // public: /** * Used by Document::postMessage(). */ void postMessage(KTextEditor::Message *message, QList > actions); private: /** * Message widgets showing KTextEditor::Messages. * The index of the array maps to the enum KTextEditor::Message::MessagePosition. */ std::array m_messageWidgets{{nullptr}}; /** Layout for floating notifications */ KateMessageLayout *m_notificationLayout = nullptr; // for unit test 'tests/messagetest.cpp' public: KateMessageWidget *messageWidget(); private: /** * The main window responsible for this view, if any */ QPointer m_mainWindow; // // KTextEditor::PrintInterface // public Q_SLOTS: - bool print() Q_DECL_OVERRIDE; - void printPreview() Q_DECL_OVERRIDE; + bool print() override; + void printPreview() override; public: /** * Get the view status bar * @return status bar, in enabled */ KateStatusBar *statusBar () const { return m_statusBar; } /** * Toogle status bar, e.g. create or remove it */ void toggleStatusBar (); /** * Get the encoding menu * @return the encoding menu */ KateViewEncodingAction *encodingAction () const { return m_encodingAction; } /** * Get the mode menu * @return the mode menu */ KateModeMenu *modeAction () const { return m_modeAction; } private: /** * the status bar of this view */ KateStatusBar *m_statusBar; /** * the encoding selection menu, used by view + status bar */ KateViewEncodingAction *m_encodingAction; /** * the mode selection menu, used by view + status bar */ KateModeMenu *m_modeAction; /** * is automatic invocation of completion disabled temporarily? */ bool m_temporaryAutomaticInvocationDisabled; public: /** * Returns the attribute for the default style \p defaultStyle. */ - Attribute::Ptr defaultStyleAttribute(DefaultStyle defaultStyle) const Q_DECL_OVERRIDE; + Attribute::Ptr defaultStyleAttribute(DefaultStyle defaultStyle) const override; /** * Get the list of AttributeBlocks for a given \p line in the document. * * \return list of AttributeBlocks for given \p line. */ - QList lineAttributes(int line) Q_DECL_OVERRIDE; + QList lineAttributes(int line) override; private: // remember folding state to prevent refolding the first line if it was manually unfolded, // e.g. when saving a file or changing other config vars bool m_autoFoldedFirstLine; private: KateMultiClipboard m_clipboard; public: void setScrollPositionInternal(KTextEditor::Cursor &cursor); void setHorizontalScrollPositionInternal(int x); KTextEditor::Cursor maxScrollPositionInternal() const; int firstDisplayedLineInternal(LineType lineType) const; int lastDisplayedLineInternal(LineType lineType) const; QRect textAreaRectInternal() const; private: /** * script action menu, stored in scoped pointer to ensure * destruction before other QObject auto-cleanup as it * manage sub objects on its own that have this view as parent */ QScopedPointer m_scriptActionMenu; }; } #endif diff --git a/src/view/kateviewaccessible.h b/src/view/kateviewaccessible.h index c3387008..4e3257e3 100644 --- a/src/view/kateviewaccessible.h +++ b/src/view/kateviewaccessible.h @@ -1,247 +1,294 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Sebastian Sauer Copyright (C) 2012 Frederik Gladhorn 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_VIEW_ACCESSIBLE_ #define _KATE_VIEW_ACCESSIBLE_ #ifndef QT_NO_ACCESSIBILITY #include "kateviewinternal.h" #include "katedocument.h" #include #include #include /** * This class implements a QAccessible-interface for a KateViewInternal. * * This is the root class for the kateview. The \a KateCursorAccessible class * represents the cursor in the kateview and is a child of this class. */ class KateViewAccessible : public QAccessibleWidget, public QAccessibleTextInterface // FIXME maybe:, public QAccessibleEditableTextInterface { public: explicit KateViewAccessible(KateViewInternal *view) : QAccessibleWidget(view, QAccessible::EditableText) { + // to invalidate positionFromCursor cache when the document is changed + m_conn = QObject::connect(view->view()->document(), &KTextEditor::Document::textChanged, + [this](){ m_lastPosition = -1; }); } - void *interface_cast(QAccessible::InterfaceType t) Q_DECL_OVERRIDE + void *interface_cast(QAccessible::InterfaceType t) override { if (t == QAccessible::TextInterface) return static_cast(this); return nullptr; } - virtual ~KateViewAccessible() + ~KateViewAccessible() override { + QObject::disconnect(m_conn); } - QAccessibleInterface *childAt(int x, int y) const Q_DECL_OVERRIDE + QAccessibleInterface *childAt(int x, int y) const override { Q_UNUSED(x); Q_UNUSED(y); return nullptr; } - void setText(QAccessible::Text t, const QString &text) Q_DECL_OVERRIDE + void setText(QAccessible::Text t, const QString &text) override { if (t == QAccessible::Value && view()->view()->document()) { view()->view()->document()->setText(text); + m_lastPosition = -1; } } - QAccessible::State state() const Q_DECL_OVERRIDE + QAccessible::State state() const override { QAccessible::State s = QAccessibleWidget::state(); s.focusable = view()->focusPolicy() != Qt::NoFocus; s.focused = view()->hasFocus(); s.editable = true; s.multiLine = true; s.selectableText = true; return s; } - QString text(QAccessible::Text t) const Q_DECL_OVERRIDE + QString text(QAccessible::Text t) const override { QString s; if (view()->view()->document()) { if (t == QAccessible::Name) { s = view()->view()->document()->documentName(); } if (t == QAccessible::Value) { s = view()->view()->document()->text(); } } return s; } - int characterCount() const Q_DECL_OVERRIDE + int characterCount() const override { return view()->view()->document()->text().size(); } - void addSelection(int startOffset, int endOffset) Q_DECL_OVERRIDE + void addSelection(int startOffset, int endOffset) override { KTextEditor::Range range; range.setRange(cursorFromInt(startOffset), cursorFromInt(endOffset)); view()->view()->setSelection(range); view()->view()->setCursorPosition(cursorFromInt(endOffset)); } - QString attributes(int offset, int *startOffset, int *endOffset) const Q_DECL_OVERRIDE + QString attributes(int offset, int *startOffset, int *endOffset) const override { Q_UNUSED(offset); *startOffset = 0; *endOffset = characterCount(); return QString(); } - QRect characterRect(int offset) const Q_DECL_OVERRIDE + QRect characterRect(int offset) const override { KTextEditor::Cursor c = cursorFromInt(offset); if (!c.isValid()) { return QRect(); } QPoint p = view()->cursorToCoordinate(c); KTextEditor::Cursor endCursor = KTextEditor::Cursor(c.line(), c.column() + 1); QPoint size = view()->cursorToCoordinate(endCursor) - p; return QRect(view()->mapToGlobal(p), QSize(size.x(), size.y())); } - int cursorPosition() const Q_DECL_OVERRIDE + int cursorPosition() const override { KTextEditor::Cursor c = view()->primaryCursor(); return positionFromCursor(view(), c); } - int offsetAtPoint(const QPoint & /*point*/) const Q_DECL_OVERRIDE + int offsetAtPoint(const QPoint & /*point*/) const override { return 0; } - void removeSelection(int selectionIndex) Q_DECL_OVERRIDE + void removeSelection(int selectionIndex) override { if (selectionIndex != 0) { return; } view()->view()->clearSelection(); } - void scrollToSubstring(int /*startIndex*/, int /*endIndex*/) Q_DECL_OVERRIDE + void scrollToSubstring(int /*startIndex*/, int /*endIndex*/) override { // FIXME } - void selection(int selectionIndex, int *startOffset, int *endOffset) const Q_DECL_OVERRIDE + void selection(int selectionIndex, int *startOffset, int *endOffset) const override { if (selectionIndex != 0 || !view()->view()->selection()) { *startOffset = 0; *endOffset = 0; return; } KTextEditor::Range range = view()->view()->selectionRange(); *startOffset = positionFromCursor(view(), range.start()); *endOffset = positionFromCursor(view(), range.end()); } - int selectionCount() const Q_DECL_OVERRIDE + int selectionCount() const override { return view()->view()->selection() ? 1 : 0; } - void setCursorPosition(int position) Q_DECL_OVERRIDE + void setCursorPosition(int position) override { view()->view()->setCursorPosition(cursorFromInt(position)); } - void setSelection(int selectionIndex, int startOffset, int endOffset) Q_DECL_OVERRIDE + void setSelection(int selectionIndex, int startOffset, int endOffset) override { if (selectionIndex != 0) { return; } KTextEditor::Range range = KTextEditor::Range(cursorFromInt(startOffset), cursorFromInt(endOffset)); view()->view()->setSelection(range); } - QString text(int startOffset, int endOffset) const Q_DECL_OVERRIDE + QString text(int startOffset, int endOffset) const override { if (startOffset > endOffset) { return QString(); } return view()->view()->document()->text().mid(startOffset, endOffset - startOffset); } - static int positionFromCursor(KateViewInternal *view, const KTextEditor::Cursor &cursor) + /** + * When possible, using the last returned value m_lastPosition do the count + * from the last cursor position m_lastCursor. + * @return the number of chars (including one character for new lines) + * from the beggining of the file. + */ + int positionFromCursor(KateViewInternal *view, const KTextEditor::Cursor &cursor) const { - int pos = 0; - for (int line = 0; line < cursor.line(); ++line) { - // length of the line plus newline - pos += view->view()->document()->line(line).size() + 1; + int pos = m_lastPosition; + const auto *doc = view->view()->document(); + + // m_lastPosition < 0 is invalid, calculate from the beginning of the document + if (m_lastPosition < 0 || view != m_lastView) { + pos = 0; + // Default (worst) case + for (int line = 0; line < cursor.line(); ++line) { + pos += doc->line(line).size(); + } + // new line for each line + pos += cursor.line(); + m_lastView = view; + } else { + // if the lines are the same, just add the cursor.column(), otherwise + if (cursor.line() != m_lastCursor.line()) { + // If the cursor is after the previous cursor + if (m_lastCursor.line() < cursor.line()) { + for (int line = m_lastCursor.line(); line < cursor.line(); ++line) { + pos += doc->line(line).size(); + } + // add new line character for each line + pos += cursor.line() - m_lastCursor.line(); + } else { + for (int line = cursor.line(); line < m_lastCursor.line(); ++line) { + pos -= doc->line(line).size(); + } + // remove new line character for each line + pos -= m_lastCursor.line() - cursor.line(); + } + } } - pos += cursor.column(); + m_lastCursor = cursor; + m_lastPosition = pos; - return pos; + return pos + cursor.column(); } private: inline KateViewInternal *view() const { return static_cast(object()); } KTextEditor::Cursor cursorFromInt(int position) const { int line = 0; for (;;) { const QString lineString = view()->view()->document()->line(line); if (position > lineString.length()) { // one is the newline position -= lineString.length() + 1; ++line; } else { break; } } return KTextEditor::Cursor(line, position); } QString textLine(int shiftLines, int offset, int *startOffset, int *endOffset) const { KTextEditor::Cursor pos = cursorFromInt(offset); pos.setColumn(0); if (shiftLines) { pos.setLine(pos.line() + shiftLines); } *startOffset = positionFromCursor(view(), pos); QString line = view()->view()->document()->line(pos.line()) + QLatin1Char('\n'); *endOffset = *startOffset + line.length(); return line; } +private: + // Cache data for positionFromCursor + mutable KateViewInternal *m_lastView; + mutable KTextEditor::Cursor m_lastCursor; + // m_lastPosition stores the positionFromCursor, with the cursor always in column 0 + mutable int m_lastPosition; + // to disconnect the signal + QMetaObject::Connection m_conn; }; /** * Factory-function used to create \a KateViewAccessible instances for KateViewInternal * to make the KateViewInternal accessible. */ QAccessibleInterface *accessibleInterfaceFactory(const QString &key, QObject *object) { Q_UNUSED(key) //if (key == QLatin1String("KateViewInternal")) if (KateViewInternal *view = qobject_cast(object)) { return new KateViewAccessible(view); } return nullptr; } #endif #endif diff --git a/src/view/kateviewhelpers.cpp b/src/view/kateviewhelpers.cpp index 0c8aa617..835d0e05 100644 --- a/src/view/kateviewhelpers.cpp +++ b/src/view/kateviewhelpers.cpp @@ -1,2895 +1,2902 @@ /* This file is part of the KDE libraries Copyright (C) 2008, 2009 Matthew Woehlke Copyright (C) 2007 Mirko Stocker Copyright (C) 2002 John Firebaugh Copyright (C) 2001 Anders Lund Copyright (C) 2001 Christoph Cullmann Copyright (C) 2011 Svyatoslav Kuzmich Copyright (C) 2012 Kåre Särs (Minimap) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kateviewhelpers.h" #include "katecmd.h" #include #include #include #include "kateconfig.h" #include "katedocument.h" #include #include "katerenderer.h" #include "kateview.h" #include "kateviewinternal.h" #include "katelayoutcache.h" #include "katetextlayout.h" #include "kateglobal.h" #include "katepartdebug.h" #include "katecommandrangeexpressionparser.h" #include "kateabstractinputmode.h" #include "katetextpreview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //BEGIN KateMessageLayout KateMessageLayout::KateMessageLayout(QWidget *parent) : QLayout(parent) { } KateMessageLayout::~KateMessageLayout() { while (QLayoutItem *item = takeAt(0)) delete item; } void KateMessageLayout::addItem(QLayoutItem *item) { Q_ASSERT(false); add(item, KTextEditor::Message::CenterInView); } void KateMessageLayout::addWidget(QWidget *widget, KTextEditor::Message::MessagePosition pos) { add(new QWidgetItem(widget), pos); } int KateMessageLayout::count() const { return m_items.size(); } QLayoutItem *KateMessageLayout::itemAt(int index) const { if (index < 0 || index >= m_items.size()) - return 0; + return nullptr; return m_items[index]->item; } void KateMessageLayout::setGeometry(const QRect &rect) { QLayout::setGeometry(rect); const int s = spacing(); const QRect adjustedRect = rect.adjusted(s, s, -s, -s); for (auto wrapper : m_items) { QLayoutItem *item = wrapper->item; auto position = wrapper->position; if (position == KTextEditor::Message::TopInView) { const QRect r(adjustedRect.width() - item->sizeHint().width(), s, item->sizeHint().width(), item->sizeHint().height()); item->setGeometry(r); } else if (position == KTextEditor::Message::BottomInView) { const QRect r(adjustedRect.width() - item->sizeHint().width(), adjustedRect.height() - item->sizeHint().height(), item->sizeHint().width(), item->sizeHint().height()); item->setGeometry(r); } else if (position == KTextEditor::Message::CenterInView) { QRect r(0, 0, item->sizeHint().width(), item->sizeHint().height()); r.moveCenter(adjustedRect.center()); item->setGeometry(r); } else { Q_ASSERT_X(false, "setGeometry", "Only TopInView, CenterInView, and BottomInView are supported."); } } } QSize KateMessageLayout::sizeHint() const { return QSize(); } QLayoutItem *KateMessageLayout::takeAt(int index) { if (index >= 0 && index < m_items.size()) { ItemWrapper *layoutStruct = m_items.takeAt(index); return layoutStruct->item; } - return 0; + return nullptr; } void KateMessageLayout::add(QLayoutItem *item, KTextEditor::Message::MessagePosition pos) { m_items.push_back(new ItemWrapper(item, pos)); } //END KateMessageLayout //BEGIN KateScrollBar static const int s_lineWidth = 100; static const int s_pixelMargin = 8; static const int s_linePixelIncLimit = 6; const unsigned char KateScrollBar::characterOpacity[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // <- 15 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 0, 0, 0, 0, // <- 31 0, 125, 41, 221, 138, 195, 218, 21, 142, 142, 137, 137, 97, 87, 87, 140, // <- 47 223, 164, 183, 190, 191, 193, 214, 158, 227, 216, 103, 113, 146, 140, 146, 149, // <- 63 248, 204, 240, 174, 217, 197, 178, 205, 209, 176, 168, 211, 160, 246, 238, 218, // <- 79 195, 229, 227, 196, 167, 212, 188, 238, 197, 169, 189, 158, 21, 151, 115, 90, // <- 95 15, 192, 209, 153, 208, 187, 162, 221, 183, 149, 161, 191, 146, 203, 167, 182, // <- 111 208, 203, 139, 166, 158, 167, 157, 189, 164, 179, 156, 167, 145, 166, 109, 0, // <- 127 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // <- 143 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // <- 159 0, 125, 184, 187, 146, 201, 127, 203, 89, 194, 156, 141, 117, 87, 202, 88, // <- 175 115, 165, 118, 121, 85, 190, 236, 87, 88, 111, 151, 140, 194, 191, 203, 148, // <- 191 215, 215, 222, 224, 223, 234, 230, 192, 208, 208, 216, 217, 187, 187, 194, 195, // <- 207 228, 255, 228, 228, 235, 239, 237, 150, 255, 222, 222, 229, 232, 180, 197, 225, // <- 223 208, 208, 216, 217, 212, 230, 218, 170, 202, 202, 211, 204, 156, 156, 165, 159, // <- 239 214, 194, 197, 197, 206, 206, 201, 132, 214, 183, 183, 192, 187, 195, 227, 198 }; KateScrollBar::KateScrollBar(Qt::Orientation orientation, KateViewInternal *parent) : QScrollBar(orientation, parent->m_view) , m_middleMouseDown(false) , m_leftMouseDown(false) , m_view(parent->m_view) , m_doc(parent->doc()) , m_viewInternal(parent) , m_textPreview(nullptr) , m_showMarks(false) , m_showMiniMap(false) , m_miniMapAll(true) , m_miniMapWidth(40) , m_grooveHeight(height()) , m_linesModified(0) { connect(this, SIGNAL(valueChanged(int)), this, SLOT(sliderMaybeMoved(int))); connect(m_doc, SIGNAL(marksChanged(KTextEditor::Document*)), this, SLOT(marksChanged())); m_updateTimer.setInterval(300); m_updateTimer.setSingleShot(true); QTimer::singleShot(10, this, SLOT(updatePixmap())); // track mouse for text preview widget setMouseTracking(orientation == Qt::Vertical); // setup text preview timer m_delayTextPreviewTimer.setSingleShot(true); m_delayTextPreviewTimer.setInterval(250); connect(&m_delayTextPreviewTimer, SIGNAL(timeout()), this, SLOT(showTextPreview())); } KateScrollBar::~KateScrollBar() { delete m_textPreview; } void KateScrollBar::setShowMiniMap(bool b) { if (b && !m_showMiniMap) { connect(m_view, SIGNAL(selectionChanged(KTextEditor::View*)), &m_updateTimer, SLOT(start()), Qt::UniqueConnection); connect(m_doc, SIGNAL(textChanged(KTextEditor::Document*)), &m_updateTimer, SLOT(start()), Qt::UniqueConnection); connect(m_view, SIGNAL(delayedUpdateOfView()), &m_updateTimer, SLOT(start()), Qt::UniqueConnection); connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updatePixmap()), Qt::UniqueConnection); connect(&(m_view->textFolding()), SIGNAL(foldingRangesChanged()), &m_updateTimer, SLOT(start()), Qt::UniqueConnection); } else if (!b) { disconnect(&m_updateTimer); } m_showMiniMap = b; updateGeometry(); update(); } QSize KateScrollBar::sizeHint() const { if (m_showMiniMap) { return QSize(m_miniMapWidth, QScrollBar::sizeHint().height()); } return QScrollBar::sizeHint(); } int KateScrollBar::minimapYToStdY(int y) { // Check if the minimap fills the whole scrollbar if (m_stdGroveRect.height() == m_mapGroveRect.height()) { return y; } // check if y is on the step up/down if ((y < m_stdGroveRect.top()) || (y > m_stdGroveRect.bottom())) { return y; } if (y < m_mapGroveRect.top()) { return m_stdGroveRect.top() + 1; } if (y > m_mapGroveRect.bottom()) { return m_stdGroveRect.bottom() - 1; } // check for div/0 if (m_mapGroveRect.height() == 0) { return y; } int newY = (y - m_mapGroveRect.top()) * m_stdGroveRect.height() / m_mapGroveRect.height(); newY += m_stdGroveRect.top(); return newY; } void KateScrollBar::mousePressEvent(QMouseEvent *e) { // delete text preview hideTextPreview(); if (e->button() == Qt::MidButton) { m_middleMouseDown = true; } else if (e->button() == Qt::LeftButton) { m_leftMouseDown = true; } if (m_showMiniMap) { if (m_leftMouseDown) { // if we show the minimap left-click jumps directly to the selected position int newVal = (e->pos().y()-m_mapGroveRect.top()) / (double)m_mapGroveRect.height() * (double)(maximum()+pageStep()) - pageStep()/2; newVal = qBound(0, newVal, maximum()); setSliderPosition(newVal); } QMouseEvent eMod(QEvent::MouseButtonPress, QPoint(6, minimapYToStdY(e->pos().y())), e->button(), e->buttons(), e->modifiers()); QScrollBar::mousePressEvent(&eMod); } else { QScrollBar::mousePressEvent(e); } m_toolTipPos = e->globalPos() - QPoint(e->pos().x(), 0); const int fromLine = m_viewInternal->toRealCursor(m_viewInternal->startPos()).line() + 1; const int lastLine = m_viewInternal->toRealCursor(m_viewInternal->endPos()).line() + 1; QToolTip::showText(m_toolTipPos, i18nc("from line - to line", "
%1

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

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

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

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

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

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

") + end; } bool KateCmdLineEdit::event(QEvent *e) { if (e->type() == QEvent::QueryWhatsThis) { setWhatsThis(helptext(QPoint())); e->accept(); return true; } return KLineEdit::event(e); } /** * Parse the text as a command. * * The following is a simple PEG grammar for the syntax of the command. * (A PEG grammar is like a BNF grammar, except that "/" stands for * ordered choice: only the first matching rule is used. In other words, * the parsing is short-circuited in the manner of the "or" operator in * programming languages, and so the grammar is unambiguous.) * * Text <- Range? Command * / Position * Range <- Position ("," Position)? * / "%" * Position <- Base Offset? * Base <- Line * / LastLine * / ThisLine * / Mark * Offset <- [+-] Base * Line <- [0-9]+ * LastLine <- "$" * ThisLine <- "." * Mark <- "'" [a-z] */ void KateCmdLineEdit::slotReturnPressed(const QString &text) { if (text.isEmpty()) { return; } // silently ignore leading space characters uint n = 0; const uint textlen = text.length(); while ((n < textlen) && (text[n].isSpace())) { n++; } if (n >= textlen) { return; } QString cmd = text.mid(n); // Parse any leading range expression, and strip it (and maybe do some other transforms on the command). QString leadingRangeExpression; KTextEditor::Range range = CommandRangeExpressionParser::parseRangeExpression(cmd, m_view, leadingRangeExpression, cmd); // Built in help: if the command starts with "help", [try to] show some help if (cmd.startsWith(QLatin1String("help"))) { QWhatsThis::showText(mapToGlobal(QPoint(0, 0)), helptext(QPoint())); clear(); KateCmd::self()->appendHistory(cmd); m_histpos = KateCmd::self()->historyLength(); m_oldText.clear(); return; } if (cmd.length() > 0) { KTextEditor::Command *p = KateCmd::self()->queryCommand(cmd); m_oldText = leadingRangeExpression + cmd; m_msgMode = true; // the following commands changes the focus themselves, so bar should be hidden before execution. if (QRegExp(QLatin1String("buffer|b|new|vnew|bp|bprev|bn|bnext|bf|bfirst|bl|blast|edit|e")).exactMatch(cmd.split(QLatin1Char(' ')).at(0))) { emit hideRequested(); } if (!p) { setText(i18n("No such command: \"%1\"", cmd)); } else if (range.isValid() && !p->supportsRange(cmd)) { // Raise message, when the command does not support ranges. setText(i18n("Error: No range allowed for command \"%1\".", cmd)); } else { QString msg; if (p->exec(m_view, cmd, msg, range)) { // append command along with range (will be empty if none given) to history KateCmd::self()->appendHistory(leadingRangeExpression + cmd); m_histpos = KateCmd::self()->historyLength(); m_oldText.clear(); if (msg.length() > 0) { setText(i18n("Success: ") + msg); } else if (isVisible()) { // always hide on success without message emit hideRequested(); } } else { if (msg.length() > 0) { if (msg.contains(QLatin1Char('\n'))) { // multiline error, use widget with more space QWhatsThis::showText(mapToGlobal(QPoint(0, 0)), msg); } else { setText(msg); } } else { setText(i18n("Command \"%1\" failed.", cmd)); } } } } // clean up if (completionObject() != KateCmd::self()->commandCompletionObject()) { KCompletion *c = completionObject(); setCompletionObject(KateCmd::self()->commandCompletionObject()); delete c; } m_command = nullptr; m_cmdend = 0; // the following commands change the focus themselves if (!QRegExp(QLatin1String("buffer|b|new|vnew|bp|bprev|bn|bnext|bf|bfirst|bl|blast|edit|e")).exactMatch(cmd.split(QLatin1Char(' ')).at(0))) { m_view->setFocus(); } if (isVisible()) { m_hideTimer->start(4000); } } void KateCmdLineEdit::hideLineEdit() // unless i have focus ;) { if (! hasFocus()) { emit hideRequested(); } } void KateCmdLineEdit::focusInEvent(QFocusEvent *ev) { if (m_msgMode) { m_msgMode = false; setText(m_oldText); selectAll(); } KLineEdit::focusInEvent(ev); } void KateCmdLineEdit::keyPressEvent(QKeyEvent *ev) { if (ev->key() == Qt::Key_Escape || (ev->key() == Qt::Key_BracketLeft && ev->modifiers() == Qt::ControlModifier)) { m_view->setFocus(); hideLineEdit(); clear(); } else if (ev->key() == Qt::Key_Up) { fromHistory(true); } else if (ev->key() == Qt::Key_Down) { fromHistory(false); } uint cursorpos = cursorPosition(); KLineEdit::keyPressEvent(ev); // during typing, let us see if we have a valid command if (! m_cmdend || cursorpos <= m_cmdend) { QChar c; if (! ev->text().isEmpty()) { c = ev->text().at(0); } if (! m_cmdend && ! c.isNull()) { // we have no command, so lets see if we got one if (! c.isLetterOrNumber() && c != QLatin1Char('-') && c != QLatin1Char('_')) { m_command = KateCmd::self()->queryCommand(text().trimmed()); if (m_command) { //qCDebug(LOG_KTE)<<"keypress in commandline: We have a command! "<queryCommand(text().trimmed()); if (m_command) { //qCDebug(LOG_KTE)<<"keypress in commandline: We have a command! "<commandCompletionObject()) { KCompletion *c = completionObject(); setCompletionObject(KateCmd::self()->commandCompletionObject()); delete c; } m_cmdend = 0; } } // if we got a command, check if it wants to do something. if (m_command) { KCompletion *cmpl = m_command->completionObject(m_view, text().left(m_cmdend).trimmed()); if (cmpl) { // We need to prepend the current command name + flag string // when completion is done //qCDebug(LOG_KTE)<<"keypress in commandline: Setting completion object!"; setCompletionObject(cmpl); } } } else if (m_command && !ev->text().isEmpty()) { // check if we should call the commands processText() if (m_command->wantsToProcessText(text().left(m_cmdend).trimmed())) { m_command->processText(m_view, text()); } } } void KateCmdLineEdit::fromHistory(bool up) { if (! KateCmd::self()->historyLength()) { return; } QString s; if (up) { if (m_histpos > 0) { m_histpos--; s = KateCmd::self()->fromHistory(m_histpos); } } else { if (m_histpos < (KateCmd::self()->historyLength() - 1)) { m_histpos++; s = KateCmd::self()->fromHistory(m_histpos); } else { m_histpos = KateCmd::self()->historyLength(); setText(m_oldText); } } if (! s.isEmpty()) { // Select the argument part of the command, so that it is easy to overwrite setText(s); static QRegExp reCmd = QRegExp(QLatin1String(".*[\\w\\-]+(?:[^a-zA-Z0-9_-]|:\\w+)(.*)")); if (reCmd.indexIn(text()) == 0) { setSelection(text().length() - reCmd.cap(1).length(), reCmd.cap(1).length()); } } } //END KateCmdLineEdit //BEGIN KateIconBorder using namespace KTextEditor; KateIconBorder::KateIconBorder(KateViewInternal *internalView, QWidget *parent) : QWidget(parent) , m_view(internalView->m_view) , m_doc(internalView->doc()) , m_viewInternal(internalView) , m_iconBorderOn(false) , m_lineNumbersOn(false) , m_relLineNumbersOn(false) , m_updateRelLineNumbers(false) , m_foldingMarkersOn(false) , m_dynWrapIndicatorsOn(false) , m_annotationBorderOn(false) , m_dynWrapIndicators(0) , m_lastClickedLine(-1) , m_cachedLNWidth(0) , m_maxCharWidth(0.0) , iconPaneWidth(16) , m_annotationBorderWidth(6) , m_foldingPreview(nullptr) , m_foldingRange(nullptr) , m_nextHighlightBlock(-2) , m_currentBlockLine(-1) { setAttribute(Qt::WA_StaticContents); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); setMouseTracking(true); m_doc->setMarkDescription(MarkInterface::markType01, i18n("Bookmark")); m_doc->setMarkPixmap(MarkInterface::markType01, QIcon::fromTheme(QStringLiteral("bookmarks")).pixmap(16, 16)); updateFont(); m_delayFoldingHlTimer.setSingleShot(true); m_delayFoldingHlTimer.setInterval(150); connect(&m_delayFoldingHlTimer, SIGNAL(timeout()), this, SLOT(showBlock())); // user interaction (scrolling) hides e.g. preview connect(m_view, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), this, SLOT(displayRangeChanged())); } KateIconBorder::~KateIconBorder() { delete m_foldingPreview; delete m_foldingRange; } void KateIconBorder::setIconBorderOn(bool enable) { if (enable == m_iconBorderOn) { return; } m_iconBorderOn = enable; updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } void KateIconBorder::setAnnotationBorderOn(bool enable) { if (enable == m_annotationBorderOn) { return; } m_annotationBorderOn = enable; emit m_view->annotationBorderVisibilityChanged(m_view, enable); updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } void KateIconBorder::removeAnnotationHovering() { // remove hovering if it's still there if (m_annotationBorderOn && !m_hoveredAnnotationGroupIdentifier.isEmpty()) { m_hoveredAnnotationGroupIdentifier.clear(); hideAnnotationTooltip(); QTimer::singleShot(0, this, SLOT(update())); } } void KateIconBorder::setLineNumbersOn(bool enable) { if (enable == m_lineNumbersOn) { return; } m_lineNumbersOn = enable; m_dynWrapIndicatorsOn = (m_dynWrapIndicators == 1) ? enable : m_dynWrapIndicators; updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } void KateIconBorder::setRelLineNumbersOn(bool enable) { if (enable == m_relLineNumbersOn) { return; } m_relLineNumbersOn = enable; /* * We don't have to touch the m_dynWrapIndicatorsOn because * we already got it right from the m_lineNumbersOn */ updateGeometry(); QTimer::singleShot( 0, this, SLOT(update()) ); } void KateIconBorder::updateForCursorLineChange() { if (m_relLineNumbersOn) { m_updateRelLineNumbers = true; } // always do normal update, e.g. for different current line color! update(); } void KateIconBorder::setDynWrapIndicators(int state) { if (state == m_dynWrapIndicators) { return; } m_dynWrapIndicators = state; m_dynWrapIndicatorsOn = (state == 1) ? m_lineNumbersOn : state; updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } void KateIconBorder::setFoldingMarkersOn(bool enable) { if (enable == m_foldingMarkersOn) { return; } m_foldingMarkersOn = enable; updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } QSize KateIconBorder::sizeHint() const { int w = 0; if (m_iconBorderOn) { w += iconPaneWidth + 2; } if (m_annotationBorderOn) { w += m_annotationBorderWidth + 2; } if (m_lineNumbersOn || (m_view->dynWordWrap() && m_dynWrapIndicatorsOn)) { w += lineNumberWidth() + 2; } if (m_foldingMarkersOn) { w += iconPaneWidth; } // space for the line modification system border if (m_view->config()->lineModification()) { w += 3; } // two pixel space w += 2; return QSize(w, 0); } // This function (re)calculates the maximum width of any of the digit characters (0 -> 9) // for graceful handling of variable-width fonts as the linenumber font. void KateIconBorder::updateFont() { const QFontMetricsF &fm = m_view->renderer()->config()->fontMetrics(); m_maxCharWidth = 0.0; // Loop to determine the widest numeric character in the current font. // 48 is ascii '0' for (int i = 48; i < 58; i++) { const qreal charWidth = ceil(fm.width(QChar(i))); m_maxCharWidth = qMax(m_maxCharWidth, charWidth); } // the icon pane scales with the font... iconPaneWidth = fm.height(); updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } int KateIconBorder::lineNumberWidth() const { // width = (number of digits + 1) * char width const int digits = (int) ceil(log10((double)(m_view->doc()->lines() + 1))); int width = m_lineNumbersOn ? (int)ceil((digits + 1) * m_maxCharWidth) : 0; if (m_view->dynWordWrap() && m_dynWrapIndicatorsOn) { // HACK: 16 == style().scrollBarExtent().width() width = qMax(16 + 4, width); if (m_cachedLNWidth != width || m_oldBackgroundColor != m_view->renderer()->config()->iconBarColor()) { int w = 16;// HACK: 16 == style().scrollBarExtent().width() style().scrollBarExtent().width(); int h = m_view->renderer()->lineHeight(); QSize newSize(w * devicePixelRatio(), h * devicePixelRatio()); if ((m_arrow.size() != newSize || m_oldBackgroundColor != m_view->renderer()->config()->iconBarColor()) && !newSize.isEmpty()) { m_arrow = QPixmap(newSize); m_arrow.setDevicePixelRatio(devicePixelRatio()); QPainter p(&m_arrow); p.fillRect(0, 0, w, h, m_view->renderer()->config()->iconBarColor()); h = m_view->renderer()->config()->fontMetrics().ascent(); p.setPen(m_view->renderer()->config()->lineNumberColor()); QPainterPath path; path.moveTo(w / 2, h / 2); path.lineTo(w / 2, 0); path.lineTo(w / 4, h / 4); path.lineTo(0, 0); path.lineTo(0, h / 2); path.lineTo(w / 2, h - 1); path.lineTo(w * 3 / 4, h - 1); path.lineTo(w - 1, h * 3 / 4); path.lineTo(w * 3 / 4, h / 2); path.lineTo(0, h / 2); p.drawPath(path); } } } return width; } void KateIconBorder::paintEvent(QPaintEvent *e) { paintBorder(e->rect().x(), e->rect().y(), e->rect().width(), e->rect().height()); } static void paintTriangle(QPainter &painter, QColor c, int xOffset, int yOffset, int width, int height, bool open) { painter.setRenderHint(QPainter::Antialiasing); qreal size = qMin(width, height); if (KColorUtils::luma(c) > 0.25) { c = KColorUtils::darken(c); } else { c = KColorUtils::shade(c, 0.1); } QPen pen; pen.setJoinStyle(Qt::RoundJoin); pen.setColor(c); pen.setWidthF(1.5); painter.setPen(pen); painter.setBrush(c); // let some border, if possible size *= 0.6; qreal halfSize = size / 2; qreal halfSizeP = halfSize * 0.6; QPointF middle(xOffset + (qreal)width / 2, yOffset + (qreal)height / 2); if (open) { QPointF points[3] = { middle + QPointF(-halfSize, -halfSizeP), middle + QPointF(halfSize, -halfSizeP), middle + QPointF(0, halfSizeP) }; painter.drawConvexPolygon(points, 3); } else { QPointF points[3] = { middle + QPointF(-halfSizeP, -halfSize), middle + QPointF(-halfSizeP, halfSize), middle + QPointF(halfSizeP, 0) }; painter.drawConvexPolygon(points, 3); } painter.setRenderHint(QPainter::Antialiasing, false); } void KateIconBorder::paintBorder(int /*x*/, int y, int /*width*/, int height) { uint h = m_view->renderer()->lineHeight(); uint startz = (y / h); uint endz = startz + 1 + (height / h); uint lineRangesSize = m_viewInternal->cache()->viewCacheLineCount(); uint currentLine = m_view->cursorPosition().line(); // center the folding boxes int m_px = (h - 11) / 2; if (m_px < 0) { m_px = 0; } int lnWidth(0); if (m_lineNumbersOn || (m_view->dynWordWrap() && m_dynWrapIndicatorsOn)) { // avoid calculating unless needed ;-) lnWidth = lineNumberWidth(); if (lnWidth != m_cachedLNWidth || m_oldBackgroundColor != m_view->renderer()->config()->iconBarColor()) { // we went from n0 ->n9 lines or vice verca // this causes an extra updateGeometry() first time the line numbers // are displayed, but sizeHint() is supposed to be const so we can't set // the cached value there. m_cachedLNWidth = lnWidth; m_oldBackgroundColor = m_view->renderer()->config()->iconBarColor(); updateGeometry(); update(); return; } } int w(this->width()); // sane value/calc only once QPainter p(this); p.setRenderHints(QPainter::TextAntialiasing); p.setFont(m_view->renderer()->config()->font()); // for line numbers KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel(); for (uint z = startz; z <= endz; z++) { int y = h * z; int realLine = -1; if (z < lineRangesSize) { realLine = m_viewInternal->cache()->viewLine(z).line(); } int lnX = 0; p.fillRect(0, y, w - 5, h, m_view->renderer()->config()->iconBarColor()); p.fillRect(w - 5, y, 5, h, m_view->renderer()->config()->backgroundColor()); // icon pane if (m_iconBorderOn) { p.setPen(m_view->renderer()->config()->separatorColor()); p.setBrush(m_view->renderer()->config()->separatorColor()); p.drawLine(lnX + iconPaneWidth + 1, y, lnX + iconPaneWidth + 1, y + h); if ((realLine > -1) && (m_viewInternal->cache()->viewLine(z).startCol() == 0)) { uint mrk(m_doc->mark(realLine)); // call only once if (mrk) { for (uint bit = 0; bit < 32; bit++) { MarkInterface::MarkTypes markType = (MarkInterface::MarkTypes)(1 << bit); if (mrk & markType) { QPixmap px_mark(m_doc->markPixmap(markType)); if (!px_mark.isNull() && h > 0 && iconPaneWidth > 0) { if (iconPaneWidth < px_mark.width() || h < (uint)px_mark.height()) { px_mark = px_mark.scaled(iconPaneWidth, h, Qt::KeepAspectRatio); } // center the mark pixmap int x_px = (iconPaneWidth - px_mark.width()) / 2; if (x_px < 0) { x_px = 0; } int y_px = (h - px_mark.height()) / 2; if (y_px < 0) { y_px = 0; } p.drawPixmap(lnX + x_px, y + y_px, px_mark); } } } } } lnX += iconPaneWidth + 2; } // annotation information if (m_annotationBorderOn) { // Draw a border line between annotations and the line numbers p.setPen(m_view->renderer()->config()->lineNumberColor()); p.setBrush(m_view->renderer()->config()->lineNumberColor()); int borderWidth = m_annotationBorderWidth; p.drawLine(lnX + borderWidth + 1, y, lnX + borderWidth + 1, y + h); if ((realLine > -1) && model) { // Fetch data from the model QVariant text = model->data(realLine, Qt::DisplayRole); QVariant foreground = model->data(realLine, Qt::ForegroundRole); QVariant background = model->data(realLine, Qt::BackgroundRole); // Fill the background if (background.isValid()) { p.fillRect(lnX, y, borderWidth + 1, h, background.value()); } // Set the pen for drawing the foreground if (foreground.isValid()) { p.setPen(foreground.value()); } // Draw a border around all adjacent entries that have the same text as the currently hovered one const QVariant identifier = model->data( realLine, (Qt::ItemDataRole) KTextEditor::AnnotationModel::GroupIdentifierRole ); if( m_hoveredAnnotationGroupIdentifier == identifier.toString() ) { p.drawLine(lnX, y, lnX, y + h); p.drawLine(lnX + borderWidth, y, lnX + borderWidth, y + h); QVariant beforeText = model->data(realLine - 1, Qt::DisplayRole); QVariant afterText = model->data(realLine + 1, Qt::DisplayRole); if (((beforeText.isValid() && beforeText.canConvert() && text.isValid() && text.canConvert() && beforeText.toString() != text.toString()) || realLine == 0) && m_viewInternal->cache()->viewLine(z).viewLine() == 0) { p.drawLine(lnX + 1, y, lnX + borderWidth, y); } if (((afterText.isValid() && afterText.canConvert() && text.isValid() && text.canConvert() && afterText.toString() != text.toString()) || realLine == m_view->doc()->lines() - 1) && m_viewInternal->cache()->viewLine(z).viewLine() == m_viewInternal->cache()->viewLineCount(realLine) - 1) { p.drawLine(lnX + 1, y + h - 1, lnX + borderWidth, y + h - 1); } } if (foreground.isValid()) { QPen pen = p.pen(); pen.setWidth(1); p.setPen(pen); } // Now draw the normal text if (text.isValid() && text.canConvert() && (m_viewInternal->cache()->viewLine(z).startCol() == 0)) { p.drawText(lnX + 3, y, borderWidth - 3, h, Qt::AlignLeft | Qt::AlignVCenter, text.toString()); } } // adjust current X position and reset the pen and brush lnX += borderWidth + 2; } // line number if (m_lineNumbersOn || (m_view->dynWordWrap() && m_dynWrapIndicatorsOn)) { if (realLine > -1) { int distanceToCurrent = abs(realLine - static_cast(currentLine)); QColor color; if (distanceToCurrent == 0) { color = m_view->renderer()->config()->currentLineNumberColor(); } else { color = m_view->renderer()->config()->lineNumberColor(); } p.setPen(color); p.setBrush(color); if (m_viewInternal->cache()->viewLine(z).startCol() == 0) { if (m_relLineNumbersOn) { if (distanceToCurrent == 0) { p.drawText(lnX + m_maxCharWidth / 2, y, lnWidth - m_maxCharWidth, h, Qt::TextDontClip|Qt::AlignLeft|Qt::AlignVCenter, QString::number(realLine + 1)); } else { p.drawText(lnX + m_maxCharWidth / 2, y, lnWidth - m_maxCharWidth, h, Qt::TextDontClip|Qt::AlignRight|Qt::AlignVCenter, QString::number(distanceToCurrent)); } if (m_updateRelLineNumbers) { m_updateRelLineNumbers = false; update(); } } else if (m_lineNumbersOn) { p.drawText(lnX + m_maxCharWidth / 2, y, lnWidth - m_maxCharWidth, h, Qt::TextDontClip | Qt::AlignRight | Qt::AlignVCenter, QString::number(realLine + 1)); } } else if (m_view->dynWordWrap() && m_dynWrapIndicatorsOn) { p.drawPixmap(lnX + lnWidth - (m_arrow.width() / m_arrow.devicePixelRatio()) - 2, y, m_arrow); } } lnX += lnWidth + 2; } // folding markers if (m_foldingMarkersOn) { // first icon border background p.fillRect(lnX, y, iconPaneWidth, h, m_view->renderer()->config()->iconBarColor()); // possible additional folding highlighting if ((realLine >= 0) && m_foldingRange && m_foldingRange->overlapsLine(realLine)) { p.save(); // use linear gradient as brush QLinearGradient g(lnX, y, lnX + iconPaneWidth, y); const QColor foldingColor(m_view->renderer()->config()->foldingColor()); g.setColorAt(0, foldingColor); g.setColorAt(0.3, foldingColor.lighter(110)); g.setColorAt(1, foldingColor); p.setBrush(g); p.setPen(foldingColor); p.setClipRect(lnX, y, iconPaneWidth, h); p.setRenderHint(QPainter::Antialiasing); const qreal r = 4.0; if (m_foldingRange->start().line() == realLine && m_viewInternal->cache()->viewLine(z).viewLine() == 0) { p.drawRect(lnX, y, iconPaneWidth, h + r); } else if (m_foldingRange->end().line() == realLine && m_viewInternal->cache()->viewLine(z).viewLine() == m_viewInternal->cache()->viewLineCount(realLine) - 1) { p.drawRect(lnX, y - r, iconPaneWidth, h + r); } else { p.drawRect(lnX, y - r, iconPaneWidth, h + 2 * r); } p.restore(); } if ((realLine >= 0) && (m_viewInternal->cache()->viewLine(z).startCol() == 0)) { QVector > startingRanges = m_view->textFolding().foldingRangesStartingOnLine(realLine); bool anyFolded = false; for (int i = 0; i < startingRanges.size(); ++i) if (startingRanges[i].second & Kate::TextFolding::Folded) { anyFolded = true; } Kate::TextLine tl = m_doc->kateTextLine(realLine); if (!startingRanges.isEmpty() || tl->markedAsFoldingStart()) { if (anyFolded) { paintTriangle(p, m_view->renderer()->config()->foldingColor(), lnX, y, iconPaneWidth, h, false); } else { paintTriangle(p, m_view->renderer()->config()->foldingColor(), lnX, y, iconPaneWidth, h, true); } } } lnX += iconPaneWidth; } // modified line system if (m_view->config()->lineModification() && realLine > -1 && !m_doc->url().isEmpty()) { // one pixel space ++lnX; Kate::TextLine tl = m_doc->plainKateTextLine(realLine); if (tl->markedAsModified()) { p.fillRect(lnX, y, 3, h, m_view->renderer()->config()->modifiedLineColor()); } if (tl->markedAsSavedOnDisk()) { p.fillRect(lnX, y, 3, h, m_view->renderer()->config()->savedLineColor()); } } } } KateIconBorder::BorderArea KateIconBorder::positionToArea(const QPoint &p) const { // see KateIconBorder::sizeHint() for pixel spacings int x = 0; if (m_iconBorderOn) { x += iconPaneWidth; if (p.x() <= x) { return IconBorder; } x += 2; } if (this->m_annotationBorderOn) { x += m_annotationBorderWidth; if (p.x() <= x) { return AnnotationBorder; } x += 2; } if (m_lineNumbersOn || m_dynWrapIndicators) { x += lineNumberWidth(); if (p.x() <= x) { return LineNumbers; } x += 2; } if (m_foldingMarkersOn) { x += iconPaneWidth; if (p.x() <= x) { return FoldingMarkers; } } if (m_view->config()->lineModification()) { x += 3 + 2; if (p.x() <= x) { return ModificationBorder; } } return None; } void KateIconBorder::mousePressEvent(QMouseEvent *e) { const KateTextLayout &t = m_viewInternal->yToKateTextLayout(e->y()); if (t.isValid()) { m_lastClickedLine = t.line(); - if (positionToArea(e->pos()) != IconBorder && positionToArea(e->pos()) != AnnotationBorder) { + const auto area = positionToArea(e->pos()); + // IconBorder and AnnotationBorder have their own behavior; don't forward to view + if (area != IconBorder && area != AnnotationBorder) { + const auto pos = QPoint(0, e->y()); + if (area == LineNumbers && e->button() == Qt::LeftButton && !(e->modifiers() & Qt::ShiftModifier)) { + // setup view so the following mousePressEvent will select the line + m_viewInternal->beginSelectLine(pos); + } QMouseEvent forward(QEvent::MouseButtonPress, - QPoint(0, e->y()), e->button(), e->buttons(), e->modifiers()); + pos, e->button(), e->buttons(), e->modifiers()); m_viewInternal->mousePressEvent(&forward); } return e->accept(); } QWidget::mousePressEvent(e); } void KateIconBorder::showDelayedBlock(int line) { // save the line over which the mouse hovers // either we start the timer for delay, or we show the block immediately // if the moving range already exists m_nextHighlightBlock = line; if (!m_foldingRange) { if (!m_delayFoldingHlTimer.isActive()) { m_delayFoldingHlTimer.start(); } } else { showBlock(); } } void KateIconBorder::showBlock() { if (m_nextHighlightBlock == m_currentBlockLine) { return; } m_currentBlockLine = m_nextHighlightBlock; if (m_currentBlockLine >= m_doc->buffer().lines()) { return; } /** * compute to which folding range we belong * FIXME: optimize + perhaps have some better threshold or use timers! */ KTextEditor::Range newRange = KTextEditor::Range::invalid(); for (int line = m_currentBlockLine; line >= qMax(0, m_currentBlockLine - 1024); --line) { /** * try if we have folding range from that line, should be fast per call */ KTextEditor::Range foldingRange = m_doc->buffer().computeFoldingRangeForStartLine(line); if (!foldingRange.isValid()) { continue; } /** * does the range reach us? */ if (foldingRange.overlapsLine(m_currentBlockLine)) { newRange = foldingRange; break; } } if (newRange.isValid() && m_foldingRange && *m_foldingRange == newRange) { // new range equals the old one, nothing to do. return; } else { // the ranges differ, delete the old, if it exists delete m_foldingRange; m_foldingRange = nullptr; } if (newRange.isValid()) { //qCDebug(LOG_KTE) << "new folding hl-range:" << newRange; m_foldingRange = m_doc->newMovingRange(newRange, KTextEditor::MovingRange::ExpandRight); KTextEditor::Attribute::Ptr attr(new KTextEditor::Attribute()); /** * create highlighting color with alpha for the range! */ QColor result = m_view->renderer()->config()->foldingColor(); result.setAlphaF(0.5); attr->setBackground(QBrush(result)); m_foldingRange->setView(m_view); // use z depth defined in moving ranges interface m_foldingRange->setZDepth(-100.0); m_foldingRange->setAttribute(attr); } // show text preview, if a folded region starts here bool foldUnderMouse = false; if (m_foldingRange && m_view->config()->foldingPreview()) { const QPoint globalPos = QCursor::pos(); const QPoint pos = mapFromGlobal(globalPos); const KateTextLayout &t = m_view->m_viewInternal->yToKateTextLayout(pos.y()); if (t.isValid() && positionToArea(pos) == FoldingMarkers) { const int realLine = t.line(); foldUnderMouse = !m_view->textFolding().isLineVisible(realLine + 1); if (foldUnderMouse) { if (!m_foldingPreview) { m_foldingPreview = new KateTextPreview(m_view); m_foldingPreview->setAttribute(Qt::WA_ShowWithoutActivating); m_foldingPreview->setFrameStyle(QFrame::StyledPanel); // event filter to catch application WindowDeactivate event, to hide the preview window // qApp->installEventFilter(this); } // TODO: use KateViewInternal::maxLen() somehow to compute proper width for amount of lines to display // try using the end line of the range for proper popup height const int lineCount = qMin(m_foldingRange->numberOfLines() + 1, (height() - pos.y()) / m_view->renderer()->lineHeight()); m_foldingPreview->resize(m_view->width() / 2, lineCount * m_view->renderer()->lineHeight() + 2 * m_foldingPreview->frameWidth()); const int xGlobal = mapToGlobal(QPoint(width(), 0)).x(); const int yGlobal = m_view->mapToGlobal(m_view->cursorToCoordinate(KTextEditor::Cursor(realLine, 0))).y(); m_foldingPreview->move(QPoint(xGlobal, yGlobal) - m_foldingPreview->contentsRect().topLeft()); m_foldingPreview->setLine(realLine); m_foldingPreview->setCenterView(false); m_foldingPreview->setShowFoldedLines(true); m_foldingPreview->raise(); m_foldingPreview->show(); } } } if (!foldUnderMouse) { delete m_foldingPreview; } /** * repaint */ repaint(); } void KateIconBorder::hideBlock() { if (m_delayFoldingHlTimer.isActive()) { m_delayFoldingHlTimer.stop(); } m_nextHighlightBlock = -2; m_currentBlockLine = -1; delete m_foldingRange; m_foldingRange = nullptr; delete m_foldingPreview; } void KateIconBorder::leaveEvent(QEvent *event) { hideBlock(); removeAnnotationHovering(); QWidget::leaveEvent(event); } void KateIconBorder::mouseMoveEvent(QMouseEvent *e) { const KateTextLayout &t = m_viewInternal->yToKateTextLayout(e->y()); if (t.isValid()) { if (positionToArea(e->pos()) == FoldingMarkers) { showDelayedBlock(t.line()); } else { hideBlock(); } if (positionToArea(e->pos()) == AnnotationBorder) { KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel(); if (model) { m_hoveredAnnotationGroupIdentifier = model->data( t.line(), (Qt::ItemDataRole) KTextEditor::AnnotationModel::GroupIdentifierRole ).toString(); showAnnotationTooltip(t.line(), e->globalPos()); QTimer::singleShot(0, this, SLOT(update())); } } else { if (positionToArea(e->pos()) == IconBorder) { m_doc->requestMarkTooltip(t.line(), e->globalPos()); } m_hoveredAnnotationGroupIdentifier.clear(); hideAnnotationTooltip(); QTimer::singleShot(0, this, SLOT(update())); } if (positionToArea(e->pos()) != IconBorder) { QPoint p = m_viewInternal->mapFromGlobal(e->globalPos()); QMouseEvent forward(QEvent::MouseMove, p, e->button(), e->buttons(), e->modifiers()); m_viewInternal->mouseMoveEvent(&forward); } } else { // remove hovering if it's still there removeAnnotationHovering(); } QWidget::mouseMoveEvent(e); } void KateIconBorder::mouseReleaseEvent(QMouseEvent *e) { const int cursorOnLine = m_viewInternal->yToKateTextLayout(e->y()).line(); if (cursorOnLine == m_lastClickedLine && cursorOnLine >= 0 && cursorOnLine <= m_doc->lastLine()) { BorderArea area = positionToArea(e->pos()); if (area == IconBorder) { if (e->button() == Qt::LeftButton) { if (!m_doc->handleMarkClick(cursorOnLine)) { KateViewConfig *config = m_view->config(); const uint editBits = m_doc->editableMarks(); // is the default or the only editable mark const uint singleMark = qPopulationCount(editBits) > 1 ? editBits & config->defaultMarkType() : editBits; if (singleMark) { if (m_doc->mark(cursorOnLine) & singleMark) { m_doc->removeMark(cursorOnLine, singleMark); } else { m_doc->addMark(cursorOnLine, singleMark); } } else if (config->allowMarkMenu()) { showMarkMenu(cursorOnLine, QCursor::pos()); } } } else if (e->button() == Qt::RightButton) { showMarkMenu(cursorOnLine, QCursor::pos()); } } if (area == FoldingMarkers) { // ask the folding info for this line, if any folds are around! QVector > startingRanges = m_view->textFolding().foldingRangesStartingOnLine(cursorOnLine); bool anyFolded = false; for (int i = 0; i < startingRanges.size(); ++i) if (startingRanges[i].second & Kate::TextFolding::Folded) { anyFolded = true; } // fold or unfold all ranges, remember if any action happened! bool actionDone = false; for (int i = 0; i < startingRanges.size(); ++i) { actionDone = (anyFolded ? m_view->textFolding().unfoldRange(startingRanges[i].first) : m_view->textFolding().foldRange(startingRanges[i].first)) || actionDone; } // if no action done, try to fold it, create non-persistent folded range, if possible! if (!actionDone) { // either use the fold for this line or the range that is highlighted ATM if any! KTextEditor::Range foldingRange = m_view->doc()->buffer().computeFoldingRangeForStartLine(cursorOnLine); if (!foldingRange.isValid() && m_foldingRange) { foldingRange = m_foldingRange->toRange(); } m_view->textFolding().newFoldingRange(foldingRange, Kate::TextFolding::Folded); } delete m_foldingPreview; } if (area == AnnotationBorder) { const bool singleClick = style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this); if (e->button() == Qt::LeftButton && singleClick) { emit m_view->annotationActivated(m_view, cursorOnLine); } else if (e->button() == Qt::RightButton) { showAnnotationMenu(cursorOnLine, e->globalPos()); } } } QMouseEvent forward(QEvent::MouseButtonRelease, QPoint(0, e->y()), e->button(), e->buttons(), e->modifiers()); m_viewInternal->mouseReleaseEvent(&forward); } void KateIconBorder::mouseDoubleClickEvent(QMouseEvent *e) { int cursorOnLine = m_viewInternal->yToKateTextLayout(e->y()).line(); if (cursorOnLine == m_lastClickedLine && cursorOnLine <= m_doc->lastLine()) { BorderArea area = positionToArea(e->pos()); const bool singleClick = style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this); if (area == AnnotationBorder && !singleClick) { emit m_view->annotationActivated(m_view, cursorOnLine); } } QMouseEvent forward(QEvent::MouseButtonDblClick, QPoint(0, e->y()), e->button(), e->buttons(), e->modifiers()); m_viewInternal->mouseDoubleClickEvent(&forward); } void KateIconBorder::wheelEvent(QWheelEvent *e) { QCoreApplication::sendEvent(m_viewInternal, e); } void KateIconBorder::showMarkMenu(uint line, const QPoint &pos) { if (m_doc->handleMarkContextMenu(line, pos)) { return; } if (!m_view->config()->allowMarkMenu()) { return; } QMenu markMenu; QMenu selectDefaultMark; auto selectDefaultMarkActionGroup = new QActionGroup(&selectDefaultMark); QVector vec(33); int i = 1; for (uint bit = 0; bit < 32; bit++) { MarkInterface::MarkTypes markType = (MarkInterface::MarkTypes)(1 << bit); if (!(m_doc->editableMarks() & markType)) { continue; } QAction *mA; QAction *dMA; const QPixmap icon = m_doc->markPixmap(markType); if (!m_doc->markDescription(markType).isEmpty()) { mA = markMenu.addAction(icon, m_doc->markDescription(markType)); dMA = selectDefaultMark.addAction(icon, m_doc->markDescription(markType)); } else { mA = markMenu.addAction(icon, i18n("Mark Type %1", bit + 1)); dMA = selectDefaultMark.addAction(icon, i18n("Mark Type %1", bit + 1)); } selectDefaultMarkActionGroup->addAction(dMA); mA->setData(i); mA->setCheckable(true); dMA->setData(i + 100); dMA->setCheckable(true); if (m_doc->mark(line) & markType) { mA->setChecked(true); } if (markType & KateViewConfig::global()->defaultMarkType()) { dMA->setChecked(true); } vec[i++] = markType; } if (markMenu.actions().count() == 0) { return; } if (markMenu.actions().count() > 1) { markMenu.addAction(i18n("Set Default Mark Type"))->setMenu(&selectDefaultMark); } QAction *rA = markMenu.exec(pos); if (!rA) { return; } int result = rA->data().toInt(); if (result > 100) { KateViewConfig::global()->setDefaultMarkType(vec[result - 100]); } else { MarkInterface::MarkTypes markType = (MarkInterface::MarkTypes) vec[result]; if (m_doc->mark(line) & markType) { m_doc->removeMark(line, markType); } else { m_doc->addMark(line, markType); } } } void KateIconBorder::showAnnotationTooltip(int line, const QPoint &pos) { KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel(); if (model) { QVariant data = model->data(line, Qt::ToolTipRole); QString tip = data.toString(); if (!tip.isEmpty()) { QToolTip::showText(pos, data.toString(), this); } } } int KateIconBorder::annotationLineWidth(int line) { KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel(); if (model) { QVariant data = model->data(line, Qt::DisplayRole); return data.toString().length() * m_maxCharWidth + 8; } return 8; } void KateIconBorder::updateAnnotationLine(int line) { if (annotationLineWidth(line) > m_annotationBorderWidth) { m_annotationBorderWidth = annotationLineWidth(line); updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } } void KateIconBorder::showAnnotationMenu(int line, const QPoint &pos) { QMenu menu; QAction a(i18n("Disable Annotation Bar"), &menu); a.setIcon(QIcon::fromTheme(QStringLiteral("dialog-close"))); menu.addAction(&a); emit m_view->annotationContextMenuAboutToShow(m_view, &menu, line); if (menu.exec(pos) == &a) { m_view->setAnnotationBorderVisible(false); } } void KateIconBorder::hideAnnotationTooltip() { QToolTip::hideText(); } void KateIconBorder::updateAnnotationBorderWidth() { m_annotationBorderWidth = 6; KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel(); if (model) { for (int i = 0; i < m_view->doc()->lines(); i++) { int curwidth = annotationLineWidth(i); if (curwidth > m_annotationBorderWidth) { m_annotationBorderWidth = curwidth; } } } updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } void KateIconBorder::annotationModelChanged(KTextEditor::AnnotationModel *oldmodel, KTextEditor::AnnotationModel *newmodel) { if (oldmodel) { oldmodel->disconnect(this); } if (newmodel) { connect(newmodel, SIGNAL(reset()), this, SLOT(updateAnnotationBorderWidth())); connect(newmodel, SIGNAL(lineChanged(int)), this, SLOT(updateAnnotationLine(int))); } updateAnnotationBorderWidth(); } void KateIconBorder::displayRangeChanged() { hideBlock(); removeAnnotationHovering(); } //END KateIconBorder //BEGIN KateViewEncodingAction // Acording to http://www.iana.org/assignments/ianacharset-mib // the default/unknown mib value is 2. #define MIB_DEFAULT 2 bool lessThanAction(KSelectAction *a, KSelectAction *b) { return a->text() < b->text(); } void KateViewEncodingAction::Private::init() { QList actions; q->setToolBarMode(MenuMode); int i; foreach (const QStringList &encodingsForScript, KCharsets::charsets()->encodingsByScript()) { KSelectAction *tmp = new KSelectAction(encodingsForScript.at(0), q); for (i = 1; i < encodingsForScript.size(); ++i) { tmp->addAction(encodingsForScript.at(i)); } q->connect(tmp, SIGNAL(triggered(QAction*)), q, SLOT(_k_subActionTriggered(QAction*))); //tmp->setCheckable(true); actions << tmp; } qSort(actions.begin(), actions.end(), lessThanAction); foreach (KSelectAction *action, actions) { q->addAction(action); } } void KateViewEncodingAction::Private::_k_subActionTriggered(QAction *action) { if (currentSubAction == action) { return; } currentSubAction = action; bool ok = false; int mib = q->mibForName(action->text(), &ok); if (ok) { emit q->KSelectAction::triggered(action->text()); emit q->triggered(q->codecForMib(mib)); } } KateViewEncodingAction::KateViewEncodingAction(KTextEditor::DocumentPrivate *_doc, KTextEditor::ViewPrivate *_view, const QString &text, QObject *parent, bool saveAsMode) : KSelectAction(text, parent), doc(_doc), view(_view), d(new Private(this)) , m_saveAsMode(saveAsMode) { d->init(); connect(menu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShow())); connect(this, SIGNAL(triggered(QString)), this, SLOT(setEncoding(QString))); } KateViewEncodingAction::~KateViewEncodingAction() { delete d; } void KateViewEncodingAction::slotAboutToShow() { setCurrentCodec(doc->config()->encoding()); } void KateViewEncodingAction::setEncoding(const QString &e) { /** * in save as mode => trigger saveAs */ if (m_saveAsMode) { doc->documentSaveAsWithEncoding(e); return; } /** * else switch encoding */ doc->userSetEncodingForNextReload(); doc->setEncoding(e); view->reloadFile(); } int KateViewEncodingAction::mibForName(const QString &codecName, bool *ok) const { // FIXME logic is good but code is ugly bool success = false; int mib = MIB_DEFAULT; KCharsets *charsets = KCharsets::charsets(); QTextCodec *codec = charsets->codecForName(codecName, success); if (!success) { // Maybe we got a description name instead codec = charsets->codecForName(charsets->encodingForName(codecName), success); } if (codec) { mib = codec->mibEnum(); } if (ok) { *ok = success; } if (success) { return mib; } qCWarning(LOG_KTE) << "Invalid codec name: " << codecName; return MIB_DEFAULT; } QTextCodec *KateViewEncodingAction::codecForMib(int mib) const { if (mib == MIB_DEFAULT) { // FIXME offer to change the default codec return QTextCodec::codecForLocale(); } else { return QTextCodec::codecForMib(mib); } } QTextCodec *KateViewEncodingAction::currentCodec() const { return codecForMib(currentCodecMib()); } bool KateViewEncodingAction::setCurrentCodec(QTextCodec *codec) { disconnect(this, SIGNAL(triggered(QString)), this, SLOT(setEncoding(QString))); int i, j; for (i = 0; i < actions().size(); ++i) { if (actions().at(i)->menu()) { for (j = 0; j < actions().at(i)->menu()->actions().size(); ++j) { if (!j && !actions().at(i)->menu()->actions().at(j)->data().isNull()) { continue; } if (actions().at(i)->menu()->actions().at(j)->isSeparator()) { continue; } if (codec == KCharsets::charsets()->codecForName(actions().at(i)->menu()->actions().at(j)->text())) { d->currentSubAction = actions().at(i)->menu()->actions().at(j); d->currentSubAction->setChecked(true); } else { actions().at(i)->menu()->actions().at(j)->setChecked(false); } } } } connect(this, SIGNAL(triggered(QString)), this, SLOT(setEncoding(QString))); return true; } QString KateViewEncodingAction::currentCodecName() const { return d->currentSubAction->text(); } bool KateViewEncodingAction::setCurrentCodec(const QString &codecName) { return setCurrentCodec(KCharsets::charsets()->codecForName(codecName)); } int KateViewEncodingAction::currentCodecMib() const { return mibForName(currentCodecName()); } bool KateViewEncodingAction::setCurrentCodec(int mib) { return setCurrentCodec(codecForMib(mib)); } //END KateViewEncodingAction //BEGIN KateViewBar related classes KateViewBarWidget::KateViewBarWidget(bool addCloseButton, QWidget *parent) : QWidget(parent) , m_viewBar(nullptr) { QHBoxLayout *layout = new QHBoxLayout(this); // NOTE: Here be cosmetics. layout->setMargin(0); // hide button if (addCloseButton) { QToolButton *hideButton = new QToolButton(this); hideButton->setAutoRaise(true); hideButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close"))); connect(hideButton, SIGNAL(clicked()), SIGNAL(hideMe())); layout->addWidget(hideButton); layout->setAlignment(hideButton, Qt::AlignLeft | Qt::AlignTop); } // widget to be used as parent for the real content m_centralWidget = new QWidget(this); layout->addWidget(m_centralWidget); setLayout(layout); setFocusProxy(m_centralWidget); } KateViewBar::KateViewBar(bool external, QWidget *parent, KTextEditor::ViewPrivate *view) : QWidget(parent), m_external(external), m_view(view), m_permanentBarWidget(nullptr) { m_layout = new QVBoxLayout(this); m_stack = new QStackedWidget(this); m_layout->addWidget(m_stack); m_layout->setMargin(0); m_stack->hide(); hide(); } void KateViewBar::addBarWidget(KateViewBarWidget *newBarWidget) { // just ignore additional adds for already existing widgets if (hasBarWidget(newBarWidget)) { return; } // add new widget, invisible... newBarWidget->hide(); m_stack->addWidget(newBarWidget); newBarWidget->setAssociatedViewBar(this); connect(newBarWidget, SIGNAL(hideMe()), SLOT(hideCurrentBarWidget())); } void KateViewBar::removeBarWidget(KateViewBarWidget *barWidget) { // remove only if there if (!hasBarWidget(barWidget)) { return; } m_stack->removeWidget(barWidget); barWidget->setAssociatedViewBar(nullptr); barWidget->hide(); disconnect(barWidget, nullptr, this, nullptr); } void KateViewBar::addPermanentBarWidget(KateViewBarWidget *barWidget) { Q_ASSERT(barWidget); Q_ASSERT(!m_permanentBarWidget); m_stack->addWidget(barWidget); m_stack->setCurrentWidget(barWidget); m_stack->show(); m_permanentBarWidget = barWidget; m_permanentBarWidget->show(); setViewBarVisible(true); } void KateViewBar::removePermanentBarWidget(KateViewBarWidget *barWidget) { Q_ASSERT(m_permanentBarWidget == barWidget); Q_UNUSED(barWidget); const bool hideBar = m_stack->currentWidget() == m_permanentBarWidget; m_permanentBarWidget->hide(); m_stack->removeWidget(m_permanentBarWidget); m_permanentBarWidget = nullptr; if (hideBar) { m_stack->hide(); setViewBarVisible(false); } } bool KateViewBar::hasPermanentWidget(KateViewBarWidget *barWidget) const { return (m_permanentBarWidget == barWidget); } void KateViewBar::showBarWidget(KateViewBarWidget *barWidget) { Q_ASSERT(barWidget != nullptr); if (barWidget != qobject_cast(m_stack->currentWidget())) { hideCurrentBarWidget(); } // raise correct widget m_stack->setCurrentWidget(barWidget); barWidget->show(); barWidget->setFocus(Qt::ShortcutFocusReason); m_stack->show(); setViewBarVisible(true); } bool KateViewBar::hasBarWidget(KateViewBarWidget *barWidget) const { return m_stack->indexOf(barWidget) != -1; } void KateViewBar::hideCurrentBarWidget() { KateViewBarWidget *current = qobject_cast(m_stack->currentWidget()); if (current) { current->closed(); } // if we have any permanent widget, make it visible again if (m_permanentBarWidget) { m_stack->setCurrentWidget (m_permanentBarWidget); } else { // else: hide the bar m_stack->hide(); setViewBarVisible(false); } m_view->setFocus(); } void KateViewBar::setViewBarVisible(bool visible) { if (m_external) { if (visible) { m_view->mainWindow()->showViewBar(m_view); } else { m_view->mainWindow()->hideViewBar(m_view); } } else { setVisible(visible); } } bool KateViewBar::hiddenOrPermanent() const { KateViewBarWidget *current = qobject_cast(m_stack->currentWidget()); if (!isVisible() || (m_permanentBarWidget && m_permanentBarWidget == current)) { return true; } return false; } void KateViewBar::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape) { hideCurrentBarWidget(); return; } QWidget::keyPressEvent(event); } void KateViewBar::hideEvent(QHideEvent *event) { Q_UNUSED(event); // if (!event->spontaneous()) // m_view->setFocus(); } //END KateViewBar related classes KatePasteMenu::KatePasteMenu(const QString &text, KTextEditor::ViewPrivate *view) : KActionMenu(text, view) , m_view(view) { connect(menu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShow())); } void KatePasteMenu::slotAboutToShow() { menu()->clear(); /** * insert complete paste history */ int i = 0; Q_FOREACH (const auto &texts, KTextEditor::EditorPrivate::self()->clipboardHistory()) { /** * get text for the menu ;) */ QString text; Q_FOREACH (const auto& t, texts) { if ( !text.isEmpty() ) { text.append(QLatin1String(" ")); } text.append(t); } if ( texts.size() > 1 ) { text.prepend(QLatin1String("[") + i18nc("%1 entries", "always plural", texts.size()) + QLatin1String("] ")); } QString leftPart = (text.size() > 48) ? (text.left(48) + QLatin1String("...")) : text; QAction *a = menu()->addAction(leftPart.replace(QLatin1String("\n"), QLatin1String(" ")), this, SLOT(paste())); a->setData(i++); } } void KatePasteMenu::paste() { if (!sender()) { return; } QAction *action = qobject_cast(sender()); if (!action) { return; } // get index int i = action->data().toInt(); if (i >= KTextEditor::EditorPrivate::self()->clipboardHistory().size()) { return; } // paste m_view->pasteInternal(KTextEditor::EditorPrivate::self()->clipboardHistory().at(i)); } diff --git a/src/view/kateviewhelpers.h b/src/view/kateviewhelpers.h index 9e2723ea..66fba0fc 100644 --- a/src/view/kateviewhelpers.h +++ b/src/view/kateviewhelpers.h @@ -1,634 +1,634 @@ /* This file is part of the KDE libraries Copyright (C) 2002 John Firebaugh Copyright (C) 2001 Anders Lund Copyright (C) 2001 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. */ #ifndef __KATE_VIEW_HELPERS_H__ #define __KATE_VIEW_HELPERS_H__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "katetextline.h" namespace KTextEditor { class DocumentPrivate; } namespace KTextEditor { class ViewPrivate; } class KateViewInternal; #define MAXFOLDINGCOLORS 16 class KateLineInfo; class KateTextPreview; namespace KTextEditor { class Command; class AnnotationModel; class MovingRange; } class QTimer; class QVBoxLayout; /** * Class to layout KTextEditor::Message%s in KateView. Only the floating * positions TopInView, CenterInView, and BottomInView are supported. * AboveView and BelowView are not supported and ASSERT. */ class KateMessageLayout : public QLayout { public: explicit KateMessageLayout (QWidget *parent); - ~KateMessageLayout(); + ~KateMessageLayout() override; void addWidget(QWidget *widget, KTextEditor::Message::MessagePosition pos); - int count() const Q_DECL_OVERRIDE; - QLayoutItem *itemAt(int index) const Q_DECL_OVERRIDE; - void setGeometry(const QRect &rect) Q_DECL_OVERRIDE; - QSize sizeHint() const Q_DECL_OVERRIDE; - QLayoutItem *takeAt(int index) Q_DECL_OVERRIDE; + int count() const override; + QLayoutItem *itemAt(int index) const override; + void setGeometry(const QRect &rect) override; + QSize sizeHint() const override; + QLayoutItem *takeAt(int index) override; void add(QLayoutItem *item, KTextEditor::Message::MessagePosition pos); private: - void addItem(QLayoutItem *item) Q_DECL_OVERRIDE; // never called publically + void addItem(QLayoutItem *item) override; // never called publically struct ItemWrapper { ItemWrapper(QLayoutItem *i, KTextEditor::Message::MessagePosition pos) : item(i) , position(pos) {} QLayoutItem * item = nullptr; KTextEditor::Message::MessagePosition position; }; QVector m_items; }; /** * This class is required because QScrollBar's sliderMoved() signal is * really supposed to be a sliderDragged() signal... so this way we can capture * MMB slider moves as well * * Also, it adds some useful indicators on the scrollbar. */ class KateScrollBar : public QScrollBar { Q_OBJECT public: KateScrollBar(Qt::Orientation orientation, class KateViewInternal *parent); - virtual ~KateScrollBar(); - QSize sizeHint() const Q_DECL_OVERRIDE; + ~KateScrollBar() override; + QSize sizeHint() const override; inline bool showMarks() const { return m_showMarks; } inline void setShowMarks(bool b) { m_showMarks = b; update(); } inline bool showMiniMap() const { return m_showMiniMap; } void setShowMiniMap(bool b); inline bool miniMapAll() const { return m_miniMapAll; } inline void setMiniMapAll(bool b) { m_miniMapAll = b; updateGeometry(); update(); } inline bool miniMapWidth() const { return m_miniMapWidth; } inline void setMiniMapWidth(int width) { m_miniMapWidth = width; updateGeometry(); update(); } inline void queuePixmapUpdate() { m_updateTimer.start(); } Q_SIGNALS: void sliderMMBMoved(int value); protected: - void mousePressEvent(QMouseEvent *e) Q_DECL_OVERRIDE; - void mouseReleaseEvent(QMouseEvent *e) Q_DECL_OVERRIDE; - void mouseMoveEvent(QMouseEvent *e) Q_DECL_OVERRIDE; - void leaveEvent(QEvent *event) Q_DECL_OVERRIDE; - bool eventFilter(QObject *object, QEvent *event) Q_DECL_OVERRIDE; - void paintEvent(QPaintEvent *e) Q_DECL_OVERRIDE; - void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE; - void sliderChange(SliderChange change) Q_DECL_OVERRIDE; + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void leaveEvent(QEvent *event) override; + bool eventFilter(QObject *object, QEvent *event) override; + void paintEvent(QPaintEvent *e) override; + void resizeEvent(QResizeEvent *) override; + void sliderChange(SliderChange change) override; protected Q_SLOTS: void sliderMaybeMoved(int value); void marksChanged(); public Q_SLOTS: void updatePixmap(); private Q_SLOTS: void showTextPreview(); private: void showTextPreviewDelayed(); void hideTextPreview(); void redrawMarks(); void recomputeMarksPositions(); void miniMapPaintEvent(QPaintEvent *e); void normalPaintEvent(QPaintEvent *e); int minimapYToStdY(int y); const QColor charColor(const QVector &attributes, int &attributeIndex, const QList &decorations, const QColor &defaultColor, int x, QChar ch); bool m_middleMouseDown; bool m_leftMouseDown; KTextEditor::ViewPrivate *m_view; KTextEditor::DocumentPrivate *m_doc; class KateViewInternal *m_viewInternal; QPointer m_textPreview; QTimer m_delayTextPreviewTimer; QHash m_lines; bool m_showMarks; bool m_showMiniMap; bool m_miniMapAll; int m_miniMapWidth; QPixmap m_pixmap; int m_grooveHeight; QRect m_stdGroveRect; QRect m_mapGroveRect; QTimer m_updateTimer; QPoint m_toolTipPos; // lists of lines added/removed recently to avoid scrollbar flickering QHash m_linesAdded; int m_linesModified; static const unsigned char characterOpacity[256]; }; class KateIconBorder : public QWidget { Q_OBJECT public: KateIconBorder(KateViewInternal *internalView, QWidget *parent); - virtual ~KateIconBorder(); + ~KateIconBorder() override; // VERY IMPORTANT ;) - QSize sizeHint() const Q_DECL_OVERRIDE; + QSize sizeHint() const override; void updateFont(); int lineNumberWidth() const; void setIconBorderOn(bool enable); void setLineNumbersOn(bool enable); void setRelLineNumbersOn(bool enable); void setAnnotationBorderOn(bool enable); void setDynWrapIndicators(int state); int dynWrapIndicators() const { return m_dynWrapIndicators; } bool dynWrapIndicatorsOn() const { return m_dynWrapIndicatorsOn; } void setFoldingMarkersOn(bool enable); void toggleIconBorder() { setIconBorderOn(!iconBorderOn()); } void toggleLineNumbers() { setLineNumbersOn(!lineNumbersOn()); } void toggleFoldingMarkers() { setFoldingMarkersOn(!foldingMarkersOn()); } inline bool iconBorderOn() const { return m_iconBorderOn; } inline bool lineNumbersOn() const { return m_lineNumbersOn; } inline bool viRelNumbersOn() const { return m_relLineNumbersOn; } inline bool foldingMarkersOn() const { return m_foldingMarkersOn; } inline bool annotationBorderOn() const { return m_annotationBorderOn; } void updateForCursorLineChange(); enum BorderArea { None, LineNumbers, IconBorder, FoldingMarkers, AnnotationBorder, ModificationBorder }; BorderArea positionToArea(const QPoint &) const; public Q_SLOTS: void updateAnnotationBorderWidth(); void updateAnnotationLine(int line); void annotationModelChanged(KTextEditor::AnnotationModel *oldmodel, KTextEditor::AnnotationModel *newmodel); void displayRangeChanged(); private: - void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE; + void paintEvent(QPaintEvent *) override; void paintBorder(int x, int y, int width, int height); - void mousePressEvent(QMouseEvent *) Q_DECL_OVERRIDE; - void mouseMoveEvent(QMouseEvent *) Q_DECL_OVERRIDE; - void mouseReleaseEvent(QMouseEvent *) Q_DECL_OVERRIDE; - void mouseDoubleClickEvent(QMouseEvent *) Q_DECL_OVERRIDE; - void leaveEvent(QEvent *event) Q_DECL_OVERRIDE; - void wheelEvent(QWheelEvent *e) Q_DECL_OVERRIDE; + void mousePressEvent(QMouseEvent *) override; + void mouseMoveEvent(QMouseEvent *) override; + void mouseReleaseEvent(QMouseEvent *) override; + void mouseDoubleClickEvent(QMouseEvent *) override; + void leaveEvent(QEvent *event) override; + void wheelEvent(QWheelEvent *e) override; void showMarkMenu(uint line, const QPoint &pos); void showAnnotationTooltip(int line, const QPoint &pos); void hideAnnotationTooltip(); void removeAnnotationHovering(); void showAnnotationMenu(int line, const QPoint &pos); int annotationLineWidth(int line); KTextEditor::ViewPrivate *m_view; KTextEditor::DocumentPrivate *m_doc; KateViewInternal *m_viewInternal; bool m_iconBorderOn: 1; bool m_lineNumbersOn: 1; bool m_relLineNumbersOn:1; bool m_updateRelLineNumbers:1; bool m_foldingMarkersOn: 1; bool m_dynWrapIndicatorsOn: 1; bool m_annotationBorderOn: 1; int m_dynWrapIndicators; int m_lastClickedLine; int m_cachedLNWidth; qreal m_maxCharWidth; int iconPaneWidth; int m_annotationBorderWidth; mutable QPixmap m_arrow; mutable QColor m_oldBackgroundColor; QPointer m_foldingPreview; KTextEditor::MovingRange *m_foldingRange; int m_nextHighlightBlock; int m_currentBlockLine; QTimer m_delayFoldingHlTimer; void showDelayedBlock(int line); void hideBlock(); private Q_SLOTS: void showBlock(); private: QString m_hoveredAnnotationGroupIdentifier; void initializeFoldingColors(); }; class KateViewEncodingAction: public KSelectAction { Q_OBJECT Q_PROPERTY(QString codecName READ currentCodecName WRITE setCurrentCodec) Q_PROPERTY(int codecMib READ currentCodecMib) public: KateViewEncodingAction(KTextEditor::DocumentPrivate *_doc, KTextEditor::ViewPrivate *_view, const QString &text, QObject *parent, bool saveAsMode = false); ~KateViewEncodingAction(); int mibForName(const QString &codecName, bool *ok = nullptr) const; QTextCodec *codecForMib(int mib) const; QTextCodec *currentCodec() const; bool setCurrentCodec(QTextCodec *codec); QString currentCodecName() const; bool setCurrentCodec(const QString &codecName); int currentCodecMib() const; bool setCurrentCodec(int mib); Q_SIGNALS: /** * Specific (proper) codec was selected */ void triggered(QTextCodec *codec); private: KTextEditor::DocumentPrivate *doc; KTextEditor::ViewPrivate *view; class Private { public: Private(KateViewEncodingAction *parent) : q(parent), currentSubAction(nullptr) { } void init(); void _k_subActionTriggered(QAction *); KateViewEncodingAction *q; QAction *currentSubAction; }; Private *const d; Q_PRIVATE_SLOT(d, void _k_subActionTriggered(QAction *)) const bool m_saveAsMode; private Q_SLOTS: void setEncoding(const QString &e); void slotAboutToShow(); }; class KateViewBar; class KateViewBarWidget : public QWidget { Q_OBJECT friend class KateViewBar; public: explicit KateViewBarWidget(bool addCloseButton, QWidget *parent = nullptr); virtual void closed() {} /// returns the currently associated KateViewBar and 0, if it is not associated KateViewBar *viewBar() { return m_viewBar; } protected: /** * @return widget that should be used to add controls to bar widget */ QWidget *centralWidget() { return m_centralWidget; } Q_SIGNALS: void hideMe(); // for friend class KateViewBar private: void setAssociatedViewBar(KateViewBar *bar) { m_viewBar = bar; } private: QWidget *m_centralWidget; KateViewBar *m_viewBar; // 0-pointer, if not added to a view bar }; class KateViewBar : public QWidget { Q_OBJECT public: KateViewBar(bool external, QWidget *parent, KTextEditor::ViewPrivate *view); /** * Adds a widget to this viewbar. * Widget is initially invisible, you should call showBarWidget, to show it. * Several widgets can be added to the bar, but only one can be visible */ void addBarWidget(KateViewBarWidget *newBarWidget); /** * Removes a widget from this viewbar. * Removing a widget makes sense if it takes a lot of space vertically, * because we use a QStackedWidget to maintain the same height for all * widgets in the viewbar. */ void removeBarWidget(KateViewBarWidget *barWidget); /** * @return if viewbar has widget @p barWidget */ bool hasBarWidget(KateViewBarWidget *barWidget) const; /** * Shows barWidget that was previously added with addBarWidget. * @see hideCurrentBarWidget */ void showBarWidget(KateViewBarWidget *barWidget); /** * Adds widget that will be always shown in the viewbar. * After adding permanent widget viewbar is immediately shown. * ViewBar with permanent widget won't hide itself * until permanent widget is removed. * OTOH showing/hiding regular barWidgets will work as usual * (they will be shown above permanent widget) * * If permanent widget already exists, asserts! */ void addPermanentBarWidget(KateViewBarWidget *barWidget); /** * Removes permanent bar widget from viewbar. * If no other viewbar widgets are shown, viewbar gets hidden. * * barWidget is not deleted, caller must do it if it wishes */ void removePermanentBarWidget(KateViewBarWidget *barWidget); /** * @return if viewbar has permanent widget @p barWidget */ bool hasPermanentWidget(KateViewBarWidget *barWidget) const; /** * @return true if the KateViewBar is hidden or displays a permanentBarWidget */ bool hiddenOrPermanent() const; public Q_SLOTS: /** * Hides currently shown bar widget */ void hideCurrentBarWidget(); protected: - void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE; - void hideEvent(QHideEvent *event) Q_DECL_OVERRIDE; + void keyPressEvent(QKeyEvent *event) override; + void hideEvent(QHideEvent *event) override; private: /** * Shows or hides whole viewbar */ void setViewBarVisible(bool visible); bool m_external; private: KTextEditor::ViewPrivate *m_view; QStackedWidget *m_stack; KateViewBarWidget *m_permanentBarWidget; QVBoxLayout *m_layout; }; class KTEXTEDITOR_EXPORT KateCommandLineBar : public KateViewBarWidget { Q_OBJECT public: explicit KateCommandLineBar(KTextEditor::ViewPrivate *view, QWidget *parent = nullptr); ~KateCommandLineBar(); void setText(const QString &text, bool selected = true); void execute(const QString &text); public Q_SLOTS: void showHelpPage(); private: class KateCmdLineEdit *m_lineEdit; }; class KateCmdLineEdit : public KLineEdit { Q_OBJECT public: KateCmdLineEdit(KateCommandLineBar *bar, KTextEditor::ViewPrivate *view); - bool event(QEvent *e) Q_DECL_OVERRIDE; + bool event(QEvent *e) override; - void hideEvent(QHideEvent *e) Q_DECL_OVERRIDE; + void hideEvent(QHideEvent *e) override; Q_SIGNALS: void hideRequested(); public Q_SLOTS: void slotReturnPressed(const QString &cmd); private Q_SLOTS: void hideLineEdit(); protected: - void focusInEvent(QFocusEvent *ev) Q_DECL_OVERRIDE; - void keyPressEvent(QKeyEvent *ev) Q_DECL_OVERRIDE; + void focusInEvent(QFocusEvent *ev) override; + void keyPressEvent(QKeyEvent *ev) override; private: /** * Parse an expression denoting a position in the document. * Return the position as an integer. * Examples of expressions are "10" (the 10th line), * "$" (the last line), "." (the current line), * "'a" (the mark 'a), "/foo/" (a forwards search for "foo"), * and "?bar?" (a backwards search for "bar"). * @param string the expression to parse * @return the position, an integer */ int calculatePosition(QString string); void fromHistory(bool up); QString helptext(const QPoint &) const; KTextEditor::ViewPrivate *m_view; KateCommandLineBar *m_bar; bool m_msgMode; QString m_oldText; uint m_histpos; ///< position in the history uint m_cmdend; ///< the point where a command ends in the text, if we have a valid one. KTextEditor::Command *m_command; ///< For completing flags/args and interactiveness class KateCmdLnWhatsThis *m_help; QTimer *m_hideTimer; }; class KatePasteMenu : public KActionMenu { Q_OBJECT public: KatePasteMenu(const QString &text, KTextEditor::ViewPrivate *view); private: KTextEditor::ViewPrivate *m_view; private Q_SLOTS: void slotAboutToShow(); void paste(); }; #endif diff --git a/src/view/kateviewinternal.cpp b/src/view/kateviewinternal.cpp index c92cf272..b589f185 100644 --- a/src/view/kateviewinternal.cpp +++ b/src/view/kateviewinternal.cpp @@ -1,3267 +1,3289 @@ /* This file is part of the KDE libraries Copyright (C) 2002 John Firebaugh Copyright (C) 2002 Joseph Wenninger Copyright (C) 2002,2003 Christoph Cullmann Copyright (C) 2002-2007 Hamish Rodda Copyright (C) 2003 Anakim Border Copyright (C) 2007 Mirko Stocker Copyright (C) 2007 Matthew Woehlke Copyright (C) 2008 Erlend Hamberg Copyright (C) 2016 Sven Brauch Based on: KWriteView : 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. */ #include "kateviewinternal.h" #include "kateview.h" #include "kateviewhelpers.h" #include "katehighlight.h" #include "katebuffer.h" #include "katerenderer.h" #include "kateconfig.h" #include "katelayoutcache.h" #include "katecompletionwidget.h" #include "spellcheck/spellingmenu.h" #include "kateviewaccessible.h" #include "katetextanimation.h" #include "katemessagewidget.h" #include "kateglobal.h" #include "kateabstractinputmodefactory.h" #include "kateabstractinputmode.h" #include "katepartdebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const bool debugPainting = false; class ZoomEventFilter { public: ZoomEventFilter() = default; bool detectZoomingEvent(QWheelEvent *e, Qt::KeyboardModifiers modifier = Qt::ControlModifier) { Qt::KeyboardModifiers modState = e->modifiers(); if (modState == modifier) { if (m_lastWheelEvent.isValid()) { const qint64 deltaT = m_lastWheelEvent.elapsed(); - // Pressing the specified modifier key within 200ms of the previous "unmodified" + // Pressing the specified modifier key within 200ms of the previous "unmodified" // wheelevent is not allowed to toggle on text zooming if (m_lastWheelEventUnmodified && deltaT < 200) { m_ignoreZoom = true; } else if (deltaT > 1000) { // the protection is kept active for 1s after the last wheel event // TODO: this value should be tuned, preferrably by someone using // Ctrl+Wheel zooming frequently. m_ignoreZoom = false; } } else { // we can't say anything and have to assume there's nothing // accidental to the modifier being pressed. m_ignoreZoom = false; } m_lastWheelEventUnmodified = false; if (m_ignoreZoom) { // unset the modifier so the view scrollbars can handle the scroll // event and produce normal, not accelerated scrolling modState &= ~modifier; e->setModifiers(modState); } } else { // state is reset after any wheel event without the zoom modifier m_lastWheelEventUnmodified = true; m_ignoreZoom = false; } m_lastWheelEvent.start(); // inform the caller whether this event is allowed to trigger text zooming. return !m_ignoreZoom && modState == modifier; } protected: QElapsedTimer m_lastWheelEvent; bool m_ignoreZoom = false; bool m_lastWheelEventUnmodified = false; }; KateViewInternal::KateViewInternal(KTextEditor::ViewPrivate *view) : QWidget(view) , editSessionNumber(0) , editIsRunning(false) , m_view(view) , m_cursors(this) , m_selections(this) , m_mouse() , m_possibleTripleClick(false) , m_completionItemExpanded(false) , m_bm(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand)) , m_bmStart(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand)) , m_bmEnd(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand)) , m_bmLastFlashPos(doc()->newMovingCursor(KTextEditor::Cursor::invalid())) , m_dummy(nullptr) // stay on cursor will avoid that the view scroll around on press return at beginning , m_startPos(doc()->buffer(), KTextEditor::Cursor(0, 0), Kate::TextCursor::StayOnInsert) , m_visibleLineCount(0) , m_madeVisible(false) , m_shiftKeyPressed(false) , m_autoCenterLines(0) , m_minLinesVisible(0) , m_selChangedByUser(false) , m_selectAnchor(-1, -1) , m_layoutCache(new KateLayoutCache(renderer(), this)) , m_cachedMaxStartPos(-1, -1) , m_dragScrollTimer(this) , m_scrollTimer(this) , m_cursorTimer(this) , m_textHintTimer(this) , m_textHintDelay(500) , m_textHintPos(-1, -1) , m_imPreeditRange(nullptr) { QList factories = KTextEditor::EditorPrivate::self()->inputModeFactories(); Q_FOREACH(KateAbstractInputModeFactory *factory, factories) { KateAbstractInputMode *m = factory->createInputMode(this); m_inputModes.insert(m->viewInputMode(), m); } m_currentInputMode = m_inputModes[KTextEditor::View::NormalInputMode]; // TODO: twisted, but needed setMinimumSize(0, 0); setAttribute(Qt::WA_OpaquePaintEvent); setAttribute(Qt::WA_InputMethodEnabled); // bracket markers are only for this view and should not be printed m_bm->setView(m_view); m_bmStart->setView(m_view); m_bmEnd->setView(m_view); m_bm->setAttributeOnlyForViews(true); m_bmStart->setAttributeOnlyForViews(true); m_bmEnd->setAttributeOnlyForViews(true); // use z depth defined in moving ranges interface m_bm->setZDepth(-1000.0); m_bmStart->setZDepth(-1000.0); m_bmEnd->setZDepth(-1000.0); // update mark attributes updateBracketMarkAttributes(); // // scrollbar for lines // m_lineScroll = new KateScrollBar(Qt::Vertical, this); m_lineScroll->show(); m_lineScroll->setTracking(true); m_lineScroll->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); // Hijack the line scroller's controls, so we can scroll nicely for word-wrap connect(m_lineScroll, SIGNAL(actionTriggered(int)), SLOT(scrollAction(int))); connect(m_lineScroll, SIGNAL(sliderMoved(int)), SLOT(scrollLines(int))); connect(m_lineScroll, SIGNAL(sliderMMBMoved(int)), SLOT(scrollLines(int))); connect(m_lineScroll, SIGNAL(valueChanged(int)), SLOT(scrollLines(int))); // // scrollbar for columns // m_columnScroll = new QScrollBar(Qt::Horizontal, m_view); if (m_view->dynWordWrap()) { m_columnScroll->hide(); } else { m_columnScroll->show(); } m_columnScroll->setTracking(true); m_startX = 0; connect(m_columnScroll, SIGNAL(valueChanged(int)), SLOT(scrollColumns(int))); // bottom corner box m_dummy = new QWidget(m_view); m_dummy->setFixedSize(m_lineScroll->width(), m_columnScroll->sizeHint().height()); m_dummy->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); if (m_view->dynWordWrap()) { m_dummy->hide(); } else { m_dummy->show(); } cache()->setWrap(m_view->dynWordWrap()); // // iconborder ;) // m_leftBorder = new KateIconBorder(this, m_view); m_leftBorder->show(); // update view if folding ranges change connect(&m_view->textFolding(), SIGNAL(foldingRangesChanged()), SLOT(slotRegionVisibilityChanged())); m_displayCursor.setPosition(0, 0); setAcceptDrops(true); m_zoomEventFilter = new ZoomEventFilter(); // event filter installEventFilter(this); // set initial cursor m_mouseCursor = Qt::IBeamCursor; setCursor(m_mouseCursor); // call mouseMoveEvent also if no mouse button is pressed setMouseTracking(true); m_dragInfo.state = diNone; // timers connect(&m_dragScrollTimer, SIGNAL(timeout()), this, SLOT(doDragScroll())); connect(&m_scrollTimer, SIGNAL(timeout()), this, SLOT(scrollTimeout())); connect(&m_cursorTimer, SIGNAL(timeout()), this, SLOT(cursorTimeout())); connect(&m_textHintTimer, SIGNAL(timeout()), this, SLOT(textHintTimeout())); #ifndef QT_NO_ACCESSIBILITY QAccessible::installFactory(accessibleInterfaceFactory); #endif connect(doc(), &KTextEditor::DocumentPrivate::textInserted, this, &KateViewInternal::documentTextInserted); connect(doc(), &KTextEditor::DocumentPrivate::textRemoved, this, &KateViewInternal::documentTextRemoved); // update is called in KTextEditor::ViewPrivate, after construction and layout is over // but before any other kateviewinternal call } KateViewInternal::~KateViewInternal() { // delete text animation object here, otherwise it updates the view in its destructor delete m_textAnimation; #ifndef QT_NO_ACCESSIBILITY QAccessible::removeFactory(accessibleInterfaceFactory); #endif // kill preedit ranges delete m_imPreeditRange; qDeleteAll(m_imPreeditRangeChildren); qDeleteAll(m_inputModes); // delete bracket markers delete m_bm; delete m_bmStart; delete m_bmEnd; delete m_zoomEventFilter; } void KateViewInternal::prepareForDynWrapChange() { // Which is the current view line? m_wrapChangeViewLine = cache()->displayViewLine(m_displayCursor, true); } void KateViewInternal::dynWrapChanged() { m_dummy->setFixedSize(m_lineScroll->width(), m_columnScroll->sizeHint().height()); if (m_view->dynWordWrap()) { m_columnScroll->hide(); m_dummy->hide(); } else { // column scrollbar + bottom corner box m_columnScroll->show(); m_dummy->show(); } cache()->setWrap(m_view->dynWordWrap()); updateView(); if (m_view->dynWordWrap()) { scrollColumns(0); } // Determine where the cursor should be to get the cursor on the same view line if (m_wrapChangeViewLine != -1) { KTextEditor::Cursor newStart = viewLineOffset(m_displayCursor, -m_wrapChangeViewLine); makeVisible(newStart, newStart.column(), true); } else { update(); } } KTextEditor::Cursor KateViewInternal::endPos() const { // Hrm, no lines laid out at all?? if (!cache()->viewCacheLineCount()) { return KTextEditor::Cursor(); } for (int i = qMin(linesDisplayed() - 1, cache()->viewCacheLineCount() - 1); i >= 0; i--) { const KateTextLayout &thisLine = cache()->viewLine(i); if (thisLine.line() == -1) { continue; } if (thisLine.virtualLine() >= m_view->textFolding().visibleLines()) { // Cache is too out of date return KTextEditor::Cursor(m_view->textFolding().visibleLines() - 1, doc()->lineLength(m_view->textFolding().visibleLineToLine(m_view->textFolding().visibleLines() - 1))); } return KTextEditor::Cursor(thisLine.virtualLine(), thisLine.wrap() ? thisLine.endCol() - 1 : thisLine.endCol()); } // can happen, if view is still invisible return KTextEditor::Cursor(); } int KateViewInternal::endLine() const { return endPos().line(); } KateTextLayout KateViewInternal::yToKateTextLayout(int y) const { if (y < 0 || y > size().height()) { return KateTextLayout::invalid(); } int range = y / renderer()->lineHeight(); // lineRanges is always bigger than 0, after the initial updateView call if (range >= 0 && range < cache()->viewCacheLineCount()) { return cache()->viewLine(range); } return KateTextLayout::invalid(); } int KateViewInternal::lineToY(int viewLine) const { return (viewLine - startLine()) * renderer()->lineHeight(); } void KateViewInternal::slotIncFontSizes(qreal step) { renderer()->increaseFontSizes(step); } void KateViewInternal::slotDecFontSizes(qreal step) { renderer()->decreaseFontSizes(step); } /** * Line is the real line number to scroll to. */ void KateViewInternal::scrollLines(int line) { KTextEditor::Cursor newPos(line, 0); scrollPos(newPos); } // This can scroll less than one true line void KateViewInternal::scrollViewLines(int offset) { KTextEditor::Cursor c = viewLineOffset(startPos(), offset); scrollPos(c); bool blocked = m_lineScroll->blockSignals(true); m_lineScroll->setValue(startLine()); m_lineScroll->blockSignals(blocked); } void KateViewInternal::scrollAction(int action) { switch (action) { case QAbstractSlider::SliderSingleStepAdd: scrollNextLine(); break; case QAbstractSlider::SliderSingleStepSub: scrollPrevLine(); break; case QAbstractSlider::SliderPageStepAdd: scrollNextPage(); break; case QAbstractSlider::SliderPageStepSub: scrollPrevPage(); break; case QAbstractSlider::SliderToMinimum: top_home(); break; case QAbstractSlider::SliderToMaximum: bottom_end(); break; } } void KateViewInternal::scrollNextPage() { scrollViewLines(qMax(linesDisplayed() - 1, 0)); } void KateViewInternal::scrollPrevPage() { scrollViewLines(-qMax(linesDisplayed() - 1, 0)); } void KateViewInternal::scrollPrevLine() { scrollViewLines(-1); } void KateViewInternal::scrollNextLine() { scrollViewLines(1); } KTextEditor::Cursor KateViewInternal::maxStartPos(bool changed) { cache()->setAcceptDirtyLayouts(true); if (m_cachedMaxStartPos.line() == -1 || changed) { KTextEditor::Cursor end(m_view->textFolding().visibleLines() - 1, doc()->lineLength(m_view->textFolding().visibleLineToLine(m_view->textFolding().visibleLines() - 1))); if (m_view->config()->scrollPastEnd()) { m_cachedMaxStartPos = viewLineOffset(end, -m_minLinesVisible); } else { m_cachedMaxStartPos = viewLineOffset(end, -(linesDisplayed() - 1)); } } cache()->setAcceptDirtyLayouts(false); return m_cachedMaxStartPos; } // c is a virtual cursor void KateViewInternal::scrollPos(KTextEditor::Cursor &c, bool force, bool calledExternally, bool emitSignals) { if (!force && ((!m_view->dynWordWrap() && c.line() == startLine()) || c == startPos())) { return; } if (c.line() < 0) { c.setLine(0); } KTextEditor::Cursor limit = maxStartPos(); if (c > limit) { c = limit; // Re-check we're not just scrolling to the same place if (!force && ((!m_view->dynWordWrap() && c.line() == startLine()) || c == startPos())) { return; } } int viewLinesScrolled = 0; // only calculate if this is really used and useful, could be wrong here, please recheck // for larger scrolls this makes 2-4 seconds difference on my xeon with dyn. word wrap on // try to get it really working ;) bool viewLinesScrolledUsable = !force && (c.line() >= startLine() - linesDisplayed() - 1) && (c.line() <= endLine() + linesDisplayed() + 1); if (viewLinesScrolledUsable) { viewLinesScrolled = cache()->displayViewLine(c); } m_startPos.setPosition(c); // set false here but reversed if we return to makeVisible m_madeVisible = false; if (viewLinesScrolledUsable) { int lines = linesDisplayed(); if (m_view->textFolding().visibleLines() < lines) { KTextEditor::Cursor end(m_view->textFolding().visibleLines() - 1, doc()->lineLength(m_view->textFolding().visibleLineToLine(m_view->textFolding().visibleLines() - 1))); lines = qMin(linesDisplayed(), cache()->displayViewLine(end) + 1); } Q_ASSERT(lines >= 0); if (!calledExternally && qAbs(viewLinesScrolled) < lines && // NOTE: on some machines we must update if the floating widget is visible // otherwise strange painting bugs may occur during scrolling... !((m_view->m_messageWidgets[KTextEditor::Message::TopInView] && m_view->m_messageWidgets[KTextEditor::Message::TopInView]->isVisible()) ||(m_view->m_messageWidgets[KTextEditor::Message::CenterInView] && m_view->m_messageWidgets[KTextEditor::Message::CenterInView]->isVisible()) ||(m_view->m_messageWidgets[KTextEditor::Message::BottomInView] && m_view->m_messageWidgets[KTextEditor::Message::BottomInView]->isVisible()) ) ) { updateView(false, viewLinesScrolled); int scrollHeight = -(viewLinesScrolled * (int)renderer()->lineHeight()); // scroll excluding child widgets (floating notifications) scroll(0, scrollHeight, rect()); m_leftBorder->scroll(0, scrollHeight); if (emitSignals) { emit m_view->verticalScrollPositionChanged(m_view, c); emit m_view->displayRangeChanged(m_view); } return; } } updateView(); update(); m_leftBorder->update(); if (emitSignals) { emit m_view->verticalScrollPositionChanged(m_view, c); emit m_view->displayRangeChanged(m_view); } } void KateViewInternal::scrollColumns(int x) { if (x < 0) { x = 0; } if (x > m_columnScroll->maximum()) { x = m_columnScroll->maximum(); } if (x == m_startX) { return; } int dx = m_startX - x; m_startX = x; if (qAbs(dx) < width()) { // scroll excluding child widgets (floating notifications) scroll(dx, 0, rect()); } else { update(); } emit m_view->horizontalScrollPositionChanged(m_view); emit m_view->displayRangeChanged(m_view); bool blocked = m_columnScroll->blockSignals(true); m_columnScroll->setValue(m_startX); m_columnScroll->blockSignals(blocked); } // If changed is true, the lines that have been set dirty have been updated. void KateViewInternal::updateView(bool changed, int viewLinesScrolled) { doUpdateView(changed, viewLinesScrolled); if (changed) { updateDirty(); } } void KateViewInternal::doUpdateView(bool changed, int viewLinesScrolled) { if (!isVisible() && !viewLinesScrolled && !changed) { return; //When this view is not visible, don't do anything } bool blocked = m_lineScroll->blockSignals(true); if (width() != cache()->viewWidth()) { cache()->setViewWidth(width()); changed = true; } /* It was observed that height() could be negative here -- when the main Kate view has 0 as size (during creation), and there frame arount KateViewInternal. In which case we'd set the view cache to 0 (or less!) lines, and start allocating huge chunks of data, later. */ int newSize = (qMax(0, height()) / renderer()->lineHeight()) + 1; cache()->updateViewCache(startPos(), newSize, viewLinesScrolled); m_visibleLineCount = newSize; KTextEditor::Cursor maxStart = maxStartPos(changed); int maxLineScrollRange = maxStart.line(); if (m_view->dynWordWrap() && maxStart.column() != 0) { maxLineScrollRange++; } m_lineScroll->setRange(0, maxLineScrollRange); m_lineScroll->setValue(startPos().line()); m_lineScroll->setSingleStep(1); m_lineScroll->setPageStep(qMax(0, height()) / renderer()->lineHeight()); m_lineScroll->blockSignals(blocked); KateViewConfig::ScrollbarMode show_scrollbars = static_cast(view()->config()->showScrollbars()); bool visible = ((show_scrollbars == KateViewConfig::AlwaysOn) || ((show_scrollbars == KateViewConfig::ShowWhenNeeded) && (maxLineScrollRange != 0))); bool visible_dummy = visible; m_lineScroll->setVisible(visible); if (!m_view->dynWordWrap()) { int max = maxLen(startLine()) - width(); if (max < 0) { max = 0; } // if we lose the ability to scroll horizontally, move view to the far-left if (max == 0) { scrollColumns(0); } blocked = m_columnScroll->blockSignals(true); // disable scrollbar m_columnScroll->setDisabled(max == 0); visible = ((show_scrollbars == KateViewConfig::AlwaysOn) || ((show_scrollbars == KateViewConfig::ShowWhenNeeded) && (max != 0))); visible_dummy &= visible; m_columnScroll->setVisible(visible); m_columnScroll->setRange(0, max + (renderer()->spaceWidth() / 2)); // Add some space for the caret at EOL m_columnScroll->setValue(m_startX); // Approximate linescroll m_columnScroll->setSingleStep(renderer()->config()->fontMetrics().width(QLatin1Char('a'))); m_columnScroll->setPageStep(width()); m_columnScroll->blockSignals(blocked); } else { visible_dummy = false; } m_dummy->setVisible(visible_dummy); } /** * this function ensures a certain location is visible on the screen. * if endCol is -1, ignore making the columns visible. */ void KateViewInternal::makeVisible(const KTextEditor::Cursor &c, int endCol, bool force, bool center, bool calledExternally) { //qCDebug(LOG_KTE) << "MakeVisible start " << startPos() << " end " << endPos() << " -> request: " << c;// , new start [" << scroll.line << "," << scroll.col << "] lines " << (linesDisplayed() - 1) << " height " << height(); // if the line is in a folded region, unfold all the way up //if ( doc()->foldingTree()->findNodeForLine( c.line )->visible ) // qCDebug(LOG_KTE)<<"line ("< endPos())) { KTextEditor::Cursor scroll = viewLineOffset(c, -int(linesDisplayed()) / 2); scrollPos(scroll, false, calledExternally); } else if (c > viewLineOffset(startPos(), linesDisplayed() - m_minLinesVisible - 1)) { KTextEditor::Cursor scroll = viewLineOffset(c, -(linesDisplayed() - m_minLinesVisible - 1)); scrollPos(scroll, false, calledExternally); } else if (c < viewLineOffset(startPos(), m_minLinesVisible)) { KTextEditor::Cursor scroll = viewLineOffset(c, -m_minLinesVisible); scrollPos(scroll, false, calledExternally); } else { // Check to see that we're not showing blank lines KTextEditor::Cursor max = maxStartPos(); if (startPos() > max) { scrollPos(max, max.column(), calledExternally); } } if (!m_view->dynWordWrap() && (endCol != -1 || m_view->wrapCursor())) { KTextEditor::Cursor rc = toRealCursor(c); int sX = renderer()->cursorToX(cache()->textLayout(rc), rc, !m_view->wrapCursor()); int sXborder = sX - 8; if (sXborder < 0) { sXborder = 0; } if (sX < m_startX) { scrollColumns(sXborder); } else if (sX > m_startX + width()) { scrollColumns(sX - width() + 8); } } m_madeVisible = !force; #ifndef QT_NO_ACCESSIBILITY // FIXME -- is this needed? // QAccessible::updateAccessibility(this, KateCursorAccessible::ChildId, QAccessible::Focus); #endif } void KateViewInternal::slotRegionVisibilityChanged() { qCDebug(LOG_KTE); cache()->clear(); m_cachedMaxStartPos.setLine(-1); KTextEditor::Cursor max = maxStartPos(); if (startPos() > max) { scrollPos(max, false, false, false /* don't emit signals! */); } // if text was folded: make sure the cursor is on a visible line qint64 foldedRangeId = -1; if (!m_view->textFolding().isLineVisible(primaryCursor().line(), &foldedRangeId)) { KTextEditor::Range foldingRange = m_view->textFolding().foldingRange(foldedRangeId); Q_ASSERT(foldingRange.start().isValid()); // set cursor to start of folding region cursors()->setPrimaryCursor(foldingRange.start(), true); } else { // force an update of the cursor, since otherwise the m_displayCursor // line may be below the total amount of visible lines. cursors()->setPrimaryCursor(primaryCursor(), true); } updateView(); update(); m_leftBorder->update(); // emit signals here, scrollPos has this disabled, to ensure we do this after all stuff is updated! emit m_view->verticalScrollPositionChanged(m_view, max); emit m_view->displayRangeChanged(m_view); } void KateViewInternal::slotRegionBeginEndAddedRemoved(unsigned int) { qCDebug(LOG_KTE); // FIXME: performance problem m_leftBorder->update(); } void KateViewInternal::showEvent(QShowEvent *e) { updateView(); QWidget::showEvent(e); } int KateViewInternal::linesDisplayed() const { int h = height(); // catch zero heights, even if should not happen int fh = qMax(1, renderer()->lineHeight()); // default to 1, there is always one line around.... // too many places calc with linesDisplayed() - 1 return qMax(1, (h - (h % fh)) / fh); } QPoint KateViewInternal::cursorToCoordinate(const KTextEditor::Cursor &cursor, bool realCursor, bool includeBorder) const { if (cursor.line() >= doc()->lines()) { return QPoint(-1, -1); } int viewLine = cache()->displayViewLine(realCursor ? toVirtualCursor(cursor) : cursor, true); if (viewLine < 0 || viewLine >= cache()->viewCacheLineCount()) { return QPoint(-1, -1); } const int y = (int)viewLine * renderer()->lineHeight(); KateTextLayout layout = cache()->viewLine(viewLine); if (cursor.column() > doc()->lineLength(cursor.line())) { return QPoint(-1, -1); } int x = 0; // only set x value if we have a valid layout (bug #171027) if (layout.isValid()) { x = (int)layout.lineLayout().cursorToX(cursor.column()); } // else // qCDebug(LOG_KTE) << "Invalid Layout"; if (includeBorder) { x += m_leftBorder->width(); } x -= startX(); return QPoint(x, y); } QPoint KateViewInternal::cursorCoordinates(bool includeBorder) const { return cursorToCoordinate(m_displayCursor, false, includeBorder); } KTextEditor::Cursor KateViewInternal::findMatchingBracket() { KTextEditor::Cursor c; if (!m_bm->toRange().isValid()) { return KTextEditor::Cursor::invalid(); } Q_ASSERT(m_bmEnd->toRange().isValid()); Q_ASSERT(m_bmStart->toRange().isValid()); auto cursor = primaryCursor(); if (m_bmStart->toRange().contains(cursor) || m_bmStart->end() == cursor) { c = m_bmEnd->end(); } else if (m_bmEnd->toRange().contains(cursor) || m_bmEnd->end() == cursor) { c = m_bmStart->start(); } else { // should never happen: a range exists, but the cursor position is // neither at the start nor at the end... return KTextEditor::Cursor::invalid(); } return c; } void KateViewInternal::doReturn() { doc()->newLine(m_view); m_leftBorder->updateForCursorLineChange(); updateView(); } void KateViewInternal::doSmartNewline() { int ln = primaryCursor().line(); Kate::TextLine line = doc()->kateTextLine(ln); int col = qMin(primaryCursor().column(), line->firstChar()); if (col != -1) { while (line->length() > col && !(line->at(col).isLetterOrNumber() || line->at(col) == QLatin1Char('_')) && col < primaryCursor().column()) { ++col; } } else { col = line->length(); // stay indented } doc()->editStart(); doc()->editWrapLine(ln, primaryCursor().column()); doc()->insertText(KTextEditor::Cursor(ln + 1, 0), line->string(0, col)); doc()->editEnd(); updateView(); } void KateViewInternal::doDelete() { auto cursors = view()->allCursors(); KTextEditor::Document::EditingTransaction t(doc()); bool hadSelection = view()->selection(); Q_FOREACH ( const auto& cursor, cursors ) { doc()->del(m_view, cursor); if (hadSelection) { // if we had a selection, only call del() once. break; } } } void KateViewInternal::doBackspace() { auto cursors = view()->allCursors(); KTextEditor::Document::EditingTransaction t(doc()); bool hadSelection = view()->selection(); Q_FOREACH ( const auto& cursor, cursors ) { doc()->backspace(m_view, cursor); if (hadSelection) { // if we had a selection, only call backspace() once. break; } } } void KateViewInternal::doTabulator() { doc()->insertTab(m_view, primaryCursor()); } void KateViewInternal::doTranspose() { doc()->transpose(primaryCursor()); } void KateViewInternal::doDeletePrevWord() { doc()->editStart(); wordPrev(true); KTextEditor::Range selection = m_view->selectionRange(); m_view->removeSelectedText(); doc()->editEnd(); tagRange(selection, true); updateDirty(); } void KateViewInternal::doDeleteNextWord() { doc()->editStart(); wordNext(true); KTextEditor::Range selection = m_view->selectionRange(); m_view->removeSelectedText(); doc()->editEnd(); tagRange(selection, true); updateDirty(); } void KateViewInternal::clearSelectionUnless(bool sel) { if ( ! sel ) { selections()->clearSelectionIfNotPersistent(); } } void KateViewInternal::cursorPrevChar(bool sel) { clearSelectionUnless(sel); cursors()->moveCursorsLeft(sel); } void KateViewInternal::cursorNextChar(bool sel) { clearSelectionUnless(sel); cursors()->moveCursorsRight(sel); } void KateViewInternal::wordPrev(bool sel) { clearSelectionUnless(sel); cursors()->moveCursorsWordPrevious(sel); } void KateViewInternal::wordNext(bool sel) { clearSelectionUnless(sel); cursors()->moveCursorsWordNext(sel); } void KateViewInternal::home(bool sel) { clearSelectionUnless(sel); cursors()->moveCursorsStartOfLine(sel); } void KateViewInternal::end(bool sel) { clearSelectionUnless(sel); cursors()->moveCursorsEndOfLine(sel); } KateTextLayout KateViewInternal::currentLayout(const KTextEditor::Cursor& cursor) const { return cache()->textLayout(cursor); } KateTextLayout KateViewInternal::previousLayout(const KTextEditor::Cursor& cursor) const { int currentViewLine = cache()->viewLine(cursor); if (currentViewLine) { return cache()->textLayout(cursor.line(), currentViewLine - 1); } else { return cache()->textLayout(m_view->textFolding().visibleLineToLine(toVirtualCursor(cursor).line() - 1), -1); } } KateTextLayout KateViewInternal::nextLayout(const KTextEditor::Cursor& cursor) const { int currentViewLine = cache()->viewLine(cursor) + 1; if (currentViewLine >= cache()->line(cursor.line())->viewLineCount()) { currentViewLine = 0; return cache()->textLayout(m_view->textFolding().visibleLineToLine(toVirtualCursor(cursor).line() + 1), currentViewLine); } else { return cache()->textLayout(cursor.line(), currentViewLine); } } /* * This returns the cursor which is offset by (offset) view lines. * This is the main function which is called by code not specifically dealing with word-wrap. * The opposite conversion (cursor to offset) can be done with cache()->displayViewLine(). * * The cursors involved are virtual cursors (ie. equivalent to m_displayCursor) */ KTextEditor::Cursor KateViewInternal::viewLineOffset(const KTextEditor::Cursor &virtualCursor, int offset) { if (!m_view->dynWordWrap()) { KTextEditor::Cursor ret(qMin((int)m_view->textFolding().visibleLines() - 1, virtualCursor.line() + offset), 0); if (ret.line() < 0) { ret.setLine(0); } return ret; } KTextEditor::Cursor realCursor = virtualCursor; realCursor.setLine(m_view->textFolding().visibleLineToLine(m_view->textFolding().lineToVisibleLine(virtualCursor.line()))); int cursorViewLine = cache()->viewLine(realCursor); int currentOffset = 0; int virtualLine = 0; bool forwards = (offset > 0) ? true : false; if (forwards) { currentOffset = cache()->lastViewLine(realCursor.line()) - cursorViewLine; if (offset <= currentOffset) { // the answer is on the same line KateTextLayout thisLine = cache()->textLayout(realCursor.line(), cursorViewLine + offset); Q_ASSERT(thisLine.virtualLine() == (int) m_view->textFolding().lineToVisibleLine(virtualCursor.line())); return KTextEditor::Cursor(virtualCursor.line(), thisLine.startCol()); } virtualLine = virtualCursor.line() + 1; } else { offset = -offset; currentOffset = cursorViewLine; if (offset <= currentOffset) { // the answer is on the same line KateTextLayout thisLine = cache()->textLayout(realCursor.line(), cursorViewLine - offset); Q_ASSERT(thisLine.virtualLine() == (int) m_view->textFolding().lineToVisibleLine(virtualCursor.line())); return KTextEditor::Cursor(virtualCursor.line(), thisLine.startCol()); } virtualLine = virtualCursor.line() - 1; } currentOffset++; while (virtualLine >= 0 && virtualLine < (int)m_view->textFolding().visibleLines()) { int realLine = m_view->textFolding().visibleLineToLine(virtualLine); KateLineLayoutPtr thisLine = cache()->line(realLine, virtualLine); if (!thisLine) { break; } for (int i = 0; i < thisLine->viewLineCount(); ++i) { if (offset == currentOffset) { KateTextLayout thisViewLine = thisLine->viewLine(i); if (!forwards) { // We actually want it the other way around int requiredViewLine = cache()->lastViewLine(realLine) - thisViewLine.viewLine(); if (requiredViewLine != thisViewLine.viewLine()) { thisViewLine = thisLine->viewLine(requiredViewLine); } } KTextEditor::Cursor ret(virtualLine, thisViewLine.startCol()); return ret; } currentOffset++; } if (forwards) { virtualLine++; } else { virtualLine--; } } // Looks like we were asked for something a bit exotic. // Return the max/min valid position. if (forwards) { return KTextEditor::Cursor(m_view->textFolding().visibleLines() - 1, doc()->lineLength(m_view->textFolding().visibleLineToLine(m_view->textFolding().visibleLines() - 1))); } else { return KTextEditor::Cursor(0, 0); } } int KateViewInternal::lineMaxCursorX(const KateTextLayout &range) { if (!m_view->wrapCursor() && !range.wrap()) { return INT_MAX; } int maxX = range.endX(); if (maxX && range.wrap()) { QChar lastCharInLine = doc()->kateTextLine(range.line())->at(range.endCol() - 1); maxX -= renderer()->config()->fontMetrics().width(lastCharInLine); } return maxX; } int KateViewInternal::lineMaxCol(const KateTextLayout &range) { int maxCol = range.endCol(); if (maxCol && range.wrap()) { maxCol--; } return maxCol; } void KateViewInternal::cursorUp(bool sel) { if (!sel && m_view->completionWidget()->isCompletionActive()) { m_view->completionWidget()->cursorUp(); return; } cursors()->moveCursorsUp(sel); } void KateViewInternal::cursorDown(bool sel) { if (!sel && m_view->completionWidget()->isCompletionActive()) { m_view->completionWidget()->cursorDown(); return; } cursors()->moveCursorsDown(sel); } void KateViewInternal::cursorToMatchingBracket(bool sel) { KTextEditor::Cursor c = findMatchingBracket(); if (c.isValid()) { updateSelection(c, sel); cursors()->setPrimaryCursor(c); } } void KateViewInternal::topOfView(bool sel) { KTextEditor::Cursor c = viewLineOffset(startPos(), m_minLinesVisible); updateSelection(toRealCursor(c), sel); cursors()->setPrimaryCursor(toRealCursor(c)); } void KateViewInternal::bottomOfView(bool sel) { KTextEditor::Cursor c = viewLineOffset(endPos(), -m_minLinesVisible); updateSelection(toRealCursor(c), sel); cursors()->setPrimaryCursor(toRealCursor(c)); } // lines is the offset to scroll by void KateViewInternal::scrollLines(int lines, bool sel) { KTextEditor::Cursor c = viewLineOffset(m_displayCursor, lines); // Fix the virtual cursor -> real cursor c.setLine(m_view->textFolding().visibleLineToLine(c.line())); // how far do we move? auto moveLines = c.line() - primaryCursor().line(); cursors()->moveCursorsDown(sel, moveLines); // handles negative values } // This is a bit misleading... it's asking for the view to be scrolled, not the cursor void KateViewInternal::scrollUp() { KTextEditor::Cursor newPos = viewLineOffset(startPos(), -1); scrollPos(newPos); } void KateViewInternal::scrollDown() { KTextEditor::Cursor newPos = viewLineOffset(startPos(), 1); scrollPos(newPos); } void KateViewInternal::setAutoCenterLines(int viewLines, bool updateView) { m_autoCenterLines = viewLines; m_minLinesVisible = qMin(int((linesDisplayed() - 1) / 2), m_autoCenterLines); if (updateView) { KateViewInternal::updateView(); } } void KateViewInternal::pageUp(bool sel, bool half) { if (m_view->isCompletionActive()) { m_view->completionWidget()->pageUp(); return; } bool atTop = startPos().atStartOfDocument(); // Adjust for an auto-centering cursor int lineadj = m_minLinesVisible; int linesToScroll; if (! half) { linesToScroll = -qMax((linesDisplayed() - 1) - lineadj, 0); } else { linesToScroll = -qMax((linesDisplayed() / 2 - 1) - lineadj, 0); } qDebug() << "scroll by:" << linesToScroll; if (!doc()->pageUpDownMovesCursor() && !atTop) { KTextEditor::Cursor newStartPos = viewLineOffset(startPos(), linesToScroll - 1); scrollPos(newStartPos); cursors()->moveCursorsDown(sel, linesToScroll - 1); } else { scrollLines(linesToScroll, sel); } } void KateViewInternal::pageDown(bool sel, bool half) { if (m_view->isCompletionActive()) { m_view->completionWidget()->pageDown(); return; } bool atEnd = startPos() >= m_cachedMaxStartPos; // Adjust for an auto-centering cursor int lineadj = m_minLinesVisible; int linesToScroll; if (! half) { linesToScroll = qMax((linesDisplayed() - 1) - lineadj, 0); } else { linesToScroll = qMax((linesDisplayed() / 2 - 1) - lineadj, 0); } qDebug() << "scroll by:" << linesToScroll; if (!doc()->pageUpDownMovesCursor() && !atEnd) { KTextEditor::Cursor newStartPos = viewLineOffset(startPos(), linesToScroll + 1); scrollPos(newStartPos); cursors()->moveCursorsDown(sel, linesToScroll + 1); } else { scrollLines(linesToScroll, sel); } } int KateViewInternal::maxLen(int startLine) { Q_ASSERT(!m_view->dynWordWrap()); int displayLines = (m_view->height() / renderer()->lineHeight()) + 1; int maxLen = 0; for (int z = 0; z < displayLines; z++) { int virtualLine = startLine + z; if (virtualLine < 0 || virtualLine >= (int)m_view->textFolding().visibleLines()) { break; } maxLen = qMax(maxLen, cache()->line(m_view->textFolding().visibleLineToLine(virtualLine))->width()); } return maxLen; } bool KateViewInternal::columnScrollingPossible() { return !m_view->dynWordWrap() && m_columnScroll->isEnabled() && (m_columnScroll->maximum() > 0); } bool KateViewInternal::lineScrollingPossible() { return m_lineScroll->minimum() != m_lineScroll->maximum(); } void KateViewInternal::top_home(bool sel) { if (m_view->isCompletionActive()) { m_view->completionWidget()->top(); return; } cursors()->moveCursorsTopHome(sel); } void KateViewInternal::bottom_end(bool sel) { if (m_view->isCompletionActive()) { m_view->completionWidget()->bottom(); return; } cursors()->moveCursorsBottomEnd(sel); } void KateViewInternal::updateSelection(const KTextEditor::Cursor &_newCursor, bool keepSel) { /** KTextEditor::Cursor newCursor = _newCursor; if (keepSel) { if (!m_view->selection() || (m_selectAnchor.line() == -1) //don't kill the selection if we have a persistent selection and //the cursor is inside or at the boundaries of the selected area || (m_view->config()->persistentSelection() && !(m_view->selectionRange().contains(primaryCursor()) || m_view->selectionRange().boundaryAtCursor(primaryCursor())))) { m_selectAnchor = primaryCursor(); setSelection(KTextEditor::Range(primaryCursor(), newCursor)); } else { bool doSelect = true; switch (m_selectionMode) { case Word: { // Restore selStartCached if needed. It gets nuked by // viewSelectionChanged if we drag the selection into non-existence, // which can legitimately happen if a shift+DC selection is unable to // set a "proper" (i.e. non-empty) cached selection, e.g. because the // start was on something that isn't a word. Word select mode relies // on the cached selection being set properly, even if it is empty // (i.e. selStartCached == selEndCached). if (!m_selectionCached.isValid()) { m_selectionCached.setStart(m_selectionCached.end()); } int c; if (newCursor > m_selectionCached.start()) { m_selectAnchor = m_selectionCached.start(); Kate::TextLine l = doc()->kateTextLine(newCursor.line()); c = newCursor.column(); if (c > 0 && doc()->highlight()->isInWord(l->at(c - 1))) { for (; c < l->length(); c++) if (!doc()->highlight()->isInWord(l->at(c))) { break; } } newCursor.setColumn(c); } else if (newCursor < m_selectionCached.start()) { m_selectAnchor = m_selectionCached.end(); Kate::TextLine l = doc()->kateTextLine(newCursor.line()); c = newCursor.column(); if (c > 0 && c < doc()->lineLength(newCursor.line()) && doc()->highlight()->isInWord(l->at(c)) && doc()->highlight()->isInWord(l->at(c - 1))) { for (c -= 2; c >= 0; c--) if (!doc()->highlight()->isInWord(l->at(c))) { break; } newCursor.setColumn(c + 1); } } else { doSelect = false; } } break; case Line: if (!m_selectionCached.isValid()) { m_selectionCached = KTextEditor::Range(endLine(), 0, endLine(), 0); } if (newCursor.line() > m_selectionCached.start().line()) { if (newCursor.line() + 1 >= doc()->lines()) { newCursor.setColumn(doc()->line(newCursor.line()).length()); } else { newCursor.setPosition(newCursor.line() + 1, 0); } // Grow to include the entire line m_selectAnchor = m_selectionCached.start(); m_selectAnchor.setColumn(0); } else if (newCursor.line() < m_selectionCached.start().line()) { newCursor.setColumn(0); // Grow to include entire line m_selectAnchor = m_selectionCached.end(); if (m_selectAnchor.column() > 0) { if (m_selectAnchor.line() + 1 >= doc()->lines()) { m_selectAnchor.setColumn(doc()->line(newCursor.line()).length()); } else { m_selectAnchor.setPosition(m_selectAnchor.line() + 1, 0); } } } else { // same line, ignore doSelect = false; } break; case Mouse: { if (!m_selectionCached.isValid()) { break; } if (newCursor > m_selectionCached.end()) { m_selectAnchor = m_selectionCached.start(); } else if (newCursor < m_selectionCached.start()) { m_selectAnchor = m_selectionCached.end(); } else { doSelect = false; } } break; default:; } if (doSelect) { setSelection(KTextEditor::Range(m_selectAnchor, newCursor)); } else if (m_selectionCached.isValid()) { // we have a cached selection, so we restore that setSelection(m_selectionCached); } } m_selChangedByUser = true; } else if (!m_view->config()->persistentSelection()) { m_view->clearSelection(); m_selectionCached = KTextEditor::Range::invalid(); m_selectAnchor = KTextEditor::Cursor::invalid(); } **/ #ifndef QT_NO_ACCESSIBILITY // FIXME KF5 // QAccessibleTextSelectionEvent ev(this, /* selection start, selection end*/); // QAccessible::updateAccessibility(&ev); #endif } void KateViewInternal::setSelection(const KTextEditor::Range &range) { m_view->setSelection(range); } void KateViewInternal::moveCursorToSelectionEdge() { if (!m_view->selection()) { return; } int tmp = m_minLinesVisible; m_minLinesVisible = 0; if (m_view->selectionRange().start() < m_selectAnchor) { cursors()->setPrimaryCursorWithoutSelection(m_view->selectionRange().start()); } else { cursors()->setPrimaryCursorWithoutSelection(m_view->selectionRange().end()); } m_minLinesVisible = tmp; } void KateViewInternal::updateCursorFlashTimer() { if (m_cursorTimer.isActive()) { if (QApplication::cursorFlashTime() > 0) { m_cursorTimer.start(QApplication::cursorFlashTime() / 2); } renderer()->setDrawCaret(true); } } void KateViewInternal::notifyPrimaryCursorChanged(const KTextEditor::Cursor &newCursor, bool force, bool center, bool calledExternally) { if (!force && (m_lastUpdatedPrimary == newCursor)) { m_displayCursor = toVirtualCursor(newCursor); if (!m_madeVisible && m_view == doc()->activeView()) { // unfold if required m_view->textFolding().ensureLineIsVisible(newCursor.line()); makeVisible(m_displayCursor, m_displayCursor.column(), false, center, calledExternally); } return; } if (m_lastUpdatedPrimary.line() != newCursor.line()) { m_leftBorder->updateForCursorLineChange(); } // unfold if required m_view->textFolding().ensureLineIsVisible(newCursor.line()); m_displayCursor = toVirtualCursor(newCursor); Q_ASSERT(m_displayCursor.isValid()); m_lastUpdatedPrimary = newCursor; if (m_view == doc()->activeView()) { makeVisible(m_displayCursor, m_displayCursor.column(), false, center, calledExternally); } updateBracketMarks(); updateMicroFocus(); updateCursorFlashTimer(); cursorMoved(); emit m_view->cursorPositionChanged(m_view, primaryCursor()); } void KateViewInternal::updateBracketMarkAttributes() { KTextEditor::Attribute::Ptr bracketFill = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute()); bracketFill->setBackground(m_view->m_renderer->config()->highlightedBracketColor()); bracketFill->setBackgroundFillWhitespace(false); if (QFontInfo(renderer()->currentFont()).fixedPitch()) { // make font bold only for fixed fonts, otherwise text jumps around bracketFill->setFontBold(); } m_bmStart->setAttribute(bracketFill); m_bmEnd->setAttribute(bracketFill); if (m_view->m_renderer->config()->showWholeBracketExpression()) { KTextEditor::Attribute::Ptr expressionFill = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute()); expressionFill->setBackground(m_view->m_renderer->config()->highlightedBracketColor()); expressionFill->setBackgroundFillWhitespace(false); m_bm->setAttribute(expressionFill); } else { m_bm->setAttribute(KTextEditor::Attribute::Ptr(new KTextEditor::Attribute())); } } void KateViewInternal::updateBracketMarks() { // add some limit to this, this is really endless on big files without limit const int maxLines = 5000; const KTextEditor::Range newRange = doc()->findMatchingBracket(primaryCursor(), maxLines); // new range valid, then set ranges to it if (newRange.isValid()) { if (m_bm->toRange() == newRange) { return; } // modify full range m_bm->setRange(newRange); // modify start and end ranges m_bmStart->setRange(KTextEditor::Range(m_bm->start(), KTextEditor::Cursor(m_bm->start().line(), m_bm->start().column() + 1))); m_bmEnd->setRange(KTextEditor::Range(m_bm->end(), KTextEditor::Cursor(m_bm->end().line(), m_bm->end().column() + 1))); // flash matching bracket if (!renderer()->config()->animateBracketMatching()) { return; } const KTextEditor::Cursor flashPos = (primaryCursor() == m_bmStart->start() || primaryCursor() == m_bmStart->end()) ? m_bmEnd->start() : m_bm->start(); if (flashPos != m_bmLastFlashPos->toCursor()) { m_bmLastFlashPos->setPosition(flashPos); KTextEditor::Attribute::Ptr attribute = doc()->attributeAt(flashPos); attribute->setBackground(m_view->m_renderer->config()->highlightedBracketColor()); attribute->setFontBold(m_bmStart->attribute()->fontBold()); flashChar(flashPos, attribute); } return; } // new range was invalid m_bm->setRange(KTextEditor::Range::invalid()); m_bmStart->setRange(KTextEditor::Range::invalid()); m_bmEnd->setRange(KTextEditor::Range::invalid()); m_bmLastFlashPos->setPosition(KTextEditor::Cursor::invalid()); } bool KateViewInternal::tagLine(const KTextEditor::Cursor &virtualCursor) { // FIXME may be a more efficient way for this if ((int)m_view->textFolding().visibleLineToLine(virtualCursor.line()) > doc()->lastLine()) { return false; } // End FIXME int viewLine = cache()->displayViewLine(virtualCursor, true); if (viewLine >= 0 && viewLine < cache()->viewCacheLineCount()) { cache()->viewLine(viewLine).setDirty(); // tag one line more because of overlapping things like _, bug 335079 if (viewLine+1 < cache()->viewCacheLineCount()) { cache()->viewLine(viewLine+1).setDirty(); } m_leftBorder->update(0, lineToY(viewLine), m_leftBorder->width(), renderer()->lineHeight()); return true; } return false; } bool KateViewInternal::tagLines(int start, int end, bool realLines) { return tagLines(KTextEditor::Cursor(start, 0), KTextEditor::Cursor(end, -1), realLines); } bool KateViewInternal::tagLines(KTextEditor::Cursor start, KTextEditor::Cursor end, bool realCursors) { if (realCursors) { cache()->relayoutLines(start.line(), end.line()); //qCDebug(LOG_KTE)<<"realLines is true"; start = toVirtualCursor(start); end = toVirtualCursor(end); } else { cache()->relayoutLines(toRealCursor(start).line(), toRealCursor(end).line()); } if (end.line() < startLine()) { //qCDebug(LOG_KTE)<<"end endLine(), but cache may not be valid when checking, so use a // less optimal but still adequate approximation (potential overestimation but minimal performance difference) if (start.line() > startLine() + cache()->viewCacheLineCount()) { //qCDebug(LOG_KTE)<<"start> endLine"<updateViewCache(startPos()); //qCDebug(LOG_KTE) << "tagLines( [" << start << "], [" << end << "] )"; bool ret = false; for (int z = 0; z < cache()->viewCacheLineCount(); z++) { KateTextLayout &line = cache()->viewLine(z); if ((line.virtualLine() > start.line() || (line.virtualLine() == start.line() && line.endCol() >= start.column() && start.column() != -1)) && (line.virtualLine() < end.line() || (line.virtualLine() == end.line() && (line.startCol() <= end.column() || end.column() == -1)))) { ret = true; break; //qCDebug(LOG_KTE) << "Tagged line " << line.line(); } } if (!m_view->dynWordWrap()) { int y = lineToY(start.line()); // FIXME is this enough for when multiple lines are deleted int h = (end.line() - start.line() + 2) * renderer()->lineHeight(); if (end.line() >= m_view->textFolding().visibleLines() - 1) { h = height(); } m_leftBorder->update(0, y, m_leftBorder->width(), h); } else { // FIXME Do we get enough good info in editRemoveText to optimize this more? //bool justTagged = false; for (int z = 0; z < cache()->viewCacheLineCount(); z++) { KateTextLayout &line = cache()->viewLine(z); if (!line.isValid() || ((line.virtualLine() > start.line() || (line.virtualLine() == start.line() && line.endCol() >= start.column() && start.column() != -1)) && (line.virtualLine() < end.line() || (line.virtualLine() == end.line() && (line.startCol() <= end.column() || end.column() == -1))))) { //justTagged = true; m_leftBorder->update(0, z * renderer()->lineHeight(), m_leftBorder->width(), m_leftBorder->height()); break; } /*else if (justTagged) { justTagged = false; leftBorder->update (0, z * doc()->viewFont.fontHeight, leftBorder->width(), doc()->viewFont.fontHeight); break; }*/ } } return ret; } bool KateViewInternal::tagRange(const KTextEditor::Range &range, bool realCursors) { return tagLines(range.start(), range.end(), realCursors); } void KateViewInternal::tagAll() { // clear the cache... cache()->clear(); m_leftBorder->updateFont(); m_leftBorder->update(); } void KateViewInternal::paintCursor() { Q_FOREACH ( const auto& secondary, view()->cursors()->cursors() ) { if (tagLine(secondary)) { updateDirty(); } } } KTextEditor::Cursor KateViewInternal::pointToCursor(const QPoint& p) const { KateTextLayout thisLine = yToKateTextLayout(p.y()); KTextEditor::Cursor c; if (!thisLine.isValid()) { // probably user clicked below the last line -> use the last line thisLine = cache()->textLayout(doc()->lines() - 1, -1); } c = renderer()->xToCursor(thisLine, startX() + p.x(), !m_view->wrapCursor()); if (c.line() < 0 || c.line() >= doc()->lines()) { return {}; } return c; } // Point in content coordinates void KateViewInternal::placeCursor(const QPoint &p, bool keepSelection, bool updateSelection) { auto c = pointToCursor(p); if ( ! c.isValid() ) { return; } int tmp = m_minLinesVisible; m_minLinesVisible = 0; if ( keepSelection ) { cursors()->setPrimaryCursorWithoutSelection(c); } else { cursors()->setPrimaryCursor(c); } m_minLinesVisible = tmp; if (updateSelection && keepSelection) { moveCursorToSelectionEdge(); } } // Point in content coordinates bool KateViewInternal::isTargetSelected(const QPoint &p) { const KateTextLayout &thisLine = yToKateTextLayout(p.y()); if (!thisLine.isValid()) { return false; } return m_view->cursorSelected(renderer()->xToCursor(thisLine, startX() + p.x(), !m_view->wrapCursor())); } //BEGIN EVENT HANDLING STUFF bool KateViewInternal::eventFilter(QObject *obj, QEvent *e) { switch (e->type()) { case QEvent::ChildAdded: case QEvent::ChildRemoved: { QChildEvent *c = static_cast(e); if (c->added()) { c->child()->installEventFilter(this); /*foreach (QWidget* child, c->child()->findChildren()) child->installEventFilter(this);*/ } else if (c->removed()) { c->child()->removeEventFilter(this); /*foreach (QWidget* child, c->child()->findChildren()) child->removeEventFilter(this);*/ } } break; case QEvent::ShortcutOverride: { QKeyEvent *k = static_cast(e); if (k->key() == Qt::Key_Escape && k->modifiers() == Qt::NoModifier) { if (m_view->isCompletionActive()) { m_view->abortCompletion(); k->accept(); //qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "aborting completion"; return true; } else if (!m_view->bottomViewBar()->hiddenOrPermanent()) { m_view->bottomViewBar()->hideCurrentBarWidget(); k->accept(); //qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "closing view bar"; return true; } else if (!m_view->config()->persistentSelection() && m_view->selection()) { m_currentInputMode->clearSelection(); k->accept(); //qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "clearing selection"; return true; } else if (m_view->cursors()->hasSecondaryCursors()) { m_view->cursors()->clearSecondaryCursors(); k->accept(); return true; } } if (m_currentInputMode->stealKey(k)) { k->accept(); return true; } } break; case QEvent::KeyPress: { QKeyEvent *k = static_cast(e); // Override all other single key shortcuts which do not use a modifier other than Shift if (obj == this && (!k->modifiers() || k->modifiers() == Qt::ShiftModifier)) { keyPressEvent(k); if (k->isAccepted()) { //qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "using keystroke"; return true; } } //qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "ignoring"; } break; case QEvent::DragMove: { QPoint currentPoint = ((QDragMoveEvent *) e)->pos(); QRect doNotScrollRegion(s_scrollMargin, s_scrollMargin, width() - s_scrollMargin * 2, height() - s_scrollMargin * 2); if (!doNotScrollRegion.contains(currentPoint)) { startDragScroll(); // Keep sending move events ((QDragMoveEvent *)e)->accept(QRect(0, 0, 0, 0)); } dragMoveEvent((QDragMoveEvent *)e); } break; case QEvent::DragLeave: // happens only when pressing ESC while dragging stopDragScroll(); break; default: break; } return QWidget::eventFilter(obj, e); } void KateViewInternal::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Left && e->modifiers() == Qt::AltModifier) { m_view->emitNavigateLeft(); e->setAccepted(true); return; } if (e->key() == Qt::Key_Right && e->modifiers() == Qt::AltModifier) { m_view->emitNavigateRight(); e->setAccepted(true); return; } if (e->key() == Qt::Key_Up && e->modifiers() == Qt::AltModifier) { m_view->emitNavigateUp(); e->setAccepted(true); return; } if (e->key() == Qt::Key_Down && e->modifiers() == Qt::AltModifier) { m_view->emitNavigateDown(); e->setAccepted(true); return; } if (e->key() == Qt::Key_Return && e->modifiers() == Qt::AltModifier) { m_view->emitNavigateAccept(); e->setAccepted(true); return; } if (e->key() == Qt::Key_Backspace && e->modifiers() == Qt::AltModifier) { m_view->emitNavigateBack(); e->setAccepted(true); return; } if (e->key() == Qt::Key_Alt && m_view->completionWidget()->isCompletionActive()) { m_completionItemExpanded = m_view->completionWidget()->toggleExpanded(true); m_view->completionWidget()->resetHadNavigation(); m_altDownTime.start(); } // Note: AND'ing with is a quick hack to fix Key_Enter const int key = e->key() | (e->modifiers() & Qt::ShiftModifier); if (m_currentInputMode->keyPress(e)) { return; } if (!doc()->isReadWrite()) { e->ignore(); return; } if ((key == Qt::Key_Return) || (key == Qt::Key_Enter) || (key == Qt::SHIFT + Qt::Key_Return) || (key == Qt::SHIFT + Qt::Key_Enter)) { doReturn(); e->accept(); return; } if (key == Qt::Key_Backspace || key == Qt::SHIFT + Qt::Key_Backspace) { //m_view->backspace(); e->accept(); return; } if (key == Qt::Key_Tab || key == Qt::SHIFT + Qt::Key_Backtab || key == Qt::Key_Backtab) { if (m_view->completionWidget()->isCompletionActive()) { e->accept(); m_view->completionWidget()->tab(key != Qt::Key_Tab); return; } if (key == Qt::Key_Tab) { uint tabHandling = doc()->config()->tabHandling(); // convert tabSmart into tabInsertsTab or tabIndents: if (tabHandling == KateDocumentConfig::tabSmart) { // multiple lines selected if (m_view->selection() && !m_view->selectionRange().onSingleLine()) { tabHandling = KateDocumentConfig::tabIndents; } // otherwise: take look at cursor position else { // if the cursor is at or before the first non-space character // or on an empty line, // Tab indents, otherwise it inserts a tab character. Kate::TextLine line = doc()->kateTextLine(primaryCursor().line()); int first = line->firstChar(); if (first < 0 || primaryCursor().column() <= first) { tabHandling = KateDocumentConfig::tabIndents; } else { tabHandling = KateDocumentConfig::tabInsertsTab; } } } if (tabHandling == KateDocumentConfig::tabInsertsTab) { doc()->typeChars(m_view, QStringLiteral("\t")); } else { Q_FOREACH ( const auto& cursor, m_view->allCursors() ) { doc()->indent(m_view->selection() ? m_view->selectionRange() : KTextEditor::Range(cursor.line(), 0, cursor.line(), 0), 1); } } e->accept(); return; } else if (doc()->config()->tabHandling() != KateDocumentConfig::tabInsertsTab) { // key == Qt::SHIFT+Qt::Key_Backtab || key == Qt::Key_Backtab Q_FOREACH ( const auto& cursor, m_view->allCursors() ) { doc()->indent(m_view->selection() ? m_view->selectionRange() : KTextEditor::Range(cursor.line(), 0, cursor.line(), 0), -1); } e->accept(); return; } } if (!(e->modifiers() & Qt::ControlModifier) && !e->text().isEmpty() && doc()->typeChars(m_view, e->text())) { e->accept(); return; } // allow composition of AltGr + (q|2|3) on windows static const int altGR = Qt::ControlModifier | Qt::AltModifier; if ((e->modifiers() & altGR) == altGR && !e->text().isEmpty() && doc()->typeChars(m_view, e->text())) { e->accept(); return; } e->ignore(); } void KateViewInternal::keyReleaseEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Alt && m_view->completionWidget()->isCompletionActive() && ((m_completionItemExpanded && (m_view->completionWidget()->hadNavigation() || m_altDownTime.elapsed() > 300)) || (!m_completionItemExpanded && !m_view->completionWidget()->hadNavigation()))) { m_view->completionWidget()->toggleExpanded(false, true); } if ((e->modifiers() & Qt::SHIFT) == Qt::SHIFT) { m_shiftKeyPressed = true; } else { if (m_shiftKeyPressed) { m_shiftKeyPressed = false; if (m_selChangedByUser) { if (m_view->selection()) { QApplication::clipboard()->setText(m_view->selectionText(), QClipboard::Selection); } m_selChangedByUser = false; } } } e->ignore(); return; } void KateViewInternal::contextMenuEvent(QContextMenuEvent *e) { // try to show popup menu QPoint p = e->pos(); if (e->reason() == QContextMenuEvent::Keyboard) { makeVisible(m_displayCursor, 0); p = cursorCoordinates(false); p.rx() -= startX(); } else if (! m_view->selection() || m_view->config()->persistentSelection()) { placeCursor(e->pos()); } // popup is a qguardedptr now if (m_view->contextMenu()) { m_view->spellingMenu()->setUseMouseForMisspelledRange((e->reason() == QContextMenuEvent::Mouse)); m_view->contextMenu()->popup(mapToGlobal(p)); e->accept(); } } void KateViewInternal::mousePressEvent(QMouseEvent *e) { qDebug() << "called"; if ( e->button() == Qt::LeftButton ) { // request the software keyboard, if any if (qApp->autoSipEnabled()) { QStyle::RequestSoftwareInputPanel behavior = QStyle::RequestSoftwareInputPanel(style()->styleHint(QStyle::SH_RequestSoftwareInputPanel)); if (hasFocus() || behavior == QStyle::RSIP_OnMouseClick) { QEvent event(QEvent::RequestSoftwareInputPanel); QApplication::sendEvent(this, &event); } } // handle cursor placement and selection auto newCursor = pointToCursor(e->pos()); if (e->modifiers() & Qt::ShiftModifier) { auto flags = (KateMultiSelection::SelectionFlags) (KateMultiSelection::UsePrimaryCursor | KateMultiSelection::KeepSelectionRange); selections()->beginNewSelection(newCursor, KateMultiSelection::Character, flags); cursors()->setPrimaryCursorWithoutSelection(newCursor); Q_EMIT m_view->selectionChanged(m_view); } else { KateMultiSelection::SelectionMode selectionMode = KateMultiSelection::Character; KateMultiSelection::SelectionFlags flags = KateMultiSelection::UsePrimaryCursor; if ( m_possibleTripleClick ) { selectionMode = KateMultiSelection::Line; } if ( !m_possibleTripleClick && isTargetSelected(e->pos())) { m_dragInfo.state = diPending; m_dragInfo.start = e->pos(); } else { if ( e->modifiers() == (Qt::ControlModifier | Qt::MetaModifier) ) { flags = KateMultiSelection::AddNewCursor; } else { view()->cursors()->clearSecondaryCursors(); } selections()->beginNewSelection(newCursor, selectionMode, flags); Q_EMIT m_view->selectionChanged(m_view); } m_possibleTripleClick = false; } updateCursorFlashTimer(); e->accept(); } else { e->ignore(); } } void KateViewInternal::mouseDoubleClickEvent(QMouseEvent *e) { auto secondary = (e->modifiers() == (Qt::MetaModifier | Qt::ControlModifier)); auto newCursor = pointToCursor(e->pos()); switch (e->button()) { case Qt::LeftButton: selections()->beginNewSelection(newCursor, KateMultiSelection::Word, secondary ? KateMultiSelection::AddNewCursor : KateMultiSelection::UsePrimaryCursor); Q_EMIT m_view->selectionChanged(m_view); #warning fixme: this weird "shift double click" feature #warning fixme: select to matching bracket on dclick #if 0 if (e->modifiers() & Qt::ShiftModifier) { // Now select the word under the select anchor int cs, ce; Kate::TextLine l = doc()->kateTextLine(m_selectAnchor.line()); ce = m_selectAnchor.column(); if (ce > 0 && doc()->highlight()->isInWord(l->at(ce))) { for (; ce < l->length(); ce++) if (!doc()->highlight()->isInWord(l->at(ce))) { break; } } cs = m_selectAnchor.column() - 1; if (cs < doc()->lineLength(m_selectAnchor.line()) && doc()->highlight()->isInWord(l->at(cs))) { for (cs--; cs >= 0; cs--) if (!doc()->highlight()->isInWord(l->at(cs))) { break; } } // ...and keep it selected if (cs + 1 < ce) { m_selectionCached.setStart(KTextEditor::Cursor(m_selectAnchor.line(), cs + 1)); m_selectionCached.setEnd(KTextEditor::Cursor(m_selectAnchor.line(), ce)); } else { m_selectionCached.setStart(m_selectAnchor); m_selectionCached.setEnd(m_selectAnchor); } // Now word select to the mouse cursor placeCursor(e->pos(), true); } else { // first clear the selection, otherwise we run into bug #106402 // ...and set the cursor position, for the same reason (otherwise there // are *other* idiosyncrasies we can't fix without reintroducing said // bug) // Parameters: don't redraw, and don't emit selectionChanged signal yet m_view->clearSelection(false, false); placeCursor(e->pos()); m_view->selectWord(primaryCursor()); cursorToMatchingBracket(true); if (m_view->selection()) { m_selectAnchor = m_view->selectionRange().start(); m_selectionCached = m_view->selectionRange(); } else { m_selectAnchor = primaryCursor(); m_selectionCached = KTextEditor::Range(primaryCursor(), primaryCursor()); } } #endif if (m_view->selection()) { #if !defined(Q_OS_OSX) QApplication::clipboard()->setText(m_view->selectionText(), QClipboard::Selection); #endif } m_possibleTripleClick = true; QTimer::singleShot(QApplication::doubleClickInterval(), this, SLOT(tripleClickTimeout())); m_scrollX = 0; m_scrollY = 0; m_scrollTimer.start(50); e->accept(); break; default: e->ignore(); break; } } void KateViewInternal::tripleClickTimeout() { m_possibleTripleClick = false; } +void KateViewInternal::beginSelectLine(const QPoint &pos) +{ + placeCursor(pos); + m_possibleTripleClick = true; // set so subsequent mousePressEvent will select line +} + void KateViewInternal::mouseReleaseEvent(QMouseEvent *e) { switch (e->button()) { case Qt::LeftButton: if ( selections()->currentlySelecting() ) { selections()->finishNewSelection(); Q_EMIT m_view->selectionChanged(m_view); updateCursorFlashTimer(); } if (m_selChangedByUser) { if (m_view->selection()) { QApplication::clipboard()->setText(m_view->selectionText(), QClipboard::Selection); } m_selChangedByUser = false; } if (m_dragInfo.state == diPending) { placeCursor(e->pos(), e->modifiers() & Qt::ShiftModifier); Q_EMIT m_view->selectionChanged(m_view); } else if (m_dragInfo.state == diNone) { m_scrollTimer.stop(); } m_dragInfo.state = diNone; e->accept(); break; case Qt::MidButton: placeCursor(e->pos()); if (doc()->isReadWrite()) { view()->m_clipboard.pasteFromClipboard(QClipboard::Selection); } e->accept(); break; default: e->ignore(); break; } } void KateViewInternal::leaveEvent(QEvent *) { m_textHintTimer.stop(); // fix bug 194452, scrolling keeps going if you scroll via mouse drag and press and other mouse // button outside the view area if (m_dragInfo.state == diNone) { m_scrollTimer.stop(); } } KTextEditor::Cursor KateViewInternal::coordinatesToCursor(const QPoint &_coord, bool includeBorder) const { QPoint coord(_coord); KTextEditor::Cursor ret = KTextEditor::Cursor::invalid(); if (includeBorder) { coord.rx() -= m_leftBorder->width(); } coord.rx() += startX(); const KateTextLayout &thisLine = yToKateTextLayout(coord.y()); if (thisLine.isValid()) { ret = renderer()->xToCursor(thisLine, coord.x(), !m_view->wrapCursor()); } if (ret.column() > view()->document()->lineLength(ret.line())) { // The cursor is beyond the end of the line; in that case the renderer // gives the index of the character behind the last one. return KTextEditor::Cursor::invalid(); } return ret; } void KateViewInternal::mouseMoveEvent(QMouseEvent *e) { KTextEditor::Cursor newPosition = coordinatesToCursor(e->pos(), false); if (newPosition != m_mouse) { m_mouse = newPosition; mouseMoved(); } if (e->buttons() & Qt::LeftButton) { if (m_dragInfo.state == diPending) { // we had a mouse down, but haven't confirmed a drag yet // if the mouse has moved sufficiently, we will confirm QPoint p(e->pos() - m_dragInfo.start); // we've left the drag square, we can start a real drag operation now if (p.manhattanLength() > QApplication::startDragDistance()) { doDrag(); } return; } else if (m_dragInfo.state == diDragging) { // Don't do anything after a canceled drag until the user lets go of // the mouse button! return; } m_mouseX = e->x(); m_mouseY = e->y(); m_scrollX = 0; m_scrollY = 0; int d = renderer()->lineHeight(); if (m_mouseX < 0) { m_scrollX = -d; } if (m_mouseX > width()) { m_scrollX = d; } if (m_mouseY < 0) { m_mouseY = 0; m_scrollY = -d; } if (m_mouseY > height()) { m_mouseY = height(); m_scrollY = d; } auto c = pointToCursor(QPoint(m_mouseX, m_mouseY)); selections()->updateNewSelection(c); updateCursorFlashTimer(); } else { if (isTargetSelected(e->pos())) { // mouse is over selected text. indicate that the text is draggable by setting // the arrow cursor as other Qt text editing widgets do if (m_mouseCursor != Qt::ArrowCursor) { m_mouseCursor = Qt::ArrowCursor; setCursor(m_mouseCursor); } } else { // normal text cursor if (m_mouseCursor != Qt::IBeamCursor) { m_mouseCursor = Qt::IBeamCursor; setCursor(m_mouseCursor); } } //We need to check whether the mouse position is actually within the widget, //because other widgets like the icon border forward their events to this, //and we will create invalid text hint requests if we don't check if (textHintsEnabled() && geometry().contains(parentWidget()->mapFromGlobal(e->globalPos()))) { if (QToolTip::isVisible()) { QToolTip::hideText(); } m_textHintTimer.start(m_textHintDelay); m_textHintPos = e->pos(); } } } void KateViewInternal::updateDirty() { const int h = renderer()->lineHeight(); int currentRectStart = -1; int currentRectEnd = -1; QRegion updateRegion; { for (int i = 0; i < cache()->viewCacheLineCount(); ++i) { if (cache()->viewLine(i).isDirty()) { if (currentRectStart == -1) { currentRectStart = h * i; currentRectEnd = h; } else { currentRectEnd += h; } } else if (currentRectStart != -1) { updateRegion += QRect(0, currentRectStart, width(), currentRectEnd); currentRectStart = -1; currentRectEnd = -1; } } } if (currentRectStart != -1) { updateRegion += QRect(0, currentRectStart, width(), currentRectEnd); } if (!updateRegion.isEmpty()) { if (debugPainting) { qCDebug(LOG_KTE) << "Update dirty region " << updateRegion; } update(updateRegion); } } void KateViewInternal::hideEvent(QHideEvent *e) { Q_UNUSED(e); if (m_view->isCompletionActive()) { m_view->completionWidget()->abortCompletion(); } } void KateViewInternal::paintEvent(QPaintEvent *e) { if (debugPainting) { qCDebug(LOG_KTE) << "GOT PAINT EVENT: Region" << e->region(); } const QRect &unionRect = e->rect(); int xStart = startX() + unionRect.x(); int xEnd = xStart + unionRect.width(); uint h = renderer()->lineHeight(); uint startz = (unionRect.y() / h); uint endz = startz + 1 + (unionRect.height() / h); uint lineRangesSize = cache()->viewCacheLineCount(); QPainter paint(this); paint.setRenderHints(QPainter::Antialiasing); paint.save(); renderer()->setCaretStyle(m_currentInputMode->caretStyle()); renderer()->setShowTabs(doc()->config()->showTabs()); renderer()->setShowTrailingSpaces(doc()->config()->showSpaces()); renderer()->updateMarkerSize(); int sy = startz * h; paint.translate(unionRect.x(), startz * h); for (uint z = startz; z <= endz; z++) { paint.save(); if ((z >= lineRangesSize) || (cache()->viewLine(z).line() == -1)) { if (!(z >= lineRangesSize)) { cache()->viewLine(z).setDirty(false); } paint.fillRect(0, 0, unionRect.width(), h, renderer()->config()->backgroundColor()); } else { //qCDebug(LOG_KTE)<<"KateViewInternal::paintEvent(QPaintEvent *e):cache()->viewLine"<viewLine(z); /* If viewLine() returns non-zero, then a document line was split in several visual lines, and we're trying to paint visual line that is not the first. In that case, this line was already painted previously, since KateRenderer::paintTextLine paints all visual lines. Except if we're at the start of the region that needs to be painted -- when no previous calls to paintTextLine were made. */ if (!thisLine.viewLine() || z == startz) { //qDebug() << "paint text: line: " << thisLine.line() << " viewLine " << thisLine.viewLine() << " x: " << unionRect.x() << " y: " << unionRect.y() << " width: " << xEnd-xStart << " height: " << h << endl; KTextEditor::Cursor pos = primaryCursor(); // first: paint our line paint.translate(QPoint(0, h * - thisLine.viewLine())); paint.setClipRect(QRect(0, 0, unionRect.width(), h * thisLine.kateLineLayout()->viewLineCount())); renderer()->paintTextLine(paint, thisLine.kateLineLayout(), xStart, xEnd, &pos); paint.translate(0, h * thisLine.viewLine()); // second: paint previous line elements, that span into our line like _, bug 335079 if (z > 0) { KateTextLayout &previousLine = cache()->viewLine(z-1); paint.translate(QPoint(0, h * - (previousLine.viewLine() + 1))); renderer()->paintTextLine(paint, previousLine.kateLineLayout(), xStart, xEnd, &pos); paint.translate(0, h * (previousLine.viewLine() + 1)); } /** * line painted, reset and state + mark line as non-dirty */ thisLine.setDirty(false); } } paint.restore(); paint.translate(0, h); sy += h; } paint.restore(); if (m_textAnimation) { m_textAnimation->draw(paint); } } void KateViewInternal::resizeEvent(QResizeEvent *e) { bool expandedHorizontally = width() > e->oldSize().width(); bool expandedVertically = height() > e->oldSize().height(); bool heightChanged = height() != e->oldSize().height(); m_dummy->setFixedSize(m_lineScroll->width(), m_columnScroll->sizeHint().height()); m_madeVisible = false; if (heightChanged) { setAutoCenterLines(m_autoCenterLines, false); m_cachedMaxStartPos.setPosition(-1, -1); } if (m_view->dynWordWrap()) { bool dirtied = false; for (int i = 0; i < cache()->viewCacheLineCount(); i++) { // find the first dirty line // the word wrap updateView algorithm is forced to check all lines after a dirty one KateTextLayout viewLine = cache()->viewLine(i); if (viewLine.wrap() || viewLine.isRightToLeft() || viewLine.width() > width()) { dirtied = true; viewLine.setDirty(); break; } } if (dirtied || heightChanged) { updateView(true); m_leftBorder->update(); } } else { updateView(); if (expandedHorizontally && startX() > 0) { scrollColumns(startX() - (width() - e->oldSize().width())); } } if (width() < e->oldSize().width() && !m_view->wrapCursor()) { // May have to restrain cursor to new smaller width... if (primaryCursor().column() > doc()->lineLength(primaryCursor().line())) { KateTextLayout thisLine = m_layoutCache->viewLine(primaryCursor().line()); KTextEditor::Cursor newCursor(primaryCursor().line(), thisLine.endCol() + ((width() - thisLine.xOffset() - (thisLine.width() - m_startX)) / renderer()->spaceWidth()) - 1); if (newCursor.column() < primaryCursor().column()) { cursors()->setPrimaryCursor(newCursor); } } } if (expandedVertically) { KTextEditor::Cursor max = maxStartPos(); if (startPos() > max) { scrollPos(max); return; // already fired displayRangeChanged } } emit m_view->displayRangeChanged(m_view); } void KateViewInternal::scrollTimeout() { if (m_scrollX || m_scrollY) { scrollLines(startPos().line() + (m_scrollY / (int) renderer()->lineHeight())); placeCursor(QPoint(m_mouseX, m_mouseY), true); } } void KateViewInternal::cursorTimeout() { if (!debugPainting && m_currentInputMode->blinkCaret()) { renderer()->setDrawCaret(!renderer()->drawCaret()); paintCursor(); } } void KateViewInternal::textHintTimeout() { m_textHintTimer.stop(); KTextEditor::Cursor c = coordinatesToCursor(m_textHintPos, false); if (!c.isValid()) { return; } QStringList textHints; foreach(KTextEditor::TextHintProvider * const p, m_textHintProviders) { const QString hint = p->textHint(m_view, c); if (!hint.isEmpty()) { textHints.append(hint); } } if (!textHints.isEmpty()) { qCDebug(LOG_KTE) << "Hint text: " << textHints; QString hint; foreach(const QString & str, textHints) { hint += QStringLiteral("

%1

").arg(str); } QPoint pos(startX() + m_textHintPos.x(), m_textHintPos.y()); QToolTip::showText(mapToGlobal(pos), hint); } } void KateViewInternal::focusInEvent(QFocusEvent *) { if (QApplication::cursorFlashTime() > 0) { m_cursorTimer.start(QApplication::cursorFlashTime() / 2); } paintCursor(); doc()->setActiveView(m_view); // this will handle focus stuff in kateview m_view->slotGotFocus(); } void KateViewInternal::focusOutEvent(QFocusEvent *) { //if (m_view->isCompletionActive()) //m_view->abortCompletion(); m_cursorTimer.stop(); m_view->renderer()->setDrawCaret(true); paintCursor(); m_textHintTimer.stop(); m_view->slotLostFocus(); } void KateViewInternal::doDrag() { m_dragInfo.state = diDragging; m_dragInfo.dragObject = new QDrag(this); QMimeData *mimeData = new QMimeData(); mimeData->setText(m_view->selectionText()); m_dragInfo.dragObject->setMimeData(mimeData); m_dragInfo.dragObject->start(Qt::MoveAction); } void KateViewInternal::dragEnterEvent(QDragEnterEvent *event) { if (event->source() == this) { event->setDropAction(Qt::MoveAction); } event->setAccepted((event->mimeData()->hasText() && doc()->isReadWrite()) || event->mimeData()->hasUrls()); } void KateViewInternal::fixDropEvent(QDropEvent *event) { if (event->source() != this) { event->setDropAction(Qt::CopyAction); } else { Qt::DropAction action = Qt::MoveAction; #ifdef Q_WS_MAC if (event->keyboardModifiers() & Qt::AltModifier) { action = Qt::CopyAction; } #else if (event->keyboardModifiers() & Qt::ControlModifier) { action = Qt::CopyAction; } #endif event->setDropAction(action); } } void KateViewInternal::dragMoveEvent(QDragMoveEvent *event) { // track the cursor to the current drop location placeCursor(event->pos(), true, false); qDebug() << "update drag:" << m_view->cursors()->cursors() << m_view->selections()->selections(); // important: accept action to switch between copy and move mode // without this, the text will always be copied. fixDropEvent(event); } void KateViewInternal::dropEvent(QDropEvent *event) { /** * if we have urls, pass this event off to the hosting application */ if (event->mimeData()->hasUrls()) { emit dropEventPass(event); return; } if (event->mimeData()->hasText() && doc()->isReadWrite()) { const QString text = event->mimeData()->text(); // is the source our own document? bool priv = false; if (KateViewInternal *vi = qobject_cast(event->source())) { priv = doc()->ownedView(vi->m_view); } // dropped on a text selection area? qDebug() << "have selections:" << m_view->selections()->selections(); bool selected = m_view->cursorSelected(primaryCursor()); fixDropEvent(event); if (priv && selected && event->dropAction() != Qt::CopyAction) { // this is a drag that we started and dropped on our selection // ignore this case return; } // fix the cursor position before editStart(), so that it is correctly // stored for the undo action KTextEditor::Cursor targetCursor(primaryCursor()); // backup current cursor int selectionWidth = m_view->selectionRange().columnWidth(); // for block selection int selectionHeight = m_view->selectionRange().numberOfLines(); // for block selection if (event->dropAction() == Qt::CopyAction) { m_view->clearSelection(); } // use one transaction doc()->editStart(); // on move: remove selected text; on copy: duplicate text qDebug() << "insert text:" << text << text.length() << "at" << targetCursor; doc()->insertText(targetCursor, text, m_view->blockSelection()); KTextEditor::DocumentCursor startCursor(doc(), targetCursor); if (event->dropAction() != Qt::CopyAction) { m_view->removeSelectedText(); auto selectionStartsAhead = m_view->primarySelection().start() < targetCursor; if ( selectionStartsAhead ) { startCursor.move(-text.length()); } } auto endCursor = startCursor; endCursor.move(text.length()); qDebug() << "end and taget cursor:" << endCursor << targetCursor; setSelection({startCursor, endCursor}); editSetCursor(endCursor); doc()->editEnd(); event->acceptProposedAction(); updateView(); } // finally finish drag and drop mode m_dragInfo.state = diNone; // important, because the eventFilter`s DragLeave does not occur stopDragScroll(); } //END EVENT HANDLING STUFF void KateViewInternal::clear() { m_startPos.setPosition(0, 0); m_displayCursor = KTextEditor::Cursor(0, 0); primaryCursor().setPosition(0, 0); cache()->clear(); updateView(true); + m_lineScroll->updatePixmap(); } void KateViewInternal::wheelEvent(QWheelEvent *e) { // check if this event should change the font size (Ctrl pressed, angle reported and not accidentally so) // Note: if detectZoomingEvent() doesn't unset the ControlModifier we'll get accelerated scrolling. if (m_zoomEventFilter->detectZoomingEvent(e)) { if (e->angleDelta().y() > 0) { slotIncFontSizes(qreal(e->angleDelta().y()) / QWheelEvent::DefaultDeltasPerStep); } else if (e->angleDelta().y() < 0) { slotDecFontSizes(qreal(-e->angleDelta().y()) / QWheelEvent::DefaultDeltasPerStep); } // accept always and be done for zooming e->accept(); return; } // handle vertical scrolling via the scrollbar if (e->orientation() == Qt::Vertical) { - QWheelEvent copy = *e; - QApplication::sendEvent(m_lineScroll, ©); - if (copy.isAccepted()) { - e->accept(); + // compute distance + auto sign = m_lineScroll->invertedControls() ? -1 : 1; + auto offset = sign * qreal(e->angleDelta().y()) / 120; + if ( e->modifiers() & Qt::ShiftModifier ) { + const auto pageStep = m_lineScroll->pageStep(); + offset = qBound(-pageStep, int(offset * pageStep), pageStep); + } else { + offset *= QApplication::wheelScrollLines(); } + + // handle accumulation + m_accumulatedScroll += offset - int(offset); + auto extraAccumulated = int(m_accumulatedScroll); + m_accumulatedScroll -= extraAccumulated; + + // do scroll + scrollViewLines(int(offset) + extraAccumulated); + e->accept(); } // handle horizontal scrolling via the scrollbar if (e->orientation() == Qt::Horizontal) { // if we have dyn word wrap, we should ignore the scroll events if (m_view->dynWordWrap()) { e->accept(); return; } QWheelEvent copy = *e; QApplication::sendEvent(m_columnScroll, ©); if (copy.isAccepted()) { e->accept(); } } } void KateViewInternal::startDragScroll() { if (!m_dragScrollTimer.isActive()) { m_dragScrollTimer.start(s_scrollTime); } } void KateViewInternal::stopDragScroll() { m_dragScrollTimer.stop(); updateView(); } void KateViewInternal::doDragScroll() { QPoint p = this->mapFromGlobal(QCursor::pos()); int dx = 0, dy = 0; if (p.y() < s_scrollMargin) { dy = p.y() - s_scrollMargin; } else if (p.y() > height() - s_scrollMargin) { dy = s_scrollMargin - (height() - p.y()); } if (p.x() < s_scrollMargin) { dx = p.x() - s_scrollMargin; } else if (p.x() > width() - s_scrollMargin) { dx = s_scrollMargin - (width() - p.x()); } dy /= 4; if (dy) { scrollLines(startPos().line() + dy); } if (columnScrollingPossible() && dx) { scrollColumns(qMin(m_startX + dx, m_columnScroll->maximum())); } if (!dy && !dx) { stopDragScroll(); } } void KateViewInternal::registerTextHintProvider(KTextEditor::TextHintProvider *provider) { if (! m_textHintProviders.contains(provider)) { m_textHintProviders.append(provider); } // we have a client, so start timeout m_textHintTimer.start(m_textHintDelay); } void KateViewInternal::unregisterTextHintProvider(KTextEditor::TextHintProvider *provider) { const int index = m_textHintProviders.indexOf(provider); if (index >= 0) { m_textHintProviders.removeAt(index); } if (m_textHintProviders.isEmpty()) { m_textHintTimer.stop(); } } void KateViewInternal::setTextHintDelay(int delay) { if (delay <= 0) { m_textHintDelay = 200; // ms } else { m_textHintDelay = delay; // ms } } int KateViewInternal::textHintDelay() const { return m_textHintDelay; } bool KateViewInternal::textHintsEnabled() { return ! m_textHintProviders.isEmpty(); } //BEGIN EDIT STUFF void KateViewInternal::editStart() { editSessionNumber++; if (editSessionNumber > 1) { return; } editIsRunning = true; editOldCursor = primaryCursor(); editOldSelection = m_view->selectionRange(); } void KateViewInternal::editEnd(int editTagLineStart, int editTagLineEnd, bool tagFrom) { if (editSessionNumber == 0) { return; } editSessionNumber--; if (editSessionNumber > 0) { return; } // fix start position, might have moved from column 0 // try to clever calculate the right start column for the tricky dyn word wrap case int col = 0; if (m_view->dynWordWrap()) { if (KateLineLayoutPtr layout = cache()->line(m_startPos.line())) { int index = layout->viewLineForColumn(m_startPos.column()); if (index >= 0 && index < layout->viewLineCount()) { col = layout->viewLine(index).startCol(); } } } m_startPos.setPosition(m_startPos.line(), col); if (tagFrom && (editTagLineStart <= int(m_view->textFolding().visibleLineToLine(startLine())))) { tagAll(); } else { tagLines(editTagLineStart, tagFrom ? qMax(doc()->lastLine() + 1, editTagLineEnd) : editTagLineEnd, true); } if (editOldCursor == primaryCursor()) { updateBracketMarks(); } updateView(true); if (editOldCursor != primaryCursor() || m_view == doc()->activeView()) { // Only scroll the view to the cursor if the insertion happens at the cursor. // This might not be the case for e.g. collaborative editing, when a remote user // inserts text at a position not at the caret. if (primaryCursor().line() >= editTagLineStart && primaryCursor().line() <= editTagLineEnd) { m_madeVisible = false; notifyPrimaryCursorChanged(primaryCursor(), true); } } /** * selection changed? * fixes bug 316226 */ if (editOldSelection != m_view->selectionRange() || (editOldSelection.isValid() && !editOldSelection.isEmpty() && !(editTagLineStart > editOldSelection.end().line() && editTagLineEnd < editOldSelection.start().line()))) { emit m_view->selectionChanged(m_view); } editIsRunning = false; } void KateViewInternal::editSetCursor(const KTextEditor::Cursor &_cursor) { if (primaryCursor() != _cursor) { cursors()->setPrimaryCursor(_cursor, false); } } //END KateLayoutCache *KateViewInternal::cache() const { return m_layoutCache; } void KateViewInternal::notifyLinesUpdated(const QVector& changed) { Q_FOREACH ( const auto& cursor, changed ) { tagLine(toVirtualCursor(cursor)); } updateCursorFlashTimer(); updateDirty(); } KTextEditor::Cursor KateViewInternal::toRealCursor(const KTextEditor::Cursor &virtualCursor) const { return KTextEditor::Cursor(m_view->textFolding().visibleLineToLine(virtualCursor.line()), virtualCursor.column()); } KTextEditor::Cursor KateViewInternal::toVirtualCursor(const KTextEditor::Cursor &realCursor) const { /** * only convert valid lines, folding doesn't like invalid input! * don't validate whole cursor, column might be -1 */ if (realCursor.line() < 0) { return KTextEditor::Cursor::invalid(); } return KTextEditor::Cursor(m_view->textFolding().lineToVisibleLine(realCursor.line()), realCursor.column()); } KateRenderer *KateViewInternal::renderer() const { return m_view->renderer(); } void KateViewInternal::mouseMoved() { m_view->notifyMousePositionChanged(m_mouse); m_view->updateRangesIn(KTextEditor::Attribute::ActivateMouseIn); } void KateViewInternal::cursorMoved() { m_view->updateRangesIn(KTextEditor::Attribute::ActivateCaretIn); #ifndef QT_NO_ACCESSIBILITY - QAccessibleTextCursorEvent ev(this, KateViewAccessible::positionFromCursor(this, primaryCursor())); - QAccessible::updateAccessibility(&ev); + if (QAccessible::isActive()) { + QAccessibleTextCursorEvent ev(this, static_cast(QAccessible::queryAccessibleInterface(this))->positionFromCursor(this, primaryCursor())); + QAccessible::updateAccessibility(&ev); + } #endif } bool KateViewInternal::rangeAffectsView(const KTextEditor::Range &range, bool realCursors) const { int startLine = m_startPos.line(); int endLine = startLine + (int)m_visibleLineCount; if (realCursors) { startLine = (int)m_view->textFolding().visibleLineToLine(startLine); endLine = (int)m_view->textFolding().visibleLineToLine(endLine); } return (range.end().line() >= startLine) || (range.start().line() <= endLine); } //BEGIN IM INPUT STUFF QVariant KateViewInternal::inputMethodQuery(Qt::InputMethodQuery query) const { switch (query) { case Qt::ImCursorRectangle: { // Cursor placement code is changed for Asian input method that // shows candidate window. This behavior is same as Qt/E 2.3.7 // which supports Asian input methods. Asian input methods need // start point of IM selection text to place candidate window as // adjacent to the selection text. // // in Qt5, cursor rectangle is used as QRectF internally, and it // will be checked by QRectF::isValid(), which will mark rectangle // with width == 0 or height == 0 as invalid. auto lineHeight = renderer()->lineHeight(); return QRect(cursorToCoordinate(primaryCursor(), true, false), QSize(1, lineHeight ? lineHeight : 1)); } case Qt::ImFont: return renderer()->currentFont(); case Qt::ImCursorPosition: return primaryCursor().column(); case Qt::ImAnchorPosition: // If selectAnchor is at the same line, return the real anchor position // Otherwise return the same position of cursor if (m_view->selection() && m_selectAnchor.line() == primaryCursor().line()) { return m_selectAnchor.column(); } else { return primaryCursor().column(); } case Qt::ImSurroundingText: if (Kate::TextLine l = doc()->kateTextLine(primaryCursor().line())) { return l->string(); } else { return QString(); } case Qt::ImCurrentSelection: if (m_view->selection()) { return m_view->selectionText(); } else { return QString(); } default: /* values: ImMaximumTextLength */ break; } return QWidget::inputMethodQuery(query); } void KateViewInternal::inputMethodEvent(QInputMethodEvent *e) { if (doc()->readOnly()) { e->ignore(); return; } //qCDebug(LOG_KTE) << "Event: cursor" << primaryCursor() << "commit" << e->commitString() << "preedit" << e->preeditString() << "replacement start" << e->replacementStart() << "length" << e->replacementLength(); if (!m_imPreeditRange) { m_imPreeditRange = doc()->newMovingRange(KTextEditor::Range(primaryCursor(), primaryCursor()), KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight); } if (!m_imPreeditRange->toRange().isEmpty()) { doc()->inputMethodStart(); doc()->removeText(*m_imPreeditRange); doc()->inputMethodEnd(); } if (!e->commitString().isEmpty() || e->replacementLength()) { m_view->removeSelectedText(); KTextEditor::Range preeditRange = *m_imPreeditRange; KTextEditor::Cursor start(m_imPreeditRange->start().line(), m_imPreeditRange->start().column() + e->replacementStart()); KTextEditor::Cursor removeEnd = start + KTextEditor::Cursor(0, e->replacementLength()); doc()->editStart(); if (start != removeEnd) { doc()->removeText(KTextEditor::Range(start, removeEnd)); } if (!e->commitString().isEmpty()) { // if the input method event is text that should be inserted, call KTextEditor::DocumentPrivate::typeChars() // with the text. that method will handle the input and take care of overwrite mode, etc. doc()->typeChars(m_view, e->commitString()); } doc()->editEnd(); // Revert to the same range as above m_imPreeditRange->setRange(preeditRange); } if (!e->preeditString().isEmpty()) { doc()->inputMethodStart(); doc()->insertText(m_imPreeditRange->start(), e->preeditString()); doc()->inputMethodEnd(); // The preedit range gets automatically repositioned } // Finished this input method context? if (m_imPreeditRange && e->preeditString().isEmpty()) { // delete the range and reset the pointer delete m_imPreeditRange; m_imPreeditRange = nullptr; qDeleteAll(m_imPreeditRangeChildren); m_imPreeditRangeChildren.clear(); if (QApplication::cursorFlashTime() > 0) { renderer()->setDrawCaret(false); } renderer()->setCaretOverrideColor(QColor()); e->accept(); return; } KTextEditor::Cursor newCursor = primaryCursor(); bool hideCursor = false; QColor caretColor; if (m_imPreeditRange) { qDeleteAll(m_imPreeditRangeChildren); m_imPreeditRangeChildren.clear(); int decorationColumn = 0; foreach (const QInputMethodEvent::Attribute &a, e->attributes()) { if (a.type == QInputMethodEvent::Cursor) { newCursor = m_imPreeditRange->start() + KTextEditor::Cursor(0, a.start); hideCursor = !a.length; QColor c = qvariant_cast(a.value); if (c.isValid()) { caretColor = c; } } else if (a.type == QInputMethodEvent::TextFormat) { QTextCharFormat f = qvariant_cast(a.value).toCharFormat(); if (f.isValid() && decorationColumn <= a.start) { KTextEditor::Range fr(m_imPreeditRange->start().line(), m_imPreeditRange->start().column() + a.start, m_imPreeditRange->start().line(), m_imPreeditRange->start().column() + a.start + a.length); KTextEditor::MovingRange *formatRange = doc()->newMovingRange(fr); KTextEditor::Attribute::Ptr attribute(new KTextEditor::Attribute()); attribute->merge(f); formatRange->setAttribute(attribute); decorationColumn = a.start + a.length; m_imPreeditRangeChildren.push_back(formatRange); } } } } renderer()->setDrawCaret(hideCursor); renderer()->setCaretOverrideColor(caretColor); if (newCursor != primaryCursor()) { cursors()->setPrimaryCursor(newCursor); } e->accept(); } //END IM INPUT STUFF void KateViewInternal::flashChar(const KTextEditor::Cursor &pos, KTextEditor::Attribute::Ptr attribute) { Q_ASSERT(pos.isValid()); Q_ASSERT(attribute.constData()); // if line is folded away, do nothing if (!m_view->textFolding().isLineVisible(pos.line())) { return; } KTextEditor::Range range(pos, KTextEditor::Cursor(pos.line(), pos.column() + 1)); if (m_textAnimation) { m_textAnimation->deleteLater(); } m_textAnimation = new KateTextAnimation(range, attribute, this); } void KateViewInternal::documentTextInserted(KTextEditor::Document *document, const KTextEditor::Range &range) { #ifndef QT_NO_ACCESSIBILITY if (QAccessible::isActive()) { QAccessibleTextInsertEvent ev(this, - KateViewAccessible::positionFromCursor(this, range.start()), document->text(range)); + static_cast(QAccessible::queryAccessibleInterface(this))->positionFromCursor(this, range.start()), document->text(range)); QAccessible::updateAccessibility(&ev); } #endif } void KateViewInternal::documentTextRemoved(KTextEditor::Document * /*document*/, const KTextEditor::Range &range, const QString &oldText) { #ifndef QT_NO_ACCESSIBILITY if (QAccessible::isActive()) { QAccessibleTextRemoveEvent ev(this, - KateViewAccessible::positionFromCursor(this, range.start()), oldText); + static_cast(QAccessible::queryAccessibleInterface(this))->positionFromCursor(this, range.start()), oldText); QAccessible::updateAccessibility(&ev); } #endif } diff --git a/src/view/kateviewinternal.h b/src/view/kateviewinternal.h index bce1eb65..11c9ad04 100644 --- a/src/view/kateviewinternal.h +++ b/src/view/kateviewinternal.h @@ -1,492 +1,494 @@ /* This file is part of the KDE libraries Copyright (C) 2002-2007 Hamish Rodda Copyright (C) 2002 John Firebaugh Copyright (C) 2002 Joseph Wenninger Copyright (C) 2002 Christoph Cullmann Copyright (C) 2007 Mirko Stocker Copyright (C) 2016 Sven Brauch Based on: KWriteView : 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. */ #ifndef _KATE_VIEW_INTERNAL_ #define _KATE_VIEW_INTERNAL_ #include #include "katetextcursor.h" #include "katetextline.h" #include "katedocument.h" #include "kateview.h" #include "katerenderer.h" #include #include #include #include #include #include #include #include #include namespace KTextEditor { class MovingRange; class TextHintProvider; } class KateIconBorder; class KateScrollBar; class KateTextLayout; class KateTextAnimation; class KateAbstractInputMode; class ZoomEventFilter; class QScrollBar; class KateViewInternal : public QWidget { Q_OBJECT friend class KTextEditor::ViewPrivate; friend class KateIconBorder; friend class KateScrollBar; friend class CalculatingCursor; friend class BoundedCursor; friend class WrappingCursor; friend class KateAbstractInputMode; friend class KateMultiCursor; friend class ::KateTextPreview; public: enum Bias { left = -1, none = 0, right = 1 }; public: KateViewInternal(KTextEditor::ViewPrivate *view); - ~KateViewInternal(); + ~KateViewInternal() override; KTextEditor::ViewPrivate *view() const { return m_view; } //BEGIN EDIT STUFF public: void editStart(); void editEnd(int editTagLineStart, int editTagLineEnd, bool tagFrom); void editSetCursor(const KTextEditor::Cursor &cursor); private: uint editSessionNumber; bool editIsRunning; KTextEditor::Cursor editOldCursor; KTextEditor::Range editOldSelection; //END //BEGIN TAG & CLEAR & UPDATE STUFF public: bool tagLine(const KTextEditor::Cursor &virtualCursor); bool tagLines(int start, int end, bool realLines = false); // cursors not const references as they are manipulated within bool tagLines(KTextEditor::Cursor start, KTextEditor::Cursor end, bool realCursors = false); bool tagRange(const KTextEditor::Range &range, bool realCursors); void tagAll(); void updateDirty(); void clear(); //END private Q_SLOTS: // Updates the view and requests a redraw. void updateView(bool changed = false, int viewLinesScrolled = 0); private: // Actually performs the updating, but doesn't call update(). void doUpdateView(bool changed = false, int viewLinesScrolled = 0); void makeVisible(const KTextEditor::Cursor &c, int endCol, bool force = false, bool center = false, bool calledExternally = false); public: // Start Position is a virtual cursor KTextEditor::Cursor startPos() const { return m_startPos; } int startLine() const { return m_startPos.line(); } int startX() const { return m_startX; } KTextEditor::Cursor endPos() const; int endLine() const; KateTextLayout yToKateTextLayout(int y) const; void prepareForDynWrapChange(); void dynWrapChanged(); public Q_SLOTS: void slotIncFontSizes(qreal step = 1.0); void slotDecFontSizes(qreal step = 1.0); void paintCursor(); private Q_SLOTS: void scrollLines(int line); // connected to the sliderMoved of the m_lineScroll void scrollViewLines(int offset); void scrollAction(int action); void scrollNextPage(); void scrollPrevPage(); void scrollPrevLine(); void scrollNextLine(); void scrollColumns(int x); // connected to the valueChanged of the m_columnScroll public: void doReturn(); void doSmartNewline(); void doDelete(); void doBackspace(); void doTabulator(); void doTranspose(); void doDeletePrevWord(); void doDeleteNextWord(); void clearSelectionUnless(bool sel); void cursorPrevChar(bool sel = false); void cursorNextChar(bool sel = false); void wordPrev(bool sel = false); void wordNext(bool sel = false); void home(bool sel = false); void end(bool sel = false); void cursorUp(bool sel = false); void cursorDown(bool sel = false); void cursorToMatchingBracket(bool sel = false); void scrollUp(); void scrollDown(); void topOfView(bool sel = false); void bottomOfView(bool sel = false); void pageUp(bool sel = false, bool half = false); void pageDown(bool sel = false, bool half = false); void top_home(bool sel = false); void bottom_end(bool sel = false); KTextEditor::Cursor primaryCursor() const { return m_cursors.primaryCursor(); } KTextEditor::Cursor getMouse() const { return m_mouse; } QPoint cursorToCoordinate(const KTextEditor::Cursor &cursor, bool realCursor = true, bool includeBorder = true) const; // by default, works on coordinates of the whole widget, eg. offsetted by the border KTextEditor::Cursor coordinatesToCursor(const QPoint &coord, bool includeBorder = true) const; QPoint cursorCoordinates(bool includeBorder = true) const; KTextEditor::Cursor findMatchingBracket(); KateIconBorder *iconBorder() const { return m_leftBorder; } // EVENT HANDLING STUFF - IMPORTANT private: void fixDropEvent(QDropEvent *event); protected: - void hideEvent(QHideEvent *e) Q_DECL_OVERRIDE; - void paintEvent(QPaintEvent *e) Q_DECL_OVERRIDE; - bool eventFilter(QObject *obj, QEvent *e) Q_DECL_OVERRIDE; - void keyPressEvent(QKeyEvent *) Q_DECL_OVERRIDE; - void keyReleaseEvent(QKeyEvent *) Q_DECL_OVERRIDE; - void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE; - void mousePressEvent(QMouseEvent *) Q_DECL_OVERRIDE; - void mouseDoubleClickEvent(QMouseEvent *) Q_DECL_OVERRIDE; - void mouseReleaseEvent(QMouseEvent *) Q_DECL_OVERRIDE; - void mouseMoveEvent(QMouseEvent *) Q_DECL_OVERRIDE; - void leaveEvent(QEvent *) Q_DECL_OVERRIDE; - void dragEnterEvent(QDragEnterEvent *) Q_DECL_OVERRIDE; - void dragMoveEvent(QDragMoveEvent *) Q_DECL_OVERRIDE; - void dropEvent(QDropEvent *) Q_DECL_OVERRIDE; - void showEvent(QShowEvent *) Q_DECL_OVERRIDE; - void wheelEvent(QWheelEvent *e) Q_DECL_OVERRIDE; - void focusInEvent(QFocusEvent *) Q_DECL_OVERRIDE; - void focusOutEvent(QFocusEvent *) Q_DECL_OVERRIDE; - void inputMethodEvent(QInputMethodEvent *e) Q_DECL_OVERRIDE; - - void contextMenuEvent(QContextMenuEvent *e) Q_DECL_OVERRIDE; + void hideEvent(QHideEvent *e) override; + void paintEvent(QPaintEvent *e) override; + bool eventFilter(QObject *obj, QEvent *e) override; + void keyPressEvent(QKeyEvent *) override; + void keyReleaseEvent(QKeyEvent *) override; + void resizeEvent(QResizeEvent *) override; + void mousePressEvent(QMouseEvent *) override; + void mouseDoubleClickEvent(QMouseEvent *) override; + void mouseReleaseEvent(QMouseEvent *) override; + void mouseMoveEvent(QMouseEvent *) override; + void leaveEvent(QEvent *) override; + void dragEnterEvent(QDragEnterEvent *) override; + void dragMoveEvent(QDragMoveEvent *) override; + void dropEvent(QDropEvent *) override; + void showEvent(QShowEvent *) override; + void wheelEvent(QWheelEvent *e) override; + void focusInEvent(QFocusEvent *) override; + void focusOutEvent(QFocusEvent *) override; + void inputMethodEvent(QInputMethodEvent *e) override; + + void contextMenuEvent(QContextMenuEvent *e) override; private Q_SLOTS: void tripleClickTimeout(); Q_SIGNALS: // emitted when KateViewInternal is not handling its own URI drops void dropEventPass(QDropEvent *); private Q_SLOTS: void slotRegionVisibilityChanged(); void slotRegionBeginEndAddedRemoved(unsigned int); private: void moveChar(Bias bias, bool sel); void moveEdge(Bias bias, bool sel); KTextEditor::Cursor maxStartPos(bool changed = false); void scrollPos(KTextEditor::Cursor &c, bool force = false, bool calledExternally = false, bool emitSignals = true); void scrollLines(int lines, bool sel); int linesDisplayed() const; int lineToY(int viewLine) const; void updateSelection(const KTextEditor::Cursor &, bool keepSel); void setSelection(const KTextEditor::Range &); void moveCursorToSelectionEdge(); void notifyPrimaryCursorChanged(const KTextEditor::Cursor &newCursor, bool force = false, bool center = false, bool calledExternally = false); void updateCursorFlashTimer(); void updateBracketMarks(); + void beginSelectLine(const QPoint &pos); KTextEditor::Cursor pointToCursor(const QPoint& p) const; void placeCursor(const QPoint &p, bool keepSelection = false, bool updateSelection = true); bool isTargetSelected(const QPoint &p); //Returns whether the given range affects the area currently visible in the view bool rangeAffectsView(const KTextEditor::Range &range, bool realCursors) const; void doDrag(); KateRenderer *renderer() const; KTextEditor::ViewPrivate *m_view; class KateIconBorder *m_leftBorder; int m_mouseX; int m_mouseY; int m_scrollX; int m_scrollY; ZoomEventFilter *m_zoomEventFilter; Qt::CursorShape m_mouseCursor; KateMultiCursor m_cursors; KateMultiSelection m_selections; KTextEditor::Cursor m_lastUpdatedPrimary; public: const KateMultiCursor* cursors() const { return &m_cursors; } const KateMultiSelection* selections() const { return &m_selections; } KateMultiCursor* cursors() { return &m_cursors; } KateMultiSelection* selections() { return &m_selections; } private: KTextEditor::Cursor m_mouse; KTextEditor::Cursor m_displayCursor; bool m_possibleTripleClick; //Whether the current completion-item was expanded while the last press of ALT bool m_completionItemExpanded; QElapsedTimer m_altDownTime; // Bracket mark and corresponding decorative ranges KTextEditor::MovingRange *m_bm, *m_bmStart, *m_bmEnd; KTextEditor::MovingCursor *m_bmLastFlashPos; void updateBracketMarkAttributes(); enum DragState { diNone, diPending, diDragging }; struct _dragInfo { DragState state; QPoint start; QDrag *dragObject; } m_dragInfo; // // line scrollbar + first visible (virtual) line in the current view // KateScrollBar *m_lineScroll; + qreal m_accumulatedScroll = 0.0; QWidget *m_dummy; // These are now cursors to account for word-wrap. // Start Position is a virtual cursor Kate::TextCursor m_startPos; //Count of lines that are visible behind m_startPos. //This does not respect dynamic word wrap, so take it as an approximation. uint m_visibleLineCount; // This is set to false on resize or scroll (other than that called by makeVisible), // so that makeVisible is again called when a key is pressed and the cursor is in the same spot bool m_madeVisible; bool m_shiftKeyPressed; // How many lines to should be kept visible above/below the cursor when possible void setAutoCenterLines(int viewLines, bool updateView = true); int m_autoCenterLines; int m_minLinesVisible; // // column scrollbar + x position // QScrollBar *m_columnScroll; int m_startX; // has selection changed while your mouse or shift key is pressed bool m_selChangedByUser; KTextEditor::Cursor m_selectAnchor; // uint m_selectionMode; // // when drag selecting after double/triple click, keep the initial selected // // word/line independent of direction. // // They get set in the event of a double click, and is used with mouse move + leftbutton // KTextEditor::Range m_selectionCached; // maximal length of textlines visible from given startLine int maxLen(int startLine); // are we allowed to scroll columns? bool columnScrollingPossible(); // the same for lines bool lineScrollingPossible(); // returns the maximum X value / col value a cursor can take for a specific line range int lineMaxCursorX(const KateTextLayout &line); int lineMaxCol(const KateTextLayout &line); class KateLayoutCache *cache() const; KateLayoutCache *m_layoutCache; // convenience methods KateTextLayout currentLayout(const KTextEditor::Cursor& cursor) const; KateTextLayout previousLayout(const KTextEditor::Cursor& cursor) const; KateTextLayout nextLayout(const KTextEditor::Cursor& cursor) const; // find the cursor offset by (offset) view lines from a cursor. // when keepX is true, the column position will be calculated based on the x // position of the specified cursor. KTextEditor::Cursor viewLineOffset(const KTextEditor::Cursor &virtualCursor, int offset); KTextEditor::Cursor toRealCursor(const KTextEditor::Cursor &virtualCursor) const; KTextEditor::Cursor toVirtualCursor(const KTextEditor::Cursor &realCursor) const; int m_wrapChangeViewLine; KTextEditor::Cursor m_cachedMaxStartPos; // // implementation details for KTextEditor::FlashTextInterface // public: void flashChar(const KTextEditor::Cursor &pos, KTextEditor::Attribute::Ptr attribute); private: QPointer m_textAnimation; private Q_SLOTS: void doDragScroll(); void startDragScroll(); void stopDragScroll(); private: // Timers QTimer m_dragScrollTimer; QTimer m_scrollTimer; QTimer m_cursorTimer; QTimer m_textHintTimer; static const int s_scrollTime = 30; static const int s_scrollMargin = 16; private Q_SLOTS: void scrollTimeout(); void cursorTimeout(); void textHintTimeout(); void documentTextInserted(KTextEditor::Document *document, const KTextEditor::Range &range); void documentTextRemoved(KTextEditor::Document *document, const KTextEditor::Range &range, const QString &oldText); // // KTE::TextHintInterface // public: void registerTextHintProvider(KTextEditor::TextHintProvider *provider); void unregisterTextHintProvider(KTextEditor::TextHintProvider *provider); void setTextHintDelay(int delay); int textHintDelay() const; bool textHintsEnabled(); // not part of the interface private: QVector m_textHintProviders; int m_textHintDelay; QPoint m_textHintPos; // // IM input stuff // public: - QVariant inputMethodQuery(Qt::InputMethodQuery query) const Q_DECL_OVERRIDE; + QVariant inputMethodQuery(Qt::InputMethodQuery query) const override; public: void notifyLinesUpdated(const QVector& changed); private: KTextEditor::MovingRange *m_imPreeditRange; QList m_imPreeditRangeChildren; private: void mouseMoved(); void cursorMoved(); private: inline KTextEditor::DocumentPrivate *doc() { return m_view->doc(); } inline KTextEditor::DocumentPrivate *doc() const { return m_view->doc(); } // input modes private: QMap m_inputModes; KateAbstractInputMode *m_currentInputMode; }; #endif diff --git a/src/view/wordcounter.cpp b/src/view/wordcounter.cpp index 4845493c..f576baa5 100644 --- a/src/view/wordcounter.cpp +++ b/src/view/wordcounter.cpp @@ -1,183 +1,184 @@ /* This file is part of the KDE libraries 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. */ #include "wordcounter.h" #include "katedocument.h" #include "kateview.h" namespace { const int MaximumLinesToRecalculate = 100; } WordCounter::WordCounter(KTextEditor::ViewPrivate *view) : QObject(view) , m_wordsInDocument(0) , m_wordsInSelection(0) , m_charsInDocument(0) , m_charsInSelection(0) , m_startRecalculationFrom(0) , m_document(view->document()) { connect(view->doc(), &KTextEditor::DocumentPrivate::textInserted, this, &WordCounter::textInserted); connect(view->doc(), &KTextEditor::DocumentPrivate::textRemoved, this, &WordCounter::textRemoved); connect(view->doc(), &KTextEditor::DocumentPrivate::loaded, this, &WordCounter::recalculate); connect(view, &KTextEditor::View::selectionChanged, this, &WordCounter::selectionChanged); m_timer.setInterval(500); m_timer.setSingleShot(true); connect(&m_timer, &QTimer::timeout, this, &WordCounter::recalculateLines); recalculate(m_document); } void WordCounter::textInserted(KTextEditor::Document *, const KTextEditor::Range &range) { const int startLine = range.start().line(); const int endLine = range.end().line(); int newLines = endLine - startLine; if (m_countByLine.count() == 0) { // was empty document before insert newLines++; } if (newLines > 0) { m_countByLine.insert(startLine, newLines, -1); } m_countByLine[endLine] = -1; m_timer.start(); } void WordCounter::textRemoved(KTextEditor::Document *, const KTextEditor::Range &range, const QString &) { const int startLine = range.start().line(); const int endLine = range.end().line(); const int removedLines = endLine - startLine; if (removedLines > 0) { m_countByLine.remove(startLine, removedLines); } if (m_countByLine.size() > 0) { m_countByLine[startLine] = -1; m_timer.start(); } else { emit changed(0, 0, 0, 0); } } void WordCounter::recalculate(KTextEditor::Document *) { m_countByLine = QVector(m_document->lines(), -1); m_timer.start(); } void WordCounter::selectionChanged(KTextEditor::View *view) { if (view->selectionRange().isEmpty()) { m_wordsInSelection = m_charsInSelection = 0; emit changed(m_wordsInDocument, 0, m_charsInDocument, 0); return; } const int firstLine = view->selectionRange().start().line(); const int lastLine = view->selectionRange().end().line(); if (firstLine == lastLine || view->blockSelection()) { const QString text = view->selectionText(); m_wordsInSelection = countWords(text); m_charsInSelection = text.size(); } else { m_wordsInSelection = m_charsInSelection = 0; const KTextEditor::Range firstLineRange(view->selectionRange().start(), firstLine, view->document()->lineLength(firstLine)); const QString firstLineText = view->document()->text(firstLineRange); m_wordsInSelection += countWords(firstLineText); m_charsInSelection += firstLineText.size(); // whole lines for (int i = firstLine + 1; i < lastLine; i++) { m_wordsInSelection += m_countByLine[i]; m_charsInSelection += m_document->lineLength(i); } const KTextEditor::Range lastLineRange(KTextEditor::Cursor(lastLine, 0), view->selectionRange().end()); const QString lastLineText = view->document()->text(lastLineRange); m_wordsInSelection += countWords(lastLineText); m_charsInSelection += lastLineText.size(); } emit changed(m_wordsInDocument, m_wordsInSelection, m_charsInDocument, m_charsInSelection); } void WordCounter::recalculateLines() { if (m_startRecalculationFrom >= m_countByLine.size()) { m_startRecalculationFrom = 0; } int wordsCount = 0, charsCount = 0; int calculated = 0; int i = m_startRecalculationFrom; - forever { + // stay in bounds, vector might be empty, even 0 is too large then + while (i < m_countByLine.size()) { if (m_countByLine[i] == -1) { m_countByLine[i] = countWords(m_document->line(i)); if (++calculated > MaximumLinesToRecalculate) { m_startRecalculationFrom = i; m_timer.start(); return; } } wordsCount += m_countByLine[i]; charsCount += m_document->lineLength(i); if (++i == m_countByLine.size()) { // array cycle i = 0; } if (i == m_startRecalculationFrom) { break; } } m_wordsInDocument = wordsCount; m_charsInDocument = charsCount; emit changed(m_wordsInDocument, m_wordsInSelection, m_charsInDocument, m_charsInSelection); } int WordCounter::countWords(const QString &text) const { int count = 0; bool inWord = false; for (const QChar &c : text) { if (c.isLetterOrNumber()) { if (!inWord) { inWord = true; } } else { if (inWord) { inWord = false; count++; } } } return (inWord) ? count + 1: count; } diff --git a/src/vimode/appcommands.h b/src/vimode/appcommands.h index 65ffb6c0..a24a6889 100644 --- a/src/vimode/appcommands.h +++ b/src/vimode/appcommands.h @@ -1,119 +1,119 @@ /* This file is part of the KDE libraries Copyright (C) 2009 Erlend Hamberg Copyright (C) 2011 Svyatoslav Kuzmich 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 KATEVI_APP_COMMANDS_H #define KATEVI_APP_COMMANDS_H #include #include namespace KTextEditor { class MainWindow; } namespace KateVi { class AppCommands : public KTextEditor::Command { Q_OBJECT AppCommands(); static AppCommands* m_instance; public: - virtual ~AppCommands(); - bool exec(KTextEditor::View *view, const QString &cmd, QString &msg, const KTextEditor::Range &range = KTextEditor::Range::invalid()) Q_DECL_OVERRIDE; - bool help(KTextEditor::View *view, const QString &cmd, QString &msg) Q_DECL_OVERRIDE; + ~AppCommands() override; + bool exec(KTextEditor::View *view, const QString &cmd, QString &msg, const KTextEditor::Range &range = KTextEditor::Range::invalid()) override; + bool help(KTextEditor::View *view, const QString &cmd, QString &msg) override; static AppCommands* self() { if (m_instance == nullptr) { m_instance = new AppCommands(); } return m_instance; } private: /** * @returns a view in the given \p window that does not share a split * view with the given \p view. If such view could not be found, then * nullptr is returned. */ KTextEditor::View * findViewInDifferentSplitView(KTextEditor::MainWindow *window, KTextEditor::View *view); private Q_SLOTS: void closeCurrentDocument(); void closeCurrentView(); void closeCurrentSplitView(); void closeOtherSplitViews(); void quit(); private: QRegExp re_write; QRegExp re_close; QRegExp re_quit; QRegExp re_exit; QRegExp re_edit; QRegExp re_tabedit; QRegExp re_new; QRegExp re_split; QRegExp re_vsplit; QRegExp re_vclose; QRegExp re_only; }; class BufferCommands : public KTextEditor::Command { Q_OBJECT BufferCommands(); static BufferCommands* m_instance; public: - virtual ~BufferCommands(); - bool exec(KTextEditor::View *view, const QString &cmd, QString &msg, const KTextEditor::Range &range = KTextEditor::Range::invalid()) Q_DECL_OVERRIDE; - bool help(KTextEditor::View *view, const QString &cmd, QString &msg) Q_DECL_OVERRIDE; + ~BufferCommands() override; + bool exec(KTextEditor::View *view, const QString &cmd, QString &msg, const KTextEditor::Range &range = KTextEditor::Range::invalid()) override; + bool help(KTextEditor::View *view, const QString &cmd, QString &msg) override; static BufferCommands* self() { if (m_instance == nullptr) { m_instance = new BufferCommands(); } return m_instance; } private: void switchDocument(KTextEditor::View *, const QString &doc); void prevBuffer(KTextEditor::View *); void nextBuffer(KTextEditor::View *); void firstBuffer(KTextEditor::View *); void lastBuffer(KTextEditor::View *); void prevTab(KTextEditor::View *); void nextTab(KTextEditor::View *); void firstTab(KTextEditor::View *); void lastTab(KTextEditor::View *); void activateDocument(KTextEditor::View *, KTextEditor::Document *); QList documents(); }; } #endif /* KATEVI_APP_COMMANDS_H */ diff --git a/src/vimode/cmds.h b/src/vimode/cmds.h index c9d4e07c..41c006fb 100644 --- a/src/vimode/cmds.h +++ b/src/vimode/cmds.h @@ -1,125 +1,125 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2003-2005 Anders Lund * Copyright (C) 2001-2010 Christoph Cullmann * Copyright (C) 2001 Charles Samuels * * 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 KATEVI_COMMANDS_H #define KATEVI_COMMANDS_H #include #include "kateregexpsearch.h" #include #include #include "mappings.h" #include namespace KTextEditor { class DocumentPrivate; } class KCompletion; namespace KateVi { /** * This KTextEditor::Command provides vi 'ex' commands */ class Commands : public KTextEditor::Command, public KateViCommandInterface { Commands() : KTextEditor::Command (QStringList() << mappingCommands() << QStringLiteral("d") << QStringLiteral("delete") << QStringLiteral("j") << QStringLiteral("c") << QStringLiteral("change") << QStringLiteral("<") << QStringLiteral(">") << QStringLiteral("y") << QStringLiteral("yank") << QStringLiteral("ma") << QStringLiteral("mark") << QStringLiteral("k")) { } static Commands *m_instance; public: - ~Commands() + ~Commands() override { m_instance = nullptr; } /** * execute command on given range * @param view view to use for execution * @param cmd cmd string * @param msg message returned from running the command * @param range range to execute command on * @return success */ bool exec(class KTextEditor::View *view, const QString &cmd, QString &msg, - const KTextEditor::Range &range = KTextEditor::Range(-1, -0, -1, 0)) Q_DECL_OVERRIDE; + const KTextEditor::Range &range = KTextEditor::Range(-1, -0, -1, 0)) override; - bool supportsRange(const QString &range) Q_DECL_OVERRIDE; + bool supportsRange(const QString &range) override; /** This command does not have help. @see KTextEditor::Command::help */ - bool help(class KTextEditor::View *, const QString &, QString &) Q_DECL_OVERRIDE + bool help(class KTextEditor::View *, const QString &, QString &) override { return false; } /** * Reimplement from KTextEditor::Command */ - KCompletion *completionObject(KTextEditor::View *, const QString &) Q_DECL_OVERRIDE; + KCompletion *completionObject(KTextEditor::View *, const QString &) override; static Commands *self() { if (m_instance == nullptr) { m_instance = new Commands(); } return m_instance; } private: const QStringList &mappingCommands(); Mappings::MappingMode modeForMapCommand(const QString &mapCommand); bool isMapCommandRecursive(const QString &mapCommand); }; /** * Support vim/sed style search and replace * @author Charles Samuels **/ class SedReplace : public KateCommands::SedReplace, public KateViCommandInterface { SedReplace() { } static SedReplace *m_instance; public: - ~SedReplace() + ~SedReplace() override { m_instance = nullptr; } static SedReplace *self() { if (m_instance == nullptr) { m_instance = new SedReplace(); } return m_instance; } protected: - bool interactiveSedReplace(KTextEditor::ViewPrivate *kateView, QSharedPointer interactiveSedReplace) Q_DECL_OVERRIDE; + bool interactiveSedReplace(KTextEditor::ViewPrivate *kateView, QSharedPointer interactiveSedReplace) override; }; } #endif /* KATEVI_COMMANDS_H */ diff --git a/src/vimode/config/configtab.h b/src/vimode/config/configtab.h index fbcb461a..77ce3b2f 100644 --- a/src/vimode/config/configtab.h +++ b/src/vimode/config/configtab.h @@ -1,67 +1,67 @@ /* This file is part of the KDE libraries and the Kate part. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KATEVI_CONFIG_TAB_H #define KATEVI_CONFIG_TAB_H #include #include class QTableWidget; namespace KateVi { namespace Ui { class ConfigWidget; } class ConfigTab : public KateConfigPage { Q_OBJECT public: explicit ConfigTab(QWidget *parent, Mappings *mappings); - virtual ~ConfigTab(); + ~ConfigTab() override; - QString name() const Q_DECL_OVERRIDE; + QString name() const override; protected: Ui::ConfigWidget *ui; private: void applyTab(QTableWidget *mappingsTable, Mappings::MappingMode mode); void reloadTab(QTableWidget *mappingsTable, Mappings::MappingMode mode); public Q_SLOTS: - void apply() Q_DECL_OVERRIDE; - void reload() Q_DECL_OVERRIDE; - void reset() Q_DECL_OVERRIDE; - void defaults() Q_DECL_OVERRIDE; + void apply() override; + void reload() override; + void reset() override; + void defaults() override; private Q_SLOTS: void showWhatsThis(const QString &text); void addMappingRow(); void removeSelectedMappingRows(); void importNormalMappingRow(); private: Mappings *m_mappings; }; } #endif /* KATEVI_CONFIG_TAB_H */ diff --git a/src/vimode/emulatedcommandbar/commandmode.h b/src/vimode/emulatedcommandbar/commandmode.h index 4ab62e1e..d29b0e25 100644 --- a/src/vimode/emulatedcommandbar/commandmode.h +++ b/src/vimode/emulatedcommandbar/commandmode.h @@ -1,97 +1,97 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2013-2016 Simon St James * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KATEVI_EMULATED_COMMAND_BAR_COMMANDMODE_H #define KATEVI_EMULATED_COMMAND_BAR_COMMANDMODE_H #include "activemode.h" #include #include namespace KTextEditor { class ViewPrivate; } namespace KateVi { class EmulatedCommandBar; class MatchHighlighter; class InteractiveSedReplaceMode; class Completer; class InputModeManager; class CommandMode : public ActiveMode { public: CommandMode(EmulatedCommandBar* emulatedCommandBar, MatchHighlighter* matchHighlighter, InputModeManager* viInputModeManager, KTextEditor::ViewPrivate* view, QLineEdit* edit, InteractiveSedReplaceMode *interactiveSedReplaceMode, Completer* completer); - virtual ~CommandMode() + ~CommandMode() override { } - bool handleKeyPress ( const QKeyEvent* keyEvent ) Q_DECL_OVERRIDE; - void editTextChanged(const QString &newText) Q_DECL_OVERRIDE; - CompletionStartParams completionInvoked(Completer::CompletionInvocation invocationType) Q_DECL_OVERRIDE; - void completionChosen() Q_DECL_OVERRIDE; - void deactivate(bool wasAborted) Q_DECL_OVERRIDE; + bool handleKeyPress ( const QKeyEvent* keyEvent ) override; + void editTextChanged(const QString &newText) override; + CompletionStartParams completionInvoked(Completer::CompletionInvocation invocationType) override; + void completionChosen() override; + void deactivate(bool wasAborted) override; QString executeCommand(const QString &commandToExecute); private: CompletionStartParams activateCommandCompletion(); CompletionStartParams activateCommandHistoryCompletion(); CompletionStartParams activateSedFindHistoryCompletion(); CompletionStartParams activateSedReplaceHistoryCompletion(); QString withoutRangeExpression(); QString rangeExpression(); QString withSedFindTermReplacedWith(const QString &newFindTerm); QString withSedDelimiterEscaped(const QString &text); bool isCursorInFindTermOfSed(); bool isCursorInReplaceTermOfSed(); QString sedFindTerm(); QString sedReplaceTerm(); /** * Stuff to do with expressions of the form: * * s/find/replace/ */ struct ParsedSedExpression { bool parsedSuccessfully; int findBeginPos; int findEndPos; int replaceBeginPos; int replaceEndPos; QChar delimiter; }; /** * The "range expression" is the (optional) expression before the command that describes * the range over which the command should be run e.g. '<,'>. @see CommandRangeExpressionParser */ CommandMode::ParsedSedExpression parseAsSedExpression(); void replaceCommandBeforeCursorWith(const QString &newCommand); int commandBeforeCursorBegin(); QLineEdit *m_edit; InteractiveSedReplaceMode *m_interactiveSedReplaceMode; Completer *m_completer; KCompletion m_cmdCompletion; QHash m_cmdDict; KTextEditor::Command *queryCommand(const QString &cmd) const; }; } #endif diff --git a/src/vimode/emulatedcommandbar/emulatedcommandbar.h b/src/vimode/emulatedcommandbar/emulatedcommandbar.h index 6326b2b7..8bab15aa 100644 --- a/src/vimode/emulatedcommandbar/emulatedcommandbar.h +++ b/src/vimode/emulatedcommandbar/emulatedcommandbar.h @@ -1,131 +1,131 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2013-2016 Simon St James * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KATEVI_EMULATED_COMMAND_BAR_H #define KATEVI_EMULATED_COMMAND_BAR_H #include "kateviewhelpers.h" #include #include #include #include "../searcher.h" #include "activemode.h" namespace KTextEditor { class ViewPrivate; class Command; } class QLabel; class QLayout; namespace KateVi { class MatchHighlighter; class InteractiveSedReplaceMode; class SearchMode; class CommandMode; /** * A KateViewBarWidget that attempts to emulate some of the features of Vim's own command bar, * including insertion of register contents via ctr-r; dismissal via * ctrl-c and ctrl-[; bi-directional incremental searching, with SmartCase; interactive sed-replace; * plus a few extensions such as completion from document and navigable sed search and sed replace history. */ class KTEXTEDITOR_EXPORT EmulatedCommandBar : public KateViewBarWidget { Q_OBJECT public: enum Mode { NoMode, SearchForward, SearchBackward, Command }; explicit EmulatedCommandBar(KateViInputMode* viInputMode, InputModeManager *viInputModeManager, QWidget *parent = nullptr); - virtual ~EmulatedCommandBar(); + ~EmulatedCommandBar() override; void init(Mode mode, const QString &initialText = QString()); bool isActive(); void setCommandResponseMessageTimeout(long commandResponseMessageTimeOutMS); bool handleKeyPress(const QKeyEvent *keyEvent); bool isSendingSyntheticSearchCompletedKeypress(); void startInteractiveSearchAndReplace(QSharedPointer interactiveSedReplace); QString executeCommand(const QString &commandToExecute); void setViInputModeManager(InputModeManager *viInputModeManager); private: KateViInputMode *m_viInputMode; InputModeManager *m_viInputModeManager; bool m_isActive = false; bool m_wasAborted = true; Mode m_mode = NoMode; KTextEditor::ViewPrivate *m_view = nullptr; QLineEdit *m_edit = nullptr; QLabel *m_barTypeIndicator = nullptr; void showBarTypeIndicator(Mode mode); bool m_suspendEditEventFiltering = false; bool m_waitingForRegister = false ; QLabel *m_waitingForRegisterIndicator; bool m_insertedTextShouldBeEscapedForSearchingAsLiteral = false; void hideAllWidgetsExcept(QWidget* widgetToKeepVisible); friend class ActiveMode; QScopedPointer m_matchHighligher; QScopedPointer m_completer; QScopedPointer m_interactiveSedReplaceMode; QScopedPointer m_searchMode; QScopedPointer m_commandMode; void switchToMode(ActiveMode *newMode); ActiveMode *m_currentMode = nullptr; bool barHandledKeypress(const QKeyEvent* keyEvent); void insertRegisterContents(const QKeyEvent *keyEvent); - bool eventFilter(QObject *object, QEvent *event) Q_DECL_OVERRIDE; + bool eventFilter(QObject *object, QEvent *event) override; void deleteSpacesToLeftOfCursor(); void deleteWordCharsToLeftOfCursor(); bool deleteNonWordCharsToLeftOfCursor(); - void closed() Q_DECL_OVERRIDE; + void closed() override; void closeWithStatusMessage(const QString& exitStatusMessage); QTimer *m_exitStatusMessageDisplayHideTimer; QLabel *m_exitStatusMessageDisplay; long m_exitStatusMessageHideTimeOutMS = 4000; void createAndAddBarTypeIndicator(QLayout* layout); void createAndAddEditWidget(QLayout* layout); void createAndAddExitStatusMessageDisplay(QLayout* layout); void createAndInitExitStatusMessageDisplayTimer(); void createAndAddWaitingForRegisterIndicator(QLayout* layout); private Q_SLOTS: void editTextChanged(const QString &newText); void startHideExitStatusMessageTimer(); }; } #endif /* KATEVI_EMULATED_COMMAND_BAR_H */ diff --git a/src/vimode/emulatedcommandbar/interactivesedreplacemode.h b/src/vimode/emulatedcommandbar/interactivesedreplacemode.h index 0948a581..c8ab319c 100644 --- a/src/vimode/emulatedcommandbar/interactivesedreplacemode.h +++ b/src/vimode/emulatedcommandbar/interactivesedreplacemode.h @@ -1,62 +1,62 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2013-2016 Simon St James * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KATEVI_EMULATED_COMMAND_BAR_INTERACTIVESEDREPLACEMODE_H #define KATEVI_EMULATED_COMMAND_BAR_INTERACTIVESEDREPLACEMODE_H #include "activemode.h" #include "../cmds.h" #include class QKeyEvent; class QLabel; namespace KateVi { class EmulatedCommandBar; class MatchHighlighter; class InteractiveSedReplaceMode : public ActiveMode { public: InteractiveSedReplaceMode(EmulatedCommandBar* emulatedCommandBar, MatchHighlighter* matchHighlighter, InputModeManager* viInputModeManager, KTextEditor::ViewPrivate* view); - virtual ~InteractiveSedReplaceMode() + ~InteractiveSedReplaceMode() override { - }; + } void activate(QSharedPointer interactiveSedReplace); bool isActive() const { return m_isActive; } - bool handleKeyPress(const QKeyEvent* keyEvent) Q_DECL_OVERRIDE; - void deactivate(bool wasAborted) Q_DECL_OVERRIDE; + bool handleKeyPress(const QKeyEvent* keyEvent) override; + void deactivate(bool wasAborted) override; QWidget *label(); private: void updateInteractiveSedReplaceLabelText(); void finishInteractiveSedReplace(); QSharedPointer m_interactiveSedReplacer; bool m_isActive; QLabel *m_interactiveSedReplaceLabel; }; } #endif diff --git a/src/vimode/emulatedcommandbar/matchhighlighter.h b/src/vimode/emulatedcommandbar/matchhighlighter.h index d5810b0d..66700aaf 100644 --- a/src/vimode/emulatedcommandbar/matchhighlighter.h +++ b/src/vimode/emulatedcommandbar/matchhighlighter.h @@ -1,52 +1,52 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2013-2016 Simon St James * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KATEVI_EMULATED_COMMAND_BAR_MATCHHIGHLIGHTER_H #define KATEVI_EMULATED_COMMAND_BAR_MATCHHIGHLIGHTER_H #include #include namespace KTextEditor { class ViewPrivate; class Range; class MovingRange; } namespace KateVi { class MatchHighlighter : public QObject { Q_OBJECT public: - MatchHighlighter(KTextEditor::ViewPrivate* view); + explicit MatchHighlighter(KTextEditor::ViewPrivate* view); ~MatchHighlighter(); void updateMatchHighlight(const KTextEditor::Range &matchRange); private Q_SLOTS: void updateMatchHighlightAttrib(); private: KTextEditor::ViewPrivate *m_view = nullptr; KTextEditor::Attribute::Ptr m_highlightMatchAttribute; KTextEditor::MovingRange *m_highlightedMatch; }; } #endif diff --git a/src/vimode/emulatedcommandbar/searchmode.h b/src/vimode/emulatedcommandbar/searchmode.h index 3f31619c..08da74d5 100644 --- a/src/vimode/emulatedcommandbar/searchmode.h +++ b/src/vimode/emulatedcommandbar/searchmode.h @@ -1,71 +1,71 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2013-2016 Simon St James * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KATEVI_EMULATED_COMMAND_BAR_SEARCHMODE_H #define KATEVI_EMULATED_COMMAND_BAR_SEARCHMODE_H #include "activemode.h" #include "../searcher.h" namespace KTextEditor { class ViewPrivate; } #include namespace KateVi { class EmulatedCommandBar; QString vimRegexToQtRegexPattern(const QString &vimRegexPattern); // TODO - move these generic helper functions into their own file? QString withCaseSensitivityMarkersStripped(const QString &originalSearchTerm); QString ensuredCharEscaped(const QString &originalString, QChar charToEscape); QStringList reversed(const QStringList &originalList); class SearchMode : public ActiveMode { public: SearchMode(EmulatedCommandBar* emulatedCommandBar, MatchHighlighter* matchHighlighter, InputModeManager* viInputModeManager, KTextEditor::ViewPrivate* view, QLineEdit* edit); - virtual ~SearchMode() + ~SearchMode() override { - }; + } enum class SearchDirection { Forward, Backward }; void init(SearchDirection); - bool handleKeyPress ( const QKeyEvent* keyEvent ) Q_DECL_OVERRIDE; - void editTextChanged(const QString &newText) Q_DECL_OVERRIDE; - CompletionStartParams completionInvoked(Completer::CompletionInvocation invocationType) Q_DECL_OVERRIDE; - void completionChosen() Q_DECL_OVERRIDE; - void deactivate(bool wasAborted) Q_DECL_OVERRIDE; + bool handleKeyPress ( const QKeyEvent* keyEvent ) override; + void editTextChanged(const QString &newText) override; + CompletionStartParams completionInvoked(Completer::CompletionInvocation invocationType) override; + void completionChosen() override; + void deactivate(bool wasAborted) override; bool isSendingSyntheticSearchCompletedKeypress() const { return m_isSendingSyntheticSearchCompletedKeypress; } private: QLineEdit *m_edit = nullptr; SearchDirection m_searchDirection; KTextEditor::Cursor m_startingCursorPos; KateVi::Searcher::SearchParams m_currentSearchParams; CompletionStartParams activateSearchHistoryCompletion(); enum BarBackgroundStatus { Normal, MatchFound, NoMatchFound }; void setBarBackground(BarBackgroundStatus status); bool m_isSendingSyntheticSearchCompletedKeypress = false; }; } #endif diff --git a/src/vimode/marks.cpp b/src/vimode/marks.cpp index 86d6c539..704e9780 100644 --- a/src/vimode/marks.cpp +++ b/src/vimode/marks.cpp @@ -1,309 +1,309 @@ /* This file is part of the KDE libraries * * 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 "marks.h" #include "kateview.h" #include "katedocument.h" #include #include #include using namespace KateVi; namespace { const QChar BeginEditYanked = QLatin1Char('['); const QChar EndEditYanked = QLatin1Char(']'); const QChar LastChange = QLatin1Char('.'); const QChar InsertStopped = QLatin1Char('^'); const QChar SelectionBegin = QLatin1Char('<'); const QChar SelectionEnd = QLatin1Char('>'); const QChar FirstUserMark = QLatin1Char('a'); const QChar LastUserMark = QLatin1Char('z'); const QChar BeforeJump = QLatin1Char('\''); const QChar BeforeJumpAlter = QLatin1Char('`'); const QChar UserMarks[] = {QLatin1Char('a'), QLatin1Char('b'), QLatin1Char('c'), QLatin1Char('d'), QLatin1Char('e'), QLatin1Char('f'), QLatin1Char('g'), QLatin1Char('h'), QLatin1Char('i'), QLatin1Char('j'), QLatin1Char('k'), QLatin1Char('l'), QLatin1Char('m'), QLatin1Char('n'), QLatin1Char('o'), QLatin1Char('p'), QLatin1Char('q'), QLatin1Char('r'), QLatin1Char('s'), QLatin1Char('t'), QLatin1Char('u'), QLatin1Char('v'), QLatin1Char('w'), QLatin1Char('x'), QLatin1Char('y'), QLatin1Char('z')}; } Marks::Marks(InputModeManager *imm) : m_inputModeManager(imm) , m_doc(imm->view()->doc()) , m_settingMark(false) { connect(m_doc, &KTextEditor::DocumentPrivate::markChanged, this, &Marks::markChanged); } Marks::~Marks() { } void Marks::readSessionConfig(const KConfigGroup &config) { QStringList marks = config.readEntry("ViMarks", QStringList()); for (int i = 0; i + 2 < marks.size(); i += 3) { KTextEditor::Cursor c(marks.at(i + 1).toInt(), marks.at(i + 2).toInt()); setMark(marks.at(i).at(0), c); } syncViMarksAndBookmarks(); } void Marks::writeSessionConfig(KConfigGroup &config) const { QStringList l; Q_FOREACH (QChar key, m_marks.keys()) { l << key << QString::number(m_marks.value(key)->line()) << QString::number(m_marks.value(key)->column()); } config.writeEntry("ViMarks", l); } void Marks::setMark(const QChar &_mark, const KTextEditor::Cursor &pos, const bool moveoninsert) { m_settingMark = true; uint marktype = m_doc->mark(pos.line()); // ` and ' is the same register (position before jump) const QChar mark = (_mark == BeforeJumpAlter) ? BeforeJump : _mark; // delete old cursor if any if (KTextEditor::MovingCursor *oldCursor = m_marks.value(mark)) { int number_of_marks = 0; foreach (QChar c, m_marks.keys()) { if (m_marks.value(c)->line() == oldCursor->line()) { number_of_marks++; } } if (number_of_marks == 1 && pos.line() != oldCursor->line()) { m_doc->removeMark(oldCursor->line(), KTextEditor::MarkInterface::markType01); } delete oldCursor; } KTextEditor::MovingCursor::InsertBehavior behavior = moveoninsert ? KTextEditor::MovingCursor::MoveOnInsert : KTextEditor::MovingCursor::StayOnInsert; // create and remember new one m_marks.insert(mark, m_doc->newMovingCursor(pos, behavior)); // Showing what mark we set: if (isShowable(mark)) { if (!(marktype & KTextEditor::MarkInterface::markType01)) { m_doc->addMark(pos.line(), KTextEditor::MarkInterface::markType01); } // only show message for active view if ( m_inputModeManager->view()->viewInputMode() == KTextEditor::View::ViInputMode ) { if (m_doc->activeView() == m_inputModeManager->view()) { m_inputModeManager->getViNormalMode()->message(i18n("Mark set: %1", mark)); } } } m_settingMark = false; } KTextEditor::Cursor Marks::getMarkPosition(const QChar &mark) const { if (m_marks.contains(mark)) { KTextEditor::MovingCursor *c = m_marks.value(mark); return KTextEditor::Cursor(c->line(), c->column()); } return KTextEditor::Cursor::invalid(); } void Marks::markChanged(KTextEditor::Document *doc, KTextEditor::Mark mark, KTextEditor::MarkInterface::MarkChangeAction action) { Q_UNUSED(doc) if (mark.type != KTextEditor::MarkInterface::Bookmark || m_settingMark) { return; } if (action == KTextEditor::MarkInterface::MarkRemoved) { foreach (QChar markerChar, m_marks.keys()) { if (m_marks.value(markerChar)->line() == mark.line) { m_marks.remove(markerChar); } } } else if (action == KTextEditor::MarkInterface::MarkAdded) { bool freeMarkerCharFound = false; - for (const QChar markerChar : UserMarks) { + for (const QChar &markerChar : UserMarks) { if (!m_marks.value(markerChar)) { setMark(markerChar, KTextEditor::Cursor(mark.line, 0)); freeMarkerCharFound = true; break; } } if (!freeMarkerCharFound) { m_inputModeManager->getViNormalMode()->error(i18n("There are no more chars for the next bookmark.")); } } } void Marks::syncViMarksAndBookmarks() { const QHash &marks = m_doc->marks(); // Each bookmark should have a vi mark on the same line. for (auto mark : marks) { if (!(mark->type & KTextEditor::MarkInterface::markType01)) { continue; } bool thereIsViMarkForThisLine = false; Q_FOREACH (const KTextEditor::MovingCursor *cursor, m_marks) { if (cursor->line() == mark->line) { thereIsViMarkForThisLine = true; break; } } if (thereIsViMarkForThisLine) { continue; } - for (const QChar markerChar : UserMarks) { + for (const QChar &markerChar : UserMarks) { if (!m_marks.value(markerChar)) { setMark(markerChar, KTextEditor::Cursor(mark->line, 0)); break; } } } // For showable vi mark a line should be bookmarked. Q_FOREACH (const QChar &markChar, m_marks.keys()) { if (!isShowable(markChar)) { continue; } bool thereIsKateMarkForThisLine = false; for (auto mark : marks) { if (!(mark->type & KTextEditor::MarkInterface::markType01)) { continue; } if (m_marks.value(markChar)->line() == mark->line) { thereIsKateMarkForThisLine = true; break; } } if (!thereIsKateMarkForThisLine) { m_doc->addMark(m_marks.value(markChar)->line(), KTextEditor::MarkInterface::markType01); } } } QString Marks::getMarksOnTheLine(int line) const { QString res; Q_FOREACH (QChar markerChar, m_marks.keys()) { if (m_marks.value(markerChar)->line() == line) { res += markerChar + QLatin1String(":") + QString::number(m_marks.value(markerChar)->column()) + QLatin1String(" "); } } return res; } bool Marks::isShowable(const QChar &mark) { return FirstUserMark <= mark && mark <= LastUserMark; } void Marks::setStartEditYanked(const KTextEditor::Cursor &pos) { setMark(BeginEditYanked, pos, false); } void Marks::setFinishEditYanked(const KTextEditor::Cursor &pos) { setMark(EndEditYanked, pos); } void Marks::setLastChange(const KTextEditor::Cursor &pos) { setMark(LastChange, pos); } void Marks::setInsertStopped(const KTextEditor::Cursor &pos) { setMark(InsertStopped, pos); } void Marks::setSelectionStart(const KTextEditor::Cursor &pos) { setMark(SelectionBegin, pos); } void Marks::setSelectionFinish(const KTextEditor::Cursor &pos) { setMark(SelectionEnd, pos); } void Marks::setUserMark(const QChar &mark, const KTextEditor::Cursor &pos) { Q_ASSERT(FirstUserMark <= mark && mark <= LastUserMark); setMark(mark, pos); } KTextEditor::Cursor Marks::getStartEditYanked() const { return getMarkPosition(BeginEditYanked); } KTextEditor::Cursor Marks::getFinishEditYanked() const { return getMarkPosition(EndEditYanked); } KTextEditor::Cursor Marks::getSelectionStart() const { return getMarkPosition(SelectionBegin); } KTextEditor::Cursor Marks::getSelectionFinish() const { return getMarkPosition(SelectionEnd); } KTextEditor::Cursor Marks::getLastChange() const { return getMarkPosition(LastChange); } KTextEditor::Cursor Marks::getInsertStopped() const { return getMarkPosition(InsertStopped); } diff --git a/src/vimode/modes/insertvimode.cpp b/src/vimode/modes/insertvimode.cpp index 08de89f8..93f25eee 100644 --- a/src/vimode/modes/insertvimode.cpp +++ b/src/vimode/modes/insertvimode.cpp @@ -1,595 +1,593 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2008-2011 Erlend Hamberg * Copyright (C) 2011 Svyatoslav Kuzmich * Copyright (C) 2012 - 2013 Simon St James * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include "kateview.h" #include "kateviewinternal.h" #include "kateconfig.h" #include "katecompletionwidget.h" #include "katecompletiontree.h" #include "kateglobal.h" #include #include "katepartdebug.h" #include "kateviinputmode.h" #include #include #include #include #include #include using namespace KateVi; InsertViMode::InsertViMode(InputModeManager *viInputModeManager, KTextEditor::ViewPrivate *view, KateViewInternal *viewInternal) : ModeBase() { m_view = view; m_viewInternal = viewInternal; m_viInputModeManager = viInputModeManager; m_waitingRegister = false; m_blockInsert = None; m_eolPos = 0; m_count = 1; m_countedRepeatsBeginOnNewLine = false; m_isExecutingCompletion = false; connect(doc(), SIGNAL(textInserted(KTextEditor::Document*,KTextEditor::Range)), this, SLOT(textInserted(KTextEditor::Document*,KTextEditor::Range))); } InsertViMode::~InsertViMode() { } bool InsertViMode::commandInsertFromAbove() { KTextEditor::Cursor c(m_view->cursorPosition()); if (c.line() <= 0) { return false; } QString line = doc()->line(c.line() - 1); int tabWidth = doc()->config()->tabWidth(); QChar ch = getCharAtVirtualColumn(line, m_view->virtualCursorColumn(), tabWidth); if (ch == QChar::Null) { return false; } return doc()->insertText(c, ch); } bool InsertViMode::commandInsertFromBelow() { KTextEditor::Cursor c(m_view->cursorPosition()); if (c.line() >= doc()->lines() - 1) { return false; } QString line = doc()->line(c.line() + 1); int tabWidth = doc()->config()->tabWidth(); QChar ch = getCharAtVirtualColumn(line, m_view->virtualCursorColumn(), tabWidth); if (ch == QChar::Null) { return false; } return doc()->insertText(c, ch); } bool InsertViMode::commandDeleteWord() { KTextEditor::Cursor c1(m_view->cursorPosition()); KTextEditor::Cursor c2; c2 = findPrevWordStart(c1.line(), c1.column()); if (c2.line() != c1.line()) { if (c1.column() == 0) { c2.setColumn(doc()->line(c2.line()).length()); } else { c2.setColumn(0); c2.setLine(c2.line() + 1); } } Range r(c2, c1, ExclusiveMotion); return deleteRange(r, CharWise, false); } bool InsertViMode::commandDeleteLine() { KTextEditor::Cursor c(m_view->cursorPosition()); Range r(c.line(), 0, c.line(), c.column(), ExclusiveMotion); if (c.column() == 0) { // Try to move the current line to the end of the previous line. if (c.line() == 0) { return true; } else { r.startColumn = doc()->line(c.line() - 1).length(); r.startLine--; } } else { /* * Remove backwards until the first non-space character. If no * non-space was found, remove backwards to the first column. */ QRegExp nonSpace(QLatin1String("\\S")); r.startColumn = getLine().indexOf(nonSpace); if (r.startColumn == -1 || r.startColumn >= c.column()) { r.startColumn = 0; } } return deleteRange(r, CharWise, false); } bool InsertViMode::commandDeleteCharBackward() { KTextEditor::Cursor c(m_view->cursorPosition()); Range r(c.line(), c.column() - getCount(), c.line(), c.column(), ExclusiveMotion); if (c.column() == 0) { if (c.line() == 0) { return true; } else { r.startColumn = doc()->line(c.line() - 1).length(); r.startLine--; } } return deleteRange(r, CharWise); } bool InsertViMode::commandNewLine() { doc()->newLine(m_view); return true; } bool InsertViMode::commandIndent() { KTextEditor::Cursor c(m_view->cursorPosition()); doc()->indent(KTextEditor::Range(c.line(), 0, c.line(), 0), 1); return true; } bool InsertViMode::commandUnindent() { KTextEditor::Cursor c(m_view->cursorPosition()); doc()->indent(KTextEditor::Range(c.line(), 0, c.line(), 0), -1); return true; } bool InsertViMode::commandToFirstCharacterInFile() { KTextEditor::Cursor c(0,0); updateCursor(c); return true; } bool InsertViMode::commandToLastCharacterInFile() { int lines = doc()->lines() - 1; KTextEditor::Cursor c(lines, doc()->line(lines).length()); updateCursor(c); return true; } bool InsertViMode::commandMoveOneWordLeft() { KTextEditor::Cursor c(m_view->cursorPosition()); c = findPrevWordStart(c.line(), c.column()); if (!c.isValid()) { c = KTextEditor::Cursor(0, 0); } updateCursor(c); return true; } bool InsertViMode::commandMoveOneWordRight() { KTextEditor::Cursor c(m_view->cursorPosition()); c = findNextWordStart(c.line(), c.column()); if (!c.isValid()) { c = doc()->documentEnd(); } updateCursor(c); return true; } bool InsertViMode::commandCompleteNext() { if (m_view->completionWidget()->isCompletionActive()) { const QModelIndex oldCompletionItem = m_view->completionWidget()->treeView()->selectionModel()->currentIndex(); m_view->completionWidget()->cursorDown(); const QModelIndex newCompletionItem = m_view->completionWidget()->treeView()->selectionModel()->currentIndex(); if (newCompletionItem == oldCompletionItem) { // Wrap to top. m_view->completionWidget()->top(); } } else { m_view->userInvokedCompletion(); } return true; } bool InsertViMode::commandCompletePrevious() { if (m_view->completionWidget()->isCompletionActive()) { const QModelIndex oldCompletionItem = m_view->completionWidget()->treeView()->selectionModel()->currentIndex(); m_view->completionWidget()->cursorUp(); const QModelIndex newCompletionItem = m_view->completionWidget()->treeView()->selectionModel()->currentIndex(); if (newCompletionItem == oldCompletionItem) { // Wrap to bottom. m_view->completionWidget()->bottom(); } } else { m_view->userInvokedCompletion(); m_view->completionWidget()->bottom(); } return true; } bool InsertViMode::commandInsertContentOfRegister() { KTextEditor::Cursor c(m_view->cursorPosition()); KTextEditor::Cursor cAfter = c; QChar reg = getChosenRegister(m_register); OperationMode m = getRegisterFlag(reg); QString textToInsert = getRegisterContent(reg); if (textToInsert.isNull()) { error(i18n("Nothing in register %1", reg)); return false; } if (m == LineWise) { textToInsert.chop(1); // remove the last \n c.setColumn(doc()->lineLength(c.line())); // paste after the current line and ... textToInsert.prepend(QLatin1Char('\n')); // ... prepend a \n, so the text starts on a new line cAfter.setLine(cAfter.line() + 1); cAfter.setColumn(0); } else { cAfter.setColumn(cAfter.column() + textToInsert.length()); } doc()->insertText(c, textToInsert, m == Block); updateCursor(cAfter); return true; } // Start Normal mode just for one command and return to Insert mode bool InsertViMode::commandSwitchToNormalModeForJustOneCommand() { m_viInputModeManager->setTemporaryNormalMode(true); m_viInputModeManager->changeViMode(ViMode::NormalMode); const KTextEditor::Cursor cursorPos = m_view->cursorPosition(); // If we're at end of the line, move the cursor back one step, as in Vim. if (doc()->line(cursorPos.line()).length() == cursorPos.column()) { m_view->setCursorPosition(KTextEditor::Cursor(cursorPos.line(), cursorPos.column() - 1)); } m_viInputModeManager->inputAdapter()->setCaretStyle(KateRenderer::Block); emit m_view->viewModeChanged(m_view, m_view->viewMode()); m_viewInternal->repaint(); return true; } /** * checks if the key is a valid command * @return true if a command was completed and executed, false otherwise */ bool InsertViMode::handleKeypress(const QKeyEvent *e) { // backspace should work even if the shift key is down if (e->modifiers() != Qt::ControlModifier && e->key() == Qt::Key_Backspace) { m_view->backspace(); return true; } if (m_keys.isEmpty() && !m_waitingRegister) { if (e->modifiers() == Qt::NoModifier) { switch (e->key()) { case Qt::Key_Escape: leaveInsertMode(); return true; case Qt::Key_Left: m_view->cursorLeft(); return true; case Qt::Key_Right: m_view->cursorRight(); return true; case Qt::Key_Up: m_view->up(); return true; case Qt::Key_Down: m_view->down(); return true; case Qt::Key_Insert: startReplaceMode(); return true; case Qt::Key_Delete: m_view->keyDelete(); return true; case Qt::Key_Home: m_view->home(); return true; case Qt::Key_End: m_view->end(); return true; case Qt::Key_PageUp: m_view->pageUp(); return true; case Qt::Key_PageDown: m_view->pageDown(); return true; case Qt::Key_Enter: case Qt::Key_Return: if (m_view->completionWidget()->isCompletionActive() && !m_viInputModeManager->macroRecorder()->isReplaying() && !m_viInputModeManager->lastChangeRecorder()->isReplaying()) { // Filter out Enter/ Return's that trigger a completion when recording macros/ last change stuff; they // will be replaced with the special code "ctrl-space". // (This is why there is a "!m_viInputModeManager->isReplayingMacro()" above.) m_viInputModeManager->doNotLogCurrentKeypress(); m_isExecutingCompletion = true; m_textInsertedByCompletion.clear(); m_view->completionWidget()->execute(); completionFinished(); m_isExecutingCompletion = false; return true; } -#if QT_VERSION >= QT_VERSION_CHECK(5,8,0) Q_FALLTHROUGH(); -#endif default: return false; } } else if (e->modifiers() == Qt::ControlModifier) { switch (e->key()) { case Qt::Key_BracketLeft: case Qt::Key_3: leaveInsertMode(); return true; case Qt::Key_Space: // We use Ctrl-space as a special code in macros/ last change, which means: if replaying // a macro/ last change, fetch and execute the next completion for this macro/ last change ... if (!m_viInputModeManager->macroRecorder()->isReplaying() && !m_viInputModeManager->lastChangeRecorder()->isReplaying()) { commandCompleteNext(); // ... therefore, we should not record ctrl-space indiscriminately. m_viInputModeManager->doNotLogCurrentKeypress(); } else { m_viInputModeManager->completionReplayer()->replay(); } return true; case Qt::Key_C: leaveInsertMode(true); return true; case Qt::Key_D: commandUnindent(); return true; case Qt::Key_E: commandInsertFromBelow(); return true; case Qt::Key_N: if (!m_viInputModeManager->macroRecorder()->isReplaying()) { commandCompleteNext(); } return true; case Qt::Key_P: if (!m_viInputModeManager->macroRecorder()->isReplaying()) { commandCompletePrevious(); } return true; case Qt::Key_T: commandIndent(); return true; case Qt::Key_W: commandDeleteWord(); return true; case Qt::Key_U: return commandDeleteLine(); case Qt::Key_J: commandNewLine(); return true; case Qt::Key_H: commandDeleteCharBackward(); return true; case Qt::Key_Y: commandInsertFromAbove(); return true; case Qt::Key_O: commandSwitchToNormalModeForJustOneCommand(); return true; case Qt::Key_Home: commandToFirstCharacterInFile(); return true; case Qt::Key_R: m_waitingRegister = true; return true; case Qt::Key_End: commandToLastCharacterInFile(); return true; case Qt::Key_Left: commandMoveOneWordLeft(); return true; case Qt::Key_Right: commandMoveOneWordRight(); return true; default: return false; } } return false; } else if (m_waitingRegister) { // ignore modifier keys alone if (e->key() == Qt::Key_Shift || e->key() == Qt::Key_Control || e->key() == Qt::Key_Alt || e->key() == Qt::Key_Meta) { return false; } QChar key = KeyParser::self()->KeyEventToQChar(*e); key = key.toLower(); m_waitingRegister = false; // is it register ? // TODO: add registers such as '/'. See :h if ((key >= QLatin1Char('0') && key <= QLatin1Char('9')) || (key >= QLatin1Char('a') && key <= QLatin1Char('z')) || key == QLatin1Char('_') || key == QLatin1Char('+') || key == QLatin1Char('*') || key == QLatin1Char('"')) { m_register = key; } else { return false; } commandInsertContentOfRegister(); return true; } return false; } // leave insert mode when esc, etc, is pressed. if leaving block // prepend/append, the inserted text will be added to all block lines. if // ctrl-c is used to exit insert mode this is not done. void InsertViMode::leaveInsertMode(bool force) { m_view->abortCompletion(); if (!force) { if (m_blockInsert != None) { // block append/prepend // make sure cursor haven't been moved if (m_blockRange.startLine == m_view->cursorPosition().line()) { int start, len; QString added; KTextEditor::Cursor c; switch (m_blockInsert) { case Append: case Prepend: if (m_blockInsert == Append) { start = m_blockRange.endColumn + 1; } else { start = m_blockRange.startColumn; } len = m_view->cursorPosition().column() - start; added = getLine().mid(start, len); c = KTextEditor::Cursor(m_blockRange.startLine, start); for (int i = m_blockRange.startLine + 1; i <= m_blockRange.endLine; i++) { c.setLine(i); doc()->insertText(c, added); } break; case AppendEOL: start = m_eolPos; len = m_view->cursorPosition().column() - start; added = getLine().mid(start, len); c = KTextEditor::Cursor(m_blockRange.startLine, start); for (int i = m_blockRange.startLine + 1; i <= m_blockRange.endLine; i++) { c.setLine(i); c.setColumn(doc()->lineLength(i)); doc()->insertText(c, added); } break; default: error(QStringLiteral("not supported")); } } m_blockInsert = None; } else { const QString added = doc()->text(KTextEditor::Range(m_viInputModeManager->marks()->getStartEditYanked(), m_view->cursorPosition())); if (m_count > 1) { for (unsigned int i = 0; i < m_count - 1; i++) { if (m_countedRepeatsBeginOnNewLine) { doc()->newLine(m_view); } doc()->insertText(m_view->cursorPosition(), added); } } } } m_countedRepeatsBeginOnNewLine = false; startNormalMode(); } void InsertViMode::setBlockPrependMode(Range blockRange) { // ignore if not more than one line is selected if (blockRange.startLine != blockRange.endLine) { m_blockInsert = Prepend; m_blockRange = blockRange; } } void InsertViMode::setBlockAppendMode(Range blockRange, BlockInsert b) { Q_ASSERT(b == Append || b == AppendEOL); // ignore if not more than one line is selected if (blockRange.startLine != blockRange.endLine) { m_blockRange = blockRange; m_blockInsert = b; if (b == AppendEOL) { m_eolPos = doc()->lineLength(m_blockRange.startLine); } } else { qCDebug(LOG_KTE) << "cursor moved. ignoring block append/prepend"; } } void InsertViMode::completionFinished() { Completion::CompletionType completionType = Completion::PlainText; if (m_view->cursorPosition() != m_textInsertedByCompletionEndPos) { completionType = Completion::FunctionWithArgs; } else if (m_textInsertedByCompletion.endsWith(QLatin1String("()")) || m_textInsertedByCompletion.endsWith(QLatin1String("();"))) { completionType = Completion::FunctionWithoutArgs; } m_viInputModeManager->completionRecorder()->logCompletionEvent(Completion(m_textInsertedByCompletion, KateViewConfig::global()->wordCompletionRemoveTail(), completionType)); } void InsertViMode::textInserted(KTextEditor::Document *document, KTextEditor::Range range) { if (m_isExecutingCompletion) { m_textInsertedByCompletion += document->text(range); m_textInsertedByCompletionEndPos = range.end(); } } diff --git a/src/vimode/modes/insertvimode.h b/src/vimode/modes/insertvimode.h index 3d1bba4e..ca79f82a 100644 --- a/src/vimode/modes/insertvimode.h +++ b/src/vimode/modes/insertvimode.h @@ -1,120 +1,120 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2008-2011 Erlend Hamberg * Copyright (C) 2011 Svyatoslav Kuzmich * Copyright (C) 2012 - 2013 Simon St James * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KATEVI_INSERT_VI_MODE_H #define KATEVI_INSERT_VI_MODE_H #include #include namespace KTextEditor { class ViewPrivate; } class KateViewInternal; class QKeyEvent; namespace KateVi { class Motion; /** * Commands for the vi insert mode */ enum BlockInsert { None, Prepend, Append, AppendEOL }; class KTEXTEDITOR_EXPORT InsertViMode : public ModeBase { Q_OBJECT public: explicit InsertViMode(InputModeManager *viInputModeManager, KTextEditor::ViewPrivate *view, KateViewInternal *viewInternal); - virtual ~InsertViMode(); + ~InsertViMode() override; - bool handleKeypress(const QKeyEvent *e) Q_DECL_OVERRIDE; + bool handleKeypress(const QKeyEvent *e) override; bool commandInsertFromAbove(); bool commandInsertFromBelow(); bool commandDeleteWord(); bool commandDeleteLine(); bool commandNewLine(); bool commandDeleteCharBackward(); bool commandIndent(); bool commandUnindent(); bool commandToFirstCharacterInFile(); bool commandToLastCharacterInFile(); bool commandMoveOneWordLeft(); bool commandMoveOneWordRight(); bool commandCompleteNext(); bool commandCompletePrevious(); bool commandInsertContentOfRegister(); bool commandSwitchToNormalModeForJustOneCommand(); void setBlockPrependMode(Range blockRange); void setBlockAppendMode(Range blockRange, BlockInsert b); void setCount(int count) { m_count = count; - }; + } void setCountedRepeatsBeginOnNewLine(bool countedRepeatsBeginOnNewLine) { m_countedRepeatsBeginOnNewLine = countedRepeatsBeginOnNewLine; - }; + } protected: void leaveInsertMode(bool force = false); void completionFinished(); protected: BlockInsert m_blockInsert; unsigned int m_eolPos; // length of first line in eol mode before text is appended Range m_blockRange; QString m_keys; bool m_waitingRegister; unsigned int m_count; bool m_countedRepeatsBeginOnNewLine; bool m_isExecutingCompletion; QString m_textInsertedByCompletion; KTextEditor::Cursor m_textInsertedByCompletionEndPos; private Q_SLOTS: void textInserted(KTextEditor::Document *document, KTextEditor::Range range); }; } #endif /* KATEVI_INSERT_VI_MODE_H */ diff --git a/src/vimode/modes/modebase.h b/src/vimode/modes/modebase.h index 7a9da971..5d2484e1 100644 --- a/src/vimode/modes/modebase.h +++ b/src/vimode/modes/modebase.h @@ -1,182 +1,178 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2008 - 2009 Erlend Hamberg * Copyright (C) 2009 Paul Gideon Dann * Copyright (C) 2011 Svyatoslav Kuzmich * Copyright (C) 2012 - 2013 Simon St James * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KATEVI_MODE_BASE_H #define KATEVI_MODE_BASE_H #include #include #include "kateview.h" #include #include class QKeyEvent; class QString; class QRegExp; namespace KTextEditor { class DocumentPrivate; } namespace KateVi { class InputModeManager; enum Direction { Up, Down, Left, Right, Next, Prev }; class KTEXTEDITOR_EXPORT ModeBase : public QObject { Q_OBJECT public: ModeBase() - : QObject(), - m_count(0), - m_oneTimeCountOverride(-1), - m_iscounted(false), - m_stickyColumn(-1) + : QObject() { } virtual ~ModeBase() {} /** * @return normal mode command accumulated so far */ QString getVerbatimKeys() const; virtual bool handleKeypress(const QKeyEvent *e) = 0; void setCount(unsigned int count) { m_count = count; } void setRegister(QChar reg) { m_register = reg; } void error(const QString &errorMsg); void message(const QString &msg); Range motionFindNext(); Range motionFindPrev(); virtual void goToPos(const Range &r); int getCount() const; protected: // helper methods void yankToClipBoard(QChar chosen_register, QString text); bool deleteRange(Range &r, OperationMode mode = LineWise, bool addToRegister = true); const QString getRange(Range &r, OperationMode mode = LineWise) const; const QString getLine(int line = -1) const; const QChar getCharUnderCursor() const; const QString getWordUnderCursor() const; const KTextEditor::Range getWordRangeUnderCursor() const; KTextEditor::Cursor findNextWordStart(int fromLine, int fromColumn, bool onlyCurrentLine = false) const; KTextEditor::Cursor findNextWORDStart(int fromLine, int fromColumn, bool onlyCurrentLine = false) const; KTextEditor::Cursor findPrevWordStart(int fromLine, int fromColumn, bool onlyCurrentLine = false) const; KTextEditor::Cursor findPrevWORDStart(int fromLine, int fromColumn, bool onlyCurrentLine = false) const; KTextEditor::Cursor findPrevWordEnd(int fromLine, int fromColumn, bool onlyCurrentLine = false) const; KTextEditor::Cursor findPrevWORDEnd(int fromLine, int fromColumn, bool onlyCurrentLine = false) const; KTextEditor::Cursor findWordEnd(int fromLine, int fromColumn, bool onlyCurrentLine = false) const; KTextEditor::Cursor findWORDEnd(int fromLine, int fromColumn, bool onlyCurrentLine = false) const; Range findSurroundingBrackets(const QChar &c1, const QChar &c2, bool inner, const QChar &nested1, const QChar &nested2) const; Range findSurrounding(const QRegExp &c1, const QRegExp &c2, bool inner = false) const; Range findSurroundingQuotes(const QChar &c, bool inner = false) const; int findLineStartingWitchChar(const QChar &c, int count, bool forward = true) const; void updateCursor(const KTextEditor::Cursor &c) const; const QChar getCharAtVirtualColumn(const QString &line, int virtualColumn, int tabWidht) const; void addToNumberUnderCursor(int count); Range goLineUp(); Range goLineDown(); Range goLineUpDown(int lines); Range goVisualLineUpDown(int lines); unsigned int linesDisplayed() const; void scrollViewLines(int l); bool isCounted() const { return m_iscounted; } bool startNormalMode(); bool startInsertMode(); bool startVisualMode(); bool startVisualLineMode(); bool startVisualBlockMode(); bool startReplaceMode(); QChar getChosenRegister(const QChar &defaultReg) const; QString getRegisterContent(const QChar ®); OperationMode getRegisterFlag(const QChar ®) const; void fillRegister(const QChar ®, const QString &text, OperationMode flag = CharWise); void switchView(Direction direction = Next); void addJump(KTextEditor::Cursor cursor); KTextEditor::Cursor getNextJump(KTextEditor::Cursor) const; KTextEditor::Cursor getPrevJump(KTextEditor::Cursor) const; inline KTextEditor::DocumentPrivate *doc() const { return m_view->doc(); - }; + } protected: QChar m_register; Range m_commandRange; - unsigned int m_count; - int m_oneTimeCountOverride; - bool m_iscounted; + unsigned int m_count = 0; + int m_oneTimeCountOverride = -1; + bool m_iscounted = false; QString m_extraWordCharacters; QString m_keysVerbatim; - int m_stickyColumn; + int m_stickyColumn = -1; bool m_lastMotionWasVisualLineUpOrDown; bool m_currentMotionWasVisualLineUpOrDown; KTextEditor::ViewPrivate *m_view; KateViewInternal *m_viewInternal; InputModeManager *m_viInputModeManager; // info message of vi mode QPointer m_infoMessage; }; } #endif diff --git a/src/vimode/modes/normalvimode.h b/src/vimode/modes/normalvimode.h index 5d78003d..35dda322 100644 --- a/src/vimode/modes/normalvimode.h +++ b/src/vimode/modes/normalvimode.h @@ -1,398 +1,398 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2008 - 2009 Erlend Hamberg * Copyright (C) 2009 Paul Gideon Dann * Copyright (C) 2011 Svyatoslav Kuzmich * Copyright (C) 2012 - 2013 Simon St James * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KATEVI_NORMAL_VI_MODE_H #define KATEVI_NORMAL_VI_MODE_H #include #include #include #include #include #include #include #include class QKeyEvent; class KateViInputMode; namespace KateVi { class Command; class Motion; class KeyParser; class InputModeManager; /** * Commands for the vi normal mode */ class KTEXTEDITOR_EXPORT NormalViMode : public ModeBase { Q_OBJECT friend KateViInputMode; public: explicit NormalViMode(InputModeManager *viInputModeManager, KTextEditor::ViewPrivate *view, KateViewInternal *viewInternal); - virtual ~NormalViMode(); + ~NormalViMode() override; - bool handleKeypress(const QKeyEvent *e) Q_DECL_OVERRIDE; + bool handleKeypress(const QKeyEvent *e) override; bool commandEnterInsertMode(); bool commandEnterInsertModeAppend(); bool commandEnterInsertModeAppendEOL(); bool commandEnterInsertModeBeforeFirstNonBlankInLine(); bool commandEnterInsertModeLast(); bool commandEnterVisualMode(); bool commandEnterVisualLineMode(); bool commandEnterVisualBlockMode(); bool commandReselectVisual(); bool commandToOtherEnd(); bool commandEnterReplaceMode(); bool commandDelete(); bool commandDeleteToEOL(); bool commandDeleteLine(); bool commandMakeLowercase(); bool commandMakeLowercaseLine(); bool commandMakeUppercase(); bool commandMakeUppercaseLine(); bool commandChangeCase(); bool commandChangeCaseRange(); bool commandChangeCaseLine(); bool commandOpenNewLineUnder(); bool commandOpenNewLineOver(); bool commandJoinLines(); bool commandChange(); bool commandChangeLine(); bool commandChangeToEOL(); bool commandSubstituteChar(); bool commandSubstituteLine(); bool commandYank(); bool commandYankLine(); bool commandYankToEOL(); bool commandPaste(); bool commandPasteBefore(); bool commandgPaste(); bool commandgPasteBefore(); bool commandIndentedPaste(); bool commandIndentedPasteBefore(); bool commandDeleteChar(); bool commandDeleteCharBackward(); bool commandReplaceCharacter(); bool commandSwitchToCmdLine(); bool commandSearchBackward(); bool commandSearchForward(); bool commandUndo(); bool commandRedo(); bool commandSetMark(); bool commandIndentLine(); bool commandUnindentLine(); bool commandIndentLines(); bool commandUnindentLines(); bool commandScrollPageDown(); bool commandScrollPageUp(); bool commandScrollHalfPageUp(); bool commandScrollHalfPageDown(); bool commandCenterView(bool onFirst); bool commandCenterViewOnNonBlank(); bool commandCenterViewOnCursor(); bool commandTopView(bool onFirst); bool commandTopViewOnNonBlank(); bool commandTopViewOnCursor(); bool commandBottomView(bool onFirst); bool commandBottomViewOnNonBlank(); bool commandBottomViewOnCursor(); bool commandAbort(); bool commandPrintCharacterCode(); bool commandRepeatLastChange(); bool commandAlignLine(); bool commandAlignLines(); bool commandAddToNumber(); bool commandSubtractFromNumber(); bool commandPrependToBlock(); bool commandAppendToBlock(); bool commandGoToNextJump(); bool commandGoToPrevJump(); bool commandSwitchToLeftView(); bool commandSwitchToUpView(); bool commandSwitchToDownView(); bool commandSwitchToRightView(); bool commandSwitchToNextView(); bool commandSplitHoriz(); bool commandSplitVert(); bool commandCloseView(); bool commandSwitchToNextTab(); bool commandSwitchToPrevTab(); bool commandFormatLine(); bool commandFormatLines(); bool commandCollapseToplevelNodes(); bool commandCollapseLocal(); bool commandExpandAll(); bool commandExpandLocal(); bool commandToggleRegionVisibility(); bool commandStartRecordingMacro(); bool commandReplayMacro(); bool commandCloseWrite(); bool commandCloseNocheck(); // MOTIONS Range motionLeft(); Range motionRight(); Range motionDown(); Range motionUp(); Range motionPageDown(); Range motionPageUp(); Range motionHalfPageDown(); Range motionHalfPageUp(); Range motionUpToFirstNonBlank(); Range motionDownToFirstNonBlank(); Range motionWordForward(); Range motionWordBackward(); Range motionWORDForward(); Range motionWORDBackward(); Range motionToEndOfWord(); Range motionToEndOfWORD(); Range motionToEndOfPrevWord(); Range motionToEndOfPrevWORD(); Range motionFindChar(); Range motionFindCharBackward(); Range motionToChar(); Range motionToCharBackward(); Range motionRepeatlastTF(); Range motionRepeatlastTFBackward(); Range motionToEOL(); Range motionToColumn0(); Range motionToFirstCharacterOfLine(); Range motionToLineFirst(); Range motionToLineLast(); Range motionToScreenColumn(); Range motionToMark(); Range motionToMarkLine(); Range motionToMatchingItem(); Range motionToPreviousBraceBlockStart(); Range motionToNextBraceBlockStart(); Range motionToPreviousBraceBlockEnd(); Range motionToNextBraceBlockEnd(); Range motionToNextOccurrence(); Range motionToPrevOccurrence(); Range motionToFirstLineOfWindow(); Range motionToMiddleLineOfWindow(); Range motionToLastLineOfWindow(); Range motionToNextVisualLine(); Range motionToPrevVisualLine(); Range motionToPreviousSentence(); Range motionToNextSentence(); Range motionToBeforeParagraph(); Range motionToAfterParagraph(); Range motionToIncrementalSearchMatch(); // TEXT OBJECTS Range textObjectAWord(); Range textObjectInnerWord(); Range textObjectAWORD(); Range textObjectInnerWORD(); Range textObjectInnerSentence(); Range textObjectASentence(); Range textObjectInnerParagraph(); Range textObjectAParagraph(); Range textObjectAQuoteDouble(); Range textObjectInnerQuoteDouble(); Range textObjectAQuoteSingle(); Range textObjectInnerQuoteSingle(); Range textObjectABackQuote(); Range textObjectInnerBackQuote(); Range textObjectAParen(); Range textObjectInnerParen(); Range textObjectABracket(); Range textObjectInnerBracket(); Range textObjectACurlyBracket(); Range textObjectInnerCurlyBracket(); Range textObjectAInequalitySign(); Range textObjectInnerInequalitySign(); Range textObjectAComma(); Range textObjectInnerComma(); virtual void reset(); void beginMonitoringDocumentChanges(); protected: void resetParser(); void initializeCommands(); QRegExp generateMatchingItemRegex() const; void executeCommand(const Command *cmd); OperationMode getOperationMode() const; void highlightYank(const Range &range, const OperationMode mode = CharWise); void addHighlightYank(const KTextEditor::Range &range); bool motionWillBeUsedWithCommand() const { return !m_awaitingMotionOrTextObject.isEmpty(); }; void joinLines(unsigned int from, unsigned int to) const; void reformatLines(unsigned int from, unsigned int to) const; bool executeKateCommand(const QString &command); /** * Get the index of the first non-blank character from the given line. * * @param line The line to be picked. The current line will picked instead * if this parameter is set to a negative value. * @returns the index of the first non-blank character from the given line. * If a non-space character cannot be found, the 0 is returned. */ int getFirstNonBlank(int line = -1) const; Range textObjectComma(bool inner) const; void shrinkRangeAroundCursor(Range &toShrink, const Range &rangeToShrinkTo) const; KTextEditor::Cursor findSentenceStart(); KTextEditor::Cursor findSentenceEnd(); KTextEditor::Cursor findParagraphStart(); KTextEditor::Cursor findParagraphEnd(); protected: // The 'current position' is the current cursor position for non-linewise pastes, and the current // line for linewise. enum PasteLocation { AtCurrentPosition, AfterCurrentPosition }; bool paste(NormalViMode::PasteLocation pasteLocation, bool isgPaste, bool isIndentedPaste); KTextEditor::Cursor cursorPosAtEndOfPaste(const KTextEditor::Cursor &pasteLocation, const QString &pastedText) const; protected: QString m_keys; QString m_lastTFcommand; // holds the last t/T/f/F command so that it can be repeated with ;/, unsigned int m_countTemp; int m_motionOperatorIndex; int m_scroll_count_limit; QVector m_commands; QVector m_motions; QVector m_matchingCommands; QVector m_matchingMotions; QStack m_awaitingMotionOrTextObject; bool m_findWaitingForChar; bool m_isRepeatedTFcommand; bool m_linewiseCommand; bool m_commandWithMotion; bool m_lastMotionWasLinewiseInnerBlock; bool m_motionCanChangeWholeVisualModeSelection; bool m_commandShouldKeepSelection; bool m_deleteCommand; // Ctrl-c or ESC have been pressed, leading to a call to reset(). bool m_pendingResetIsDueToExit; bool m_isUndo; bool waitingForRegisterOrCharToSearch(); // item matching ('%' motion) QHash m_matchingItems; QRegExp m_matchItemRegex; KeyParser *m_keyParser; KTextEditor::Attribute::Ptr m_highlightYankAttribute; QSet m_highlightedYanks; QSet &highlightedYankForDocument(); KTextEditor::Cursor m_currentChangeEndMarker; KTextEditor::Cursor m_positionWhenIncrementalSearchBegan; private Q_SLOTS: void textInserted(KTextEditor::Document *document, KTextEditor::Range range); void textRemoved(KTextEditor::Document *, KTextEditor::Range); void undoBeginning(); void undoEnded(); void updateYankHighlightAttrib(); void clearYankHighlight(); void aboutToDeleteMovingInterfaceContent(); }; } #endif /* KATEVI_NORMAL_VI_MODE_H */ diff --git a/src/vimode/modes/replacevimode.h b/src/vimode/modes/replacevimode.h index a39a8b2a..b5ef994d 100644 --- a/src/vimode/modes/replacevimode.h +++ b/src/vimode/modes/replacevimode.h @@ -1,91 +1,91 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2008 Erlend Hamberg * * 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 KATEVI_REPLACE_MODE_H #define KATEVI_REPLACE_MODE_H #include namespace KateVi { /** * Commands for the vi replace mode */ class ReplaceViMode : public ModeBase { public: explicit ReplaceViMode(InputModeManager *viInputModeManager, KTextEditor::ViewPrivate *view, KateViewInternal *viewInternal); - virtual ~ReplaceViMode(); + ~ReplaceViMode() override; /// Update the track of overwritten characters with the \p s character. inline void overwrittenChar(const QChar &s) { m_overwritten += s; } - void setCount(int count) { m_count = count;}; + void setCount(int count) { m_count = count;} protected: /** * Checks if the key is a valid command in replace mode. * * @returns true if a command was completed and executed, false otherwise. */ - bool handleKeypress(const QKeyEvent *e) Q_DECL_OVERRIDE; + bool handleKeypress(const QKeyEvent *e) override; private: /** * Replace the character at the current column with a character from * the same column but in a different line. * * @param offset The offset of the line to be picked. This value is * relative to the current line. * @returns true if the character could be replaced, false otherwise. */ bool commandInsertFromLine(int offset); // Auxiliar methods for moving the cursor in replace mode. bool commandMoveOneWordLeft(); bool commandMoveOneWordRight(); // Auxiliar methods for removing modifications. void backspace(); void commandBackWord(); void commandBackLine(); void leaveReplaceMode(); protected: unsigned int m_count; private: /// Keeps track of the characters that have been overwritten so far. QString m_overwritten; }; } #endif /* KATEVI_REPLACE_MODE_H */ diff --git a/src/vimode/modes/visualvimode.h b/src/vimode/modes/visualvimode.h index 70c866f3..bd442dbe 100644 --- a/src/vimode/modes/visualvimode.h +++ b/src/vimode/modes/visualvimode.h @@ -1,121 +1,121 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2008 - 2009 Erlend Hamberg * Copyright (C) 2009 Paul Gideon Dann * Copyright (C) 2011 Svyatoslav Kuzmich * Copyright (C) 2012 - 2013 Simon St James * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KATEVI_VISUAL_VI_MODE_H #define KATEVI_VISUAL_VI_MODE_H #include #include namespace KateVi { class InputModeManager; class VisualViMode : public NormalViMode { Q_OBJECT public: explicit VisualViMode(InputModeManager *viInputModeManager, KTextEditor::ViewPrivate *view, KateViewInternal *viewInternal); - virtual ~VisualViMode(); + ~VisualViMode() override; void init(); bool isVisualLine() const { return m_mode == VisualLineMode; } bool isVisualBlock() const { return m_mode == VisualBlockMode; } void switchStartEnd(); - void reset() Q_DECL_OVERRIDE; + void reset() override; void setVisualModeType(const ViMode mode); void saveRangeMarks(); void setStart(const KTextEditor::Cursor &c) { m_start = c; } KTextEditor::Cursor getStart() { return m_start; } void goToPos(const KTextEditor::Cursor &c); ViMode getLastVisualMode() const { return m_lastVisualMode; } const KTextEditor::Cursor &getStart() const { return m_start; } // Selects all lines in range; void selectLines(const KTextEditor::Range &range); // Selects range between c1 and c2, but includes the end cursor position. void selectInclusive(const KTextEditor::Cursor &c1, const KTextEditor::Cursor &c2); // Select block between c1 and c2. void selectBlockInclusive(const KTextEditor::Cursor &c1, const KTextEditor::Cursor &c2); protected: /** * Called when a motion/text object is used. Updates the cursor position * and modifies the range. A motion will only modify the end of the range * (i.e. move the cursor) while a text object may modify both the start and * the end. Overriden from the ModeBase class. */ - void goToPos(const Range &r) Q_DECL_OVERRIDE; + void goToPos(const Range &r) override; private: void initializeCommands(); public Q_SLOTS: /** * Updates the visual mode's range to reflect a new cursor position. This * needs to be called if modifying the range from outside the vi mode, e.g. * via mouse selection. */ void updateSelection(); private: KTextEditor::Cursor m_start; ViMode m_mode, m_lastVisualMode; }; } #endif /* KATEVI_VISUAL_VI_MODE_H */ diff --git a/templates/ktexteditor-plugin/ktexteditor-plugin.kdevtemplate b/templates/ktexteditor-plugin/ktexteditor-plugin.kdevtemplate index 0a87cfeb..c068c307 100644 --- a/templates/ktexteditor-plugin/ktexteditor-plugin.kdevtemplate +++ b/templates/ktexteditor-plugin/ktexteditor-plugin.kdevtemplate @@ -1,69 +1,71 @@ # KDE Config File [General] Name=C++ Name[ast]=C++ Name[ca]=C++ Name[ca@valencia]=C++ Name[cs]=C++ Name[da]=C++ Name[de]=C++ Name[en_GB]=C++ Name[es]=C++ Name[eu]=C++ Name[fi]=C++ Name[fr]=C++ Name[gl]=C++ Name[ia]=C++ +Name[id]=C++ Name[it]=C++ Name[ko]=C++ Name[nb]=C++ Name[nl]=C++ Name[nn]=C++ Name[pl]=C++ Name[pt]=C++ Name[pt_BR]=C++ Name[ru]=C++ Name[sl]=C++ Name[sr]=Ц++ Name[sr@ijekavian]=Ц++ Name[sr@ijekavianlatin]=C++ Name[sr@latin]=C++ Name[sv]=C++ Name[tr]=C++ Name[uk]=C++ Name[x-test]=xxC++xx Name[zh_CN]=C++ Name[zh_TW]=C++ Comment=Generates a KTextEditor C++ plugin to perform special operations on text in KWrite, Kate, KDevelop etc. Comment[ca]=Genera un connector en C++ del KTextEditor C++ per portar a terme operacions especials amb el text al KWrite, Kate, KDevelop, etc. Comment[ca@valencia]=Genera un connector en C++ del KTextEditor C++ per portar a terme operacions especials amb el text al KWrite, Kate, KDevelop, etc. Comment[da]=Genererer en skabelon til et KTextEditor C++-plugin til at udføre særlige handlinger på tekst i KWrite, Kate, KDevelop osv. Comment[de]=Erstellt ein C++-KTextEditor-Module zur Ausführung spezieller Aufgaben an Texten in KWrite, Kate, KDevelop usw. Comment[en_GB]=Generates a KTextEditor C++ plugin to perform special operations on text in KWrite, Kate, KDevelop etc. Comment[es]=Genera un complemento de KTextEditor en C++ para realizar operaciones especiales sobre el texto en KWrite, Kate, KDevelop, etc. Comment[eu]=KTextEditor C++ plugin bat sortzen du testuan eragiketa bereziak egiteko KWrite, Kate, KDevelop eta abarretan. Comment[fi]=Luo C++-kielisen KTextEditor-liitännäisen, jolla voi käsitellä tekstiä KWritessa, Katessa, KDevelopissa jne. Comment[fr]=Génère un module C++ pour KTextEditor pour appliquer des opérations spéciales sur du texte dans KWrite, Kate, KDevelop etc. Comment[gl]=Xera un complemento de KTextEditor en C++ para realizar operacións especiais en texto en KWrite, Kate, KDevelop, etc. +Comment[id]=Menghasilkan sebuah plugin KTextEditor C++ untuk melakukan operasi khusus pada teks dalam KWrite, Kate, KDevelop dll. Comment[it]=Genera un'estensione KTextEditor C++ per eseguire particolari operazioni sul testo in KWrite, Kate, KDevelop ecc. Comment[ko]=KWrite, Kate, KDevelop 등에서 텍스트에 작업을 수행하는 KTextEditor C++ 플러그인을 만듭니다. Comment[nl]=Genereert een KTextEditor C++ plug-in om speciale bewerkingen uit te voeren op tekst in KWrite, Kate, KDevelop etc. Comment[nn]=Genererer eit C++-basert programtillegg for KTextEditor som kan utføra spesielle handlingar på tekst i KWrite, Kate, KDevelop osv. Comment[pl]=Tworzy wtyczkę Co KTextEditor-a, umożliwiającej specjalne operacje na tekście w programach KWrite, Kate, KDevelop itp. Comment[pt]=Gera um 'plugin' em C++ do KTextEditor para efectuar operações especiais sobre o texto no KWrite, Kate, KDevelop etc. Comment[pt_BR]=Gera um plugin do KTextEditor para executar operações especiais no texto no KWrite, Kate, KDevelop, etc. Comment[ru]=Модуль для KTextEditor на языке C++ для добавления новых действий по работе с текстом в KWrite, Kate, KDevelop и другие программы. Comment[sl]=Ustvari vstavek C++ za KTextEditor, ki omogoča izvajanje posebnih dejanj na besedilu v KWrite, Kate, KDevelop, itn. Comment[sr]=Генерише Ц++ прикључак за KTextEditor, за посебне поступке над текстом у К‑писању, Кејт, К‑девелопу итд. Comment[sr@ijekavian]=Генерише Ц++ прикључак за KTextEditor, за посебне поступке над текстом у К‑писању, Кејт, К‑девелопу итд. Comment[sr@ijekavianlatin]=Generiše C++ priključak za KTextEditor, za posebne postupke nad tekstom u K‑pisanju, Kate, KDevelopu itd. Comment[sr@latin]=Generiše C++ priključak za KTextEditor, za posebne postupke nad tekstom u K‑pisanju, Kate, KDevelopu itd. Comment[sv]=Skapar en Ktexteditor C++ insticksmodul för att utföra särskilda åtgärder med text i Kwrite, Kate, KDevlop, etc. Comment[tr]=KWrite, Kate, KDevelop vb. Metin üzerinde özel işlemleri yapmak için bir KTextEditor C++ eklentisi üretir. Comment[uk]=Створює додаток C++ до KTextEditor для виконання спеціальних дій над текстом у KWrite, Kate, KDevelop тощо. Comment[x-test]=xxGenerates a KTextEditor C++ plugin to perform special operations on text in KWrite, Kate, KDevelop etc.xx Comment[zh_CN]=生成一个 KTextEditor c + + 插件在 KWrite、 Kate、 KDevelop 等应用中执行特殊文本操作的文本。 Comment[zh_TW]=生成一個 KTextEditor C++ 外掛程式來對 KWrite、Kate 與 KDevelop 中的文字做出特殊操作 Category=KTextEditor/Plugin Icon=ktexteditor-plugin.png ShowFilesAfterGeneration=%{dest}/README.md