bad char
[vg.git] / vg_console.h
1 /* Copyright (C) 2021-2022 Harry Godden (hgn) - All Rights Reserved */
2
3 #ifndef VG_CONSOLE_H
4 #define VG_CONSOLE_H
5
6 #ifndef VG_GAME
7 #define VG_GAME
8 #endif
9
10 #include "vg/vg_ui.h"
11 #include "vg/vg_log.h"
12
13 #define VG_VAR_F32_PERSISTENT( NAME ) \
14 vg_var_push( (struct vg_var){ \
15 .name = #NAME, \
16 .data = &NAME, \
17 .data_type = k_var_dtype_f32, \
18 .persistent = 1 \
19 });
20
21 #define VG_VAR_F32( NAME ) \
22 vg_var_push( (struct vg_var){ \
23 .name = #NAME, \
24 .data = &NAME, \
25 .data_type = k_var_dtype_f32, \
26 });
27
28 #define VG_VAR_I32_PERSISTENT( NAME ) \
29 vg_var_push( (struct vg_var){ \
30 .name = #NAME, \
31 .data = &NAME, \
32 .data_type = k_var_dtype_i32, \
33 .persistent = 1 \
34 });
35
36 #define VG_VAR_I32( NAME ) \
37 vg_var_push( (struct vg_var){ \
38 .name = #NAME, \
39 .data = &NAME, \
40 .data_type = k_var_dtype_i32, \
41 });
42
43 typedef struct vg_var vg_var;
44 typedef struct vg_cmd vg_cmd;
45
46 struct vg_console
47 {
48 struct vg_var
49 {
50 void *data;
51 const char *name;
52
53 enum vg_var_dtype
54 {
55 k_var_dtype_i32,
56 k_var_dtype_u32,
57 k_var_dtype_f32
58 }
59 data_type;
60
61 union
62 {
63 struct
64 {
65 int min, max, clamp;
66 }
67 opt_i32;
68
69 struct
70 {
71 float min, max;
72 int clamp;
73 }
74 opt_f32;
75 };
76
77 int persistent; /* Should this var be stored to cfg/auto.conf? */
78 }
79 vars[ 128 ];
80
81 struct vg_cmd
82 {
83 int (*function)( int argc, char const *argv[] );
84 void (*poll_suggest)( int argc, char const *argv[] );
85 const char *name;
86 }
87 functions[ 32 ];
88
89 struct
90 {
91 const char *str;
92 int len;
93
94 u32 lev_score;
95 }
96 suggestions[12];
97 u32 suggestion_count;
98 int suggestion_select,
99 suggestion_pastepos,
100 suggestion_maxlen;
101
102 u32 var_count, function_count;
103
104 char input[96],
105 input_copy[96];
106 int cursor_user, cursor_pos, string_length;
107
108 char history[32][96];
109 int history_last, history_pos, history_count;
110
111 int enabled;
112 }
113 vg_console;
114
115 VG_STATIC void vg_var_push( struct vg_var cv );
116 VG_STATIC void vg_function_push( struct vg_cmd cmd );
117
118 VG_STATIC void _vg_console_draw( void );
119 void _vg_console_println( const char *str );
120 VG_STATIC int _vg_console_list( int argc, char const *argv[] );
121 VG_STATIC void _vg_console_init(void);
122 VG_STATIC void _vg_console_write_persistent(void);
123 VG_STATIC void _vg_console_free(void);
124 VG_STATIC void vg_execute_console_input( const char *cmd );
125
126 /*
127 * Console interface
128 */
129 VG_STATIC void console_make_selection( int* start, int* end );
130 VG_STATIC void console_move_cursor( int* cursor0, int* cursor1,
131 int dir, int snap_together );
132 VG_STATIC int console_makeroom( int datastart, int length );
133 VG_STATIC int console_delete_char( int direction );
134 VG_STATIC void console_to_clipboard(void);
135 VG_STATIC void console_clipboard_paste(void);
136 VG_STATIC void console_put_char( char c );
137 VG_STATIC void console_history_get( char* buf, int entry_num );
138 VG_STATIC int _vg_console_enabled(void);
139 VG_STATIC void console_proc_key( SDL_Keysym ev );
140
141 /*
142 * Implementation
143 */
144 VG_STATIC int _vg_console_enabled(void)
145 {
146 return vg_console.enabled;
147 }
148
149 VG_STATIC void vg_var_push( vg_var cv )
150 {
151 if( vg_console.var_count > vg_list_size(vg_console.vars) )
152 vg_fatal_exit_loop( "Too many vars registered" );
153
154 vg_info( "Console variable '%s' registered\n", cv.name );
155 vg_console.vars[ vg_console.var_count ++ ] = cv;
156 }
157
158 VG_STATIC void vg_function_push( struct vg_cmd cmd )
159 {
160 if( vg_console.function_count > vg_list_size(vg_console.functions) )
161 vg_fatal_exit_loop( "Too many functions registered" );
162
163 vg_console.functions[ vg_console.function_count ++ ] = cmd;
164 }
165
166 VG_STATIC void _vg_console_draw( void )
167 {
168 if( !vg_console.enabled )
169 return;
170
171 SDL_AtomicLock( &log_print_sl );
172
173 int ptr = vg_log.buffer_line_current;
174 int const fh = 14,
175 log_lines = 32;
176 int console_lines = VG_MIN( log_lines, vg_log.buffer_line_count );
177
178 vg_uictx.cursor[0] = 0;
179 vg_uictx.cursor[1] = 0;
180 vg_uictx.cursor[3] = log_lines*fh;
181 ui_fill_x();
182
183 /*
184 * log
185 */
186 ui_new_node();
187 {
188 ui_fill_rect( vg_uictx.cursor, 0x77181818 );
189
190 vg_uictx.cursor[3] = fh;
191 ui_align_bottom();
192
193 for( int i=0; i<console_lines; i ++ )
194 {
195 ptr --;
196
197 if( ptr < 0 )
198 ptr = vg_list_size( vg_log.buffer )-1;
199
200 ui_text( vg_uictx.cursor, vg_log.buffer[ptr], 1, 0 );
201 vg_uictx.cursor[1] -= fh;
202 }
203
204 }
205 ui_end_down();
206
207
208 /* Input area */
209 vg_uictx.cursor[1] += 2;
210 vg_uictx.cursor[3] = fh;
211
212 ui_new_node();
213 {
214 ui_fill_rect( vg_uictx.cursor, 0x77111111 );
215 ui_text( vg_uictx.cursor, vg_console.input, 1, 0 );
216
217 int start = VG_MIN( vg_console.cursor_pos, vg_console.cursor_user ),
218 end = VG_MAX( vg_console.cursor_pos, vg_console.cursor_user );
219
220 vg_uictx.cursor[0] = start * UI_GLYPH_SPACING_X;
221 vg_uictx.cursor[2] = (start == end? 0.5f: (float)(end-start))
222 * (float)UI_GLYPH_SPACING_X;
223
224 ui_fill_rect( vg_uictx.cursor, 0x66ffffff );
225 }
226 ui_end_down();
227
228
229 /* suggestions */
230 if( vg_console.suggestion_count )
231 {
232 vg_uictx.cursor[0] += UI_GLYPH_SPACING_X * vg_console.suggestion_pastepos;
233 vg_uictx.cursor[1] += 2;
234 vg_uictx.cursor[3] = vg_console.suggestion_count * fh;
235 vg_uictx.cursor[2] = UI_GLYPH_SPACING_X * vg_console.suggestion_maxlen;
236
237 ui_new_node();
238 {
239 ui_fill_rect( vg_uictx.cursor, 0x77040404 );
240
241 vg_uictx.cursor[3] = fh;
242 for( int i=0; i<vg_console.suggestion_count; i ++ )
243 {
244 if( i == vg_console.suggestion_select )
245 ui_fill_rect( vg_uictx.cursor, 0x66a0e508 );
246
247 ui_text( vg_uictx.cursor, vg_console.suggestions[i].str, 1, 0 );
248 vg_uictx.cursor[1] += fh;
249 }
250 }
251 ui_end_down();
252 }
253
254 SDL_AtomicUnlock( &log_print_sl );
255 }
256
257 VG_STATIC int _vg_console_list( int argc, char const *argv[] )
258 {
259 for( int i=0; i<vg_console.function_count; i ++ )
260 {
261 struct vg_cmd *cmd = &vg_console.functions[ i ];
262 vg_info( "* %s\n", cmd->name );
263 }
264
265 for( int i=0; i<vg_console.var_count; i ++ )
266 {
267 struct vg_var *cv = &vg_console.vars[ i ];
268 vg_info( "%s\n", cv->name );
269 }
270
271 return 0;
272 }
273
274 int _test_break( int argc, const char *argv[] )
275 {
276 vg_fatal_exit_loop( "Test crash from main, after loading (console)" );
277 return 0;
278 }
279
280 VG_STATIC void _vg_console_init(void)
281 {
282 vg_function_push( (struct vg_cmd)
283 {
284 .name = "list",
285 .function = _vg_console_list
286 });
287
288 vg_function_push( (struct vg_cmd)
289 {
290 .name = "crash",
291 .function = _test_break
292 });
293 }
294
295 VG_STATIC void vg_console_load_autos(void)
296 {
297 /* Read and exec persistent commands */
298 FILE *fp = fopen( "cfg/auto.conf", "r" );
299 if( fp )
300 {
301 char line[256];
302
303 while( fgets( line, sizeof( line ), fp ) )
304 {
305 line[ strcspn( line, "\r\n#" ) ] = 0x00;
306
307 if( line[0] != 0x00 )
308 {
309 vg_execute_console_input( line );
310 }
311 }
312
313 fclose( fp );
314 }
315 }
316
317 VG_STATIC void _vg_console_write_persistent(void)
318 {
319 FILE *fp = fopen( "cfg/auto.conf", "w" );
320
321 for( int i=0; i<vg_console.var_count; i ++ )
322 {
323 struct vg_var *cv = &vg_console.vars[i];
324
325 if( cv->persistent )
326 {
327 switch( cv->data_type )
328 {
329 case k_var_dtype_i32:
330 fprintf( fp, "%s %d\n", cv->name, *(i32 *)(cv->data) );
331 break;
332 case k_var_dtype_u32:
333 fprintf( fp, "%s %u\n", cv->name, *(u32 *)(cv->data) );
334 break;
335 case k_var_dtype_f32:
336 fprintf( fp, "%s %.5f\n", cv->name, *(float *)(cv->data ) );
337 break;
338 }
339 }
340 }
341
342 fclose( fp );
343 }
344
345 VG_STATIC void _vg_console_free(void)
346 {
347 _vg_console_write_persistent();
348 }
349
350 /*
351 * splits src into tokens and fills out args as pointers to those tokens
352 * returns number of tokens
353 * dst must be as long as src
354 */
355 VG_STATIC int vg_console_tokenize( const char *src, char *dst,
356 const char *args[8] )
357 {
358 int arg_count = 0,
359 in_token = 0;
360
361 for( int i=0; 1; i ++ )
362 {
363 if( src[i] )
364 {
365 if( src[i] == ' ' || src[i] == '\t' )
366 {
367 if( in_token )
368 dst[i] = '\0';
369
370 in_token = 0;
371
372 if( arg_count == 8 )
373 break;
374 }
375 else
376 {
377 dst[i] = src[i];
378
379 if( !in_token )
380 {
381 args[ arg_count ++ ] = &dst[i];
382 in_token = 1;
383 }
384 }
385 }
386 else
387 {
388 dst[i] = '\0';
389 break;
390 }
391 }
392
393 return arg_count;
394 }
395
396 VG_STATIC vg_var *vg_console_match_var( const char *kw )
397 {
398 for( int i=0; i<vg_console.var_count; i ++ )
399 {
400 struct vg_var *cv = &vg_console.vars[ i ];
401 if( !strcmp( cv->name, kw ) )
402 {
403 return cv;
404 }
405 }
406
407 return NULL;
408 }
409
410 VG_STATIC vg_cmd *vg_console_match_cmd( const char *kw )
411 {
412 for( int i=0; i<vg_console.function_count; i ++ )
413 {
414 struct vg_cmd *cmd = &vg_console.functions[ i ];
415 if( !strcmp( cmd->name, kw ) )
416 {
417 return cmd;
418 }
419 }
420
421 return NULL;
422 }
423
424 VG_STATIC void vg_execute_console_input( const char *cmd )
425 {
426 char temp[512];
427 char const *args[8];
428 int arg_count = vg_console_tokenize( cmd, temp, args );
429
430 if( arg_count == 0 )
431 return;
432
433 int data_int;
434 float data_float;
435
436 vg_var *cv = vg_console_match_var( args[0] );
437 vg_cmd *fn = vg_console_match_cmd( args[0] );
438
439 assert( !(cv && fn) );
440
441 if( cv )
442 {
443 /* Cvar Matched, try get value */
444 if( arg_count >= 2 )
445 {
446 if( (cv->data_type == k_var_dtype_u32) ||
447 (cv->data_type == k_var_dtype_i32) )
448 {
449 data_int = atoi( args[1] );
450
451 *((int *)cv->data) = cv->opt_i32.clamp?
452 VG_MIN( VG_MAX(data_int, cv->opt_i32.min), cv->opt_i32.max ):
453 data_int;
454 }
455 else if( cv->data_type == k_var_dtype_f32 )
456 {
457 data_float = atof( args[1] );
458 *((float *)cv->data) = cv->opt_f32.clamp?
459 vg_minf( vg_maxf( data_float, cv->opt_f32.min),
460 cv->opt_f32.max ):
461 data_float;
462 }
463 }
464 else
465 {
466 if( cv->data_type == k_var_dtype_i32 )
467 vg_info( "= %d\n", *((int *)cv->data) );
468 else if( cv->data_type == k_var_dtype_u32 )
469 vg_info( "= %u\n", *((u32 *)cv->data) );
470 else if( cv->data_type == k_var_dtype_f32 )
471 vg_info( "= %.4f\n", *((float *)cv->data) );
472 }
473
474 return;
475 }
476 else if( fn )
477 {
478 fn->function( arg_count-1, args+1 );
479 return;
480 }
481
482 vg_error( "No command/var named '%s'. Use 'list' to view all\n", args[0] );
483 }
484
485 u32 str_lev_distance( const char *s1, const char *s2 )
486 {
487 u32 m = strlen( s1 ),
488 n = strlen( s2 );
489
490 if( m==0 ) return n;
491 if( n==0 ) return m;
492
493 assert( n+1 <= 256 );
494
495 u32 costs[ 256 ];
496
497 for( u32 k=0; k<=n; k++ )
498 costs[k] = k;
499
500 u32 i = 0;
501 for( u32 i=0; i<m; i++ )
502 {
503 costs[0] = i+1;
504
505 u32 corner = i;
506
507 for( u32 j=0; j<n; j++ )
508 {
509 u32 upper = costs[j+1];
510
511 if( s1[i] == s2[j] )
512 costs[ j+1 ] = corner;
513 else
514 {
515 u32 t = (upper < corner)? upper: corner;
516 costs[j+1] = ((costs[j] < t)? costs[j]: t) + 1;
517 }
518
519 corner = upper;
520 }
521 }
522
523 return costs[n];
524 }
525
526 u32 str_lcs( const char *s1, const char *s2 )
527 {
528 u32 m = VG_MIN( 31, strlen( s1 ) ),
529 n = VG_MIN( 31, strlen( s2 ) );
530
531 int suff[32][32],
532 result = 0;
533
534 for( int i=0; i<=m; i++ )
535 {
536 for( int j=0; j<=n; j++ )
537 {
538 if( i == 0 || j == 0 )
539 suff[i][j] = 0;
540 else if( s1[i-1] == s2[j-1] )
541 {
542 suff[i][j] = suff[i-1][j-1] + 1;
543 result = VG_MAX( result, suff[i][j] );
544 }
545 else
546 suff[i][j] = 0;
547 }
548 }
549
550 return result;
551 }
552
553 /* str must not fuckoff ever! */
554 VG_STATIC void console_suggest_score_text( const char *str, const char *input,
555 int minscore )
556 {
557 /* filter duplicates */
558 for( int i=0; i<vg_console.suggestion_count; i++ )
559 if( !strcmp( vg_console.suggestions[i].str, str ) )
560 return;
561
562 /* calc score */
563 u32 score = str_lcs( str, input );
564
565 if( score < minscore )
566 return;
567
568 int best_pos = vg_console.suggestion_count;
569 for( int j=best_pos-1; j>=0; j -- )
570 if( score > vg_console.suggestions[j].lev_score )
571 best_pos = j;
572
573 /* insert if good score */
574 if( best_pos < vg_list_size( vg_console.suggestions ) )
575 {
576 int start = VG_MIN( vg_console.suggestion_count,
577 vg_list_size( vg_console.suggestions )-1 );
578 for( int j=start; j>best_pos; j -- )
579 vg_console.suggestions[j] = vg_console.suggestions[j-1];
580
581 vg_console.suggestions[ best_pos ].str = str;
582 vg_console.suggestions[ best_pos ].len = strlen( str );
583 vg_console.suggestions[ best_pos ].lev_score = score;
584
585 if( vg_console.suggestion_count <
586 vg_list_size( vg_console.suggestions ) )
587 vg_console.suggestion_count ++;
588 }
589 }
590
591 VG_STATIC void console_update_suggestions(void)
592 {
593 vg_console.suggestion_count = 0;
594 vg_console.suggestion_select = -1;
595 vg_console.suggestion_maxlen = 0;
596
597 /*
598 * - must be typing something
599 * - must be at the end
600 * - prev char must not be a whitespace
601 * - cursors should match
602 */
603
604 if( vg_console.cursor_pos == 0 )
605 return;
606
607 if( vg_console.cursor_pos != vg_console.cursor_user )
608 return;
609
610 if( vg_console.input[ vg_console.cursor_pos ] != '\0' )
611 return;
612
613 if( (vg_console.input[ vg_console.cursor_pos -1 ] == ' ') ||
614 (vg_console.input[ vg_console.cursor_pos -1 ] == '\t') )
615 return;
616
617 char temp[128];
618 const char *args[8];
619
620 int token_count = vg_console_tokenize( vg_console.input, temp, args );
621
622 vg_console.suggestion_pastepos = args[token_count-1]-temp;
623
624 /* Score all our commands and cvars */
625 if( token_count == 1 )
626 {
627 for( int i=0; i<vg_console.var_count; i++ )
628 {
629 vg_var *cvar = &vg_console.vars[i];
630 console_suggest_score_text( cvar->name, args[0], 1 );
631 }
632
633 for( int i=0; i<vg_console.function_count; i++ )
634 {
635 vg_cmd *cmd = &vg_console.functions[i];
636 console_suggest_score_text( cmd->name, args[0], 1 );
637 }
638 }
639 else
640 {
641 vg_cmd *cmd = vg_console_match_cmd( args[0] );
642 vg_var *var = vg_console_match_var( args[0] );
643
644 assert( !( cmd && var ) );
645
646 if( cmd )
647 if( cmd->poll_suggest )
648 cmd->poll_suggest( token_count-1, &args[1] );
649 }
650
651 /* some post processing */
652 for( int i=0; i<vg_console.suggestion_count; i++ )
653 {
654 vg_console.suggestion_maxlen = VG_MAX( vg_console.suggestion_maxlen,
655 vg_console.suggestions[i].len );
656
657 if( vg_console.suggestions[i].lev_score <
658 vg_console.suggestions[0].lev_score/2 )
659 {
660 vg_console.suggestion_count = i;
661 return;
662 }
663 }
664 }
665
666 /*
667 * Console Interface
668 */
669 VG_STATIC void console_make_selection( int* start, int* end )
670 {
671 *start = VG_MIN( vg_console.cursor_pos, vg_console.cursor_user );
672 *end = VG_MAX( vg_console.cursor_pos, vg_console.cursor_user );
673 }
674
675 VG_STATIC void console_move_cursor( int* cursor0, int* cursor1,
676 int dir, int snap_together )
677 {
678 *cursor0 = VG_MAX( 0, vg_console.cursor_user + dir );
679 *cursor0 =
680 VG_MIN(
681 VG_MIN( vg_list_size(vg_console.input)-1, strlen( vg_console.input )),
682 *cursor0 );
683
684 if( snap_together )
685 *cursor1 = *cursor0;
686 }
687
688 VG_STATIC int console_makeroom( int datastart, int length )
689 {
690 int move_to = VG_MIN( datastart+length, vg_list_size( vg_console.input )-1 );
691 int move_amount = strlen( vg_console.input )-datastart;
692 int move_end =
693 VG_MIN( move_to+move_amount, vg_list_size( vg_console.input )-1 );
694 move_amount = move_end-move_to;
695
696 if( move_amount )
697 memmove( &vg_console.input[ move_to ],
698 &vg_console.input[ datastart ],
699 move_end-move_to );
700
701 vg_console.input[ move_end ] = '\0';
702
703 return VG_MIN( length, vg_list_size( vg_console.input )-datastart-1 );
704 }
705
706 VG_STATIC int console_delete_char( int direction )
707 {
708 int start, end;
709 console_make_selection( &start, &end );
710
711 /* There is no selection */
712 if( !(end-start) )
713 {
714 if( direction == 1 ) end = VG_MIN( end+1, strlen( vg_console.input ) );
715 else if( direction == -1 ) start = VG_MAX( start-1, 0 );
716 }
717
718 /* Still no selction, no need to do anything */
719 if( !(end-start) )
720 return start;
721
722 /* Copy the end->terminator to start */
723 int remaining_length = strlen( vg_console.input )+1-end;
724 memmove( &vg_console.input[ start ],
725 &vg_console.input[ end ],
726 remaining_length );
727 return start;
728 }
729
730 VG_STATIC void console_to_clipboard(void)
731 {
732 int start, end;
733 console_make_selection( &start, &end );
734 char buffer[512];
735
736 if( end-start )
737 {
738 memcpy( buffer, &vg_console.input[ start ], end-start );
739 buffer[ end-start ] = 0x00;
740 SDL_SetClipboardText( buffer );
741 }
742 }
743
744 VG_STATIC void console_clipboard_paste(void)
745 {
746 if( !SDL_HasClipboardText() )
747 return;
748
749 char *text = SDL_GetClipboardText();
750
751 if( !text )
752 return;
753
754 int datastart = console_delete_char( 0 );
755 int length = strlen( text );
756 int cpylength = console_makeroom( datastart, length );
757
758 memcpy( vg_console.input + datastart, text, cpylength);
759 console_move_cursor( &vg_console.cursor_user,
760 &vg_console.cursor_pos, cpylength, 1 );
761 SDL_free( text );
762
763 console_update_suggestions();
764 }
765
766 VG_STATIC void console_put_char( char c )
767 {
768 if( !vg_console.enabled )
769 return;
770
771 vg_console.cursor_user = console_delete_char(0);
772
773 if( console_makeroom( vg_console.cursor_user, 1 ) )
774 vg_console.input[ vg_console.cursor_user ] = c;
775
776 console_move_cursor( &vg_console.cursor_user, &vg_console.cursor_pos, 1, 1 );
777 }
778
779 VG_STATIC void console_history_get( char* buf, int entry_num )
780 {
781 if( !vg_console.history_count )
782 return;
783
784 int offset = VG_MIN( entry_num, vg_console.history_count -1 ),
785 pick = (vg_console.history_last - offset) %
786 vg_list_size( vg_console.history );
787 strcpy( buf, vg_console.history[ pick ] );
788 }
789
790 /* Receed secondary cursor */
791 VG_STATIC void _console_left_select(void)
792 {
793 console_move_cursor( &vg_console.cursor_user, NULL, -1, 0 );
794 }
795
796 /* Match and receed both cursors */
797 VG_STATIC void _console_left(void)
798 {
799 int cursor_diff = vg_console.cursor_pos - vg_console.cursor_user? 0: 1;
800
801 console_move_cursor( &vg_console.cursor_user,
802 &vg_console.cursor_pos, -cursor_diff, 1 );
803 }
804
805 VG_STATIC void _console_right_select(void)
806 {
807 console_move_cursor( &vg_console.cursor_user, NULL, 1, 0 );
808 }
809
810 VG_STATIC void _console_right(void)
811 {
812 int cursor_diff = vg_console.cursor_pos - vg_console.cursor_user? 0: 1;
813
814 console_move_cursor( &vg_console.cursor_user,
815 &vg_console.cursor_pos, +cursor_diff, 1 );
816 }
817
818 VG_STATIC void _console_down(void)
819 {
820 vg_console.history_pos = VG_MAX( 0, vg_console.history_pos-1 );
821 console_history_get( vg_console.input, vg_console.history_pos );
822
823 console_move_cursor( &vg_console.cursor_user,
824 &vg_console.cursor_pos,
825 vg_list_size(vg_console.input)-1, 1 );
826 }
827
828 VG_STATIC void _console_up(void)
829 {
830 vg_console.history_pos = VG_MAX
831 (
832 0,
833 VG_MIN
834 (
835 vg_console.history_pos+1,
836 VG_MIN
837 (
838 vg_list_size( vg_console.history ),
839 vg_console.history_count - 1
840 )
841 )
842 );
843
844 console_history_get( vg_console.input, vg_console.history_pos );
845 console_move_cursor( &vg_console.cursor_user,
846 &vg_console.cursor_pos,
847 vg_list_size(vg_console.input)-1, 1);
848 }
849
850 VG_STATIC void _console_backspace(void)
851 {
852 vg_console.cursor_user = console_delete_char( -1 );
853 vg_console.cursor_pos = vg_console.cursor_user;
854
855 console_update_suggestions();
856 }
857
858 VG_STATIC void _console_delete(void)
859 {
860 vg_console.cursor_user = console_delete_char( 1 );
861 vg_console.cursor_pos = vg_console.cursor_user;
862
863 console_update_suggestions();
864 }
865
866 VG_STATIC void _console_home_select(void)
867 {
868 console_move_cursor( &vg_console.cursor_user, NULL, -10000, 0 );
869 }
870
871 VG_STATIC void _console_home(void)
872 {
873 console_move_cursor( &vg_console.cursor_user,
874 &vg_console.cursor_pos, -10000, 1 );
875 }
876
877 VG_STATIC void _console_end_select(void)
878 {
879 console_move_cursor( &vg_console.cursor_user, NULL, 10000, 0 );
880 }
881
882 VG_STATIC void _console_end(void)
883 {
884 console_move_cursor( &vg_console.cursor_user,
885 &vg_console.cursor_pos,
886 vg_list_size(vg_console.input)-1, 1 );
887 }
888
889 VG_STATIC void _console_select_all(void)
890 {
891 console_move_cursor( &vg_console.cursor_user, NULL, 10000, 0);
892 console_move_cursor( &vg_console.cursor_pos, NULL, -10000, 0);
893 }
894
895 VG_STATIC void _console_cut(void)
896 {
897 console_to_clipboard();
898 vg_console.cursor_user = console_delete_char(0);
899 vg_console.cursor_pos = vg_console.cursor_user;
900 }
901
902 VG_STATIC void _console_enter(void)
903 {
904 if( !strlen( vg_console.input ) )
905 return;
906
907 vg_info( "%s\n", vg_console.input );
908
909 if( strcmp( vg_console.input,
910 vg_console.history[ vg_console.history_last ]) )
911 {
912 vg_console.history_last = ( vg_console.history_last + 1) %
913 vg_list_size(vg_console.history );
914 vg_console.history_count =
915 VG_MIN( vg_list_size( vg_console.history ),
916 vg_console.history_count + 1 );
917 strcpy( vg_console.history[ vg_console.history_last ],
918 vg_console.input );
919 }
920
921 vg_console.history_pos = -1;
922 vg_execute_console_input( vg_console.input );
923 console_move_cursor( &vg_console.cursor_user,
924 &vg_console.cursor_pos, -10000, 1 );
925 vg_console.input[0] = '\0';
926
927 console_update_suggestions();
928 }
929
930 /*
931 * Suggestion controls
932 */
933 VG_STATIC void _console_fetch_suggestion(void)
934 {
935 char *target = &vg_console.input[ vg_console.suggestion_pastepos ];
936
937 if( vg_console.suggestion_select == -1 )
938 {
939 strcpy( target, vg_console.input_copy );
940 console_move_cursor( &vg_console.cursor_user,
941 &vg_console.cursor_pos, 10000, 1 );
942 }
943 else
944 {
945 strncpy( target,
946 vg_console.suggestions[ vg_console.suggestion_select ].str,
947 vg_list_size( vg_console.input )-1 );
948
949 console_move_cursor( &vg_console.cursor_user,
950 &vg_console.cursor_pos, 10000, 1 );
951 console_put_char( ' ' );
952 }
953 }
954
955 VG_STATIC void _console_suggest_store_normal(void)
956 {
957 if( vg_console.suggestion_select == -1 )
958 {
959 char *target = &vg_console.input[ vg_console.suggestion_pastepos ];
960 strcpy( vg_console.input_copy, target );
961 }
962 }
963
964 VG_STATIC void _console_suggest_next(void)
965 {
966 if( vg_console.suggestion_count )
967 {
968 _console_suggest_store_normal();
969
970 vg_console.suggestion_select ++;
971
972 if( vg_console.suggestion_select >= vg_console.suggestion_count )
973 vg_console.suggestion_select = -1;
974
975 _console_fetch_suggestion();
976 }
977 }
978
979 VG_STATIC void _console_suggest_prev(void)
980 {
981 if( vg_console.suggestion_count )
982 {
983 _console_suggest_store_normal();
984
985 vg_console.suggestion_select --;
986
987 if( vg_console.suggestion_select < -1 )
988 vg_console.suggestion_select = vg_console.suggestion_count-1;
989
990 _console_fetch_suggestion();
991 }
992 }
993
994 /*
995 * Handles binds
996 */
997 VG_STATIC void console_proc_key( SDL_Keysym ev )
998 {
999 /* Open / close console */
1000 if( ev.sym == SDLK_BACKQUOTE )
1001 {
1002 vg_console.enabled = !vg_console.enabled;
1003
1004 if( vg_console.enabled )
1005 SDL_StartTextInput();
1006 else
1007 SDL_StopTextInput();
1008 }
1009
1010 if( !vg_console.enabled ) return;
1011
1012 struct console_mapping
1013 {
1014 u16 mod;
1015 SDL_Keycode key;
1016
1017 void (*handler)(void);
1018 }
1019 mappings[] =
1020 {
1021 { 0, SDLK_LEFT, _console_left },
1022 { KMOD_SHIFT, SDLK_LEFT, _console_left_select },
1023 { 0, SDLK_RIGHT, _console_right },
1024 { KMOD_SHIFT, SDLK_RIGHT, _console_right_select },
1025 { 0, SDLK_DOWN, _console_down },
1026 { 0, SDLK_UP, _console_up },
1027 { 0, SDLK_BACKSPACE, _console_backspace },
1028 { 0, SDLK_DELETE, _console_delete },
1029 { 0, SDLK_HOME, _console_home },
1030 { KMOD_SHIFT, SDLK_HOME, _console_home_select },
1031 { 0, SDLK_END, _console_end },
1032 { KMOD_SHIFT, SDLK_END, _console_end_select },
1033 { KMOD_CTRL, SDLK_a, _console_select_all },
1034 { KMOD_CTRL, SDLK_c, console_to_clipboard },
1035 { KMOD_CTRL, SDLK_x, _console_cut },
1036 { KMOD_CTRL, SDLK_v, console_clipboard_paste },
1037 { 0, SDLK_RETURN, _console_enter },
1038 { KMOD_CTRL, SDLK_n, _console_suggest_next },
1039 { KMOD_CTRL, SDLK_p, _console_suggest_prev }
1040 };
1041
1042 SDL_Keymod mod = 0;
1043
1044 if( ev.mod & KMOD_SHIFT )
1045 mod |= KMOD_SHIFT;
1046
1047 if( ev.mod & KMOD_CTRL )
1048 mod |= KMOD_CTRL;
1049
1050 if( ev.mod & KMOD_ALT )
1051 mod |= KMOD_ALT;
1052
1053 for( int i=0; i<vg_list_size( mappings ); i++ )
1054 {
1055 struct console_mapping *mapping = &mappings[i];
1056
1057 if( mapping->key == ev.sym )
1058 {
1059 if( mapping->mod == 0 )
1060 {
1061 if( mod == 0 )
1062 {
1063 mapping->handler();
1064 return;
1065 }
1066 }
1067 else if( (mod & mapping->mod) == mapping->mod )
1068 {
1069 mapping->handler();
1070 return;
1071 }
1072 }
1073 }
1074 }
1075
1076 /*
1077 * Callback for text entry mode
1078 */
1079 VG_STATIC void console_proc_utf8( const char *text )
1080 {
1081 const char *ptr = text;
1082
1083 while( *ptr )
1084 {
1085 if( *ptr != '`' )
1086 console_put_char( *ptr );
1087 ptr ++;
1088 }
1089
1090 console_update_suggestions();
1091 }
1092
1093 #endif /* VG_CONSOLE_H */