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