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
25using namespace KateVi;
26
27// BEGIN ViCommands
28Commands *Commands::m_instance = nullptr;
29
30bool 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
172bool 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
184KCompletion *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
201const 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
218Mappings::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
244bool 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
255SedReplace *SedReplace::m_instance = nullptr;
256
257bool 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

source code of ktexteditor/src/vimode/cmds.cpp