Commits

Rafael García committed cef9d2e

VIM keybindings + 256 colors by Dimitris Zervas

  • Participants
  • Parent commits 60d6044

Comments (0)

Files changed (4)

 - Update manpage
 - Create m_*vline
 - Bracket matching?
-- Number modifier?
 - Smart end? is it really needed?
 - BUG: high CPU usage on continuous resize
 - BUG: Deal with the bigger-than-window line
 - Improve regex search (backwards!!)
 - Improve syntax highlight, multiline?
 - Group delete undos?
+- Show line numbers
+- Show some documentation when you press tab in command mode
+- FINDBW prompt? is it needed?
+- Support multiple marks (with S_Parameter)
+- Instead of delete, just cut
 
 At config.def.h:
 - Bindings!

File config.def.h

 #define HILIGHT_SYNTAX  1
 #define SHOW_NONPRINT   0
 #define HANDLE_MOUSE    1
+#define VIM_BINDINGS    1
 
 /* Things unlikely to be changed, yet still in the config.h file */
 static const bool   isutf8     = TRUE;
 #endif
 
 /* Helper config functions, not used in main code */
-static void f_moveboth(const Arg*);
 static void f_pipeai(const Arg*);
 static void f_pipeline(const Arg*);
 static void f_pipenull(const Arg*);
 #define CMD_P   PROMPT("Command:",     "/\n?\nw\nq\n!\nsyntax\noffset\nicase\nro\nai\ndump", "")
 
 /* Args to f_pipe and friends, simple examples are inlined instead */
-#define TOCLIP     { .v = "tee /tmp/.sandy.clipboard.$USER | xsel -ib 2>/dev/null" }
-#define FROMCLIP   { .v = "xsel -ob 2>/dev/null || cat /tmp/.sandy.clipboard.$USER" }
-#define TOSEL      { .v = "tee /tmp/.sandy.selection.$USER | xsel -i 2>/dev/null" }
-#define FROMSEL    { .v = "xsel -o 2>/dev/null || cat /tmp/.sandy.selection.$USER" }
-#define AUTOINDENT { .v = "awk 'BEGIN{ l=\"\\n\" }; \
+#define TOCLIP     "tee /tmp/.sandy.clipboard.$USER | xsel -ib 2>/dev/null"
+#define FROMCLIP   "xsel -ob 2>/dev/null || cat /tmp/.sandy.clipboard.$USER"
+#define TOSEL      "tee /tmp/.sandy.selection.$USER | xsel -i 2>/dev/null"
+#define FROMSEL    "xsel -o 2>/dev/null || cat /tmp/.sandy.selection.$USER"
+#define AUTOINDENT "awk 'BEGIN{ l=\"\\n\" }; \
 				{ if(match($0, \"^[\t ]+[^\t ]\")) l=substr($0, RSTART, RLENGTH-1); \
 				  else l=\"\"; \
 				  if(FNR==NR && $0 ~ /^[\t ]+$/) print \"\"; \
 				  else print }; \
-				END{ ORS=\"\"; print l }' 2>/dev/null" }
-#define CAPITALIZE { .v = "awk 'BEGIN{ ORS=\"\" }; \
+				END{ ORS=\"\"; print l }' 2>/dev/null"
+#define CAPITALIZE "awk 'BEGIN{ ORS=\"\" }; \
 				{ for ( i=1; i <= NF; i++) { $i=tolower($i) ; sub(\".\", substr(toupper($i),1,1) , $i) } \
 				if(FNR==NF) print $0; \
-				else print $0\"\\n\" }' 2>/dev/null" }
+				else print $0\"\\n\" }' 2>/dev/null"
 
 /* Hooks are launched from the main code */
 #define HOOK_SAVE_NO_FILE f_spawn (&(const Arg)SAVEAS)
 
 static const Key curskeys[] = { /* Plain keys here, no CONTROL or META */
 /* keyv.i,                  tests,                     func,       arg */
-{ .keyv.i = KEY_BACKSPACE,  { t_rw,  0,    0,   0 },   f_delete,   { .m = m_prevchar } },
-{ .keyv.i = KEY_DC,         { t_sel, t_rw, 0,   0 },   f_delete,   { .m = m_tosel    } },
-{ .keyv.i = KEY_DC,         { t_rw,  0,    0,   0 },   f_delete,   { .m = m_nextchar } },
-{ .keyv.i = KEY_SDC,        { t_sel, t_rw, 0,   0 },   f_delete,   { .m = m_tosel } },
-{ .keyv.i = KEY_SDC,        { t_rw,  0,    0,   0 },   f_delete,   { .m = m_nextchar } },
-{ .keyv.i = KEY_IC,         { t_sel, 0,    0,   0 },   f_pipero,   TOCLIP },
-{ .keyv.i = KEY_SIC,        { t_rw,  0,    0,   0 },   f_pipenull, FROMCLIP },
-{ .keyv.i = KEY_HOME,       { t_ai,  0,    0,   0 },   f_moveboth, { .m = m_smartbol } },
-{ .keyv.i = KEY_HOME,       { 0,     0,    0,   0 },   f_moveboth, { .m = m_bol      } },
-{ .keyv.i = KEY_END,        { 0,     0,    0,   0 },   f_moveboth, { .m = m_eol      } },
-{ .keyv.i = KEY_SHOME,      { 0,     0,    0,   0 },   f_moveboth, { .m = m_bof      } },
-{ .keyv.i = KEY_SEND,       { 0,     0,    0,   0 },   f_moveboth, { .m = m_eof      } },
-{ .keyv.i = KEY_PPAGE,      { 0,     0,    0,   0 },   f_moveboth, { .m = m_prevscr  } },
-{ .keyv.i = KEY_NPAGE,      { 0,     0,    0,   0 },   f_moveboth, { .m = m_nextscr  } },
-{ .keyv.i = KEY_UP,         { 0,     0,    0,   0 },   f_moveboth, { .m = m_prevline } },
-{ .keyv.i = KEY_DOWN,       { 0,     0,    0,   0 },   f_moveboth, { .m = m_nextline } },
-{ .keyv.i = KEY_LEFT,       { 0,     0,    0,   0 },   f_moveboth, { .m = m_prevchar } },
-{ .keyv.i = KEY_RIGHT,      { 0,     0,    0,   0 },   f_moveboth, { .m = m_nextchar } },
-{ .keyv.i = KEY_SLEFT,      { 0,     0,    0,   0 },   f_moveboth, { .m = m_prevword } },
-{ .keyv.i = KEY_SRIGHT,     { 0,     0,    0,   0 },   f_moveboth, { .m = m_nextword } },
+{ .keyv.i = KEY_BACKSPACE,  { t_rw,  t_ins,0,   0 },   f_delete,    { .m = m_prevchar } },
+{ .keyv.i = KEY_BACKSPACE,  { 0,     0,    0,   0 },   f_move,      { .m = m_prevchar } },
+{ .keyv.i = KEY_DC,         { t_sel, t_rw, 0,   0 },   f_delete,    { .m = m_tosel    } },
+{ .keyv.i = KEY_DC,         { t_rw,  0,    0,   0 },   f_delete,    { .m = m_nextchar } },
+{ .keyv.i = KEY_SDC,        { t_sel, t_rw, 0,   0 },   f_delete,    { .m = m_tosel    } },
+{ .keyv.i = KEY_SDC,        { t_rw,  0,    0,   0 },   f_delete,    { .m = m_nextchar } },
+{ .keyv.i = KEY_IC,         { t_sel, 0,    0,   0 },   f_pipero,    { .v = TOCLIP     } },
+{ .keyv.i = KEY_SIC,        { t_rw,  0,    0,   0 },   f_pipenull,  { .v = FROMCLIP   } },
+{ .keyv.i = KEY_HOME,       { t_ai,  0,    0,   0 },   f_move,      { .m = m_smartbol } },
+{ .keyv.i = KEY_HOME,       { 0,     0,    0,   0 },   f_move,      { .m = m_bol      } },
+{ .keyv.i = KEY_END,        { 0,     0,    0,   0 },   f_move,      { .m = m_eol      } },
+{ .keyv.i = KEY_SHOME,      { 0,     0,    0,   0 },   f_move,      { .m = m_bof      } },
+{ .keyv.i = KEY_SEND,       { 0,     0,    0,   0 },   f_move,      { .m = m_eof      } },
+{ .keyv.i = KEY_PPAGE,      { 0,     0,    0,   0 },   f_move,      { .m = m_prevscr  } },
+{ .keyv.i = KEY_NPAGE,      { 0,     0,    0,   0 },   f_move,      { .m = m_nextscr  } },
+{ .keyv.i = KEY_UP,         { t_sent,0,    0,   0 },   f_adjective, { .m = m_prevline } },
+{ .keyv.i = KEY_UP,         { 0,     0,    0,   0 },   f_move,      { .m = m_prevline } },
+{ .keyv.i = KEY_DOWN,       { t_sent,0,    0,   0 },   f_move,      { .m = m_nextline } },
+{ .keyv.i = KEY_DOWN,       { t_sent,0,    0,   0 },   f_adjective, { .m = m_nextline } },
+{ .keyv.i = KEY_DOWN,       { 0,     0,    0,   0 },   f_move,      { .m = m_nextline } },
+{ .keyv.i = KEY_LEFT,       { t_sent,0,    0,   0 },   f_adjective, { .m = m_prevchar } },
+{ .keyv.i = KEY_LEFT,       { 0,     0,    0,   0 },   f_move,      { .m = m_prevchar } },
+{ .keyv.i = KEY_RIGHT,      { t_sent,0,    0,   0 },   f_adjective, { .m = m_nextchar } },
+{ .keyv.i = KEY_RIGHT,      { 0,     0,    0,   0 },   f_move,      { .m = m_nextchar } },
+{ .keyv.i = KEY_SLEFT,      { 0,     0,    0,   0 },   f_move,      { .m = m_prevword } },
+{ .keyv.i = KEY_SRIGHT,     { 0,     0,    0,   0 },   f_move,      { .m = m_nextword } },
 };
 
 static const Key stdkeys[] = {
 { .keyv.c = CONTROL('A'), { 0,     0,    0,   0 },  f_move,      { .m = m_bol } },
 { .keyv.c = CONTROL('B'), { 0,     0,    0,   0 },  f_move,      { .m = m_prevchar } },
 { .keyv.c = META('b'),    { 0,     0,    0,   0 },  f_move,      { .m = m_prevword } },
-{ .keyv.c = CONTROL('C'), { t_warn,t_mod,0,   0 },  f_toggle,    { .i = S_Running } },
-{ .keyv.c = CONTROL('C'), { t_mod, 0,    0,   0 },  f_toggle,    { .i = S_Warned } },
-{ .keyv.c = CONTROL('C'), { 0,     0,    0,   0 },  f_toggle,    { .i = S_Running } },
-{ .keyv.c = META('c'),    { t_sel, t_rw, 0,   0 },  f_pipe,      CAPITALIZE },
-{ .keyv.c = CONTROL('D'), { t_sel, t_rw, 0,   0 },  f_pipe,      TOCLIP },
-{ .keyv.c = CONTROL('D'), { t_rw,  0,    0,   0 },  f_delete,    { .m = m_nextchar } },
-{ .keyv.c = META('d'),    { t_rw,  0,    0,   0 },  f_delete,    { .m = m_nextword } },
-{ .keyv.c = CONTROL('E'), { 0,     0,    0,   0 },  f_move,      { .m = m_eol } },
-{ .keyv.c = CONTROL('F'), { 0,     0,    0,   0 },  f_move,      { .m = m_nextchar } },
-{ .keyv.c = META('f'),    { 0,     0,    0,   0 },  f_move,      { .m = m_nextword } },
-{ .keyv.c = CONTROL('G'), { t_sel, 0,    0,   0 },  f_select,    { .m = m_stay } },
-{ .keyv.c = CONTROL('H'), { t_rw,  0,    0,   0 },  f_delete,    { .m = m_prevchar } },
-{ .keyv.c = CONTROL('I'), { t_rw,  0,    0,   0 },  f_insert,    { .v = "\t" } },
-{ .keyv.c = CONTROL('J'), { t_rw,  t_ai, 0,   0 },  f_pipeai,    AUTOINDENT } ,
-{ .keyv.c = CONTROL('J'), { t_rw,  0,    0,   0 },  f_insert,    { .v = "\n" } },
-{ .keyv.c = CONTROL('J'), { 0,     0,    0,   0 },  f_move,      { .m = m_nextline } },
-{ .keyv.c = CONTROL('K'), { t_eol, t_rw, 0,   0 },  f_delete,    { .m = m_nextchar } },
-{ .keyv.c = CONTROL('K'), { t_rw,  0,    0,   0 },  f_delete,    { .m = m_eol } },
-{ .keyv.c = CONTROL('L'), { 0,     0,    0,   0 },  f_center,    { 0 } },
+{ .keyv.c = CONTROL('C'), { t_warn,t_mod,0,   0 },  f_toggle,    { .i = S_Running     } },
+{ .keyv.c = CONTROL('C'), { t_mod, 0,    0,   0 },  f_toggle,    { .i = S_Warned      } },
+{ .keyv.c = CONTROL('C'), { 0,     0,    0,   0 },  f_toggle,    { .i = S_Running     } },
+{ .keyv.c = META('c'),    { t_sel, t_rw, 0,   0 },  f_pipe,      { .v = CAPITALIZE    } },
+{ .keyv.c = CONTROL('D'), { t_sel, t_rw, 0,   0 },  f_pipe,      { .v = TOCLIP        } },
+{ .keyv.c = CONTROL('D'), { t_rw,  0,    0,   0 },  f_delete,    { .m = m_nextchar    } },
+{ .keyv.c = META('d'),    { t_rw,  0,    0,   0 },  f_delete,    { .m = m_nextword    } },
+{ .keyv.c = CONTROL('E'), { 0,     0,    0,   0 },  f_move,      { .m = m_eol         } },
+{ .keyv.c = CONTROL('F'), { 0,     0,    0,   0 },  f_move,      { .m = m_nextchar    } },
+{ .keyv.c = META('f'),    { 0,     0,    0,   0 },  f_move,      { .m = m_nextword    } },
+{ .keyv.c = CONTROL('G'), { t_sel, 0,    0,   0 },  f_select,    { .m = m_stay        } },
+{ .keyv.c = CONTROL('H'), { t_rw,  0,    0,   0 },  f_delete,    { .m = m_prevchar    } },
+{ .keyv.c = CONTROL('I'), { t_rw,  t_ins,0,   0 },  f_insert,    { .v = "\t"          } },
+{ .keyv.c = CONTROL('J'), { t_rw,  t_ai, 0,   0 },  f_pipeai,    { .v = AUTOINDENT    } },
+{ .keyv.c = CONTROL('J'), { t_rw,  t_ins,0,   0 },  f_insert,    { .v = "\n"          } },
+{ .keyv.c = CONTROL('J'), { 0,     0,    0,   0 },  f_move,      { .m = m_nextline    } },
+{ .keyv.c = CONTROL('K'), { t_eol, t_rw, 0,   0 },  f_delete,    { .m = m_nextchar    } },
+{ .keyv.c = CONTROL('K'), { t_rw,  0,    0,   0 },  f_delete,    { .m = m_eol         } },
+{ .keyv.c = CONTROL('L'), { 0,     0,    0,   0 },  f_center,    { 0                  } },
 { .keyv.c = META('l'),    { t_sel, t_rw, 0,   0 },  f_pipe,      { .v = "tr [A-ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ] [a-zàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþ]" } }, /* Lowercase */
-{ .keyv.c = CONTROL('M'), { t_rw,  t_ai, 0,   0 },  f_pipeai,    AUTOINDENT } ,
-{ .keyv.c = CONTROL('M'), { t_rw,  0,    0,   0 },  f_insert,    { .v = "\n" } },
-{ .keyv.c = CONTROL('M'), { 0,     0,    0,   0 },  f_move,      { .m = m_nextline } },
-{ .keyv.c = CONTROL('N'), { 0,     0,    0,   0 },  f_move,      { .m = m_nextline } },
-{ .keyv.c = CONTROL('O'), { t_sel, 0,    0,   0 },  f_select,    { .m = m_tosel } }, /* Swap fsel and fcur */
-{ .keyv.c = CONTROL('P'), { 0,     0,    0,   0 },  f_move,      { .m = m_prevline } },
-{ .keyv.c = CONTROL('Q'), { t_rw,  0,    0,   0 },  f_toggle,    { .i = S_InsEsc } },
-{ .keyv.c = CONTROL('R'), { t_sel, 0,    0,   0 },  f_findbw,    { 0 } },
-{ .keyv.c = CONTROL('R'), { 0,     0,    0,   0 },  f_spawn,     FINDBW },
-{ .keyv.c = META('r'),    { 0,     0,    0,   0 },  f_findbw,    { 0 } },
-{ .keyv.c = CONTROL('S'), { t_sel, 0,    0,   0 },  f_findfw,    { 0 } },
+{ .keyv.c = CONTROL('M'), { t_rw,  t_ai, 0,   0 },  f_pipeai,    { .v = AUTOINDENT    } } ,
+{ .keyv.c = CONTROL('M'), { t_rw,  t_ins,0,   0 },  f_insert,    { .v = "\n"          } },
+{ .keyv.c = CONTROL('M'), { 0,     0,    0,   0 },  f_move,      { .m = m_nextline    } },
+{ .keyv.c = CONTROL('N'), { 0,     0,    0,   0 },  f_move,      { .m = m_nextline    } },
+{ .keyv.c = CONTROL('O'), { t_sel, 0,    0,   0 },  f_select,    { .m = m_tosel       } }, /* Swap fsel and fcur */
+{ .keyv.c = CONTROL('P'), { 0,     0,    0,   0 },  f_move,      { .m = m_prevline    } },
+{ .keyv.c = CONTROL('Q'), { t_rw,  0,    0,   0 },  f_toggle,    { .i = S_InsEsc      } },
+{ .keyv.c = CONTROL('R'), { t_redo,t_rw, 0,   0 },  f_undo,      { .i = -1            } },
+{ .keyv.c = META('r'),    { 0,     0,    0,   0 },  f_findbw,    { 0                  } },
+{ .keyv.c = CONTROL('S'), { t_sel, 0,    0,   0 },  f_findfw,    { 0                  } },
 { .keyv.c = CONTROL('S'), { 0,     0,    0,   0 },  f_spawn,     FIND },
-{ .keyv.c = META('s'),    { 0,     0,    0,   0 },  f_findfw,    { 0 } },
-{ .keyv.c = CONTROL('T'), { 0,     0,    0,   0 },  f_pipero ,   TOCLIP },
-{ .keyv.c = CONTROL('U'), { t_bol, t_rw, 0,   0 },  f_delete,    { .m = m_prevchar } },
-{ .keyv.c = CONTROL('U'), { t_rw,  0,    0,   0 },  f_delete,    { .m = m_bol } },
+{ .keyv.c = META('s'),    { 0,     0,    0,   0 },  f_findfw,    { 0                  } },
+{ .keyv.c = CONTROL('T'), { 0,     0,    0,   0 },  f_pipero ,   { .v = TOCLIP        } },
+{ .keyv.c = CONTROL('U'), { t_bol, t_rw, 0,   0 },  f_delete,    { .m = m_prevchar    } },
+{ .keyv.c = CONTROL('U'), { t_rw,  0,    0,   0 },  f_delete,    { .m = m_bol         } },
 { .keyv.c = META('u'),    { t_sel, t_rw, 0,   0 },  f_pipe,      { .v = "tr [a-zàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþ] [A-ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ] | sed 's/ß/SS/g'" } }, /* Uppercase */
-{ .keyv.c = CONTROL('V'), { 0,     0,    0,   0 },  f_move,      { .m = m_prevscr } },
-{ .keyv.c = META('v'),    { 0,     0,    0,   0 },  f_move,      { .m = m_nextscr } },
-{ .keyv.c = CONTROL('W'), { t_rw,  0,    0,   0 },  f_delete,    { .m = m_prevword } },
+{ .keyv.c = CONTROL('V'), { 0,     0,    0,   0 },  f_move,      { .m = m_prevscr     } },
+{ .keyv.c = META('v'),    { 0,     0,    0,   0 },  f_move,      { .m = m_nextscr     } },
+{ .keyv.c = CONTROL('W'), { t_rw,  0,    0,   0 },  f_delete,    { .m = m_prevword    } },
 { .keyv.c = CONTROL('X'), { t_mod, t_rw, 0,   0 },  f_save,      { 0 } },
 { .keyv.c = CONTROL('X'), { 0,     0,    0,   0 },  f_toggle,    { .i = S_Running } },
 { .keyv.c = META('x'),    { 0,     0,    0,   0 },  f_spawn,     CMD_P },
-{ .keyv.c = CONTROL('Y'), { t_rw,  0,    0,   0 },  f_pipenull,  FROMCLIP },
-{ .keyv.c = CONTROL('Z'), { 0     ,0,    0,   0 },  f_suspend,   { 0 } },
+{ .keyv.c = CONTROL('Y'), { t_rw,  0,    0,   0 },  f_pipenull,  { .v = FROMCLIP } },
+{ .keyv.c = CONTROL('Z'), { 0,     0,    0,   0 },  f_suspend,   { 0                  } },
+{ .keyv.c = CONTROL('['), { t_vis, 0,    0,   0 },  f_toggle,    { .i = S_Visual      } },
+#if VIM_BINDINGS
+{ .keyv.c = CONTROL('['), { t_ins, 0,  0,   0 },  f_toggle,    { .i = S_Command     } },
+#else
 { .keyv.c = CONTROL('['), { 0,     0,    0,   0 },  f_spawn,     CMD_P },
-{ .keyv.c = CONTROL('\\'),{ t_rw,  0,    0,   0 },  f_spawn,     PIPE },
-{ .keyv.c = META('\\'),   { t_rw,  0,    0,   0 },  f_spawn,     SED },
-{ .keyv.c = CONTROL(']'), { 0,     0,    0,   0 },  f_extsel,    { .i = ExtDefault } },
+#endif
+//{ .keyv.c = CONTROL('['), { 0,     0,    0,   0 },  0,           { 0  } },
+{ .keyv.c = CONTROL('\\'),{ t_rw,  0,    0,   0 },  f_spawn,     PIPE                   },
+{ .keyv.c = META('\\'),   { t_rw,  0,    0,   0 },  f_spawn,     SED                    },
+{ .keyv.c = CONTROL(']'), { 0,     0,    0,   0 },  f_extsel,    { .i = ExtDefault    } },
 { .keyv.c = CONTROL('^'), { t_redo,t_rw, 0,   0 },  f_undo,      { .i = -1 } },
 { .keyv.c = CONTROL('^'), { t_rw,  0,    0,   0 },  f_repeat,    { 0 } },
 { .keyv.c = META('6'),    { t_rw,  0,    0,   0 },  f_pipeline,  { .v = "tr '\n' ' '" } }, /* Join lines */
 { .keyv.c = META('5'),    { t_sel, t_rw, 0,   0 },  f_spawn,     REPLACE },
-{ .keyv.c = CONTROL('_'), { t_undo,t_rw, 0,   0 },  f_undo,      { .i = 1 } },
-{ .keyv.c = CONTROL('?'), { t_rw,  0,    0,   0 },  f_delete,    { .m = m_prevchar } },
-{ .keyv.c = META(','),    { 0,     0,    0,   0 },  f_move,      { .m = m_bof } },
-{ .keyv.c = META('.'),    { 0,     0,    0,   0 },  f_move,      { .m = m_eof } },
+{ .keyv.c = CONTROL('?'), { t_rw,  0,    0,   0 },  f_delete,    { .m = m_prevchar    } },
+{ .keyv.c = META(','),    { 0,     0,    0,   0 },  f_move,      { .m = m_bof         } },
+{ .keyv.c = META('.'),    { 0,     0,    0,   0 },  f_move,      { .m = m_eof         } },
 };
 
+#if VIM_BINDINGS
+// TODO: add better paste support (if whole line was yanked, append above, not where you are)
+static const Key commkeys[] = { /* Command mode keys here */
+/* keyv.c,                  tests,                     func,       arg */
+{ .keyv.c = { '$' },      { t_sent,0,    0,   0 },  f_adjective, { .m = m_eol          } },
+{ .keyv.c = { '^' },      { t_sent,0,    0,   0 },  f_adjective, { .m = m_bol          } },
+{ .keyv.c = { 'a' },      { 0,     0,    0,   0 },  f_move,      { .m = m_nextchar     } },
+{ .keyv.c = { 'a' },      { 0,     0,    0,   0 },  f_toggle,    { .i = S_Command      } },
+{ .keyv.c = { 'b' },      { t_sent,0,    0,   0 },  f_adjective, { .m = m_prevword     } },
+{ .keyv.c = { 'b' },      { 0,     0,    0,   0 },  f_move,      { .m = m_prevword     } },
+{ .keyv.c = { 'c' },      { t_rw,  0,    0,   0 },  f_delete,    { .i = 0              } },
+{ .keyv.c = { 'd' },      { t_rw,  0,    0,   0 },  f_delete,    { .i = 0              } },
+{ .keyv.c = { 'g' },      { 0,     0,    0,   0 },  f_move,      { .m = m_bof          } },
+{ .keyv.c = { 'G' },      { 0,     0,    0,   0 },  f_move,      { .m = m_eof          } },
+{ .keyv.c = { 'h' },      { t_sent,0,    0,   0 },  f_adjective, { .m = m_prevchar     } },
+{ .keyv.c = { 'h' },      { 0,     0,    0,   0 },  f_move,      { .m = m_prevchar     } },
+{ .keyv.c = { 'i' },      { 0,     0,    0,   0 },  f_toggle,    { .i = S_Command      } },
+{ .keyv.c = { 'j' },      { t_sent,0,    0,   0 },  f_adjective, { .m = m_nextline     } },
+{ .keyv.c = { 'j' },      { 0,     0,    0,   0 },  f_move,      { .m = m_nextline     } },
+{ .keyv.c = { 'k' },      { t_sent,0,    0,   0 },  f_adjective, { .m = m_prevline     } },
+{ .keyv.c = { 'k' },      { 0,     0,    0,   0 },  f_move,      { .m = m_prevline     } },
+{ .keyv.c = { 'l' },      { t_sent,0,    0,   0 },  f_adjective, { .m = m_nextchar     } },
+{ .keyv.c = { 'l' },      { 0,     0,    0,   0 },  f_move,      { .m = m_nextchar     } },
+{ .keyv.c = { 'm' },      { 0,     0,    0,   0 },  f_mark,      { 0                   } },
+{ .keyv.c = { 'n' },      { t_sel, 0,    0,   0 },  f_findfw,    { 0                   } },
+{ .keyv.c = { 'N' },      { t_sel, 0,    0,   0 },  f_findbw,    { 0                   } },
+{ .keyv.c = { 'o' },      { t_rw,  t_ai, 0,   0 },  f_move,      { .m = m_eol          } },
+{ .keyv.c = { 'o' },      { t_rw,  t_ai, 0,   0 },  f_pipeai,    { .v = AUTOINDENT     } },
+{ .keyv.c = { 'o' },      { t_rw,  t_ai, 0,   0 },  f_toggle,    { .i = S_Command      } },
+{ .keyv.c = { 'o' },      { t_rw,  0,    0,   0 },  f_move,      { .m = m_eol          } },
+{ .keyv.c = { 'o' },      { t_rw,  0,    0,   0 },  f_insert,    { .v = "\n"           } },
+{ .keyv.c = { 'o' },      { t_rw,  0,    0,   0 },  f_toggle,    { .i = S_Command      } },
+{ .keyv.c = { 'O' },      { t_rw,  t_ai, 0,   0 },  f_move,      { .m = m_bol          } },
+{ .keyv.c = { 'O' },      { t_rw,  t_ai, 0,   0 },  f_pipeai,    { .v = AUTOINDENT     } },
+{ .keyv.c = { 'O' },      { t_rw,  t_ai, 0,   0 },  f_move,      { .m = m_prevline     } },
+{ .keyv.c = { 'O' },      { t_rw,  t_ai, 0,   0 },  f_toggle,    { .i = S_Command      } },
+{ .keyv.c = { 'O' },      { t_rw,  0,    0,   0 },  f_move,      { .m = m_bol          } },
+{ .keyv.c = { 'O' },      { t_rw,  0,    0,   0 },  f_insert,    { .v = "\n"           } },
+{ .keyv.c = { 'O' },      { t_rw,  0,    0,   0 },  f_move,      { .m = m_prevline     } },
+{ .keyv.c = { 'O' },      { t_rw,  0,    0,   0 },  f_toggle,    { .i = S_Command      } },
+{ .keyv.c = { 'p' },      { t_rw,  0,    0,   0 },  f_pipenull,  { .v = FROMCLIP       } },
+{ .keyv.c = { 's' },      { t_sel, t_rw, 0,   0 },  f_delete,    { .m = m_tosel        } },
+{ .keyv.c = { 's' },      { t_sel, t_rw, 0,   0 },  f_toggle,    { .i = S_Command      } },
+{ .keyv.c = { 's' },      { t_rw,  0,    0,   0 },  f_delete,    { .m = m_nextchar     } },
+{ .keyv.c = { 's' },      { t_rw,  0,    0,   0 },  f_toggle,    { .i = S_Command      } },
+{ .keyv.c = { 'u' },      { t_undo,t_rw, 0,   0 },  f_undo,      { .i = 1              } },
+{ .keyv.c = { 'v' },      { 0,     0,    0,   0 },  f_toggle,    { .i = S_Visual       } },
+{ .keyv.c = { 'w' },      { t_sent,0,    0,   0 },  f_adjective, { .m = m_nextword     } },
+{ .keyv.c = { 'w' },      { 0,     0,    0,   0 },  f_move,      { .m = m_nextword     } },
+{ .keyv.c = { 'x' },      { t_sel, t_rw, 0,   0 },  f_delete,    { .m = m_tosel        } },
+{ .keyv.c = { 'x' },      { t_rw,  0,    0,   0 },  f_delete,    { .m = m_nextchar     } },
+{ .keyv.c = { 'X' },      { t_sel, t_rw, 0,   0 },  f_delete,    { .m = m_tosel        } },
+{ .keyv.c = { 'X' },      { t_rw,  0,    0,   0 },  f_delete,    { .m = m_prevchar     } },
+{ .keyv.c = { 'y' },      { t_rw,  0,    0,   0 },  f_pipero,    { .i = 0, .v = TOCLIP } },
+{ .keyv.c = { ';' },      { 0,     0,    0,   0 },  f_spawn,     CMD_P                   },
+{ .keyv.c = { ':' },      { 0,     0,    0,   0 },  f_spawn,     CMD_P                   },
+{ .keyv.c = { '\'' },     { 0,     0,    0,   0 },  f_move,      { .m = m_tomark       } },
+{ .keyv.c = { '.' },      { t_rw,  0,    0,   0 },  f_repeat,    { 0                   } },
+{ .keyv.c = { '/' },      { 0,     0,    0,   0 },  f_spawn,     FIND                    },
+{ .keyv.c = { ' ' },      { 0,     0,    0,   0 },  f_move,      { .m = m_nextchar     } },
+/* TODO: Keybindings left:
+ * e/E go to the end of the word (adj) (?)
+ * r replace char (verb)
+ * t/T do until char (adj)
+ * i do inside (adj) (ex. diw deletes current word)
+ * </> ident
+ */
+};
+#endif
+
 #if HANDLE_MOUSE
 /*Mouse clicks */
 static const Click clks[] = {
 /* mouse mask,           fcur / fsel,      tests,               func,       arg */
 {BUTTON1_CLICKED,        { TRUE , TRUE  }, { 0,     0,     0 }, 0,          { 0 } },
-{BUTTON3_CLICKED,        { TRUE , FALSE }, { t_sel, 0,     0 }, f_pipero,   TOSEL },
-{BUTTON2_CLICKED,        { FALSE, FALSE }, { 0,     0,     0 }, f_pipenull, FROMSEL },
+{BUTTON3_CLICKED,        { TRUE , FALSE }, { t_sel, 0,     0 }, f_pipero,   { .v = TOSEL } },
+{BUTTON2_CLICKED,        { FALSE, FALSE }, { 0,     0,     0 }, f_pipenull, { .v = FROMSEL } },
 //{BUTTON4_CLICKED,        { FALSE, FALSE }, { 0,     0,     0 }, f_move,     { .m = m_prevscr } },
 //{BUTTON5_CLICKED,        { FALSE, FALSE }, { 0,     0,     0 }, f_move,     { .m = m_nextscr } },
 /* ^^ NCurses is a sad old library.... it does not include button 5 nor cursor movement in its mouse declaration by default */
 };
 
 /* Helper config functions implementation */
-void /* Move both cursor and selection point, thus cancelling the selection */
-f_moveboth(const Arg *arg) {
-	fsel=fcur=arg->m(fcur);
-}
-
 void /* Pipe selection from bol, then select last line only, special for autoindenting */
 f_pipeai(const Arg *arg) {
 	i_sortpos(&fsel, &fcur);
 	fsel.o=0;
 	f_pipe(arg);
-	fsel.l=fcur.l; fsel.o=0;
+	fsel=fcur;
 }
 
 void /* Pipe full lines including the selection */
 	fsel=fcur;
 	f_pipe(arg);
 }
-
 # sandy version
-VERSION = 0.5
+VERSION = 0.6
 
 # Customize below to fit your system
 
 	S_GroupUndo  = 1<<9,  /* Last action was an insert, so another insert should group with it, set automatically */
 	S_AutoIndent = 1<<10, /* Perform autoindenting on RET */
 	S_DumpStdout = 1<<11, /* Dump to stdout instead of writing to a file */
+	S_Command    = 1<<12, /* Command mode */
+	S_Sentence   = 1<<13, /* Sentence mode. Pass the next command's parameters (if adjective) to the verb's function */
+	S_Parameter  = 1<<14, /* Parameter mode. Pass the next character as parameter to the function of the command */
+	S_Multiply   = 1<<15, /* Multiply mode. Replay a command x times */
+	S_Visual     = 1<<16, /* Visual mode. You just select things */
 };
 
 enum { /* To use in Undo.flags */
 };
 
 /* Variables */
-static Line     *fstline;                   /* First line*/
-static Line     *lstline;                   /* Last line */
-static Line     *scrline;                   /* First line seen on screen */
-static Filepos   fsel;                      /* Selection point on file */
-static Filepos   fcur;                      /* Insert position on file, cursor, current position */
-static Filepos   fmrk = { NULL, 0 };        /* Mark */
-static int       syntx = -1;                /* Current syntax index */
-static regex_t  *find_res[2];               /* Compiled regex for search term */
-static int       sel_re = 0;                /* Index to the above, we keep 2 REs so regexec does not segfault */
-static char     *fifopath = NULL;           /* Path to command fifo */
-static char     *filename = NULL;           /* Path to file loade on buffer */
-static char     *title    = NULL;           /* Screen title */
-static char     *tmptitle = NULL;           /* Screen title, temporary */
-static char     *tsl_str  = NULL;           /* String to print to status line */
-static char     *fsl_str  = NULL;           /* String to come back from status line */
-static WINDOW   *titlewin = NULL;           /* Title ncurses window, NULL if there is a status line */
-static WINDOW   *textwin  = NULL;           /* Main ncurses window */
-static Undo     *undos;                     /* Undo ring */
-static Undo     *redos;                     /* Redo ring */
-static int       textattrs[LastFG][LastBG]; /* Text attributes for each color pair */
-static int       savestep=0;                /* Index to determine the need to save in undo/redo action */
-static int       fifofd;                    /* Command fifo file descriptor */
-static long      statusflags=S_Running;     /* Status flags, very important, OR'd (see enums above) */
-static int       lastaction=LastNone;       /* The last action we took (see enums above) */
-static int       cols, lines;               /* Ncurses: to use instead of COLS and LINES, wise */
-static mmask_t   defmmask = 0;              /* Ncurses: mouse event mask */
+static Line     *fstline;                           /* First line*/
+static Line     *lstline;                           /* Last line */
+static Line     *scrline;                           /* First line seen on screen */
+static Filepos   fsel;                              /* Selection point on file */
+static Filepos   fcur;                              /* Insert position on file, cursor, current position */
+static Filepos   fmrk = { NULL, 0 };                /* Mark */
+static int       syntx = -1;                        /* Current syntax index */
+static regex_t  *find_res[2];                       /* Compiled regex for search term */
+static int       sel_re = 0;                        /* Index to the above, we keep 2 REs so regexec does not segfault */
+static char     *fifopath = NULL;                   /* Path to command fifo */
+static char     *filename = NULL;                   /* Path to file loade on buffer */
+static char     *title    = NULL;                   /* Screen title */
+static char     *tmptitle = NULL;                   /* Screen title, temporary */
+static char     *tsl_str  = NULL;                   /* String to print to status line */
+static char     *fsl_str  = NULL;                   /* String to come back from status line */
+static WINDOW   *titlewin = NULL;                   /* Title ncurses window, NULL if there is a status line */
+static WINDOW   *textwin  = NULL;                   /* Main ncurses window */
+static Undo     *undos;                             /* Undo ring */
+static Undo     *redos;                             /* Redo ring */
+static int       textattrs[LastFG][LastBG];         /* Text attributes for each color pair */
+static int       savestep=0;                        /* Index to determine the need to save in undo/redo action */
+static int       fifofd;                            /* Command fifo file descriptor */
+static long      statusflags=S_Running | S_Command; /* Status flags, very important, OR'd (see enums above) */
+static int       lastaction=LastNone;               /* The last action we took (see enums above) */
+static int       cols, lines;                       /* Ncurses: to use instead of COLS and LINES, wise */
+static mmask_t   defmmask = 0;                      /* Ncurses: mouse event mask */
+static void    (*verb)(const Arg *arg);             /* Verb of current sentence */
+static int       multiply = 1;                      /* Times to replay a command */
 
 /* Functions */
 /* f_* functions can be linked to an action or keybinding */
+static void f_adjective(const Arg*);
 static void f_center(const Arg*);
 static void f_delete(const Arg*);
 static void f_extsel(const Arg*);
 static Line          *i_lineat(unsigned long);
 static unsigned long  i_lineno(Line*);
 static void           i_mouse(void);
+static void           i_multiply(void (*func)(const Arg *arg), const Arg arg);
 static void           i_pipetext(const char*);
 static void           i_readfifo(void);
 static void           i_readfile(char*);
 static bool t_ai(void);
 static bool t_bol(void);
 static bool t_eol(void);
+static bool t_ins(void);
 static bool t_mod(void);
 static bool t_rw(void);
 static bool t_redo(void);
 static bool t_sel(void);
+static bool t_sent(void);
 static bool t_undo(void);
+static bool t_vis(void);
 static bool t_warn(void);
 
 /* m_ functions represent a cursor movement and can be passed in an Arg */
 /* F_* FUNCTIONS
 	Can be linked to an action or keybinding. Always return void and take const Arg* */
 
+#if VIM_BINDINGS
+void
+f_adjective(const Arg *arg) {
+	statusflags&=~S_Sentence;
+	verb(arg);
+}
+#endif /* VIM_BINDINGS */
+
 void /* Make cursor line the one in the middle of the screen if possible, refresh screen */
 f_center(const Arg *arg) {
 	int i=LINES2/2;
 		i_addundo(TRUE, fcur, newcur, strdup((char*)arg->v));
 		if(fcur.l!=newcur.l) fsel=newcur;
 	}
-	fcur=newcur;
+	fcur=fsel=newcur;
 	statusflags|=(S_Modified|S_GroupUndo);
 	lastaction=LastInsert;
 }
 void /* Move cursor and extend/shrink selection as per arg->m */
 f_move(const Arg *arg) {
 	fcur=arg->m(fcur);
+	if(!t_vis()) fsel=fcur;
 }
 
 void /* Got to atoi(arg->v) position in the current line */
 			i_deltext(start, end);
 			fcur=fsel=start;
 		} else
-			fcur=i_addtext(u->str, fcur);
+			fcur=fsel=i_addtext(u->str, fcur);
 		if(isredo)
 			redos=u->prev, u->prev=undos, undos=u;
 		else
 
 void /* Main editing loop */
 i_edit(void) {
-	int ch, i;
+	int ch, i, j;
 	char c[7];
+	bool pass;
 	fd_set fds;
 	Filepos oldsel, oldcur;
 
 #endif /* HANDLE_MOUSE */
 			for(i=0; i<LENGTH(curskeys); i++) {
 				if(ch == curskeys[i].keyv.i && i_dotests(curskeys[i].test) ) {
+
+#if VIM_BINDINGS
+					if(t_sent()) {
+						if(curskeys[i].func == verb) i_multiply(verb, (const Arg){ .m = m_nextline });
+
+						if(curskeys[i].func != f_adjective) {
+							statusflags&=~S_Sentence;
+							break;
+						}
+					}
+#endif /* VIM_BINDINGS */
+
 					if(curskeys[i].func != f_insert) statusflags&=~(S_GroupUndo);
-					curskeys[i].func(&(curskeys[i].arg));
+					i_multiply(curskeys[i].func, curskeys[i].arg);
 					break;
 				}
 			}
 		if(!(statusflags&S_InsEsc) && ISCTRL(c[0])) {
 			for(i=0; i<LENGTH(stdkeys); i++) {
 				if(memcmp(c, stdkeys[i].keyv.c, sizeof stdkeys[i].keyv.c) == 0 && i_dotests(stdkeys[i].test) ) {
+
+#if VIM_BINDINGS
+					if(t_sent()) {
+						if(stdkeys[i].func == verb) i_multiply(verb, (const Arg){ .m = m_nextline });
+
+						if(stdkeys[i].func != f_adjective) {
+							statusflags&=~S_Sentence;
+							break;
+						}
+					}
+#endif /* VIM_BINDINGS */
+
 					if(stdkeys[i].func != f_insert) statusflags&=~(S_GroupUndo);
-					stdkeys[i].func(&(stdkeys[i].arg));
+					i_multiply(stdkeys[i].func, stdkeys[i].arg);
 					break;
 				}
 			}
 			continue;
 		}
 		statusflags&=~(S_InsEsc);
+
+#if VIM_BINDINGS
+		if(t_rw() && t_ins()) f_insert(&(const Arg){ .v = c });
+		else if(!t_ins()) {
+			if(ch >= '0' && ch <= '9' && !(statusflags & S_Parameter)) {
+				if(statusflags & S_Multiply) {
+					multiply*=10;
+					multiply+=(int)ch-'0';
+				} else {
+					statusflags|=S_Multiply;
+					multiply=(int)ch-'0';
+				}
+			} else for(i=0; i<LENGTH(commkeys); i++) {
+				if(memcmp(c, commkeys[i].keyv.c, sizeof commkeys[i].keyv.c) == 0 && i_dotests(commkeys[i].test) ) {
+					if(commkeys[i].func != f_insert) statusflags&=~(S_GroupUndo);
+
+					/* Handle sentences */
+					// FIXME: Find a better way to tell if a func is a verb or parameter
+					if(t_sent()) {
+						if(commkeys[i].func == verb) i_multiply(verb, (const Arg){ .m = m_nextline });
+
+						if(commkeys[i].func != f_adjective) {
+							statusflags&=~S_Sentence;
+							break;
+						}
+					} else if(commkeys[i].arg.i == 0) {
+						statusflags|=(long)S_Sentence;
+						verb=commkeys[i].func;
+						break;
+					}
+
+					/* Handle parameter sentences (verb is used here to define the command to execute) */
+					if(statusflags & S_Parameter) {
+						statusflags&=~S_Parameter;
+						i_multiply(verb, (const Arg){ .v = c });
+						break;
+					} else if(commkeys[i].arg.m == 0) {
+						statusflags|=(long)S_Parameter;
+						verb=commkeys[i].func;
+						break;
+					}
+
+					i_multiply(commkeys[i].func, commkeys[i].arg);
+
+					/* Handle multi-function commands */
+					// TODO: Find a way to handle multi-function verbs
+					if(i+1 < LENGTH(commkeys)) {
+						if(memcmp(commkeys[i+1].keyv.c, commkeys[i].keyv.c, sizeof commkeys[i].keyv.c) == 0) {
+							j=-1;
+							pass=TRUE;
+
+							while(1)
+								if(commkeys[i].test[++j]) {
+									if(commkeys[i].test[j] != commkeys[i+1].test[j]) {
+										pass=FALSE;
+										break;
+									}
+								} else break;
+
+							if(pass) continue;
+							else break;
+						}
+					}
+
+					break;
+				}
+			}
+		}
+#else
 		if(t_rw()) f_insert(&(const Arg){ .v = c });
+#endif /* VIM_BINDINGS */
 		else tmptitle="WARNING! File is read-only!!!";
+
 	}
 }
 
 }
 #endif /* HANDLE_MOUSE */
 
+// FIXME: Some weird thing can be executed (example: 5a or 0a which does nothing)
+void /* Handle multiplication */
+i_multiply(void (*func)(const Arg *arg), const Arg arg) {
+	int i;
+
+	if(statusflags & S_Multiply) {
+		//statusflags|=S_GroupUndo;
+		for(i=0; i<multiply; i++)
+			func(&arg);
+
+		statusflags&=~S_Multiply;
+		multiply=1;
+	} else
+		func(&arg);
+}
+
 void /* Pipe text between fsel and fcur through cmd */
 i_pipetext(const char *cmd) {
 	struct timeval tv;
 	else {
 		statusflags&=~S_Warned; /* Reset warning */
 		snprintf(buf, 4, "%ld%%", (100*ncur)/nlst);
-		snprintf(title, BUFSIZ, "%s [%s]%s%s%s%s %ld,%d  %s",
+		snprintf(title, BUFSIZ, "%s%s [%s]%s%s%s%s %ld,%d  %s",
+			t_vis()?"Visual ":
+#if VIM_BINDINGS
+				(t_ins()?"Insert ":"Command "),
+#else
+				"",
+#endif /* VIM_BINDINGS */
 			(statusflags&S_DumpStdout?"<Stdout>":(filename == NULL?"<No file>":filename)),
 			(syntx>=0 ? syntaxes[syntx].name : "none"),
 			(t_mod()?"[+]":""),
 
 /* T_* FUNCTIONS
 	Used to test for conditions, take no arguments and return bool. */
+
 bool /* TRUE is autoindent is on */
 t_ai(void) {
 	return (statusflags & S_AutoIndent);
 	return (statusflags & S_Modified);
 }
 
+bool /* TRUE if we are not in command mode */
+t_ins(void) {
+#if VIM_BINDINGS
+	return !(statusflags & S_Command);
+#else
+	return TRUE;
+#endif /* VIM_BINDINGS */
+}
+
 bool /* TRUE if the file is writable */
 t_rw(void) {
 	return !(statusflags & S_Readonly);
 	return !(fcur.l==fsel.l && fcur.o == fsel.o);
 }
 
+bool /* TRUE if a sentence has started */
+t_sent(void) {
+#if VIM_BINDINGS
+	return (statusflags & S_Sentence);
+#else
+	return FALSE;
+#endif /* VIM_BINDINGS */
+}
+
 bool /* TRUE if there is anything to undo */
 t_undo(void) {
 	return (undos != NULL);
 }
 
+bool /* TRUE if we are in visual mode */
+t_vis(void) {
+	return (statusflags & S_Visual);
+}
+
 bool /* TRUE if we have warned the file is modified */
 t_warn(void) {
 	return (statusflags & S_Warned);
 
 	/* Use system locale, hopefully UTF-8 */
 	setlocale(LC_ALL,"");
+	ESCDELAY=0; // FIXME: Change this to something else to support num keys?
 
 	for(i = 1; i < argc && argv[i][0] == '-' && argv[i][1] != '\0'; i++) {
 		if(!strcmp(argv[i], "-r")) {
 			i++;
 			break;
 		} else if(!strcmp(argv[i], "-v"))
-			i_die("sandy-"VERSION", © 2011 sandy engineers, see LICENSE for details\n");
+			i_die("sandy-"VERSION", © 2014 sandy engineers, see LICENSE for details\n");
 		else
 			i_usage();
 	}