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 "excmd-runner.h"
20 #include "excmd-params.h"
21 #include "cmds/excmds.h"
22 #include "utils.h"
23 
24 #include <string.h>
25 #include <ctype.h>
26 
27 
28 typedef struct {
29 	ExCmd cmd;
30 	const gchar *name;
31 } ExCmdDef;
32 
33 
34 ExCmdDef ex_cmds[] = {
35 	{excmd_save, "w"},
36 	{excmd_save, "write"},
37 	{excmd_save, "up"},
38 	{excmd_save, "update"},
39 
40 	{excmd_save_all, "wall"},
41 
42 	{excmd_quit, "q"},
43 	{excmd_quit, "quit"},
44 	{excmd_quit, "quita"},
45 	{excmd_quit, "quitall"},
46 	{excmd_quit, "qa"},
47 	{excmd_quit, "qall"},
48 	{excmd_quit, "cq"},
49 	{excmd_quit, "cquit"},
50 
51 	{excmd_save_quit, "wq"},
52 	{excmd_save_quit, "x"},
53 	{excmd_save_quit, "xit"},
54 	{excmd_save_quit, "exi"},
55 	{excmd_save_quit, "exit"},
56 
57 	{excmd_save_all_quit, "xa"},
58 	{excmd_save_all_quit, "xall"},
59 	{excmd_save_all_quit, "wqa"},
60 	{excmd_save_all_quit, "wqall"},
61 	{excmd_save_all_quit, "x"},
62 	{excmd_save_all_quit, "xit"},
63 
64 	{excmd_repeat_subst, "s"},
65 	{excmd_repeat_subst, "substitute"},
66 	{excmd_repeat_subst, "&"},
67 	{excmd_repeat_subst, "~"},
68 	{excmd_repeat_subst_orig_flags, "&&"},
69 
70 	{excmd_yank, "yank"},
71 	{excmd_yank, "y"},
72 	{excmd_put, "put"},
73 	{excmd_put, "pu"},
74 
75 	{excmd_undo, "undo"},
76 	{excmd_undo, "u"},
77 	{excmd_redo, "redo"},
78 	{excmd_redo, "red"},
79 
80 	{excmd_shift_left, "<"},
81 	{excmd_shift_right, ">"},
82 
83 	{excmd_delete, "delete"},
84 	{excmd_delete, "d"},
85 
86 	{excmd_join, "join"},
87 	{excmd_join, "j"},
88 
89 	{excmd_copy, "copy"},
90 	{excmd_copy, "co"},
91 	{excmd_copy, "t"},
92 
93 	{excmd_move, "move"},
94 	{excmd_move, "m"},
95 
96 	{NULL, NULL}
97 };
98 
99 
100 typedef enum
101 {
102 	TK_END,
103 	TK_EOL,
104 	TK_ERROR,
105 
106 	TK_PLUS,
107 	TK_MINUS,
108 	TK_NUMBER,
109 	TK_COLON,
110 	TK_SEMICOLON,
111 	TK_DOT,
112 	TK_DOLLAR,
113 	TK_VISUAL_START,
114 	TK_VISUAL_END,
115 	TK_PATTERN,
116 
117 	TK_STAR,
118 	TK_PERCENT,
119 } TokenType;
120 
121 
122 typedef enum
123 {
124 	ST_START,
125 	ST_AFTER_NUMBER,
126 	ST_BEFORE_END
127 } State;
128 
129 
130 typedef struct
131 {
132 	TokenType type;
133 	gint num;
134 	gchar *str;
135 } Token;
136 
137 
init_tk(Token * tk,TokenType type,gint num,gchar * str)138 static void init_tk(Token *tk, TokenType type, gint num, gchar *str)
139 {
140 	tk->type = type;
141 	tk->num = num;
142 	g_free(tk->str);
143 	tk->str = str;
144 }
145 
146 
get_simple_token_type(gchar c)147 static TokenType get_simple_token_type(gchar c)
148 {
149 	switch (c)
150 	{
151 		case '+':
152 			return TK_PLUS;
153 		case '-':
154 			return TK_MINUS;
155 		case ';':
156 			return TK_SEMICOLON;
157 		case ',':
158 			return TK_COLON;
159 		case '.':
160 			return TK_DOT;
161 		case '$':
162 			return TK_DOLLAR;
163 		case '*':
164 			return TK_STAR;
165 		case '%':
166 			return TK_PERCENT;
167 		default:
168 			break;
169 	}
170 	return TK_ERROR;
171 }
172 
173 
next_token(const gchar ** p,Token * tk)174 static void next_token(const gchar **p, Token *tk)
175 {
176 	TokenType type;
177 
178 	while (isspace(**p))
179 		(*p)++;
180 
181 	if (**p == '\0')
182 	{
183 		init_tk(tk, TK_EOL, 0, NULL);
184 		return;
185 	}
186 
187 	if (isdigit(**p))
188 	{
189 		gint num = 0;
190 		while (isdigit(**p))
191 		{
192 			num = 10 * num + (**p - '0');
193 			(*p)++;
194 		}
195 		init_tk(tk, TK_NUMBER, num, NULL);
196 		return;
197 	}
198 
199 	type = get_simple_token_type(**p);
200 	if (type != TK_ERROR)
201 	{
202 		(*p)++;
203 		init_tk(tk, type, 0, NULL);
204 		return;
205 	}
206 
207 	if (**p == '/' || **p == '?')
208 	{
209 		gchar c = **p;
210 		gchar begin[2] = {c, '\0'};
211 		GString *s = g_string_new(begin);
212 		(*p)++;
213 		while (**p != c && **p != '\0')
214 		{
215 			g_string_append_c(s, **p);
216 			(*p)++;
217 		}
218 		if (**p == c)
219 			(*p)++;
220 		init_tk(tk, TK_PATTERN, 0, s->str);
221 		g_string_free(s, FALSE);
222 		return ;
223 	}
224 
225 	if (**p == '\'')
226 	{
227 		(*p)++;
228 		if (**p == '<')
229 		{
230 			(*p)++;
231 			init_tk(tk, TK_VISUAL_START, 0, NULL);
232 			return;
233 		}
234 		else if (**p == '>')
235 		{
236 			(*p)++;
237 			init_tk(tk, TK_VISUAL_END, 0, NULL);
238 			return;
239 		}
240 		else
241 		{
242 			init_tk(tk, TK_ERROR, 0, NULL);
243 			return;
244 		}
245 	}
246 
247 	init_tk(tk, TK_END, 0, NULL);
248 	return;
249 }
250 
251 
parse_ex_range(const gchar ** p,CmdContext * ctx,gint * from,gint * to)252 static gboolean parse_ex_range(const gchar **p, CmdContext *ctx, gint *from, gint *to)
253 {
254 	Token *tk = g_new0(Token, 1);
255 	State state = ST_START;
256 	gint num = 0;
257 	gboolean neg = FALSE;
258 	gint count = 0;
259 	gboolean success = TRUE;
260 
261 	next_token(p, tk);
262 
263 	while (TRUE)
264 	{
265 		if (state == ST_START)
266 		{
267 			switch (tk->type)
268 			{
269 				case TK_PLUS:
270 					state = ST_AFTER_NUMBER;
271 					break;
272 				case TK_MINUS:
273 					state = ST_AFTER_NUMBER;
274 					neg = !neg;
275 					break;
276 				case TK_NUMBER:
277 					state = ST_AFTER_NUMBER;
278 					num = tk->num - 1;
279 					break;
280 				case TK_DOT:
281 					state = ST_AFTER_NUMBER;
282 					num = GET_CUR_LINE(ctx->sci);
283 					break;
284 				case TK_DOLLAR:
285 					state = ST_AFTER_NUMBER;
286 					num = SSM(ctx->sci, SCI_GETLINECOUNT, 0, 0) - 1;
287 					break;
288 				case TK_VISUAL_START:
289 				{
290 					state = ST_AFTER_NUMBER;
291 					gint min = MIN(ctx->sel_anchor, SSM(ctx->sci, SCI_GETCURRENTPOS, 0, 0));
292 					num = SSM(ctx->sci, SCI_LINEFROMPOSITION, min, 0);
293 					break;
294 				}
295 				case TK_VISUAL_END:
296 				{
297 					state = ST_AFTER_NUMBER;
298 					gint max = MAX(ctx->sel_anchor, SSM(ctx->sci, SCI_GETCURRENTPOS, 0, 0));
299 					num = SSM(ctx->sci, SCI_LINEFROMPOSITION, max, 0);
300 					break;
301 				}
302 				case TK_PATTERN:
303 				{
304 					gint pos = perform_search(ctx->sci, tk->str, ctx->num, FALSE);
305 					num = SSM(ctx->sci, SCI_LINEFROMPOSITION, pos, 0);
306 					state = ST_AFTER_NUMBER;
307 					break;
308 				}
309 
310 				case TK_PERCENT:
311 					state = ST_BEFORE_END;
312 					*to = 0;
313 					num = SSM(ctx->sci, SCI_GETLINECOUNT, 0, 0) - 1;
314 					count++;
315 					break;
316 				case TK_STAR:
317 				{
318 					gint pos = MIN(ctx->sel_anchor, SSM(ctx->sci, SCI_GETCURRENTPOS, 0, 0));
319 					*to = SSM(ctx->sci, SCI_LINEFROMPOSITION, pos, 0);
320 					pos = MAX(ctx->sel_anchor, SSM(ctx->sci, SCI_GETCURRENTPOS, 0, 0));
321 					num = SSM(ctx->sci, SCI_LINEFROMPOSITION, pos, 0);
322 					state = ST_BEFORE_END;
323 					count++;
324 					break;
325 				}
326 
327 				case TK_SEMICOLON:
328 				case TK_COLON:
329 					//we don't have number yet, ignore
330 					break;
331 				default:
332 					goto finish;
333 			}
334 		}
335 		else if (state == ST_AFTER_NUMBER || state == ST_BEFORE_END)
336 		{
337 			if (tk->type == TK_SEMICOLON || tk->type == TK_COLON ||
338 				tk->type == TK_END || tk->type == TK_EOL)
339 			{
340 				num = MAX(num, 0);
341 				num = MIN(num, SSM(ctx->sci, SCI_GETLINECOUNT, 0, 0) - 1);
342 				if (tk->type == TK_SEMICOLON || tk->type == TK_EOL)
343 					goto_nonempty(ctx->sci, num, TRUE);
344 
345 				*from = *to;
346 				*to = num;
347 
348 				neg = FALSE;
349 				num = 0;
350 				count++;
351 				state = ST_START;
352 			}
353 			else if (state == ST_AFTER_NUMBER)
354 			{
355 				switch (tk->type)
356 				{
357 					case TK_PLUS:
358 						break;
359 					case TK_MINUS:
360 						neg = !neg;
361 						break;
362 					case TK_NUMBER:
363 						num += neg ? -tk->num : tk->num;
364 						neg = FALSE;
365 						break;
366 					default:
367 						goto finish;
368 				}
369 			}
370 			else
371 				goto finish;
372 		}
373 		next_token(p, tk);
374 	}
375 
376 finish:
377 	if (tk->type != TK_EOL && tk->type != TK_END)
378 		success = FALSE;
379 	g_free(tk->str);
380 	g_free(tk);
381 	if (count == 0)
382 		*from = *to = GET_CUR_LINE(ctx->sci);
383 	else if (count == 1)
384 		*from = *to;
385 	return success;
386 }
387 
388 
perform_simple_ex_cmd(CmdContext * ctx,const gchar * cmd)389 static void perform_simple_ex_cmd(CmdContext *ctx, const gchar *cmd)
390 {
391 	ExCmdParams params;
392 	gchar **parts, **part;
393 	gchar *cmd_name = NULL;
394 	gchar *param1 = NULL;
395 
396 	params.range_from = 0;
397 	params.range_to = 0;
398 
399 	if (strlen(cmd) < 1)
400 		return;
401 
402 	if (!parse_ex_range(&cmd, ctx, &params.range_from, &params.range_to))
403 		return;
404 
405 	if (g_str_has_prefix(cmd, "s/") || g_str_has_prefix(cmd, "substitute/"))
406 	{
407 		g_free(ctx->substitute_text);
408 		ctx->substitute_text = g_strdup(cmd);
409 		perform_substitute(ctx->sci, cmd, params.range_from, params.range_to, NULL);
410 		return;
411 	}
412 
413 	parts = g_strsplit(cmd, " ", 0);
414 
415 	for (part = parts; *part; part++)
416 	{
417 		if (strlen(*part) != 0)
418 		{
419 			if (!cmd_name)
420 				cmd_name = *part;
421 			else if (!param1)
422 				param1 = *part;
423 		}
424 	}
425 
426 	if (cmd_name)
427 	{
428 		gint i;
429 
430 		params.param1 = param1;
431 		params.force = FALSE;
432 		if (cmd_name[strlen(cmd_name)-1] == '!')
433 		{
434 			cmd_name[strlen(cmd_name)-1] = '\0';
435 			params.force = TRUE;
436 		}
437 
438 		for (i = 0; ex_cmds[i].cmd != NULL; i++)
439 		{
440 			ExCmdDef *def = &ex_cmds[i];
441 			if (strcmp(def->name, cmd_name) == 0)
442 			{
443 				if (def->cmd == excmd_copy || def->cmd == excmd_move)
444 					parse_ex_range(&(params.param1), ctx, &(params.dest), &(params.dest));
445 				SSM(ctx->sci, SCI_BEGINUNDOACTION, 0, 0);
446 				def->cmd(ctx, &params);
447 				SSM(ctx->sci, SCI_ENDUNDOACTION, 0, 0);
448 				break;
449 			}
450 		}
451 	}
452 
453 	g_strfreev(parts);
454 }
455 
456 
excmd_perform(CmdContext * ctx,const gchar * cmd)457 void excmd_perform(CmdContext *ctx, const gchar *cmd)
458 {
459 	guint len = strlen(cmd);
460 
461 	if (cmd == NULL || len < 1)
462 		return;
463 
464 	switch (cmd[0])
465 	{
466 		case ':':
467 			perform_simple_ex_cmd(ctx, cmd + 1);
468 			break;
469 		case '/':
470 		case '?':
471 		{
472 			gint pos;
473 			if (len == 1)
474 			{
475 				if (ctx->search_text && strlen(ctx->search_text) > 1)
476 					ctx->search_text[0] = cmd[0];
477 			}
478 			else
479 			{
480 				g_free(ctx->search_text);
481 				ctx->search_text = g_strdup(cmd);
482 			}
483 			pos = perform_search(ctx->sci, ctx->search_text, ctx->num, FALSE);
484 			if (pos >= 0)
485 				SET_POS(ctx->sci, pos, TRUE);
486 			break;
487 		}
488 	}
489 }
490