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