1 /* Copyright (C) 2021-2023 Harry Godden (hgn) - All Rights Reserved */
3 /* TODO: String types using dynamic, vg_str! */
14 #include "vg_string.h"
16 #define VG_VAR_F32( NAME, ... ) \
17 { u32 flags=0x00; __VA_ARGS__ ;\
18 vg_console_reg_var( #NAME, &NAME, k_var_dtype_f32, flags ); }
20 #define VG_VAR_I32( NAME, ... ) \
21 { u32 flags=0x00; __VA_ARGS__ ;\
22 vg_console_reg_var( #NAME, &NAME, k_var_dtype_i32, flags ); }
24 #define VG_VAR_PERSISTENT 0x1
25 #define VG_VAR_CHEAT 0x2
27 typedef struct vg_var vg_var
;
28 typedef struct vg_cmd vg_cmd
;
47 int (*function
)( int argc
, char const *argv
[] );
48 void (*poll_suggest
)( int argc
, char const *argv
[] );
61 int suggestion_select
,
65 u32 var_count
, function_count
;
71 int history_last
, history_pos
, history_count
;
77 static void _vg_console_draw( void );
78 void _vg_console_println( const char *str
);
79 static int _vg_console_list( int argc
, char const *argv
[] );
80 static void _vg_console_init(void);
81 static void _vg_console_write_persistent(void);
82 static void _vg_console_free(void);
83 static void vg_execute_console_input( const char *cmd
, bool silent
);
88 static void console_history_get( char* buf
, int entry_num
);
89 static int _vg_console_enabled(void);
90 static void console_proc_key( SDL_Keysym ev
);
95 static int _vg_console_enabled(void){
96 return vg_console
.enabled
;
100 void vg_console_reg_var( const char *alias
, void *ptr
, enum vg_var_dtype type
,
103 if( vg_console
.var_count
> vg_list_size(vg_console
.vars
) )
104 vg_fatal_error( "Too many vars registered" );
106 vg_var
*var
= &vg_console
.vars
[ vg_console
.var_count
++ ];
109 var
->data_type
= type
;
112 vg_info( "Console variable '%s' registered\n", alias
);
116 void vg_console_reg_cmd( const char *alias
,
117 int (*function
)(int argc
, const char *argv
[]),
118 void (*poll_suggest
)(int argc
, const char *argv
[]) )
120 if( vg_console
.function_count
> vg_list_size(vg_console
.functions
) )
121 vg_fatal_error( "Too many functions registered" );
123 vg_cmd
*cmd
= &vg_console
.functions
[ vg_console
.function_count
++ ];
125 cmd
->function
= function
;
126 cmd
->poll_suggest
= poll_suggest
;
129 vg_info( "Console function '%s' registered\n", alias
);
132 static int _vg_console_list( int argc
, char const *argv
[] ){
133 for( int i
=0; i
<vg_console
.function_count
; i
++ ){
134 struct vg_cmd
*cmd
= &vg_console
.functions
[ i
];
135 vg_info( "* %s\n", cmd
->name
);
138 for( int i
=0; i
<vg_console
.var_count
; i
++ ){
139 struct vg_var
*cv
= &vg_console
.vars
[ i
];
141 (const char *[]){ "i32","u32","f32","str" }[cv
->data_type
],
148 int _test_break( int argc
, const char *argv
[] ){
149 vg_fatal_error( "Test crash from main, after loading (console)" );
153 int _vg_console_exec( int argc
, const char *argv
[] ){
154 if( argc
< 1 ) return 0;
157 if( argc
== 2 ) silent
=1;
160 strcpy( path
, "cfg/" );
161 strncat( path
, argv
[0], 250 );
163 FILE *fp
= fopen( path
, "r" );
167 while( fgets( line
, sizeof( line
), fp
) ){
168 line
[ strcspn( line
, "\r\n#" ) ] = 0x00;
170 if( line
[0] != 0x00 ){
171 vg_execute_console_input( line
, silent
);
178 vg_error( "Could not open '%s'\n", path
);
184 static void _vg_console_init(void){
185 vg_console_reg_cmd( "list", _vg_console_list
, NULL
);
186 vg_console_reg_cmd( "crash", _test_break
, NULL
);
187 vg_console_reg_cmd( "exec", _vg_console_exec
, NULL
);
188 vg_console_reg_var( "cheats", &vg_console
.cheats
, k_var_dtype_i32
,
197 static void vg_console_load_autos(void){
198 _vg_console_exec( 2, (const char *[]){ "auto.conf", "silent" } );
201 static void _vg_console_write_persistent(void){
202 FILE *fp
= fopen( "cfg/auto.conf", "w" );
205 vg_error( "Cannot open cfg/auto.conf\n" );
209 for( int i
=0; i
<vg_console
.var_count
; i
++ ){
210 struct vg_var
*cv
= &vg_console
.vars
[i
];
212 if( cv
->flags
& VG_VAR_PERSISTENT
){
213 if( cv
->data_type
== k_var_dtype_i32
){
214 fprintf( fp
, "%s %d\n", cv
->name
, *(i32
*)(cv
->data
) );
216 else if( cv
->data_type
== k_var_dtype_u32
){
217 fprintf( fp
, "%s %u\n", cv
->name
, *(u32
*)(cv
->data
) );
219 else if( cv
->data_type
== k_var_dtype_f32
){
220 fprintf( fp
, "%s %.5f\n", cv
->name
, *(float *)(cv
->data
) );
222 else if( cv
->data_type
== k_var_dtype_str
){
223 vg_str
*str
= cv
->data
;
224 if( str
->buffer
&& (str
->i
> 0) )
225 fprintf( fp
, "%s %s\n", cv
->name
, str
->buffer
);
233 static void _vg_console_free(void)
235 _vg_console_write_persistent();
239 * splits src into tokens and fills out args as pointers to those tokens
240 * returns number of tokens
241 * dst must be as long as src
243 static int vg_console_tokenize( const char *src
, char *dst
,
244 const char *args
[8] )
249 for( int i
=0;; i
++ ){
251 if( src
[i
] == ' ' || src
[i
] == '\t' ){
264 args
[ arg_count
++ ] = &dst
[i
];
278 static vg_var
*vg_console_match_var( const char *kw
)
280 for( int i
=0; i
<vg_console
.var_count
; i
++ ){
281 struct vg_var
*cv
= &vg_console
.vars
[ i
];
282 if( !strcmp( cv
->name
, kw
) ){
290 static vg_cmd
*vg_console_match_cmd( const char *kw
)
292 for( int i
=0; i
<vg_console
.function_count
; i
++ ){
293 struct vg_cmd
*cmd
= &vg_console
.functions
[ i
];
294 if( !strcmp( cmd
->name
, kw
) ){
302 static void vg_execute_console_input( const char *cmd
, bool silent
){
305 int arg_count
= vg_console_tokenize( cmd
, temp
, args
);
310 vg_var
*cv
= vg_console_match_var( args
[0] );
311 vg_cmd
*fn
= vg_console_match_cmd( args
[0] );
313 assert( !(cv
&& fn
) );
316 /* Cvar Matched, try get value */
317 if( arg_count
>= 2 ){
318 if( cv
->flags
& VG_VAR_CHEAT
){
319 if( !vg_console
.cheats
&& !silent
){
320 vg_error( "variable is cheat protected\n" );
325 if( (cv
->data_type
== k_var_dtype_u32
) ||
326 (cv
->data_type
== k_var_dtype_i32
) ){
328 *ptr
= atoi( args
[1] );
330 else if( cv
->data_type
== k_var_dtype_f32
){
331 float *ptr
= cv
->data
;
332 *ptr
= atof( args
[1] );
334 else if( cv
->data_type
== k_var_dtype_str
){
335 vg_str
*str
= cv
->data
;
337 vg_strnull( str
, NULL
, -1 );
339 for( int i
=1; i
<arg_count
; i
++ ){
340 vg_strcat( str
, args
[i
] );
341 if( i
!=arg_count
-1 ) vg_strcatch( str
, ' ' );
346 if( cv
->data_type
== k_var_dtype_i32
)
347 vg_info( "= %d\n", *((int *)cv
->data
) );
348 else if( cv
->data_type
== k_var_dtype_u32
)
349 vg_info( "= %u\n", *((u32
*)cv
->data
) );
350 else if( cv
->data_type
== k_var_dtype_f32
)
351 vg_info( "= %.4f\n", *((float *)cv
->data
) );
352 else if( cv
->data_type
== k_var_dtype_str
){
353 vg_str
*str
= cv
->data
;
354 vg_info( "= '%s'\n", str
->buffer
);
361 fn
->function( arg_count
-1, args
+1 );
366 vg_error( "No command/var named '%s'. Use 'list' to view all\n",args
[0]);
369 u32
str_lev_distance( const char *s1
, const char *s2
){
370 u32 m
= strlen( s1
),
376 assert( n
+1 <= 256 );
380 for( u32 k
=0; k
<=n
; k
++ )
384 for( u32 i
=0; i
<m
; i
++ ){
389 for( u32 j
=0; j
<n
; j
++ ){
390 u32 upper
= costs
[j
+1];
393 costs
[ j
+1 ] = corner
;
395 u32 t
= (upper
< corner
)? upper
: corner
;
396 costs
[j
+1] = ((costs
[j
] < t
)? costs
[j
]: t
) + 1;
406 u32
str_lcs( const char *s1
, const char *s2
){
407 u32 m
= VG_MIN( 31, strlen( s1
) ),
408 n
= VG_MIN( 31, strlen( s2
) );
413 for( int i
=0; i
<=m
; i
++ ){
414 for( int j
=0; j
<=n
; j
++ ){
415 if( i
== 0 || j
== 0 )
417 else if( s1
[i
-1] == s2
[j
-1] ){
418 suff
[i
][j
] = suff
[i
-1][j
-1] + 1;
419 result
= VG_MAX( result
, suff
[i
][j
] );
429 /* str must not fuckoff ever! */
430 static void console_suggest_score_text( const char *str
, const char *input
,
433 /* filter duplicates */
434 for( int i
=0; i
<vg_console
.suggestion_count
; i
++ )
435 if( !strcmp( vg_console
.suggestions
[i
].str
, str
) )
439 u32 score
= str_lcs( str
, input
);
441 if( score
< minscore
)
444 int best_pos
= vg_console
.suggestion_count
;
445 for( int j
=best_pos
-1; j
>=0; j
-- )
446 if( score
> vg_console
.suggestions
[j
].lev_score
)
449 /* insert if good score */
450 if( best_pos
< vg_list_size( vg_console
.suggestions
) ){
451 int start
= VG_MIN( vg_console
.suggestion_count
,
452 vg_list_size( vg_console
.suggestions
)-1 );
453 for( int j
=start
; j
>best_pos
; j
-- )
454 vg_console
.suggestions
[j
] = vg_console
.suggestions
[j
-1];
456 vg_console
.suggestions
[ best_pos
].str
= str
;
457 vg_console
.suggestions
[ best_pos
].len
= strlen( str
);
458 vg_console
.suggestions
[ best_pos
].lev_score
= score
;
460 if( vg_console
.suggestion_count
<
461 vg_list_size( vg_console
.suggestions
) )
462 vg_console
.suggestion_count
++;
466 static void console_update_suggestions(void)
468 if( vg_ui
.focused_control_type
!= k_ui_control_textbox
||
469 vg_ui
.textbuf
!= vg_console
.input
)
472 vg_console
.suggestion_count
= 0;
473 vg_console
.suggestion_select
= -1;
474 vg_console
.suggestion_maxlen
= 0;
477 * - must be typing something
478 * - must be at the end
479 * - prev char must not be a whitespace
480 * - cursors should match
483 if( vg_ui
.textbox
.cursor_pos
== 0 ) return;
484 if( vg_ui
.textbox
.cursor_pos
!= vg_ui
.textbox
.cursor_user
) return;
485 if( vg_console
.input
[ vg_ui
.textbox
.cursor_pos
] != '\0' ) return;
487 if( (vg_console
.input
[ vg_ui
.textbox
.cursor_pos
-1 ] == ' ') ||
488 (vg_console
.input
[ vg_ui
.textbox
.cursor_pos
-1 ] == '\t') )
494 int token_count
= vg_console_tokenize( vg_console
.input
, temp
, args
);
495 if( !token_count
) return;
496 vg_console
.suggestion_pastepos
= args
[token_count
-1]-temp
;
498 /* Score all our commands and cvars */
499 if( token_count
== 1 ){
500 for( int i
=0; i
<vg_console
.var_count
; i
++ ){
501 vg_var
*cvar
= &vg_console
.vars
[i
];
502 console_suggest_score_text( cvar
->name
, args
[0], 1 );
505 for( int i
=0; i
<vg_console
.function_count
; i
++ ){
506 vg_cmd
*cmd
= &vg_console
.functions
[i
];
507 console_suggest_score_text( cmd
->name
, args
[0], 1 );
511 vg_cmd
*cmd
= vg_console_match_cmd( args
[0] );
512 vg_var
*var
= vg_console_match_var( args
[0] );
514 assert( !( cmd
&& var
) );
517 if( cmd
->poll_suggest
)
518 cmd
->poll_suggest( token_count
-1, &args
[1] );
521 /* some post processing */
522 for( int i
=0; i
<vg_console
.suggestion_count
; i
++ ){
523 vg_console
.suggestion_maxlen
= VG_MAX( vg_console
.suggestion_maxlen
,
524 vg_console
.suggestions
[i
].len
);
526 if( vg_console
.suggestions
[i
].lev_score
<
527 vg_console
.suggestions
[0].lev_score
/2 )
529 vg_console
.suggestion_count
= i
;
536 * Suggestion controls
538 static void _console_fetch_suggestion(void)
540 char *target
= &vg_console
.input
[ vg_console
.suggestion_pastepos
];
542 if( vg_console
.suggestion_select
== -1 ){
543 strcpy( target
, vg_console
.input_copy
);
544 _ui_textbox_move_cursor( &vg_ui
.textbox
.cursor_user
,
545 &vg_ui
.textbox
.cursor_pos
, 10000, 1 );
549 vg_console
.suggestions
[ vg_console
.suggestion_select
].str
,
550 vg_list_size( vg_console
.input
)-1 );
552 _ui_textbox_move_cursor( &vg_ui
.textbox
.cursor_user
,
553 &vg_ui
.textbox
.cursor_pos
, 10000, 1 );
554 _ui_textbox_put_char( ' ' );
558 static void _console_suggest_store_normal(void)
560 if( vg_console
.suggestion_select
== -1 ){
561 char *target
= &vg_console
.input
[ vg_console
.suggestion_pastepos
];
562 strcpy( vg_console
.input_copy
, target
);
566 static void _console_suggest_next(void)
568 if( vg_console
.suggestion_count
){
569 _console_suggest_store_normal();
571 vg_console
.suggestion_select
++;
573 if( vg_console
.suggestion_select
>= vg_console
.suggestion_count
)
574 vg_console
.suggestion_select
= -1;
576 _console_fetch_suggestion();
580 static void _console_suggest_prev(void)
582 if( vg_console
.suggestion_count
){
583 _console_suggest_store_normal();
585 vg_console
.suggestion_select
--;
587 if( vg_console
.suggestion_select
< -1 )
588 vg_console
.suggestion_select
= vg_console
.suggestion_count
-1;
590 _console_fetch_suggestion();
594 static void _vg_console_on_update( char *buf
, u32 len
)
596 if( buf
== vg_console
.input
){
597 console_update_suggestions();
601 static void console_history_get( char* buf
, int entry_num
)
603 if( !vg_console
.history_count
)
606 int offset
= VG_MIN( entry_num
, vg_console
.history_count
-1 ),
607 pick
= (vg_console
.history_last
- offset
) %
608 vg_list_size( vg_console
.history
);
609 strcpy( buf
, vg_console
.history
[ pick
] );
612 static void _vg_console_on_up( char *buf
, u32 len
)
614 if( buf
== vg_console
.input
){
615 vg_console
.history_pos
=
621 vg_console
.history_pos
+1,
624 vg_list_size( vg_console
.history
),
625 vg_console
.history_count
- 1
630 console_history_get( vg_console
.input
, vg_console
.history_pos
);
631 _ui_textbox_move_cursor( &vg_ui
.textbox
.cursor_user
,
632 &vg_ui
.textbox
.cursor_pos
,
633 vg_list_size(vg_console
.input
)-1, 1);
637 static void _vg_console_on_down( char *buf
, u32 len
)
639 if( buf
== vg_console
.input
){
640 vg_console
.history_pos
= VG_MAX( 0, vg_console
.history_pos
-1 );
641 console_history_get( vg_console
.input
, vg_console
.history_pos
);
643 _ui_textbox_move_cursor( &vg_ui
.textbox
.cursor_user
,
644 &vg_ui
.textbox
.cursor_pos
,
645 vg_list_size(vg_console
.input
)-1, 1 );
649 static void _vg_console_on_enter( char *buf
, u32 len
)
651 if( buf
== vg_console
.input
){
652 if( !strlen( vg_console
.input
) )
655 vg_info( "%s\n", vg_console
.input
);
657 if( strcmp( vg_console
.input
,
658 vg_console
.history
[ vg_console
.history_last
]) )
660 vg_console
.history_last
= ( vg_console
.history_last
+ 1) %
661 vg_list_size(vg_console
.history
);
662 vg_console
.history_count
=
663 VG_MIN( vg_list_size( vg_console
.history
),
664 vg_console
.history_count
+ 1 );
665 strcpy( vg_console
.history
[ vg_console
.history_last
],
669 vg_console
.history_pos
= -1;
670 vg_execute_console_input( vg_console
.input
, 0 );
671 _ui_textbox_move_cursor( &vg_ui
.textbox
.cursor_user
,
672 &vg_ui
.textbox
.cursor_pos
, -10000, 1 );
674 vg_console
.input
[0] = '\0';
675 console_update_suggestions();
679 static void _vg_console_draw(void)
681 if( !vg_console
.enabled
) return;
683 SDL_AtomicLock( &log_print_sl
);
685 int ptr
= vg_log
.log_line_current
;
686 int const fh
= vg_ui
.font
->line_height
, log_lines
= 32;
687 int console_lines
= VG_MIN( log_lines
, vg_log
.log_line_count
);
689 ui_rect rect_log
= { 0, 0, vg
.window_x
, log_lines
*fh
},
690 rect_input
= { 0, log_lines
*fh
+ 1, vg
.window_x
, fh
*2 },
691 rect_line
= { 0, 0, vg
.window_x
, fh
};
696 u32 bg_colour
= (ui_colour( k_ui_bg
)&0x00ffffff)|0x9f000000;
698 ui_fill( rect_log
, bg_colour
);
699 rect_line
[1] = rect_log
[1]+rect_log
[3]-fh
;
701 for( int i
=0; i
<console_lines
; i
++ ){
704 if( ptr
< 0 ) ptr
= vg_list_size( vg_log
.log
)-1;
706 ui_text( rect_line
, vg_log
.log
[ptr
], 1, k_ui_align_left
, 0 );
714 struct ui_textbox_callbacks callbacks
= {
715 .up
= _vg_console_on_up
,
716 .down
= _vg_console_on_down
,
717 .change
= _vg_console_on_update
,
718 .enter
= _vg_console_on_enter
,
720 ui_textbox( rect_input
, NULL
,
721 vg_console
.input
, vg_list_size(vg_console
.input
), 1,
722 UI_TEXTBOX_AUTOFOCUS
, &callbacks
);
727 if( vg_console
.suggestion_count
){
728 ui_rect rect_suggest
;
729 rect_copy( rect_input
, rect_suggest
);
731 rect_suggest
[0] += 6 + vg_ui
.font
->spacing
*vg_console
.suggestion_pastepos
;
732 rect_suggest
[1] += rect_input
[3];
733 rect_suggest
[2] = vg_ui
.font
->spacing
* vg_console
.suggestion_maxlen
;
734 rect_suggest
[3] = vg_console
.suggestion_count
* fh
;
736 ui_fill( rect_suggest
, bg_colour
);
738 rect_suggest
[3] = fh
;
740 for( int i
=0; i
<vg_console
.suggestion_count
; i
++ ){
742 if( i
== vg_console
.suggestion_select
){
743 ui_fill( rect_suggest
, ui_colour( k_ui_orange
) );
744 text_colour
= ui_colourcont( k_ui_orange
);
746 else text_colour
= ui_colourcont( k_ui_bg
);
748 ui_text( rect_suggest
, vg_console
.suggestions
[i
].str
, 1,
749 k_ui_align_left
, text_colour
);
751 rect_suggest
[1] += fh
;
755 SDL_AtomicUnlock( &log_print_sl
);
759 #endif /* VG_CONSOLE_H */