File indexing completed on 2024-04-21 03:58:05

0001 /*
0002     SPDX-FileCopyrightText: 2009 Erlend Hamberg <ehamberg@gmail.com>
0003     SPDX-FileCopyrightText: 2011 Svyatoslav Kuzmich <svatoslav1@gmail.com>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include <QDir>
0009 #include <QTimer>
0010 
0011 #include <KLocalizedString>
0012 #include <KTextEditor/Application>
0013 #include <KTextEditor/Document>
0014 #include <KTextEditor/Editor>
0015 #include <KTextEditor/MainWindow>
0016 #include <KTextEditor/View>
0017 
0018 #include <vimode/appcommands.h>
0019 
0020 using namespace KateVi;
0021 
0022 // BEGIN AppCommands
0023 AppCommands *AppCommands::m_instance = nullptr;
0024 
0025 AppCommands::AppCommands()
0026     : KTextEditor::Command({QStringLiteral("q"),      QStringLiteral("qa"),      QStringLiteral("qall"), QStringLiteral("q!"),       QStringLiteral("qa!"),
0027                             QStringLiteral("qall!"),  QStringLiteral("w"),       QStringLiteral("wq"),   QStringLiteral("wa"),       QStringLiteral("wqa"),
0028                             QStringLiteral("x"),      QStringLiteral("xa"),      QStringLiteral("new"),  QStringLiteral("vnew"),     QStringLiteral("e"),
0029                             QStringLiteral("edit"),   QStringLiteral("enew"),    QStringLiteral("sp"),   QStringLiteral("split"),    QStringLiteral("vs"),
0030                             QStringLiteral("vsplit"), QStringLiteral("only"),    QStringLiteral("tabe"), QStringLiteral("tabedit"),  QStringLiteral("tabnew"),
0031                             QStringLiteral("bd"),     QStringLiteral("bdelete"), QStringLiteral("tabc"), QStringLiteral("tabclose"), QStringLiteral("clo"),
0032                             QStringLiteral("close")})
0033     , re_write(QStringLiteral("^w(a)?$"))
0034     , re_close(QStringLiteral("^bd(elete)?|tabc(lose)?$"))
0035     , re_quit(QStringLiteral("^(w)?q(a|all)?(!)?$"))
0036     , re_exit(QStringLiteral("^x(a)?$"))
0037     , re_edit(QStringLiteral("^e(dit)?|tabe(dit)?|tabnew$"))
0038     , re_tabedit(QStringLiteral("^tabe(dit)?|tabnew$"))
0039     , re_new(QStringLiteral("^(v)?new$"))
0040     , re_split(QStringLiteral("^sp(lit)?$"))
0041     , re_vsplit(QStringLiteral("^vs(plit)?$"))
0042     , re_vclose(QStringLiteral("^clo(se)?$"))
0043     , re_only(QStringLiteral("^on(ly)?$"))
0044 {
0045 }
0046 
0047 AppCommands::~AppCommands()
0048 {
0049     m_instance = nullptr;
0050 }
0051 
0052 bool AppCommands::exec(KTextEditor::View *view, const QString &cmd, QString &msg, const KTextEditor::Range &)
0053 {
0054     QStringList args(cmd.split(QRegularExpression(QStringLiteral("\\s+")), Qt::SkipEmptyParts));
0055     QString command(args.takeFirst());
0056 
0057     KTextEditor::MainWindow *mainWin = view->mainWindow();
0058     KTextEditor::Application *app = KTextEditor::Editor::instance()->application();
0059 
0060     QRegularExpressionMatch match;
0061     if ((match = re_write.match(command)).hasMatch()) { // TODO: handle writing to specific file
0062         if (!match.captured(1).isEmpty()) { // [a]ll
0063             const auto docs = app->documents();
0064             for (KTextEditor::Document *doc : docs) {
0065                 doc->save();
0066             }
0067             msg = i18n("All documents written to disk");
0068         } else {
0069             view->document()->documentSave();
0070             msg = i18n("Document written to disk");
0071         }
0072     }
0073     // Other buffer commands are implemented by the KateFileTree plugin
0074     else if ((match = re_close.match(command)).hasMatch()) {
0075         QTimer::singleShot(0, view, [app, view]() {
0076             app->closeDocument(view->document());
0077         });
0078     } else if ((match = re_quit.match(command)).hasMatch()) {
0079         const bool save = !match.captured(1).isEmpty(); // :[w]q
0080         const bool allDocuments = !match.captured(2).isEmpty(); // :q[all]
0081         const bool doNotPromptForSave = !match.captured(3).isEmpty(); // :q[!]
0082 
0083         if (allDocuments) {
0084             if (save) {
0085                 const auto docs = app->documents();
0086                 for (KTextEditor::Document *doc : docs) {
0087                     doc->save();
0088                 }
0089             }
0090 
0091             if (doNotPromptForSave) {
0092                 const auto docs = app->documents();
0093                 for (KTextEditor::Document *doc : docs) {
0094                     if (doc->isModified()) {
0095                         doc->setModified(false);
0096                     }
0097                 }
0098             }
0099 
0100             QTimer::singleShot(0, this, [this, app]() {
0101                 closeDocuments(app->documents());
0102             });
0103         } else {
0104             if (save && view->document()->isModified()) {
0105                 view->document()->documentSave();
0106             }
0107 
0108             if (doNotPromptForSave) {
0109                 view->document()->setModified(false);
0110             }
0111 
0112             if (mainWin->views().size() > 1) {
0113                 QTimer::singleShot(0, this, &AppCommands::closeCurrentView);
0114             } else {
0115                 Q_ASSERT(app->documents().size() > 0);
0116                 QTimer::singleShot(0, this, &AppCommands::closeCurrentDocument);
0117             }
0118         }
0119     } else if ((match = re_exit.match(command)).hasMatch()) {
0120         if (!match.captured(1).isEmpty()) { // a[ll]
0121             const auto docs = app->documents();
0122             for (KTextEditor::Document *doc : docs) {
0123                 doc->save();
0124             }
0125             QTimer::singleShot(0, this, &AppCommands::quit);
0126         } else {
0127             if (view->document()->isModified()) {
0128                 view->document()->documentSave();
0129             }
0130 
0131             if (app->documents().size() > 1) {
0132                 QTimer::singleShot(0, this, &AppCommands::closeCurrentDocument);
0133             } else {
0134                 QTimer::singleShot(0, this, &AppCommands::quit);
0135             }
0136         }
0137     } else if ((match = re_edit.match(command)).hasMatch()) {
0138         QString argument = args.join(QLatin1Char(' '));
0139         if (argument.isEmpty() || argument == QLatin1String("!")) {
0140             if ((match = re_tabedit.match(command)).hasMatch()) {
0141                 if (auto doc = app->openUrl(QUrl())) {
0142                     QTimer::singleShot(0, [mainWin, doc]() {
0143                         mainWin->activateView(doc);
0144                     });
0145                 }
0146             } else {
0147                 view->document()->documentReload();
0148             }
0149         } else {
0150             QUrl base = view->document()->url();
0151             QUrl url;
0152             QUrl arg2path(argument);
0153             if (base.isValid()) { // first try to use the same path as the current open document has
0154                 url =
0155                     QUrl(base.resolved(arg2path)); // resolved handles the case where the args is a relative path, and is the same as using QUrl(args) elsewise
0156             } else { // else use the cwd
0157                 url = QUrl(QUrl::fromLocalFile(QDir::currentPath() + QLatin1Char('/'))
0158                                .resolved(arg2path)); // + "/" is needed because of https://lists.qt-project.org/pipermail/qt-interest-old/2011-May/033913.html
0159             }
0160 
0161             // either find existing document or just open it, openUrl will take care of non-existing files, too
0162             KTextEditor::Document *doc = app->findUrl(url);
0163             if (!doc) {
0164                 doc = app->openUrl(url);
0165             }
0166             if (doc) {
0167                 QTimer::singleShot(0, [mainWin, doc]() {
0168                     mainWin->activateView(doc);
0169                 });
0170             }
0171         }
0172         // splitView() orientations are reversed from the usual editor convention.
0173         // 'vsplit' and 'vnew' use Qt::Horizontal to match vi and the Kate UI actions.
0174     } else if ((match = re_new.match(command)).hasMatch()) {
0175         if (match.captured(1) == QLatin1String("v")) { // vertical split
0176             mainWin->splitView(Qt::Horizontal);
0177         } else { // horizontal split
0178             mainWin->splitView(Qt::Vertical);
0179         }
0180         mainWin->openUrl(QUrl());
0181     } else if (command == QLatin1String("enew")) {
0182         mainWin->openUrl(QUrl());
0183     } else if ((match = re_split.match(command)).hasMatch()) {
0184         mainWin->splitView(Qt::Vertical); // see above
0185     } else if ((match = re_vsplit.match(command)).hasMatch()) {
0186         mainWin->splitView(Qt::Horizontal);
0187     } else if ((match = re_vclose.match(command)).hasMatch()) {
0188         QTimer::singleShot(0, this, &AppCommands::closeCurrentSplitView);
0189     } else if ((match = re_only.match(command)).hasMatch()) {
0190         QTimer::singleShot(0, this, &AppCommands::closeOtherSplitViews);
0191     }
0192 
0193     return true;
0194 }
0195 
0196 bool AppCommands::help(KTextEditor::View *view, const QString &cmd, QString &msg)
0197 {
0198     Q_UNUSED(view);
0199 
0200     if (re_write.match(cmd).hasMatch()) {
0201         msg = i18n(
0202             "<p><b>w/wa &mdash; write document(s) to disk</b></p>"
0203             "<p>Usage: <tt><b>w[a]</b></tt></p>"
0204             "<p>Writes the current document(s) to disk. "
0205             "It can be called in two ways:<br />"
0206             " <tt>w</tt> &mdash; writes the current document to disk<br />"
0207             " <tt>wa</tt> &mdash; writes all documents to disk.</p>"
0208             "<p>If no file name is associated with the document, "
0209             "a file dialog will be shown.</p>");
0210         return true;
0211     } else if (re_quit.match(cmd).hasMatch()) {
0212         msg = i18n(
0213             "<p><b>q/qa/wq/wqa &mdash; [write and] quit</b></p>"
0214             "<p>Usage: <tt><b>[w]q[a]</b></tt></p>"
0215             "<p>Quits the application. If <tt>w</tt> is prepended, it also writes"
0216             " the document(s) to disk. This command "
0217             "can be called in several ways:<br />"
0218             " <tt>q</tt> &mdash; closes the current view.<br />"
0219             " <tt>qa</tt> &mdash; closes all views, effectively quitting the application.<br />"
0220             " <tt>wq</tt> &mdash; writes the current document to disk and closes its view.<br />"
0221             " <tt>wqa</tt> &mdash; writes all documents to disk and quits.</p>"
0222             "<p>In all cases, if the view being closed is the last view, the application quits. "
0223             "If no file name is associated with the document and it should be written to disk, "
0224             "a file dialog will be shown.</p>");
0225         return true;
0226     } else if (re_exit.match(cmd).hasMatch()) {
0227         msg = i18n(
0228             "<p><b>x/xa &mdash; write and quit</b></p>"
0229             "<p>Usage: <tt><b>x[a]</b></tt></p>"
0230             "<p>Saves document(s) and quits (e<b>x</b>its). This command "
0231             "can be called in two ways:<br />"
0232             " <tt>x</tt> &mdash; closes the current view.<br />"
0233             " <tt>xa</tt> &mdash; closes all views, effectively quitting the application.</p>"
0234             "<p>In all cases, if the view being closed is the last view, the application quits. "
0235             "If no file name is associated with the document and it should be written to disk, "
0236             "a file dialog will be shown.</p>"
0237             "<p>Unlike the 'w' commands, this command only writes the document if it is modified."
0238             "</p>");
0239         return true;
0240     } else if (re_split.match(cmd).hasMatch()) {
0241         msg = i18n(
0242             "<p><b>sp,split&mdash; Split horizontally the current view into two</b></p>"
0243             "<p>Usage: <tt><b>sp[lit]</b></tt></p>"
0244             "<p>The result is two views on the same document.</p>");
0245         return true;
0246     } else if (re_vsplit.match(cmd).hasMatch()) {
0247         msg = i18n(
0248             "<p><b>vs,vsplit&mdash; Split vertically the current view into two</b></p>"
0249             "<p>Usage: <tt><b>vs[plit]</b></tt></p>"
0250             "<p>The result is two views on the same document.</p>");
0251         return true;
0252     } else if (re_vclose.match(cmd).hasMatch()) {
0253         msg = i18n(
0254             "<p><b>clo[se]&mdash; Close the current view</b></p>"
0255             "<p>Usage: <tt><b>clo[se]</b></tt></p>"
0256             "<p>After executing it, the current view will be closed.</p>");
0257         return true;
0258     } else if (re_new.match(cmd).hasMatch()) {
0259         msg = i18n(
0260             "<p><b>[v]new &mdash; split view and create new document</b></p>"
0261             "<p>Usage: <tt><b>[v]new</b></tt></p>"
0262             "<p>Splits the current view and opens a new document in the new view."
0263             " This command can be called in two ways:<br />"
0264             " <tt>new</tt> &mdash; splits the view horizontally and opens a new document.<br />"
0265             " <tt>vnew</tt> &mdash; splits the view vertically and opens a new document.<br />"
0266             "</p>");
0267         return true;
0268     } else if (re_edit.match(cmd).hasMatch()) {
0269         msg = i18n(
0270             "<p><b>e[dit] &mdash; reload current document</b></p>"
0271             "<p>Usage: <tt><b>e[dit]</b></tt></p>"
0272             "<p>Starts <b>e</b>diting the current document again. This is useful to re-edit"
0273             " the current file, when it was modified on disk.</p>");
0274         return true;
0275     }
0276 
0277     return false;
0278 }
0279 
0280 KTextEditor::View *AppCommands::findViewInDifferentSplitView(KTextEditor::MainWindow *window, KTextEditor::View *view)
0281 {
0282     const auto views = window->views();
0283     for (KTextEditor::View *it : views) {
0284         if (!window->viewsInSameSplitView(it, view)) {
0285             return it;
0286         }
0287     }
0288     return nullptr;
0289 }
0290 
0291 void AppCommands::closeDocuments(const QList<KTextEditor::Document *> &documents)
0292 {
0293     KTextEditor::Application *app = KTextEditor::Editor::instance()->application();
0294     QTimer::singleShot(0, app, [app, documents]() {
0295         app->closeDocuments(documents);
0296     });
0297 }
0298 
0299 void AppCommands::closeCurrentDocument()
0300 {
0301     KTextEditor::Application *app = KTextEditor::Editor::instance()->application();
0302     KTextEditor::Document *doc = app->activeMainWindow()->activeView()->document();
0303     QTimer::singleShot(0, doc, [app, doc]() {
0304         app->closeDocument(doc);
0305     });
0306 }
0307 
0308 void AppCommands::closeCurrentView()
0309 {
0310     KTextEditor::Application *app = KTextEditor::Editor::instance()->application();
0311     KTextEditor::MainWindow *mw = app->activeMainWindow();
0312     mw->closeView(mw->activeView());
0313 }
0314 
0315 void AppCommands::closeCurrentSplitView()
0316 {
0317     KTextEditor::Application *app = KTextEditor::Editor::instance()->application();
0318     KTextEditor::MainWindow *mw = app->activeMainWindow();
0319     mw->closeSplitView(mw->activeView());
0320 }
0321 
0322 void AppCommands::closeOtherSplitViews()
0323 {
0324     KTextEditor::Application *app = KTextEditor::Editor::instance()->application();
0325     KTextEditor::MainWindow *mw = app->activeMainWindow();
0326     KTextEditor::View *view = mw->activeView();
0327     KTextEditor::View *viewToRemove = nullptr;
0328 
0329     while ((viewToRemove = findViewInDifferentSplitView(mw, view))) {
0330         mw->closeSplitView(viewToRemove);
0331     }
0332 }
0333 
0334 void AppCommands::quit()
0335 {
0336     KTextEditor::Editor::instance()->application()->quit();
0337 }
0338 
0339 // END AppCommands
0340 
0341 // BEGIN KateViBufferCommand
0342 BufferCommands *BufferCommands::m_instance = nullptr;
0343 
0344 BufferCommands::BufferCommands()
0345     : KTextEditor::Command({QStringLiteral("ls"),
0346                             QStringLiteral("b"),
0347                             QStringLiteral("buffer"),
0348                             QStringLiteral("bn"),
0349                             QStringLiteral("bnext"),
0350                             QStringLiteral("bp"),
0351                             QStringLiteral("bprevious"),
0352                             QStringLiteral("tabn"),
0353                             QStringLiteral("tabnext"),
0354                             QStringLiteral("tabp"),
0355                             QStringLiteral("tabprevious"),
0356                             QStringLiteral("bf"),
0357                             QStringLiteral("bfirst"),
0358                             QStringLiteral("bl"),
0359                             QStringLiteral("blast"),
0360                             QStringLiteral("tabf"),
0361                             QStringLiteral("tabfirst"),
0362                             QStringLiteral("tabl"),
0363                             QStringLiteral("tablast")})
0364 {
0365 }
0366 
0367 BufferCommands::~BufferCommands()
0368 {
0369     m_instance = nullptr;
0370 }
0371 
0372 bool BufferCommands::exec(KTextEditor::View *view, const QString &cmd, QString &, const KTextEditor::Range &)
0373 {
0374     // create list of args
0375     QStringList args(cmd.split(QLatin1Char(' '), Qt::KeepEmptyParts));
0376     QString command = args.takeFirst(); // same as cmd if split failed
0377     QString argument = args.join(QLatin1Char(' '));
0378 
0379     if (command == QLatin1String("ls")) {
0380         // TODO: open quickview
0381     } else if (command == QLatin1String("b") || command == QLatin1String("buffer")) {
0382         switchDocument(view, argument);
0383     } else if (command == QLatin1String("bp") || command == QLatin1String("bprevious")) {
0384         prevBuffer(view);
0385     } else if (command == QLatin1String("bn") || command == QLatin1String("bnext")) {
0386         nextBuffer(view);
0387     } else if (command == QLatin1String("bf") || command == QLatin1String("bfirst")) {
0388         firstBuffer(view);
0389     } else if (command == QLatin1String("bl") || command == QLatin1String("blast")) {
0390         lastBuffer(view);
0391     } else if (command == QLatin1String("tabn") || command == QLatin1String("tabnext")) {
0392         nextTab(view);
0393     } else if (command == QLatin1String("tabp") || command == QLatin1String("tabprevious")) {
0394         prevTab(view);
0395     } else if (command == QLatin1String("tabf") || command == QLatin1String("tabfirst")) {
0396         firstTab(view);
0397     } else if (command == QLatin1String("tabl") || command == QLatin1String("tablast")) {
0398         lastTab(view);
0399     }
0400     return true;
0401 }
0402 
0403 void BufferCommands::switchDocument(KTextEditor::View *view, const QString &address)
0404 {
0405     if (address.isEmpty()) {
0406         // no argument: switch to the previous document
0407         prevBuffer(view);
0408         return;
0409     }
0410 
0411     const int idx = address.toInt();
0412     QList<KTextEditor::Document *> docs = documents();
0413 
0414     if (idx > 0 && idx <= docs.size()) {
0415         // numerical argument: switch to the nth document
0416         activateDocument(view, docs.at(idx - 1));
0417     } else {
0418         // string argument: switch to the given file
0419         KTextEditor::Document *doc = nullptr;
0420 
0421         for (KTextEditor::Document *it : docs) {
0422             if (it->documentName() == address) {
0423                 doc = it;
0424                 break;
0425             }
0426         }
0427 
0428         if (doc) {
0429             activateDocument(view, doc);
0430         }
0431     }
0432 }
0433 
0434 void BufferCommands::prevBuffer(KTextEditor::View *view)
0435 {
0436     const QList<KTextEditor::Document *> docs = documents();
0437     const int idx = docs.indexOf(view->document());
0438 
0439     if (idx > 0) {
0440         activateDocument(view, docs.at(idx - 1));
0441     } else if (!docs.isEmpty()) { // wrap
0442         activateDocument(view, docs.last());
0443     }
0444 }
0445 
0446 void BufferCommands::nextBuffer(KTextEditor::View *view)
0447 {
0448     QList<KTextEditor::Document *> docs = documents();
0449     const int idx = docs.indexOf(view->document());
0450 
0451     if (idx + 1 < docs.size()) {
0452         activateDocument(view, docs.at(idx + 1));
0453     } else if (!docs.isEmpty()) { // wrap
0454         activateDocument(view, docs.first());
0455     }
0456 }
0457 
0458 void BufferCommands::firstBuffer(KTextEditor::View *view)
0459 {
0460     auto docs = documents();
0461     if (!docs.isEmpty()) {
0462         activateDocument(view, documents().at(0));
0463     }
0464 }
0465 
0466 void BufferCommands::lastBuffer(KTextEditor::View *view)
0467 {
0468     auto docs = documents();
0469     if (!docs.isEmpty()) {
0470         activateDocument(view, documents().last());
0471     }
0472 }
0473 
0474 void BufferCommands::prevTab(KTextEditor::View *view)
0475 {
0476     prevBuffer(view); // TODO: implement properly, when interface is added
0477 }
0478 
0479 void BufferCommands::nextTab(KTextEditor::View *view)
0480 {
0481     nextBuffer(view); // TODO: implement properly, when interface is added
0482 }
0483 
0484 void BufferCommands::firstTab(KTextEditor::View *view)
0485 {
0486     firstBuffer(view); // TODO: implement properly, when interface is added
0487 }
0488 
0489 void BufferCommands::lastTab(KTextEditor::View *view)
0490 {
0491     lastBuffer(view); // TODO: implement properly, when interface is added
0492 }
0493 
0494 void BufferCommands::activateDocument(KTextEditor::View *view, KTextEditor::Document *doc)
0495 {
0496     KTextEditor::MainWindow *mainWindow = view->mainWindow();
0497     QTimer::singleShot(0, [mainWindow, doc]() {
0498         mainWindow->activateView(doc);
0499     });
0500 }
0501 
0502 QList<KTextEditor::Document *> BufferCommands::documents()
0503 {
0504     KTextEditor::Application *app = KTextEditor::Editor::instance()->application();
0505     return app->documents();
0506 }
0507 
0508 bool BufferCommands::help(KTextEditor::View * /*view*/, const QString &cmd, QString &msg)
0509 {
0510     if (cmd == QLatin1String("b") || cmd == QLatin1String("buffer")) {
0511         msg = i18n(
0512             "<p><b>b,buffer &mdash; Edit document N from the document list</b></p>"
0513             "<p>Usage: <tt><b>b[uffer] [N]</b></tt></p>");
0514         return true;
0515     } else if (cmd == QLatin1String("bp") || cmd == QLatin1String("bprevious") || cmd == QLatin1String("tabp") || cmd == QLatin1String("tabprevious")) {
0516         msg = i18n(
0517             "<p><b>bp,bprev &mdash; previous buffer</b></p>"
0518             "<p>Usage: <tt><b>bp[revious] [N]</b></tt></p>"
0519             "<p>Goes to <b>[N]</b>th previous document (\"<b>b</b>uffer\") in document list. </p>"
0520             "<p> <b>[N]</b> defaults to one. </p>"
0521             "<p>Wraps around the start of the document list.</p>");
0522         return true;
0523     } else if (cmd == QLatin1String("bn") || cmd == QLatin1String("bnext") || cmd == QLatin1String("tabn") || cmd == QLatin1String("tabnext")) {
0524         msg = i18n(
0525             "<p><b>bn,bnext &mdash; switch to next document</b></p>"
0526             "<p>Usage: <tt><b>bn[ext] [N]</b></tt></p>"
0527             "<p>Goes to <b>[N]</b>th next document (\"<b>b</b>uffer\") in document list."
0528             "<b>[N]</b> defaults to one. </p>"
0529             "<p>Wraps around the end of the document list.</p>");
0530         return true;
0531     } else if (cmd == QLatin1String("bf") || cmd == QLatin1String("bfirst") || cmd == QLatin1String("tabf") || cmd == QLatin1String("tabfirst")) {
0532         msg = i18n(
0533             "<p><b>bf,bfirst &mdash; first document</b></p>"
0534             "<p>Usage: <tt><b>bf[irst]</b></tt></p>"
0535             "<p>Goes to the <b>f</b>irst document (\"<b>b</b>uffer\") in document list.</p>");
0536         return true;
0537     } else if (cmd == QLatin1String("bl") || cmd == QLatin1String("blast") || cmd == QLatin1String("tabl") || cmd == QLatin1String("tablast")) {
0538         msg = i18n(
0539             "<p><b>bl,blast &mdash; last document</b></p>"
0540             "<p>Usage: <tt><b>bl[ast]</b></tt></p>"
0541             "<p>Goes to the <b>l</b>ast document (\"<b>b</b>uffer\") in document list.</p>");
0542         return true;
0543     } else if (cmd == QLatin1String("ls")) {
0544         msg = i18n(
0545             "<p><b>ls</b></p>"
0546             "<p>list current buffers<p>");
0547     }
0548 
0549     return false;
0550 }
0551 // END KateViBufferCommand