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 "cmds/edit.h"
20 #include "utils.h"
21 
22 
delete_char(CmdContext * c,CmdParams * p,gboolean yank)23 static void delete_char(CmdContext *c, CmdParams *p, gboolean yank)
24 {
25 	gint end_pos = NTH(p->sci, p->pos, p->num);
26 	end_pos = end_pos > p->line_end_pos ? p->line_end_pos : end_pos;
27 	if (yank)
28 	{
29 		c->line_copy = FALSE;
30 		SSM(p->sci, SCI_COPYRANGE, p->pos, end_pos);
31 	}
32 	SSM(p->sci, SCI_DELETERANGE, p->pos, end_pos - p->pos);
33 }
34 
35 
cmd_delete_char(CmdContext * c,CmdParams * p)36 void cmd_delete_char(CmdContext *c, CmdParams *p)
37 {
38 	delete_char(c, p, FALSE);
39 }
40 
41 
cmd_delete_char_copy(CmdContext * c,CmdParams * p)42 void cmd_delete_char_copy(CmdContext *c, CmdParams *p)
43 {
44 	delete_char(c, p, TRUE);
45 }
46 
47 
delete_char_back(CmdContext * c,CmdParams * p,gboolean yank)48 static void delete_char_back(CmdContext *c, CmdParams *p, gboolean yank)
49 {
50 	gint start_pos = NTH(p->sci, p->pos, -p->num);
51 	start_pos = start_pos < p->line_start_pos ? p->line_start_pos : start_pos;
52 	if (yank)
53 	{
54 		c->line_copy = FALSE;
55 		SSM(p->sci, SCI_COPYRANGE, start_pos, p->pos);
56 	}
57 	SSM(p->sci, SCI_DELETERANGE, start_pos, p->pos - start_pos);
58 }
59 
60 
cmd_delete_char_back(CmdContext * c,CmdParams * p)61 void cmd_delete_char_back(CmdContext *c, CmdParams *p)
62 {
63 	delete_char_back(c, p, FALSE);
64 }
65 
66 
cmd_delete_char_back_copy(CmdContext * c,CmdParams * p)67 void cmd_delete_char_back_copy(CmdContext *c, CmdParams *p)
68 {
69 	delete_char_back(c, p, TRUE);
70 }
71 
72 
cmd_clear_right(CmdContext * c,CmdParams * p)73 void cmd_clear_right(CmdContext *c, CmdParams *p)
74 {
75 	gint new_line = get_line_number_rel(p->sci, p->num - 1);
76 	gint pos = SSM(p->sci, SCI_GETLINEENDPOSITION, new_line, 0);
77 
78 	c->line_copy = FALSE;
79 	SSM(p->sci, SCI_COPYRANGE, p->pos, pos);
80 	SSM(p->sci, SCI_DELETERANGE, p->pos, pos - p->pos);
81 }
82 
83 
insert_eof_nl_if_missing(CmdParams * p)84 static gboolean insert_eof_nl_if_missing(CmdParams *p)
85 {
86 	gint pos = SSM(p->sci, SCI_GETCURRENTPOS, 0, 0);
87 	gint last_line = SSM(p->sci, SCI_GETLINECOUNT, 0, 0);
88 	gint line_start_pos = SSM(p->sci, SCI_POSITIONFROMLINE, last_line, 0);
89 	gint line_end_pos = SSM(p->sci, SCI_GETLINEENDPOSITION, last_line, 0);
90 
91 	if (line_start_pos == line_end_pos) {
92 		SET_POS(p->sci, line_end_pos, FALSE);
93 		SSM(p->sci, SCI_NEWLINE, 0, 0);
94 		SET_POS(p->sci, pos, FALSE);
95 		return TRUE;
96 	}
97 	return FALSE;
98 }
99 
100 
remove_char_from_eof(CmdParams * p)101 static void remove_char_from_eof(CmdParams *p)
102 {
103 	gint pos = SSM(p->sci, SCI_GETCURRENTPOS, 0, 0);
104 	gint last_line = SSM(p->sci, SCI_GETLINECOUNT, 0, 0);
105 	gint line_end_pos = SSM(p->sci, SCI_GETLINEENDPOSITION, last_line, 0);
106 
107 	SET_POS(p->sci, line_end_pos, FALSE);
108 	SSM(p->sci, SCI_DELETEBACK, 0, 0);
109 	SET_POS(p->sci, pos, FALSE);
110 }
111 
112 
cmd_delete_line(CmdContext * c,CmdParams * p)113 void cmd_delete_line(CmdContext *c, CmdParams *p)
114 {
115 	gboolean nl_inserted = insert_eof_nl_if_missing(p);
116 	gint num = get_line_number_rel(p->sci, p->num);
117 	gint end = SSM(p->sci, SCI_POSITIONFROMLINE, num, 0);
118 
119 	c->line_copy = TRUE;
120 	SSM(p->sci, SCI_COPYRANGE, p->line_start_pos, end);
121 	SSM(p->sci, SCI_DELETERANGE, p->line_start_pos, end - p->line_start_pos);
122 	if (nl_inserted)
123 		remove_char_from_eof(p);
124 	goto_nonempty(p->sci, GET_CUR_LINE(p->sci), TRUE);
125 }
126 
127 
cmd_copy_line(CmdContext * c,CmdParams * p)128 void cmd_copy_line(CmdContext *c, CmdParams *p)
129 {
130 	gboolean nl_inserted = insert_eof_nl_if_missing(p);
131 	gint num = get_line_number_rel(p->sci, p->num);
132 	gint end = SSM(p->sci, SCI_POSITIONFROMLINE, num, 0);
133 
134 	c->line_copy = TRUE;
135 	SSM(p->sci, SCI_COPYRANGE, p->line_start_pos, end);
136 	if (nl_inserted)
137 		remove_char_from_eof(p);
138 }
139 
140 
cmd_newline(CmdContext * c,CmdParams * p)141 void cmd_newline(CmdContext *c, CmdParams *p)
142 {
143 	SSM(p->sci, SCI_NEWLINE, 0, 0);
144 }
145 
146 
cmd_tab(CmdContext * c,CmdParams * p)147 void cmd_tab(CmdContext *c, CmdParams *p)
148 {
149 	SSM(p->sci, SCI_TAB, 0, 0);
150 }
151 
152 
cmd_del_word_left(CmdContext * c,CmdParams * p)153 void cmd_del_word_left(CmdContext *c, CmdParams *p)
154 {
155 	SSM(p->sci, SCI_DELWORDLEFT, 0, 0);
156 }
157 
158 
cmd_undo(CmdContext * c,CmdParams * p)159 void cmd_undo(CmdContext *c, CmdParams *p)
160 {
161 	gint i;
162 	for (i = 0; i < p->num; i++)
163 	{
164 		if (!SSM(p->sci, SCI_CANUNDO, 0, 0))
165 			break;
166 		SSM(p->sci, SCI_UNDO, 0, 0);
167 	}
168 }
169 
170 
cmd_redo(CmdContext * c,CmdParams * p)171 void cmd_redo(CmdContext *c, CmdParams *p)
172 {
173 	gint i;
174 	for (i = 0; i < p->num; i++)
175 	{
176 		if (!SSM(p->sci, SCI_CANREDO, 0, 0))
177 			break;
178 		SSM(p->sci, SCI_REDO, 0, 0);
179 	}
180 }
181 
182 
paste(CmdContext * c,CmdParams * p,gboolean after)183 static void paste(CmdContext *c, CmdParams *p, gboolean after)
184 {
185 	gboolean nl_inserted = FALSE;
186 	gint pos;
187 	gint i;
188 
189 	if (c->line_copy)
190 	{
191 		if (after)
192 		{
193 			nl_inserted = insert_eof_nl_if_missing(p);
194 			pos = SSM(p->sci, SCI_POSITIONFROMLINE, p->line+1, 0);
195 		}
196 		else
197 			pos = p->line_start_pos;
198 	}
199 	else
200 	{
201 		pos = p->pos;
202 		if (after && pos < p->line_end_pos)
203 			pos = NEXT(p->sci, pos);
204 	}
205 
206 	SET_POS(p->sci, pos, TRUE);
207 	for (i = 0; i < p->num; i++)
208 		SSM(p->sci, SCI_PASTE, 0, 0);
209 	if (c->line_copy)
210 	{
211 		SET_POS(p->sci, pos, TRUE);
212 		if (nl_inserted)
213 			remove_char_from_eof(p);
214 		goto_nonempty(p->sci, GET_CUR_LINE(p->sci), TRUE);
215 	}
216 	else if (!VI_IS_INSERT(vi_get_mode()))
217 		SSM(p->sci, SCI_CHARLEFT, 0, 0);
218 }
219 
220 
cmd_paste_after(CmdContext * c,CmdParams * p)221 void cmd_paste_after(CmdContext *c, CmdParams *p)
222 {
223 	paste(c, p, TRUE);
224 }
225 
226 
cmd_paste_before(CmdContext * c,CmdParams * p)227 void cmd_paste_before(CmdContext *c, CmdParams *p)
228 {
229 	paste(c, p, FALSE);
230 }
231 
232 
join_lines(CmdContext * c,CmdParams * p,gint line,gint num)233 static void join_lines(CmdContext *c, CmdParams *p, gint line, gint num)
234 {
235 	for (int i = 0; i < num; i++)
236 	{
237 		gint line_start_pos = SSM(p->sci, SCI_POSITIONFROMLINE, line, 0);
238 		gint line_end_pos = SSM(p->sci, SCI_GETLINEENDPOSITION, line, 0);
239 		gint next_line_end_pos = SSM(p->sci, SCI_GETLINEENDPOSITION, line+1, 0);
240 		gint pos = line_end_pos;
241 		gint pos_start;
242 
243 		while (g_ascii_isspace(SSM(p->sci, SCI_GETCHARAT, pos, 0)) && pos > line_start_pos)
244 			pos = PREV(p->sci, pos);
245 		if (!g_ascii_isspace(SSM(p->sci, SCI_GETCHARAT, pos, 0)))
246 			pos = NEXT(p->sci, pos);
247 		pos_start = pos;
248 
249 		pos = line_end_pos;
250 		while (g_ascii_isspace(SSM(p->sci, SCI_GETCHARAT, pos, 0))  && pos < next_line_end_pos)
251 			pos = NEXT(p->sci, pos);
252 
253 		SSM(p->sci, SCI_DELETERANGE, pos_start, pos - pos_start);
254 		SSM(p->sci, SCI_INSERTTEXT, pos_start, (sptr_t)" ");
255 	}
256 }
257 
258 
cmd_join_lines(CmdContext * c,CmdParams * p)259 void cmd_join_lines(CmdContext *c, CmdParams *p)
260 {
261 	gint num = p->num;
262 	if (p->num_present && num > 1)
263 		num--;
264 	join_lines(c, p, p->line, num);
265 }
266 
267 
cmd_join_lines_sel(CmdContext * c,CmdParams * p)268 void cmd_join_lines_sel(CmdContext *c, CmdParams *p)
269 {
270 	join_lines(c, p, p->sel_first_line, p->sel_last_line - p->sel_first_line);
271 	vi_set_mode(VI_MODE_COMMAND);
272 }
273 
274 
replace_char(ScintillaObject * sci,gint pos,gint num,gint line,gboolean force_upper,gboolean force_lower,gunichar repl_char)275 static void replace_char(ScintillaObject *sci, gint pos, gint num, gint line,
276 	gboolean force_upper, gboolean force_lower, gunichar repl_char)
277 {
278 	gint i;
279 	gint max_num;
280 	gint last_pos;
281 	struct Sci_TextRange tr;
282 	gchar *original, *replacement, *repl, *orig;
283 
284 	max_num = DIFF(sci, pos, SSM(sci, SCI_GETLINEENDPOSITION, line, 0));
285 	if (line != -1 && num > max_num)
286 		num = max_num;
287 
288 	max_num = DIFF(sci, pos, SSM(sci, SCI_GETLENGTH, 0, 0));
289 	if (num > max_num)
290 		num = max_num;
291 
292 	if (num <= 0)
293 		return;
294 
295 	last_pos = NTH(sci, pos, num);
296 	original = g_malloc(last_pos - pos + 1);
297 	replacement = g_malloc(6 * num + 1);
298 	repl = replacement;
299 	orig = original;
300 
301 	tr.chrg.cpMin = pos;
302 	tr.chrg.cpMax = last_pos;
303 	tr.lpstrText = orig;
304 	SSM(sci, SCI_GETTEXTRANGE, 0, (sptr_t)&tr);
305 
306 	for (i = 0; i < num; i++)
307 	{
308 		gunichar c = g_utf8_get_char(orig);
309 
310 		if (repl_char == 0)
311 		{
312 			if ((force_upper || g_unichar_islower(c)) && !force_lower)
313 				c = g_unichar_toupper(c);
314 			else if (force_lower || g_unichar_isupper(c))
315 				c = g_unichar_tolower(c);
316 		}
317 		else
318 		{
319 			GUnicodeBreakType t = g_unichar_break_type(c);
320 			if (t != G_UNICODE_BREAK_CARRIAGE_RETURN  &&
321 				t != G_UNICODE_BREAK_LINE_FEED)
322 				c = repl_char;
323 		}
324 
325 		repl += g_unichar_to_utf8(c, repl);
326 		orig = g_utf8_find_next_char(orig, NULL);
327 
328 	}
329 	*repl = '\0';
330 
331 	SSM(sci, SCI_SETTARGETRANGE, pos, last_pos);
332 	SSM(sci, SCI_REPLACETARGET, -1, (sptr_t)replacement);
333 
334 	if (line != -1)
335 		SET_POS(sci, last_pos, TRUE);
336 
337 	g_free(original);
338 	g_free(replacement);
339 }
340 
341 
cmd_replace_char(CmdContext * c,CmdParams * p)342 void cmd_replace_char(CmdContext *c, CmdParams *p)
343 {
344 	gunichar repl = gdk_keyval_to_unicode(p->last_kp->key);
345 	replace_char(p->sci, p->pos, p->num, p->line, FALSE, FALSE, repl);
346 }
347 
348 
cmd_replace_char_sel(CmdContext * c,CmdParams * p)349 void cmd_replace_char_sel(CmdContext *c, CmdParams *p)
350 {
351 	gint num = DIFF(p->sci, p->sel_start, p->sel_start + p->sel_len);
352 	gunichar repl = gdk_keyval_to_unicode(p->last_kp->key);
353 	replace_char(p->sci, p->sel_start, num, -1, FALSE, FALSE, repl);
354 	vi_set_mode(VI_MODE_COMMAND);
355 }
356 
357 
switch_case(CmdContext * c,CmdParams * p,gboolean force_upper,gboolean force_lower)358 static void switch_case(CmdContext *c, CmdParams *p,
359 	gboolean force_upper, gboolean force_lower)
360 {
361 	if (VI_IS_VISUAL(vi_get_mode()) || p->sel_len > 0)
362 	{
363 		gint num = DIFF(p->sci, p->sel_start, p->sel_start + p->sel_len);
364 		replace_char(p->sci, p->sel_start, num, -1, force_upper, force_lower, 0);
365 		vi_set_mode(VI_MODE_COMMAND);
366 	}
367 	else
368 		replace_char(p->sci, p->pos, p->num, p->line, force_upper, force_lower, 0);
369 }
370 
371 
cmd_switch_case(CmdContext * c,CmdParams * p)372 void cmd_switch_case(CmdContext *c, CmdParams *p)
373 {
374 	switch_case(c, p, FALSE, FALSE);
375 }
376 
377 
cmd_lower_case(CmdContext * c,CmdParams * p)378 void cmd_lower_case(CmdContext *c, CmdParams *p)
379 {
380 	switch_case(c, p, FALSE, TRUE);
381 }
382 
383 
cmd_upper_case(CmdContext * c,CmdParams * p)384 void cmd_upper_case(CmdContext *c, CmdParams *p)
385 {
386 	switch_case(c, p, TRUE, FALSE);
387 }
388 
389 
indent(ScintillaObject * sci,gboolean unindent,gint pos,gint num,gint indent_num)390 static void indent(ScintillaObject *sci, gboolean unindent, gint pos, gint num, gint indent_num)
391 {
392 	gint i;
393 	gint line_start = SSM(sci, SCI_LINEFROMPOSITION, pos, 0);
394 	gint line_count = SSM(sci, SCI_GETLINECOUNT, 0, 0);
395 	gint line_end = line_start + num > line_count ? line_count : line_start + num;
396 	gint end_pos = SSM(sci, SCI_POSITIONFROMLINE, line_end, 0);
397 
398 	SSM(sci, SCI_HOME, 0, 0);
399 	SSM(sci, SCI_SETSEL, end_pos, pos);
400 	for (i = 0; i < indent_num; i++)
401 		SSM(sci, unindent ? SCI_BACKTAB : SCI_TAB, 0, 0);
402 	goto_nonempty(sci, line_start, TRUE);
403 }
404 
405 
cmd_indent(CmdContext * c,CmdParams * p)406 void cmd_indent(CmdContext *c, CmdParams *p)
407 {
408 	indent(p->sci, FALSE, p->pos, p->num, 1);
409 }
410 
411 
cmd_unindent(CmdContext * c,CmdParams * p)412 void cmd_unindent(CmdContext *c, CmdParams *p)
413 {
414 	indent(p->sci, TRUE, p->pos, p->num, 1);
415 }
416 
417 
cmd_indent_sel(CmdContext * c,CmdParams * p)418 void cmd_indent_sel(CmdContext *c, CmdParams *p)
419 {
420 	indent(p->sci, FALSE, p->sel_start, p->sel_last_line - p->sel_first_line + 1, p->num);
421 	vi_set_mode(VI_MODE_COMMAND);
422 }
423 
424 
cmd_unindent_sel(CmdContext * c,CmdParams * p)425 void cmd_unindent_sel(CmdContext *c, CmdParams *p)
426 {
427 	indent(p->sci, TRUE, p->sel_start, p->sel_last_line - p->sel_first_line + 1, p->num);
428 	vi_set_mode(VI_MODE_COMMAND);
429 }
430 
431 
indent_ins(CmdContext * c,CmdParams * p,gboolean indent)432 static void indent_ins(CmdContext *c, CmdParams *p, gboolean indent)
433 {
434 	gint delta;
435 	SSM(p->sci, SCI_HOME, 0, 0);
436 	SSM(p->sci, indent ? SCI_TAB : SCI_BACKTAB, 0, 0);
437 	delta = SSM(p->sci, SCI_GETLINEENDPOSITION, p->line, 0) - p->line_end_pos;
438 	SET_POS(p->sci, p->pos + delta, TRUE);
439 }
440 
441 
cmd_indent_ins(CmdContext * c,CmdParams * p)442 void cmd_indent_ins(CmdContext *c, CmdParams *p)
443 {
444 	indent_ins(c, p, TRUE);
445 }
446 
cmd_unindent_ins(CmdContext * c,CmdParams * p)447 void cmd_unindent_ins(CmdContext *c, CmdParams *p)
448 {
449 	indent_ins(c, p, FALSE);
450 }
451 
452 
copy_char(CmdContext * c,CmdParams * p,gboolean above)453 static void copy_char(CmdContext *c, CmdParams *p, gboolean above)
454 {
455 	if ((above && p->line > 0) || (!above && p->line < p->line_num - 1))
456 	{
457 		gint line = above ? p->line - 1 : p->line + 1;
458 		gint col = SSM(p->sci, SCI_GETCOLUMN, p->pos, 0);
459 		gint pos = SSM(p->sci, SCI_FINDCOLUMN, line, col);
460 		gint line_end = SSM(p->sci, SCI_GETLINEENDPOSITION, line, 0);
461 
462 		if (pos < line_end)
463 		{
464 			gchar txt[MAX_CHAR_SIZE];
465 			struct Sci_TextRange tr;
466 
467 			tr.chrg.cpMin = pos;
468 			tr.chrg.cpMax = NEXT(p->sci, pos);
469 			tr.lpstrText = txt;
470 
471 			SSM(p->sci, SCI_GETTEXTRANGE, 0, (sptr_t)&tr);
472 			SSM(p->sci, SCI_INSERTTEXT, p->pos, (sptr_t)txt);
473 			SET_POS(p->sci, NEXT(p->sci, p->pos), TRUE);
474 		}
475 	}
476 }
477 
478 
cmd_copy_char_from_above(CmdContext * c,CmdParams * p)479 void cmd_copy_char_from_above(CmdContext *c, CmdParams *p)
480 {
481 	copy_char(c, p, TRUE);
482 }
483 
484 
cmd_copy_char_from_below(CmdContext * c,CmdParams * p)485 void cmd_copy_char_from_below(CmdContext *c, CmdParams *p)
486 {
487 	copy_char(c, p, FALSE);
488 }
489 
490 
cmd_repeat_subst(CmdContext * c,CmdParams * p)491 void cmd_repeat_subst(CmdContext *c, CmdParams *p)
492 {
493 	perform_substitute(p->sci, c->substitute_text, p->line, p->line, "");
494 }
495