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, ¶ms.range_from, ¶ms.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, ¶ms);
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