1 /*
2  * Copyright 2018 Jiri Techet <techet@gmail.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18 
19 #include "cmd-runner.h"
20 #include "utils.h"
21 
22 #include "cmds/motion.h"
23 #include "cmds/motion-word.h"
24 #include "cmds/txtobjs.h"
25 #include "cmds/changemode.h"
26 #include "cmds/edit.h"
27 #include "cmds/special.h"
28 
29 #include <gdk/gdkkeysyms.h>
30 
31 typedef struct {
32 	Cmd cmd;
33 	guint key1;
34 	guint key2;
35 	guint modif1;
36 	guint modif2;
37 	gboolean param;
38 	gboolean needs_selection;
39 } CmdDef;
40 
41 
42 #define ARROW_MOTIONS \
43 	/* left */ \
44 	{cmd_goto_left, GDK_KEY_Left, 0, 0, 0, FALSE, FALSE}, \
45 	{cmd_goto_left, GDK_KEY_KP_Left, 0, 0, 0, FALSE, FALSE}, \
46 	{cmd_goto_left, GDK_KEY_leftarrow, 0, 0, 0, FALSE, FALSE}, \
47 	/* right */ \
48 	{cmd_goto_right, GDK_KEY_Right, 0, 0, 0, FALSE, FALSE}, \
49 	{cmd_goto_right, GDK_KEY_KP_Right, 0, 0, 0, FALSE, FALSE}, \
50 	{cmd_goto_right, GDK_KEY_rightarrow, 0, 0, 0, FALSE, FALSE}, \
51 	/* up */ \
52 	{cmd_goto_up, GDK_KEY_Up, 0, 0, 0, FALSE, FALSE}, \
53 	{cmd_goto_up, GDK_KEY_KP_Up, 0, 0, 0, FALSE, FALSE}, \
54 	{cmd_goto_up, GDK_KEY_uparrow, 0, 0, 0, FALSE, FALSE}, \
55 	/* down */ \
56 	{cmd_goto_down, GDK_KEY_Down, 0, 0, 0, FALSE, FALSE}, \
57 	{cmd_goto_down, GDK_KEY_KP_Down, 0, 0, 0, FALSE, FALSE}, \
58 	{cmd_goto_down, GDK_KEY_downarrow, 0, 0, 0, FALSE, FALSE}, \
59 	/* goto next word */ \
60 	{cmd_goto_next_word, GDK_KEY_Right, 0, GDK_SHIFT_MASK, 0, FALSE, FALSE}, \
61 	{cmd_goto_next_word, GDK_KEY_Right, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \
62 	{cmd_goto_next_word, GDK_KEY_KP_Right, 0, GDK_SHIFT_MASK, 0, FALSE, FALSE}, \
63 	{cmd_goto_next_word, GDK_KEY_KP_Right, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \
64 	{cmd_goto_next_word, GDK_KEY_rightarrow, 0, GDK_SHIFT_MASK, 0, FALSE, FALSE}, \
65 	{cmd_goto_next_word, GDK_KEY_rightarrow, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \
66 	/* goto prev word */ \
67 	{cmd_goto_previous_word, GDK_KEY_Left, 0, GDK_SHIFT_MASK, 0, FALSE, FALSE}, \
68 	{cmd_goto_previous_word, GDK_KEY_Left, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \
69 	{cmd_goto_previous_word, GDK_KEY_KP_Left, 0, GDK_SHIFT_MASK, 0, FALSE, FALSE}, \
70 	{cmd_goto_previous_word, GDK_KEY_KP_Left, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \
71 	{cmd_goto_previous_word, GDK_KEY_leftarrow, 0, GDK_SHIFT_MASK, 0, FALSE, FALSE}, \
72 	{cmd_goto_previous_word, GDK_KEY_leftarrow, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \
73 	/* page up/down */ \
74 	{cmd_goto_page_up, GDK_KEY_Up, 0, GDK_SHIFT_MASK, 0, FALSE, FALSE}, \
75 	{cmd_goto_page_up, GDK_KEY_KP_Up, 0, GDK_SHIFT_MASK, 0, FALSE, FALSE}, \
76 	{cmd_goto_page_up, GDK_KEY_uparrow, 0, GDK_SHIFT_MASK, 0, FALSE, FALSE}, \
77 	{cmd_goto_page_down, GDK_KEY_Down, 0, GDK_SHIFT_MASK, 0, FALSE, FALSE}, \
78 	{cmd_goto_page_down, GDK_KEY_KP_Down, 0, GDK_SHIFT_MASK, 0, FALSE, FALSE}, \
79 	{cmd_goto_page_down, GDK_KEY_downarrow, 0, GDK_SHIFT_MASK, 0, FALSE, FALSE}, \
80 	/* END */
81 
82 
83 #define MOVEMENT_CMDS \
84 	ARROW_MOTIONS \
85 	/* left */ \
86 	{cmd_goto_left, GDK_KEY_h, 0, 0, 0, FALSE, FALSE}, \
87 	{cmd_goto_left, GDK_KEY_h, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \
88 	{cmd_goto_left, GDK_KEY_BackSpace, 0, 0, 0, FALSE, FALSE}, \
89 	/* right */ \
90 	{cmd_goto_right, GDK_KEY_l, 0, 0, 0, FALSE, FALSE}, \
91 	{cmd_goto_right, GDK_KEY_space, 0, 0, 0, FALSE, FALSE}, \
92 	/* up */ \
93 	{cmd_goto_up, GDK_KEY_k, 0, 0, 0, FALSE, FALSE}, \
94 	{cmd_goto_up, GDK_KEY_p, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \
95 	{cmd_goto_up_nonempty, GDK_KEY_minus, 0, 0, 0, FALSE, FALSE}, \
96 	{cmd_goto_up_nonempty, GDK_KEY_KP_Subtract, 0, 0, 0, FALSE, FALSE}, \
97 	/* down */ \
98 	{cmd_goto_down, GDK_KEY_j, 0, 0, 0, FALSE, FALSE}, \
99 	{cmd_goto_down, GDK_KEY_j, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \
100 	{cmd_goto_down, GDK_KEY_n, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \
101 	{cmd_goto_down_nonempty, GDK_KEY_plus, 0, 0, 0, FALSE, FALSE}, \
102 	{cmd_goto_down_nonempty, GDK_KEY_KP_Add, 0, 0, 0, FALSE, FALSE}, \
103 	{cmd_goto_down_nonempty, GDK_KEY_m, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \
104 	{cmd_goto_down_nonempty, GDK_KEY_Return, 0, 0, 0, FALSE, FALSE}, \
105 	{cmd_goto_down_nonempty, GDK_KEY_KP_Enter, 0, 0, 0, FALSE, FALSE}, \
106 	{cmd_goto_down_nonempty, GDK_KEY_ISO_Enter, 0, 0, 0, FALSE, FALSE}, \
107 	{cmd_goto_down_one_less_nonempty, GDK_KEY_underscore, 0, 0, 0, FALSE, FALSE}, \
108 	/* line beginning */ \
109 	{cmd_goto_line_start, GDK_KEY_0, 0, 0, 0, FALSE, FALSE}, \
110 	{cmd_goto_line_start, GDK_KEY_Home, 0, 0, 0, FALSE, FALSE}, \
111 	{cmd_goto_line_start_nonempty, GDK_KEY_asciicircum, 0, 0, 0, FALSE, FALSE}, \
112 	/* line end */ \
113 	{cmd_goto_line_end, GDK_KEY_dollar, 0, 0, 0, FALSE, FALSE}, \
114 	{cmd_goto_line_end, GDK_KEY_End, 0, 0, 0, FALSE, FALSE}, \
115 	{cmd_goto_column, GDK_KEY_bar, 0, 0, 0, FALSE, FALSE}, \
116 	/* find character */ \
117 	{cmd_goto_next_char, GDK_KEY_f, 0, 0, 0, TRUE, FALSE}, \
118 	{cmd_goto_prev_char, GDK_KEY_F, 0, 0, 0, TRUE, FALSE}, \
119 	{cmd_goto_next_char_before, GDK_KEY_t, 0, 0, 0, TRUE, FALSE}, \
120 	{cmd_goto_prev_char_before, GDK_KEY_T, 0, 0, 0, TRUE, FALSE}, \
121 	{cmd_goto_char_repeat, GDK_KEY_semicolon, 0, 0, 0, FALSE, FALSE}, \
122 	{cmd_goto_char_repeat_opposite, GDK_KEY_comma, 0, 0, 0, FALSE, FALSE}, \
123 	/* goto line */ \
124 	{cmd_goto_line_last, GDK_KEY_G, 0, 0, 0, FALSE, FALSE}, \
125 	{cmd_goto_line_last, GDK_KEY_End, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \
126 	{cmd_goto_line_last, GDK_KEY_KP_End, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \
127 	{cmd_goto_line, GDK_KEY_g, GDK_KEY_g, 0, 0, FALSE, FALSE}, \
128 	{cmd_goto_line, GDK_KEY_Home, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \
129 	{cmd_goto_line, GDK_KEY_KP_Home, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \
130 	{cmd_goto_doc_percentage, GDK_KEY_percent, 0, 0, 0, FALSE, FALSE}, \
131 	/* goto next word */ \
132 	{cmd_goto_next_word, GDK_KEY_w, 0, 0, 0, FALSE, FALSE}, \
133 	{cmd_goto_next_word_space, GDK_KEY_W, 0, 0, 0, FALSE, FALSE}, \
134 	{cmd_goto_next_word_end, GDK_KEY_e, 0, 0, 0, FALSE, FALSE}, \
135 	{cmd_goto_next_word_end_space, GDK_KEY_E, 0, 0, 0, FALSE, FALSE}, \
136 	/* goto prev word */ \
137 	{cmd_goto_previous_word, GDK_KEY_b, 0, 0, 0, FALSE, FALSE}, \
138 	{cmd_goto_previous_word_space, GDK_KEY_B, 0, 0, 0, FALSE, FALSE}, \
139 	{cmd_goto_previous_word_end, GDK_KEY_g, GDK_KEY_e, 0, 0, FALSE, FALSE}, \
140 	{cmd_goto_previous_word_end_space, GDK_KEY_g, GDK_KEY_E, 0, 0, FALSE, FALSE}, \
141 	/* various motions */ \
142 	{cmd_goto_matching_brace, GDK_KEY_percent, 0, 0, 0, FALSE, FALSE}, \
143 	{cmd_goto_screen_top, GDK_KEY_H, 0, 0, 0, FALSE, FALSE}, \
144 	{cmd_goto_screen_middle, GDK_KEY_M, 0, 0, 0, FALSE, FALSE}, \
145 	{cmd_goto_screen_bottom, GDK_KEY_L, 0, 0, 0, FALSE, FALSE}, \
146 	/* page up/down */ \
147 	{cmd_goto_page_down, GDK_KEY_f, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \
148 	{cmd_goto_page_down, GDK_KEY_Page_Down, 0, 0, 0, FALSE, FALSE}, \
149 	{cmd_goto_page_down, GDK_KEY_KP_Page_Down, 0, 0, 0, FALSE, FALSE}, \
150 	{cmd_goto_page_up, GDK_KEY_b, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \
151 	{cmd_goto_page_up, GDK_KEY_Page_Up, 0, 0, 0, FALSE, FALSE}, \
152 	{cmd_goto_page_up, GDK_KEY_KP_Page_Up, 0, 0, 0, FALSE, FALSE}, \
153 	{cmd_goto_halfpage_down, GDK_KEY_d, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \
154 	{cmd_goto_halfpage_up, GDK_KEY_u, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \
155 	/* scrolling */ \
156 	{cmd_scroll_down, GDK_KEY_e, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \
157 	{cmd_scroll_up, GDK_KEY_y, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \
158 	{cmd_scroll_center, GDK_KEY_z, GDK_KEY_z, 0, 0, FALSE, FALSE}, \
159 	{cmd_scroll_top, GDK_KEY_z, GDK_KEY_t, 0, 0, FALSE, FALSE}, \
160 	{cmd_scroll_bottom, GDK_KEY_z, GDK_KEY_b, 0, 0, FALSE, FALSE}, \
161 	{cmd_scroll_center_nonempty, GDK_KEY_z, GDK_KEY_period, 0, 0, FALSE, FALSE}, \
162 	{cmd_scroll_top_nonempty, GDK_KEY_z, GDK_KEY_Return, 0, 0, FALSE, FALSE}, \
163 	{cmd_scroll_top_nonempty, GDK_KEY_z, GDK_KEY_KP_Enter, 0, 0, FALSE, FALSE}, \
164 	{cmd_scroll_top_nonempty, GDK_KEY_z, GDK_KEY_ISO_Enter, 0, 0, FALSE, FALSE}, \
165 	{cmd_scroll_top_next_nonempty, GDK_KEY_z, GDK_KEY_plus, 0, 0, FALSE, FALSE}, \
166 	{cmd_scroll_bottom_nonempty, GDK_KEY_z, GDK_KEY_minus, 0, 0, FALSE, FALSE}, \
167 	/* END */
168 
169 
170 /* From the above commands, these commands also include the character
171  * where the destionation movement ends for motion commands (e.g. 'de' will
172  * delete the word including the last character) */
173 CmdDef include_dest_char_movement_cmds[] = {
174 	{cmd_goto_next_char, GDK_KEY_f, 0, 0, 0, TRUE, FALSE},
175 	{cmd_goto_next_char_before, GDK_KEY_t, 0, 0, 0, TRUE, FALSE},
176 	{cmd_goto_next_word_end, GDK_KEY_e, 0, 0, 0, FALSE, FALSE},
177 	{cmd_goto_next_word_end_space, GDK_KEY_E, 0, 0, 0, FALSE, FALSE},
178 	{cmd_goto_previous_word, GDK_KEY_b, 0, 0, 0, FALSE, FALSE},
179 	{cmd_goto_previous_word_space, GDK_KEY_B, 0, 0, 0, FALSE, FALSE},
180 	{cmd_goto_matching_brace, GDK_KEY_percent, 0, 0, 0, FALSE, FALSE},
181 	{NULL, 0, 0, 0, 0, FALSE, FALSE}
182 };
183 
184 
185 CmdDef movement_cmds[] = {
186 	MOVEMENT_CMDS
187 	{NULL, 0, 0, 0, 0, FALSE, FALSE}
188 };
189 
190 
191 #define OPERATOR_CMDS \
192 	{cmd_enter_command_cut_sel, GDK_KEY_d, 0, 0, 0, FALSE, TRUE}, \
193 	{cmd_enter_command_copy_sel, GDK_KEY_y, 0, 0, 0, FALSE, TRUE}, \
194 	{cmd_enter_insert_cut_sel, GDK_KEY_c, 0, 0, 0, FALSE, TRUE}, \
195 	{cmd_unindent_sel, GDK_KEY_less, 0, 0, 0, FALSE, TRUE}, \
196 	{cmd_indent_sel, GDK_KEY_greater, 0, 0, 0, FALSE, TRUE}, \
197 	{cmd_switch_case, GDK_KEY_g, GDK_KEY_asciitilde, 0, 0, FALSE, TRUE}, \
198 	{cmd_switch_case, GDK_KEY_asciitilde, 0, 0, 0, FALSE, TRUE}, \
199 	{cmd_upper_case, GDK_KEY_g, GDK_KEY_U, 0, 0, FALSE, TRUE}, \
200 	{cmd_lower_case, GDK_KEY_g, GDK_KEY_u, 0, 0, FALSE, TRUE}, \
201 	/* END */
202 
203 
204 CmdDef operator_cmds[] = {
205 	OPERATOR_CMDS
206 	{NULL, 0, 0, 0, 0, FALSE, FALSE}
207 };
208 
209 
210 #define TEXT_OBJECT_CMDS \
211 	/* inclusive */ \
212 	{cmd_select_quotedbl, GDK_KEY_a, GDK_KEY_quotedbl, 0, 0, FALSE, FALSE}, \
213 	{cmd_select_quoteleft, GDK_KEY_a, GDK_KEY_quoteleft, 0, 0, FALSE, FALSE}, \
214 	{cmd_select_apostrophe, GDK_KEY_a, GDK_KEY_apostrophe, 0, 0, FALSE, FALSE}, \
215 	{cmd_select_brace, GDK_KEY_a, GDK_KEY_braceleft, 0, 0, FALSE, FALSE}, \
216 	{cmd_select_brace, GDK_KEY_a, GDK_KEY_braceright, 0, 0, FALSE, FALSE}, \
217 	{cmd_select_brace, GDK_KEY_a, GDK_KEY_B, 0, 0, FALSE, FALSE}, \
218 	{cmd_select_paren, GDK_KEY_a, GDK_KEY_parenleft, 0, 0, FALSE, FALSE}, \
219 	{cmd_select_paren, GDK_KEY_a, GDK_KEY_parenright, 0, 0, FALSE, FALSE}, \
220 	{cmd_select_paren, GDK_KEY_a, GDK_KEY_b, 0, 0, FALSE, FALSE}, \
221 	{cmd_select_less, GDK_KEY_a, GDK_KEY_less, 0, 0, FALSE, FALSE}, \
222 	{cmd_select_less, GDK_KEY_a, GDK_KEY_greater, 0, 0, FALSE, FALSE}, \
223 	{cmd_select_bracket, GDK_KEY_a, GDK_KEY_bracketleft, 0, 0, FALSE, FALSE}, \
224 	{cmd_select_bracket, GDK_KEY_a, GDK_KEY_bracketright, 0, 0, FALSE, FALSE}, \
225 	/* inner */ \
226 	{cmd_select_quotedbl_inner, GDK_KEY_i, GDK_KEY_quotedbl, 0, 0, FALSE, FALSE}, \
227 	{cmd_select_quoteleft_inner, GDK_KEY_i, GDK_KEY_quoteleft, 0, 0, FALSE, FALSE}, \
228 	{cmd_select_apostrophe_inner, GDK_KEY_i, GDK_KEY_apostrophe, 0, 0, FALSE, FALSE}, \
229 	{cmd_select_brace_inner, GDK_KEY_i, GDK_KEY_braceleft, 0, 0, FALSE, FALSE}, \
230 	{cmd_select_brace_inner, GDK_KEY_i, GDK_KEY_braceright, 0, 0, FALSE, FALSE}, \
231 	{cmd_select_brace_inner, GDK_KEY_i, GDK_KEY_B, 0, 0, FALSE, FALSE}, \
232 	{cmd_select_paren_inner, GDK_KEY_i, GDK_KEY_parenleft, 0, 0, FALSE, FALSE}, \
233 	{cmd_select_paren_inner, GDK_KEY_i, GDK_KEY_parenright, 0, 0, FALSE, FALSE}, \
234 	{cmd_select_paren_inner, GDK_KEY_i, GDK_KEY_b, 0, 0, FALSE, FALSE}, \
235 	{cmd_select_less_inner, GDK_KEY_i, GDK_KEY_less, 0, 0, FALSE, FALSE}, \
236 	{cmd_select_less_inner, GDK_KEY_i, GDK_KEY_greater, 0, 0, FALSE, FALSE}, \
237 	{cmd_select_bracket_inner, GDK_KEY_i, GDK_KEY_bracketleft, 0, 0, FALSE, FALSE}, \
238 	{cmd_select_bracket_inner, GDK_KEY_i, GDK_KEY_bracketright, 0, 0, FALSE, FALSE}, \
239 	/* END */
240 
241 
242 CmdDef text_object_cmds[] = {
243 	TEXT_OBJECT_CMDS
244 	{NULL, 0, 0, 0, 0, FALSE, FALSE}
245 };
246 
247 
248 #define EDIT_CMDS \
249 	/* deletion */ \
250 	{cmd_delete_char_copy, GDK_KEY_x, 0, 0, 0, FALSE, FALSE}, \
251 	{cmd_delete_char_copy, GDK_KEY_Delete, 0, 0, 0, FALSE, FALSE}, \
252 	{cmd_delete_char_copy, GDK_KEY_KP_Delete, 0, 0, 0, FALSE, FALSE}, \
253 	{cmd_delete_char_back_copy, GDK_KEY_X, 0, 0, 0, FALSE, FALSE}, \
254 	{cmd_delete_line, GDK_KEY_d, GDK_KEY_d, 0, 0, FALSE, FALSE}, \
255 	{cmd_clear_right, GDK_KEY_D, 0, 0, 0, FALSE, FALSE}, \
256 	/* copy/paste */ \
257 	{cmd_copy_line, GDK_KEY_y, GDK_KEY_y, 0, 0, FALSE, FALSE}, \
258 	{cmd_copy_line, GDK_KEY_Y, 0, 0, 0, FALSE, FALSE}, \
259 	{cmd_paste_after, GDK_KEY_p, 0, 0, 0, FALSE, FALSE}, \
260 	{cmd_paste_before, GDK_KEY_P, 0, 0, 0, FALSE, FALSE}, \
261 	/* changing text */ \
262 	{cmd_enter_insert_cut_line, GDK_KEY_c, GDK_KEY_c, 0, 0, FALSE, FALSE}, \
263 	{cmd_enter_insert_cut_line, GDK_KEY_S, 0, 0, 0, FALSE, FALSE}, \
264 	{cmd_enter_insert_clear_right, GDK_KEY_C, 0, 0, 0, FALSE, FALSE}, \
265 	{cmd_enter_insert_delete_char, GDK_KEY_s, 0, 0, 0, FALSE, FALSE}, \
266 	{cmd_replace_char, GDK_KEY_r, 0, 0, 0, TRUE, FALSE}, \
267 	{cmd_switch_case, GDK_KEY_asciitilde, 0, 0, 0, FALSE, FALSE}, \
268 	{cmd_indent, GDK_KEY_greater, GDK_KEY_greater, 0, 0, FALSE, FALSE}, \
269 	{cmd_unindent, GDK_KEY_less, GDK_KEY_less, 0, 0, FALSE, FALSE}, \
270 	{cmd_repeat_subst, GDK_KEY_ampersand, 0, 0, 0, FALSE, FALSE}, \
271 	{cmd_join_lines, GDK_KEY_J, 0, 0, 0, FALSE, FALSE}, \
272 	/* undo/redo */ \
273 	{cmd_undo, GDK_KEY_U, 0, 0, 0, FALSE, FALSE}, \
274 	{cmd_undo, GDK_KEY_u, 0, 0, 0, FALSE, FALSE}, \
275 	{cmd_redo, GDK_KEY_r, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE},
276 
277 CmdDef edit_cmds[] = {
278 	EDIT_CMDS
279 	OPERATOR_CMDS
280 	{NULL, 0, 0, 0, 0, FALSE, FALSE}
281 };
282 
283 
284 #define ENTER_EX_CMDS \
285 	{cmd_enter_ex, GDK_KEY_colon, 0, 0, 0, FALSE, FALSE}, \
286 	{cmd_enter_ex, GDK_KEY_slash, 0, 0, 0, FALSE, FALSE}, \
287 	{cmd_enter_ex, GDK_KEY_KP_Divide, 0, 0, 0, FALSE, FALSE}, \
288 	{cmd_enter_ex, GDK_KEY_question, 0, 0, 0, FALSE, FALSE}, \
289 	/* END */
290 
291 
292 #define SEARCH_CMDS \
293 	{cmd_search_next, GDK_KEY_n, 0, 0, 0, FALSE, FALSE}, \
294 	{cmd_search_next, GDK_KEY_N, 0, 0, 0, FALSE, FALSE}, \
295 	{cmd_search_current_next, GDK_KEY_asterisk, 0, 0, 0, FALSE, FALSE}, \
296 	{cmd_search_current_next, GDK_KEY_KP_Multiply, 0, 0, 0, FALSE, FALSE}, \
297 	{cmd_search_current_prev, GDK_KEY_numbersign, 0, 0, 0, FALSE, FALSE}, \
298 	/* END */
299 
300 CmdDef cmd_mode_cmds[] = {
301 	/* enter insert mode */
302 	{cmd_enter_insert_after, GDK_KEY_a, 0, 0, 0, FALSE, FALSE},
303 	{cmd_enter_insert_line_end, GDK_KEY_A, 0, 0, 0, FALSE, FALSE},
304 	{cmd_enter_insert, GDK_KEY_i, 0, 0, 0, FALSE, FALSE},
305 	{cmd_enter_insert, GDK_KEY_Insert, 0, 0, 0, FALSE, FALSE},
306 	{cmd_enter_insert, GDK_KEY_KP_Insert, 0, 0, 0, FALSE, FALSE},
307 	{cmd_enter_insert_line_start_nonempty, GDK_KEY_I, 0, 0, 0, FALSE, FALSE},
308 	{cmd_enter_insert_line_start, GDK_KEY_g, GDK_KEY_I, 0, 0, FALSE, FALSE},
309 	{cmd_enter_insert_next_line, GDK_KEY_o, 0, 0, 0, FALSE, FALSE},
310 	{cmd_enter_insert_prev_line, GDK_KEY_O, 0, 0, 0, FALSE, FALSE},
311 	/* enter replace mode */
312 	{cmd_enter_replace, GDK_KEY_R, 0, 0, 0, FALSE, FALSE},
313 	/* enter visual mode */
314 	{cmd_enter_visual, GDK_KEY_v, 0, 0, 0, FALSE, FALSE},
315 	{cmd_enter_visual_line, GDK_KEY_V, 0, 0, 0, FALSE, FALSE},
316 
317 	/* special */
318 	{cmd_repeat_last_command, GDK_KEY_period, 0, 0, 0, FALSE, FALSE},
319 	{cmd_repeat_last_command, GDK_KEY_KP_Decimal, 0, 0, 0, FALSE, FALSE},
320 	{cmd_enter_command, GDK_KEY_Escape, 0, 0, 0, FALSE, FALSE},
321 	{cmd_nop, GDK_KEY_Insert, 0, 0, 0, FALSE, FALSE},
322 	{cmd_nop, GDK_KEY_KP_Insert, 0, 0, 0, FALSE, FALSE},
323 	{cmd_write_exit, GDK_KEY_Z, GDK_KEY_Z, 0, 0, FALSE, FALSE},
324 	{cmd_force_exit, GDK_KEY_Z, GDK_KEY_Q, 0, 0, FALSE, FALSE},
325 
326 	EDIT_CMDS
327 	OPERATOR_CMDS
328 	SEARCH_CMDS
329 	MOVEMENT_CMDS
330 	TEXT_OBJECT_CMDS
331 	ENTER_EX_CMDS
332 
333 	{NULL, 0, 0, 0, 0, FALSE, FALSE}
334 };
335 
336 
337 CmdDef vis_mode_cmds[] = {
338 	{cmd_enter_command, GDK_KEY_Escape, 0, 0, 0, FALSE, FALSE},
339 	{cmd_enter_command, GDK_KEY_c, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE},
340 	{cmd_enter_visual, GDK_KEY_v, 0, 0, 0, FALSE, FALSE},
341 	{cmd_enter_visual_line, GDK_KEY_V, 0, 0, 0, FALSE, FALSE},
342 
343 	{cmd_enter_insert_cut_line_sel, GDK_KEY_C, 0, 0, 0, FALSE, FALSE},
344 	{cmd_enter_insert_cut_line_sel, GDK_KEY_S, 0, 0, 0, FALSE, FALSE},
345 	{cmd_enter_insert_cut_line_sel, GDK_KEY_R, 0, 0, 0, FALSE, FALSE},
346 	{cmd_enter_command_cut_line_sel, GDK_KEY_D, 0, 0, 0, FALSE, FALSE},
347 	{cmd_enter_command_cut_line_sel, GDK_KEY_X, 0, 0, 0, FALSE, FALSE},
348 	{cmd_enter_command_cut_sel, GDK_KEY_x, 0, 0, 0, FALSE, FALSE},
349 	{cmd_enter_command_cut_sel, GDK_KEY_Delete, 0, 0, 0, FALSE, FALSE},
350 	{cmd_enter_command_cut_sel, GDK_KEY_KP_Delete, 0, 0, 0, FALSE, FALSE},
351 	{cmd_enter_insert_cut_sel, GDK_KEY_s, 0, 0, 0, FALSE, FALSE},
352 	{cmd_enter_command_copy_line_sel, GDK_KEY_Y, 0, 0, 0, FALSE, FALSE},
353 
354 	{cmd_upper_case, GDK_KEY_U, 0, 0, 0, FALSE, FALSE},
355 	{cmd_lower_case, GDK_KEY_u, 0, 0, 0, FALSE, FALSE},
356 	{cmd_join_lines_sel, GDK_KEY_J, 0, 0, 0, FALSE, FALSE},
357 	{cmd_replace_char_sel, GDK_KEY_r, 0, 0, 0, TRUE, FALSE},
358 
359 	{cmd_swap_anchor, GDK_KEY_o, 0, 0, 0, FALSE, FALSE},
360 	{cmd_swap_anchor, GDK_KEY_O, 0, 0, 0, FALSE, FALSE},
361 	{cmd_nop, GDK_KEY_Insert, 0, 0, 0, FALSE, FALSE},
362 	{cmd_nop, GDK_KEY_KP_Insert, 0, 0, 0, FALSE, FALSE},
363 
364 	SEARCH_CMDS
365 	MOVEMENT_CMDS
366 	TEXT_OBJECT_CMDS
367 	OPERATOR_CMDS
368 	ENTER_EX_CMDS
369 
370 	{NULL, 0, 0, 0, 0, FALSE, FALSE}
371 };
372 
373 
374 CmdDef ins_mode_cmds[] = {
375 	{cmd_enter_command, GDK_KEY_Escape, 0, 0, 0, FALSE, FALSE},
376 	{cmd_enter_command, GDK_KEY_c, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE},
377 	{cmd_enter_command, GDK_KEY_bracketleft, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE},
378 	{cmd_enter_command_single, GDK_KEY_o, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE},
379 
380 	{cmd_goto_line_last, GDK_KEY_End, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE},
381 	{cmd_goto_line_last, GDK_KEY_KP_End, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE},
382 	{cmd_goto_line, GDK_KEY_Home, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE},
383 	{cmd_goto_line, GDK_KEY_KP_Home, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE},
384 
385 	{cmd_newline, GDK_KEY_m, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE},
386 	{cmd_newline, GDK_KEY_j, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE},
387 	{cmd_tab, GDK_KEY_i, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE},
388 
389 	{cmd_paste_inserted_text, GDK_KEY_a, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE},
390 	{cmd_paste_inserted_text_leave_ins, GDK_KEY_at, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE},
391 	/* it's enough to press Ctrl+2 instead of Ctrl+Shift+2 to get Ctrl+@ */
392 	{cmd_paste_inserted_text_leave_ins, GDK_KEY_2, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE},
393 
394 	{cmd_delete_char, GDK_KEY_Delete, 0, 0, 0, FALSE, FALSE},
395 	{cmd_delete_char, GDK_KEY_KP_Delete, 0, 0, 0, FALSE, FALSE},
396 	{cmd_delete_char_back, GDK_KEY_h, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE},
397 	{cmd_del_word_left, GDK_KEY_w, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE},
398 	{cmd_indent_ins, GDK_KEY_t, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE},
399 	{cmd_unindent_ins, GDK_KEY_d, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE},
400 	{cmd_copy_char_from_below, GDK_KEY_e, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE},
401 	{cmd_copy_char_from_above, GDK_KEY_y, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE},
402 	{cmd_paste_before, GDK_KEY_r, 0, GDK_CONTROL_MASK, 0, TRUE, FALSE},
403 
404 	ARROW_MOTIONS
405 
406 	{NULL, 0, 0, 0, 0, FALSE, FALSE}
407 };
408 
409 
is_in_cmd_group(CmdDef * cmds,CmdDef * def)410 static gboolean is_in_cmd_group(CmdDef *cmds, CmdDef *def)
411 {
412 	int i;
413 	for (i = 0; cmds[i].cmd != NULL; i++)
414 	{
415 		CmdDef *d = &cmds[i];
416 		if (def->cmd == d->cmd && def->key1 == d->key1 && def->key2 == d->key2 &&
417 			def->modif1 == d->modif1 && def->modif2 == d->modif2 && def->param == d->param)
418 			return TRUE;
419 	}
420 	return FALSE;
421 }
422 
423 
key_equals(KeyPress * kp,guint key,guint modif)424 static gboolean key_equals(KeyPress *kp, guint key, guint modif)
425 {
426 	return kp->key == key && (kp->modif & modif || kp->modif == modif);
427 }
428 
429 
430 /* is the current keypress the first character of a 2-keypress command? */
is_cmdpart(GSList * kpl,CmdDef * cmds)431 static gboolean is_cmdpart(GSList *kpl, CmdDef *cmds)
432 {
433 	gint i;
434 	KeyPress *curr = g_slist_nth_data(kpl, 0);
435 
436 	for (i = 0; cmds[i].cmd != NULL; i++)
437 	{
438 		CmdDef *cmd = &cmds[i];
439 		if ((cmd->key2 != 0 || cmd->param) && key_equals(curr, cmd->key1, cmd->modif1))
440 			return TRUE;
441 	}
442 
443 	return FALSE;
444 }
445 
446 
is_printable(GSList * kpl)447 static gboolean is_printable(GSList *kpl)
448 {
449 	guint mask = gtk_accelerator_get_default_mod_mask() & ~GDK_SHIFT_MASK;
450 	KeyPress *kp = g_slist_nth_data(kpl, 0);
451 
452 	if (kp->modif & mask)
453 		return FALSE;
454 
455 	return g_unichar_isprint(gdk_keyval_to_unicode(kp->key));
456 }
457 
458 
get_cmd_to_run(GSList * kpl,CmdDef * cmds,gboolean have_selection)459 static CmdDef *get_cmd_to_run(GSList *kpl, CmdDef *cmds, gboolean have_selection)
460 {
461 	gint i;
462 	KeyPress *curr = g_slist_nth_data(kpl, 0);
463 	KeyPress *prev = g_slist_nth_data(kpl, 1);
464 	GSList *below = g_slist_next(kpl);
465 	ViMode mode = vi_get_mode();
466 
467 	if (!kpl)
468 		return NULL;
469 
470 	// commands such as rc or fc (replace char c, find char c) which are specified
471 	// by the previous character and current character is used as their parameter
472 	if (prev != NULL && !kp_isdigit(prev))
473 	{
474 		for (i = 0; cmds[i].cmd != NULL; i++)
475 		{
476 			CmdDef *cmd = &cmds[i];
477 			if (cmd->key2 == 0 && cmd->param &&
478 					((cmd->needs_selection && have_selection) || !cmd->needs_selection) &&
479 					key_equals(prev, cmd->key1, cmd->modif1))
480 				return cmd;
481 		}
482 	}
483 
484 	// 2-letter commands
485 	if (prev != NULL && !kp_isdigit(prev))
486 	{
487 		for (i = 0; cmds[i].cmd != NULL; i++)
488 		{
489 			CmdDef *cmd = &cmds[i];
490 			if (cmd->key2 != 0 && !cmd->param &&
491 					((cmd->needs_selection && have_selection) || !cmd->needs_selection) &&
492 					key_equals(curr, cmd->key2, cmd->modif2) &&
493 					key_equals(prev, cmd->key1, cmd->modif1))
494 				return cmd;
495 		}
496 	}
497 
498 	// 1-letter commands
499 	for (i = 0; cmds[i].cmd != NULL; i++)
500 	{
501 		CmdDef *cmd = &cmds[i];
502 		if (cmd->key2 == 0 && !cmd->param &&
503 			((cmd->needs_selection && have_selection) || !cmd->needs_selection) &&
504 			key_equals(curr, cmd->key1, cmd->modif1))
505 		{
506 			// now solve some quirks manually
507 			if (curr->key == GDK_KEY_0 && !VI_IS_INSERT(mode))
508 			{
509 				// 0 jumps to the beginning of line only when not preceded
510 				// by another number in which case we want to add it to the accumulator
511 				if (prev == NULL || !kp_isdigit(prev))
512 					return cmd;
513 			}
514 			else if (curr->key == GDK_KEY_percent && !VI_IS_INSERT(mode))
515 			{
516 				// % when preceded by a number jumps to N% of the file, otherwise
517 				// % goes to matching brace
518 				Cmd c = cmd_goto_matching_brace;
519 				gint val = kpl_get_int(below, NULL);
520 				if (val != -1)
521 					c = cmd_goto_doc_percentage;
522 				if (cmd->cmd == c)
523 					return cmd;
524 			}
525 			else if (prev && prev->key == GDK_KEY_g && !VI_IS_INSERT(mode))
526 			{
527 				// takes care of operator commands like g~, gu, gU where we
528 				// have no selection yet so the 2-letter command isn't found
529 				// above and a corresponding 1-letter command ~, u, U exists and
530 				// would be used instead of waiting for the full command
531 			}
532 			else if (is_cmdpart(kpl, text_object_cmds) &&
533 					get_cmd_to_run(below, operator_cmds, TRUE) && !VI_IS_INSERT(mode))
534 			{
535 				// if we received "a" or "i", we have to check if there's not
536 				// an operator command below because these can be part of
537 				// text object commands (like a<) and in this case we don't
538 				// want to have "a" or "i" executed yet
539 			}
540 			else
541 				return cmd;
542 		}
543 	}
544 
545 	return NULL;
546 }
547 
548 
perform_cmd(CmdDef * def,CmdContext * ctx)549 static void perform_cmd(CmdDef *def, CmdContext *ctx)
550 {
551 	GSList *top;
552 	gint num;
553 	gint cmd_len = 0;
554 	gboolean num_present;
555 	CmdParams param;
556 	gint orig_pos = SSM(ctx->sci, SCI_GETCURRENTPOS, 0, 0);
557 	gint sel_start, sel_len;
558 
559 	if (def->key1 != 0)
560 		cmd_len++;
561 	if (def->key2 != 0)
562 		cmd_len++;
563 	if (def->param)
564 		cmd_len++;
565 	top = g_slist_nth(ctx->kpl, cmd_len);
566 	num = kpl_get_int(top, &top);
567 	num_present = num != -1;
568 
569 	sel_start = SSM(ctx->sci, SCI_GETSELECTIONSTART, 0, 0);
570 	sel_len = SSM(ctx->sci, SCI_GETSELECTIONEND, 0, 0) - sel_start;
571 	cmd_params_init(&param, ctx->sci,
572 		num_present ? num : 1, num_present, ctx->kpl, FALSE,
573 		sel_start, sel_len);
574 
575 	SSM(ctx->sci, SCI_BEGINUNDOACTION, 0, 0);
576 
577 //	if (def->cmd != cmd_undo && def->cmd != cmd_redo)
578 //		SSM(sci, SCI_ADDUNDOACTION, param.pos, UNDO_MAY_COALESCE);
579 
580 	def->cmd(ctx, &param);
581 
582 	if (VI_IS_COMMAND(vi_get_mode()))
583 	{
584 		gboolean is_text_object_cmd = is_in_cmd_group(text_object_cmds, def);
585 		gboolean is_include_dest_char_movement_cmd = is_in_cmd_group(include_dest_char_movement_cmds, def);
586 		if (is_text_object_cmd || is_in_cmd_group(movement_cmds, def))
587 		{
588 			def = get_cmd_to_run(top, operator_cmds, TRUE);
589 			if (def)
590 			{
591 				gint new_pos = SSM(ctx->sci, SCI_GETCURRENTPOS, 0, 0);
592 
593 				SET_POS(ctx->sci, orig_pos, FALSE);
594 
595 				if (is_text_object_cmd)
596 				{
597 					sel_start = param.sel_start;
598 					sel_len = param.sel_len;
599 				}
600 				else
601 				{
602 					sel_start = MIN(new_pos, orig_pos);
603 					sel_len = ABS(new_pos - orig_pos);
604 					if (sel_len > 0 && is_include_dest_char_movement_cmd)
605 					{
606 						sel_len++;
607 						if (new_pos < orig_pos)
608 							sel_start--;
609 					}
610 				}
611 				cmd_params_init(&param, ctx->sci,
612 					1, FALSE, top, TRUE,
613 					sel_start, sel_len);
614 
615 				def->cmd(ctx, &param);
616 			}
617 		}
618 	}
619 
620 	/* mode could have changed after performing command */
621 	if (VI_IS_COMMAND(vi_get_mode()))
622 		clamp_cursor_pos(ctx->sci);
623 
624 	SSM(ctx->sci, SCI_ENDUNDOACTION, 0, 0);
625 }
626 
627 
perform_repeat_cmd(CmdContext * ctx)628 static gboolean perform_repeat_cmd(CmdContext *ctx)
629 {
630 	GSList *top = g_slist_next(ctx->kpl);  // get behind "."
631 	gint num = kpl_get_int(top, NULL);
632 	CmdDef *def;
633 	gint i;
634 
635 	def = get_cmd_to_run(ctx->repeat_kpl, edit_cmds, FALSE);
636 	num = num == -1 ? 1 : num;
637 	if (def) {
638 		for (i = 0; i < num; i++)
639 			perform_cmd(def, ctx);
640 		return TRUE;
641 	}
642 	else if (ctx->insert_buf_len > 0) {
643 		gint pos;
644 
645 		SSM(ctx->sci, SCI_BEGINUNDOACTION, 0, 0);
646 		for (i = 0; i < num; i++)
647 			SSM(ctx->sci, SCI_ADDTEXT, ctx->insert_buf_len, (sptr_t) ctx->insert_buf);
648 		pos = SSM(ctx->sci, SCI_GETCURRENTPOS, 0, 0);
649 		SET_POS(ctx->sci, PREV(ctx->sci, pos), FALSE);
650 		SSM(ctx->sci, SCI_ENDUNDOACTION, 0, 0);
651 		return TRUE;
652 	}
653 
654 	return FALSE;
655 }
656 
657 
process_cmd(CmdDef * cmds,CmdContext * ctx,gboolean ins_mode)658 static gboolean process_cmd(CmdDef *cmds, CmdContext *ctx, gboolean ins_mode)
659 {
660 	gboolean consumed;
661 	gboolean performed = FALSE;
662 	ViMode orig_mode = vi_get_mode();
663 	gboolean have_selection =
664 		SSM(ctx->sci, SCI_GETSELECTIONEND, 0, 0) - SSM(ctx->sci, SCI_GETSELECTIONSTART, 0, 0) > 0;
665 	CmdDef *def = get_cmd_to_run(ctx->kpl, cmds, have_selection);
666 
667 	consumed = is_cmdpart(ctx->kpl, cmds) || (!ins_mode && is_printable(ctx->kpl));
668 
669 	if (def)
670 	{
671 		if (def->cmd == cmd_repeat_last_command)
672 		{
673 			if (perform_repeat_cmd(ctx))
674 			{
675 				performed = TRUE;
676 
677 				g_slist_free_full(ctx->kpl, g_free);
678 				ctx->kpl = NULL;
679 			}
680 		}
681 		else
682 		{
683 			perform_cmd(def, ctx);
684 			performed = TRUE;
685 
686 			if (is_in_cmd_group(edit_cmds, def))
687 			{
688 				g_slist_free_full(ctx->repeat_kpl, g_free);
689 				ctx->repeat_kpl = ctx->kpl;
690 			}
691 			else
692 				g_slist_free_full(ctx->kpl, g_free);
693 			ctx->kpl = NULL;
694 		}
695 	}
696 
697 	consumed = consumed || performed;
698 
699 	if (performed)
700 	{
701 		if (orig_mode == VI_MODE_COMMAND_SINGLE)
702 			vi_set_mode(VI_MODE_INSERT);
703 	}
704 	else if (!consumed && ctx->kpl)
705 	{
706 		g_free(ctx->kpl->data);
707 		ctx->kpl = g_slist_delete_link(ctx->kpl, ctx->kpl);
708 	}
709 
710 	return consumed;
711 }
712 
713 
cmd_perform_cmd(CmdContext * ctx)714 gboolean cmd_perform_cmd(CmdContext *ctx)
715 {
716 	return process_cmd(cmd_mode_cmds, ctx, FALSE);
717 }
718 
719 
cmd_perform_vis(CmdContext * ctx)720 gboolean cmd_perform_vis(CmdContext *ctx)
721 {
722 	return process_cmd(vis_mode_cmds, ctx, FALSE);
723 }
724 
725 
cmd_perform_ins(CmdContext * ctx)726 gboolean cmd_perform_ins(CmdContext *ctx)
727 {
728 	return process_cmd(ins_mode_cmds, ctx, TRUE);
729 }
730