]> icculus.org git repositories - taylor/freespace2.git/blob - src/debugconsole/console.cpp
Initial revision
[taylor/freespace2.git] / src / debugconsole / console.cpp
1 /*
2  * $Logfile: /Freespace2/code/DebugConsole/Console.cpp $
3  * $Revision$
4  * $Date$
5  * $Author$
6  *
7  * Routines for managing the debug console window.
8  *
9  * $Log$
10  * Revision 1.1  2002/05/03 03:28:08  root
11  * Initial revision
12  *
13  * 
14  * 4     6/04/99 10:35a Dave
15  * 
16  * 3     10/13/98 9:28a Dave
17  * Started neatening up freespace.h. Many variables renamed and
18  * reorganized. Added AlphaColors.[h,cpp]
19  * 
20  * 2     10/07/98 10:52a Dave
21  * Initial checkin.
22  * 
23  * 1     10/07/98 10:48a Dave
24  * 
25  * 17    2/05/98 11:43a Allender
26  * enhcanced network statistic collection.  Made callback in debug console
27  * to do networking if player is in the console
28  * 
29  * 16    1/22/98 6:42p John
30  * Move game_flush out of debug console into freespace.     Made object
31  * pair code throw out some weapons that turn.   Added stats for how many
32  * object pair are actually checked.
33  * 
34  * 15    1/16/98 11:56a Allender
35  * ability to scroll back in debug console, and call game_flush() when
36  * leaving
37  * 
38  * 14    1/10/98 1:14p John
39  * Added explanation to debug console commands
40  * 
41  * 13    12/21/97 4:39p John
42  * fixed bug in name compare
43  * 
44  * 12    12/21/97 4:33p John
45  * Made debug console functions a class that registers itself
46  * automatically, so you don't need to add the function to
47  * debugfunctions.cpp.  
48  * 
49  * 11    9/13/97 9:31a Lawrance
50  * if playing a demo, clear key filter then reset it once done with debug
51  * console
52  * 
53  * 10    9/09/97 3:39p Sandeep
54  * warning level 4 bugs
55  * 
56  * 9     6/13/97 3:50p John
57  * sped up debug console printf
58  * 
59  * 8     6/13/97 3:27p John
60  * made debug console pretty
61  * 
62  * 7     6/09/97 9:24a John
63  * Changed the way fonts are set.
64  * 
65  * 6     5/29/97 3:09p John
66  * Took out debug menu.  
67  * Made software scaler draw larger bitmaps.
68  * Optimized Direct3D some.
69  * 
70  * 5     5/13/97 4:07p John
71  * made numbers also be marked as strings.
72  * 
73  * 4     4/28/97 5:24p John
74  * made so : and \ don't get parsed out in debug console.
75  * 
76  * 3     4/24/97 11:49a John
77  * added new lighting commands to console.
78  * 
79  * 2     4/24/97 10:36a John
80  * moved the debug console stuff into it's own lib... made it compile with
81  * Fred.
82  * 
83  * 1     4/24/97 10:16a John
84  * Initial rev
85  *
86  * $NoKeywords: $
87  */
88
89 #include <stdlib.h>
90 #include <stdio.h>
91 #include <stdarg.h>
92 #include <setjmp.h>
93 #include <string.h>
94
95 #include "pstypes.h"
96 #include "freespace.h"
97 #include "font.h"
98 #include "timer.h"
99 #include "2d.h"
100 #include "key.h"
101 #include "alphacolors.h"
102 #include "osapi.h"
103
104 #define MAX_COMMANDS 300
105
106 static int Num_debug_commands = 0;
107 static debug_command *Debug_command[MAX_COMMANDS];
108
109
110 debug_command::debug_command(char *_name, char *_help, void (*_func)() )
111 {
112         int i;
113
114         if ( Num_debug_commands >= MAX_COMMANDS )       {
115                 Int3();                 // Too many debug console commands!! Increase MAX_COMMANDS!!
116                 return;
117         }
118
119         for (i=0; i<Num_debug_commands; i++ )   {
120                 int ret  = stricmp( Debug_command[i]->name, _name );
121
122                 if ( ret == 0)  {
123                         Int3();         // This debug console command already exists!!!! 
124                         return;
125                 } else if ( ret > 0 )   {
126                         break;          // Insert it here
127
128                 } else if ( ret < 0 )   {
129                         // do nothing
130                 }
131         }
132
133         if ( i < Num_debug_commands )   {
134                 // Insert it at element i
135                 int j;
136                 for (j=Num_debug_commands; j>i; j-- )   {
137                         Debug_command[j] = Debug_command[j-1];
138                 }
139                 Debug_command[i] = this;                
140                 Num_debug_commands++;
141         } else {
142                 Debug_command[Num_debug_commands] = this;               
143                 Num_debug_commands++;
144         }
145
146         name = _name;
147         help = _help;
148         func = _func;
149 }
150
151 // some global variables
152 int Dc_command; // If this is set, then process the command
153 int Dc_help;            // If this is set, then print out the help text in the form, "usage: ... \nLong description\n" );
154 int Dc_status;          // If this is set, then print out the current status of the command.
155 char *Dc_arg;           // The (lowercased) string value of the argument retrieved from dc_arg
156 char *Dc_arg_org;       // Dc_arg before it got converted to lowercase
157 uint Dc_arg_type;       // The type of dc_arg.
158 char *Dc_command_line;          // The rest of the command line, from the end of the last processed arg on.
159 int Dc_arg_int;         // If Dc_arg_type & ARG_INT is set, then this is the value
160 float Dc_arg_float;     // If Dc_arg_type & ARG_FLOAT is set, then this is the value
161
162 int scroll_times = 0;           // incremented each time display scrolls
163
164 int debug_inited = 0;
165
166 #define DROWS 25
167 #define DCOLS 80
168
169 int debug_x=0, debug_y=0;
170 char debug_text[DROWS][DCOLS];
171
172
173 static char command_line[1024];
174 static int command_line_pos = 0;
175 #define DEBUG_HISTORY 16
176 static char oldcommand_line[DEBUG_HISTORY][1024];
177 int last_oldcommand=-1;
178 int command_scroll = 0;
179
180 ///=========================== SCANNER =======================
181 typedef enum {
182         LETTER, QUOTE, SPECIAL, EOF_CODE, DIGIT,
183 } CHAR_CODE;
184
185 typedef enum {
186         NO_TOKEN, IDENTIFIER, NUMBER, STRING, 
187 } TOKEN_CODE;
188
189
190 #define MAX_TOKEN_STRING_LENGTH 128
191
192 char                    scanner_ch;
193 TOKEN_CODE      scanner_token;
194
195 char scanner_token_string[MAX_TOKEN_STRING_LENGTH];
196 char scanner_word_string[MAX_TOKEN_STRING_LENGTH];
197 char * scanner_bufferp = "";
198 char * scanner_tokenp = scanner_token_string;
199
200 CHAR_CODE scanner_char_table[256];
201
202 #define scanner_char_code(x) scanner_char_table[x]
203
204 void scanner_get_char()
205 {
206         if ( *scanner_bufferp == '\0' ) {
207                 scanner_ch = 0;
208                 return;
209         }
210         scanner_ch = *scanner_bufferp++;
211 }
212
213 void scanner_init()
214 {
215         int ch;
216         for (ch=0; ch<256; ++ch) scanner_char_table[ch] = SPECIAL;
217         for (ch='0'; ch<='9'; ++ch) scanner_char_table[ch] = DIGIT;
218         for (ch='A'; ch<='Z'; ++ch) scanner_char_table[ch] = LETTER;
219         for (ch='a'; ch<='z'; ++ch) scanner_char_table[ch] = LETTER;
220
221         scanner_char_table['.'] = DIGIT;
222         scanner_char_table['-'] = DIGIT;
223         scanner_char_table['+'] = DIGIT;
224         
225         scanner_char_table['_'] = LETTER;
226         scanner_char_table[34] = QUOTE;
227         scanner_char_table[0] = EOF_CODE;
228
229
230         scanner_char_table[':'] = LETTER;
231         scanner_char_table['\\'] = LETTER;
232
233         scanner_ch = 0;
234 }
235
236
237 void scanner_skip_blanks()
238 {
239         while( (scanner_ch ==' ') || (scanner_ch =='\t') ) 
240                 scanner_get_char();
241 }
242
243
244 void scanner_downshift_word()
245 {
246         int offset = 'a' - 'A';
247         char * tp;
248
249         strcpy( scanner_word_string, scanner_token_string );
250         
251         tp = scanner_word_string;
252         do {
253                 *tp = (char)((*tp>='A') && (*tp <='Z') ? *tp + offset : *tp) ;
254                 tp++;
255         } while (*tp != '\0' );
256 }
257
258
259 void scanner_get_word()
260 {
261         while( (scanner_char_code(scanner_ch)==LETTER) || (scanner_char_code(scanner_ch)==DIGIT)  )     {
262                 *scanner_tokenp++ = scanner_ch;
263                 scanner_get_char();
264         }
265         *scanner_tokenp = '\0';
266
267         scanner_token = IDENTIFIER;
268 }
269
270
271 void scanner_get_string()
272 {
273         *scanner_tokenp++ = 34;
274         scanner_get_char();
275
276         while(scanner_ch != 34 )        {
277                 *scanner_tokenp++ = scanner_ch;
278                 scanner_get_char();
279         }
280         scanner_get_char();
281         *scanner_tokenp++ = 34;
282         *scanner_tokenp = '\0';
283         scanner_token = STRING;
284 }
285
286
287
288 void scanner_get_token()
289 {
290         scanner_skip_blanks();
291         scanner_tokenp = scanner_token_string;
292         *scanner_tokenp = 0;
293
294
295         switch( scanner_char_code(scanner_ch) ) {
296         case QUOTE:             scanner_get_string();   break;
297         case EOF_CODE:  scanner_token = NO_TOKEN;       break;
298         case DIGIT:
299         case LETTER:    scanner_get_word(); break;
300         default:                        
301                 *scanner_tokenp++ = scanner_ch;
302                 *scanner_tokenp = '\0';
303                 scanner_get_char();
304                 scanner_token = IDENTIFIER;
305                 break;
306         }
307
308         scanner_downshift_word();
309 }
310
311 void scanner_start_command( char * s )
312 {
313         scanner_bufferp = s;
314         scanner_get_char();
315 }
316
317
318 int Dc_debug_on = 0;
319 jmp_buf dc_bad_arg;
320
321 void dc_get_arg(uint type)
322 {
323         scanner_get_token();
324
325         Dc_command_line = scanner_bufferp;      
326         Dc_arg_org = scanner_token_string;
327         Dc_arg = scanner_word_string;
328
329         if (Dc_debug_on)        {
330                 dc_printf( "next arg is '%s', was originally '%s'\n", Dc_arg, Dc_arg_org );
331                 dc_printf( "Rest of the command line is '%s'\n", Dc_command_line );
332         }
333         
334         if ( scanner_token == NO_TOKEN )        {
335                 Dc_arg_type = ARG_NONE;
336         } else if ( scanner_token == IDENTIFIER )       {
337                 Dc_arg_type = ARG_STRING;
338         } else if ( scanner_token == STRING )   {
339                 Dc_arg_type = ARG_QUOTE;
340         } else {
341                 Dc_arg_type = ARG_STRING;
342         }
343
344         if ( Dc_arg_type & ARG_STRING ) {
345                 int i, num_digits, len;
346
347                 len = strlen(Dc_arg);
348                 num_digits = 0;
349
350                 for (i=0; i<len; i++)
351                         if ( scanner_char_table[Dc_arg[i]] == DIGIT ) num_digits++;
352
353                 if ( num_digits==len )  {
354                         Dc_arg_type |= ARG_FLOAT;
355                         Dc_arg_float = (float)atof(Dc_arg);
356                         if ( !strchr( Dc_arg, '.' ))    {
357                                 Dc_arg_type |= ARG_INT;
358                                 Dc_arg_int = atoi(Dc_arg);
359                         }
360                 } else {
361                         if ( (Dc_arg[0] == '0') && (Dc_arg[1] == 'x') ) {
362                                 char *p;
363                                 int n;
364                                 n = strtol(Dc_arg,&p,0);
365                                 if ( *p == 0 )  {
366                                         Dc_arg_type |= ARG_INT|ARG_HEX;
367                                         Dc_arg_int = n;
368                                 }
369                         } 
370                 }
371
372                 if (Dc_debug_on)        {
373                         if ( Dc_arg_type & ARG_FLOAT )
374                                 dc_printf( "Found float number! %f\n", Dc_arg_float );
375
376                         if ( Dc_arg_type & ARG_INT )
377                                 dc_printf( "Found int number! %d\n", Dc_arg_int );
378
379                         if ( Dc_arg_type & ARG_HEX )
380                                 dc_printf( "Found hex number! 0x%x\n", Dc_arg_int );
381                 }
382
383                 if ( !stricmp( Dc_arg, "on" ))
384                         Dc_arg_type |= ARG_TRUE;
385                 if ( !stricmp( Dc_arg, "true" ))
386                         Dc_arg_type |= ARG_TRUE;
387                 if ( !stricmp( Dc_arg, "off" ))
388                         Dc_arg_type |= ARG_FALSE;
389                 if ( !stricmp( Dc_arg, "false" ))
390                         Dc_arg_type |= ARG_FALSE;
391
392                 if ( !stricmp( Dc_arg, "+" ))
393                         Dc_arg_type |= ARG_PLUS;
394
395                 if ( !stricmp( Dc_arg, "-" ))
396                         Dc_arg_type |= ARG_MINUS;
397
398                 if ( !stricmp( Dc_arg, "," ))
399                         Dc_arg_type |= ARG_COMMA;
400         }
401
402         if ( Dc_arg_type & ARG_INT)     {
403                 if ( Dc_arg_int )       
404                         Dc_arg_type |= ARG_TRUE;
405                 else
406                         Dc_arg_type |= ARG_FALSE;
407         }
408
409         if ( !(Dc_arg_type&type) )      {
410                 if ( (Dc_arg_type & ARG_NONE) && !(type & ARG_NONE))    
411                         dc_printf( "Error: Not enough parameters.\n" );
412                 else
413                         dc_printf( "Error: '%s' invalid type\n", Dc_arg );
414                 longjmp(dc_bad_arg,1);
415         }
416
417 }
418
419 void debug_help();
420
421 void debug_do_command(char * command)
422 {
423
424         int i;
425         int mode = 0;
426
427         if ( strlen(command) < 1 ) return;
428         
429         Dc_debug_on = 0;
430         Dc_command_line = command;
431         scanner_start_command(command);
432
433         if (setjmp(dc_bad_arg) )        {
434                 return;
435         }
436         
437         dc_get_arg( ARG_ANY );
438
439         if ( !strcmp( Dc_arg, "debug" ) )       {
440                 Dc_debug_on = 1;
441                 dc_printf( "Command line: '%s'\n", Dc_command_line );
442                 dc_get_arg( ARG_ANY );
443         }
444
445         if ( !strcmp( Dc_arg, "?" ) )   {
446                 mode = 1;
447                 dc_get_arg( ARG_ANY );
448
449                 if ( Dc_arg_type&ARG_NONE )     {
450                         debug_help();           
451                         return;
452                 }
453         }
454
455         if ( !strcmp( Dc_arg, "help" ) || !strcmp( Dc_arg, "man" ) )    {
456                 mode = 2;
457                 dc_get_arg( ARG_ANY );
458                 if ( Dc_arg_type&ARG_NONE )     {
459                         debug_help();           
460                         return;
461                 }
462         }
463
464         if ( strstr( Dc_command_line, "?" ) )   {
465                 mode = 2;
466         }
467
468         if ( !(Dc_arg_type&ARG_STRING) )        {
469                 dc_printf( "Invalid keyword '%s'\n", Dc_arg );
470                 return;
471         }
472
473
474         if (Dc_debug_on)        {
475                 dc_printf( "Searching for command '%s'\n", Dc_arg );
476         }
477
478         for (i=0; i<Num_debug_commands; i++ )   {
479                 if ( !stricmp( Debug_command[i]->name, Dc_arg ))        {
480                 
481                         if (mode==0)    {
482                                 if (Dc_debug_on)        
483                                         dc_printf( "Calling function '%s'\n", Dc_arg );
484                                 Dc_command = 1;
485                                 Dc_help = 0;
486                                 Dc_status = 1;
487                         } else if (mode==1) {
488                                 if (Dc_debug_on)        
489                                         dc_printf( "Checking status for '%s'\n", Dc_arg );
490                                 Dc_command = 0;
491                                 Dc_help = 0;
492                                 Dc_status = 1;
493                         } else {
494                                 if (Dc_debug_on)        
495                                         dc_printf( "Doing help for '%s'\n", Dc_arg );
496                                 Dc_command = 0;
497                                 Dc_help = 1;
498                                 Dc_status = 0;
499                         }
500
501                         (*Debug_command[i]->func)();
502
503                         if (mode==0)    {
504                                 dc_get_arg(ARG_ANY);
505                                 if (!(Dc_arg_type&ARG_NONE))    {
506                                         dc_printf( "Ignoring the unused command line tail '%s %s'\n", Dc_arg_org, Dc_command_line );
507                                 }
508                         }
509
510                         return;
511                 }
512         }
513
514         dc_printf( "Unknown command '%s'\n", Dc_arg );
515 }
516
517 void debug_draw()
518 {
519         int i;
520         
521         gr_clear();
522         gr_set_font(FONT1);
523         gr_set_color_fast( &Color_bright );
524         gr_string( 0x8000, 3, "Debug Console" );
525
526         gr_set_color_fast( &Color_normal );
527
528         for (i=0; i<DROWS; i++ )        {
529                 gr_string( 0, i*16+16, debug_text[i] );
530         }
531
532         int t = timer_get_fixed_seconds() / (F1_0/3);
533         if ( t & 1 )    {
534                 int w,h;
535                 char c;
536
537                 c = debug_text[debug_y][command_line_pos+1];
538                 debug_text[debug_y][command_line_pos+1] = 0;
539
540                 gr_get_string_size( &w, &h, debug_text[debug_y] );
541                 
542                 //gr_string( w, debug_y*16, "_" );
543                 gr_rect(w+1,debug_y*16+1+16,2,14);
544         
545                 debug_text[debug_y][command_line_pos+1] = c;
546         }
547
548         gr_flip();
549 }
550
551
552 void debug_output( char c )
553 {
554         if ( c == '\t' )        {
555                 int next_tab = ((debug_x/28)+1)*28;
556
557                 if ( next_tab >= DCOLS-1 )      {
558                         debug_x=0;
559                         debug_y++;
560                         scroll_times++;
561                         if ( debug_y >= DROWS ) {
562                                 int i;
563                                 for (i=1; i<DROWS; i++ )
564                                         strcpy( debug_text[i-1], debug_text[i] );
565                                 debug_y = DROWS-1;
566                                 debug_x = 0;
567                                 debug_text[debug_y][debug_x] = 0;
568                         }
569                         debug_text[debug_y][debug_x] = 0;
570                         return;
571                 }
572         
573                 for ( ; debug_x < next_tab; )
574                         debug_text[debug_y][debug_x++] = ' ';
575                 debug_text[debug_y][debug_x] = 0;
576                 return;
577         }
578
579         if ( (c == '\n') || (debug_x >= DCOLS-1) )      {
580                 debug_x=0;
581                 debug_y++;
582                 scroll_times++;
583                 if ( debug_y >= DROWS ) {
584                         int i;
585                         for (i=1; i<DROWS; i++ )
586                                 strcpy( debug_text[i-1], debug_text[i] );
587                         debug_y = DROWS-1;
588                         debug_x = 0;
589                         debug_text[debug_y][debug_x] = 0;
590                 }
591                 debug_text[debug_y][debug_x] = 0;
592                 if ( c == '\n' ) return;
593         }
594
595         debug_text[debug_y][debug_x++] = c;
596         debug_text[debug_y][debug_x] = 0;
597 }
598
599 void dc_printf(char *format, ...)
600 {
601         char tmp[DCOLS*2];
602         va_list args;
603         
604         va_start(args, format);
605         vsprintf(tmp, format, args);
606         va_end(args);
607
608         char *p = tmp;
609         while( *p )     {
610                 debug_output(*p);
611                 p++;
612         } 
613 }
614
615
616
617 void debug_init()
618 {
619         int i;
620         if ( debug_inited ) return;
621
622         debug_inited = 1;
623
624         debug_x=0;
625         debug_y=0;
626
627         for (i=0; i<DROWS; i++ )        {
628                 debug_text[i][0] = 0;
629         }
630         
631         dc_printf("Debug console started.\n" );
632 }
633
634
635 void debug_console( void (*_func)() )
636 {
637         int done = 0;
638
639         scanner_init();
640
641         while( key_inkey() ){
642                 os_poll();
643         }
644
645         if ( !debug_inited ) debug_init();
646
647
648         debug_draw();
649         
650         while (!done)   {
651                 // poll the os
652                 os_poll();
653
654                 int k = key_inkey();
655                 switch( k )     {
656
657                 case KEY_SHIFTED+KEY_ENTER:
658                 case KEY_ESC:   
659                         done=1; break;
660
661                 case KEY_BACKSP:
662                         if ( command_line_pos > 0 )     {
663                                 command_line[--command_line_pos] = 0;
664                         }
665                         break;
666
667                 case KEY_F3:
668                         if ( last_oldcommand > -1 )     {
669                                 strcpy( command_line, oldcommand_line[last_oldcommand] );
670                                 command_line_pos = strlen(command_line);
671                                 command_line[command_line_pos] = 0;
672                         }
673                         break;
674
675                 case KEY_UP:
676                         command_scroll--;
677                         if (command_scroll<0) 
678                                 command_scroll = last_oldcommand;
679
680                         if ( command_scroll > -1 )      {
681                                 strcpy( command_line, oldcommand_line[command_scroll] );
682                                 command_line_pos = strlen(command_line);
683                                 command_line[command_line_pos] = 0;
684                         }
685                         break;
686
687                 case KEY_DOWN:
688                         command_scroll++;
689                         if (command_scroll>last_oldcommand) 
690                                 command_scroll = 0;
691                         if (command_scroll>last_oldcommand) 
692                                 command_scroll = -1;
693                         if ( command_scroll > -1 )      {
694                                 strcpy( command_line, oldcommand_line[command_scroll] );
695                                 command_line_pos = strlen(command_line);
696                                 command_line[command_line_pos] = 0;
697                         }
698                         break;
699
700                 case KEY_ENTER: {
701                         debug_output( '\n' );
702                         debug_draw();
703
704                         debug_do_command(command_line);
705
706                         int i, found = 0;
707                         for (i=0; i<=last_oldcommand; i++ )     {
708                                 if (!stricmp( oldcommand_line[i], command_line ))       {
709                                         found = 1;
710                                 }
711                         }
712                         if ( !found )   {
713                                 if ( last_oldcommand < DEBUG_HISTORY-1 )        {
714                                         last_oldcommand++;
715                                         strcpy( oldcommand_line[last_oldcommand], command_line);
716                                 } else {
717                                         int i;
718                                         for (i=0; i<last_oldcommand; i++ )      {
719                                                 strcpy( oldcommand_line[i], oldcommand_line[i+1] );
720                                         }
721                                         strcpy( oldcommand_line[last_oldcommand], command_line);
722                                 }
723                         }
724 //                      int i;
725 //                      for (i=0; i<=last_oldcommand; i++ )     {
726 //                              dc_printf( "OC %d. %s\n", i, oldcommand_line[i] );
727 //                      }
728         
729                         debug_output( '\n' );
730                         command_line_pos = 0;
731                         command_line[command_line_pos] = 0;
732
733                         command_scroll = 0;
734
735                         } 
736                         break;
737                 default:        {
738                                 ubyte c = (ubyte)key_to_ascii(k);
739                                 if ( c != 255 ) {
740                                         command_line[command_line_pos++] = c;
741                                         command_line[command_line_pos] = 0;
742                                 }
743                         }
744                 }
745
746                 strcpy( debug_text[debug_y], ">" );
747                 strcat( debug_text[debug_y], command_line );
748                 debug_draw();
749
750                 if ( _func ){
751                         _func();
752                 }
753         }
754
755         while( key_inkey() ){
756                 os_poll();
757         }
758 }
759
760 void debug_help()
761 {
762         int s, i;
763
764         dc_printf( "Available functions:\n\n" );        
765
766         s = scroll_times;
767         for (i=0; i<Num_debug_commands; i++ )   {
768                 dc_printf( " %s - %s\n", Debug_command[i]->name, Debug_command[i]->help );
769                 //mprintf(( "Scroll times %d\n", scroll_times - s ));
770                 if ( scroll_times - s > DROWS - 3 )     {
771                         int k;
772                         dc_printf( "       Press a key...B for back\n" );
773                         debug_draw();
774                         k = key_getch();
775                         s = scroll_times;
776                         if ( k == KEY_B )  {
777                                 i -= ((DROWS-3)*2);
778                                 if ( i <= 0 )
779                                         i = -1;
780                         }
781                 } 
782                 debug_draw();
783         }
784         dc_printf( "\n" );
785
786         dc_printf( "Typing '? function_name' will give the current status.\n" );
787         dc_printf( "Typing 'function_name ?' will give help on the function.\n" );
788         dc_printf( "Typing ? or help will give you help.\n");
789         dc_printf( "F3 selects last command line.\n" );
790 }