1 /* Copyright (C) 2021-2023 Harry Godden (hgn) - All Rights Reserved */
10 #include "vg/vg_imgui.h"
11 #include "vg/vg_log.h"
13 #define VG_VAR_F32( NAME, ... ) \
14 { u32 flags=0x00; __VA_ARGS__ ;\
15 vg_console_reg_var( #NAME, &NAME, k_var_dtype_f32, flags ); }
17 #define VG_VAR_I32( NAME, ... ) \
18 { u32 flags=0x00; __VA_ARGS__ ;\
19 vg_console_reg_var( #NAME, &NAME, k_var_dtype_i32, flags ); }
21 #define VG_VAR_PERSISTENT 0x1
22 #define VG_VAR_CHEAT 0x2
24 typedef struct vg_var vg_var
;
25 typedef struct vg_cmd vg_cmd
;
44 int (*function
)( int argc
, char const *argv
[] );
45 void (*poll_suggest
)( int argc
, char const *argv
[] );
58 int suggestion_select
,
62 u32 var_count
, function_count
;
68 int history_last
, history_pos
, history_count
;
75 VG_STATIC
void _vg_console_draw( void );
76 void _vg_console_println( const char *str
);
77 VG_STATIC
int _vg_console_list( int argc
, char const *argv
[] );
78 VG_STATIC
void _vg_console_init(void);
79 VG_STATIC
void _vg_console_write_persistent(void);
80 VG_STATIC
void _vg_console_free(void);
81 VG_STATIC
void vg_execute_console_input( const char *cmd
);
86 VG_STATIC
void console_history_get( char* buf
, int entry_num
);
87 VG_STATIC
int _vg_console_enabled(void);
88 VG_STATIC
void console_proc_key( SDL_Keysym ev
);
93 VG_STATIC
int _vg_console_enabled(void)
95 return vg_console
.enabled
;
99 void vg_console_reg_var( const char *alias
, void *ptr
, enum vg_var_dtype type
,
102 if( vg_console
.var_count
> vg_list_size(vg_console
.vars
) )
103 vg_fatal_error( "Too many vars registered" );
105 vg_var
*var
= &vg_console
.vars
[ vg_console
.var_count
++ ];
108 var
->data_type
= type
;
111 vg_info( "Console variable '%s' registered\n", alias
);
115 void vg_console_reg_cmd( const char *alias
,
116 int (*function
)(int argc
, const char *argv
[]),
117 void (*poll_suggest
)(int argc
, const char *argv
[]) )
119 if( vg_console
.function_count
> vg_list_size(vg_console
.functions
) )
120 vg_fatal_error( "Too many functions registered" );
122 vg_cmd
*cmd
= &vg_console
.functions
[ vg_console
.function_count
++ ];
124 cmd
->function
= function
;
125 cmd
->poll_suggest
= poll_suggest
;
128 vg_info( "Console function '%s' registered\n", alias
);
131 VG_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
];
140 vg_info( "%s\n", cv
->name
);
146 int _test_break( int argc
, const char *argv
[] )
148 vg_fatal_error( "Test crash from main, after loading (console)" );
152 int _vg_console_exec( int argc
, const char *argv
[] )
154 if( argc
< 1 ) return 0;
157 strcpy( path
, "cfg/" );
158 strncat( path
, argv
[0], 250 );
160 FILE *fp
= fopen( path
, "r" );
164 while( fgets( line
, sizeof( line
), fp
) ){
165 line
[ strcspn( line
, "\r\n#" ) ] = 0x00;
167 if( line
[0] != 0x00 ){
168 vg_execute_console_input( line
);
175 vg_error( "Could not open '%s'\n", path
);
181 VG_STATIC
void _vg_console_init(void)
183 vg_console_reg_cmd( "list", _vg_console_list
, NULL
);
184 vg_console_reg_cmd( "crash", _test_break
, NULL
);
185 vg_console_reg_cmd( "exec", _vg_console_exec
, NULL
);
188 VG_STATIC
void vg_console_load_autos(void)
190 _vg_console_exec( 1, (const char *[]){ "auto.conf" } );
193 VG_STATIC
void _vg_console_write_persistent(void)
195 FILE *fp
= fopen( "cfg/auto.conf", "w" );
197 for( int i
=0; i
<vg_console
.var_count
; i
++ ){
198 struct vg_var
*cv
= &vg_console
.vars
[i
];
200 if( cv
->flags
& VG_VAR_PERSISTENT
){
201 switch( cv
->data_type
){
202 case k_var_dtype_i32
:
203 fprintf( fp
, "%s %d\n", cv
->name
, *(i32
*)(cv
->data
) );
205 case k_var_dtype_u32
:
206 fprintf( fp
, "%s %u\n", cv
->name
, *(u32
*)(cv
->data
) );
208 case k_var_dtype_f32
:
209 fprintf( fp
, "%s %.5f\n", cv
->name
, *(float *)(cv
->data
) );
218 VG_STATIC
void _vg_console_free(void)
220 _vg_console_write_persistent();
224 * splits src into tokens and fills out args as pointers to those tokens
225 * returns number of tokens
226 * dst must be as long as src
228 VG_STATIC
int vg_console_tokenize( const char *src
, char *dst
,
229 const char *args
[8] )
234 for( int i
=0;; i
++ ){
236 if( src
[i
] == ' ' || src
[i
] == '\t' ){
249 args
[ arg_count
++ ] = &dst
[i
];
263 VG_STATIC vg_var
*vg_console_match_var( const char *kw
)
265 for( int i
=0; i
<vg_console
.var_count
; i
++ ){
266 struct vg_var
*cv
= &vg_console
.vars
[ i
];
267 if( !strcmp( cv
->name
, kw
) ){
275 VG_STATIC vg_cmd
*vg_console_match_cmd( const char *kw
)
277 for( int i
=0; i
<vg_console
.function_count
; i
++ ){
278 struct vg_cmd
*cmd
= &vg_console
.functions
[ i
];
279 if( !strcmp( cmd
->name
, kw
) ){
287 VG_STATIC
void vg_execute_console_input( const char *cmd
)
291 int arg_count
= vg_console_tokenize( cmd
, temp
, args
);
296 vg_var
*cv
= vg_console_match_var( args
[0] );
297 vg_cmd
*fn
= vg_console_match_cmd( args
[0] );
299 assert( !(cv
&& fn
) );
302 /* Cvar Matched, try get value */
303 if( arg_count
>= 2 ){
304 if( (cv
->data_type
== k_var_dtype_u32
) ||
305 (cv
->data_type
== k_var_dtype_i32
) )
308 *ptr
= atoi( args
[1] );
310 else if( cv
->data_type
== k_var_dtype_f32
){
311 float *ptr
= cv
->data
;
312 *ptr
= atof( args
[1] );
316 if( cv
->data_type
== k_var_dtype_i32
)
317 vg_info( "= %d\n", *((int *)cv
->data
) );
318 else if( cv
->data_type
== k_var_dtype_u32
)
319 vg_info( "= %u\n", *((u32
*)cv
->data
) );
320 else if( cv
->data_type
== k_var_dtype_f32
)
321 vg_info( "= %.4f\n", *((float *)cv
->data
) );
327 fn
->function( arg_count
-1, args
+1 );
331 vg_error( "No command/var named '%s'. Use 'list' to view all\n", args
[0] );
334 u32
str_lev_distance( const char *s1
, const char *s2
)
336 u32 m
= strlen( s1
),
342 assert( n
+1 <= 256 );
346 for( u32 k
=0; k
<=n
; k
++ )
350 for( u32 i
=0; i
<m
; i
++ ){
355 for( u32 j
=0; j
<n
; j
++ ){
356 u32 upper
= costs
[j
+1];
359 costs
[ j
+1 ] = corner
;
361 u32 t
= (upper
< corner
)? upper
: corner
;
362 costs
[j
+1] = ((costs
[j
] < t
)? costs
[j
]: t
) + 1;
372 u32
str_lcs( const char *s1
, const char *s2
)
374 u32 m
= VG_MIN( 31, strlen( s1
) ),
375 n
= VG_MIN( 31, strlen( s2
) );
380 for( int i
=0; i
<=m
; i
++ ){
381 for( int j
=0; j
<=n
; j
++ ){
382 if( i
== 0 || j
== 0 )
384 else if( s1
[i
-1] == s2
[j
-1] ){
385 suff
[i
][j
] = suff
[i
-1][j
-1] + 1;
386 result
= VG_MAX( result
, suff
[i
][j
] );
396 /* str must not fuckoff ever! */
397 VG_STATIC
void console_suggest_score_text( const char *str
, const char *input
,
400 /* filter duplicates */
401 for( int i
=0; i
<vg_console
.suggestion_count
; i
++ )
402 if( !strcmp( vg_console
.suggestions
[i
].str
, str
) )
406 u32 score
= str_lcs( str
, input
);
408 if( score
< minscore
)
411 int best_pos
= vg_console
.suggestion_count
;
412 for( int j
=best_pos
-1; j
>=0; j
-- )
413 if( score
> vg_console
.suggestions
[j
].lev_score
)
416 /* insert if good score */
417 if( best_pos
< vg_list_size( vg_console
.suggestions
) ){
418 int start
= VG_MIN( vg_console
.suggestion_count
,
419 vg_list_size( vg_console
.suggestions
)-1 );
420 for( int j
=start
; j
>best_pos
; j
-- )
421 vg_console
.suggestions
[j
] = vg_console
.suggestions
[j
-1];
423 vg_console
.suggestions
[ best_pos
].str
= str
;
424 vg_console
.suggestions
[ best_pos
].len
= strlen( str
);
425 vg_console
.suggestions
[ best_pos
].lev_score
= score
;
427 if( vg_console
.suggestion_count
<
428 vg_list_size( vg_console
.suggestions
) )
429 vg_console
.suggestion_count
++;
433 VG_STATIC
void console_update_suggestions(void)
435 if( vg_ui
.focused_control_type
!= k_ui_control_textbox
||
436 vg_ui
.textbuf
!= vg_console
.input
)
439 vg_console
.suggestion_count
= 0;
440 vg_console
.suggestion_select
= -1;
441 vg_console
.suggestion_maxlen
= 0;
444 * - must be typing something
445 * - must be at the end
446 * - prev char must not be a whitespace
447 * - cursors should match
450 if( vg_ui
.textbox
.cursor_pos
== 0 ) return;
451 if( vg_ui
.textbox
.cursor_pos
!= vg_ui
.textbox
.cursor_user
) return;
452 if( vg_console
.input
[ vg_ui
.textbox
.cursor_pos
] != '\0' ) return;
454 if( (vg_console
.input
[ vg_ui
.textbox
.cursor_pos
-1 ] == ' ') ||
455 (vg_console
.input
[ vg_ui
.textbox
.cursor_pos
-1 ] == '\t') )
461 int token_count
= vg_console_tokenize( vg_console
.input
, temp
, args
);
462 if( !token_count
) return;
463 vg_console
.suggestion_pastepos
= args
[token_count
-1]-temp
;
465 /* Score all our commands and cvars */
466 if( token_count
== 1 ){
467 for( int i
=0; i
<vg_console
.var_count
; i
++ ){
468 vg_var
*cvar
= &vg_console
.vars
[i
];
469 console_suggest_score_text( cvar
->name
, args
[0], 1 );
472 for( int i
=0; i
<vg_console
.function_count
; i
++ ){
473 vg_cmd
*cmd
= &vg_console
.functions
[i
];
474 console_suggest_score_text( cmd
->name
, args
[0], 1 );
478 vg_cmd
*cmd
= vg_console_match_cmd( args
[0] );
479 vg_var
*var
= vg_console_match_var( args
[0] );
481 assert( !( cmd
&& var
) );
484 if( cmd
->poll_suggest
)
485 cmd
->poll_suggest( token_count
-1, &args
[1] );
488 /* some post processing */
489 for( int i
=0; i
<vg_console
.suggestion_count
; i
++ ){
490 vg_console
.suggestion_maxlen
= VG_MAX( vg_console
.suggestion_maxlen
,
491 vg_console
.suggestions
[i
].len
);
493 if( vg_console
.suggestions
[i
].lev_score
<
494 vg_console
.suggestions
[0].lev_score
/2 )
496 vg_console
.suggestion_count
= i
;
503 * Suggestion controls
505 VG_STATIC
void _console_fetch_suggestion(void)
507 char *target
= &vg_console
.input
[ vg_console
.suggestion_pastepos
];
509 if( vg_console
.suggestion_select
== -1 ){
510 strcpy( target
, vg_console
.input_copy
);
511 _ui_textbox_move_cursor( &vg_ui
.textbox
.cursor_user
,
512 &vg_ui
.textbox
.cursor_pos
, 10000, 1 );
516 vg_console
.suggestions
[ vg_console
.suggestion_select
].str
,
517 vg_list_size( vg_console
.input
)-1 );
519 _ui_textbox_move_cursor( &vg_ui
.textbox
.cursor_user
,
520 &vg_ui
.textbox
.cursor_pos
, 10000, 1 );
521 _ui_textbox_put_char( ' ' );
525 VG_STATIC
void _console_suggest_store_normal(void)
527 if( vg_console
.suggestion_select
== -1 ){
528 char *target
= &vg_console
.input
[ vg_console
.suggestion_pastepos
];
529 strcpy( vg_console
.input_copy
, target
);
533 VG_STATIC
void _console_suggest_next(void)
535 if( vg_console
.suggestion_count
){
536 _console_suggest_store_normal();
538 vg_console
.suggestion_select
++;
540 if( vg_console
.suggestion_select
>= vg_console
.suggestion_count
)
541 vg_console
.suggestion_select
= -1;
543 _console_fetch_suggestion();
547 VG_STATIC
void _console_suggest_prev(void)
549 if( vg_console
.suggestion_count
){
550 _console_suggest_store_normal();
552 vg_console
.suggestion_select
--;
554 if( vg_console
.suggestion_select
< -1 )
555 vg_console
.suggestion_select
= vg_console
.suggestion_count
-1;
557 _console_fetch_suggestion();
561 VG_STATIC
void _vg_console_on_update( char *buf
, u32 len
)
563 if( buf
== vg_console
.input
){
564 console_update_suggestions();
568 VG_STATIC
void console_history_get( char* buf
, int entry_num
)
570 if( !vg_console
.history_count
)
573 int offset
= VG_MIN( entry_num
, vg_console
.history_count
-1 ),
574 pick
= (vg_console
.history_last
- offset
) %
575 vg_list_size( vg_console
.history
);
576 strcpy( buf
, vg_console
.history
[ pick
] );
579 VG_STATIC
void _vg_console_on_up( char *buf
, u32 len
)
581 if( buf
== vg_console
.input
){
582 vg_console
.history_pos
=
588 vg_console
.history_pos
+1,
591 vg_list_size( vg_console
.history
),
592 vg_console
.history_count
- 1
597 console_history_get( vg_console
.input
, vg_console
.history_pos
);
598 _ui_textbox_move_cursor( &vg_ui
.textbox
.cursor_user
,
599 &vg_ui
.textbox
.cursor_pos
,
600 vg_list_size(vg_console
.input
)-1, 1);
604 VG_STATIC
void _vg_console_on_down( char *buf
, u32 len
)
606 if( buf
== vg_console
.input
){
607 vg_console
.history_pos
= VG_MAX( 0, vg_console
.history_pos
-1 );
608 console_history_get( vg_console
.input
, vg_console
.history_pos
);
610 _ui_textbox_move_cursor( &vg_ui
.textbox
.cursor_user
,
611 &vg_ui
.textbox
.cursor_pos
,
612 vg_list_size(vg_console
.input
)-1, 1 );
616 VG_STATIC
void _vg_console_on_enter( char *buf
, u32 len
)
618 if( buf
== vg_console
.input
){
619 if( !strlen( vg_console
.input
) )
622 vg_info( "%s\n", vg_console
.input
);
624 if( strcmp( vg_console
.input
,
625 vg_console
.history
[ vg_console
.history_last
]) )
627 vg_console
.history_last
= ( vg_console
.history_last
+ 1) %
628 vg_list_size(vg_console
.history
);
629 vg_console
.history_count
=
630 VG_MIN( vg_list_size( vg_console
.history
),
631 vg_console
.history_count
+ 1 );
632 strcpy( vg_console
.history
[ vg_console
.history_last
],
636 vg_console
.history_pos
= -1;
637 vg_execute_console_input( vg_console
.input
);
638 _ui_textbox_move_cursor( &vg_ui
.textbox
.cursor_user
,
639 &vg_ui
.textbox
.cursor_pos
, -10000, 1 );
641 vg_console
.input
[0] = '\0';
642 console_update_suggestions();
646 VG_STATIC
void _vg_console_draw(void)
648 if( !vg_console
.enabled
) return;
650 SDL_AtomicLock( &log_print_sl
);
652 int ptr
= vg_log
.buffer_line_current
;
653 int const fh
= 14, log_lines
= 32;
654 int console_lines
= VG_MIN( log_lines
, vg_log
.buffer_line_count
);
656 ui_rect rect_log
= { 0, 0, vg
.window_x
, log_lines
*fh
},
657 rect_input
= { 0, log_lines
*fh
+ 1, vg
.window_x
, fh
*2 },
658 rect_line
= { 0, 0, vg
.window_x
, fh
};
663 u32 bg_colour
= (ui_colour( k_ui_bg
)&0x00ffffff)|0x9f000000;
665 ui_fill( rect_log
, bg_colour
);
666 rect_line
[1] = rect_log
[1]+rect_log
[3]-fh
;
668 for( int i
=0; i
<console_lines
; i
++ ){
671 if( ptr
< 0 ) ptr
= vg_list_size( vg_log
.buffer
)-1;
673 ui_text( rect_line
, vg_log
.buffer
[ptr
], 1, k_ui_align_left
, 0 );
681 struct ui_textbox_callbacks callbacks
= {
682 .up
= _vg_console_on_up
,
683 .down
= _vg_console_on_down
,
684 .change
= _vg_console_on_update
,
685 .enter
= _vg_console_on_enter
687 ui_textbox( rect_input
, vg_console
.input
, vg_list_size(vg_console
.input
),
688 UI_TEXTBOX_AUTOFOCUS
, &callbacks
);
693 if( vg_console
.suggestion_count
){
694 ui_rect rect_suggest
;
695 rect_copy( rect_input
, rect_suggest
);
697 rect_suggest
[0] += 6 + UI_GLYPH_SPACING_X
*vg_console
.suggestion_pastepos
;
698 rect_suggest
[1] += rect_input
[3];
699 rect_suggest
[2] = UI_GLYPH_SPACING_X
* vg_console
.suggestion_maxlen
;
700 rect_suggest
[3] = vg_console
.suggestion_count
* fh
;
702 ui_fill( rect_suggest
, bg_colour
);
704 rect_suggest
[3] = fh
;
706 for( int i
=0; i
<vg_console
.suggestion_count
; i
++ ){
708 if( i
== vg_console
.suggestion_select
){
709 ui_fill( rect_suggest
, ui_colour( k_ui_orange
) );
710 text_colour
= ui_colourcont( k_ui_orange
);
712 else text_colour
= ui_colourcont( k_ui_bg
);
714 ui_text( rect_suggest
, vg_console
.suggestions
[i
].str
, 1,
715 k_ui_align_left
, text_colour
);
717 rect_suggest
[1] += fh
;
721 SDL_AtomicUnlock( &log_print_sl
);
725 #endif /* VG_CONSOLE_H */