heebie
[vg.git] / vg_console.h
index 8d35aa286fdce8933e35b39720cd5dfd6534d850..26c6a6d20c00414eef330fa18f7500b4caf52dd9 100644 (file)
 #include "vg/vg_ui.h"
 #include "vg/vg_log.h"
 
-typedef struct vg_convar vg_convar;
+#define VG_VAR_F32_PERSISTENT( NAME )  \
+   vg_var_push( (struct vg_var){       \
+      .name = #NAME,                   \
+      .data = &NAME,                   \
+      .data_type = k_var_dtype_f32,    \
+      .persistent = 1                  \
+   });
+
+#define VG_VAR_F32( NAME )             \
+   vg_var_push( (struct vg_var){       \
+      .name = #NAME,                   \
+      .data = &NAME,                   \
+      .data_type = k_var_dtype_f32,    \
+   });
+
+#define VG_VAR_I32_PERSISTENT( NAME )  \
+   vg_var_push( (struct vg_var){       \
+      .name = #NAME,                   \
+      .data = &NAME,                   \
+      .data_type = k_var_dtype_i32,    \
+      .persistent = 1                  \
+   });
+
+#define VG_VAR_I32( NAME )             \
+   vg_var_push( (struct vg_var){       \
+      .name = #NAME,                   \
+      .data = &NAME,                   \
+      .data_type = k_var_dtype_i32,    \
+   });
+
+typedef struct vg_var vg_var;
 typedef struct vg_cmd vg_cmd;
 
 struct vg_console
 {
-       struct vg_convar
+       struct vg_var
        {
                void *data;
-      void (*update)(void);
                const char *name;
                
-               enum vg_convar_dtype
+               enum vg_var_dtype
                {
-                       k_convar_dtype_i32,
-                       k_convar_dtype_u32,
-                       k_convar_dtype_f32
+                       k_var_dtype_i32,
+                       k_var_dtype_u32,
+                       k_var_dtype_f32
                } 
                data_type;
                
@@ -47,18 +76,33 @@ struct vg_console
 
                int persistent; /* Should this var be stored to cfg/auto.conf? */
        } 
-       convars[ 32 ];
+       vars[ 128 ];
        
        struct vg_cmd
        {
-               int (*function)( int argc, char const *argv[] );
+               int  (*function)( int argc, char const *argv[] );
+      void (*poll_suggest)( int argc, char const *argv[] );
                const char *name;
        }
        functions[ 32 ];
 
-   u32 convar_count, function_count;
+   struct 
+   {
+      const char *str;
+      int len;
+
+      u32 lev_score;
+   }
+   suggestions[12];
+   u32 suggestion_count;
+   int suggestion_select,
+       suggestion_pastepos,
+       suggestion_maxlen;
+
+   u32 var_count, function_count;
        
-       char input[96];
+       char input[96],
+        input_copy[96];
        int cursor_user, cursor_pos, string_length;
        
        char history[32][96];
@@ -68,7 +112,7 @@ struct vg_console
 }
 vg_console;
 
-VG_STATIC void  vg_convar_push( struct vg_convar cv );
+VG_STATIC void  vg_var_push( struct vg_var cv );
 VG_STATIC void  vg_function_push( struct vg_cmd cmd );
 
 VG_STATIC void _vg_console_draw( void );
@@ -102,13 +146,13 @@ VG_STATIC int _vg_console_enabled(void)
        return vg_console.enabled; 
 }
 
-VG_STATIC void vg_convar_push( vg_convar cv )
+VG_STATIC void vg_var_push( vg_var cv )
 {
-   if( vg_console.convar_count > vg_list_size(vg_console.convars) )
-      vg_fatal_exit_loop( "Too many convars registered" );
+   if( vg_console.var_count > vg_list_size(vg_console.vars) )
+      vg_fatal_exit_loop( "Too many vars registered" );
 
    vg_info( "Console variable '%s' registered\n", cv.name );
-   vg_console.convars[ vg_console.convar_count ++ ] = cv;
+   vg_console.vars[ vg_console.var_count ++ ] = cv;
 }
 
 VG_STATIC void vg_function_push( struct vg_cmd cmd )
@@ -136,9 +180,12 @@ VG_STATIC void _vg_console_draw( void )
        vg_uictx.cursor[3] = log_lines*fh;
        ui_fill_x();
        
+   /* 
+    * log
+    */
        ui_new_node();
        {
-               ui_fill_rect( vg_uictx.cursor, 0x77333333 );
+               ui_fill_rect( vg_uictx.cursor, 0x77181818 );
        
                vg_uictx.cursor[3] = fh;
                ui_align_bottom();
@@ -157,12 +204,14 @@ VG_STATIC void _vg_console_draw( void )
        }
        ui_end_down();
        
+       
+   /* Input area */
        vg_uictx.cursor[1] += 2;
        vg_uictx.cursor[3] = fh;
-       
+
        ui_new_node();
        {
-               ui_fill_rect( vg_uictx.cursor, 0x77333333 );
+               ui_fill_rect( vg_uictx.cursor, 0x77111111 );
                ui_text( vg_uictx.cursor, vg_console.input, 1, 0 );
                
                int start = VG_MIN( vg_console.cursor_pos, vg_console.cursor_user ),
@@ -175,6 +224,33 @@ VG_STATIC void _vg_console_draw( void )
                ui_fill_rect( vg_uictx.cursor, 0x66ffffff );
        }
        ui_end_down();
+
+
+   /* suggestions */
+   if( vg_console.suggestion_count )
+   {
+      vg_uictx.cursor[0] += UI_GLYPH_SPACING_X * vg_console.suggestion_pastepos;
+      vg_uictx.cursor[1] += 2;
+      vg_uictx.cursor[3] = vg_console.suggestion_count * fh;
+      vg_uictx.cursor[2] = UI_GLYPH_SPACING_X * vg_console.suggestion_maxlen;
+
+      ui_new_node();
+      {
+         ui_fill_rect( vg_uictx.cursor, 0x77040404 );
+
+         vg_uictx.cursor[3] = fh;
+         for( int i=0; i<vg_console.suggestion_count; i ++ )
+         {
+            if( i == vg_console.suggestion_select )
+               ui_fill_rect( vg_uictx.cursor, 0x66a0e508 );
+
+            ui_text( vg_uictx.cursor, vg_console.suggestions[i].str, 1, 0 );
+            vg_uictx.cursor[1] += fh;
+         }
+      }
+      ui_end_down();
+   }
+
    SDL_AtomicUnlock( &log_print_sl );
 }
 
@@ -186,9 +262,9 @@ VG_STATIC int _vg_console_list( int argc, char const *argv[] )
                vg_info( "* %s\n", cmd->name );
        }
        
-       for( int i=0; i<vg_console.convar_count; i ++ )
+       for( int i=0; i<vg_console.var_count; i ++ )
        {
-               struct vg_convar *cv = &vg_console.convars[ i ];
+               struct vg_var *cv = &vg_console.vars[ i ];
                vg_info( "%s\n", cv->name );
        }
        
@@ -242,21 +318,21 @@ VG_STATIC void _vg_console_write_persistent(void)
 {
        FILE *fp = fopen( "cfg/auto.conf", "w" );
        
-       for( int i=0; i<vg_console.convar_count; i ++ )
+       for( int i=0; i<vg_console.var_count; i ++ )
        {
-               struct vg_convar *cv = &vg_console.convars[i];
+               struct vg_var *cv = &vg_console.vars[i];
 
                if( cv->persistent )
                {
                        switch( cv->data_type )
                        {
-                               case k_convar_dtype_i32:
+                               case k_var_dtype_i32:
                                        fprintf( fp, "%s %d\n", cv->name, *(i32 *)(cv->data) );
                                break;
-                               case k_convar_dtype_u32:
+                               case k_var_dtype_u32:
                                        fprintf( fp, "%s %u\n", cv->name, *(u32 *)(cv->data) );
                                break;
-                               case k_convar_dtype_f32:
+                               case k_var_dtype_f32:
                                        fprintf( fp, "%s %.5f\n", cv->name, *(float *)(cv->data ) );
                                break;
                        }
@@ -271,119 +347,322 @@ VG_STATIC void _vg_console_free(void)
        _vg_console_write_persistent();
 }
 
-VG_STATIC void vg_execute_console_input( const char *cmd )
+/*
+ * splits src into tokens and fills out args as pointers to those tokens
+ * returns number of tokens
+ * dst must be as long as src
+ */
+VG_STATIC int vg_console_tokenize( const char *src, char *dst, 
+                                   const char *args[8] )
 {
-       char temp[512];
-       char const *args[9];
-       int arg_count = 0;
+       int arg_count = 0,
+       in_token = 0;
        
-       int in_token = 0;
-       
-       /* Split string into tokens */
-       for( int i = 0; i < vg_list_size( temp ); i ++ )
+       for( int i=0; 1; i ++ )
        {
-               if( cmd[i] )
+               if( src[i] )
                {
-                       if( cmd[i] == ' ' || cmd[i] == '\t' )
+                       if( src[i] == ' ' || src[i] == '\t' )
                        {
-                               temp[i] = '\0';
+            if( in_token )
+               dst[i] = '\0';
+
                                in_token = 0;
                                
-                               if( arg_count == vg_list_size( args ) )
+                               if( arg_count == 8 )
                                        break;
                        }
                        else
                        {
-                               temp[i] = cmd[i];
+                               dst[i] = src[i];
                                
                                if( !in_token )
                                {
-                                       args[ arg_count ++ ] = temp + i;
+                                       args[ arg_count ++ ] = &dst[i];
                                        in_token = 1;
                                }
                        }
                }
                else
                {
-                       temp[i] = '\0';
+                       dst[i] = '\0';
                        break;
                }
        }
-       
-       if( arg_count == 0 )
-               return;
-       
-       int data_int;
-   float data_float;
-       
-       for( int i=0; i<vg_console.convar_count; i ++ )
+
+   return arg_count;
+}
+
+VG_STATIC vg_var *vg_console_match_var( const char *kw )
+{
+       for( int i=0; i<vg_console.var_count; i ++ )
        {
-               struct vg_convar *cv = &vg_console.convars[ i ];
-               if( !strcmp( cv->name, args[0] ) )
+               struct vg_var *cv = &vg_console.vars[ i ];
+               if( !strcmp( cv->name, kw ) )
                {
-                       /* Cvar Matched, try get value */
-                       if( arg_count >= 2 )
-                       {
-                               switch( cv->data_type )
-                               {
-                                       case k_convar_dtype_u32:
-                                       case k_convar_dtype_i32: 
-                                               
-                                               data_int = atoi( args[1] ); 
-                  
-                                               *((int *)cv->data) = cv->opt_i32.clamp? 
-                   VG_MIN( VG_MAX(data_int, cv->opt_i32.min), cv->opt_i32.max ): 
-                   data_int;
-                                       
-                                       break;
-                                       case k_convar_dtype_f32: 
-                  data_float = atof( args[1] );
-                  *((float *)cv->data) = cv->opt_f32.clamp?
-                     vg_minf( vg_maxf( data_float, cv->opt_f32.min), 
-                              cv->opt_f32.max ):
-                     data_float;
-               break;
-                               }
+         return cv;
+      }
+   }
 
-            if( cv->update )
-               cv->update();
-                       }
-                       else
-                       {
-                               switch( cv->data_type )
-                               {
-                                       case k_convar_dtype_i32: 
-                  vg_info( "= %d\n", *((int *)cv->data) ); 
-               break;
-                                       case k_convar_dtype_u32: 
-                  vg_info( "= %u\n", *((u32 *)cv->data) );
-               break;
-                                       case k_convar_dtype_f32: 
-                  vg_info( "= %.4f\n", *((float *)cv->data) );
-               break;
-                               }
-                       }
-               
-                       return;
-               }
-       }
-       
-   /*
-    * Find and excecute command
-    */
+   return NULL;
+}
+
+VG_STATIC vg_cmd *vg_console_match_cmd( const char *kw )
+{
        for( int i=0; i<vg_console.function_count; i ++ )
        {
                struct vg_cmd *cmd = &vg_console.functions[ i ];
-               if( !strcmp( cmd->name, args[0] ) )
+               if( !strcmp( cmd->name, kw ) )
                {
-                       cmd->function( arg_count-1, args+1 );
-                       return;
-               }
+         return cmd;
+      }
+   }
+
+   return NULL;
+}
+
+VG_STATIC void vg_execute_console_input( const char *cmd )
+{
+       char temp[512];
+       char const *args[8];
+       int arg_count = vg_console_tokenize( cmd, temp, args );
+       
+       if( arg_count == 0 )
+               return;
+       
+       int data_int;
+   float data_float;
+
+   vg_var *cv = vg_console_match_var( args[0] );
+   vg_cmd *fn = vg_console_match_cmd( args[0] );
+
+   assert( !(cv && fn) );
+
+   if( cv )
+   {
+      /* Cvar Matched, try get value */
+      if( arg_count >= 2 )
+      {
+         if( (cv->data_type == k_var_dtype_u32) ||
+             (cv->data_type == k_var_dtype_i32) )
+         {
+            data_int = atoi( args[1] ); 
+            
+            *((int *)cv->data) = cv->opt_i32.clamp? 
+             VG_MIN( VG_MAX(data_int, cv->opt_i32.min), cv->opt_i32.max ): 
+             data_int;
+         }
+         else if( cv->data_type == k_var_dtype_f32 )
+         {
+            data_float = atof( args[1] );
+            *((float *)cv->data) = cv->opt_f32.clamp?
+               vg_minf( vg_maxf( data_float, cv->opt_f32.min), 
+                        cv->opt_f32.max ):
+               data_float;
+         }
+      }
+      else
+      {
+         if( cv->data_type == k_var_dtype_i32 )
+            vg_info( "= %d\n", *((int *)cv->data) ); 
+         else if( cv->data_type == k_var_dtype_u32 )
+            vg_info( "= %u\n", *((u32 *)cv->data) );
+         else if( cv->data_type == k_var_dtype_f32 )
+            vg_info( "= %.4f\n", *((float *)cv->data) );
+      }
+   
+      return;
        }
+   else if( fn )
+   {
+      fn->function( arg_count-1, args+1 );
+      return;
+   }
        
        vg_error( "No command/var named '%s'. Use 'list' to view all\n", args[0] );
 }
 
+u32 str_lev_distance( const char *s1, const char *s2 )
+{
+   u32 m = strlen( s1 ),
+       n = strlen( s2 );
+
+   if( m==0 ) return n;
+   if( n==0 ) return m;
+
+   assert( n+1 <= 256 );
+
+   u32 costs[ 256 ];
+
+   for( u32 k=0; k<=n; k++ ) 
+      costs[k] = k;
+
+   u32 i = 0;
+   for( u32 i=0; i<m; i++ )
+   {
+      costs[0] = i+1;
+
+      u32 corner = i;
+
+      for( u32 j=0; j<n; j++ )
+      {
+         u32 upper = costs[j+1];
+
+         if( s1[i] == s2[j] )
+            costs[ j+1 ] = corner;
+         else
+         {
+            u32 t = (upper < corner)? upper: corner;
+            costs[j+1] = ((costs[j] < t)? costs[j]: t) + 1;
+         }
+
+         corner = upper;
+      }
+   }
+
+   return costs[n];
+}
+
+u32 str_lcs( const char *s1, const char *s2 )
+{
+   u32 m = VG_MIN( 31, strlen( s1 ) ),
+       n = VG_MIN( 31, strlen( s2 ) );
+
+   int suff[32][32],
+       result = 0;
+
+   for( int i=0; i<=m; i++ )
+   {
+      for( int j=0; j<=n; j++ )
+      {
+         if( i == 0 || j == 0 )
+            suff[i][j] = 0;
+         else if( s1[i-1] == s2[j-1] )
+         {
+            suff[i][j] = suff[i-1][j-1] + 1;
+            result = VG_MAX( result, suff[i][j] );
+         }
+         else
+            suff[i][j] = 0;
+      }
+   }
+   
+   return result;
+}
+
+/* str must not fuckoff ever! */
+VG_STATIC void console_suggest_score_text( const char *str, const char *input,
+                                           int minscore )
+{
+   /* filter duplicates */
+   for( int i=0; i<vg_console.suggestion_count; i++ )
+      if( !strcmp( vg_console.suggestions[i].str, str ) )
+         return;
+
+   /* calc score */
+   u32 score = str_lcs( str, input );
+
+   if( score < minscore )
+      return;
+
+   int best_pos = vg_console.suggestion_count;
+   for( int j=best_pos-1; j>=0; j -- )
+      if( score > vg_console.suggestions[j].lev_score )
+         best_pos = j;
+   
+   /* insert if good score */
+   if( best_pos < vg_list_size( vg_console.suggestions ) )
+   {
+      int start = VG_MIN( vg_console.suggestion_count, 
+                          vg_list_size( vg_console.suggestions )-1 );
+      for( int j=start; j>best_pos; j -- )
+         vg_console.suggestions[j] = vg_console.suggestions[j-1];
+
+      vg_console.suggestions[ best_pos ].str = str;
+      vg_console.suggestions[ best_pos ].len = strlen( str );
+      vg_console.suggestions[ best_pos ].lev_score = score;
+
+      if( vg_console.suggestion_count < 
+            vg_list_size( vg_console.suggestions ) )
+         vg_console.suggestion_count ++;
+   }
+}
+
+VG_STATIC void console_update_suggestions(void)
+{
+   vg_console.suggestion_count = 0;
+   vg_console.suggestion_select = -1;
+   vg_console.suggestion_maxlen = 0;
+
+   /* 
+    * - must be typing something
+    * - must be at the end
+    * - prev char must not be a whitespace
+    * - cursors should match
+    */
+
+   if( vg_console.cursor_pos == 0 )
+      return;
+
+   if( vg_console.cursor_pos != vg_console.cursor_user )
+      return;
+
+   if( vg_console.input[ vg_console.cursor_pos ] != '\0' )
+      return;
+
+   if(  (vg_console.input[ vg_console.cursor_pos -1 ] == ' ') ||
+        (vg_console.input[ vg_console.cursor_pos -1 ] == '\t') )
+      return;
+
+   char temp[128];
+   const char *args[8];
+
+   int token_count = vg_console_tokenize( vg_console.input, temp, args );
+
+   vg_console.suggestion_pastepos = args[token_count-1]-temp;
+
+   /* Score all our commands and cvars */
+   if( token_count == 1 )
+   {
+      for( int i=0; i<vg_console.var_count; i++ )
+      {
+         vg_var *cvar = &vg_console.vars[i];
+         console_suggest_score_text( cvar->name, args[0], 1 );
+      }
+
+      for( int i=0; i<vg_console.function_count; i++ )
+      {
+         vg_cmd *cmd = &vg_console.functions[i];
+         console_suggest_score_text( cmd->name, args[0], 1 );
+      }
+   }
+   else
+   {
+      vg_cmd *cmd = vg_console_match_cmd( args[0] );
+      vg_var *var = vg_console_match_var( args[0] );
+
+      assert( !( cmd && var ) );
+
+      if( cmd )
+         if( cmd->poll_suggest )
+            cmd->poll_suggest( token_count-1, &args[1] );
+   }
+
+   /* some post processing */
+   for( int i=0; i<vg_console.suggestion_count; i++ )
+   {
+      vg_console.suggestion_maxlen = VG_MAX( vg_console.suggestion_maxlen,
+                                             vg_console.suggestions[i].len );
+
+      if( vg_console.suggestions[i].lev_score <
+          vg_console.suggestions[0].lev_score/2 )
+      {
+         vg_console.suggestion_count = i;
+         return;
+      }
+   }
+}
+
 /*
  * Console Interface
  */
@@ -394,12 +673,12 @@ VG_STATIC void console_make_selection( int* start, int* end )
 }
 
 VG_STATIC void console_move_cursor( int* cursor0, int* cursor1, 
-      int dir, int snap_together )
+                                    int dir, int snap_together )
 {
        *cursor0 = VG_MAX( 0, vg_console.cursor_user + dir );
        *cursor0 = 
       VG_MIN( 
-         VG_MIN( vg_list_size( vg_console.input ), strlen( vg_console.input )), 
+         VG_MIN( vg_list_size(vg_console.input)-1, strlen( vg_console.input )), 
       *cursor0 );
 
        if( snap_together ) 
@@ -408,10 +687,10 @@ VG_STATIC void console_move_cursor( int* cursor0, int* cursor1,
 
 VG_STATIC int console_makeroom( int datastart, int length )
 {
-       int move_to = VG_MIN( datastart+length, vg_list_size( vg_console.input ) );
+       int move_to = VG_MIN( datastart+length, vg_list_size( vg_console.input )-1 );
        int move_amount = strlen( vg_console.input )-datastart;
        int move_end = 
-      VG_MIN( move_to+move_amount, vg_list_size( vg_console.input ) );
+      VG_MIN( move_to+move_amount, vg_list_size( vg_console.input )-1 );
        move_amount = move_end-move_to;
        
        if( move_amount )
@@ -421,7 +700,7 @@ VG_STATIC int console_makeroom( int datastart, int length )
        
        vg_console.input[ move_end ] = '\0';
        
-       return VG_MIN( length, vg_list_size( vg_console.input )-datastart );
+       return VG_MIN( length, vg_list_size( vg_console.input )-datastart-1 );
 }
 
 VG_STATIC int console_delete_char( int direction )
@@ -479,8 +758,9 @@ VG_STATIC void console_clipboard_paste(void)
        memcpy( vg_console.input + datastart, text, cpylength);
        console_move_cursor( &vg_console.cursor_user, 
                         &vg_console.cursor_pos, cpylength, 1 );
-   
    SDL_free( text );
+
+   console_update_suggestions();
 }
 
 VG_STATIC void console_put_char( char c )
@@ -542,7 +822,7 @@ VG_STATIC void _console_down(void)
 
    console_move_cursor( &vg_console.cursor_user, 
                         &vg_console.cursor_pos, 
-                        vg_list_size( vg_console.input ), 1 );
+                        vg_list_size(vg_console.input)-1, 1 );
 }
 
 VG_STATIC void _console_up(void)
@@ -564,19 +844,23 @@ VG_STATIC void _console_up(void)
    console_history_get( vg_console.input, vg_console.history_pos );
    console_move_cursor( &vg_console.cursor_user, 
                         &vg_console.cursor_pos, 
-                        vg_list_size( vg_console.input ), 1);
+                        vg_list_size(vg_console.input)-1, 1);
 }
 
 VG_STATIC void _console_backspace(void)
 {
    vg_console.cursor_user = console_delete_char( -1 );
    vg_console.cursor_pos = vg_console.cursor_user;
+
+   console_update_suggestions();
 }
 
 VG_STATIC void _console_delete(void)
 {
    vg_console.cursor_user = console_delete_char( 1 );
    vg_console.cursor_pos = vg_console.cursor_user;
+
+   console_update_suggestions();
 }
 
 VG_STATIC void _console_home_select(void)
@@ -599,13 +883,13 @@ VG_STATIC void _console_end(void)
 {
    console_move_cursor( &vg_console.cursor_user, 
                         &vg_console.cursor_pos, 
-                        vg_list_size( vg_console.input ), 1 );
+                        vg_list_size(vg_console.input)-1, 1 );
 }
 
 VG_STATIC void _console_select_all(void)
 {
-   console_move_cursor( &vg_console.cursor_user, NULL, 10000, 0);
-   console_move_cursor( &vg_console.cursor_pos, NULL, -10000, 0);
+   console_move_cursor( &vg_console.cursor_user, NULL,  10000, 0);
+   console_move_cursor( &vg_console.cursor_pos,  NULL, -10000, 0);
 }
 
 VG_STATIC void _console_cut(void)
@@ -639,8 +923,77 @@ VG_STATIC void _console_enter(void)
    console_move_cursor( &vg_console.cursor_user, 
                         &vg_console.cursor_pos, -10000, 1 );
    vg_console.input[0] = '\0';
+
+   console_update_suggestions();
+}
+
+/*
+ * Suggestion controls
+ */
+VG_STATIC void _console_fetch_suggestion(void)
+{
+   char *target = &vg_console.input[ vg_console.suggestion_pastepos ];
+
+   if( vg_console.suggestion_select == -1 )
+   {
+      strcpy( target, vg_console.input_copy );
+      console_move_cursor( &vg_console.cursor_user, 
+                           &vg_console.cursor_pos, 10000, 1 );
+   }
+   else
+   {
+      strncpy( target,
+            vg_console.suggestions[ vg_console.suggestion_select ].str,
+            vg_list_size( vg_console.input )-1 );
+
+      console_move_cursor( &vg_console.cursor_user, 
+                           &vg_console.cursor_pos, 10000, 1 );
+      console_put_char( ' ' ); 
+   }
+}
+
+VG_STATIC void _console_suggest_store_normal(void)
+{
+   if( vg_console.suggestion_select == -1 )
+   {
+      char *target = &vg_console.input[ vg_console.suggestion_pastepos ];
+      strcpy( vg_console.input_copy, target );
+   }
 }
 
+VG_STATIC void _console_suggest_next(void)
+{
+   if( vg_console.suggestion_count )
+   {
+      _console_suggest_store_normal();
+
+      vg_console.suggestion_select ++;
+
+      if( vg_console.suggestion_select >= vg_console.suggestion_count )
+         vg_console.suggestion_select = -1;
+      
+      _console_fetch_suggestion();
+   }
+}
+
+VG_STATIC void _console_suggest_prev(void)
+{
+   if( vg_console.suggestion_count )
+   {
+      _console_suggest_store_normal();
+
+      vg_console.suggestion_select --;
+
+      if( vg_console.suggestion_select < -1 )
+         vg_console.suggestion_select = vg_console.suggestion_count-1;
+
+      _console_fetch_suggestion();
+   }
+}
+
+/*
+ * Handles binds
+ */
 VG_STATIC void console_proc_key( SDL_Keysym ev )
 {
    /* Open / close console */
@@ -655,7 +1008,7 @@ VG_STATIC void console_proc_key( SDL_Keysym ev )
    }
    
    if( !vg_console.enabled ) return;
-   
+
    struct console_mapping
    {
       u16 mod;
@@ -663,7 +1016,7 @@ VG_STATIC void console_proc_key( SDL_Keysym ev )
       
       void (*handler)(void);
    }
-   mapping[] =
+   mappings[] =
    {
       { 0,              SDLK_LEFT,       _console_left              },
       { KMOD_SHIFT,     SDLK_LEFT,       _console_left_select       },
@@ -681,32 +1034,48 @@ VG_STATIC void console_proc_key( SDL_Keysym ev )
       { KMOD_CTRL,      SDLK_c,          console_to_clipboard       },
       { KMOD_CTRL,      SDLK_x,          _console_cut               },
       { KMOD_CTRL,      SDLK_v,          console_clipboard_paste    },
-      { 0,              SDLK_RETURN,     _console_enter             }
+      { 0,              SDLK_RETURN,     _console_enter             },
+      { KMOD_CTRL,      SDLK_n,          _console_suggest_next      },
+      { KMOD_CTRL,      SDLK_p,          _console_suggest_prev      }
    };
 
-   for( int i=0; i<vg_list_size( mapping ); i++ )
+   SDL_Keymod mod = 0;
+
+   if( ev.mod & KMOD_SHIFT )
+      mod |= KMOD_SHIFT;
+
+   if( ev.mod & KMOD_CTRL )
+      mod |= KMOD_CTRL;
+
+   if( ev.mod & KMOD_ALT )
+      mod |= KMOD_ALT;
+
+   for( int i=0; i<vg_list_size( mappings ); i++ )
    {
-      struct console_mapping *mk = &mapping[i];
+      struct console_mapping *mapping = &mappings[i];
 
-      if( mk->key == ev.sym )
+      if( mapping->key == ev.sym )
       {
-         if( mk->mod == 0 )
+         if( mapping->mod == 0 )
          {
-            if( ev.mod == 0 )
+            if( mod == 0 )
             {
-               mk->handler();
+               mapping->handler();
                return;
             }
          }
-         else if( (ev.mod & mk->mod) == mk->mod )
+         else if( (mod & mapping->mod) == mapping->mod )
          {
-            mk->handler();
+            mapping->handler();
             return;
          }
       }
    }
 }
 
+/*
+ * Callback for text entry mode
+ */
 VG_STATIC void console_proc_utf8( const char *text )
 {
    const char *ptr = text;
@@ -717,6 +1086,8 @@ VG_STATIC void console_proc_utf8( const char *text )
          console_put_char( *ptr );
       ptr ++;
    }
+
+   console_update_suggestions();
 }
 
 #endif /* VG_CONSOLE_H */