92e5d22be0c71149b4eb9aaa6ee20d9ecc3cf91a
[xonotic/darkplaces.git] / console.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20 // console.c
21
22 #if !defined(WIN32) || defined(__MINGW32__)
23 # include <unistd.h>
24 #endif
25 #include <time.h>
26 #include "quakedef.h"
27
28 int con_linewidth;
29
30 float con_cursorspeed = 4;
31
32 #define         CON_TEXTSIZE    131072
33
34 // total lines in console scrollback
35 int con_totallines;
36 // lines up from bottom to display
37 int con_backscroll;
38 // where next message will be printed
39 int con_current;
40 // offset in current line for next print
41 int con_x;
42 char *con_text = 0;
43
44 //seconds
45 cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3"};
46 cvar_t con_notify = {CVAR_SAVE, "con_notify","4"};
47
48 #define MAX_NOTIFYLINES 32
49 // cl.time time the line was generated for transparent notify lines
50 float con_times[MAX_NOTIFYLINES];
51
52 int con_vislines;
53
54 #define MAXCMDLINE      256
55 extern char key_lines[32][MAXCMDLINE];
56 extern int edit_line;
57 extern int key_linepos;
58 extern int key_insert;
59
60
61 qboolean con_initialized;
62
63 mempool_t *console_mempool;
64
65
66 /*
67 ==============================================================================
68
69 LOGGING
70
71 ==============================================================================
72 */
73
74 cvar_t log_file = {0, "log_file",""};
75 cvar_t log_sync = {0, "log_sync","0"};
76 char crt_log_file [MAX_OSPATH] = "";
77 qfile_t* logfile = NULL;
78
79 qbyte* logqueue = NULL;
80 size_t logq_ind = 0;
81 size_t logq_size = 0;
82
83 void Log_ConPrint (const char *msg);
84
85 /*
86 ====================
87 Log_Timestamp
88 ====================
89 */
90 const char* Log_Timestamp (const char *desc)
91 {
92         static char timestamp [128];
93         time_t crt_time;
94         const struct tm *crt_tm;
95         char timestring [64];
96
97         // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
98         time (&crt_time);
99         crt_tm = localtime (&crt_time);
100         strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
101
102         if (desc != NULL)
103                 snprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
104         else
105                 snprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
106
107         return timestamp;
108 }
109
110
111 /*
112 ====================
113 Log_Init
114 ====================
115 */
116 void Log_Init (void)
117 {
118         // Allocate a log queue
119         logq_size = 512;
120         logqueue = Mem_Alloc (tempmempool, logq_size);
121         logq_ind = 0;
122
123         Cvar_RegisterVariable (&log_file);
124         Cvar_RegisterVariable (&log_sync);
125
126         // support for the classic Quake option
127         if (COM_CheckParm ("-condebug") != 0)
128         {
129                 Cvar_SetQuick (&log_file, "qconsole.log");
130                 Cvar_SetValueQuick (&log_sync, 1);
131                 unlink (va("%s/qconsole.log", fs_gamedir));
132         }
133 }
134
135
136 /*
137 ====================
138 Log_Open
139 ====================
140 */
141 void Log_Open (void)
142 {
143         if (logfile != NULL || log_file.string[0] == '\0')
144                 return;
145
146         logfile = FS_Open (log_file.string, "at", false);
147         if (logfile != NULL)
148         {
149                 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
150                 FS_Print (logfile, Log_Timestamp ("Log started"));
151         }
152 }
153
154
155 /*
156 ====================
157 Log_Close
158 ====================
159 */
160 void Log_Close (void)
161 {
162         if (logfile == NULL)
163                 return;
164
165         FS_Print (logfile, Log_Timestamp ("Log stopped"));
166         FS_Print (logfile, "\n");
167         FS_Close (logfile);
168
169         logfile = NULL;
170         crt_log_file[0] = '\0';
171 }
172
173
174 /*
175 ====================
176 Log_Start
177 ====================
178 */
179 void Log_Start (void)
180 {
181         Log_Open ();
182
183         // Dump the contents of the log queue into the log file and free it
184         if (logqueue != NULL)
185         {
186                 if (logfile != NULL && logq_ind != 0)
187                         FS_Write (logfile, logqueue, logq_ind);
188                 Mem_Free (logqueue);
189                 logqueue = NULL;
190                 logq_ind = 0;
191                 logq_size = 0;
192         }
193 }
194
195
196 /*
197 ================
198 Log_ConPrint
199 ================
200 */
201 void Log_ConPrint (const char *msg)
202 {
203         // Until the host is completely initialized, we maintain a log queue
204         // to store the messages, since the log can't be started before
205         if (logqueue != NULL)
206         {
207                 size_t remain = logq_size - logq_ind;
208                 size_t len = strlen (msg);
209
210                 // If we need to enlarge the log queue
211                 if (len > remain)
212                 {
213                         unsigned int factor = ((logq_ind + len) / logq_size) + 1;
214                         qbyte* newqueue;
215
216                         logq_size *= factor;
217                         newqueue = Mem_Alloc (tempmempool, logq_size);
218                         memcpy (newqueue, logqueue, logq_ind);
219                         Mem_Free (logqueue);
220                         logqueue = newqueue;
221                         remain = logq_size - logq_ind;
222                 }
223                 memcpy (&logqueue[logq_ind], msg, len);
224                 logq_ind += len;
225
226                 return;
227         }
228
229         // Check if log_file has changed
230         if (strcmp (crt_log_file, log_file.string) != 0)
231         {
232                 Log_Close ();
233                 Log_Open ();
234         }
235
236         // If a log file is available
237         if (logfile != NULL)
238         {
239                 FS_Print (logfile, msg);
240                 if (log_sync.integer)
241                         FS_Flush (logfile);
242         }
243 }
244
245
246 /*
247 ================
248 Log_Print
249 ================
250 */
251 void Log_Print (const char *logfilename, const char *msg)
252 {
253         qfile_t *file;
254         file = FS_Open(logfilename, "at", true);
255         if (file)
256         {
257                 FS_Print(file, msg);
258                 FS_Close(file);
259         }
260 }
261
262 /*
263 ================
264 Log_Printf
265 ================
266 */
267 void Log_Printf (const char *logfilename, const char *fmt, ...)
268 {
269         qfile_t *file;
270
271         file = FS_Open (logfilename, "at", true);
272         if (file != NULL)
273         {
274                 va_list argptr;
275
276                 va_start (argptr, fmt);
277                 FS_VPrintf (file, fmt, argptr);
278                 va_end (argptr);
279
280                 FS_Close (file);
281         }
282 }
283
284
285 /*
286 ==============================================================================
287
288 CONSOLE
289
290 ==============================================================================
291 */
292
293 /*
294 ================
295 Con_ToggleConsole_f
296 ================
297 */
298 void Con_ToggleConsole_f (void)
299 {
300         // toggle the 'user wants console' bit
301         key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
302         memset (con_times, 0, sizeof(con_times));
303 }
304
305 /*
306 ================
307 Con_Clear_f
308 ================
309 */
310 void Con_Clear_f (void)
311 {
312         if (con_text)
313                 memset (con_text, ' ', CON_TEXTSIZE);
314 }
315
316
317 /*
318 ================
319 Con_ClearNotify
320 ================
321 */
322 void Con_ClearNotify (void)
323 {
324         int i;
325
326         for (i=0 ; i<MAX_NOTIFYLINES ; i++)
327                 con_times[i] = 0;
328 }
329
330
331 /*
332 ================
333 Con_MessageMode_f
334 ================
335 */
336 void Con_MessageMode_f (void)
337 {
338         key_dest = key_message;
339         chat_team = false;
340 }
341
342
343 /*
344 ================
345 Con_MessageMode2_f
346 ================
347 */
348 void Con_MessageMode2_f (void)
349 {
350         key_dest = key_message;
351         chat_team = true;
352 }
353
354
355 /*
356 ================
357 Con_CheckResize
358
359 If the line width has changed, reformat the buffer.
360 ================
361 */
362 void Con_CheckResize (void)
363 {
364         int i, j, width, oldwidth, oldtotallines, numlines, numchars;
365         char tbuf[CON_TEXTSIZE];
366
367         width = (vid.conwidth >> 3);
368
369         if (width == con_linewidth)
370                 return;
371
372         if (width < 1)                  // video hasn't been initialized yet
373         {
374                 width = 80;
375                 con_linewidth = width;
376                 con_totallines = CON_TEXTSIZE / con_linewidth;
377                 memset (con_text, ' ', CON_TEXTSIZE);
378         }
379         else
380         {
381                 oldwidth = con_linewidth;
382                 con_linewidth = width;
383                 oldtotallines = con_totallines;
384                 con_totallines = CON_TEXTSIZE / con_linewidth;
385                 numlines = oldtotallines;
386
387                 if (con_totallines < numlines)
388                         numlines = con_totallines;
389
390                 numchars = oldwidth;
391
392                 if (con_linewidth < numchars)
393                         numchars = con_linewidth;
394
395                 memcpy (tbuf, con_text, CON_TEXTSIZE);
396                 memset (con_text, ' ', CON_TEXTSIZE);
397
398                 for (i=0 ; i<numlines ; i++)
399                 {
400                         for (j=0 ; j<numchars ; j++)
401                         {
402                                 con_text[(con_totallines - 1 - i) * con_linewidth + j] =
403                                                 tbuf[((con_current - i + oldtotallines) %
404                                                           oldtotallines) * oldwidth + j];
405                         }
406                 }
407
408                 Con_ClearNotify ();
409         }
410
411         con_backscroll = 0;
412         con_current = con_totallines - 1;
413 }
414
415 /*
416 ================
417 Con_Init
418 ================
419 */
420 void Con_Init (void)
421 {
422         console_mempool = Mem_AllocPool("console", 0, NULL);
423         con_text = Mem_Alloc(console_mempool, CON_TEXTSIZE);
424         memset (con_text, ' ', CON_TEXTSIZE);
425         con_linewidth = -1;
426         Con_CheckResize ();
427
428         Con_Print("Console initialized.\n");
429
430         // register our cvars
431         Cvar_RegisterVariable (&con_notifytime);
432         Cvar_RegisterVariable (&con_notify);
433
434         // register our commands
435         Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f);
436         Cmd_AddCommand ("messagemode", Con_MessageMode_f);
437         Cmd_AddCommand ("messagemode2", Con_MessageMode2_f);
438         Cmd_AddCommand ("clear", Con_Clear_f);
439         con_initialized = true;
440 }
441
442
443 /*
444 ===============
445 Con_Linefeed
446 ===============
447 */
448 void Con_Linefeed (void)
449 {
450         if (con_backscroll)
451                 con_backscroll++;
452
453         con_x = 0;
454         con_current++;
455         memset (&con_text[(con_current%con_totallines)*con_linewidth], ' ', con_linewidth);
456 }
457
458 /*
459 ================
460 Con_PrintToHistory
461
462 Handles cursor positioning, line wrapping, etc
463 All console printing must go through this in order to be displayed
464 If no console is visible, the notify window will pop up.
465 ================
466 */
467 void Con_PrintToHistory(const char *txt)
468 {
469         int y, c, l, mask;
470         static int cr;
471
472         if (txt[0] == 1)
473         {
474                 mask = 128;             // go to colored text
475                 S_LocalSound ("misc/talk.wav", true);
476         // play talk wav
477                 txt++;
478         }
479         else if (txt[0] == 2)
480         {
481                 mask = 128;             // go to colored text
482                 txt++;
483         }
484         else
485                 mask = 0;
486
487
488         while ( (c = *txt) )
489         {
490         // count word length
491                 for (l=0 ; l< con_linewidth ; l++)
492                         if ( txt[l] <= ' ')
493                                 break;
494
495         // word wrap
496                 if (l != con_linewidth && (con_x + l > con_linewidth) )
497                         con_x = 0;
498
499                 txt++;
500
501                 if (cr)
502                 {
503                         con_current--;
504                         cr = false;
505                 }
506
507
508                 if (!con_x)
509                 {
510                         Con_Linefeed ();
511                 // mark time for transparent overlay
512                         if (con_current >= 0)
513                         {
514                                 if (con_notify.integer < 0)
515                                         Cvar_SetValueQuick(&con_notify, 0);
516                                 if (con_notify.integer > MAX_NOTIFYLINES)
517                                         Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
518                                 if (con_notify.integer > 0)
519                                         con_times[con_current % con_notify.integer] = cl.time;
520                         }
521                 }
522
523                 switch (c)
524                 {
525                 case '\n':
526                         con_x = 0;
527                         break;
528
529                 case '\r':
530                         con_x = 0;
531                         cr = 1;
532                         break;
533
534                 default:        // display character and advance
535                         y = con_current % con_totallines;
536                         con_text[y*con_linewidth+con_x] = c | mask;
537                         con_x++;
538                         if (con_x >= con_linewidth)
539                                 con_x = 0;
540                         break;
541                 }
542
543         }
544 }
545
546 /*
547 ================
548 Con_Print
549
550 Prints to all appropriate console targets
551 ================
552 */
553 void Con_Print(const char *msg)
554 {
555         // also echo to debugging console
556         Sys_Print(msg);
557
558         // log all messages to file
559         Log_ConPrint (msg);
560
561         if (!con_initialized)
562                 return;
563
564         if (cls.state == ca_dedicated)
565                 return;         // no graphics mode
566
567         // write it to the scrollable buffer
568         Con_PrintToHistory(msg);
569 }
570
571
572 // LordHavoc: increased from 4096 to 16384
573 #define MAXPRINTMSG     16384
574
575 /*
576 ================
577 Con_Printf
578
579 Prints to all appropriate console targets
580 ================
581 */
582 void Con_Printf(const char *fmt, ...)
583 {
584         va_list argptr;
585         char msg[MAXPRINTMSG];
586
587         va_start(argptr,fmt);
588         vsprintf(msg,fmt,argptr);
589         va_end(argptr);
590
591         Con_Print(msg);
592 }
593
594 /*
595 ================
596 Con_DPrint
597
598 A Con_Print that only shows up if the "developer" cvar is set
599 ================
600 */
601 void Con_DPrint(const char *msg)
602 {
603         if (!developer.integer)
604                 return;                 // don't confuse non-developers with techie stuff...
605         Con_Print(msg);
606 }
607
608 /*
609 ================
610 Con_DPrintf
611
612 A Con_Printf that only shows up if the "developer" cvar is set
613 ================
614 */
615 void Con_DPrintf(const char *fmt, ...)
616 {
617         va_list argptr;
618         char msg[MAXPRINTMSG];
619
620         if (!developer.integer)
621                 return;                 // don't confuse non-developers with techie stuff...
622
623         va_start(argptr,fmt);
624         vsprintf(msg,fmt,argptr);
625         va_end(argptr);
626
627         Con_Print(msg);
628 }
629
630
631 /*
632 ================
633 Con_SafePrint
634
635 Okay to call even when the screen can't be updated
636 ==================
637 */
638 void Con_SafePrint(const char *msg)
639 {
640         Con_Print(msg);
641 }
642
643 /*
644 ==================
645 Con_SafePrintf
646
647 Okay to call even when the screen can't be updated
648 ==================
649 */
650 void Con_SafePrintf(const char *fmt, ...)
651 {
652         va_list argptr;
653         char msg[MAXPRINTMSG];
654
655         va_start(argptr,fmt);
656         vsprintf(msg,fmt,argptr);
657         va_end(argptr);
658
659         Con_Print(msg);
660 }
661
662
663 /*
664 ==============================================================================
665
666 DRAWING
667
668 ==============================================================================
669 */
670
671
672 /*
673 ================
674 Con_DrawInput
675
676 The input line scrolls horizontally if typing goes beyond the right edge
677
678 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
679 ================
680 */
681 void Con_DrawInput (void)
682 {
683         int             y;
684         int             i;
685         char editlinecopy[257], *text;
686
687         if (!key_consoleactive)
688                 return;         // don't draw anything
689
690         text = strcpy(editlinecopy, key_lines[edit_line]);
691
692         // Advanced Console Editing by Radix radix@planetquake.com
693         // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
694         // use strlen of edit_line instead of key_linepos to allow editing
695         // of early characters w/o erasing
696
697         y = strlen(text);
698
699 // fill out remainder with spaces
700         for (i = y; i < 256; i++)
701                 text[i] = ' ';
702
703         // add the cursor frame
704         if ((int)(realtime*con_cursorspeed) & 1)                // cursor is visible
705                 text[key_linepos] = 11 + 130 * key_insert;      // either solid or triangle facing right
706
707 //      text[key_linepos + 1] = 0;
708
709         // prestep if horizontally scrolling
710         if (key_linepos >= con_linewidth)
711                 text += 1 + key_linepos - con_linewidth;
712
713         // draw it
714         DrawQ_String(0, con_vislines - 16, text, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
715
716         // remove cursor
717 //      key_lines[edit_line][key_linepos] = 0;
718 }
719
720
721 /*
722 ================
723 Con_DrawNotify
724
725 Draws the last few lines of output transparently over the game top
726 ================
727 */
728 void Con_DrawNotify (void)
729 {
730         int             x, v;
731         char    *text;
732         int             i;
733         float   time;
734         extern char chat_buffer[];
735         char    temptext[256];
736
737         if (con_notify.integer < 0)
738                 Cvar_SetValueQuick(&con_notify, 0);
739         if (con_notify.integer > MAX_NOTIFYLINES)
740                 Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
741         if (gamemode == GAME_TRANSFUSION)
742                 v = 8;
743         else
744                 v = 0;
745         for (i= con_current-con_notify.integer+1 ; i<=con_current ; i++)
746         {
747                 if (i < 0)
748                         continue;
749                 time = con_times[i % con_notify.integer];
750                 if (time == 0)
751                         continue;
752                 time = cl.time - time;
753                 if (time > con_notifytime.value)
754                         continue;
755                 text = con_text + (i % con_totallines)*con_linewidth;
756
757                 clearnotify = 0;
758
759                 DrawQ_String(0, v, text, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
760
761                 v += 8;
762         }
763
764
765         if (key_dest == key_message)
766         {
767                 clearnotify = 0;
768
769                 x = 0;
770
771                 // LordHavoc: speedup, and other improvements
772                 if (chat_team)
773                         sprintf(temptext, "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
774                 else
775                         sprintf(temptext, "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
776                 while (strlen(temptext) >= (size_t) con_linewidth)
777                 {
778                         DrawQ_String (0, v, temptext, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
779                         strcpy(temptext, &temptext[con_linewidth]);
780                         v += 8;
781                 }
782                 if (strlen(temptext) > 0)
783                 {
784                         DrawQ_String (0, v, temptext, 0, 8, 8, 1, 1, 1, 1, 0);
785                         v += 8;
786                 }
787         }
788 }
789
790 /*
791 ================
792 Con_DrawConsole
793
794 Draws the console with the solid background
795 The typing input line at the bottom should only be drawn if typing is allowed
796 ================
797 */
798 extern char engineversion[40];
799 void Con_DrawConsole (int lines)
800 {
801         int i, y, rows, j;
802         char *text;
803
804         if (lines <= 0)
805                 return;
806
807 // draw the background
808         if (scr_conbrightness.value >= 0.01f)
809                 DrawQ_Pic(0, lines - vid.conheight, "gfx/conback", vid.conwidth, vid.conheight, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, scr_conalpha.value, 0);
810         else
811                 DrawQ_Fill(0, lines - vid.conheight, vid.conwidth, vid.conheight, 0, 0, 0, scr_conalpha.value, 0);
812         DrawQ_String(vid.conwidth - strlen(engineversion) * 8 - 8, lines - 8, engineversion, 0, 8, 8, 1, 0, 0, 1, 0);
813
814 // draw the text
815         con_vislines = lines;
816
817         rows = (lines-16)>>3;           // rows of text to draw
818         y = lines - 16 - (rows<<3);     // may start slightly negative
819
820         for (i = con_current - rows + 1;i <= con_current;i++, y += 8)
821         {
822                 j = max(i - con_backscroll, 0);
823                 text = con_text + (j % con_totallines)*con_linewidth;
824
825                 DrawQ_String(0, y, text, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
826         }
827
828 // draw the input prompt, user text, and cursor if desired
829         Con_DrawInput ();
830 }
831
832 /*
833         Con_DisplayList
834
835         New function for tab-completion system
836         Added by EvilTypeGuy
837         MEGA Thanks to Taniwha
838
839 */
840 void Con_DisplayList(const char **list)
841 {
842         int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
843         const char **walk = list;
844
845         while (*walk) {
846                 len = strlen(*walk);
847                 if (len > maxlen)
848                         maxlen = len;
849                 walk++;
850         }
851         maxlen += 1;
852
853         while (*list) {
854                 len = strlen(*list);
855                 if (pos + maxlen >= width) {
856                         Con_Print("\n");
857                         pos = 0;
858                 }
859
860                 Con_Print(*list);
861                 for (i = 0; i < (maxlen - len); i++)
862                         Con_Print(" ");
863
864                 pos += maxlen;
865                 list++;
866         }
867
868         if (pos)
869                 Con_Print("\n\n");
870 }
871
872 /*
873         Con_CompleteCommandLine
874
875         New function for tab-completion system
876         Added by EvilTypeGuy
877         Thanks to Fett erich@heintz.com
878         Thanks to taniwha
879
880 */
881 void Con_CompleteCommandLine (void)
882 {
883         const char *cmd = "", *s;
884         const char **list[3] = {0, 0, 0};
885         int c, v, a, i, cmd_len;
886
887         s = key_lines[edit_line] + 1;
888         // Count number of possible matches
889         c = Cmd_CompleteCountPossible(s);
890         v = Cvar_CompleteCountPossible(s);
891         a = Cmd_CompleteAliasCountPossible(s);
892
893         if (!(c + v + a))       // No possible matches
894                 return;
895
896         if (c + v + a == 1) {
897                 if (c)
898                         list[0] = Cmd_CompleteBuildList(s);
899                 else if (v)
900                         list[0] = Cvar_CompleteBuildList(s);
901                 else
902                         list[0] = Cmd_CompleteAliasBuildList(s);
903                 cmd = *list[0];
904                 cmd_len = strlen (cmd);
905         } else {
906                 if (c)
907                         cmd = *(list[0] = Cmd_CompleteBuildList(s));
908                 if (v)
909                         cmd = *(list[1] = Cvar_CompleteBuildList(s));
910                 if (a)
911                         cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
912
913                 cmd_len = strlen (s);
914                 do {
915                         for (i = 0; i < 3; i++) {
916                                 char ch = cmd[cmd_len];
917                                 const char **l = list[i];
918                                 if (l) {
919                                         while (*l && (*l)[cmd_len] == ch)
920                                                 l++;
921                                         if (*l)
922                                                 break;
923                                 }
924                         }
925                         if (i == 3)
926                                 cmd_len++;
927                 } while (i == 3);
928                 // 'quakebar'
929                 Con_Print("\n\35");
930                 for (i = 0; i < con_linewidth - 4; i++)
931                         Con_Print("\36");
932                 Con_Print("\37\n");
933
934                 // Print Possible Commands
935                 if (c) {
936                         Con_Printf("%i possible command%s\n", c, (c > 1) ? "s: " : ":");
937                         Con_DisplayList(list[0]);
938                 }
939
940                 if (v) {
941                         Con_Printf("%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
942                         Con_DisplayList(list[1]);
943                 }
944
945                 if (a) {
946                         Con_Printf("%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
947                         Con_DisplayList(list[2]);
948                 }
949         }
950
951         if (cmd) {
952                 strncpy(key_lines[edit_line] + 1, cmd, cmd_len);
953                 key_linepos = cmd_len + 1;
954                 if (c + v + a == 1) {
955                         key_lines[edit_line][key_linepos] = ' ';
956                         key_linepos++;
957                 }
958                 key_lines[edit_line][key_linepos] = 0;
959         }
960         for (i = 0; i < 3; i++)
961                 if (list[i])
962                         Mem_Free((void *)list[i]);
963 }
964