diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b50de81b..9e4dc5ba 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,340 +1,341 @@ # handle data files, .desktop & .cmake add_subdirectory(data) # syntax highlighting data files add_subdirectory( syntax/data ) # jscripts for the part add_subdirectory( script/data ) # set right defines for libgit2 usage if(LIBGIT2_FOUND) add_definitions(-DLIBGIT2_FOUND) SET (CMAKE_REQUIRED_LIBRARIES LibGit2::LibGit2) set (KTEXTEDITOR_OPTIONAL_LIBS ${KTEXTEDITOR_OPTIONAL_LIBS} LibGit2::LibGit2) 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/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/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/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 # 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 # syntax hl files as resource file "${CMAKE_CURRENT_BINARY_DIR}/syntax_resource.cpp" ) # tool to create json index of highlightings, will validate all highlightings, too! add_executable(katehighlightingindexer syntax/data/katehighlightingindexer.cpp) target_link_libraries(katehighlightingindexer Qt5::XmlPatterns) # generate the hl index resource add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/syntax_resource.cpp" COMMAND katehighlightingindexer "${CMAKE_CURRENT_BINARY_DIR}/syntax/data/index.json" "${CMAKE_CURRENT_SOURCE_DIR}/syntax/data/language.xsd" "${CMAKE_CURRENT_BINARY_DIR}/syntax/data/index.qrc" COMMAND ${Qt5Core_RCC_EXECUTABLE} -o "${CMAKE_CURRENT_BINARY_DIR}/syntax_resource.cpp" "${CMAKE_CURRENT_BINARY_DIR}/syntax/data/index.qrc" DEPENDS GeneratePhpXmlFiles ) 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/modonhdwidget.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/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::Script Qt5::PrintSupport KF5::I18n KF5::Archive KF5::GuiAddons KF5::IconThemes KF5::ItemViews KF5::SonnetCore ${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 ) 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 part add_subdirectory(part) diff --git a/src/vimode/emulatedcommandbar/activemode.cpp b/src/vimode/emulatedcommandbar/activemode.cpp index 3f2a11ec..86f7d6ab 100644 --- a/src/vimode/emulatedcommandbar/activemode.cpp +++ b/src/vimode/emulatedcommandbar/activemode.cpp @@ -1,49 +1,49 @@ #include "activemode.h" #include "completer.h" #include "emulatedcommandbar.h" #include "matchhighlighter.h" using namespace KateVi; CompletionStartParams ActiveMode::completionInvoked(Completer::CompletionInvocation invocationType) { Q_UNUSED(invocationType); return CompletionStartParams(); -}; +} ActiveMode::~ActiveMode() { } void ActiveMode::hideAllWidgetsExcept(QWidget* widgetToKeepVisible) { m_emulatedCommandBar->hideAllWidgetsExcept(widgetToKeepVisible); } void ActiveMode::moveCursorTo(const KTextEditor::Cursor &cursorPos) { m_emulatedCommandBar->moveCursorTo(cursorPos); } void ActiveMode::updateMatchHighlight(const KTextEditor::Range& matchRange) { m_matchHighligher->updateMatchHighlight(matchRange); } void ActiveMode::close( bool wasAborted ) { m_emulatedCommandBar->m_wasAborted = wasAborted; m_emulatedCommandBar->hideMe(); } void ActiveMode::closeWithStatusMessage(const QString& exitStatusMessage) { m_emulatedCommandBar->closeWithStatusMessage(exitStatusMessage); } void ActiveMode::startCompletion ( const CompletionStartParams& completionStartParams ) { m_emulatedCommandBar->m_completer->startCompletion(completionStartParams); } diff --git a/src/vimode/emulatedcommandbar/emulatedcommandbar.cpp b/src/vimode/emulatedcommandbar/emulatedcommandbar.cpp index 30757968..00b0355e 100644 --- a/src/vimode/emulatedcommandbar/emulatedcommandbar.cpp +++ b/src/vimode/emulatedcommandbar/emulatedcommandbar.cpp @@ -1,1245 +1,1163 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 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 "katedocument.h" #include "kateglobal.h" #include "../commandrangeexpressionparser.h" #include "kateview.h" #include "../globalstate.h" #include #include #include #include "matchhighlighter.h" +#include "interactivesedreplacemode.h" #include #include #include #include "../history.h" #include "katecmds.h" #include "katescriptmanager.h" #include "../registers.h" #include "../searcher.h" #include #include #include #include #include #include #include #include using namespace KateVi; namespace { bool isCharEscaped(const QString &string, int charPos) { if (charPos == 0) { return false; } int numContiguousBackslashesToLeft = 0; charPos--; while (charPos >= 0 && string[charPos] == QLatin1Char('\\')) { numContiguousBackslashesToLeft++; charPos--; } return ((numContiguousBackslashesToLeft % 2) == 1); } QString toggledEscaped(const QString &originalString, QChar escapeChar) { int searchFrom = 0; QString toggledEscapedString = originalString; do { const int indexOfEscapeChar = toggledEscapedString.indexOf(escapeChar, searchFrom); if (indexOfEscapeChar == -1) { break; } if (!isCharEscaped(toggledEscapedString, indexOfEscapeChar)) { // Escape. toggledEscapedString.replace(indexOfEscapeChar, 1, QLatin1String("\\") + escapeChar); searchFrom = indexOfEscapeChar + 2; } else { // Unescape. toggledEscapedString.remove(indexOfEscapeChar - 1, 1); searchFrom = indexOfEscapeChar; } } while (true); return toggledEscapedString; } QString ensuredCharEscaped(const QString &originalString, QChar charToEscape) { QString escapedString = originalString; for (int i = 0; i < escapedString.length(); i++) { if (escapedString[i] == charToEscape && !isCharEscaped(escapedString, i)) { escapedString.replace(i, 1, QLatin1String("\\") + charToEscape); } } return escapedString; } QString vimRegexToQtRegexPattern(const QString &vimRegexPattern) { QString qtRegexPattern = vimRegexPattern; qtRegexPattern = toggledEscaped(qtRegexPattern, QLatin1Char('(')); qtRegexPattern = toggledEscaped(qtRegexPattern, QLatin1Char(')')); qtRegexPattern = toggledEscaped(qtRegexPattern, QLatin1Char('+')); qtRegexPattern = toggledEscaped(qtRegexPattern, QLatin1Char('|')); qtRegexPattern = ensuredCharEscaped(qtRegexPattern, QLatin1Char('?')); { // All curly brackets, except the closing curly bracket of a matching pair where the opening bracket is escaped, // must have their escaping toggled. bool lookingForMatchingCloseBracket = false; QList matchingClosedCurlyBracketPositions; for (int i = 0; i < qtRegexPattern.length(); i++) { if (qtRegexPattern[i] == QLatin1Char('{') && isCharEscaped(qtRegexPattern, i)) { lookingForMatchingCloseBracket = true; } if (qtRegexPattern[i] == QLatin1Char('}') && lookingForMatchingCloseBracket && qtRegexPattern[i - 1] != QLatin1Char('\\')) { matchingClosedCurlyBracketPositions.append(i); } } if (matchingClosedCurlyBracketPositions.isEmpty()) { // Escape all {'s and }'s - there are no matching pairs. qtRegexPattern = toggledEscaped(qtRegexPattern, QLatin1Char('{')); qtRegexPattern = toggledEscaped(qtRegexPattern, QLatin1Char('}')); } else { // Ensure that every chunk of qtRegexPattern that does *not* contain a curly closing bracket // that is matched have their { and } escaping toggled. QString qtRegexPatternNonMatchingCurliesToggled; int previousNonMatchingClosedCurlyPos = 0; // i.e. the position of the last character which is either // not a curly closing bracket, or is a curly closing bracket // that is not matched. foreach (int matchingClosedCurlyPos, matchingClosedCurlyBracketPositions) { QString chunkExcludingMatchingCurlyClosed = qtRegexPattern.mid(previousNonMatchingClosedCurlyPos, matchingClosedCurlyPos - previousNonMatchingClosedCurlyPos); chunkExcludingMatchingCurlyClosed = toggledEscaped(chunkExcludingMatchingCurlyClosed, QLatin1Char('{')); chunkExcludingMatchingCurlyClosed = toggledEscaped(chunkExcludingMatchingCurlyClosed, QLatin1Char('}')); qtRegexPatternNonMatchingCurliesToggled += chunkExcludingMatchingCurlyClosed + qtRegexPattern[matchingClosedCurlyPos]; previousNonMatchingClosedCurlyPos = matchingClosedCurlyPos + 1; } QString chunkAfterLastMatchingClosedCurly = qtRegexPattern.mid(matchingClosedCurlyBracketPositions.last() + 1); chunkAfterLastMatchingClosedCurly = toggledEscaped(chunkAfterLastMatchingClosedCurly, QLatin1Char('{')); chunkAfterLastMatchingClosedCurly = toggledEscaped(chunkAfterLastMatchingClosedCurly, QLatin1Char('}')); qtRegexPatternNonMatchingCurliesToggled += chunkAfterLastMatchingClosedCurly; qtRegexPattern = qtRegexPatternNonMatchingCurliesToggled; } } // All square brackets, *except* for those that are a) unescaped; and b) form a matching pair, must be // escaped. bool lookingForMatchingCloseBracket = false; int openingBracketPos = -1; QList matchingSquareBracketPositions; for (int i = 0; i < qtRegexPattern.length(); i++) { if (qtRegexPattern[i] == QLatin1Char('[') && !isCharEscaped(qtRegexPattern, i) && !lookingForMatchingCloseBracket) { lookingForMatchingCloseBracket = true; openingBracketPos = i; } if (qtRegexPattern[i] == QLatin1Char(']') && lookingForMatchingCloseBracket && !isCharEscaped(qtRegexPattern, i)) { lookingForMatchingCloseBracket = false; matchingSquareBracketPositions.append(openingBracketPos); matchingSquareBracketPositions.append(i); } } if (matchingSquareBracketPositions.isEmpty()) { // Escape all ['s and ]'s - there are no matching pairs. qtRegexPattern = ensuredCharEscaped(qtRegexPattern, QLatin1Char('[')); qtRegexPattern = ensuredCharEscaped(qtRegexPattern, QLatin1Char(']')); } else { // Ensure that every chunk of qtRegexPattern that does *not* contain one of the matching pairs of // square brackets have their square brackets escaped. QString qtRegexPatternNonMatchingSquaresMadeLiteral; int previousNonMatchingSquareBracketPos = 0; // i.e. the position of the last character which is // either not a square bracket, or is a square bracket but // which is not matched. foreach (int matchingSquareBracketPos, matchingSquareBracketPositions) { QString chunkExcludingMatchingSquareBrackets = qtRegexPattern.mid(previousNonMatchingSquareBracketPos, matchingSquareBracketPos - previousNonMatchingSquareBracketPos); chunkExcludingMatchingSquareBrackets = ensuredCharEscaped(chunkExcludingMatchingSquareBrackets, QLatin1Char('[')); chunkExcludingMatchingSquareBrackets = ensuredCharEscaped(chunkExcludingMatchingSquareBrackets, QLatin1Char(']')); qtRegexPatternNonMatchingSquaresMadeLiteral += chunkExcludingMatchingSquareBrackets + qtRegexPattern[matchingSquareBracketPos]; previousNonMatchingSquareBracketPos = matchingSquareBracketPos + 1; } QString chunkAfterLastMatchingSquareBracket = qtRegexPattern.mid(matchingSquareBracketPositions.last() + 1); chunkAfterLastMatchingSquareBracket = ensuredCharEscaped(chunkAfterLastMatchingSquareBracket, QLatin1Char('[')); chunkAfterLastMatchingSquareBracket = ensuredCharEscaped(chunkAfterLastMatchingSquareBracket, QLatin1Char(']')); qtRegexPatternNonMatchingSquaresMadeLiteral += chunkAfterLastMatchingSquareBracket; qtRegexPattern = qtRegexPatternNonMatchingSquaresMadeLiteral; } qtRegexPattern = qtRegexPattern.replace(QLatin1String("\\>"), QLatin1String("\\b")); qtRegexPattern = qtRegexPattern.replace(QLatin1String("\\<"), QLatin1String("\\b")); return qtRegexPattern; } /** * @return \a originalRegex but escaped in such a way that a Qt regex search for * the resulting string will match the string \a originalRegex. */ QString escapedForSearchingAsLiteral(const QString &originalQtRegex) { QString escapedForSearchingAsLiteral = originalQtRegex; escapedForSearchingAsLiteral.replace(QLatin1Char('\\'), QLatin1String("\\\\")); escapedForSearchingAsLiteral.replace(QLatin1Char('$'), QLatin1String("\\$")); escapedForSearchingAsLiteral.replace(QLatin1Char('^'), QLatin1String("\\^")); escapedForSearchingAsLiteral.replace(QLatin1Char('.'), QLatin1String("\\.")); escapedForSearchingAsLiteral.replace(QLatin1Char('*'), QLatin1String("\\*")); escapedForSearchingAsLiteral.replace(QLatin1Char('/'), QLatin1String("\\/")); escapedForSearchingAsLiteral.replace(QLatin1Char('['), QLatin1String("\\[")); escapedForSearchingAsLiteral.replace(QLatin1Char(']'), QLatin1String("\\]")); escapedForSearchingAsLiteral.replace(QLatin1Char('\n'), QLatin1String("\\n")); return escapedForSearchingAsLiteral; } QStringList reversed(const QStringList &originalList) { QStringList reversedList = originalList; std::reverse(reversedList.begin(), reversedList.end()); return reversedList; } QString withCaseSensitivityMarkersStripped(const QString &originalSearchTerm) { // Only \C is handled, for now - I'll implement \c if someone asks for it. int pos = 0; QString caseSensitivityMarkersStripped = originalSearchTerm; while (pos < caseSensitivityMarkersStripped.length()) { if (caseSensitivityMarkersStripped.at(pos) == QLatin1Char('C') && isCharEscaped(caseSensitivityMarkersStripped, pos)) { caseSensitivityMarkersStripped.replace(pos - 1, 2, QString()); pos--; } pos++; } return caseSensitivityMarkersStripped; } int findPosOfSearchConfigMarker(const QString &searchText, const bool isSearchBackwards) { const QChar searchConfigMarkerChar = (isSearchBackwards ? QLatin1Char('?') : QLatin1Char('/')); for (int pos = 0; pos < searchText.length(); pos++) { if (searchText.at(pos) == searchConfigMarkerChar) { if (!isCharEscaped(searchText, pos)) { return pos; } } } return -1; } bool isRepeatLastSearch(const QString &searchText, const bool isSearchBackwards) { const int posOfSearchConfigMarker = findPosOfSearchConfigMarker(searchText, isSearchBackwards); if (posOfSearchConfigMarker != -1) { if (searchText.leftRef(posOfSearchConfigMarker).isEmpty()) { return true; } } return false; } bool shouldPlaceCursorAtEndOfMatch(const QString &searchText, const bool isSearchBackwards) { const int posOfSearchConfigMarker = findPosOfSearchConfigMarker(searchText, isSearchBackwards); if (posOfSearchConfigMarker != -1) { if (searchText.length() > posOfSearchConfigMarker + 1 && searchText.at(posOfSearchConfigMarker + 1) == QLatin1Char('e')) { return true; } } return false; } QString withSearchConfigRemoved(const QString &originalSearchText, const bool isSearchBackwards) { const int posOfSearchConfigMarker = findPosOfSearchConfigMarker(originalSearchText, isSearchBackwards); if (posOfSearchConfigMarker == -1) { return originalSearchText; } else { return originalSearchText.left(posOfSearchConfigMarker); } } } EmulatedCommandBar::EmulatedCommandBar(InputModeManager *viInputModeManager, QWidget *parent) : KateViewBarWidget(false, parent) , m_viInputModeManager(viInputModeManager) , m_view(viInputModeManager->view()){ QHBoxLayout *layout = new QHBoxLayout(); layout->setMargin(0); centralWidget()->setLayout(layout); m_barTypeIndicator = new QLabel(this); m_barTypeIndicator->setObjectName(QStringLiteral("bartypeindicator")); layout->addWidget(m_barTypeIndicator); m_edit = new QLineEdit(this); m_edit->setObjectName(QStringLiteral("commandtext")); layout->addWidget(m_edit); m_exitStatusMessageDisplay = new QLabel(this); m_exitStatusMessageDisplay->setObjectName(QStringLiteral("commandresponsemessage")); m_exitStatusMessageDisplay->setAlignment(Qt::AlignLeft); layout->addWidget(m_exitStatusMessageDisplay); m_waitingForRegisterIndicator = new QLabel(this); m_waitingForRegisterIndicator->setObjectName(QStringLiteral("waitingforregisterindicator")); m_waitingForRegisterIndicator->setVisible(false); m_waitingForRegisterIndicator->setText(QStringLiteral("\"")); layout->addWidget(m_waitingForRegisterIndicator); m_matchHighligher.reset(new MatchHighlighter(m_view)); m_interactiveSedReplaceMode.reset(new InteractiveSedReplaceMode(this, m_matchHighligher.data())); layout->addWidget(m_interactiveSedReplaceMode->label()); m_completer.reset(new Completer(this, m_view, m_edit)); m_searchMode.reset(new SearchMode(this, m_matchHighligher.data(), m_view, m_edit)); m_searchMode->setViInputModeManager(viInputModeManager); m_commandMode.reset(new CommandMode(this, m_matchHighligher.data(), m_view, m_edit, m_interactiveSedReplaceMode.data(), m_completer.data())); m_edit->installEventFilter(this); connect(m_edit, SIGNAL(textChanged(QString)), this, SLOT(editTextChanged(QString))); m_exitStatusMessageDisplayHideTimer = new QTimer(this); m_exitStatusMessageDisplayHideTimer->setSingleShot(true); connect(m_exitStatusMessageDisplayHideTimer, SIGNAL(timeout()), this, SIGNAL(hideMe())); // 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 as a result of m_commandResponseMessageDisplayHide // timing out. connect(m_view, SIGNAL(focusOut(KTextEditor::View*)), m_exitStatusMessageDisplayHideTimer, SLOT(stop())); // We can restart the timer once the view has focus again, though. connect(m_view, SIGNAL(focusIn(KTextEditor::View*)), this, SLOT(startHideExitStatusMessageTimer())); } EmulatedCommandBar::~EmulatedCommandBar() { } void EmulatedCommandBar::init(EmulatedCommandBar::Mode mode, const QString &initialText) { m_mode = mode; m_isActive = true; m_wasAborted = true; showBarTypeIndicator(mode); if (mode == KateVi::EmulatedCommandBar::SearchBackward || mode == SearchForward) { switchToMode(m_searchMode.data()); m_searchMode->init(mode == SearchBackward ? SearchMode::SearchDirection::Backward : SearchMode::SearchDirection::Forward); } else { switchToMode(m_commandMode.data()); } m_edit->setFocus(); m_edit->setText(initialText); m_edit->show(); m_exitStatusMessageDisplay->hide(); m_exitStatusMessageDisplayHideTimer->stop(); // A change in focus will have occurred: make sure we process it now, instead of having it // occur later and stop() m_commandResponseMessageDisplayHide. // This is generally only a problem when feeding a sequence of keys without human intervention, // as when we execute a mapping, macro, or test case. while (QApplication::hasPendingEvents()) { QApplication::processEvents(); } } bool EmulatedCommandBar::isActive() { return m_isActive; } void EmulatedCommandBar::setCommandResponseMessageTimeout(long int commandResponseMessageTimeOutMS) { m_exitStatusMessageHideTimeOutMS = commandResponseMessageTimeOutMS; } void EmulatedCommandBar::closed() { m_matchHighligher->updateMatchHighlight(KTextEditor::Range::invalid()); m_completer->deactivateCompletion(); m_isActive = false; m_currentMode->deactivate(m_wasAborted); m_currentMode = nullptr; } void EmulatedCommandBar::switchToMode ( ActiveMode* newMode ) { if (m_currentMode) m_currentMode->deactivate(false); m_currentMode = newMode; m_completer->setCurrentMode(newMode); } bool EmulatedCommandBar::barHandledKeypress ( const QKeyEvent* keyEvent ) { if ((keyEvent->modifiers() == Qt::ControlModifier && keyEvent->key() == Qt::Key_H) || keyEvent->key() == Qt::Key_Backspace) { if (m_edit->text().isEmpty()) { emit hideMe(); } m_edit->backspace(); return true; } if (keyEvent->modifiers() != Qt::ControlModifier) return false; if (keyEvent->key() == Qt::Key_B) { m_edit->setCursorPosition(0); return true; } else if (keyEvent->key() == Qt::Key_E) { m_edit->setCursorPosition(m_edit->text().length()); return true; } else if (keyEvent->key() == Qt::Key_W) { deleteSpacesToLeftOfCursor(); if (!deleteNonWordCharsToLeftOfCursor()) { deleteWordCharsToLeftOfCursor(); } return true; } else if (keyEvent->key() == Qt::Key_R || keyEvent->key() == Qt::Key_G) { m_waitingForRegister = true; m_waitingForRegisterIndicator->setVisible(true); if (keyEvent->key() == Qt::Key_G) { m_insertedTextShouldBeEscapedForSearchingAsLiteral = true; } return true; } return false; } void EmulatedCommandBar::insertRegisterContents(const QKeyEvent *keyEvent) { if (keyEvent->key() != Qt::Key_Shift && keyEvent->key() != Qt::Key_Control) { const QChar key = KeyParser::self()->KeyEventToQChar(*keyEvent).toLower(); const int oldCursorPosition = m_edit->cursorPosition(); QString textToInsert; if (keyEvent->modifiers() == Qt::ControlModifier && keyEvent->key() == Qt::Key_W) { textToInsert = m_view->doc()->wordAt(m_view->cursorPosition()); } else { textToInsert = m_viInputModeManager->globalState()->registers()->getContent(key); } if (m_insertedTextShouldBeEscapedForSearchingAsLiteral) { textToInsert = escapedForSearchingAsLiteral(textToInsert); m_insertedTextShouldBeEscapedForSearchingAsLiteral = false; } m_edit->setText(m_edit->text().insert(m_edit->cursorPosition(), textToInsert)); m_edit->setCursorPosition(oldCursorPosition + textToInsert.length()); m_waitingForRegister = false; m_waitingForRegisterIndicator->setVisible(false); } } bool EmulatedCommandBar::eventFilter(QObject *object, QEvent *event) { Q_ASSERT(object == m_edit || object == m_completer->m_completer->popup()); if (m_suspendEditEventFiltering) { return false; } Q_UNUSED(object); if (event->type() == QEvent::KeyPress) { // Re-route this keypress through Vim's central keypress handling area, so that we can use the keypress in e.g. // mappings and macros. return m_viInputModeManager->handleKeypress(static_cast(event)); } return false; } void EmulatedCommandBar::deleteSpacesToLeftOfCursor() { while (m_edit->cursorPosition() != 0 && m_edit->text().at(m_edit->cursorPosition() - 1) == QLatin1Char(' ')) { m_edit->backspace(); } } void EmulatedCommandBar::deleteWordCharsToLeftOfCursor() { while (m_edit->cursorPosition() != 0) { const QChar charToTheLeftOfCursor = m_edit->text().at(m_edit->cursorPosition() - 1); if (!charToTheLeftOfCursor.isLetterOrNumber() && charToTheLeftOfCursor != QLatin1Char('_')) { break; } m_edit->backspace(); } } bool EmulatedCommandBar::deleteNonWordCharsToLeftOfCursor() { bool deletionsMade = false; while (m_edit->cursorPosition() != 0) { const QChar charToTheLeftOfCursor = m_edit->text().at(m_edit->cursorPosition() - 1); if (charToTheLeftOfCursor.isLetterOrNumber() || charToTheLeftOfCursor == QLatin1Char('_') || charToTheLeftOfCursor == QLatin1Char(' ')) { break; } m_edit->backspace(); deletionsMade = true; } return deletionsMade; } bool EmulatedCommandBar::handleKeyPress(const QKeyEvent *keyEvent) { if (m_waitingForRegister) { insertRegisterContents(keyEvent); return true; } const bool completerHandled = m_completer->completerHandledKeypress(keyEvent); if (completerHandled) return true; if (keyEvent->modifiers() == Qt::ControlModifier && (keyEvent->key() == Qt::Key_C || keyEvent->key() == Qt::Key_BracketLeft)) { emit hideMe(); return true; } // Is this a built-in Emulated Command Bar keypress e.g. insert from register, ctrl-h, etc? const bool barHandled = barHandledKeypress(keyEvent); if (barHandled) return true; // Can the current mode handle it? const bool currentModeHandled = m_currentMode->handleKeyPress(keyEvent); if (currentModeHandled) return true; // Couldn't handle this key event. // Send the keypress back to the QLineEdit. Ideally, instead of doing this, we would simply return "false" // and let Qt re-dispatch the event itself; however, there is a corner case in that if the selection // changes (as a result of e.g. incremental searches during Visual Mode), and the keypress that causes it // is not dispatched from within KateViInputModeHandler::handleKeypress(...) // (so KateViInputModeManager::isHandlingKeypress() returns false), we lose information about whether we are // in Visual Mode, Visual Line Mode, etc. See VisualViMode::updateSelection( ). if (m_edit->isVisible()) { m_suspendEditEventFiltering = true; QKeyEvent keyEventCopy(keyEvent->type(), keyEvent->key(), keyEvent->modifiers(), keyEvent->text(), keyEvent->isAutoRepeat(), keyEvent->count()); qApp->notify(m_edit, &keyEventCopy); m_suspendEditEventFiltering = false; } return true; } bool EmulatedCommandBar::isSendingSyntheticSearchCompletedKeypress() { return m_searchMode->isSendingSyntheticSearchCompletedKeypress(); } void EmulatedCommandBar::startInteractiveSearchAndReplace(QSharedPointer interactiveSedReplace) { Q_ASSERT_X(interactiveSedReplace->currentMatch().isValid(), "startInteractiveSearchAndReplace", "KateCommands shouldn't initiate an interactive sed replace with no initial match"); switchToMode(m_interactiveSedReplaceMode.data()); m_interactiveSedReplaceMode->activate(interactiveSedReplace); } void EmulatedCommandBar::showBarTypeIndicator(EmulatedCommandBar::Mode mode) { QChar barTypeIndicator = QChar::Null; switch (mode) { case SearchForward: barTypeIndicator = QLatin1Char('/'); break; case SearchBackward: barTypeIndicator = QLatin1Char('?'); break; case Command: barTypeIndicator = QLatin1Char(':'); break; default: Q_ASSERT(false && "Unknown mode!"); } m_barTypeIndicator->setText(barTypeIndicator); m_barTypeIndicator->show(); } QString EmulatedCommandBar::executeCommand(const QString &commandToExecute) { return m_commandMode->executeCommand(commandToExecute); } void EmulatedCommandBar::closeWithStatusMessage(const QString &exitStatusMessage) { // Display the message for a while. Become inactive, so we don't steal keys in the meantime. m_isActive = false; m_exitStatusMessageDisplay->show(); m_exitStatusMessageDisplay->setText(exitStatusMessage); hideAllWidgetsExcept(m_exitStatusMessageDisplay); m_exitStatusMessageDisplayHideTimer->start(m_exitStatusMessageHideTimeOutMS); } void EmulatedCommandBar::moveCursorTo(const KTextEditor::Cursor &cursorPos) { m_view->setCursorPosition(cursorPos); if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualMode || m_viInputModeManager->getCurrentViMode() == ViMode::VisualLineMode) { m_viInputModeManager->getViVisualMode()->goToPos(cursorPos); } } void EmulatedCommandBar::editTextChanged(const QString &newText) { Q_ASSERT(!m_interactiveSedReplaceMode->isActive()); m_currentMode->editTextChanged(newText); m_completer->editTextChanged(newText); } void EmulatedCommandBar::startHideExitStatusMessageTimer() { if (m_exitStatusMessageDisplay->isVisible() && !m_exitStatusMessageDisplayHideTimer->isActive()) { m_exitStatusMessageDisplayHideTimer->start(m_exitStatusMessageHideTimeOutMS); } } void EmulatedCommandBar::setViInputModeManager(InputModeManager *viInputModeManager) { m_viInputModeManager = viInputModeManager; m_searchMode->setViInputModeManager(viInputModeManager); m_commandMode->setViInputModeManager(viInputModeManager); } void EmulatedCommandBar::hideAllWidgetsExcept(QWidget* widgetToKeepVisible) { QList widgets = centralWidget()->findChildren(); foreach(QWidget* widget, widgets) { if (widget != widgetToKeepVisible) widget->hide(); } } -EmulatedCommandBar::InteractiveSedReplaceMode::InteractiveSedReplaceMode(EmulatedCommandBar* emulatedCommandBar, MatchHighlighter* matchHighlighter) - : ActiveMode(emulatedCommandBar, matchHighlighter), - m_isActive(false) -{ - m_interactiveSedReplaceLabel = new QLabel(); - m_interactiveSedReplaceLabel->setObjectName(QStringLiteral("interactivesedreplace")); -} - -void EmulatedCommandBar::InteractiveSedReplaceMode::activate(QSharedPointer interactiveSedReplace) -{ - Q_ASSERT_X(interactiveSedReplace->currentMatch().isValid(), "startInteractiveSearchAndReplace", "KateCommands shouldn't initiate an interactive sed replace with no initial match"); - - m_isActive = true; - m_interactiveSedReplacer = interactiveSedReplace; - - hideAllWidgetsExcept(m_interactiveSedReplaceLabel); - m_interactiveSedReplaceLabel->show(); - updateInteractiveSedReplaceLabelText(); - - updateMatchHighlight(interactiveSedReplace->currentMatch()); - moveCursorTo(interactiveSedReplace->currentMatch().start()); -} - -bool EmulatedCommandBar::InteractiveSedReplaceMode::handleKeyPress(const QKeyEvent* keyEvent) -{ - // TODO - it would be better to use e.g. keyEvent->key() == Qt::Key_Y instead of keyEvent->text() == "y", - // but this would require some slightly dicey changes to the "feed key press" code in order to make it work - // with mappings and macros. - if (keyEvent->text() == QLatin1String("y") || keyEvent->text() == QLatin1String("n")) { - const KTextEditor::Cursor cursorPosIfFinalMatch = m_interactiveSedReplacer->currentMatch().start(); - if (keyEvent->text() == QLatin1String("y")) { - m_interactiveSedReplacer->replaceCurrentMatch(); - } else { - m_interactiveSedReplacer->skipCurrentMatch(); - } - updateMatchHighlight(m_interactiveSedReplacer->currentMatch()); - updateInteractiveSedReplaceLabelText(); - moveCursorTo(m_interactiveSedReplacer->currentMatch().start()); - - if (!m_interactiveSedReplacer->currentMatch().isValid()) { - moveCursorTo(cursorPosIfFinalMatch); - finishInteractiveSedReplace(); - } - return true; - } else if (keyEvent->text() == QLatin1String("l")) { - m_interactiveSedReplacer->replaceCurrentMatch(); - finishInteractiveSedReplace(); - return true; - } else if (keyEvent->text() == QLatin1String("q")) { - finishInteractiveSedReplace(); - return true; - } else if (keyEvent->text() == QLatin1String("a")) { - m_interactiveSedReplacer->replaceAllRemaining(); - finishInteractiveSedReplace(); - return true; - } - return false; -} - -void EmulatedCommandBar::InteractiveSedReplaceMode::deactivate( bool wasAborted ) -{ - Q_UNUSED(wasAborted); - m_isActive = false; - m_interactiveSedReplaceLabel->hide(); -} - -QWidget* EmulatedCommandBar::InteractiveSedReplaceMode::label() -{ - return m_interactiveSedReplaceLabel; -} - -void EmulatedCommandBar::InteractiveSedReplaceMode::updateInteractiveSedReplaceLabelText() -{ - m_interactiveSedReplaceLabel->setText(m_interactiveSedReplacer->currentMatchReplacementConfirmationMessage() + QLatin1String(" (y/n/a/q/l)")); -} - -void EmulatedCommandBar::InteractiveSedReplaceMode::finishInteractiveSedReplace() -{ - deactivate(false); - closeWithStatusMessage(m_interactiveSedReplacer->finalStatusReportMessage()); - m_interactiveSedReplacer.clear(); -} - EmulatedCommandBar::SearchMode::SearchMode(EmulatedCommandBar* emulatedCommandBar, MatchHighlighter* matchHighlighter, KTextEditor::ViewPrivate* view, QLineEdit* edit) : ActiveMode ( emulatedCommandBar, matchHighlighter), m_emulatedCommandBar(emulatedCommandBar), m_view(view), m_edit(edit) { } void EmulatedCommandBar::SearchMode::init ( EmulatedCommandBar::SearchMode::SearchDirection searchDirection) { m_searchDirection = searchDirection; m_startingCursorPos = m_view->cursorPosition(); } void EmulatedCommandBar::SearchMode::setViInputModeManager ( InputModeManager* viInputModeManager ) { m_viInputModeManager = viInputModeManager; } bool EmulatedCommandBar::SearchMode::handleKeyPress ( const QKeyEvent* keyEvent ) { Q_UNUSED(keyEvent); return false; } void EmulatedCommandBar::SearchMode::editTextChanged ( const QString& newText ) { QString qtRegexPattern = newText; const bool searchBackwards = (m_searchDirection == SearchDirection::Backward); const bool placeCursorAtEndOfMatch = shouldPlaceCursorAtEndOfMatch(qtRegexPattern, searchBackwards); if (isRepeatLastSearch(qtRegexPattern, searchBackwards)) { qtRegexPattern = m_viInputModeManager->searcher()->getLastSearchPattern(); } else { qtRegexPattern = withSearchConfigRemoved(qtRegexPattern, searchBackwards); qtRegexPattern = vimRegexToQtRegexPattern(qtRegexPattern); } // Decide case-sensitivity via SmartCase (note: if the expression contains \C, the "case-sensitive" marker, then // we will be case-sensitive "by coincidence", as it were.). bool caseSensitive = true; if (qtRegexPattern.toLower() == qtRegexPattern) { caseSensitive = false; } qtRegexPattern = withCaseSensitivityMarkersStripped(qtRegexPattern); m_currentSearchParams.pattern = qtRegexPattern; m_currentSearchParams.isCaseSensitive = caseSensitive; m_currentSearchParams.isBackwards = searchBackwards; m_currentSearchParams.shouldPlaceCursorAtEndOfMatch = placeCursorAtEndOfMatch; // The "count" for the current search is not shared between Visual & Normal mode, so we need to pick // the right one to handle the counted search. int c = m_viInputModeManager->getCurrentViModeHandler()->getCount(); KTextEditor::Range match = m_viInputModeManager->searcher()->findPattern(m_currentSearchParams, m_startingCursorPos, c, false /* Don't add incremental searches to search history */); if (match.isValid()) { // The returned range ends one past the last character of the match, so adjust. KTextEditor::Cursor realMatchEnd = KTextEditor::Cursor(match.end().line(), match.end().column() - 1); if (realMatchEnd.column() == -1) { realMatchEnd = KTextEditor::Cursor(realMatchEnd.line() - 1, m_view->doc()->lineLength(realMatchEnd.line() - 1)); } moveCursorTo(placeCursorAtEndOfMatch ? realMatchEnd : match.start()); setBarBackground(SearchMode::MatchFound); } else { moveCursorTo(m_startingCursorPos); if (!m_edit->text().isEmpty()) { setBarBackground(SearchMode::NoMatchFound); } else { setBarBackground(SearchMode::Normal); } } updateMatchHighlight(match); } void EmulatedCommandBar::SearchMode::deactivate(bool wasAborted) { // "Deactivate" can be called multiple times between init()'s, so only reset the cursor once! if (m_startingCursorPos.isValid()) { if (wasAborted) { moveCursorTo(m_startingCursorPos); } } m_startingCursorPos = KTextEditor::Cursor::invalid(); setBarBackground(SearchMode::Normal); // Send a synthetic keypress through the system that signals whether the search was aborted or // not. If not, the keypress will "complete" the search motion, thus triggering it. // We send to KateViewInternal as it updates the status bar and removes the "?". const Qt::Key syntheticSearchCompletedKey = (wasAborted ? static_cast(0) : Qt::Key_Enter); QKeyEvent syntheticSearchCompletedKeyPress(QEvent::KeyPress, syntheticSearchCompletedKey, Qt::NoModifier); m_isSendingSyntheticSearchCompletedKeypress = true; QApplication::sendEvent(m_view->focusProxy(), &syntheticSearchCompletedKeyPress); m_isSendingSyntheticSearchCompletedKeypress = false; if (!wasAborted) { // Search was actually executed, so store it as the last search. m_viInputModeManager->searcher()->setLastSearchParams(m_currentSearchParams); } // Append the raw text of the search to the search history (i.e. without conversion // from Vim-style regex; without case-sensitivity markers stripped; etc. // Vim does this even if the search was aborted, so we follow suit. m_viInputModeManager->globalState()->searchHistory()->append(m_edit->text()); } CompletionStartParams EmulatedCommandBar::SearchMode::completionInvoked ( Completer::CompletionInvocation invocationType ) { Q_UNUSED(invocationType); return activateSearchHistoryCompletion(); } void EmulatedCommandBar::SearchMode::completionChosen() { // Choose completion with Enter/ Return -> close bar (the search will have already taken effect at this point), marking as not aborted . close(false); } CompletionStartParams EmulatedCommandBar::SearchMode::activateSearchHistoryCompletion() { return CompletionStartParams::createModeSpecific(reversed(m_viInputModeManager->globalState()->searchHistory()->items()), 0); } void EmulatedCommandBar::SearchMode::setBarBackground ( EmulatedCommandBar::SearchMode::BarBackgroundStatus status ) { QPalette barBackground(m_edit->palette()); switch (status) { case MatchFound: { KColorScheme::adjustBackground(barBackground, KColorScheme::PositiveBackground); break; } case NoMatchFound: { KColorScheme::adjustBackground(barBackground, KColorScheme::NegativeBackground); break; } case Normal: { barBackground = QPalette(); break; } } m_edit->setPalette(barBackground); } EmulatedCommandBar::CommandMode::CommandMode ( EmulatedCommandBar* emulatedCommandBar, MatchHighlighter* matchHighlighter, KTextEditor::ViewPrivate* view, QLineEdit* edit, InteractiveSedReplaceMode *interactiveSedReplaceMode, Completer* completer) : ActiveMode ( emulatedCommandBar, matchHighlighter ), m_edit(edit), m_view(view), m_interactiveSedReplaceMode(interactiveSedReplaceMode), m_completer(completer) { QList cmds; cmds.push_back(KateCommands::CoreCommands::self()); cmds.push_back(Commands::self()); cmds.push_back(AppCommands::self()); cmds.push_back(SedReplace::self()); cmds.push_back(BufferCommands::self()); Q_FOREACH (KTextEditor::Command *cmd, KateScriptManager::self()->commandLineScripts()) { cmds.push_back(cmd); } Q_FOREACH (KTextEditor::Command *cmd, cmds) { QStringList l = cmd->cmds(); for (int z = 0; z < l.count(); z++) { m_cmdDict.insert(l[z], cmd); } m_cmdCompletion.insertItems(l); } } void EmulatedCommandBar::CommandMode::setViInputModeManager ( InputModeManager* viInputModeManager ) { m_viInputModeManager = viInputModeManager; } bool EmulatedCommandBar::CommandMode::handleKeyPress ( const QKeyEvent* keyEvent ) { if (keyEvent->modifiers() == Qt::ControlModifier && (keyEvent->key() == Qt::Key_D || keyEvent->key() == Qt::Key_F)) { CommandMode::ParsedSedExpression parsedSedExpression = parseAsSedExpression(); if (parsedSedExpression.parsedSuccessfully) { const bool clearFindTerm = (keyEvent->key() == Qt::Key_D); if (clearFindTerm) { m_edit->setSelection(parsedSedExpression.findBeginPos, parsedSedExpression.findEndPos - parsedSedExpression.findBeginPos + 1); m_edit->insert(QString()); } else { // Clear replace term. m_edit->setSelection(parsedSedExpression.replaceBeginPos, parsedSedExpression.replaceEndPos - parsedSedExpression.replaceBeginPos + 1); m_edit->insert(QString()); } } return true; } return false; } void EmulatedCommandBar::CommandMode::editTextChanged ( const QString& newText ) { Q_UNUSED(newText); // We read the current text from m_edit. if (m_completer->isCompletionActive()) return; // Command completion doesn't need to be manually invoked. if (!withoutRangeExpression().isEmpty() && !m_completer->isNextTextChangeDueToCompletionChange()) { // ... However, command completion mode should not be automatically invoked if this is not the current leading // word in the text edit (it gets annoying if completion pops up after ":s/se" etc). const bool commandBeforeCursorIsLeading = (commandBeforeCursorBegin() == rangeExpression().length()); if (commandBeforeCursorIsLeading) { CompletionStartParams completionStartParams = activateCommandCompletion(); startCompletion(completionStartParams); } } } void EmulatedCommandBar::CommandMode::deactivate ( bool wasAborted ) { if (wasAborted) { // Appending the command to the history when it is executed is handled elsewhere; we can't // do it inside closed() as we may still be showing the command response display. m_viInputModeManager->globalState()->commandHistory()->append(m_edit->text()); // With Vim, aborting a command returns us to Normal mode, even if we were in Visual Mode. // If we switch from Visual to Normal mode, we need to clear the selection. m_view->clearSelection(); } } CompletionStartParams EmulatedCommandBar::CommandMode::completionInvoked(Completer::CompletionInvocation invocationType) { CompletionStartParams completionStartParams; if (invocationType == Completer::CompletionInvocation::ExtraContext) { if (isCursorInFindTermOfSed()) { completionStartParams = activateSedFindHistoryCompletion(); } else if (isCursorInReplaceTermOfSed()) { completionStartParams = activateSedReplaceHistoryCompletion(); } else { completionStartParams = activateCommandHistoryCompletion(); } } else { // Normal context, so boring, ordinary History completion. completionStartParams = activateCommandHistoryCompletion(); } return completionStartParams; } void EmulatedCommandBar::CommandMode::completionChosen() { QString commandToExecute = m_edit->text(); CommandMode::ParsedSedExpression parsedSedExpression = parseAsSedExpression(); if (parsedSedExpression.parsedSuccessfully) { const QString originalFindTerm = sedFindTerm(); const QString convertedFindTerm = vimRegexToQtRegexPattern(originalFindTerm); const QString commandWithSedSearchRegexConverted = withSedFindTermReplacedWith(convertedFindTerm); m_viInputModeManager->globalState()->searchHistory()->append(originalFindTerm); const QString replaceTerm = sedReplaceTerm(); m_viInputModeManager->globalState()->replaceHistory()->append(replaceTerm); commandToExecute = commandWithSedSearchRegexConverted; } const QString commandResponseMessage = executeCommand(commandToExecute); // Don't close the bar if executing the command switched us to Interactive Sed Replace mode. if (!m_interactiveSedReplaceMode->isActive()) { if (commandResponseMessage.isEmpty()) { m_emulatedCommandBar->hideMe(); } else { closeWithStatusMessage(commandResponseMessage); } } m_viInputModeManager->globalState()->commandHistory()->append(m_edit->text()); } QString EmulatedCommandBar::CommandMode::executeCommand ( const QString& commandToExecute ) { // Silently ignore leading space characters and colon characters (for vi-heads). uint n = 0; const uint textlen = commandToExecute.length(); while ((n < textlen) && commandToExecute[n].isSpace()) { n++; } if (n >= textlen) { return QString(); } QString commandResponseMessage; QString cmd = commandToExecute.mid(n); KTextEditor::Range range = CommandRangeExpressionParser(m_viInputModeManager).parseRange(cmd, cmd); if (cmd.length() > 0) { KTextEditor::Command *p = queryCommand(cmd); KateViCommandInterface *ci = dynamic_cast(p); if (ci) { ci->setViInputModeManager(m_viInputModeManager); ci->setViGlobal(m_viInputModeManager->globalState()); } // The following commands changes the focus themselves, so bar should be hidden before execution. // We got a range and a valid command, but the command does not inherit the RangeCommand // extension. Bail out. if (range.isValid() && !p->supportsRange(cmd)) { commandResponseMessage = i18n("Error: No range allowed for command \"%1\".", cmd); } else { if (p) { if (p->exec(m_view, cmd, commandResponseMessage, range)) { if (commandResponseMessage.length() > 0) { commandResponseMessage = i18n("Success: ") + commandResponseMessage; } } else { if (commandResponseMessage.length() > 0) { if (commandResponseMessage.contains(QLatin1Char('\n'))) { // multiline error, use widget with more space QWhatsThis::showText(m_emulatedCommandBar->mapToGlobal(QPoint(0, 0)), commandResponseMessage); } } else { commandResponseMessage = i18n("Command \"%1\" failed.", cmd); } } } else { commandResponseMessage = i18n("No such command: \"%1\"", cmd); } } } // 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(QLatin1String(" ")).at(0))) { m_view->setFocus(); } m_viInputModeManager->reset(); return commandResponseMessage; } QString EmulatedCommandBar::CommandMode::withoutRangeExpression() { const QString originalCommand = m_edit->text(); return originalCommand.mid(rangeExpression().length()); } QString EmulatedCommandBar::CommandMode::rangeExpression() { const QString command = m_edit->text(); return CommandRangeExpressionParser(m_viInputModeManager).parseRangeString(command); } EmulatedCommandBar::CommandMode::ParsedSedExpression EmulatedCommandBar::CommandMode::parseAsSedExpression() { const QString commandWithoutRangeExpression = withoutRangeExpression(); ParsedSedExpression parsedSedExpression; QString delimiter; parsedSedExpression.parsedSuccessfully = SedReplace::parse(commandWithoutRangeExpression, delimiter, parsedSedExpression.findBeginPos, parsedSedExpression.findEndPos, parsedSedExpression.replaceBeginPos, parsedSedExpression.replaceEndPos); if (parsedSedExpression.parsedSuccessfully) { parsedSedExpression.delimiter = delimiter.at(0); if (parsedSedExpression.replaceBeginPos == -1) { if (parsedSedExpression.findBeginPos != -1) { // The replace term was empty, and a quirk of the regex used is that replaceBeginPos will be -1. // It's actually the position after the first occurrence of the delimiter after the end of the find pos. parsedSedExpression.replaceBeginPos = commandWithoutRangeExpression.indexOf(delimiter, parsedSedExpression.findEndPos) + 1; parsedSedExpression.replaceEndPos = parsedSedExpression.replaceBeginPos - 1; } else { // Both find and replace terms are empty; replace term is at the third occurrence of the delimiter. parsedSedExpression.replaceBeginPos = 0; for (int delimiterCount = 1; delimiterCount <= 3; delimiterCount++) { parsedSedExpression.replaceBeginPos = commandWithoutRangeExpression.indexOf(delimiter, parsedSedExpression.replaceBeginPos + 1); } parsedSedExpression.replaceEndPos = parsedSedExpression.replaceBeginPos - 1; } } if (parsedSedExpression.findBeginPos == -1) { // The find term was empty, and a quirk of the regex used is that findBeginPos will be -1. // It's actually the position after the first occurrence of the delimiter. parsedSedExpression.findBeginPos = commandWithoutRangeExpression.indexOf(delimiter) + 1; parsedSedExpression.findEndPos = parsedSedExpression.findBeginPos - 1; } } if (parsedSedExpression.parsedSuccessfully) { parsedSedExpression.findBeginPos += rangeExpression().length(); parsedSedExpression.findEndPos += rangeExpression().length(); parsedSedExpression.replaceBeginPos += rangeExpression().length(); parsedSedExpression.replaceEndPos += rangeExpression().length(); } return parsedSedExpression; } QString EmulatedCommandBar::CommandMode::sedFindTerm() { const QString command = m_edit->text(); ParsedSedExpression parsedSedExpression = parseAsSedExpression(); Q_ASSERT(parsedSedExpression.parsedSuccessfully); return command.mid(parsedSedExpression.findBeginPos, parsedSedExpression.findEndPos - parsedSedExpression.findBeginPos + 1); } QString EmulatedCommandBar::CommandMode::sedReplaceTerm() { const QString command = m_edit->text(); ParsedSedExpression parsedSedExpression = parseAsSedExpression(); Q_ASSERT(parsedSedExpression.parsedSuccessfully); return command.mid(parsedSedExpression.replaceBeginPos, parsedSedExpression.replaceEndPos - parsedSedExpression.replaceBeginPos + 1); } QString EmulatedCommandBar::CommandMode::withSedFindTermReplacedWith ( const QString& newFindTerm ) { const QString command = m_edit->text(); ParsedSedExpression parsedSedExpression = parseAsSedExpression(); Q_ASSERT(parsedSedExpression.parsedSuccessfully); return command.mid(0, parsedSedExpression.findBeginPos) + newFindTerm + command.mid(parsedSedExpression.findEndPos + 1); } QString EmulatedCommandBar::CommandMode::withSedDelimiterEscaped ( const QString& text ) { ParsedSedExpression parsedSedExpression = parseAsSedExpression(); QString delimiterEscaped = ensuredCharEscaped(text, parsedSedExpression.delimiter); return delimiterEscaped; } bool EmulatedCommandBar::CommandMode::isCursorInFindTermOfSed() { ParsedSedExpression parsedSedExpression = parseAsSedExpression(); return parsedSedExpression.parsedSuccessfully && (m_edit->cursorPosition() >= parsedSedExpression.findBeginPos && m_edit->cursorPosition() <= parsedSedExpression.findEndPos + 1); } bool EmulatedCommandBar::CommandMode::isCursorInReplaceTermOfSed() { ParsedSedExpression parsedSedExpression = parseAsSedExpression(); return parsedSedExpression.parsedSuccessfully && m_edit->cursorPosition() >= parsedSedExpression.replaceBeginPos && m_edit->cursorPosition() <= parsedSedExpression.replaceEndPos + 1; } int EmulatedCommandBar::CommandMode::commandBeforeCursorBegin() { const QString textWithoutRangeExpression = withoutRangeExpression(); const int cursorPositionWithoutRangeExpression = m_edit->cursorPosition() - rangeExpression().length(); int commandBeforeCursorBegin = cursorPositionWithoutRangeExpression - 1; while (commandBeforeCursorBegin >= 0 && (textWithoutRangeExpression[commandBeforeCursorBegin].isLetterOrNumber() || textWithoutRangeExpression[commandBeforeCursorBegin] == QLatin1Char('_') || textWithoutRangeExpression[commandBeforeCursorBegin] == QLatin1Char('-'))) { commandBeforeCursorBegin--; } commandBeforeCursorBegin++; commandBeforeCursorBegin += rangeExpression().length(); return commandBeforeCursorBegin; } CompletionStartParams EmulatedCommandBar::CommandMode::activateCommandCompletion() { return CompletionStartParams::createModeSpecific(m_cmdCompletion.items(), commandBeforeCursorBegin()); } CompletionStartParams EmulatedCommandBar::CommandMode::activateCommandHistoryCompletion() { return CompletionStartParams::createModeSpecific(reversed(m_viInputModeManager->globalState()->commandHistory()->items()), 0); } CompletionStartParams EmulatedCommandBar::CommandMode::activateSedFindHistoryCompletion() { if (m_viInputModeManager->globalState()->searchHistory()->isEmpty()) { return CompletionStartParams::invalid(); } CommandMode::ParsedSedExpression parsedSedExpression = parseAsSedExpression(); return CompletionStartParams::createModeSpecific(reversed(m_viInputModeManager->globalState()->searchHistory()->items()), parsedSedExpression.findBeginPos, [this] (const QString& completion) -> QString { return withCaseSensitivityMarkersStripped(withSedDelimiterEscaped(completion)); }); } CompletionStartParams EmulatedCommandBar::CommandMode::activateSedReplaceHistoryCompletion() { if (m_viInputModeManager->globalState()->replaceHistory()->isEmpty()) { return CompletionStartParams::invalid(); } CommandMode::ParsedSedExpression parsedSedExpression = parseAsSedExpression(); return CompletionStartParams::createModeSpecific(reversed(m_viInputModeManager->globalState()->replaceHistory()->items()), parsedSedExpression.replaceBeginPos, [this] (const QString& completion) -> QString { return withCaseSensitivityMarkersStripped(withSedDelimiterEscaped(completion)); }); } KTextEditor::Command* EmulatedCommandBar::CommandMode::queryCommand ( const QString& cmd ) const { // a command can be named ".*[\w\-]+" with the constrain that it must // contain at least one letter. int f = 0; bool b = false; // special case: '-' and '_' can be part of a command name, but if the // command is 's' (substitute), it should be considered the delimiter and // should not be counted as part of the command name if (cmd.length() >= 2 && cmd.at(0) == QLatin1Char('s') && (cmd.at(1) == QLatin1Char('-') || cmd.at(1) == QLatin1Char('_'))) { return m_cmdDict.value(QStringLiteral("s")); } for (; f < cmd.length(); f++) { if (cmd[f].isLetter()) { b = true; } if (b && (! cmd[f].isLetterOrNumber() && cmd[f] != QLatin1Char('-') && cmd[f] != QLatin1Char('_'))) { break; } } return m_cmdDict.value(cmd.left(f)); } diff --git a/src/vimode/emulatedcommandbar/emulatedcommandbar.h b/src/vimode/emulatedcommandbar/emulatedcommandbar.h index 0e2f7080..736a6dcf 100644 --- a/src/vimode/emulatedcommandbar/emulatedcommandbar.h +++ b/src/vimode/emulatedcommandbar/emulatedcommandbar.h @@ -1,234 +1,208 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 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_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; namespace KateVi { class MatchHighlighter; +class InteractiveSedReplaceMode; /** * 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(InputModeManager *viInputModeManager, QWidget *parent = 0); virtual ~EmulatedCommandBar(); 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: InputModeManager *m_viInputModeManager; bool m_isActive = false; Mode m_mode = NoMode; KTextEditor::ViewPrivate *m_view; QLineEdit *m_edit; QLabel *m_barTypeIndicator; void showBarTypeIndicator(Mode mode); bool m_wasAborted = true; bool m_suspendEditEventFiltering = false; bool m_waitingForRegister = false ; QLabel *m_waitingForRegisterIndicator; bool m_insertedTextShouldBeEscapedForSearchingAsLiteral = false; void hideAllWidgetsExcept(QWidget* widgetToKeepVisible); - QScopedPointer m_matchHighligher; - friend class ActiveMode; + QScopedPointer m_matchHighligher; QScopedPointer m_completer; - friend class ActiveMode; - - class InteractiveSedReplaceMode : public ActiveMode - { - public: - InteractiveSedReplaceMode(EmulatedCommandBar* emulatedCommandBar, MatchHighlighter* matchHighlighter); - virtual ~InteractiveSedReplaceMode() - { - }; - void activate(QSharedPointer interactiveSedReplace); - bool isActive() const - { - return m_isActive; - } - virtual bool handleKeyPress(const QKeyEvent* keyEvent); - virtual void deactivate(bool wasAborted); - QWidget *label(); - private: - void updateInteractiveSedReplaceLabelText(); - void finishInteractiveSedReplace(); - QSharedPointer m_interactiveSedReplacer; - bool m_isActive; - QLabel *m_interactiveSedReplaceLabel; - }; - - class SearchMode : public ActiveMode { public: SearchMode(EmulatedCommandBar* emulatedCommandBar, MatchHighlighter* matchHighlighter, KTextEditor::ViewPrivate* view, QLineEdit* edit); virtual ~SearchMode() { }; enum class SearchDirection { Forward, Backward }; void init(SearchDirection); void setViInputModeManager(InputModeManager *viInputModeManager); virtual bool handleKeyPress ( const QKeyEvent* keyEvent ); virtual void editTextChanged(const QString &newText); virtual CompletionStartParams completionInvoked(Completer::CompletionInvocation invocationType); virtual void completionChosen(); virtual void deactivate(bool wasAborted); bool isSendingSyntheticSearchCompletedKeypress() const { return m_isSendingSyntheticSearchCompletedKeypress; } private: EmulatedCommandBar *m_emulatedCommandBar = nullptr; KTextEditor::ViewPrivate *m_view = nullptr; InputModeManager *m_viInputModeManager = nullptr; 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; }; class CommandMode : public ActiveMode { public: CommandMode(EmulatedCommandBar* emulatedCommandBar, MatchHighlighter* matchHighlighter, KTextEditor::ViewPrivate* view, QLineEdit* edit, InteractiveSedReplaceMode *interactiveSedReplaceMode, Completer* completer); virtual ~CommandMode() { } void setViInputModeManager(InputModeManager *viInputModeManager); virtual bool handleKeyPress ( const QKeyEvent* keyEvent ); virtual void editTextChanged(const QString &newText); virtual CompletionStartParams completionInvoked(Completer::CompletionInvocation invocationType); virtual void completionChosen(); void deactivate(bool wasAborted); 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; InputModeManager *m_viInputModeManager = nullptr; KTextEditor::ViewPrivate *m_view; InteractiveSedReplaceMode *m_interactiveSedReplaceMode; Completer *m_completer; KCompletion m_cmdCompletion; QHash m_cmdDict; KTextEditor::Command *queryCommand(const QString &cmd) const; }; QScopedPointer m_interactiveSedReplaceMode; QScopedPointer m_searchMode; QScopedPointer m_commandMode; void moveCursorTo(const KTextEditor::Cursor &cursorPos); 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; void deleteSpacesToLeftOfCursor(); void deleteWordCharsToLeftOfCursor(); bool deleteNonWordCharsToLeftOfCursor(); void closed() Q_DECL_OVERRIDE; void closeWithStatusMessage(const QString& exitStatusMessage); QTimer *m_exitStatusMessageDisplayHideTimer; QLabel *m_exitStatusMessageDisplay; long m_exitStatusMessageHideTimeOutMS = 4000; private Q_SLOTS: void editTextChanged(const QString &newText); void startHideExitStatusMessageTimer(); }; } #endif /* KATEVI_EMULATED_COMMAND_BAR_H */ diff --git a/src/vimode/emulatedcommandbar/interactivesedreplacemode.cpp b/src/vimode/emulatedcommandbar/interactivesedreplacemode.cpp new file mode 100644 index 00000000..0bd424ce --- /dev/null +++ b/src/vimode/emulatedcommandbar/interactivesedreplacemode.cpp @@ -0,0 +1,89 @@ +#include "interactivesedreplacemode.h" + +#include +#include + +using namespace KateVi; + +InteractiveSedReplaceMode::InteractiveSedReplaceMode(EmulatedCommandBar* emulatedCommandBar, MatchHighlighter* matchHighlighter) + : ActiveMode(emulatedCommandBar, matchHighlighter), + m_isActive(false) +{ + m_interactiveSedReplaceLabel = new QLabel(); + m_interactiveSedReplaceLabel->setObjectName(QStringLiteral("interactivesedreplace")); +} + +void InteractiveSedReplaceMode::activate(QSharedPointer interactiveSedReplace) +{ + Q_ASSERT_X(interactiveSedReplace->currentMatch().isValid(), "startInteractiveSearchAndReplace", "KateCommands shouldn't initiate an interactive sed replace with no initial match"); + + m_isActive = true; + m_interactiveSedReplacer = interactiveSedReplace; + + hideAllWidgetsExcept(m_interactiveSedReplaceLabel); + m_interactiveSedReplaceLabel->show(); + updateInteractiveSedReplaceLabelText(); + + updateMatchHighlight(interactiveSedReplace->currentMatch()); + moveCursorTo(interactiveSedReplace->currentMatch().start()); +} + +bool InteractiveSedReplaceMode::handleKeyPress(const QKeyEvent* keyEvent) +{ + // TODO - it would be better to use e.g. keyEvent->key() == Qt::Key_Y instead of keyEvent->text() == "y", + // but this would require some slightly dicey changes to the "feed key press" code in order to make it work + // with mappings and macros. + if (keyEvent->text() == QLatin1String("y") || keyEvent->text() == QLatin1String("n")) { + const KTextEditor::Cursor cursorPosIfFinalMatch = m_interactiveSedReplacer->currentMatch().start(); + if (keyEvent->text() == QLatin1String("y")) { + m_interactiveSedReplacer->replaceCurrentMatch(); + } else { + m_interactiveSedReplacer->skipCurrentMatch(); + } + updateMatchHighlight(m_interactiveSedReplacer->currentMatch()); + updateInteractiveSedReplaceLabelText(); + moveCursorTo(m_interactiveSedReplacer->currentMatch().start()); + + if (!m_interactiveSedReplacer->currentMatch().isValid()) { + moveCursorTo(cursorPosIfFinalMatch); + finishInteractiveSedReplace(); + } + return true; + } else if (keyEvent->text() == QLatin1String("l")) { + m_interactiveSedReplacer->replaceCurrentMatch(); + finishInteractiveSedReplace(); + return true; + } else if (keyEvent->text() == QLatin1String("q")) { + finishInteractiveSedReplace(); + return true; + } else if (keyEvent->text() == QLatin1String("a")) { + m_interactiveSedReplacer->replaceAllRemaining(); + finishInteractiveSedReplace(); + return true; + } + return false; +} + +void InteractiveSedReplaceMode::deactivate( bool wasAborted ) +{ + Q_UNUSED(wasAborted); + m_isActive = false; + m_interactiveSedReplaceLabel->hide(); +} + +QWidget* InteractiveSedReplaceMode::label() +{ + return m_interactiveSedReplaceLabel; +} + +void InteractiveSedReplaceMode::updateInteractiveSedReplaceLabelText() +{ + m_interactiveSedReplaceLabel->setText(m_interactiveSedReplacer->currentMatchReplacementConfirmationMessage() + QLatin1String(" (y/n/a/q/l)")); +} + +void InteractiveSedReplaceMode::finishInteractiveSedReplace() +{ + deactivate(false); + closeWithStatusMessage(m_interactiveSedReplacer->finalStatusReportMessage()); + m_interactiveSedReplacer.clear(); +} diff --git a/src/vimode/emulatedcommandbar/interactivesedreplacemode.h b/src/vimode/emulatedcommandbar/interactivesedreplacemode.h new file mode 100644 index 00000000..fe767745 --- /dev/null +++ b/src/vimode/emulatedcommandbar/interactivesedreplacemode.h @@ -0,0 +1,42 @@ +#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); + virtual ~InteractiveSedReplaceMode() + { + }; + void activate(QSharedPointer interactiveSedReplace); + bool isActive() const + { + return m_isActive; + } + virtual bool handleKeyPress(const QKeyEvent* keyEvent); + virtual void deactivate(bool wasAborted); + QWidget *label(); +private: + void updateInteractiveSedReplaceLabelText(); + void finishInteractiveSedReplace(); + QSharedPointer m_interactiveSedReplacer; + bool m_isActive; + QLabel *m_interactiveSedReplaceLabel; +}; +} + +#endif