| 1 | /* |
| 2 | SPDX-FileCopyrightText: 2003-2005 Anders Lund <anders@alweb.dk> |
| 3 | SPDX-FileCopyrightText: 2001-2010 Christoph Cullmann <cullmann@kde.org> |
| 4 | SPDX-FileCopyrightText: 2001 Charles Samuels <charles@kde.org> |
| 5 | |
| 6 | SPDX-License-Identifier: LGPL-2.0-or-later |
| 7 | */ |
| 8 | |
| 9 | #include <vimode/cmds.h> |
| 10 | |
| 11 | #include "globalstate.h" |
| 12 | #include "katecmd.h" |
| 13 | #include "kateview.h" |
| 14 | #include "kateviinputmode.h" |
| 15 | #include "marks.h" |
| 16 | #include <vimode/emulatedcommandbar/emulatedcommandbar.h> |
| 17 | #include <vimode/inputmodemanager.h> |
| 18 | #include <vimode/modes/normalvimode.h> |
| 19 | #include <vimode/searcher.h> |
| 20 | |
| 21 | #include <KLocalizedString> |
| 22 | |
| 23 | #include <QRegularExpression> |
| 24 | |
| 25 | using namespace KateVi; |
| 26 | |
| 27 | // BEGIN ViCommands |
| 28 | Commands *Commands::m_instance = nullptr; |
| 29 | |
| 30 | bool Commands::exec(KTextEditor::View *view, const QString &_cmd, QString &msg, const KTextEditor::Range &range) |
| 31 | { |
| 32 | Q_UNUSED(range) |
| 33 | // cast it hardcore, we know that it is really a kateview :) |
| 34 | KTextEditor::ViewPrivate *v = static_cast<KTextEditor::ViewPrivate *>(view); |
| 35 | |
| 36 | if (!v) { |
| 37 | msg = i18n("Could not access view" ); |
| 38 | return false; |
| 39 | } |
| 40 | |
| 41 | // create a list of args |
| 42 | QStringList args(_cmd.split(sep: QRegularExpression(QStringLiteral("\\s+" )), behavior: Qt::SkipEmptyParts)); |
| 43 | QString cmd(args.takeFirst()); |
| 44 | |
| 45 | // ALL commands that takes no arguments. |
| 46 | if (mappingCommands().contains(str: cmd)) { |
| 47 | if (cmd.endsWith(s: QLatin1String("unmap" ))) { |
| 48 | if (args.count() == 1) { |
| 49 | m_viInputModeManager->globalState()->mappings()->remove(mode: modeForMapCommand(mapCommand: cmd), from: args.at(i: 0)); |
| 50 | return true; |
| 51 | } else { |
| 52 | msg = i18n("Missing argument. Usage: %1 <from>" , cmd); |
| 53 | return false; |
| 54 | } |
| 55 | } else if (cmd == QLatin1String("nohlsearch" ) || cmd == QLatin1String("noh" )) { |
| 56 | m_viInputModeManager->searcher()->hideCurrentHighlight(); |
| 57 | return true; |
| 58 | } else if (cmd == QLatin1String("set-hlsearch" ) || cmd == QLatin1String("set-hls" )) { |
| 59 | m_viInputModeManager->searcher()->enableHighlightSearch(enable: true); |
| 60 | return true; |
| 61 | } else if (cmd == QLatin1String("set-nohlsearch" ) || cmd == QLatin1String("set-nohls" )) { |
| 62 | m_viInputModeManager->searcher()->enableHighlightSearch(enable: false); |
| 63 | return true; |
| 64 | } |
| 65 | if (args.count() == 1) { |
| 66 | msg = m_viInputModeManager->globalState()->mappings()->get(mode: modeForMapCommand(mapCommand: cmd), from: args.at(i: 0), decode: true); |
| 67 | if (msg.isEmpty()) { |
| 68 | msg = i18n("No mapping found for \"%1\"" , args.at(0)); |
| 69 | return false; |
| 70 | } else { |
| 71 | msg = i18n("\"%1\" is mapped to \"%2\"" , args.at(0), msg); |
| 72 | } |
| 73 | } else if (args.count() == 2) { |
| 74 | Mappings::MappingRecursion mappingRecursion = (isMapCommandRecursive(mapCommand: cmd)) ? Mappings::Recursive : Mappings::NonRecursive; |
| 75 | m_viInputModeManager->globalState()->mappings()->add(mode: modeForMapCommand(mapCommand: cmd), from: args.at(i: 0), to: args.at(i: 1), recursion: mappingRecursion); |
| 76 | } else { |
| 77 | msg = i18n("Missing argument(s). Usage: %1 <from> [<to>]" , cmd); |
| 78 | return false; |
| 79 | } |
| 80 | |
| 81 | return true; |
| 82 | } |
| 83 | |
| 84 | NormalViMode *nm = m_viInputModeManager->getViNormalMode(); |
| 85 | |
| 86 | if (cmd == QLatin1String("d" ) || cmd == QLatin1String("delete" ) || cmd == QLatin1String("j" ) || cmd == QLatin1String("c" ) || cmd == QLatin1String("change" ) |
| 87 | || cmd == QLatin1String("<" ) || cmd == QLatin1String(">" ) || cmd == QLatin1String("y" ) || cmd == QLatin1String("yank" )) { |
| 88 | KTextEditor::Cursor start_cursor_position = v->cursorPosition(); |
| 89 | |
| 90 | int count = 1; |
| 91 | if (range.isValid()) { |
| 92 | count = qAbs(t: range.end().line() - range.start().line()) + 1; |
| 93 | v->setCursorPosition(KTextEditor::Cursor(qMin(a: range.start().line(), b: range.end().line()), 0)); |
| 94 | } |
| 95 | |
| 96 | static const QRegularExpression number(QStringLiteral("^(\\d+)$" )); |
| 97 | for (int i = 0; i < args.count(); i++) { |
| 98 | auto match = number.match(subject: args.at(i)); |
| 99 | if (match.hasMatch()) { |
| 100 | count += match.captured(nth: 0).toInt() - 1; |
| 101 | } |
| 102 | |
| 103 | QChar r = args.at(i).at(i: 0); |
| 104 | if (args.at(i).size() == 1 |
| 105 | && ((r >= QLatin1Char('a') && r <= QLatin1Char('z')) || r == QLatin1Char('_') || r == QLatin1Char('-') || r == QLatin1Char('+') |
| 106 | || r == QLatin1Char('*'))) { |
| 107 | nm->setRegister(r); |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | nm->setCount(count); |
| 112 | |
| 113 | if (cmd == QLatin1String("d" ) || cmd == QLatin1String("delete" )) { |
| 114 | nm->commandDeleteLine(); |
| 115 | } |
| 116 | if (cmd == QLatin1String("j" )) { |
| 117 | nm->commandJoinLines(); |
| 118 | } |
| 119 | if (cmd == QLatin1String("c" ) || cmd == QLatin1String("change" )) { |
| 120 | nm->commandChangeLine(); |
| 121 | } |
| 122 | if (cmd == QLatin1String("<" )) { |
| 123 | nm->commandUnindentLine(); |
| 124 | } |
| 125 | if (cmd == QLatin1String(">" )) { |
| 126 | nm->commandIndentLine(); |
| 127 | } |
| 128 | if (cmd == QLatin1String("y" ) || cmd == QLatin1String("yank" )) { |
| 129 | nm->commandYankLine(); |
| 130 | v->setCursorPosition(start_cursor_position); |
| 131 | } |
| 132 | |
| 133 | // TODO - should we resetParser, here? We'd have to make it public, if so. |
| 134 | // Or maybe synthesise a KateViCommand to execute instead ... ? |
| 135 | nm->setCount(0); |
| 136 | |
| 137 | return true; |
| 138 | } |
| 139 | |
| 140 | if (cmd == QLatin1String("mark" ) || cmd == QLatin1String("ma" ) || cmd == QLatin1String("k" )) { |
| 141 | if (args.count() == 0) { |
| 142 | if (cmd == QLatin1String("mark" )) { |
| 143 | // TODO: show up mark list; |
| 144 | } else { |
| 145 | msg = i18n("Wrong arguments" ); |
| 146 | return false; |
| 147 | } |
| 148 | } else if (args.count() == 1) { |
| 149 | QChar r = args.at(i: 0).at(i: 0); |
| 150 | int line; |
| 151 | if ((r >= QLatin1Char('a') && r <= QLatin1Char('z')) || r == QLatin1Char('_') || r == QLatin1Char('+') || r == QLatin1Char('*')) { |
| 152 | if (range.isValid()) { |
| 153 | line = qMax(a: range.end().line(), b: range.start().line()); |
| 154 | } else { |
| 155 | line = v->cursorPosition().line(); |
| 156 | } |
| 157 | |
| 158 | m_viInputModeManager->marks()->setUserMark(mark: r, pos: KTextEditor::Cursor(line, 0)); |
| 159 | } |
| 160 | } else { |
| 161 | msg = i18n("Wrong arguments" ); |
| 162 | return false; |
| 163 | } |
| 164 | return true; |
| 165 | } |
| 166 | |
| 167 | // should not happen :) |
| 168 | msg = i18n("Unknown command '%1'" , cmd); |
| 169 | return false; |
| 170 | } |
| 171 | |
| 172 | bool Commands::supportsRange(const QString &range) |
| 173 | { |
| 174 | static QStringList l; |
| 175 | |
| 176 | if (l.isEmpty()) { |
| 177 | l << QStringLiteral("d" ) << QStringLiteral("delete" ) << QStringLiteral("j" ) << QStringLiteral("c" ) << QStringLiteral("change" ) << QStringLiteral("<" ) |
| 178 | << QStringLiteral(">" ) << QStringLiteral("y" ) << QStringLiteral("yank" ) << QStringLiteral("ma" ) << QStringLiteral("mark" ) << QStringLiteral("k" ); |
| 179 | } |
| 180 | |
| 181 | return l.contains(str: range.split(sep: QLatin1Char(' ')).at(i: 0)); |
| 182 | } |
| 183 | |
| 184 | KCompletion *Commands::completionObject(KTextEditor::View *view, const QString &cmd) |
| 185 | { |
| 186 | Q_UNUSED(view) |
| 187 | |
| 188 | KTextEditor::ViewPrivate *v = static_cast<KTextEditor::ViewPrivate *>(view); |
| 189 | |
| 190 | if (v && (cmd == QLatin1String("nn" ) || cmd == QLatin1String("nnoremap" ))) { |
| 191 | QStringList l = m_viInputModeManager->globalState()->mappings()->getAll(mode: Mappings::NormalModeMapping); |
| 192 | |
| 193 | KateCmdShellCompletion *co = new KateCmdShellCompletion(); |
| 194 | co->setItems(l); |
| 195 | co->setIgnoreCase(false); |
| 196 | return co; |
| 197 | } |
| 198 | return nullptr; |
| 199 | } |
| 200 | |
| 201 | const QStringList &Commands::mappingCommands() |
| 202 | { |
| 203 | static QStringList mappingsCommands; |
| 204 | if (mappingsCommands.isEmpty()) { |
| 205 | mappingsCommands << QStringLiteral("nmap" ) << QStringLiteral("nm" ) << QStringLiteral("noremap" ) << QStringLiteral("nnoremap" ) << QStringLiteral("nn" ) |
| 206 | << QStringLiteral("no" ) << QStringLiteral("vmap" ) << QStringLiteral("vm" ) << QStringLiteral("vnoremap" ) << QStringLiteral("vn" ) |
| 207 | << QStringLiteral("imap" ) << QStringLiteral("im" ) << QStringLiteral("inoremap" ) << QStringLiteral("ino" ) << QStringLiteral("cmap" ) |
| 208 | << QStringLiteral("cm" ) << QStringLiteral("cnoremap" ) << QStringLiteral("cno" ); |
| 209 | |
| 210 | mappingsCommands << QStringLiteral("nunmap" ) << QStringLiteral("vunmap" ) << QStringLiteral("iunmap" ) << QStringLiteral("cunmap" ); |
| 211 | |
| 212 | mappingsCommands << QStringLiteral("nohlsearch" ) << QStringLiteral("noh" ) << QStringLiteral("set-hlsearch" ) << QStringLiteral("set-hls" ) |
| 213 | << QStringLiteral("set-nohlsearch" ) << QStringLiteral("set-nohls" ); |
| 214 | } |
| 215 | return mappingsCommands; |
| 216 | } |
| 217 | |
| 218 | Mappings::MappingMode Commands::modeForMapCommand(const QString &mapCommand) |
| 219 | { |
| 220 | if (mapCommand.startsWith(c: u'v')) { |
| 221 | if (mapCommand == u"vmap" || mapCommand == u"vm" || mapCommand == u"vn" || mapCommand == u"vnoremap" || mapCommand == u"vunmap" ) { |
| 222 | return Mappings::VisualModeMapping; |
| 223 | } |
| 224 | } |
| 225 | |
| 226 | if (mapCommand.startsWith(c: u'i')) { |
| 227 | if (mapCommand == u"imap" || mapCommand == u"im" || mapCommand == u"ino" || mapCommand == u"inoremap" || mapCommand == u"iunmap" ) { |
| 228 | return Mappings::InsertModeMapping; |
| 229 | } |
| 230 | } |
| 231 | |
| 232 | if (mapCommand.startsWith(c: u'c')) { |
| 233 | if (mapCommand == u"cmap" || mapCommand == u"cm" || mapCommand == u"cno" || mapCommand == u"cnoremap" || mapCommand == u"cunmap" ) { |
| 234 | return Mappings::CommandModeMapping; |
| 235 | } |
| 236 | } |
| 237 | |
| 238 | // if (mapCommand == u"nunmap") { |
| 239 | // return Mappings::NormalModeMapping; |
| 240 | // } |
| 241 | return Mappings::NormalModeMapping; |
| 242 | } |
| 243 | |
| 244 | bool Commands::isMapCommandRecursive(const QString &mapCommand) |
| 245 | { |
| 246 | return mapCommand == u"nmap" || mapCommand == u"nm" // |
| 247 | || mapCommand == u"vmap" || mapCommand == u"vm" // |
| 248 | || mapCommand == u"imap" || mapCommand == u"im" // |
| 249 | || mapCommand == u"cmap" || mapCommand == u"cm" ; |
| 250 | } |
| 251 | |
| 252 | // END ViCommands |
| 253 | |
| 254 | // BEGIN SedReplace |
| 255 | SedReplace *SedReplace::m_instance = nullptr; |
| 256 | |
| 257 | bool SedReplace::interactiveSedReplace(KTextEditor::ViewPrivate *, std::shared_ptr<InteractiveSedReplacer> interactiveSedReplace) |
| 258 | { |
| 259 | EmulatedCommandBar *emulatedCommandBar = m_viInputModeManager->inputAdapter()->viModeEmulatedCommandBar(); |
| 260 | emulatedCommandBar->startInteractiveSearchAndReplace(interactiveSedReplace); |
| 261 | return true; |
| 262 | } |
| 263 | // END SedReplace |
| 264 | |