00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021 #include "katecmds.h"
00022
00023 #include "katedocument.h"
00024 #include "kateview.h"
00025 #include "kateconfig.h"
00026 #include "kateautoindent.h"
00027 #include "katetextline.h"
00028 #include "katesyntaxmanager.h"
00029 #include "kateglobal.h"
00030 #include "katerenderer.h"
00031 #include "katecmd.h"
00032
00033 #include <kdebug.h>
00034 #include <klocale.h>
00035 #include <kurl.h>
00036 #include <kshellcompletion.h>
00037
00038 #include <QtCore/QRegExp>
00039
00040
00041
00042 static void setDocFlag( KateDocumentConfig::ConfigFlags flag, bool enable,
00043 KateDocument *doc )
00044 {
00045 doc->config()->setConfigFlags( flag, enable );
00046 }
00047
00048
00049
00050
00051 static bool getBoolArg( const QString &t, bool *val )
00052 {
00053 bool res( false );
00054 QString s = t.toLower();
00055 res = (s == "on" || s == "1" || s == "true");
00056 if ( res )
00057 {
00058 *val = true;
00059 return true;
00060 }
00061 res = (s == "off" || s == "0" || s == "false");
00062 if ( res )
00063 {
00064 *val = false;
00065 return true;
00066 }
00067 return false;
00068 }
00069
00070 const QStringList &KateCommands::CoreCommands::cmds()
00071 {
00072 static QStringList l;
00073
00074 if (l.isEmpty())
00075 l << "indent" << "unindent" << "cleanindent"
00076 << "comment" << "uncomment" << "goto" << "kill-line"
00077 << "set-tab-width" << "set-replace-tabs" << "set-show-tabs"
00078 << "set-remove-trailing-space"
00079 << "set-indent-width"
00080 << "set-indent-mode" << "set-auto-indent"
00081 << "set-line-numbers" << "set-folding-markers" << "set-icon-border"
00082 << "set-wrap-cursor"
00083 << "set-word-wrap" << "set-word-wrap-column"
00084 << "set-replace-tabs-save" << "set-remove-trailing-space-save"
00085 << "set-highlight" << "set-mode" << "set-show-indent"
00086 << "print";
00087
00088 return l;
00089 }
00090
00091 bool KateCommands::CoreCommands::exec(KTextEditor::View *view,
00092 const QString &_cmd,
00093 QString &errorMsg)
00094 {
00095 return exec( view, _cmd, errorMsg, KTextEditor::Range::invalid() );
00096 }
00097
00098 bool KateCommands::CoreCommands::exec(KTextEditor::View *view,
00099 const QString &_cmd,
00100 QString &errorMsg,
00101 const KTextEditor::Range& range)
00102 {
00103 #define KCC_ERR(s) { errorMsg=s; return false; }
00104
00105 KateView *v = (KateView*) view;
00106
00107 if ( ! v )
00108 KCC_ERR( i18n("Could not access view") );
00109
00110
00111 QStringList args(_cmd.split( QRegExp("\\s+"), QString::SkipEmptyParts)) ;
00112 QString cmd ( args.takeFirst() );
00113
00114
00115 if ( cmd == "indent" )
00116 {
00117 if ( range.isValid() ) {
00118 v->doc()->editStart();
00119 for ( int line = range.start().line(); line <= range.end().line(); line++ ) {
00120 v->doc()->indent( v, line, 1 );
00121 }
00122 v->doc()->editEnd();
00123 } else {
00124 v->indent();
00125 }
00126 return true;
00127 }
00128 #if 0
00129 else if ( cmd == "run-myself" )
00130 {
00131 #ifndef Q_WS_WIN //todo
00132 return KateGlobal::self()->jscript()->execute(v, v->doc()->text(), errorMsg);
00133 #else
00134 return 0;
00135 #endif
00136 }
00137 #endif
00138 else if ( cmd == "unindent" )
00139 {
00140 if ( range.isValid() ) {
00141 v->doc()->editStart();
00142 for ( int line = range.start().line(); line <= range.end().line(); line++ ) {
00143 v->doc()->indent( v, line, -1 );
00144 }
00145 v->doc()->editEnd();
00146 } else {
00147 v->unIndent();
00148 }
00149 return true;
00150 }
00151 else if ( cmd == "cleanindent" )
00152 {
00153 if ( range.isValid() ) {
00154 v->doc()->editStart();
00155 for ( int line = range.start().line(); line <= range.end().line(); line++ ) {
00156 v->doc()->indent( v, line, 0 );
00157 }
00158 v->doc()->editEnd();
00159 } else {
00160 v->cleanIndent();
00161 }
00162 return true;
00163 }
00164 else if ( cmd == "comment" )
00165 {
00166 if ( range.isValid() ) {
00167 v->doc()->editStart();
00168 for ( int line = range.start().line(); line <= range.end().line(); line++ ) {
00169 v->doc()->comment( v, line, 0, 1 );
00170 }
00171 v->doc()->editEnd();
00172 } else {
00173 v->comment();
00174 }
00175 return true;
00176 }
00177 else if ( cmd == "uncomment" )
00178 {
00179 if ( range.isValid() ) {
00180 v->doc()->editStart();
00181 for ( int line = range.start().line(); line <= range.end().line(); line++ ) {
00182 v->doc()->comment( v, line, 0, -1 );
00183 }
00184 v->doc()->editEnd();
00185 } else {
00186 v->uncomment();
00187 }
00188 return true;
00189 }
00190 else if ( cmd == "kill-line" )
00191 {
00192 if ( range.isValid() ) {
00193 v->doc()->editStart();
00194 for ( int line = range.start().line(); line <= range.end().line(); line++ ) {
00195 v->doc()->removeLine( range.start().line() );
00196 }
00197 v->doc()->editEnd();
00198 } else {
00199 v->killLine();
00200 }
00201 return true;
00202 }
00203 else if ( cmd == "print" )
00204 {
00205 v->doc()->printDialog();
00206 return true;
00207 }
00208
00209
00210 else if ( cmd == "set-indent-mode" ||
00211 cmd == "set-highlight" ||
00212 cmd == "set-mode" )
00213 {
00214
00215 if ( ! args.count() )
00216 KCC_ERR( i18n("Missing argument. Usage: %1 <value>", cmd ) );
00217
00218 if ( cmd == "set-indent-mode" )
00219 {
00220 v->doc()->config()->setIndentationMode( args.first() );
00221 return true;
00222 }
00223 else if ( cmd == "set-highlight" )
00224 {
00225 if ( v->doc()->setHighlightingMode( args.first()) )
00226 {
00227 ((KateDocument*)v->doc())->setDontChangeHlOnSave ();
00228 return true;
00229 }
00230
00231 KCC_ERR( i18n("No such highlighting '%1'", args.first() ) );
00232 }
00233 else if ( cmd == "set-mode" )
00234 {
00235 if ( v->doc()->setMode( args.first()) )
00236 return true;
00237
00238 KCC_ERR( i18n("No such mode '%1'", args.first() ) );
00239 }
00240 }
00241
00242 else if ( cmd == "set-tab-width" ||
00243 cmd == "set-indent-width" ||
00244 cmd == "set-word-wrap-column" ||
00245 cmd == "goto" )
00246 {
00247
00248 if ( ! args.count() )
00249 KCC_ERR( i18n("Missing argument. Usage: %1 <value>", cmd ) );
00250 bool ok;
00251 int val ( args.first().toInt( &ok, 10 ) );
00252 if ( !ok )
00253 KCC_ERR( i18n("Failed to convert argument '%1' to integer.",
00254 args.first() ) );
00255
00256 if ( cmd == "set-tab-width" )
00257 {
00258 if ( val < 1 )
00259 KCC_ERR( i18n("Width must be at least 1.") );
00260 v->doc()->config()->setTabWidth( val );
00261 }
00262 else if ( cmd == "set-indent-width" )
00263 {
00264 if ( val < 1 )
00265 KCC_ERR( i18n("Width must be at least 1.") );
00266 v->doc()->config()->setIndentationWidth( val );
00267 }
00268 else if ( cmd == "set-word-wrap-column" )
00269 {
00270 if ( val < 2 )
00271 KCC_ERR( i18n("Column must be at least 1.") );
00272 v->doc()->setWordWrapAt( val );
00273 }
00274 else if ( cmd == "goto" )
00275 {
00276 if ( args.first().at(0) == '-' || args.first().at(0) == '+' ) {
00277
00278 val = v->cursorPosition().line() + val;
00279 } else {
00280 val--;
00281 }
00282
00283
00284 if ( val < 0 ) {
00285 val = 0;
00286 } else if ( val > v->doc()->lines()-1 ) {
00287 val = v->doc()->lines()-1;
00288 }
00289
00290 v->setCursorPosition( KTextEditor::Cursor( val, 0 ) );
00291 return true;
00292 }
00293 return true;
00294 }
00295
00296
00297 else if ( cmd == "set-icon-border" ||
00298 cmd == "set-folding-markers" ||
00299 cmd == "set-line-numbers" ||
00300 cmd == "set-replace-tabs" ||
00301 cmd == "set-remove-trailing-space" ||
00302 cmd == "set-show-tabs" ||
00303 cmd == "set-word-wrap" ||
00304 cmd == "set-wrap-cursor" ||
00305 cmd == "set-replace-tabs-save" ||
00306 cmd == "set-remove-trailing-space-save" ||
00307 cmd == "set-show-indent" )
00308 {
00309 if ( ! args.count() )
00310 KCC_ERR( i18n("Usage: %1 on|off|1|0|true|false", cmd ) );
00311 bool enable = false;
00312 if ( getBoolArg( args.first(), &enable ) )
00313 {
00314 if ( cmd == "set-icon-border" )
00315 v->setIconBorder( enable );
00316 else if (cmd == "set-folding-markers")
00317 v->setFoldingMarkersOn( enable );
00318 else if ( cmd == "set-line-numbers" )
00319 v->setLineNumbersOn( enable );
00320 else if ( cmd == "set-show-indent" )
00321 v->renderer()->setShowIndentLines( enable );
00322 else if ( cmd == "set-replace-tabs" )
00323 setDocFlag( KateDocumentConfig::cfReplaceTabsDyn, enable, v->doc() );
00324 else if ( cmd == "set-remove-trailing-space" )
00325 setDocFlag( KateDocumentConfig::cfRemoveTrailingDyn, enable, v->doc() );
00326 else if ( cmd == "set-show-tabs" )
00327 setDocFlag( KateDocumentConfig::cfShowTabs, enable, v->doc() );
00328 else if ( cmd == "set-show-trailing-spaces" )
00329 setDocFlag( KateDocumentConfig::cfShowSpaces, enable, v->doc() );
00330 else if ( cmd == "set-word-wrap" )
00331 v->doc()->setWordWrap( enable );
00332 else if ( cmd == "set-remove-trailing-space-save" )
00333 setDocFlag( KateDocumentConfig::cfRemoveSpaces, enable, v->doc() );
00334 else if ( cmd == "set-wrap-cursor" )
00335 setDocFlag( KateDocumentConfig::cfWrapCursor, enable, v->doc() );
00336
00337 return true;
00338 }
00339 else
00340 KCC_ERR( i18n("Bad argument '%1'. Usage: %2 on|off|1|0|true|false",
00341 args.first() , cmd ) );
00342 }
00343
00344
00345 KCC_ERR( i18n("Unknown command '%1'", cmd) );
00346 }
00347
00348 bool KateCommands::CoreCommands::supportsRange(const QString &range)
00349 {
00350 static QStringList l;
00351
00352 if (l.isEmpty())
00353 l << "indent" << "unindent" << "cleanindent"
00354 << "comment" << "uncomment" << "kill-line";
00355
00356 return l.contains(range);
00357 }
00358
00359 KCompletion *KateCommands::CoreCommands::completionObject( KTextEditor::View *view, const QString &cmd )
00360 {
00361 Q_UNUSED(view)
00362
00363 if ( cmd == "set-highlight" )
00364 {
00365 QStringList l;
00366 for ( int i = 0; i < KateHlManager::self()->highlights(); i++ )
00367 l << KateHlManager::self()->hlName (i);
00368
00369 KateCmdShellCompletion *co = new KateCmdShellCompletion();
00370 co->setItems( l );
00371 co->setIgnoreCase( true );
00372 return co;
00373 }
00374 return 0L;
00375 }
00376
00377
00378
00379 const QStringList &KateCommands::ViCommands::cmds()
00380 {
00381 static QStringList l;
00382
00383 if (l.isEmpty())
00384 l << "w" << "hardcopy" << "nnoremap" << "nn";
00385
00386 return l;
00387 }
00388
00389 bool KateCommands::ViCommands::exec(KTextEditor::View *view,
00390 const QString &_cmd,
00391 QString &msg)
00392 {
00393 return exec( view, _cmd, msg, KTextEditor::Range::invalid() );
00394 }
00395
00396 bool KateCommands::ViCommands::exec(KTextEditor::View *view,
00397 const QString &_cmd,
00398 QString &msg,
00399 const KTextEditor::Range& range)
00400 {
00401 Q_UNUSED(range)
00402
00403 KateView *v = (KateView*) view;
00404
00405 if ( !v ) {
00406 msg = i18n("Could not access view");
00407 return false;
00408 }
00409
00410
00411 QStringList args(_cmd.split( QRegExp("\\s+"), QString::SkipEmptyParts)) ;
00412 QString cmd ( args.takeFirst() );
00413
00414
00415 if ( cmd == "w" )
00416 {
00417 v->doc()->documentSave();
00418 return true;
00419 }
00420 else if ( cmd == "hardcopy" )
00421 {
00422 v->doc()->printDialog();
00423 return true;
00424 }
00425 else if ( cmd == "nnoremap" || cmd == "nn" )
00426 {
00427 if ( args.count() == 1 ) {
00428 msg = v->getViInputModeManager()->getMapping( NormalMode, args.at( 0 ) );
00429 if ( msg.isEmpty() ) {
00430 msg = i18n( "No mapping found for \"%1\"", args.at(0) );
00431 return false;
00432 } else {
00433 msg = i18n( "\"%1\" is mapped to \"%2\"", args.at( 0 ), msg );
00434 }
00435 } else if ( args.count() == 2 ) {
00436 v->getViInputModeManager()->addMapping( NormalMode, args.at( 0 ), args.at( 1 ) );
00437 } else {
00438 msg = i18n("Missing argument(s). Usage: %1 <from> [<to>]", cmd );
00439 return false;
00440 }
00441
00442 return true;
00443 }
00444
00445
00446 msg = i18n("Unknown command '%1'", cmd);
00447 return false;
00448 }
00449
00450 bool KateCommands::ViCommands::supportsRange(const QString &range)
00451 {
00452 Q_UNUSED(range)
00453 return false;
00454 }
00455
00456 KCompletion *KateCommands::ViCommands::completionObject( KTextEditor::View *view, const QString &cmd )
00457 {
00458 Q_UNUSED(view)
00459
00460 KateView *v = (KateView*) view;
00461
00462 if ( v && ( cmd == "nn" || cmd == "nnoremap" ) )
00463 {
00464 QStringList l = v->getViInputModeManager()->getMappings( NormalMode );
00465
00466 KateCmdShellCompletion *co = new KateCmdShellCompletion();
00467 co->setItems( l );
00468 co->setIgnoreCase( false );
00469 return co;
00470 }
00471 return 0L;
00472 }
00473
00474
00475
00476 static void replace(QString &s, const QString &needle, const QString &with)
00477 {
00478 int pos=0;
00479 while (1)
00480 {
00481 pos=s.indexOf(needle, pos);
00482 if (pos==-1) break;
00483 s.replace(pos, needle.length(), with);
00484 pos+=with.length();
00485 }
00486
00487 }
00488
00489 static int backslashString(const QString &haystack, const QString &needle, int index)
00490 {
00491 int len=haystack.length();
00492 int searchlen=needle.length();
00493 bool evenCount=true;
00494 while (index<len)
00495 {
00496 if (haystack[index]=='\\')
00497 {
00498 evenCount=!evenCount;
00499 }
00500 else
00501 {
00502 if (!evenCount)
00503 {
00504 if (haystack.mid(index, searchlen)==needle)
00505 return index-1;
00506 }
00507 evenCount=true;
00508 }
00509 index++;
00510
00511 }
00512
00513 return -1;
00514 }
00515
00516
00517 static void exchangeAbbrevs(QString &str)
00518 {
00519
00520 const char *magic="a\x07t\tn\n";
00521
00522 while (*magic)
00523 {
00524 int index=0;
00525 char replace=magic[1];
00526 while ((index=backslashString(str, QString (QChar::fromAscii(*magic)), index))!=-1)
00527 {
00528 str.replace(index, 2, QChar(replace));
00529 index++;
00530 }
00531 magic++;
00532 magic++;
00533 }
00534 }
00535
00536 int KateCommands::SedReplace::sedMagic( KateDocument *doc, int &line,
00537 const QString &find, const QString &repOld, const QString &delim,
00538 bool noCase, bool repeat,
00539 uint startcol, int endcol )
00540 {
00541 KateTextLine::Ptr ln = doc->kateTextLine( line );
00542 if ( ! ln || ! ln->length() ) return 0;
00543
00544
00545
00546
00547
00548
00549
00550
00551
00552
00553
00554 QStringList patterns(find.split( QRegExp("(^\\\\n|(?![^\\\\])\\\\n)"), QString::KeepEmptyParts));
00555 if ( patterns.count() > 1 )
00556 {
00557 for ( int i = 0; i < patterns.count(); i++ )
00558 {
00559 if ( i < patterns.count() - 1 )
00560 patterns[i].append("$");
00561 if ( i )
00562 patterns[i].prepend("^");
00563
00564 kDebug(13025)<<"patterns["<<i<<"] ="<<patterns[i];
00565 }
00566 }
00567
00568 QRegExp matcher(patterns[0], noCase ?Qt::CaseSensitive:Qt::CaseInsensitive);
00569
00570 uint len;
00571 int matches = 0;
00572
00573 while ( ln->searchText( startcol, matcher, &startcol, &len ) )
00574 {
00575
00576 if ( endcol >= 0 && startcol + len > (uint)endcol )
00577 break;
00578
00579 matches++;
00580
00581
00582 QString rep=repOld;
00583
00584
00585 const QStringList backrefs=matcher.capturedTexts();
00586 int refnum=1;
00587
00588 QStringList::ConstIterator i = backrefs.begin();
00589 ++i;
00590
00591 for (; i!=backrefs.end(); ++i)
00592 {
00593
00594 QString number=QString::number(refnum);
00595
00596 int index=0;
00597 while (index!=-1)
00598 {
00599 index=backslashString(rep, number, index);
00600 if (index>=0)
00601 {
00602 rep.replace(index, 2, *i);
00603 index+=(*i).length();
00604 }
00605 }
00606
00607 refnum++;
00608 }
00609
00610 replace(rep, "\\\\", "\\");
00611 replace(rep, "\\" + delim, delim);
00612
00613 doc->removeText( KTextEditor::Range (line, startcol, line, startcol + len) );
00614 doc->insertText( KTextEditor::Cursor (line, startcol), rep );
00615
00616
00617
00618
00619 int lns = rep.count(QChar::fromLatin1('\n'));
00620 if ( lns > 0 )
00621 {
00622 line += lns;
00623
00624 if ( doc->lineLength( line ) > 0 && ( endcol < 0 || (uint)endcol >= startcol + len ) )
00625 {
00626
00627 endcol -= (startcol + len);
00628 uint sc = rep.length() - rep.lastIndexOf('\n') - 1;
00629 matches += sedMagic( doc, line, find, repOld, delim, noCase, repeat, sc, endcol );
00630 }
00631 }
00632
00633 if (!repeat) break;
00634 startcol+=rep.length();
00635
00636
00637 uint ll = ln->length();
00638 if ( ! ll || startcol > ll )
00639 break;
00640 }
00641
00642 return matches;
00643 }
00644
00645 bool KateCommands::SedReplace::exec (KTextEditor::View *view, const QString &cmd, QString &msg)
00646 {
00647 return exec(view, cmd, msg, KTextEditor::Range::invalid());
00648 }
00649
00650 bool KateCommands::SedReplace::exec (class KTextEditor::View *view, const QString &cmd,
00651 QString &msg, const KTextEditor::Range &r)
00652 {
00653 kDebug(13025)<<"SedReplace::execCmd( "<<cmd<<" )";
00654 if (r.isValid())
00655 kDebug(13025)<<"Range: " << r;
00656
00657 QRegExp delim("^s\\s*([^\\w\\s])");
00658 if ( delim.indexIn( cmd ) < 0 ) return false;
00659
00660 bool noCase=cmd[cmd.length()-1]=='i' || cmd[cmd.length()-2]=='i';
00661 bool repeat=cmd[cmd.length()-1]=='g' || cmd[cmd.length()-2]=='g';
00662
00663 QString d = delim.cap(1);
00664 kDebug(13025)<<"SedReplace: delimiter is '"<<d<<"'";
00665
00666 QRegExp splitter( QString("^s\\s*") + d + "((?:[^\\\\\\" + d + "]|\\\\.)*)\\"
00667 + d +"((?:[^\\\\\\" + d + "]|\\\\.)*)(\\" + d + "[ig]{0,2})?$" );
00668 if (splitter.indexIn(cmd)<0) return false;
00669
00670 QString find=splitter.cap(1);
00671 kDebug(13025)<< "SedReplace: find=" << find;
00672
00673 QString replace=splitter.cap(2);
00674 exchangeAbbrevs(replace);
00675 kDebug(13025)<< "SedReplace: replace=" << replace;
00676
00677 if ( find.contains("\\n") )
00678 {
00679
00680 msg = i18n("Sorry, but Kate is not able to replace newlines, yet");
00681 return false;
00682 }
00683
00684 KateDocument *doc = ((KateView*)view)->doc();
00685 if ( ! doc ) return false;
00686
00687 doc->editStart();
00688
00689 int replacementsDone = 0;
00690 int linesTouched = 0;
00691
00692 if (r.isValid()) {
00693 for (int line = r.start().line(); line <= r.end().line(); line++) {
00694 int temp = replacementsDone;
00695 replacementsDone += sedMagic( doc, line, find, replace, d, !noCase, repeat );
00696 if (replacementsDone > temp) {
00697 linesTouched++;
00698 }
00699 }
00700 } else {
00701 int line= view->cursorPosition().line();
00702 replacementsDone += sedMagic(doc, line, find, replace, d, !noCase, repeat);
00703 if (replacementsDone > 0) {
00704 linesTouched = 1;
00705 }
00706 }
00707
00708 msg = i18ncp("%2 is the translation of the next message",
00709 "1 replacement done on %2", "%1 replacements done on %2", replacementsDone,
00710 i18ncp("substituted into the previous message",
00711 "1 line", "%1 lines", linesTouched));
00712
00713 doc->editEnd();
00714
00715 return true;
00716 }
00717
00718
00719
00720
00721 bool KateCommands::Character::exec (KTextEditor::View *view, const QString &_cmd, QString &)
00722 {
00723 QString cmd = _cmd;
00724
00725
00726 QRegExp num("^char *(0?x[0-9A-Fa-f]{1,4}|0[0-7]{1,6}|[0-9]{1,5})$");
00727 if (num.indexIn(cmd)==-1) return false;
00728
00729 cmd=num.cap(1);
00730
00731
00732
00733 unsigned short int number=0;
00734 int base=10;
00735 if (cmd[0]=='x' || cmd.startsWith(QLatin1String("0x")))
00736 {
00737 cmd.remove(QRegExp("^0?x"));
00738 base=16;
00739 }
00740 else if (cmd[0]=='0')
00741 base=8;
00742 bool ok;
00743 number=cmd.toUShort(&ok, base);
00744 if (!ok || number==0) return false;
00745 if (number<=255)
00746 {
00747 char buf[2];
00748 buf[0]=(char)number;
00749 buf[1]=0;
00750
00751 view->document()->insertText(view->cursorPosition(), QString(buf));
00752 }
00753 else
00754 {
00755 QChar c(number);
00756
00757 view->document()->insertText(view->cursorPosition(), QString(&c, 1));
00758 }
00759
00760 return true;
00761 }
00762
00763
00764
00765
00766 bool KateCommands::Date::exec (KTextEditor::View *view, const QString &cmd, QString &)
00767 {
00768 if (!cmd.startsWith(QLatin1String("date")))
00769 return false;
00770
00771 if (QDateTime::currentDateTime().toString(cmd.mid(5, cmd.length()-5)).length() > 0)
00772 view->document()->insertText(view->cursorPosition(), QDateTime::currentDateTime().toString(cmd.mid(5, cmd.length()-5)));
00773 else
00774 view->document()->insertText(view->cursorPosition(), QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"));
00775
00776 return true;
00777 }
00778
00779
00780
00781