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 "katecmds.h"
10
11#include "kateautoindent.h"
12#include "katecmd.h"
13#include "katedocument.h"
14#include "katepartdebug.h"
15#include "katerenderer.h"
16#include "katesyntaxmanager.h"
17#include "kateview.h"
18
19#include <KLocalizedString>
20
21#include <QCollator>
22#include <QDateTime>
23#include <QRegularExpression>
24
25// BEGIN CoreCommands
26KateCommands::CoreCommands *KateCommands::CoreCommands::m_instance = nullptr;
27
28// this returns whether the string s could be converted to
29// a bool value, one of on|off|1|0|true|false. the argument val is
30// set to the extracted value in case of success
31static bool getBoolArg(const QString &t, bool *val)
32{
33 bool res(false);
34 QString s = t.toLower();
35 res = (s == QLatin1String("on") || s == QLatin1String("1") || s == QLatin1String("true"));
36 if (res) {
37 *val = true;
38 return true;
39 }
40 res = (s == QLatin1String("off") || s == QLatin1String("0") || s == QLatin1String("false"));
41 if (res) {
42 *val = false;
43 return true;
44 }
45 return false;
46}
47
48bool KateCommands::CoreCommands::help(KTextEditor::View *, const QString &cmd, QString &msg)
49{
50 QString realcmd = cmd.trimmed();
51 if (realcmd == QLatin1String("indent")) {
52 msg = i18n(
53 "<p>indent</p>"
54 "<p>Indents the selected lines or the current line</p>");
55 return true;
56 } else if (realcmd == QLatin1String("unindent")) {
57 msg = i18n(
58 "<p>unindent</p>"
59 "<p>Unindents the selected lines or current line.</p>");
60 return true;
61 } else if (realcmd == QLatin1String("cleanindent")) {
62 msg = i18n(
63 "<p>cleanindent</p>"
64 "<p>Cleans up the indentation of the selected lines or current line according to the indentation settings in the document. </p>");
65 return true;
66 } else if (realcmd == QLatin1String("comment")) {
67 msg = i18n(
68 "<p>comment</p>"
69 "<p>Inserts comment markers to make the selection or selected lines or current line a comment according to the text format as defined by the "
70 "syntax highlight definition for the document.</p>");
71 return true;
72 } else if (realcmd == QLatin1String("uncomment")) {
73 msg = i18n(
74 "<p>uncomment</p>"
75 "<p>Removes comment markers from the selection or selected lines or current line according to the text format as defined by the syntax highlight "
76 "definition for the document.</p>");
77 return true;
78 } else if (realcmd == QLatin1String("goto")) {
79 msg = i18n(
80 "<p>goto <b>line number</b></p>"
81 "<p>This command navigates to the specified line number.</p>");
82 return true;
83 } else if (realcmd == QLatin1String("set-indent-pasted-text")) {
84 msg = i18n(
85 "<p>set-indent-pasted-text <b>enable</b></p>"
86 "<p>If enabled, indentation of text pasted from the clipboard is adjusted using the current indenter.</p>"
87 "<p>Possible true values: 1 on true<br/>"
88 "possible false values: 0 off false</p>");
89 return true;
90 } else if (realcmd == QLatin1String("kill-line")) {
91 msg = i18n("Deletes the current line.");
92 return true;
93 } else if (realcmd == QLatin1String("set-tab-width")) {
94 msg = i18n(
95 "<p>set-tab-width <b>width</b></p>"
96 "<p>Sets the tab width to the number <b>width</b></p>");
97 return true;
98 } else if (realcmd == QLatin1String("set-replace-tab")) {
99 msg = i18n(
100 "<p>set-replace-tab <b>enable</b></p>"
101 "<p>If enabled, tabs are replaced with spaces as you type.</p>"
102 "<p>Possible true values: 1 on true<br/>"
103 "possible false values: 0 off false</p>");
104 return true;
105 } else if (realcmd == QLatin1String("set-show-tabs")) {
106 msg = i18n(
107 "<p>set-show-tabs <b>enable</b></p>"
108 "<p>If enabled, TAB characters and trailing whitespace will be visualized by a small dot.</p>"
109 "<p>Possible true values: 1 on true<br/>"
110 "possible false values: 0 off false</p>");
111 return true;
112 } else if (realcmd == QLatin1String("set-remove-trailing-spaces")) {
113 msg = i18n(
114 "<p>set-remove-trailing-spaces <b>mode</b></p>"
115 "<p>Removes the trailing spaces in the document depending on the <b>mode</b>.</p>"
116 "<p>Possible values:"
117 "<ul>"
118 "<li><b>none</b>: never remove trailing spaces.</li>"
119 "<li><b>modified</b>: remove trailing spaces only of modified lines.</li>"
120 "<li><b>all</b>: remove trailing spaces in the entire document.</li>"
121 "</ul></p>");
122 return true;
123 } else if (realcmd == QLatin1String("set-indent-width")) {
124 msg = i18n(
125 "<p>set-indent-width <b>width</b></p>"
126 "<p>Sets the indentation width to the number <b>width</b>. Used only if you are indenting with spaces.</p>");
127 return true;
128 } else if (realcmd == QLatin1String("set-indent-mode")) {
129 msg = i18n(
130 "<p>set-indent-mode <b>mode</b></p>"
131 "<p>The mode parameter is a value as seen in the Tools - Indentation menu</p>");
132 return true;
133 } else if (realcmd == QLatin1String("set-auto-indent")) {
134 msg = i18n(
135 "<p>set-auto-indent <b>enable</b></p>"
136 "<p>Enable or disable autoindentation.</p>"
137 "<p>possible true values: 1 on true<br/>"
138 "possible false values: 0 off false</p>");
139 return true;
140 } else if (realcmd == QLatin1String("set-line-numbers")) {
141 msg = i18n(
142 "<p>set-line-numbers <b>enable</b></p>"
143 "<p>Sets the visibility of the line numbers pane.</p>"
144 "<p> possible true values: 1 on true<br/>"
145 "possible false values: 0 off false</p>");
146 return true;
147 } else if (realcmd == QLatin1String("set-folding-markers")) {
148 msg = i18n(
149 "<p>set-folding-markers <b>enable</b></p>"
150 "<p>Sets the visibility of the folding markers pane.</p>"
151 "<p> possible true values: 1 on true<br/>"
152 "possible false values: 0 off false</p>");
153 return true;
154 } else if (realcmd == QLatin1String("set-icon-border")) {
155 msg = i18n(
156 "<p>set-icon-border <b>enable</b></p>"
157 "<p>Sets the visibility of the icon border.</p>"
158 "<p> possible true values: 1 on true<br/>"
159 "possible false values: 0 off false</p>");
160 return true;
161 } else if (realcmd == QLatin1String("set-word-wrap")) {
162 msg = i18n(
163 "<p>set-word-wrap <b>enable</b></p>"
164 "<p>Enables dynamic word wrap according to <b>enable</b></p>"
165 "<p> possible true values: 1 on true<br/>"
166 "possible false values: 0 off false</p>");
167 return true;
168 } else if (realcmd == QLatin1String("set-word-wrap-column")) {
169 msg = i18n(
170 "<p>set-word-wrap-column <b>width</b></p>"
171 "<p>Sets the line width for hard wrapping to <b>width</b>. This is used if you are having your text wrapped automatically.</p>");
172 return true;
173 } else if (realcmd == QLatin1String("set-replace-tabs-save")) {
174 msg = i18n(
175 "<p>set-replace-tabs-save <b>enable</b></p>"
176 "<p>When enabled, tabs will be replaced with whitespace whenever the document is saved.</p>"
177 "<p> possible true values: 1 on true<br/>"
178 "possible false values: 0 off false</p>");
179 return true;
180 } else if (realcmd == QLatin1String("set-highlight")) {
181 msg = i18n(
182 "<p>set-highlight <b>highlight</b></p>"
183 "<p>Sets the syntax highlighting system for the document. The argument must be a valid highlight name, as seen in the Tools → Highlighting menu. "
184 "This command provides an autocompletion list for its argument.</p>");
185 return true;
186 } else if (realcmd == QLatin1String("set-mode")) {
187 msg = i18n(
188 "<p>set-mode <b>mode</b></p>"
189 "<p>Sets the mode as seen in Tools - Mode</p>");
190 return true;
191 } else if (realcmd == QLatin1String("set-show-indent")) {
192 msg = i18n(
193 "<p>set-show-indent <b>enable</b></p>"
194 "<p>If enabled, indentation will be visualized by a vertical dotted line.</p>"
195 "<p> possible true values: 1 on true<br/>"
196 "possible false values: 0 off false</p>");
197 return true;
198 } else if (realcmd == QLatin1String("print")) {
199 msg = i18n("<p>Open the Print dialog to print the current document.</p>");
200 return true;
201 } else {
202 return false;
203 }
204}
205
206bool KateCommands::CoreCommands::exec(KTextEditor::View *view, const QString &_cmd, QString &errorMsg, const KTextEditor::Range &range)
207{
208#define KCC_ERR(s) \
209 { \
210 errorMsg = s; \
211 return false; \
212 }
213 // cast it hardcore, we know that it is really a kateview :)
214 KTextEditor::ViewPrivate *v = static_cast<KTextEditor::ViewPrivate *>(view);
215
216 if (!v) {
217 KCC_ERR(i18n("Could not access view"));
218 }
219
220 // create a list of args
221 QStringList args(_cmd.split(sep: QRegularExpression(QStringLiteral("\\s+")), behavior: Qt::SkipEmptyParts));
222 QString cmd(args.takeFirst());
223
224 // ALL commands that takes no arguments.
225 if (cmd == QLatin1String("indent")) {
226 if (range.isValid()) {
227 v->doc()->editStart();
228 for (int line = range.start().line(); line <= range.end().line(); line++) {
229 v->doc()->indent(range: KTextEditor::Range(line, 0, line, 0), change: 1);
230 }
231 v->doc()->editEnd();
232 } else {
233 v->indent();
234 }
235 return true;
236 } else if (cmd == QLatin1String("unindent")) {
237 if (range.isValid()) {
238 v->doc()->editStart();
239 for (int line = range.start().line(); line <= range.end().line(); line++) {
240 v->doc()->indent(range: KTextEditor::Range(line, 0, line, 0), change: -1);
241 }
242 v->doc()->editEnd();
243 } else {
244 v->unIndent();
245 }
246 return true;
247 } else if (cmd == QLatin1String("cleanindent")) {
248 if (range.isValid()) {
249 v->doc()->editStart();
250 for (int line = range.start().line(); line <= range.end().line(); line++) {
251 v->doc()->indent(range: KTextEditor::Range(line, 0, line, 0), change: 0);
252 }
253 v->doc()->editEnd();
254 } else {
255 v->cleanIndent();
256 }
257 return true;
258 } else if (cmd == QLatin1String("fold")) {
259 return (v->textFolding().newFoldingRange(range: range.isValid() ? range : v->selectionRange(), flags: Kate::TextFolding::Persistent | Kate::TextFolding::Folded)
260 != -1);
261 } else if (cmd == QLatin1String("tfold")) {
262 return (v->textFolding().newFoldingRange(range: range.isValid() ? range : v->selectionRange(), flags: Kate::TextFolding::Folded) != -1);
263 } else if (cmd == QLatin1String("unfold")) {
264 QList<QPair<qint64, Kate::TextFolding::FoldingRangeFlags>> startingRanges = v->textFolding().foldingRangesStartingOnLine(line: v->cursorPosition().line());
265 bool unfolded = false;
266 for (int i = 0; i < startingRanges.size(); ++i) {
267 if (startingRanges[i].second & Kate::TextFolding::Folded) {
268 unfolded = v->textFolding().unfoldRange(id: startingRanges[i].first) || unfolded;
269 }
270 }
271 return unfolded;
272 } else if (cmd == QLatin1String("comment")) {
273 if (range.isValid()) {
274 v->doc()->editStart();
275 for (int line = range.start().line(); line <= range.end().line(); line++) {
276 v->doc()->comment(view: v, line, column: 0, change: KTextEditor::DocumentPrivate::Comment);
277 }
278 v->doc()->editEnd();
279 } else {
280 v->comment();
281 }
282 return true;
283 } else if (cmd == QLatin1String("uncomment")) {
284 if (range.isValid()) {
285 v->doc()->editStart();
286 for (int line = range.start().line(); line <= range.end().line(); line++) {
287 v->doc()->comment(view: v, line, column: 0, change: KTextEditor::DocumentPrivate::UnComment);
288 }
289 v->doc()->editEnd();
290 } else {
291 v->uncomment();
292 }
293 return true;
294 } else if (cmd == QLatin1String("kill-line")) {
295 if (range.isValid()) {
296 v->doc()->editStart();
297 for (int line = range.start().line(); line <= range.end().line(); line++) {
298 v->doc()->removeLine(line: range.start().line());
299 }
300 v->doc()->editEnd();
301 } else {
302 v->killLine();
303 }
304 return true;
305 } else if (cmd == QLatin1String("print")) {
306 v->print();
307 return true;
308 }
309
310 // ALL commands that take a string argument
311 else if (cmd == QLatin1String("set-indent-mode") || cmd == QLatin1String("set-highlight") || cmd == QLatin1String("set-mode")) {
312 // need at least one item, otherwise args.first() crashes
313 if (args.isEmpty()) {
314 KCC_ERR(i18n("Missing argument. Usage: %1 <value>", cmd));
315 }
316
317 if (cmd == QLatin1String("set-indent-mode")) {
318 v->doc()->config()->setIndentationMode(args.join(sep: QLatin1Char(' ')));
319 v->doc()->rememberUserDidSetIndentationMode();
320 return true;
321 } else if (cmd == QLatin1String("set-highlight")) {
322 if (v->doc()->setHighlightingMode(args.join(sep: QLatin1Char(' ')))) {
323 static_cast<KTextEditor::DocumentPrivate *>(v->doc())->setDontChangeHlOnSave();
324 return true;
325 }
326
327 KCC_ERR(i18n("No such highlighting '%1'", args.first()));
328 } else if (cmd == QLatin1String("set-mode")) {
329 if (v->doc()->setMode(args.first())) {
330 return true;
331 }
332
333 KCC_ERR(i18n("No such mode '%1'", args.first()));
334 }
335 }
336 // ALL commands that takes exactly one integer argument.
337 else if (cmd == QLatin1String("set-tab-width") || cmd == QLatin1String("set-indent-width") || cmd == QLatin1String("set-word-wrap-column")
338 || cmd == QLatin1String("goto")) {
339 // find a integer value > 0
340 if (args.isEmpty()) {
341 KCC_ERR(i18n("Missing argument. Usage: %1 <value>", cmd));
342 }
343 bool ok;
344 int val(args.first().toInt(ok: &ok, base: 10)); // use base 10 even if the string starts with '0'
345 if (!ok)
346 KCC_ERR(i18n("Failed to convert argument '%1' to integer.", args.first()));
347
348 if (cmd == QLatin1String("set-tab-width")) {
349 if (val < 1) {
350 KCC_ERR(i18n("Width must be at least 1."));
351 }
352 v->doc()->config()->setTabWidth(val);
353 } else if (cmd == QLatin1String("set-indent-width")) {
354 if (val < 1) {
355 KCC_ERR(i18n("Width must be at least 1."));
356 }
357 v->doc()->config()->setIndentationWidth(val);
358 } else if (cmd == QLatin1String("set-word-wrap-column")) {
359 if (val < 2) {
360 KCC_ERR(i18n("Column must be at least 1."));
361 }
362 v->doc()->setWordWrapAt(val);
363 } else if (cmd == QLatin1String("goto")) {
364 if (args.first().at(i: 0) == QLatin1Char('-') || args.first().at(i: 0) == QLatin1Char('+')) {
365 // if the number starts with a minus or plus sign, add/subtract the number
366 val = v->cursorPosition().line() + val;
367 } else {
368 val--; // convert given line number to the internal representation of line numbers
369 }
370
371 // constrain cursor to the range [0, number of lines]
372 if (val < 0) {
373 val = 0;
374 } else if (val > v->doc()->lines() - 1) {
375 val = v->doc()->lines() - 1;
376 }
377
378 v->setCursorPosition(KTextEditor::Cursor(val, 0));
379 return true;
380 }
381 return true;
382 }
383
384 // ALL commands that takes 1 boolean argument.
385 else if (cmd == QLatin1String("set-icon-border") || cmd == QLatin1String("set-folding-markers") || cmd == QLatin1String("set-indent-pasted-text")
386 || cmd == QLatin1String("set-line-numbers") || cmd == QLatin1String("set-replace-tabs") || cmd == QLatin1String("set-show-tabs")
387 || cmd == QLatin1String("set-word-wrap") || cmd == QLatin1String("set-wrap-cursor") || cmd == QLatin1String("set-replace-tabs-save")
388 || cmd == QLatin1String("set-show-indent")) {
389 if (args.isEmpty()) {
390 KCC_ERR(i18n("Usage: %1 on|off|1|0|true|false", cmd));
391 }
392 bool enable = false;
393 KateDocumentConfig *const config = v->doc()->config();
394 if (getBoolArg(t: args.first(), val: &enable)) {
395 if (cmd == QLatin1String("set-icon-border")) {
396 v->setIconBorder(enable);
397 } else if (cmd == QLatin1String("set-folding-markers")) {
398 v->setFoldingMarkersOn(enable);
399 } else if (cmd == QLatin1String("set-line-numbers")) {
400 v->setLineNumbersOn(enable);
401 } else if (cmd == QLatin1String("set-show-indent")) {
402 v->renderer()->setShowIndentLines(enable);
403 } else if (cmd == QLatin1String("set-indent-pasted-text")) {
404 config->setIndentPastedText(enable);
405 } else if (cmd == QLatin1String("set-replace-tabs")) {
406 config->setReplaceTabsDyn(enable);
407 } else if (cmd == QLatin1String("set-show-tabs")) {
408 config->setShowTabs(enable);
409 } else if (cmd == QLatin1String("set-show-trailing-spaces")) {
410 config->setShowSpaces(enable ? KateDocumentConfig::Trailing : KateDocumentConfig::None);
411 } else if (cmd == QLatin1String("set-word-wrap")) {
412 v->doc()->setWordWrap(enable);
413 }
414
415 return true;
416 } else
417 KCC_ERR(i18n("Bad argument '%1'. Usage: %2 on|off|1|0|true|false", args.first(), cmd));
418 } else if (cmd == QLatin1String("set-remove-trailing-spaces")) {
419 // need at least one item, otherwise args.first() crashes
420 if (args.count() != 1) {
421 KCC_ERR(i18n("Usage: set-remove-trailing-spaces 0|-|none or 1|+|mod|modified or 2|*|all"));
422 }
423
424 QString tmp = args.first().toLower().trimmed();
425 if (tmp == QLatin1String("1") || tmp == QLatin1String("modified") || tmp == QLatin1String("mod") || tmp == QLatin1String("+")) {
426 v->doc()->config()->setRemoveSpaces(1);
427 } else if (tmp == QLatin1String("2") || tmp == QLatin1String("all") || tmp == QLatin1String("*")) {
428 v->doc()->config()->setRemoveSpaces(2);
429 } else {
430 v->doc()->config()->setRemoveSpaces(0);
431 }
432 }
433
434 // unlikely..
435 KCC_ERR(i18n("Unknown command '%1'", cmd));
436}
437
438bool KateCommands::CoreCommands::supportsRange(const QString &range)
439{
440 static QStringList l;
441
442 if (l.isEmpty()) {
443 l << QStringLiteral("indent") << QStringLiteral("unindent") << QStringLiteral("cleanindent") << QStringLiteral("comment") << QStringLiteral("uncomment")
444 << QStringLiteral("kill-line") << QStringLiteral("fold") << QStringLiteral("tfold");
445 }
446
447 return l.contains(str: range);
448}
449
450KCompletion *KateCommands::CoreCommands::completionObject(KTextEditor::View *view, const QString &cmd)
451{
452 Q_UNUSED(view)
453
454 if (cmd == QLatin1String("set-highlight")) {
455 QStringList l;
456 l.reserve(asize: KateHlManager::self()->modeList().size());
457 const auto modeList = KateHlManager::self()->modeList();
458 for (const auto &hl : modeList) {
459 l << hl.name();
460 }
461
462 KateCmdShellCompletion *co = new KateCmdShellCompletion();
463 co->setItems(l);
464 co->setIgnoreCase(true);
465 return co;
466 } else if (cmd == QLatin1String("set-remove-trailing-spaces")) {
467 QStringList l;
468 l << QStringLiteral("none") << QStringLiteral("modified") << QStringLiteral("all");
469
470 KateCmdShellCompletion *co = new KateCmdShellCompletion();
471 co->setItems(l);
472 co->setIgnoreCase(true);
473 return co;
474 } else if (cmd == QLatin1String("set-indent-mode")) {
475 QStringList l = KateAutoIndent::listIdentifiers();
476 KateCmdShellCompletion *co = new KateCmdShellCompletion();
477 co->setItems(l);
478 co->setIgnoreCase(true);
479 return co;
480 }
481
482 return nullptr;
483}
484// END CoreCommands
485
486// BEGIN Character
487KateCommands::Character *KateCommands::Character::m_instance = nullptr;
488
489bool KateCommands::Character::help(class KTextEditor::View *, const QString &cmd, QString &msg)
490{
491 if (cmd.trimmed() == QLatin1String("char")) {
492 msg = i18n(
493 "<p> char <b>identifier</b> </p>"
494 "<p>This command allows you to insert literal characters by their numerical identifier, in decimal, octal or hexadecimal form.</p>"
495 "<p>Examples:<ul>"
496 "<li>char <b>234</b></li>"
497 "<li>char <b>0x1234</b></li>"
498 "</ul></p>");
499 return true;
500 }
501 return false;
502}
503
504bool KateCommands::Character::exec(KTextEditor::View *view, const QString &_cmd, QString &, const KTextEditor::Range &)
505{
506 QString cmd = _cmd;
507
508 // hex, octal, base 9+1
509 static const QRegularExpression num(QStringLiteral("^char *(0?x[0-9A-Fa-f]{1,4}|0[0-7]{1,6}|[0-9]{1,5})$"));
510 const QRegularExpressionMatch match = num.match(subject: cmd);
511 if (!match.hasMatch()) {
512 return false;
513 }
514
515 cmd = match.captured(nth: 1);
516
517 // identify the base
518
519 unsigned short int number = 0;
520 int base = 10;
521 if (cmd.startsWith(c: QLatin1Char('x'))) {
522 cmd.remove(i: 0, len: 1);
523 base = 16;
524 } else if (cmd.startsWith(s: QLatin1String("0x"))) {
525 cmd.remove(i: 0, len: 2);
526 base = 16;
527 } else if (cmd[0] == QLatin1Char('0')) {
528 base = 8;
529 }
530 bool ok;
531 number = cmd.toUShort(ok: &ok, base);
532 if (!ok || number == 0) {
533 return false;
534 }
535 if (number <= 255) {
536 char buf[2];
537 buf[0] = (char)number;
538 buf[1] = 0;
539
540 view->document()->insertText(position: view->cursorPosition(), text: QString::fromLatin1(ba: buf));
541 } else {
542 // do the unicode thing
543 QChar c(number);
544
545 view->document()->insertText(position: view->cursorPosition(), text: QString(&c, 1));
546 }
547
548 return true;
549}
550
551// END Character
552
553// BEGIN Date
554KateCommands::Date *KateCommands::Date::m_instance = nullptr;
555
556bool KateCommands::Date::help(class KTextEditor::View *, const QString &cmd, QString &msg)
557{
558 if (cmd.trimmed() == QLatin1String("date")) {
559 msg = i18n(
560 "<p>date or date <b>format</b></p>"
561 "<p>Inserts a date/time string as defined by the specified format, or the format yyyy-MM-dd hh:mm:ss if none is specified.</p>"
562 "<p>Possible format specifiers are:"
563 "<table>"
564 "<tr><td>d</td><td>The day as number without a leading zero (1-31).</td></tr>"
565 "<tr><td>dd</td><td>The day as number with a leading zero (01-31).</td></tr>"
566 "<tr><td>ddd</td><td>The abbreviated localized day name (e.g. 'Mon'..'Sun').</td></tr>"
567 "<tr><td>dddd</td><td>The long localized day name (e.g. 'Monday'..'Sunday').</td></tr>"
568 "<tr><td>M</td><td>The month as number without a leading zero (1-12).</td></tr>"
569 "<tr><td>MM</td><td>The month as number with a leading zero (01-12).</td></tr>"
570 "<tr><td>MMM</td><td>The abbreviated localized month name (e.g. 'Jan'..'Dec').</td></tr>"
571 "<tr><td>yy</td><td>The year as two digit number (00-99).</td></tr>"
572 "<tr><td>yyyy</td><td>The year as four digit number (1752-8000).</td></tr>"
573 "<tr><td>h</td><td>The hour without a leading zero (0..23 or 1..12 if AM/PM display).</td></tr>"
574 "<tr><td>hh</td><td>The hour with a leading zero (00..23 or 01..12 if AM/PM display).</td></tr>"
575 "<tr><td>m</td><td>The minute without a leading zero (0..59).</td></tr>"
576 "<tr><td>mm</td><td>The minute with a leading zero (00..59).</td></tr>"
577 "<tr><td>s</td><td>The second without a leading zero (0..59).</td></tr>"
578 "<tr><td>ss</td><td>The second with a leading zero (00..59).</td></tr>"
579 "<tr><td>z</td><td>The milliseconds without leading zeroes (0..999).</td></tr>"
580 "<tr><td>zzz</td><td>The milliseconds with leading zeroes (000..999).</td></tr>"
581 "<tr><td>AP</td><td>Use AM/PM display. AP will be replaced by either \"AM\" or \"PM\".</td></tr>"
582 "<tr><td>ap</td><td>Use am/pm display. ap will be replaced by either \"am\" or \"pm\".</td></tr>"
583 "</table></p>");
584 return true;
585 }
586 return false;
587}
588
589bool KateCommands::Date::exec(KTextEditor::View *view, const QString &cmd, QString &, const KTextEditor::Range &)
590{
591 if (!cmd.startsWith(s: QLatin1String("date"))) {
592 return false;
593 }
594
595 if (QDateTime::currentDateTime().toString(format: cmd.mid(position: 5, n: cmd.length() - 5)).length() > 0) {
596 view->document()->insertText(position: view->cursorPosition(), text: QDateTime::currentDateTime().toString(format: cmd.mid(position: 5, n: cmd.length() - 5)));
597 } else {
598 view->document()->insertText(position: view->cursorPosition(), text: QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd hh:mm:ss")));
599 }
600
601 return true;
602}
603
604// END Date
605
606KateCommands::EditingCommands *KateCommands::EditingCommands::s_instance = nullptr;
607
608KateCommands::EditingCommands::EditingCommands()
609 : KTextEditor::Command({
610 QStringLiteral("uniq"),
611 QStringLiteral("sortuniq"),
612 QStringLiteral("natsort"),
613 QStringLiteral("sort"),
614 })
615{
616}
617
618QList<KateCommands::EditingCommands::EditingCommand> KateCommands::EditingCommands::allCommands()
619{
620 static QList<EditingCommand> cmds{
621 {.name = i18n("Remove Duplicate Lines"), .cmd = QStringLiteral("uniq")},
622 {.name = i18n("Remove Duplicates and Sort Text Alphabetically"), .cmd = QStringLiteral("sortuniq")},
623 {.name = i18n("Sort Text Naturally"), .cmd = QStringLiteral("natsort")},
624 {.name = i18n("Sort Selected Text Alphabetically"), .cmd = QStringLiteral("sort")},
625 };
626 return cmds;
627}
628
629bool KateCommands::EditingCommands::exec(KTextEditor::View *view, const QString &cmd, QString &, const KTextEditor::Range &)
630{
631 auto getDocAndRange = [view]() -> std::pair<KTextEditor::Document *, KTextEditor::Range> {
632 KTextEditor::Document *doc = view->document();
633 if (view->selection()) {
634 KTextEditor::Range range = view->selectionRange();
635 auto start = range.start();
636 auto end = range.end();
637 start.setColumn(0);
638 end.setColumn(doc->lineLength(line: end.line()));
639 range.setRange(start, end);
640 return {doc, range};
641 }
642 return {doc, doc->documentRange()};
643 };
644
645 auto apply = [](KTextEditor::Range range, KTextEditor::Document *doc, const QStringList &lines) {
646 if (range == doc->documentRange()) {
647 doc->setText(lines);
648 } else {
649 doc->replaceText(range, text: lines);
650 }
651 };
652
653 if (cmd == QStringLiteral("uniq")) {
654 const auto [doc, range] = getDocAndRange();
655 const QStringList lines = doc->textLines(range);
656 QSet<QString> seenLines;
657 QStringList uniqueLines;
658 for (const auto &line : lines) {
659 auto it = seenLines.find(value: line);
660 if (it == seenLines.end()) {
661 seenLines.insert(value: line);
662 uniqueLines.push_back(t: line);
663 }
664 }
665 apply(range, doc, uniqueLines);
666 return true;
667 } else if (cmd == QStringLiteral("sortuniq")) {
668 const auto [doc, range] = getDocAndRange();
669 QStringList lines = doc->textLines(range);
670 std::sort(first: lines.begin(), last: lines.end());
671 auto it = std::unique(first: lines.begin(), last: lines.end());
672 if (it != lines.end()) {
673 lines.erase(abegin: it, aend: lines.end());
674 }
675 apply(range, doc, lines);
676 return true;
677 } else if (cmd == QStringLiteral("natsort")) {
678 const auto [doc, range] = getDocAndRange();
679 QStringList lines = doc->textLines(range);
680 QCollator col;
681 col.setNumericMode(true);
682 std::sort(first: lines.begin(), last: lines.end(), comp: col);
683 apply(range, doc, lines);
684 return true;
685 } else if (cmd == QStringLiteral("sort")) {
686 const auto [doc, range] = getDocAndRange();
687 QStringList lines = doc->textLines(range);
688 std::sort(first: lines.begin(), last: lines.end());
689 apply(range, doc, lines);
690 return true;
691 }
692
693 return false;
694}
695bool KateCommands::EditingCommands::help(class KTextEditor::View *, const QString &cmd, QString &msg)
696{
697 if (cmd == QStringLiteral("sort")) {
698 msg = i18n(
699 "<p>sort</p>"
700 "<p>Sort the selected text or whole document if there is no selection</p>");
701 } else if (cmd == QStringLiteral("uniq")) {
702 msg = i18n(
703 "<p>uniq</p>"
704 "<p>Remove duplicate lines from the selected text or whole document if there is no selection.</p>");
705 } else if (cmd == QStringLiteral("sortuniq")) {
706 msg = i18n(
707 "<p>sortuniq</p>"
708 "<p>Sort the selected text or whole document and then remove all duplicate lines.</p>");
709 } else if (cmd == QStringLiteral("natsort")) {
710 msg = i18n(
711 "<p>natsort</p>"
712 "<p>Sort the selected text or whole document in natural order.<br>Here is an example to show the difference to the normal sort "
713 "method:<br>sort(a10, a1, a2) => a1, a10, a2<br>natsort(a10, a1, a2) => a1, a2, a10</p>");
714 } else {
715 return false;
716 }
717 return true;
718}
719

source code of ktexteditor/src/utils/katecmds.cpp