]> icculus.org git repositories - divverent/darkplaces.git/blob - console.c
Fixed FS_Seek for unpacked files when FS_USESYSCALLS is defined (should fix HalfLife...
[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 ("sound/misc/talk.wav");
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                 DrawQ_String(0, v, text, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
766
767                 v += 8;
768         }
769
770
771         if (key_dest == key_message)
772         {
773                 x = 0;
774
775                 // LordHavoc: speedup, and other improvements
776                 if (chat_team)
777                         sprintf(temptext, "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
778                 else
779                         sprintf(temptext, "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
780                 while (strlen(temptext) >= (size_t) con_linewidth)
781                 {
782                         DrawQ_String (0, v, temptext, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
783                         strcpy(temptext, &temptext[con_linewidth]);
784                         v += 8;
785                 }
786                 if (strlen(temptext) > 0)
787                 {
788                         DrawQ_String (0, v, temptext, 0, 8, 8, 1, 1, 1, 1, 0);
789                         v += 8;
790                 }
791         }
792 }
793
794 /*
795 ================
796 Con_DrawConsole
797
798 Draws the console with the solid background
799 The typing input line at the bottom should only be drawn if typing is allowed
800 ================
801 */
802 extern char engineversion[40];
803 void Con_DrawConsole (int lines)
804 {
805         int i, y, rows, j;
806         char *text;
807
808         if (lines <= 0)
809                 return;
810
811 // draw the background
812         if (scr_conbrightness.value >= 0.01f)
813                 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);
814         else
815                 DrawQ_Fill(0, lines - vid.conheight, vid.conwidth, vid.conheight, 0, 0, 0, scr_conalpha.value, 0);
816         DrawQ_String(vid.conwidth - strlen(engineversion) * 8 - 8, lines - 8, engineversion, 0, 8, 8, 1, 0, 0, 1, 0);
817
818 // draw the text
819         con_vislines = lines;
820
821         rows = (lines-16)>>3;           // rows of text to draw
822         y = lines - 16 - (rows<<3);     // may start slightly negative
823
824         for (i = con_current - rows + 1;i <= con_current;i++, y += 8)
825         {
826                 j = max(i - con_backscroll, 0);
827                 text = con_text + (j % con_totallines)*con_linewidth;
828
829                 DrawQ_String(0, y, text, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
830         }
831
832 // draw the input prompt, user text, and cursor if desired
833         Con_DrawInput ();
834 }
835
836 /*
837         Con_DisplayList
838
839         New function for tab-completion system
840         Added by EvilTypeGuy
841         MEGA Thanks to Taniwha
842
843 */
844 void Con_DisplayList(const char **list)
845 {
846         int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
847         const char **walk = list;
848
849         while (*walk) {
850                 len = strlen(*walk);
851                 if (len > maxlen)
852                         maxlen = len;
853                 walk++;
854         }
855         maxlen += 1;
856
857         while (*list) {
858                 len = strlen(*list);
859                 if (pos + maxlen >= width) {
860                         Con_Print("\n");
861                         pos = 0;
862                 }
863
864                 Con_Print(*list);
865                 for (i = 0; i < (maxlen - len); i++)
866                         Con_Print(" ");
867
868                 pos += maxlen;
869                 list++;
870         }
871
872         if (pos)
873                 Con_Print("\n\n");
874 }
875
876 /*
877         Con_CompleteCommandLine
878
879         New function for tab-completion system
880         Added by EvilTypeGuy
881         Thanks to Fett erich@heintz.com
882         Thanks to taniwha
883
884 */
885 void Con_CompleteCommandLine (void)
886 {
887         const char *cmd = "", *s;
888         const char **list[3] = {0, 0, 0};
889         int c, v, a, i, cmd_len;
890
891         s = key_lines[edit_line] + 1;
892         // Count number of possible matches
893         c = Cmd_CompleteCountPossible(s);
894         v = Cvar_CompleteCountPossible(s);
895         a = Cmd_CompleteAliasCountPossible(s);
896
897         if (!(c + v + a))       // No possible matches
898                 return;
899
900         if (c + v + a == 1) {
901                 if (c)
902                         list[0] = Cmd_CompleteBuildList(s);
903                 else if (v)
904                         list[0] = Cvar_CompleteBuildList(s);
905                 else
906                         list[0] = Cmd_CompleteAliasBuildList(s);
907                 cmd = *list[0];
908                 cmd_len = strlen (cmd);
909         } else {
910                 if (c)
911                         cmd = *(list[0] = Cmd_CompleteBuildList(s));
912                 if (v)
913                         cmd = *(list[1] = Cvar_CompleteBuildList(s));
914                 if (a)
915                         cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
916
917                 cmd_len = strlen (s);
918                 do {
919                         for (i = 0; i < 3; i++) {
920                                 char ch = cmd[cmd_len];
921                                 const char **l = list[i];
922                                 if (l) {
923                                         while (*l && (*l)[cmd_len] == ch)
924                                                 l++;
925                                         if (*l)
926                                                 break;
927                                 }
928                         }
929                         if (i == 3)
930                                 cmd_len++;
931                 } while (i == 3);
932                 // 'quakebar'
933                 Con_Print("\n\35");
934                 for (i = 0; i < con_linewidth - 4; i++)
935                         Con_Print("\36");
936                 Con_Print("\37\n");
937
938                 // Print Possible Commands
939                 if (c) {
940                         Con_Printf("%i possible command%s\n", c, (c > 1) ? "s: " : ":");
941                         Con_DisplayList(list[0]);
942                 }
943
944                 if (v) {
945                         Con_Printf("%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
946                         Con_DisplayList(list[1]);
947                 }
948
949                 if (a) {
950                         Con_Printf("%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
951                         Con_DisplayList(list[2]);
952                 }
953         }
954
955         if (cmd) {
956                 strncpy(key_lines[edit_line] + 1, cmd, cmd_len);
957                 key_linepos = cmd_len + 1;
958                 if (c + v + a == 1) {
959                         key_lines[edit_line][key_linepos] = ' ';
960                         key_linepos++;
961                 }
962                 key_lines[edit_line][key_linepos] = 0;
963         }
964         for (i = 0; i < 3; i++)
965                 if (list[i])
966                         Mem_Free((void *)list[i]);
967 }
968