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