File indexing completed on 2024-04-28 15:31:17
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, SLOT(closeCurrentView())); 0114 } else { 0115 Q_ASSERT(app->documents().size() > 0); 0116 QTimer::singleShot(0, this, SLOT(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, SLOT(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, SLOT(closeCurrentDocument())); 0133 } else { 0134 QTimer::singleShot(0, this, SLOT(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, SLOT(closeCurrentSplitView())); 0189 } else if ((match = re_only.match(command)).hasMatch()) { 0190 QTimer::singleShot(0, this, SLOT(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 — 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> — writes the current document to disk<br />" 0207 " <tt>wa</tt> — 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 — [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> — closes the current view.<br />" 0219 " <tt>qa</tt> — closes all views, effectively quitting the application.<br />" 0220 " <tt>wq</tt> — writes the current document to disk and closes its view.<br />" 0221 " <tt>wqa</tt> — 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 — 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> — closes the current view.<br />" 0233 " <tt>xa</tt> — 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— 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— 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]— 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 — 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> — splits the view horizontally and opens a new document.<br />" 0265 " <tt>vnew</tt> — 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] — 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 — 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 — 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 — 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 — 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 — 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 0552 0553 #include "moc_appcommands.cpp"