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