diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 802594d1..505d4cd2 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -1,162 +1,163 @@ find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Test) include(ECMMarkAsTest) include(ECMAddTests) remove_definitions(-DQT_NO_CAST_FROM_ASCII) set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories( # for config.h ${CMAKE_BINARY_DIR} # for generated ktexteditor headers ${CMAKE_BINARY_DIR}/src/include # for normal sources ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/include ${CMAKE_SOURCE_DIR}/src/buffer ${CMAKE_SOURCE_DIR}/src/completion ${CMAKE_SOURCE_DIR}/src/dialogs ${CMAKE_SOURCE_DIR}/src/document ${CMAKE_SOURCE_DIR}/src/script ${CMAKE_SOURCE_DIR}/src/mode ${CMAKE_SOURCE_DIR}/src/render ${CMAKE_SOURCE_DIR}/src/search ${CMAKE_SOURCE_DIR}/src/syntax ${CMAKE_SOURCE_DIR}/src/undo ${CMAKE_SOURCE_DIR}/src/utils ${CMAKE_SOURCE_DIR}/src/view ) add_definitions(-DTEST_DATA_DIR=\"${CMAKE_SOURCE_DIR}/autotests/input/\") add_definitions(-DJS_DATA_DIR=\"${CMAKE_SOURCE_DIR}/src/script/data/\") set (KTEXTEDITOR_TEST_LINK_LIBS KF5TextEditor KF5::I18n KF5::IconThemes KF5::GuiAddons KF5::SyntaxHighlighting Qt5::Qml ) include(ECMMarkAsTest) # test executable for encoding add_executable(kateencodingtest src/kateencodingtest.cpp) target_link_libraries(kateencodingtest ${KTEXTEDITOR_TEST_LINK_LIBS}) ecm_mark_as_test(kateencodingtest) # test macro for encoding tests MACRO(KTEXTEDITOR_ENCODING_TEST _encoding _testname) ADD_TEST (NAME encoding_${_testname}_create COMMAND kateencodingtest ${_encoding} ${CMAKE_SOURCE_DIR}/autotests/input/encoding/${_testname} ${CMAKE_CURRENT_BINARY_DIR}/${_testname} ) ADD_TEST (NAME encoding_${_testname}_diff COMMAND ${CMAKE_COMMAND} -E compare_files ${CMAKE_SOURCE_DIR}/autotests/input/encoding/${_testname} ${CMAKE_CURRENT_BINARY_DIR}/${_testname} ) ENDMACRO(KTEXTEDITOR_ENCODING_TEST) # add tests # this file is utf-8, simple KTEXTEDITOR_ENCODING_TEST ("utf-8" "utf8.txt") # this file is latin15, but fallback should work! KTEXTEDITOR_ENCODING_TEST ("utf-8" "latin15.txt") # this file is utf32, little endian, but fallback should work! KTEXTEDITOR_ENCODING_TEST ("utf-8" "utf32.txt") # this file is utf16, little endian, but fallback should work! KTEXTEDITOR_ENCODING_TEST ("utf-8" "utf16.txt") # this file is utf32, big endian, but fallback should work! KTEXTEDITOR_ENCODING_TEST ("utf-8" "utf32be.txt") # this file is utf16, big endian, but fallback should work! KTEXTEDITOR_ENCODING_TEST ("utf-8" "utf16be.txt") # cyrillic utf-8 KTEXTEDITOR_ENCODING_TEST ("utf-8" "cyrillic_utf8.txt") # cyrillic cp1251 KTEXTEDITOR_ENCODING_TEST ("utf-8" "cp1251.txt") # cyrillic koi8-r KTEXTEDITOR_ENCODING_TEST ("utf-8" "koi8-r.txt") # one character latin-15 test, segfaulted KTEXTEDITOR_ENCODING_TEST ("utf-8" "one-char-latin-15.txt") # test executable for indentation add_executable(kateindenttest src/indenttest.cpp src/script_test_base.cpp src/testutils.cpp) target_link_libraries(kateindenttest ${KTEXTEDITOR_TEST_LINK_LIBS} Qt5::Test) ecm_mark_as_test(kateindenttest) # test macro for indentation tests MACRO(KTEXTEDITOR_INDENT_TEST _testname) ADD_TEST (NAME kateindenttest_${_testname} COMMAND kateindenttest ${_testname}) ENDMACRO(KTEXTEDITOR_INDENT_TEST) # test different indenters sepearately to have smaller test chunks, that takes LONG KTEXTEDITOR_INDENT_TEST ("testPython") KTEXTEDITOR_INDENT_TEST ("testCstyle") # guard against execution with qt that has broken QJSEngine regex, see: https://bugreports.qt.io/browse/QTBUG-72879 if (NOT Qt5Widgets_VERSION VERSION_EQUAL 5.12.0) KTEXTEDITOR_INDENT_TEST ("testCppstyle") endif() KTEXTEDITOR_INDENT_TEST ("testCMake") KTEXTEDITOR_INDENT_TEST ("testRuby") KTEXTEDITOR_INDENT_TEST ("testHaskell") KTEXTEDITOR_INDENT_TEST ("testLatex") KTEXTEDITOR_INDENT_TEST ("testPascal") KTEXTEDITOR_INDENT_TEST ("testAda") KTEXTEDITOR_INDENT_TEST ("testXml") KTEXTEDITOR_INDENT_TEST ("testNormal") KTEXTEDITOR_INDENT_TEST ("testReplicode") macro(ktexteditor_unit_test testname) ecm_add_test(src/${testname}.cpp ${ARGN} TEST_NAME ${testname} LINK_LIBRARIES ${KTEXTEDITOR_TEST_LINK_LIBS} Qt5::Test) endmacro() ecm_add_tests( src/katetextbuffertest.cpp src/range_test.cpp src/undomanager_test.cpp src/plaintextsearch_test.cpp src/regexpsearch_test.cpp src/scriptdocument_test.cpp src/wordcompletiontest.cpp src/searchbar_test.cpp src/movingcursor_test.cpp src/configinterface_test.cpp src/messagetest.cpp src/kte_documentcursor.cpp src/bug313769.cpp src/katedocument_test.cpp src/movingrange_test.cpp src/kateview_test.cpp src/revision_test.cpp src/modificationsystem_test.cpp src/inlinenote_test.cpp + src/variable_test.cpp src/templatehandler_test.cpp src/katefoldingtest.cpp src/bug286887.cpp src/katewildcardmatcher_test.cpp LINK_LIBRARIES ${KTEXTEDITOR_TEST_LINK_LIBS} Qt5::Test ) ktexteditor_unit_test(completion_test src/codecompletiontestmodel.cpp src/codecompletiontestmodels.cpp) ktexteditor_unit_test(commands_test src/script_test_base.cpp src/testutils.cpp) ktexteditor_unit_test(scripting_test src/script_test_base.cpp src/testutils.cpp) ktexteditor_unit_test(bug313759 src/testutils.cpp) ktexteditor_unit_test(bug317111 src/testutils.cpp) ktexteditor_unit_test(bug205447 src/testutils.cpp) if (BUILD_VIMODE) add_subdirectory(src/vimode) endif() diff --git a/autotests/src/variable_test.cpp b/autotests/src/variable_test.cpp new file mode 100644 index 00000000..1092cfd6 --- /dev/null +++ b/autotests/src/variable_test.cpp @@ -0,0 +1,164 @@ +/* This file is part of the KDE project + * + * Copyright 2019 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 "variable_test.h" +#include "moc_variable_test.cpp" + +#include +#include +#include +#include + +#include + +using namespace KTextEditor; + +QTEST_MAIN(VariableTest) + +VariableTest::VariableTest() + : QObject() +{ + KTextEditor::EditorPrivate::enableUnitTestMode(); +} + +VariableTest::~VariableTest() +{ +} + +void VariableTest::testReturnValues() +{ + auto editor = KTextEditor::Editor::instance(); + + const QString name = QStringLiteral("Document:"); + auto func = [](const QStringView&, KTextEditor::View*) { return QString(); }; + + // exact matches + QVERIFY(!editor->unregisterVariableMatch(name)); + QVERIFY(editor->registerVariableMatch(name, "Document Text", func)); + QVERIFY(!editor->registerVariableMatch(name, "Document Text", func)); + QVERIFY(editor->unregisterVariableMatch(name)); + QVERIFY(!editor->unregisterVariableMatch(name)); + + // prefix matches + QVERIFY(!editor->unregisterVariablePrefix(name)); + QVERIFY(editor->registerVariablePrefix(name, "Document Text", func)); + QVERIFY(!editor->registerVariablePrefix(name, "Document Text", func)); + QVERIFY(editor->unregisterVariablePrefix(name)); + QVERIFY(!editor->unregisterVariablePrefix(name)); +} + +void VariableTest::testExactMatch_data() +{ + QTest::addColumn("text"); + QTest::addColumn("expected"); + QTest::addColumn("expectedText"); + + QTest::newRow("World") << "World" << "World" << "World"; + QTest::newRow("Smart World") << "Smart World" << "Smart World" << "Smart World"; +} + +void VariableTest::testExactMatch() +{ + QFETCH(QString, text); + QFETCH(QString, expected); + QFETCH(QString, expectedText); + + auto editor = KTextEditor::Editor::instance(); + auto doc = editor->createDocument(nullptr); + auto view = doc->createView(nullptr); + doc->setText(text); + + const QString name = QStringLiteral("Doc:Text"); + auto func = [](const QStringView&, KTextEditor::View* view) { + return view->document()->text(); + }; + + QVERIFY(editor->registerVariableMatch(name, "Document Text", func)); + + // expandVariable + QString output; + QVERIFY(editor->expandVariable(QStringLiteral("Doc:Text"), view, output)); + QCOMPARE(output, expected); + + // expandText + editor->expandText(QStringLiteral("Hello %{Doc:Text}!"), view, output); + QCOMPARE(output, QStringLiteral("Hello ") + expectedText + QLatin1Char('!')); + + editor->expandText(QStringLiteral("Hello %{Doc:Text} %{Doc:Text}!"), view, output); + QCOMPARE(output, QStringLiteral("Hello ") + expectedText + QLatin1Char(' ') + expectedText + QLatin1Char('!')); + + QVERIFY(editor->unregisterVariableMatch("Doc:Text")); + + delete doc; +} + +void VariableTest::testPrefixMatch() +{ + auto editor = KTextEditor::Editor::instance(); + + const QString prefix = QStringLiteral("Mirror:"); + auto func = [](const QStringView& text, KTextEditor::View*) { + QStringView rest = text.right(text.size() - 7); + QString out; + for (auto it = rest.rbegin(); it != rest.rend(); ++it) { + out += *it; + } + return out; + }; + + QVERIFY(editor->registerVariablePrefix(prefix, "Reverse text", func)); + + QString output; + QVERIFY(editor->expandVariable(QStringLiteral("Mirror:12345"), nullptr, output)); + QCOMPARE(output, QStringLiteral("54321")); + + editor->expandText(QStringLiteral("Countdown: %{Mirror:12345}"), nullptr, output); + QCOMPARE(output, QStringLiteral("Countdown: 54321")); + + // Test recursive expansion + editor->expandText(QStringLiteral("Countup: %{Mirror:%{Mirror:12345}}"), nullptr, output); + QCOMPARE(output, QStringLiteral("Countup: 12345")); + + QVERIFY(editor->unregisterVariablePrefix(prefix)); +} + +void VariableTest::testRecursiveMatch() +{ + auto editor = KTextEditor::Editor::instance(); + auto doc = editor->createDocument(nullptr); + auto view = doc->createView(nullptr); + doc->setText(QStringLiteral("Text")); + + const QString name = QStringLiteral("Doc:Text"); + auto func = [](const QStringView&, KTextEditor::View* view) { + return view->document()->text(); + }; + QVERIFY(editor->registerVariableMatch(name, "Document Text", func)); + + // Test recursive expansion + doc->setText(QStringLiteral("Text")); + QString output; + editor->expandText(QStringLiteral("Hello %{Doc:%{Doc:Text}}!"), view, output); + QCOMPARE(output, QStringLiteral("Hello Text!")); + + QVERIFY(editor->unregisterVariableMatch(name)); + delete doc; +} + +// kate: indent-mode cstyle; indent-width 4; replace-tabs on; diff --git a/autotests/src/variable_test.h b/autotests/src/variable_test.h new file mode 100644 index 00000000..1b6058c8 --- /dev/null +++ b/autotests/src/variable_test.h @@ -0,0 +1,41 @@ +/* This file is part of the KDE project + * + * Copyright 2019 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_VARIABLE_TEST_H +#define KTEXTEDITOR_VARIABLE_TEST_H + +#include + +class VariableTest : public QObject +{ + Q_OBJECT + +public: + VariableTest(); + ~VariableTest(); + +private Q_SLOTS: + void testReturnValues(); + void testExactMatch_data(); + void testExactMatch(); + void testPrefixMatch(); + void testRecursiveMatch(); +}; + +#endif // KTEXTEDITOR_VARIABLE_TEST_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index db6133dd..50109a4c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,377 +1,379 @@ # 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/katehighlightmenu.cpp syntax/katehighlightingcmds.cpp # view stuff (THE view and its helpers) view/kateview.cpp view/kateviewinternal.cpp view/kateviewhelpers.cpp view/kateannotationitemdelegate.cpp view/katemessagewidget.cpp view/katefadeeffect.cpp view/kateanimation.cpp view/katetextanimation.cpp view/katetextpreview.cpp view/katestatusbar.cpp view/wordcounter.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 +utils/katemacroexpander.cpp +utils/variable.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() ecm_qt_declare_logging_category(ktexteditor_LIB_SRCS HEADER katepartdebug.h IDENTIFIER LOG_KTE CATEGORY_NAME org.kde.ktexteditor DEFAULT_SEVERITY Warning) 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 KF5::AuthCore ${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::AuthCore ) 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/include/ktexteditor/editor.h b/src/include/ktexteditor/editor.h index 40fe31d9..da2cab51 100644 --- a/src/include/ktexteditor/editor.h +++ b/src/include/ktexteditor/editor.h @@ -1,258 +1,333 @@ /* This file is part of the KDE libraries * Copyright (C) 2005-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_EDITOR_H #define KTEXTEDITOR_EDITOR_H #include #include class KAboutData; class KConfig; /** * The KTextEditor namespace contains all the public API that is required * to use the KTextEditor component. Using the KTextEditor interfaces is * described in the article \ref index. * * @warning All classes that are \e not part of the KTextEditor namespace * are internal and subject to change. We mean it! */ namespace KTextEditor { class Application; class Command; class Document; +class View; class EditorPrivate; class ConfigPage; + /** * \brief Accessor interface for the KTextEditor framework. * * Topics: * - \ref editor_intro * - \ref editor_config * - \ref editor_commands * * \section editor_intro Introduction * * The Editor part can either be accessed through the static accessor Editor::instance() * or through the KParts component model (see \ref kte_design_part). * The Editor singleton provides general information and configuration methods * for the Editor, for example KAboutData by using aboutData(). * * The Editor has a list of all opened documents. Get this list with documents(). * To create a new Document call createDocument(). The signal documentCreated() * is emitted whenever the Editor created a new document. * * \section editor_config Editor Configuration * * The config dialog can be shown with configDialog(). * Instead of using the config dialog, the config pages can also be embedded * into the application's config dialog. To do this, configPages() returns the * number of config pages that exist and configPage() returns the requested * page. The configuration are saved automatically by the Editor. * * \note It is recommended to embed the config pages into the main application's * config dialog instead of using a separate config dialog, if the config * dialog does not look cluttered then. This way, all settings are grouped * together in one place. * * \section editor_commands Command Line Commands * * With Commands it is possible to add new commands to the command line. * These Command%s then are added to all document View%s. * Common use cases include commands like \e find or setting document variables. * The list of all registered commands can be obtained either through commandList() * or through commands(). Further, a specific command can be obtained through * queryCommand(). For further information, read the Command API documentation. * * \see KTextEditor::Document, KTextEditor::ConfigPage, KTextEditor::Command * \author Christoph Cullmann \ */ class KTEXTEDITOR_EXPORT Editor : public QObject { Q_OBJECT protected: /** * Constructor. * * Create the Editor object and pass it the internal * implementation to store a d-pointer. * * @param impl d-pointer to use */ Editor(EditorPrivate *impl); /** * Virtual destructor. */ virtual ~Editor(); public: /** * Accessor to get the Editor instance. * * @note This object will stay alive until QCoreApplication terminates. * You shall not delete it yourself. * There is only ONE Editor instance of this per process. * * \return Editor controller, after initial construction, will * live until QCoreApplication is terminating. */ static Editor *instance(); public: /** * Set the global application object. * This will allow the editor component to access * the hosting application. * @param application application object * if the argument is a nullptr, this will reset the application back to a dummy interface */ virtual void setApplication(KTextEditor::Application *application) = 0; /** * Current hosting application, if any set. * @return current application object or a dummy interface that allows you to call the functions * will never return a nullptr */ virtual KTextEditor::Application *application() const = 0; /* * Methods to create and manage the documents. */ public: /** * Create a new document object with \p parent. * * For each created document, the signal documentCreated() is emitted. * * \param parent parent object * \return new KTextEditor::Document object * \see documents(), documentCreated() */ virtual Document *createDocument(QObject *parent) = 0; /** * Get a list of all documents of this editor. * \return list of all existing documents * \see createDocument() */ virtual QList documents() = 0; Q_SIGNALS: /** * The \p editor emits this signal whenever a \p document was successfully * created. * \param editor pointer to the Editor singleton which created the new document * \param document the newly created document instance * \see createDocument() */ void documentCreated(KTextEditor::Editor *editor, KTextEditor::Document *document); /* * General Information about this editor. */ public: /** * Get the about data of this Editor part. * \return about data */ virtual const KAboutData &aboutData() const = 0; /** * Get the current default encoding for this Editor part. * \return default encoding */ QString defaultEncoding() const; /* * Configuration management. */ public: /** * Show the editor's config dialog, changes will be applied to the * editor and the configuration changes are saved. * * \note Instead of using the config dialog, the config pages can be * embedded into your own config dialog by using configPages() and * configPage(). * \param parent parent widget */ virtual void configDialog(QWidget *parent) = 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 * \see configPage() */ virtual int configPages() const = 0; /** * Get the config page with the \p number, config pages from 0 to * configPages()-1 are available if configPages() > 0. * Configuration changes done over this widget are automatically * saved. * \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 * \see configPages() */ virtual ConfigPage *configPage(int number, QWidget *parent) = 0; public: /** * Query for the command \p cmd. * If the command \p cmd does not exist the return value is NULL. * * \param cmd name of command to query for * \return the found command or NULL if no such command exists */ virtual Command *queryCommand(const QString &cmd) const = 0; /** * Get a list of all registered commands. * \return list of all commands * \see queryCommand(), commandList() */ virtual QList commands() const = 0; /** * Get a list of available command line strings. * \return command line strings * \see commands() */ virtual QStringList commandList() const = 0; +public: + /** + * Function that is called to expand a variable in @p text. + */ + using ExpandFunction = QString (*)(const QStringView& text, KTextEditor::View* view); + + /** + * Registers a variable called @p name for exact matches. + * For instance, a variable called "CurrentDocument:Path" could be + * registered which then expands to the path the current document. + * + * @return true on success, false if the variable could not be registered, + * e.g. because it already was registered previously. + * + * @since 5.57 + */ + bool registerVariableMatch(const QString& name, const QString& description, ExpandFunction expansionFunc); + + /** + * Registers a variable for arbitrary text that matches the specified + * prefix. For instance, a variable called "ENV:" could be registered + * which then expands arbitrary environment variables, e.g. ENV:HOME + * would expand to the user's home directory. + * + * @note A colon ':' is used as separator for the prefix and the text + * after the colon that should be evaluated. + * + * @return true on success, false if a prefix could not be registered, + * e.g. because it already was registered previously. + * + * @since 5.57 + */ + bool registerVariablePrefix(const QString& prefix, const QString& description, ExpandFunction expansionFunc); + + /** + * Unregisters a variable that was previously registered with + * registerVariableMatch(). + * + * @return true if the variable was successfully unregistered, and + * false if the variable did not exist. + * + * @since 5.57 + */ + bool unregisterVariableMatch(const QString& variable); + + /** + * Unregisters a prefix of variable that was previously registered with + * registerVariableMatch(). + * + * @return true if the variable was successfully unregistered, and + * false if the variable did not exist. + * + * @since 5.57 + */ + bool unregisterVariablePrefix(const QString& variable); + + /** + * Expands a single @p variable, writing the expanded value to @p output. + * + * @return true on success, otherwise false. + * + * @since 5.57 + */ + bool expandVariable(const QString& variable, KTextEditor::View* view, QString& output) const; + + /** + * Expands arbitrary @p text that may contain arbitrary many variables. + * On success, the expanded text is written to @p output. + * + * @since 5.57 + */ + void expandText(const QString& text, KTextEditor::View* view, QString& output) const; + private: /** * private d-pointer, pointing to the internal implementation */ EditorPrivate *const d; }; } #endif diff --git a/src/utils/kateglobal.h b/src/utils/kateglobal.h index 00d02917..40cafdd0 100644 --- a/src/utils/kateglobal.h +++ b/src/utils/kateglobal.h @@ -1,562 +1,573 @@ /* 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 "variable.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 */ explicit EditorPrivate(QPointer &staticInstance); public: /** * Destructor */ ~EditorPrivate(); /** * Create a new document object * @param parent parent object * @return created KTextEditor::Document */ KTextEditor::Document *createDocument(QObject *parent) override; /** * Returns a list of all documents of this editor. * @return list of all existing documents */ 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) 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 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 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) 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 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) 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(); /** * 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 override; /** * Get a list of all registered commands. * \return list of all commands */ QList commands() const override; /** * Get a list of available commandline strings. * \return commandline strings */ QStringList commandList() const override; /** * Copy text to clipboard an remember it in the history * @param text text to copy to clipboard, does nothing if empty! */ void copyToClipboard(const QString &text); /** * Clipboard history, filled with text we ever copied * to clipboard via copyToClipboard. */ const QStringList &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 *) 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 */ QStringList 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; + + /** + * Contains a lookup from the variable to the Variable instance. + */ + QHash m_variableExactMatches; + + /** + * Contains a lookup from the variable prefix to the Variable instance. + */ + QHash m_variablePrefixMatches; }; } #endif diff --git a/src/utils/katemacroexpander.cpp b/src/utils/katemacroexpander.cpp new file mode 100644 index 00000000..364cf780 --- /dev/null +++ b/src/utils/katemacroexpander.cpp @@ -0,0 +1,73 @@ +/* This file is part of the KDE project + * + * Copyright 2019 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 "katemacroexpander.h" + +#include + +/** + * Find closing bracket for @p str starting a position @p pos. + */ +static int findClosing(QStringView str, int pos = 0) +{ + const int len = str.size(); + int nesting = 0; + + while (pos < len) { + ++pos; + const QChar c = str[pos]; + if (c == QLatin1Char('}')) { + if (nesting == 0) { + return pos; + } + nesting--; + } else if (c == QLatin1Char('{')) { + nesting++; + } + } + return -1; +} + +QString KateMacroExpander::expandMacro(const QString& input, KTextEditor::View* view) +{ + QString output = input; + QString oldStr; + do { + oldStr = output; + const int startIndex = output.indexOf(QStringLiteral("%{")); + if (startIndex < 0) { + break; + } + + const int endIndex = findClosing(output, startIndex + 2); + if (endIndex <= startIndex) { + break; + } + + const int varLen = endIndex - (startIndex + 2); + QString variable = output.mid(startIndex + 2, varLen); + variable = expandMacro(variable, view); + if (KTextEditor::Editor::instance()->expandVariable(variable, view, variable)) { + output.replace(startIndex, endIndex - startIndex + 1, variable); + } + } while (output != oldStr); // str comparison guards against infinite loop + return output; +} + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/src/utils/katemacroexpander.h b/src/utils/katemacroexpander.h new file mode 100644 index 00000000..6b3d92f3 --- /dev/null +++ b/src/utils/katemacroexpander.h @@ -0,0 +1,44 @@ +/* This file is part of the KDE project + * + * Copyright 2019 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_MACRO_EXPANDER_H +#define KTEXTEDITOR_MACRO_EXPANDER_H + +#include + +namespace KTextEditor +{ + class View; +} + +/** + * Helper for macro expansion. + */ +namespace KateMacroExpander +{ + /** + * Expands the @p input text based on the @p view. + * @return the expanded text. + */ + QString expandMacro(const QString& input, KTextEditor::View* view); +} + +#endif // KTEXTEDITOR_MACRO_EXPANDER_H + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/src/utils/ktexteditor.cpp b/src/utils/ktexteditor.cpp index bd32c04c..24ef536a 100644 --- a/src/utils/ktexteditor.cpp +++ b/src/utils/ktexteditor.cpp @@ -1,405 +1,481 @@ /* 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 "inlinenoteinterface.h" #include "inlinenote.h" #include "inlinenotedata.h" #include "inlinenoteprovider.h" #include "markinterface.h" #include "modificationinterface.h" #include "sessionconfiginterface.h" #include "texthintinterface.h" +#include "variable.h" #include "annotationinterface.h" #include "abstractannotationitemdelegate.h" #include "kateglobal.h" #include "kateconfig.h" #include "katecmd.h" +#include "katemacroexpander.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 Editor::registerVariableMatch(const QString& name, const QString& description, ExpandFunction expansionFunc) +{ + if (name.isEmpty() || expansionFunc == nullptr) + return false; + + if (d->m_variableExactMatches.contains(name)) + return false; + + d->m_variableExactMatches.insert(name, Variable(name, description, expansionFunc)); + return true; +} + +bool Editor::registerVariablePrefix(const QString& prefix, const QString& description, ExpandFunction expansionFunc) +{ + if (prefix.isEmpty() || expansionFunc == nullptr) + return false; + + if (d->m_variablePrefixMatches.contains(prefix)) + return false; + + if (!prefix.contains(QLatin1Char(':'))) + return false; + + d->m_variablePrefixMatches.insert(prefix, Variable(prefix, description, expansionFunc)); + return true; +} + +bool Editor::unregisterVariableMatch(const QString& variable) +{ + auto it = d->m_variableExactMatches.find(variable); + if (it != d->m_variableExactMatches.end()) { + d->m_variableExactMatches.erase(it); + return true; + } + return false; +} + +bool Editor::unregisterVariablePrefix(const QString& variable) +{ + auto it = d->m_variablePrefixMatches.find(variable); + if (it != d->m_variablePrefixMatches.end()) { + d->m_variablePrefixMatches.erase(it); + return true; + } + return false; +} + +bool Editor::expandVariable(const QString& variable, KTextEditor::View* view, QString& output) const +{ + // first try exact matches + const auto it = d->m_variableExactMatches.find(variable); + if (it != d->m_variableExactMatches.end()) { + output = it->evaluate(variable, view); + return true; + } + + // try prefix matching + const int colonIndex = variable.indexOf(QLatin1Char(':')); + if (colonIndex >= 0) { + const QString prefix = variable.left(colonIndex + 1); + const auto itPrefix = d->m_variablePrefixMatches.find(prefix); + if (itPrefix != d->m_variablePrefixMatches.end()) { + output = itPrefix->evaluate(variable, view); + return true; + } + } + return false; +} + +void Editor::expandText(const QString& text, KTextEditor::View* view, QString& output) const +{ + output = KateMacroExpander::expandMacro(text, view); +} + 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); } void View::setViewInputMode(InputMode inputMode) { d->setInputMode(inputMode); } 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() {} MarkInterface::~MarkInterface() {} ModificationInterface::ModificationInterface() {} ModificationInterface::~ModificationInterface() {} SessionConfigInterface::SessionConfigInterface() {} SessionConfigInterface::~SessionConfigInterface() {} TextHintInterface::TextHintInterface() {} TextHintInterface::~TextHintInterface() {} TextHintProvider::TextHintProvider() {} TextHintProvider::~TextHintProvider() {} InlineNoteInterface::InlineNoteInterface() {} InlineNoteInterface::~InlineNoteInterface() {} InlineNoteProvider::InlineNoteProvider() {} InlineNoteProvider::~InlineNoteProvider() {} KateInlineNoteData::KateInlineNoteData(KTextEditor::InlineNoteProvider* provider, const KTextEditor::View* view, const KTextEditor::Cursor& position, int index, bool underMouse, const QFont& font, int lineHeight) : m_provider(provider) , m_view(view) , m_position(position) , m_index(index) , m_underMouse(underMouse) , m_font(font) , m_lineHeight(lineHeight) {} InlineNote::InlineNote(const KateInlineNoteData& data) : d(data) { } qreal InlineNote::width() const { return d.m_provider->inlineNoteSize(*this).width(); } bool KTextEditor::InlineNote::underMouse() const { return d.m_underMouse; } void KTextEditor::InlineNoteProvider::inlineNoteActivated(const InlineNote& note, Qt::MouseButtons buttons, const QPoint& globalPos) { Q_UNUSED(note); Q_UNUSED(buttons); Q_UNUSED(globalPos); } void KTextEditor::InlineNoteProvider::inlineNoteFocusInEvent(const KTextEditor::InlineNote& note, const QPoint& globalPos) { Q_UNUSED(note); Q_UNUSED(globalPos); } void KTextEditor::InlineNoteProvider::inlineNoteFocusOutEvent(const KTextEditor::InlineNote& note) { Q_UNUSED(note); } void KTextEditor::InlineNoteProvider::inlineNoteMouseMoveEvent(const KTextEditor::InlineNote& note, const QPoint& globalPos) { Q_UNUSED(note); Q_UNUSED(globalPos); } KTextEditor::InlineNoteProvider* InlineNote::provider() const { return d.m_provider; } const KTextEditor::View* InlineNote::view() const { return d.m_view; } QFont InlineNote::font() const { return d.m_font; } int InlineNote::index() const { return d.m_index; } int InlineNote::lineHeight() const { return d.m_lineHeight; } KTextEditor::Cursor InlineNote::position() const { return d.m_position; } 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(); } StyleOptionAnnotationItem::StyleOptionAnnotationItem() : contentFontMetrics(QFont()) {} StyleOptionAnnotationItem::StyleOptionAnnotationItem(const StyleOptionAnnotationItem &other) : QStyleOption(Version, Type) , contentFontMetrics(QFont()) { *this = other; } StyleOptionAnnotationItem::StyleOptionAnnotationItem(int version) : QStyleOption(version, Type) , contentFontMetrics(QFont()) {} AbstractAnnotationItemDelegate::AbstractAnnotationItemDelegate(QObject *parent) : QObject(parent) {} AbstractAnnotationItemDelegate::~AbstractAnnotationItemDelegate() = default; diff --git a/src/utils/variable.cpp b/src/utils/variable.cpp new file mode 100644 index 00000000..07690782 --- /dev/null +++ b/src/utils/variable.cpp @@ -0,0 +1,50 @@ +/* This file is part of the KDE project + Copyright (C) 2019 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 "variable.h" + +namespace KTextEditor +{ + +Variable::Variable(const QString& name, const QString& description, Variable::ExpandFunction func) + : m_name(name) + , m_description(description) + , m_function(func) +{} + +bool Variable::isValid() const +{ + return (!m_name.isEmpty()) && (m_function != nullptr); +} + +QString Variable::name() const +{ + return m_name; +} + +QString Variable::description() const +{ + return m_description; +} + +QString Variable::evaluate(const QStringView& prefix, KTextEditor::View * view) const +{ + return isValid() ? m_function(prefix, view) : QString(); +} + +} diff --git a/src/utils/variable.h b/src/utils/variable.h new file mode 100644 index 00000000..22d02348 --- /dev/null +++ b/src/utils/variable.h @@ -0,0 +1,107 @@ +/* This file is part of the KDE project + Copyright (C) 2019 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_VARIABLE_H +#define KTEXTEDITOR_VARIABLE_H + +#include +#include + +namespace KTextEditor +{ + class View; + +/** + * @brief Variable for variable expansion. + * + * @section variable_intro Introduction + * + * A Variable is used by the KTextEditor::Editor to expand variables, also + * know as expanding macros. A Variable itself is defined by the variable + * name() as well as a description() and a function that replaces the variable + * by its value. + * + * To register a Variable in the Editor use either Editor::registerVariableMatch() + * or Editor::registerPrefixMatch(). + * + * @see KTextEditor::Editor, KTextEditor::Editor::registerVariableMatch(), + * KTextEditor::Editor::registerPrefixMatch() + * @author Dominik Haumann \ + */ +class Variable +{ +public: + /** + * Function that is called to expand a variable in @p text. + * @param text + */ + using ExpandFunction = QString (*)(const QStringView& text, KTextEditor::View* view); + + /** + * Constructor defining a Variable by its @p name, its @p description, and + * its function @p expansionFunc to expand a variable to its corresponding + * value. + * + * @note The @p name should @e not be translated. + */ + Variable(const QString& name, const QString& description, ExpandFunction expansionFunc); + + /** + * Returns true, if the name is non-empty and the function provided in the + * constructor is not a nullptr. + */ + bool isValid() const; + + /** + * Returns the @p name that was provided in the constructor. + * Depending on where the Variable is registered, this name is used to + * identify an exact match or a prefix match. + */ + QString name() const; + + /** + * Returns the description that was provided in the constructor. + */ + QString description() const; + + /** + * Expands the Variable to its value. + * + * As example for an exact match, a variable "CurerntDocument:Cursor:Line" + * uses the @p view to return the current line of the text cursor. In this + * case @p prefix equals the text of the variable itself, i.e. + * "CurerntDocument:Cursor:Line". + * + * As example of a prefix match, a variable "ENV:value" expands the + * environment value @e value, e.g. "ENV:HOME". In this case, the @p prefix + * equals the text "ENV:HOME" and @p view would be unused. + * + * @return the expanded variable. + */ + QString evaluate(const QStringView& prefix, KTextEditor::View * view) const; + +private: + QString m_name; + QString m_description; + ExpandFunction m_function; +}; + +} + +#endif