diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 51374c76..25ac61a3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,342 +1,343 @@ # 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/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::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/include/ktexteditor/command.h b/src/include/ktexteditor/command.h index f65d11b7..3d57ef43 100644 --- a/src/include/ktexteditor/command.h +++ b/src/include/ktexteditor/command.h @@ -1,210 +1,212 @@ /* This file is part of the KDE project Copyright (C) 2005 Christoph Cullmann (cullmann@kde.org) Copyright (C) 2005-2006 Dominik Haumann (dhdev@gmx.de) Copyright (C) 2008 Erlend Hamberg (ehamberg@gmail.com) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KTEXTEDITOR_COMMAND_H #define KTEXTEDITOR_COMMAND_H #include #include +#include + #include class QStringList; class KCompletion; namespace KTextEditor { class View; /** * \brief An Editor command line command. * * \section cmd_intro Introduction * * The Command class represents a command for the editor command line. A * command simply consists of a string, for example \e find. The command * auto-registers itself at the Editor. The Editor itself queries * the command for a list of accepted strings/commands by calling cmds(). * If the command gets invoked the function exec() is called, i.e. you have * to implement the \e reaction in exec(). Whenever the user needs help for * a command help() is called. * * \section cmd_information Command Information * To provide reasonable information about a specific command there are the * following accessor functions for a given command string: * - name() returns a label * - description() returns a descriptive text * - category() returns a category into which the command fits. * * These getters allow KTextEditor implementations to plug commands into menus * and toolbars, so that a user can assign shortcuts. * * \section cmd_completion Command Completion * * The Command optionally can show a completion popup to help the user select * a valid entry as first parameter to the Command. To this end, return a * valid completion item by reiplementing completionObject(). * * The returned completion object is deleted automatically once it is not needed * anymore. Therefore neither delete the completion object yourself nor return * the same completion object twice. * * \section cmd_interactive Interactive Commands * * In case the Command needs to interactively process the text of the parameters, * override wantsToProcessText() by returning @e true and reimplement processText(). * * A typical example of an interative command would be the incremental search. * * \section cmd_extension Command Extensions * * The class RangeCommand enables you to support ranges so that you can apply * commands on regions of text. * * \see KTextEditor::CommandInterface * \author Christoph Cullmann \ */ class KTEXTEDITOR_EXPORT Command : public QObject { Q_OBJECT public: /** * Constructor with \p parent. * Will register this command for the commands names given in \p cmds at the global editor instance. */ Command(const QStringList &cmds, QObject *parent = Q_NULLPTR); /** * Virtual destructor. * Will unregister this command at the global editor instance. */ virtual ~Command(); public: /** * Return a list of strings a command may begin with. * This is the same list the command was constructed with. * A string is the start part of a pure text which can be handled by this * command, i.e. for the command s/sdl/sdf/g the corresponding string is * simply \e s, and for char:1212 simply \e char. * \return list of supported commands */ inline const QStringList &cmds() const { return m_cmds; } - + /** * Find out if a given command can act on a range. This is used for checking * if a command should be called when the user also gave a range or if an * error should be raised. * * \return \e true if command supports acting on a range of lines, false if * not, default implementation returns false */ virtual bool supportsRange(const QString &cmd); /** * Execute the command for the given \p view and \p cmd string. * Return the success value and a \p msg for status. As example we * consider a replace command. The replace command would return the number * of replaced strings as \p msg, like "16 replacements made." If an error * occurred in the usage it would return \e false and set the \p msg to * something like "missing argument." or such. - * + * * If a non-invalid range is given, the command shall be executed on that range. * supportsRange() tells if the command supports that. * * \return \e true on success, otherwise \e false */ virtual bool exec(KTextEditor::View *view, const QString &cmd, QString &msg, const KTextEditor::Range &range = KTextEditor::Range::invalid()) = 0; /** * Shows help for the given \p view and \p cmd string. * If your command has a help text for \p cmd you have to return \e true * and set the \p msg to a meaningful text. The help text is embedded by * the Editor in a Qt::RichText enabled widget, e.g. a QToolTip. * \return \e true if your command has a help text, otherwise \e false */ virtual bool help(KTextEditor::View *view, const QString &cmd, QString &msg) = 0; /** * Return a KCompletion object that will substitute the command line * default one while typing the first argument of the command \p cmdname. * The text will be added to the command separated by one space character. * * Override this method if your command can provide a completion object. * The returned completion object is deleted automatically once it is not needed * anymore. Therefore neither delete the completion object yourself nor return * the same completion object twice. * * The default implementation returns a null pointer (\e nullptr). * * \param view the view the command will work on * \param cmdname the command name associated with this request. * \return a valid completion object or \e nullptr, if a completion object is * not supported */ virtual KCompletion *completionObject(KTextEditor::View *view, const QString &cmdname); /** * Check, whether the command wants to process text interactively for the * given command with name \p cmdname. * If you return true, the command's processText() method is called * whenever the text in the command line changed. * * Reimplement this to return true, if your commands wants to process the * text while typing. * * \param cmdname the command name associated with this query. * \return \e true, if your command wants to process text interactively, * otherwise \e false * \see processText() */ virtual bool wantsToProcessText(const QString &cmdname); /** * This is called by the command line each time the argument text for the * command changed, if wantsToProcessText() returns \e true. * \param view the current view * \param text the current command text typed by the user * \see wantsToProcessText() */ virtual void processText(KTextEditor::View *view, const QString &text); private: /** * the command list this command got constructed with */ const QStringList m_cmds; - + /** * Private d-pointer */ class CommandPrivate * const d; }; } #endif diff --git a/src/vimode/emulatedcommandbar/commandmode.cpp b/src/vimode/emulatedcommandbar/commandmode.cpp new file mode 100644 index 00000000..215b5d07 --- /dev/null +++ b/src/vimode/emulatedcommandbar/commandmode.cpp @@ -0,0 +1,394 @@ +#include "commandmode.h" + +#include "emulatedcommandbar.h" +#include "interactivesedreplacemode.h" +#include "searchmode.h" +#include "../commandrangeexpressionparser.h" + +#include +#include +#include +#include "../globalstate.h" +#include "../history.h" + +#include "katescriptmanager.h" +#include "katecmds.h" + +#include + +#include +#include + +using namespace KateVi; + +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 CommandMode::setViInputModeManager ( InputModeManager* viInputModeManager ) +{ + m_viInputModeManager = viInputModeManager; +} + +bool 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 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 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 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 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 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 CommandMode::withoutRangeExpression() +{ + const QString originalCommand = m_edit->text(); + return originalCommand.mid(rangeExpression().length()); +} + +QString CommandMode::rangeExpression() +{ + const QString command = m_edit->text(); + return CommandRangeExpressionParser(m_viInputModeManager).parseRangeString(command); +} + +CommandMode::ParsedSedExpression 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 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 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 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 CommandMode::withSedDelimiterEscaped ( const QString& text ) +{ + ParsedSedExpression parsedSedExpression = parseAsSedExpression(); + QString delimiterEscaped = ensuredCharEscaped(text, parsedSedExpression.delimiter); + return delimiterEscaped; +} + +bool CommandMode::isCursorInFindTermOfSed() +{ + ParsedSedExpression parsedSedExpression = parseAsSedExpression(); + return parsedSedExpression.parsedSuccessfully && (m_edit->cursorPosition() >= parsedSedExpression.findBeginPos && m_edit->cursorPosition() <= parsedSedExpression.findEndPos + 1); +} + +bool CommandMode::isCursorInReplaceTermOfSed() +{ + ParsedSedExpression parsedSedExpression = parseAsSedExpression(); + return parsedSedExpression.parsedSuccessfully && m_edit->cursorPosition() >= parsedSedExpression.replaceBeginPos && m_edit->cursorPosition() <= parsedSedExpression.replaceEndPos + 1; +} + +int 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 CommandMode::activateCommandCompletion() +{ + return CompletionStartParams::createModeSpecific(m_cmdCompletion.items(), commandBeforeCursorBegin()); +} + +CompletionStartParams CommandMode::activateCommandHistoryCompletion() +{ + return CompletionStartParams::createModeSpecific(reversed(m_viInputModeManager->globalState()->commandHistory()->items()), 0); +} + +CompletionStartParams 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 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* 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/commandmode.h b/src/vimode/emulatedcommandbar/commandmode.h new file mode 100644 index 00000000..f1603152 --- /dev/null +++ b/src/vimode/emulatedcommandbar/commandmode.h @@ -0,0 +1,80 @@ +#ifndef KATEVI_EMULATED_COMMAND_BAR_COMMANDMODE_H +#define KATEVI_EMULATED_COMMAND_BAR_COMMANDMODE_H + +#include "activemode.h" + +#include + +#include + +namespace KTextEditor { + class ViewPrivate; +} + +namespace KateVi +{ +class EmulatedCommandBar; +class MatchHighlighter; +class InteractiveSedReplaceMode; +class Completer; +class InputModeManager; + +class CommandMode : public ActiveMode +{ +public: + CommandMode(EmulatedCommandBar* emulatedCommandBar, MatchHighlighter* matchHighlighter, 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; +}; +} + +#endif diff --git a/src/vimode/emulatedcommandbar/emulatedcommandbar.cpp b/src/vimode/emulatedcommandbar/emulatedcommandbar.cpp index 513de48b..7ee21a18 100644 --- a/src/vimode/emulatedcommandbar/emulatedcommandbar.cpp +++ b/src/vimode/emulatedcommandbar/emulatedcommandbar.cpp @@ -1,808 +1,431 @@ /* 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 "searchmode.h" +#include "commandmode.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 using namespace KateVi; namespace { /** * @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; } } 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::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 3831f0bf..09d4485a 100644 --- a/src/vimode/emulatedcommandbar/emulatedcommandbar.h +++ b/src/vimode/emulatedcommandbar/emulatedcommandbar.h @@ -1,176 +1,120 @@ /* 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; class SearchMode; +class CommandMode; /** * A KateViewBarWidget that attempts to emulate some of the features of Vim's own command bar, * including insertion of register contents via ctr-r; dismissal via * ctrl-c and ctrl-[; bi-directional incremental searching, with SmartCase; interactive sed-replace; * plus a few extensions such as completion from document and navigable sed search and sed replace history. */ class KTEXTEDITOR_EXPORT EmulatedCommandBar : public KateViewBarWidget { Q_OBJECT public: enum Mode { NoMode, SearchForward, SearchBackward, Command }; explicit EmulatedCommandBar(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); friend class ActiveMode; QScopedPointer m_matchHighligher; QScopedPointer m_completer; - - 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 */