Small fixes to tab completion
[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 #ifdef NeXT
23 #include <libc.h>
24 #endif
25 #ifndef _MSC_VER
26 #ifndef __BORLANDC__
27 #include <unistd.h>
28 #endif
29 #endif
30 #ifdef WIN32
31 #include <io.h>
32 #endif
33 #include <fcntl.h>
34 #include "quakedef.h"
35
36 int             con_linewidth;
37
38 float           con_cursorspeed = 4;
39
40 #define         CON_TEXTSIZE    16384
41
42 qboolean        con_forcedup;           // because no entities to refresh
43
44 int                     con_totallines;         // total lines in console scrollback
45 int                     con_backscroll;         // lines up from bottom to display
46 int                     con_current;            // where next message will be printed
47 int                     con_x;                          // offset in current line for next print
48 char            *con_text = 0;
49
50 cvar_t          con_notifytime = {CVAR_SAVE, "con_notifytime","3"};     //seconds
51 cvar_t          logfile = {0, "logfile","0"};
52
53 #define NUM_CON_TIMES 4
54 float           con_times[NUM_CON_TIMES];       // realtime time the line was generated
55                                                 // for transparent notify lines
56
57 int                     con_vislines;
58
59 qboolean        con_debuglog;
60
61 #define MAXCMDLINE      256
62 extern  char    key_lines[32][MAXCMDLINE];
63 extern  int             edit_line;
64 extern  int             key_linepos;
65 extern  int             key_insert;
66                 
67
68 qboolean        con_initialized;
69
70 int             con_notifylines;                // scan lines to clear for notify lines
71
72 extern void M_Menu_Main_f (void);
73
74 /*
75 ================
76 Con_ToggleConsole_f
77 ================
78 */
79 void Con_ToggleConsole_f (void)
80 {
81         if (key_dest == key_console)
82         {
83                 if (cls.state == ca_connected)
84 //              {
85                         key_dest = key_game;
86 //                      key_lines[edit_line][1] = 0;    // clear any typing
87 //                      key_linepos = 1;
88 //              }
89                 else
90                         M_Menu_Main_f ();
91         }
92         else
93                 key_dest = key_console;
94         
95 //      SCR_EndLoadingPlaque ();
96         memset (con_times, 0, sizeof(con_times));
97 }
98
99 /*
100 ================
101 Con_Clear_f
102 ================
103 */
104 void Con_Clear_f (void)
105 {
106         if (con_text)
107                 memset (con_text, ' ', CON_TEXTSIZE);
108 }
109
110                                                 
111 /*
112 ================
113 Con_ClearNotify
114 ================
115 */
116 void Con_ClearNotify (void)
117 {
118         int             i;
119         
120         for (i=0 ; i<NUM_CON_TIMES ; i++)
121                 con_times[i] = 0;
122 }
123
124                                                 
125 /*
126 ================
127 Con_MessageMode_f
128 ================
129 */
130 extern qboolean team_message;
131
132 void Con_MessageMode_f (void)
133 {
134         key_dest = key_message;
135         team_message = false;
136 }
137
138                                                 
139 /*
140 ================
141 Con_MessageMode2_f
142 ================
143 */
144 void Con_MessageMode2_f (void)
145 {
146         key_dest = key_message;
147         team_message = true;
148 }
149
150                                                 
151 /*
152 ================
153 Con_CheckResize
154
155 If the line width has changed, reformat the buffer.
156 ================
157 */
158 void Con_CheckResize (void)
159 {
160         int             i, j, width, oldwidth, oldtotallines, numlines, numchars;
161         char    tbuf[CON_TEXTSIZE];
162
163         width = (vid.conwidth >> 3) - 2;
164
165         if (width == con_linewidth)
166                 return;
167
168         if (width < 1)                  // video hasn't been initialized yet
169         {
170                 width = 78; // LordHavoc: changed from 38 to 78 (320 -> 640 conversion)
171                 con_linewidth = width;
172                 con_totallines = CON_TEXTSIZE / con_linewidth;
173                 memset (con_text, ' ', CON_TEXTSIZE);
174         }
175         else
176         {
177                 oldwidth = con_linewidth;
178                 con_linewidth = width;
179                 oldtotallines = con_totallines;
180                 con_totallines = CON_TEXTSIZE / con_linewidth;
181                 numlines = oldtotallines;
182
183                 if (con_totallines < numlines)
184                         numlines = con_totallines;
185
186                 numchars = oldwidth;
187         
188                 if (con_linewidth < numchars)
189                         numchars = con_linewidth;
190
191                 memcpy (tbuf, con_text, CON_TEXTSIZE);
192                 memset (con_text, ' ', CON_TEXTSIZE);
193
194                 for (i=0 ; i<numlines ; i++)
195                 {
196                         for (j=0 ; j<numchars ; j++)
197                         {
198                                 con_text[(con_totallines - 1 - i) * con_linewidth + j] =
199                                                 tbuf[((con_current - i + oldtotallines) %
200                                                           oldtotallines) * oldwidth + j];
201                         }
202                 }
203
204                 Con_ClearNotify ();
205         }
206
207         con_backscroll = 0;
208         con_current = con_totallines - 1;
209 }
210
211
212 /*
213 ================
214 Con_Init
215 ================
216 */
217 void Con_Init (void)
218 {
219 #define MAXGAMEDIRLEN   1000
220         char    temp[MAXGAMEDIRLEN+1];
221         char    *t2 = "/qconsole.log";
222
223         Cvar_RegisterVariable(&logfile);
224         con_debuglog = COM_CheckParm("-condebug");
225
226         if (con_debuglog)
227         {
228                 if (strlen (com_gamedir) < (MAXGAMEDIRLEN - strlen (t2)))
229                 {
230                         sprintf (temp, "%s%s", com_gamedir, t2);
231                         unlink (temp);
232                 }
233                 logfile.value = 1;
234         }
235
236         con_text = Hunk_AllocName (CON_TEXTSIZE, "context");
237         memset (con_text, ' ', CON_TEXTSIZE);
238         con_linewidth = -1;
239         Con_CheckResize ();
240         
241         Con_Printf ("Console initialized.\n");
242
243 //
244 // register our commands
245 //
246         Cvar_RegisterVariable (&con_notifytime);
247
248         Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f);
249         Cmd_AddCommand ("messagemode", Con_MessageMode_f);
250         Cmd_AddCommand ("messagemode2", Con_MessageMode2_f);
251         Cmd_AddCommand ("clear", Con_Clear_f);
252         con_initialized = true;
253 }
254
255
256 /*
257 ===============
258 Con_Linefeed
259 ===============
260 */
261 void Con_Linefeed (void)
262 {
263         con_x = 0;
264         con_current++;
265         memset (&con_text[(con_current%con_totallines)*con_linewidth]
266         , ' ', con_linewidth);
267 }
268
269 /*
270 ================
271 Con_Print
272
273 Handles cursor positioning, line wrapping, etc
274 All console printing must go through this in order to be logged to disk
275 If no console is visible, the notify window will pop up.
276 ================
277 */
278 void Con_Print (char *txt)
279 {
280         int             y;
281         int             c, l;
282         static int      cr;
283         int             mask;
284         
285         con_backscroll = 0;
286
287         if (txt[0] == 1)
288         {
289                 mask = 128;             // go to colored text
290                 S_LocalSound ("misc/talk.wav");
291         // play talk wav
292                 txt++;
293         }
294         else if (txt[0] == 2)
295         {
296                 mask = 128;             // go to colored text
297                 txt++;
298         }
299         else
300                 mask = 0;
301
302
303         while ( (c = *txt) )
304         {
305         // count word length
306                 for (l=0 ; l< con_linewidth ; l++)
307                         if ( txt[l] <= ' ')
308                                 break;
309
310         // word wrap
311                 if (l != con_linewidth && (con_x + l > con_linewidth) )
312                         con_x = 0;
313
314                 txt++;
315
316                 if (cr)
317                 {
318                         con_current--;
319                         cr = false;
320                 }
321
322                 
323                 if (!con_x)
324                 {
325                         Con_Linefeed ();
326                 // mark time for transparent overlay
327                         if (con_current >= 0)
328                                 con_times[con_current % NUM_CON_TIMES] = realtime;
329                 }
330
331                 switch (c)
332                 {
333                 case '\n':
334                         con_x = 0;
335                         break;
336
337                 case '\r':
338                         con_x = 0;
339                         cr = 1;
340                         break;
341
342                 default:        // display character and advance
343                         y = con_current % con_totallines;
344                         con_text[y*con_linewidth+con_x] = c | mask;
345                         con_x++;
346                         if (con_x >= con_linewidth)
347                                 con_x = 0;
348                         break;
349                 }
350                 
351         }
352 }
353
354
355 /*
356 ================
357 Con_DebugLog
358 ================
359 */
360 void Con_DebugLog(char *file, char *fmt, ...)
361 {
362     va_list argptr; 
363     static char data[1024];
364     int fd;
365     
366     va_start(argptr, fmt);
367     vsprintf(data, fmt, argptr);
368     va_end(argptr);
369     fd = open(file, O_WRONLY | O_CREAT | O_APPEND, 0666);
370     write(fd, data, strlen(data));
371     close(fd);
372 }
373
374
375 /*
376 ================
377 Con_Printf
378
379 Handles cursor positioning, line wrapping, etc
380 ================
381 */
382 // LordHavoc: increased from 4096 to 16384
383 #define MAXPRINTMSG     16384
384 // FIXME: make a buffer size safe vsprintf?
385 void Con_Printf (char *fmt, ...)
386 {
387         va_list         argptr;
388         char            msg[MAXPRINTMSG];
389 //      static qboolean inupdate;
390         
391         va_start (argptr,fmt);
392         vsprintf (msg,fmt,argptr);
393         va_end (argptr);
394         
395 // also echo to debugging console
396         Sys_Printf ("%s", msg);
397
398 // log all messages to file
399         if (con_debuglog)
400                 Con_DebugLog(va("%s/qconsole.log",com_gamedir), "%s", msg);
401
402         if (!con_initialized)
403                 return;
404                 
405         if (cls.state == ca_dedicated)
406                 return;         // no graphics mode
407
408 // write it to the scrollable buffer
409         Con_Print (msg);
410         
411 // update the screen if the console is displayed
412         // LordHavoc: I don't think there's a real need for this
413         /*
414         // LordHavoc: don't print text while loading scripts
415         if (cls.state != ca_disconnected)
416         if (cls.signon != SIGNONS && !scr_disabled_for_loading )
417         {
418         // protect against infinite loop if something in SCR_UpdateScreen calls
419         // Con_Printf
420                 if (!inupdate)
421                 {
422                         inupdate = true;
423                         SCR_UpdateScreen ();
424                         inupdate = false;
425                 }
426         }
427         */
428 }
429
430 /*
431 ================
432 Con_DPrintf
433
434 A Con_Printf that only shows up if the "developer" cvar is set
435 ================
436 */
437 void Con_DPrintf (char *fmt, ...)
438 {
439         va_list         argptr;
440         char            msg[MAXPRINTMSG];
441                 
442         if (!developer.value)
443                 return;                 // don't confuse non-developers with techie stuff...
444
445         va_start (argptr,fmt);
446         vsprintf (msg,fmt,argptr);
447         va_end (argptr);
448         
449         Con_Printf ("%s", msg);
450 }
451
452
453 /*
454 ==================
455 Con_SafePrintf
456
457 Okay to call even when the screen can't be updated
458 ==================
459 */
460 void Con_SafePrintf (char *fmt, ...)
461 {
462         va_list         argptr;
463         char            msg[1024];
464         int                     temp;
465                 
466         va_start (argptr,fmt);
467         vsprintf (msg,fmt,argptr);
468         va_end (argptr);
469
470         temp = scr_disabled_for_loading;
471         scr_disabled_for_loading = true;
472         Con_Printf ("%s", msg);
473         scr_disabled_for_loading = temp;
474 }
475
476
477 /*
478 ==============================================================================
479
480 DRAWING
481
482 ==============================================================================
483 */
484
485
486 /*
487 ================
488 Con_DrawInput
489
490 The input line scrolls horizontally if typing goes beyond the right edge
491
492 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
493 ================
494 */
495 void Con_DrawInput (void)
496 {
497         int             y;
498         char    *text;
499         char    editlinecopy[256];
500
501         if (key_dest != key_console && !con_forcedup)
502                 return;         // don't draw anything
503
504         text = strcpy(editlinecopy, key_lines[edit_line]);
505         y = strlen(text);
506         
507         // Advanced Console Editing by Radix radix@planetquake.com
508         // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
509         // use strlen of edit_line instead of key_linepos to allow editing
510         // of early characters w/o erasing
511
512         // add the cursor frame
513         if ((int)(realtime*con_cursorspeed) & 1)                // cursor is visible
514                 text[key_linepos] = 11 + 130 * key_insert;      // either solid or triangle facing right
515         
516         text[key_linepos + 1] = 0; // LordHavoc: null terminate, rather than padding with spaces
517         // text[key_linepos] = 10 + ((int)(realtime*con_cursorspeed) & 1);
518         
519
520         // fill out remainder with spaces
521         //      for (i=key_linepos+1 ; i< con_linewidth ; i++)
522         //              text[i] = ' ';
523                 
524         //      prestep if horizontally scrolling
525         if (key_linepos >= con_linewidth)
526                 text += 1 + key_linepos - con_linewidth;
527                 
528         // draw it
529         y = con_vislines - 16;
530
531         //      for (i=0 ; i<con_linewidth ; i++)
532         //              Draw_Character ( (i+1)<<3, con_vislines - 16, text[i]);
533
534         // LordHavoc: speedup
535         Draw_String(8, con_vislines - 16, text, con_linewidth);
536
537         // remove cursor
538         key_lines[edit_line][key_linepos] = 0;
539 }
540
541
542 /*
543 ================
544 Con_DrawNotify
545
546 Draws the last few lines of output transparently over the game top
547 ================
548 */
549 void Con_DrawNotify (void)
550 {
551         int             x, v;
552         char    *text;
553         int             i;
554         float   time;
555         extern char chat_buffer[];
556         char    temptext[256];
557
558         v = 0;
559         for (i= con_current-NUM_CON_TIMES+1 ; i<=con_current ; i++)
560         {
561                 if (i < 0)
562                         continue;
563                 time = con_times[i % NUM_CON_TIMES];
564                 if (time == 0)
565                         continue;
566                 time = realtime - time;
567                 if (time > con_notifytime.value)
568                         continue;
569                 text = con_text + (i % con_totallines)*con_linewidth;
570                 
571                 clearnotify = 0;
572
573 //              for (x = 0 ; x < con_linewidth ; x++)
574 //                      Draw_Character ( (x+1)<<3, v, text[x]);
575                 // LordHavoc: speedup
576                 Draw_String(8, v, text, con_linewidth);
577
578                 v += 8;
579         }
580
581
582         if (key_dest == key_message)
583         {
584                 clearnotify = 0;
585         
586                 x = 0;
587                 
588                 // LordHavoc: speedup, and other improvements
589                 if (team_message)
590                         sprintf(temptext, "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
591                 else
592                         sprintf(temptext, "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
593                 while (strlen(temptext) >= con_linewidth)
594                 {
595                         Draw_String (8, v, temptext, con_linewidth);
596                         strcpy(temptext, &temptext[con_linewidth]);
597                         v += 8;
598                 }
599                 if (strlen(temptext) > 0)
600                 {
601                         Draw_String (8, v, temptext, 0);
602                         v += 8;
603                 }
604 //              Draw_String (8, v, "say:", 0);
605 //              while(chat_buffer[x])
606 //              {
607 //                      Draw_Character ( (x+5)<<3, v, chat_buffer[x]);
608 //                      x++;
609 //              }
610 //              Draw_Character ( (x+5)<<3, v, 10+((int)(realtime*con_cursorspeed)&1));
611 //              v += 8;
612         }
613         
614         if (v > con_notifylines)
615                 con_notifylines = v;
616 }
617
618 /*
619 ================
620 Con_DrawConsole
621
622 Draws the console with the solid background
623 The typing input line at the bottom should only be drawn if typing is allowed
624 ================
625 */
626 void Con_DrawConsole (int lines, qboolean drawinput)
627 {
628         int                             i, y;
629         int                             rows;
630         char                    *text;
631         int                             j;
632         
633         if (lines <= 0)
634                 return;
635
636 // draw the background
637         Draw_ConsoleBackground (lines);
638
639 // draw the text
640         con_vislines = lines;
641
642         rows = (lines-16)>>3;           // rows of text to draw
643         y = lines - 16 - (rows<<3);     // may start slightly negative
644
645         for (i= con_current - rows + 1 ; i<=con_current ; i++, y+=8 )
646         {
647                 j = i - con_backscroll;
648                 if (j<0)
649                         j = 0;
650                 text = con_text + (j % con_totallines)*con_linewidth;
651
652 //              for (x=0 ; x<con_linewidth ; x++)
653 //                      Draw_Character ( (x+1)<<3, y, text[x]);
654                 // LordHavoc: speedup
655                 Draw_String(8, y, text, con_linewidth);
656         }
657
658 // draw the input prompt, user text, and cursor if desired
659         if (drawinput)
660                 Con_DrawInput ();
661 }
662
663 /*
664         Con_DisplayList
665
666         New function for tab-completion system
667         Added by EvilTypeGuy
668         MEGA Thanks to Taniwha
669
670 */
671 void
672 Con_DisplayList(char **list)
673 {
674         int     i = 0;
675         int     pos = 0;
676         int     len = 0;
677         int     maxlen = 0;
678         int     width = (con_linewidth - 4);
679         char    **walk = list;
680
681         while (*walk) {
682                 len = strlen(*walk);
683                 if (len > maxlen)
684                         maxlen = len;
685                 walk++;
686         }
687         maxlen += 1;
688
689         while (*list) {
690                 len = strlen(*list);
691                 if (pos + maxlen >= width) {
692                         Con_Printf("\n");
693                         pos = 0;
694                 }
695
696                 Con_Printf("%s", *list);
697                 for (i = 0; i < (maxlen - len); i++)
698                         Con_Printf(" ");
699
700                 pos += maxlen;
701                 list++;
702         }
703
704         if (pos)
705                 Con_Printf("\n\n");
706 }
707
708 /*
709         Con_CompleteCommandLine
710
711         New function for tab-completion system
712         Added by EvilTypeGuy
713         Thanks to Fett erich@heintz.com
714         Thanks to taniwha
715
716 */
717 void
718 Con_CompleteCommandLine (void)
719 {
720         char    *cmd = "";
721         char    *s;
722         int             c, v, a, i;
723         int             cmd_len;
724         char    **list[3] = {0, 0, 0};
725
726         s = key_lines[edit_line] + 1;
727         // Count number of possible matches
728         c = Cmd_CompleteCountPossible(s);
729         v = Cvar_CompleteCountPossible(s);
730         a = Cmd_CompleteAliasCountPossible(s);
731         
732         if (!(c + v + a))       // No possible matches
733                 return;
734         
735         if (c + v + a == 1) {
736                 if (c)
737                         list[0] = Cmd_CompleteBuildList(s);
738                 else if (v)
739                         list[0] = Cvar_CompleteBuildList(s);
740                 else
741                         list[0] = Cmd_CompleteAliasBuildList(s);
742                 cmd = *list[0];
743                 cmd_len = strlen (cmd);
744         } else {
745                 if (c)
746                         cmd = *(list[0] = Cmd_CompleteBuildList(s));
747                 if (v)
748                         cmd = *(list[1] = Cvar_CompleteBuildList(s));
749                 if (a)
750                         cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
751
752                 cmd_len = strlen (s);
753                 do {
754                         for (i = 0; i < 3; i++) {
755                                 char ch = cmd[cmd_len];
756                                 char **l = list[i];
757                                 if (l) {
758                                         while (*l && (*l)[cmd_len] == ch)
759                                                 l++;
760                                         if (*l)
761                                                 break;
762                                 }
763                         }
764                         if (i == 3)
765                                 cmd_len++;
766                 } while (i == 3);
767                 // 'quakebar'
768                 Con_Printf("\n\35");
769                 for (i = 0; i < con_linewidth - 4; i++)
770                         Con_Printf("\36");
771                 Con_Printf("\37\n");
772
773                 // Print Possible Commands
774                 if (c) {
775                         Con_Printf("%i possible command%s\n", c, (c > 1) ? "s: " : ":");
776                         Con_DisplayList(list[0]);
777                 }
778                 
779                 if (v) {
780                         Con_Printf("%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
781                         Con_DisplayList(list[1]);
782                 }
783                 
784                 if (a) {
785                         Con_Printf("%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
786                         Con_DisplayList(list[2]);
787                 }
788         }
789         
790         if (cmd) {
791                 strncpy(key_lines[edit_line] + 1, cmd, cmd_len);
792                 key_linepos = cmd_len + 1;
793                 if (c + v + a == 1) {
794                         key_lines[edit_line][key_linepos] = ' ';
795                         key_linepos++;
796                 }
797                 key_lines[edit_line][key_linepos] = 0;
798         }
799         for (i = 0; i < 3; i++)
800                 if (list[i])
801                         qfree (list[i]);
802 }
803