8f8517cb7d41662a7afce1f31ca2e9c1d97d4fce
1 /* Copyright (C) 2021-2022 Harry Godden (hgn) - All Rights Reserved */
11 #include "vg/vg_log.h"
13 typedef struct vg_convar vg_convar
;
14 typedef struct vg_cmd vg_cmd
;
48 int persistent
; /* Should this var be stored to cfg/auto.conf? */
54 int (*function
)( int argc
, char const *argv
[] );
68 int suggestion_select
,
72 u32 convar_count
, function_count
;
76 int cursor_user
, cursor_pos
, string_length
;
79 int history_last
, history_pos
, history_count
;
85 VG_STATIC
void vg_convar_push( struct vg_convar cv
);
86 VG_STATIC
void vg_function_push( struct vg_cmd cmd
);
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
);
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
);
114 VG_STATIC
int _vg_console_enabled(void)
116 return vg_console
.enabled
;
119 VG_STATIC
void vg_convar_push( vg_convar cv
)
121 if( vg_console
.convar_count
> vg_list_size(vg_console
.convars
) )
122 vg_fatal_exit_loop( "Too many convars registered" );
124 vg_info( "Console variable '%s' registered\n", cv
.name
);
125 vg_console
.convars
[ vg_console
.convar_count
++ ] = cv
;
128 VG_STATIC
void vg_function_push( struct vg_cmd cmd
)
130 if( vg_console
.function_count
> vg_list_size(vg_console
.functions
) )
131 vg_fatal_exit_loop( "Too many functions registered" );
133 vg_console
.functions
[ vg_console
.function_count
++ ] = cmd
;
136 VG_STATIC
void _vg_console_draw( void )
138 if( !vg_console
.enabled
)
141 SDL_AtomicLock( &log_print_sl
);
143 int ptr
= vg_log
.buffer_line_current
;
146 int console_lines
= VG_MIN( log_lines
, vg_log
.buffer_line_count
);
148 vg_uictx
.cursor
[0] = 0;
149 vg_uictx
.cursor
[1] = 0;
150 vg_uictx
.cursor
[3] = log_lines
*fh
;
158 ui_fill_rect( vg_uictx
.cursor
, 0x77181818 );
160 vg_uictx
.cursor
[3] = fh
;
163 for( int i
=0; i
<console_lines
; i
++ )
168 ptr
= vg_list_size( vg_log
.buffer
)-1;
170 ui_text( vg_uictx
.cursor
, vg_log
.buffer
[ptr
], 1, 0 );
171 vg_uictx
.cursor
[1] -= fh
;
179 vg_uictx
.cursor
[1] += 2;
180 vg_uictx
.cursor
[3] = fh
;
184 ui_fill_rect( vg_uictx
.cursor
, 0x77111111 );
185 ui_text( vg_uictx
.cursor
, vg_console
.input
, 1, 0 );
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
);
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
;
194 ui_fill_rect( vg_uictx
.cursor
, 0x66ffffff );
201 if( vg_console
.suggestion_count
)
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
;
209 ui_fill_rect( vg_uictx
.cursor
, 0x77040404 );
211 vg_uictx
.cursor
[3] = fh
;
212 for( int i
=0; i
<vg_console
.suggestion_count
; i
++ )
214 if( i
== vg_console
.suggestion_select
)
215 ui_fill_rect( vg_uictx
.cursor
, 0x66a0e508 );
217 ui_text( vg_uictx
.cursor
, vg_console
.suggestions
[i
].str
, 1, 0 );
218 vg_uictx
.cursor
[1] += fh
;
224 SDL_AtomicUnlock( &log_print_sl
);
227 VG_STATIC
int _vg_console_list( int argc
, char const *argv
[] )
229 for( int i
=0; i
<vg_console
.function_count
; i
++ )
231 struct vg_cmd
*cmd
= &vg_console
.functions
[ i
];
232 vg_info( "* %s\n", cmd
->name
);
235 for( int i
=0; i
<vg_console
.convar_count
; i
++ )
237 struct vg_convar
*cv
= &vg_console
.convars
[ i
];
238 vg_info( "%s\n", cv
->name
);
244 int _test_break( int argc
, const char *argv
[] )
246 vg_fatal_exit_loop( "Test crash from main, after loading (console)" );
250 VG_STATIC
void _vg_console_init(void)
252 vg_function_push( (struct vg_cmd
)
255 .function
= _vg_console_list
258 vg_function_push( (struct vg_cmd
)
261 .function
= _test_break
265 VG_STATIC
void vg_console_load_autos(void)
267 /* Read and exec persistent commands */
268 FILE *fp
= fopen( "cfg/auto.conf", "r" );
273 while( fgets( line
, sizeof( line
), fp
) )
275 line
[ strcspn( line
, "\r\n#" ) ] = 0x00;
277 if( line
[0] != 0x00 )
279 vg_execute_console_input( line
);
287 VG_STATIC
void _vg_console_write_persistent(void)
289 FILE *fp
= fopen( "cfg/auto.conf", "w" );
291 for( int i
=0; i
<vg_console
.convar_count
; i
++ )
293 struct vg_convar
*cv
= &vg_console
.convars
[i
];
297 switch( cv
->data_type
)
299 case k_convar_dtype_i32
:
300 fprintf( fp
, "%s %d\n", cv
->name
, *(i32
*)(cv
->data
) );
302 case k_convar_dtype_u32
:
303 fprintf( fp
, "%s %u\n", cv
->name
, *(u32
*)(cv
->data
) );
305 case k_convar_dtype_f32
:
306 fprintf( fp
, "%s %.5f\n", cv
->name
, *(float *)(cv
->data
) );
315 VG_STATIC
void _vg_console_free(void)
317 _vg_console_write_persistent();
320 VG_STATIC
void vg_execute_console_input( const char *cmd
)
328 /* Split string into tokens */
329 for( int i
= 0; i
< vg_list_size( temp
); i
++ )
333 if( cmd
[i
] == ' ' || cmd
[i
] == '\t' )
338 if( arg_count
== vg_list_size( args
) )
347 args
[ arg_count
++ ] = temp
+ i
;
365 for( int i
=0; i
<vg_console
.convar_count
; i
++ )
367 struct vg_convar
*cv
= &vg_console
.convars
[ i
];
368 if( !strcmp( cv
->name
, args
[0] ) )
370 /* Cvar Matched, try get value */
373 switch( cv
->data_type
)
375 case k_convar_dtype_u32
:
376 case k_convar_dtype_i32
:
378 data_int
= atoi( args
[1] );
380 *((int *)cv
->data
) = cv
->opt_i32
.clamp
?
381 VG_MIN( VG_MAX(data_int
, cv
->opt_i32
.min
), cv
->opt_i32
.max
):
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
),
399 switch( cv
->data_type
)
401 case k_convar_dtype_i32
:
402 vg_info( "= %d\n", *((int *)cv
->data
) );
404 case k_convar_dtype_u32
:
405 vg_info( "= %u\n", *((u32
*)cv
->data
) );
407 case k_convar_dtype_f32
:
408 vg_info( "= %.4f\n", *((float *)cv
->data
) );
418 * Find and excecute command
420 for( int i
=0; i
<vg_console
.function_count
; i
++ )
422 struct vg_cmd
*cmd
= &vg_console
.functions
[ i
];
423 if( !strcmp( cmd
->name
, args
[0] ) )
425 cmd
->function( arg_count
-1, args
+1 );
430 vg_error( "No command/var named '%s'. Use 'list' to view all\n", args
[0] );
433 u32
str_lev_distance( const char *s1
, const char *s2
)
435 u32 m
= strlen( s1
),
441 assert( n
+1 <= 256 );
445 for( u32 k
=0; k
<=n
; k
++ )
449 for( u32 i
=0; i
<m
; i
++ )
455 for( u32 j
=0; j
<n
; j
++ )
457 u32 upper
= costs
[j
+1];
460 costs
[ j
+1 ] = corner
;
463 u32 t
= (upper
< corner
)? upper
: corner
;
464 costs
[j
+1] = ((costs
[j
] < t
)? costs
[j
]: t
) + 1;
474 u32
str_lcs( const char *s1
, const char *s2
)
476 u32 m
= VG_MIN( 31, strlen( s1
) ),
477 n
= VG_MIN( 31, strlen( s2
) );
482 for( int i
=0; i
<=m
; i
++ )
484 for( int j
=0; j
<=n
; j
++ )
486 if( i
== 0 || j
== 0 )
488 else if( s1
[i
-1] == s2
[j
-1] )
490 suff
[i
][j
] = suff
[i
-1][j
-1] + 1;
491 result
= VG_MAX( result
, suff
[i
][j
] );
501 /* str must not fuckoff ever! */
502 VG_STATIC
void console_suggest_score_text( const char *input
, const char *str
)
505 u32 score
= str_lcs( str
, input
);
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
)
515 /* insert if good score */
516 if( best_pos
< vg_list_size( vg_console
.suggestions
) )
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];
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
;
527 if( vg_console
.suggestion_count
<
528 vg_list_size( vg_console
.suggestions
) )
529 vg_console
.suggestion_count
++;
533 VG_STATIC
void console_update_suggestions(void)
535 vg_console
.suggestion_count
= 0;
536 vg_console
.suggestion_select
= -1;
537 vg_console
.suggestion_maxlen
= 0;
539 /* find current term */
542 for( int i
=0; 1; i
++ )
544 if( !vg_console
.input
[i
] )
547 if( isspace( vg_console
.input
[i
] ) )
551 /* TODO: not just functions
558 vg_console
.suggestion_pastepos
= start_index
;
559 const char *input_ref
= &vg_console
.input
[ start_index
];
561 /* Score all our commands and cvars */
562 for( int i
=0; i
<vg_console
.convar_count
; i
++ )
564 vg_convar
*cvar
= &vg_console
.convars
[i
];
565 console_suggest_score_text( input_ref
, cvar
->name
);
568 for( int i
=0; i
<vg_console
.function_count
; i
++ )
570 vg_cmd
*cmd
= &vg_console
.functions
[i
];
571 console_suggest_score_text( input_ref
, cmd
->name
);
574 /* some post processing */
575 for( int i
=0; i
<vg_console
.suggestion_count
; i
++ )
577 vg_console
.suggestion_maxlen
= VG_MAX( vg_console
.suggestion_maxlen
,
578 vg_console
.suggestions
[i
].len
);
580 if( vg_console
.suggestions
[i
].lev_score
<
581 vg_console
.suggestions
[0].lev_score
/2 )
583 vg_console
.suggestion_count
= i
;
592 VG_STATIC
void console_make_selection( int* start
, int* end
)
594 *start
= VG_MIN( vg_console
.cursor_pos
, vg_console
.cursor_user
);
595 *end
= VG_MAX( vg_console
.cursor_pos
, vg_console
.cursor_user
);
598 VG_STATIC
void console_move_cursor( int* cursor0
, int* cursor1
,
599 int dir
, int snap_together
)
601 *cursor0
= VG_MAX( 0, vg_console
.cursor_user
+ dir
);
604 VG_MIN( vg_list_size(vg_console
.input
)-1, strlen( vg_console
.input
)),
611 VG_STATIC
int console_makeroom( int datastart
, int length
)
613 int move_to
= VG_MIN( datastart
+length
, vg_list_size( vg_console
.input
)-1 );
614 int move_amount
= strlen( vg_console
.input
)-datastart
;
616 VG_MIN( move_to
+move_amount
, vg_list_size( vg_console
.input
)-1 );
617 move_amount
= move_end
-move_to
;
620 memmove( &vg_console
.input
[ move_to
],
621 &vg_console
.input
[ datastart
],
624 vg_console
.input
[ move_end
] = '\0';
626 return VG_MIN( length
, vg_list_size( vg_console
.input
)-datastart
-1 );
629 VG_STATIC
int console_delete_char( int direction
)
632 console_make_selection( &start
, &end
);
634 /* There is no selection */
637 if( direction
== 1 ) end
= VG_MIN( end
+1, strlen( vg_console
.input
) );
638 else if( direction
== -1 ) start
= VG_MAX( start
-1, 0 );
641 /* Still no selction, no need to do anything */
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
],
651 console_update_suggestions();
655 VG_STATIC
void console_to_clipboard(void)
658 console_make_selection( &start
, &end
);
663 memcpy( buffer
, &vg_console
.input
[ start
], end
-start
);
664 buffer
[ end
-start
] = 0x00;
665 SDL_SetClipboardText( buffer
);
669 VG_STATIC
void console_clipboard_paste(void)
671 if( !SDL_HasClipboardText() )
674 char *text
= SDL_GetClipboardText();
679 int datastart
= console_delete_char( 0 );
680 int length
= strlen( text
);
681 int cpylength
= console_makeroom( datastart
, length
);
683 memcpy( vg_console
.input
+ datastart
, text
, cpylength
);
684 console_move_cursor( &vg_console
.cursor_user
,
685 &vg_console
.cursor_pos
, cpylength
, 1 );
688 console_update_suggestions();
691 VG_STATIC
void console_put_char( char c
)
693 if( !vg_console
.enabled
)
696 vg_console
.cursor_user
= console_delete_char(0);
698 if( console_makeroom( vg_console
.cursor_user
, 1 ) )
699 vg_console
.input
[ vg_console
.cursor_user
] = c
;
701 console_move_cursor( &vg_console
.cursor_user
, &vg_console
.cursor_pos
, 1, 1 );
704 VG_STATIC
void console_history_get( char* buf
, int entry_num
)
706 if( !vg_console
.history_count
)
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
] );
715 /* Receed secondary cursor */
716 VG_STATIC
void _console_left_select(void)
718 console_move_cursor( &vg_console
.cursor_user
, NULL
, -1, 0 );
721 /* Match and receed both cursors */
722 VG_STATIC
void _console_left(void)
724 int cursor_diff
= vg_console
.cursor_pos
- vg_console
.cursor_user
? 0: 1;
726 console_move_cursor( &vg_console
.cursor_user
,
727 &vg_console
.cursor_pos
, -cursor_diff
, 1 );
730 VG_STATIC
void _console_right_select(void)
732 console_move_cursor( &vg_console
.cursor_user
, NULL
, 1, 0 );
735 VG_STATIC
void _console_right(void)
737 int cursor_diff
= vg_console
.cursor_pos
- vg_console
.cursor_user
? 0: 1;
739 console_move_cursor( &vg_console
.cursor_user
,
740 &vg_console
.cursor_pos
, +cursor_diff
, 1 );
743 VG_STATIC
void _console_down(void)
745 vg_console
.history_pos
= VG_MAX( 0, vg_console
.history_pos
-1 );
746 console_history_get( vg_console
.input
, vg_console
.history_pos
);
748 console_move_cursor( &vg_console
.cursor_user
,
749 &vg_console
.cursor_pos
,
750 vg_list_size(vg_console
.input
)-1, 1 );
753 VG_STATIC
void _console_up(void)
755 vg_console
.history_pos
= VG_MAX
760 vg_console
.history_pos
+1,
763 vg_list_size( vg_console
.history
),
764 vg_console
.history_count
- 1
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);
775 VG_STATIC
void _console_backspace(void)
777 vg_console
.cursor_user
= console_delete_char( -1 );
778 vg_console
.cursor_pos
= vg_console
.cursor_user
;
781 VG_STATIC
void _console_delete(void)
783 vg_console
.cursor_user
= console_delete_char( 1 );
784 vg_console
.cursor_pos
= vg_console
.cursor_user
;
787 VG_STATIC
void _console_home_select(void)
789 console_move_cursor( &vg_console
.cursor_user
, NULL
, -10000, 0 );
792 VG_STATIC
void _console_home(void)
794 console_move_cursor( &vg_console
.cursor_user
,
795 &vg_console
.cursor_pos
, -10000, 1 );
798 VG_STATIC
void _console_end_select(void)
800 console_move_cursor( &vg_console
.cursor_user
, NULL
, 10000, 0 );
803 VG_STATIC
void _console_end(void)
805 console_move_cursor( &vg_console
.cursor_user
,
806 &vg_console
.cursor_pos
,
807 vg_list_size(vg_console
.input
)-1, 1 );
810 VG_STATIC
void _console_select_all(void)
812 console_move_cursor( &vg_console
.cursor_user
, NULL
, 10000, 0);
813 console_move_cursor( &vg_console
.cursor_pos
, NULL
, -10000, 0);
816 VG_STATIC
void _console_cut(void)
818 console_to_clipboard();
819 vg_console
.cursor_user
= console_delete_char(0);
820 vg_console
.cursor_pos
= vg_console
.cursor_user
;
823 VG_STATIC
void _console_enter(void)
825 if( !strlen( vg_console
.input
) )
828 vg_info( "%s\n", vg_console
.input
);
830 if( strcmp( vg_console
.input
,
831 vg_console
.history
[ vg_console
.history_last
]) )
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
],
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';
848 console_update_suggestions();
852 * Suggestion controls
854 VG_STATIC
void _console_fetch_suggestion(void)
856 if( vg_console
.suggestion_select
== -1 )
858 strcpy( vg_console
.input
, vg_console
.input_copy
);
859 console_move_cursor( &vg_console
.cursor_user
,
860 &vg_console
.cursor_pos
, 10000, 1 );
864 strncpy( vg_console
.input
,
865 vg_console
.suggestions
[ vg_console
.suggestion_select
].str
,
866 vg_list_size( vg_console
.input
)-1 );
868 console_move_cursor( &vg_console
.cursor_user
,
869 &vg_console
.cursor_pos
, 10000, 1 );
870 console_put_char( ' ' );
874 VG_STATIC
void _console_suggest_store_normal(void)
876 if( vg_console
.suggestion_select
== -1 )
877 strcpy( vg_console
.input_copy
, vg_console
.input
);
880 VG_STATIC
void _console_suggest_next(void)
882 if( vg_console
.suggestion_count
)
884 _console_suggest_store_normal();
886 vg_console
.suggestion_select
++;
888 if( vg_console
.suggestion_select
>= vg_console
.suggestion_count
)
889 vg_console
.suggestion_select
= -1;
891 _console_fetch_suggestion();
895 VG_STATIC
void _console_suggest_prev(void)
897 if( vg_console
.suggestion_count
)
899 _console_suggest_store_normal();
901 vg_console
.suggestion_select
--;
903 if( vg_console
.suggestion_select
< -1 )
904 vg_console
.suggestion_select
= vg_console
.suggestion_count
-1;
906 _console_fetch_suggestion();
913 VG_STATIC
void console_proc_key( SDL_Keysym ev
)
915 /* Open / close console */
916 if( ev
.sym
== SDLK_BACKQUOTE
)
918 vg_console
.enabled
= !vg_console
.enabled
;
920 if( vg_console
.enabled
)
921 SDL_StartTextInput();
926 if( !vg_console
.enabled
) return;
928 struct console_mapping
933 void (*handler
)(void);
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
}
960 if( ev
.mod
& KMOD_SHIFT
)
963 if( ev
.mod
& KMOD_CTRL
)
966 if( ev
.mod
& KMOD_ALT
)
969 for( int i
=0; i
<vg_list_size( mappings
); i
++ )
971 struct console_mapping
*mapping
= &mappings
[i
];
973 if( mapping
->key
== ev
.sym
)
975 if( mapping
->mod
== 0 )
983 else if( (mod
& mapping
->mod
) == mapping
->mod
)
993 * Callback for text entry mode
995 VG_STATIC
void console_proc_utf8( const char *text
)
997 const char *ptr
= text
;
1002 console_put_char( *ptr
);
1006 console_update_suggestions();
1009 #endif /* VG_CONSOLE_H */