2 #include "vg_console.h"
8 struct vg_console vg_console
;
10 void vg_console_reg_var( const char *alias
, void *ptr
, enum vg_var_dtype type
,
13 if( vg_thread_purpose() == k_thread_purpose_main
)
14 vg_fatal_error( "FIXME: Cannot register VAR from main thread" );
16 if( vg_console
.var_count
> vg_list_size(vg_console
.vars
) )
17 vg_fatal_error( "Too many vars registered" );
19 vg_var
*var
= &vg_console
.vars
[ vg_console
.var_count
++ ];
22 var
->data_type
= type
;
25 vg_info( "Console variable '%s' registered\n", alias
);
28 void vg_console_reg_cmd( const char *alias
,
29 int (*function
)(int argc
, const char *argv
[]),
30 void (*poll_suggest
)(int argc
, const char *argv
[]) )
32 if( vg_thread_purpose() == k_thread_purpose_main
)
33 vg_fatal_error( "FIXME: Cannot register CMD from main thread" );
35 if( vg_console
.function_count
> vg_list_size(vg_console
.functions
) )
36 vg_fatal_error( "Too many functions registered" );
38 vg_cmd
*cmd
= &vg_console
.functions
[ vg_console
.function_count
++ ];
40 cmd
->function
= function
;
41 cmd
->poll_suggest
= poll_suggest
;
44 vg_info( "Console function '%s' registered\n", alias
);
47 static int _vg_console_list( int argc
, char const *argv
[] )
49 for( int i
=0; i
<vg_console
.function_count
; i
++ ){
50 struct vg_cmd
*cmd
= &vg_console
.functions
[ i
];
51 vg_info( "* %s\n", cmd
->name
);
54 for( int i
=0; i
<vg_console
.var_count
; i
++ ){
55 struct vg_var
*cv
= &vg_console
.vars
[ i
];
57 (const char *[]){ "i32","u32","f32","str" }[cv
->data_type
],
64 static void vg_console_write_persistent(void)
66 FILE *fp
= fopen( "cfg/auto.conf", "w" );
69 vg_error( "Cannot open cfg/auto.conf\n" );
73 for( int i
=0; i
<vg_console
.var_count
; i
++ ){
74 struct vg_var
*cv
= &vg_console
.vars
[i
];
76 if( cv
->flags
& VG_VAR_PERSISTENT
){
77 if( cv
->data_type
== k_var_dtype_i32
){
78 fprintf( fp
, "%s %d\n", cv
->name
, *(i32
*)(cv
->data
) );
80 else if( cv
->data_type
== k_var_dtype_u32
){
81 fprintf( fp
, "%s %u\n", cv
->name
, *(u32
*)(cv
->data
) );
83 else if( cv
->data_type
== k_var_dtype_f32
){
84 fprintf( fp
, "%s %.5f\n", cv
->name
, *(float *)(cv
->data
) );
86 else if( cv
->data_type
== k_var_dtype_str
){
87 vg_str
*str
= cv
->data
;
88 if( str
->buffer
&& (str
->i
> 0) )
89 fprintf( fp
, "%s %s\n", cv
->name
, str
->buffer
);
97 static void _vg_console_free(void)
99 vg_console_write_persistent();
103 * splits src into tokens and fills out args as pointers to those tokens
104 * returns number of tokens
105 * dst must be as long as src
107 static int vg_console_tokenize( const char *src
, char *dst
,
108 const char *args
[8] )
113 for( int i
=0;; i
++ ){
115 if( src
[i
] == ' ' || src
[i
] == '\t' ){
128 args
[ arg_count
++ ] = &dst
[i
];
142 vg_var
*vg_console_match_var( const char *kw
)
144 for( int i
=0; i
<vg_console
.var_count
; i
++ ){
145 struct vg_var
*cv
= &vg_console
.vars
[ i
];
146 if( !strcmp( cv
->name
, kw
) ){
154 vg_cmd
*vg_console_match_cmd( const char *kw
)
156 for( int i
=0; i
<vg_console
.function_count
; i
++ ){
157 struct vg_cmd
*cmd
= &vg_console
.functions
[ i
];
158 if( !strcmp( cmd
->name
, kw
) ){
166 void vg_execute_console_input( const char *cmd
, bool silent
)
170 int arg_count
= vg_console_tokenize( cmd
, temp
, args
);
175 vg_var
*cv
= vg_console_match_var( args
[0] );
176 vg_cmd
*fn
= vg_console_match_cmd( args
[0] );
179 /* Cvar Matched, try get value */
180 if( arg_count
>= 2 ){
181 if( cv
->flags
& VG_VAR_CHEAT
){
182 if( !vg_console
.cheats
&& !silent
){
183 vg_error( "variable is cheat protected\n" );
188 if( (cv
->data_type
== k_var_dtype_u32
) ||
189 (cv
->data_type
== k_var_dtype_i32
) ){
191 *ptr
= atoi( args
[1] );
193 else if( cv
->data_type
== k_var_dtype_f32
){
194 float *ptr
= cv
->data
;
195 *ptr
= atof( args
[1] );
197 else if( cv
->data_type
== k_var_dtype_str
){
198 vg_str
*str
= cv
->data
;
200 vg_strnull( str
, NULL
, -1 );
202 for( int i
=1; i
<arg_count
; i
++ ){
203 vg_strcat( str
, args
[i
] );
204 if( i
!=arg_count
-1 ) vg_strcatch( str
, ' ' );
209 if( cv
->data_type
== k_var_dtype_i32
)
210 vg_info( "= %d\n", *((int *)cv
->data
) );
211 else if( cv
->data_type
== k_var_dtype_u32
)
212 vg_info( "= %u\n", *((u32
*)cv
->data
) );
213 else if( cv
->data_type
== k_var_dtype_f32
)
214 vg_info( "= %.4f\n", *((float *)cv
->data
) );
215 else if( cv
->data_type
== k_var_dtype_str
){
216 vg_str
*str
= cv
->data
;
217 vg_info( "= '%s'\n", str
->buffer
);
224 fn
->function( arg_count
-1, args
+1 );
229 vg_error( "No command/var named '%s'. Use 'list' to view all\n",args
[0]);
232 u32
str_lev_distance( const char *s1
, const char *s2
){
233 u32 m
= strlen( s1
),
241 for( u32 k
=0; k
<=n
; k
++ )
245 for( u32 i
=0; i
<m
; i
++ ){
250 for( u32 j
=0; j
<n
; j
++ ){
251 u32 upper
= costs
[j
+1];
254 costs
[ j
+1 ] = corner
;
256 u32 t
= (upper
< corner
)? upper
: corner
;
257 costs
[j
+1] = ((costs
[j
] < t
)? costs
[j
]: t
) + 1;
267 u32
str_lcs( const char *s1
, const char *s2
){
268 u32 m
= VG_MIN( 31, strlen( s1
) ),
269 n
= VG_MIN( 31, strlen( s2
) );
274 for( int i
=0; i
<=m
; i
++ ){
275 for( int j
=0; j
<=n
; j
++ ){
276 if( i
== 0 || j
== 0 )
278 else if( s1
[i
-1] == s2
[j
-1] ){
279 suff
[i
][j
] = suff
[i
-1][j
-1] + 1;
280 result
= VG_MAX( result
, suff
[i
][j
] );
290 /* str must not fuckoff ever! */
291 void console_suggest_score_text( const char *str
, const char *input
,
294 /* filter duplicates */
295 for( int i
=0; i
<vg_console
.suggestion_count
; i
++ )
296 if( !strcmp( vg_console
.suggestions
[i
].str
, str
) )
300 u32 score
= str_lcs( str
, input
);
302 if( score
< minscore
)
305 int best_pos
= vg_console
.suggestion_count
;
306 for( int j
=best_pos
-1; j
>=0; j
-- )
307 if( score
> vg_console
.suggestions
[j
].lev_score
)
310 /* insert if good score */
311 if( best_pos
< vg_list_size( vg_console
.suggestions
) ){
312 int start
= VG_MIN( vg_console
.suggestion_count
,
313 vg_list_size( vg_console
.suggestions
)-1 );
314 for( int j
=start
; j
>best_pos
; j
-- )
315 vg_console
.suggestions
[j
] = vg_console
.suggestions
[j
-1];
317 vg_console
.suggestions
[ best_pos
].str
= str
;
318 vg_console
.suggestions
[ best_pos
].len
= strlen( str
);
319 vg_console
.suggestions
[ best_pos
].lev_score
= score
;
321 if( vg_console
.suggestion_count
<
322 vg_list_size( vg_console
.suggestions
) )
323 vg_console
.suggestion_count
++;
327 static void console_update_suggestions(void)
329 if( vg_ui
.focused_control_type
!= k_ui_control_textbox
||
330 vg_ui
.textbuf
!= vg_console
.input
)
333 vg_console
.suggestion_count
= 0;
334 vg_console
.suggestion_select
= -1;
335 vg_console
.suggestion_maxlen
= 0;
338 * - must be typing something
339 * - must be at the end
340 * - prev char must not be a whitespace
341 * - cursors should match
344 if( vg_ui
.textbox
.cursor_pos
== 0 ) return;
345 if( vg_ui
.textbox
.cursor_pos
!= vg_ui
.textbox
.cursor_user
) return;
346 if( vg_console
.input
[ vg_ui
.textbox
.cursor_pos
] != '\0' ) return;
348 if( (vg_console
.input
[ vg_ui
.textbox
.cursor_pos
-1 ] == ' ') ||
349 (vg_console
.input
[ vg_ui
.textbox
.cursor_pos
-1 ] == '\t') )
355 int token_count
= vg_console_tokenize( vg_console
.input
, temp
, args
);
356 if( !token_count
) return;
357 vg_console
.suggestion_pastepos
= args
[token_count
-1]-temp
;
359 /* Score all our commands and cvars */
360 if( token_count
== 1 ){
361 for( int i
=0; i
<vg_console
.var_count
; i
++ ){
362 vg_var
*cvar
= &vg_console
.vars
[i
];
363 console_suggest_score_text( cvar
->name
, args
[0], 1 );
366 for( int i
=0; i
<vg_console
.function_count
; i
++ ){
367 vg_cmd
*cmd
= &vg_console
.functions
[i
];
368 console_suggest_score_text( cmd
->name
, args
[0], 1 );
372 vg_cmd
*cmd
= vg_console_match_cmd( args
[0] );
373 vg_var
*var
= vg_console_match_var( args
[0] );
376 if( cmd
->poll_suggest
)
377 cmd
->poll_suggest( token_count
-1, &args
[1] );
380 /* some post processing */
381 for( int i
=0; i
<vg_console
.suggestion_count
; i
++ ){
382 vg_console
.suggestion_maxlen
= VG_MAX( vg_console
.suggestion_maxlen
,
383 vg_console
.suggestions
[i
].len
);
385 if( vg_console
.suggestions
[i
].lev_score
<
386 vg_console
.suggestions
[0].lev_score
/2 )
388 vg_console
.suggestion_count
= i
;
395 * Suggestion controls
397 static void _console_fetch_suggestion(void)
399 char *target
= &vg_console
.input
[ vg_console
.suggestion_pastepos
];
401 if( vg_console
.suggestion_select
== -1 ){
402 strcpy( target
, vg_console
.input_copy
);
403 _ui_textbox_move_cursor( &vg_ui
.textbox
.cursor_user
,
404 &vg_ui
.textbox
.cursor_pos
, 10000, 1 );
408 vg_console
.suggestions
[ vg_console
.suggestion_select
].str
,
409 vg_list_size( vg_console
.input
)-1 );
411 _ui_textbox_move_cursor( &vg_ui
.textbox
.cursor_user
,
412 &vg_ui
.textbox
.cursor_pos
, 10000, 1 );
413 _ui_textbox_put_char( ' ' );
417 static void _console_suggest_store_normal(void)
419 if( vg_console
.suggestion_select
== -1 ){
420 char *target
= &vg_console
.input
[ vg_console
.suggestion_pastepos
];
421 strcpy( vg_console
.input_copy
, target
);
425 static void console_suggest_next(void)
427 if( vg_console
.suggestion_count
){
428 _console_suggest_store_normal();
430 vg_console
.suggestion_select
++;
432 if( vg_console
.suggestion_select
>= vg_console
.suggestion_count
)
433 vg_console
.suggestion_select
= -1;
435 _console_fetch_suggestion();
439 static void console_suggest_prev(void)
441 if( vg_console
.suggestion_count
){
442 _console_suggest_store_normal();
444 vg_console
.suggestion_select
--;
446 if( vg_console
.suggestion_select
< -1 )
447 vg_console
.suggestion_select
= vg_console
.suggestion_count
-1;
449 _console_fetch_suggestion();
453 static void _vg_console_on_update( char *buf
, u32 len
)
455 if( buf
== vg_console
.input
){
456 console_update_suggestions();
460 static void console_history_get( char* buf
, int entry_num
)
462 if( !vg_console
.history_count
)
465 int offset
= VG_MIN( entry_num
, vg_console
.history_count
-1 ),
466 pick
= (vg_console
.history_last
- offset
) %
467 vg_list_size( vg_console
.history
);
468 strcpy( buf
, vg_console
.history
[ pick
] );
471 static void _vg_console_on_up( char *buf
, u32 len
)
473 if( buf
== vg_console
.input
){
474 vg_console
.history_pos
=
480 vg_console
.history_pos
+1,
483 vg_list_size( vg_console
.history
),
484 vg_console
.history_count
- 1
489 console_history_get( vg_console
.input
, vg_console
.history_pos
);
490 _ui_textbox_move_cursor( &vg_ui
.textbox
.cursor_user
,
491 &vg_ui
.textbox
.cursor_pos
,
492 vg_list_size(vg_console
.input
)-1, 1);
496 static void _vg_console_on_down( char *buf
, u32 len
)
498 if( buf
== vg_console
.input
){
499 vg_console
.history_pos
= VG_MAX( 0, vg_console
.history_pos
-1 );
500 console_history_get( vg_console
.input
, vg_console
.history_pos
);
502 _ui_textbox_move_cursor( &vg_ui
.textbox
.cursor_user
,
503 &vg_ui
.textbox
.cursor_pos
,
504 vg_list_size(vg_console
.input
)-1, 1 );
508 static void _vg_console_on_enter( char *buf
, u32 len
)
510 if( buf
== vg_console
.input
){
511 if( !strlen( vg_console
.input
) )
514 vg_info( "%s\n", vg_console
.input
);
516 if( strcmp( vg_console
.input
,
517 vg_console
.history
[ vg_console
.history_last
]) )
519 vg_console
.history_last
= ( vg_console
.history_last
+ 1) %
520 vg_list_size(vg_console
.history
);
521 vg_console
.history_count
=
522 VG_MIN( vg_list_size( vg_console
.history
),
523 vg_console
.history_count
+ 1 );
524 strcpy( vg_console
.history
[ vg_console
.history_last
],
528 vg_console
.history_pos
= -1;
529 vg_execute_console_input( vg_console
.input
, 0 );
530 _ui_textbox_move_cursor( &vg_ui
.textbox
.cursor_user
,
531 &vg_ui
.textbox
.cursor_pos
, -10000, 1 );
533 vg_console
.input
[0] = '\0';
534 console_update_suggestions();
538 int vg_console_exec( int argc
, const char *argv
[] )
540 if( argc
< 1 ) return 0;
543 if( argc
== 2 ) silent
=1;
546 strcpy( path
, "cfg/" );
547 strncat( path
, argv
[0], 250 );
549 FILE *fp
= fopen( path
, "r" );
553 while( fgets( line
, sizeof( line
), fp
) ){
554 line
[ strcspn( line
, "\r\n#" ) ] = 0x00;
556 if( line
[0] != 0x00 ){
557 vg_execute_console_input( line
, silent
);
564 vg_error( "Could not open '%s'\n", path
);
571 void vg_console_init(void)
573 vg_console_reg_cmd( "list", _vg_console_list
, NULL
);
574 vg_console_reg_cmd( "exec", vg_console_exec
, NULL
);
575 vg_console_reg_var( "cheats", &vg_console
.cheats
, k_var_dtype_i32
,
584 void vg_console_load_autos(void)
586 vg_console_exec( 2, (const char *[]){ "auto.conf", "silent" } );
589 void vg_console_draw(void)
591 if( !vg_console
.enabled
) return;
593 SDL_AtomicLock( &vg_log
.print_sl
);
595 int ptr
= vg_log
.log_line_current
;
596 int const fh
= vg_ui
.font
->line_height
, log_lines
= 32;
597 int console_lines
= VG_MIN( log_lines
, vg_log
.log_line_count
);
599 ui_rect rect_log
= { 0, 0, vg
.window_x
, log_lines
*fh
},
600 rect_input
= { 0, log_lines
*fh
+ 1, vg
.window_x
, fh
*2 },
601 rect_line
= { 0, 0, vg
.window_x
, fh
};
606 u32 bg_colour
= (ui_colour( k_ui_bg
)&0x00ffffff)|0x9f000000;
608 ui_fill( rect_log
, bg_colour
);
609 rect_line
[1] = rect_log
[1]+rect_log
[3]-fh
;
611 for( int i
=0; i
<console_lines
; i
++ ){
614 if( ptr
< 0 ) ptr
= vg_list_size( vg_log
.log
)-1;
616 ui_text( rect_line
, vg_log
.log
[ptr
], 1, k_ui_align_left
, 0 );
624 struct ui_textbox_callbacks callbacks
= {
625 .up
= _vg_console_on_up
,
626 .down
= _vg_console_on_down
,
627 .change
= _vg_console_on_update
,
628 .enter
= _vg_console_on_enter
,
630 ui_textbox( rect_input
, NULL
,
631 vg_console
.input
, vg_list_size(vg_console
.input
), 1,
632 UI_TEXTBOX_AUTOFOCUS
, &callbacks
);
637 if( vg_console
.suggestion_count
){
638 ui_rect rect_suggest
;
639 rect_copy( rect_input
, rect_suggest
);
641 rect_suggest
[0] += 6 + vg_ui
.font
->spacing
*vg_console
.suggestion_pastepos
;
642 rect_suggest
[1] += rect_input
[3];
643 rect_suggest
[2] = vg_ui
.font
->spacing
* vg_console
.suggestion_maxlen
;
644 rect_suggest
[3] = vg_console
.suggestion_count
* fh
;
646 ui_fill( rect_suggest
, bg_colour
);
648 rect_suggest
[3] = fh
;
650 for( int i
=0; i
<vg_console
.suggestion_count
; i
++ ){
652 if( i
== vg_console
.suggestion_select
){
653 ui_fill( rect_suggest
, ui_colour( k_ui_orange
) );
654 text_colour
= ui_colourcont( k_ui_orange
);
656 else text_colour
= ui_colourcont( k_ui_bg
);
658 ui_text( rect_suggest
, vg_console
.suggestions
[i
].str
, 1,
659 k_ui_align_left
, text_colour
);
661 rect_suggest
[1] += fh
;
665 SDL_AtomicUnlock( &vg_log
.print_sl
);