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/motion.h"
20 #include "utils.h"
21 
22 
cmd_goto_left(CmdContext * c,CmdParams * p)23 void cmd_goto_left(CmdContext *c, CmdParams *p)
24 {
25 	gint i;
26 	gint start_pos = p->line_start_pos;
27 	gint pos = p->pos;
28 	for (i = 0; i < p->num && pos > start_pos; i++)
29 		pos = PREV(p->sci, pos);
30 	SET_POS(p->sci, pos, TRUE);
31 }
32 
33 
cmd_goto_right(CmdContext * c,CmdParams * p)34 void cmd_goto_right(CmdContext *c, CmdParams *p)
35 {
36 	gint i;
37 	gint pos = p->pos;
38 	for (i = 0; i < p->num && pos < p->line_end_pos; i++)
39 		pos = NEXT(p->sci, pos);
40 	SET_POS(p->sci, pos, TRUE);
41 }
42 
43 
cmd_goto_up(CmdContext * c,CmdParams * p)44 void cmd_goto_up(CmdContext *c, CmdParams *p)
45 {
46 	gint one_above, pos;
47 
48 	if (p->line == 0)
49 		return;
50 
51 	/* Calling SCI_LINEUP/SCI_LINEDOWN in a loop for num lines leads to visible
52 	 * slow scrolling. On the other hand, SCI_LINEUP preserves the value of
53 	 * SCI_CHOOSECARETX which we cannot read directly from Scintilla and which
54 	 * we want to keep - perform jump to previous/following line and add
55 	 * one final SCI_LINEUP/SCI_LINEDOWN which recovers SCI_CHOOSECARETX for us. */
56 	one_above = p->line - p->num - 1;
57 	if (one_above >= 0)
58 	{
59 		/* Every case except for the first line - go one line above and perform
60 		 * SCI_LINEDOWN. This ensures that even with wrapping on, we get the
61 		 * caret on the first line of the wrapped line */
62 		pos = SSM(p->sci, SCI_GETLINEENDPOSITION, one_above, 0);
63 		SET_POS_NOX(p->sci, pos, FALSE);
64 		SSM(p->sci, SCI_LINEDOWN, 0, 0);
65 	}
66 	else
67 	{
68 		/* This is the first line and there is no line above - we need to go to
69 		 * the following line and do SCI_LINEUP. In addition, when wrapping is
70 		 * on, we need to repeat SCI_LINEUP to get to the first line of wrapping.
71 		 * This may lead to visible slow scrolling which is why there's the
72 		 * fast case above for anything else but the first line. */
73 		gint one_below = p->line - p->num + 1;
74 		gint wrap_count;
75 
76 		one_below = one_below > 0 ? one_below : 1;
77 		pos = SSM(p->sci, SCI_POSITIONFROMLINE, one_below, 0);
78 		SET_POS_NOX(p->sci, pos, FALSE);
79 		SSM(p->sci, SCI_LINEUP, 0, 0);
80 
81 		wrap_count = SSM(p->sci, SCI_WRAPCOUNT, GET_CUR_LINE(p->sci), 0);
82 		while (wrap_count > 1)
83 		{
84 			SSM(p->sci, SCI_LINEUP, 0, 0);
85 			wrap_count--;
86 		}
87 	}
88 }
89 
90 
cmd_goto_up_nonempty(CmdContext * c,CmdParams * p)91 void cmd_goto_up_nonempty(CmdContext *c, CmdParams *p)
92 {
93 	cmd_goto_up(c, p);
94 	goto_nonempty(p->sci, GET_CUR_LINE(p->sci), TRUE);
95 }
96 
97 
goto_down(CmdParams * p,gint num)98 static void goto_down(CmdParams *p, gint num)
99 {
100 	gint one_above, pos;
101 	gint last_line = p->line_num - 1;
102 
103 	if (p->line == last_line)
104 		return;
105 
106 	/* see cmd_goto_up() for explanation */
107 	one_above = p->line + num - 1;
108 	one_above = one_above < last_line ? one_above : last_line - 1;
109 	pos = SSM(p->sci, SCI_GETLINEENDPOSITION, one_above, 0);
110 	SET_POS_NOX(p->sci, pos, FALSE);
111 	SSM(p->sci, SCI_LINEDOWN, 0, 0);
112 }
113 
114 
cmd_goto_down(CmdContext * c,CmdParams * p)115 void cmd_goto_down(CmdContext *c, CmdParams *p)
116 {
117 	goto_down(p, p->num);
118 }
119 
120 
cmd_goto_down_nonempty(CmdContext * c,CmdParams * p)121 void cmd_goto_down_nonempty(CmdContext *c, CmdParams *p)
122 {
123 	goto_down(p, p->num);
124 	goto_nonempty(p->sci, GET_CUR_LINE(p->sci), TRUE);
125 }
126 
127 
cmd_goto_down_one_less_nonempty(CmdContext * c,CmdParams * p)128 void cmd_goto_down_one_less_nonempty(CmdContext *c, CmdParams *p)
129 {
130 	if (p->num > 1)
131 		goto_down(p, p->num - 1);
132 	goto_nonempty(p->sci, GET_CUR_LINE(p->sci), TRUE);
133 }
134 
135 
cmd_goto_page_up(CmdContext * c,CmdParams * p)136 void cmd_goto_page_up(CmdContext *c, CmdParams *p)
137 {
138 	gint shift = p->line_visible_num * p->num;
139 	gint new_line = get_line_number_rel(p->sci, -shift);
140 	goto_nonempty(p->sci, new_line, TRUE);
141 }
142 
143 
cmd_goto_page_down(CmdContext * c,CmdParams * p)144 void cmd_goto_page_down(CmdContext *c, CmdParams *p)
145 {
146 	gint shift = p->line_visible_num * p->num;
147 	gint new_line = get_line_number_rel(p->sci, shift);
148 	goto_nonempty(p->sci, new_line, TRUE);
149 }
150 
151 
cmd_goto_halfpage_up(CmdContext * c,CmdParams * p)152 void cmd_goto_halfpage_up(CmdContext *c, CmdParams *p)
153 {
154 	gint shift = p->num_present ? p->num : p->line_visible_num / 2;
155 	gint new_line = get_line_number_rel(p->sci, -shift);
156 	goto_nonempty(p->sci, new_line, TRUE);
157 }
158 
159 
cmd_goto_halfpage_down(CmdContext * c,CmdParams * p)160 void cmd_goto_halfpage_down(CmdContext *c, CmdParams *p)
161 {
162 	gint shift = p->num_present ? p->num : p->line_visible_num / 2;
163 	gint new_line = get_line_number_rel(p->sci, shift);
164 	goto_nonempty(p->sci, new_line, TRUE);
165 }
166 
167 
cmd_goto_line(CmdContext * c,CmdParams * p)168 void cmd_goto_line(CmdContext *c, CmdParams *p)
169 {
170 	gint num = p->num > p->line_num ? p->line_num : p->num;
171 	goto_nonempty(p->sci, num - 1, TRUE);
172 }
173 
174 
cmd_goto_line_last(CmdContext * c,CmdParams * p)175 void cmd_goto_line_last(CmdContext *c, CmdParams *p)
176 {
177 	gint num = p->num > p->line_num ? p->line_num : p->num;
178 	if (!p->num_present)
179 		num = p->line_num;
180 	goto_nonempty(p->sci, num - 1, TRUE);
181 }
182 
183 
cmd_goto_screen_top(CmdContext * c,CmdParams * p)184 void cmd_goto_screen_top(CmdContext *c, CmdParams *p)
185 {
186 	gint top = p->line_visible_first;
187 	gint count = p->line_visible_num;
188 	gint line = top + p->num;
189 	goto_nonempty(p->sci, line > top + count ? top + count : line, FALSE);
190 }
191 
192 
cmd_goto_screen_middle(CmdContext * c,CmdParams * p)193 void cmd_goto_screen_middle(CmdContext *c, CmdParams *p)
194 {
195 	goto_nonempty(p->sci, p->line_visible_first + p->line_visible_num/2, FALSE);
196 }
197 
198 
cmd_goto_screen_bottom(CmdContext * c,CmdParams * p)199 void cmd_goto_screen_bottom(CmdContext *c, CmdParams *p)
200 {
201 	gint top = p->line_visible_first;
202 	gint count = p->line_visible_num;
203 	gint line = top + count - p->num;
204 	goto_nonempty(p->sci, line < top ? top : line, FALSE);
205 }
206 
207 
cmd_goto_doc_percentage(CmdContext * c,CmdParams * p)208 void cmd_goto_doc_percentage(CmdContext *c, CmdParams *p)
209 {
210 	if (p->num > 100)
211 		p->num = 100;
212 
213 	goto_nonempty(p->sci, (p->line_num * p->num) / 100, TRUE);
214 }
215 
216 
cmd_goto_line_start(CmdContext * c,CmdParams * p)217 void cmd_goto_line_start(CmdContext *c, CmdParams *p)
218 {
219 	SSM(p->sci, SCI_HOME, 0, 0);
220 }
221 
222 
cmd_goto_line_start_nonempty(CmdContext * c,CmdParams * p)223 void cmd_goto_line_start_nonempty(CmdContext *c, CmdParams *p)
224 {
225 	goto_nonempty(p->sci, p->line, TRUE);
226 }
227 
228 
cmd_goto_line_end(CmdContext * c,CmdParams * p)229 void cmd_goto_line_end(CmdContext *c, CmdParams *p)
230 {
231 	if (p->num > 1)
232 		goto_down(p, p->num - 1);
233 	SSM(p->sci, SCI_LINEEND, 0, 0);
234 }
235 
236 
cmd_goto_column(CmdContext * c,CmdParams * p)237 void cmd_goto_column(CmdContext *c, CmdParams *p)
238 {
239 	gint pos = SSM(p->sci, SCI_FINDCOLUMN, p->line, p->num - 1);
240 	SET_POS(p->sci, pos, TRUE);
241 }
242 
243 
cmd_goto_matching_brace(CmdContext * c,CmdParams * p)244 void cmd_goto_matching_brace(CmdContext *c, CmdParams *p)
245 {
246 	gint pos = p->pos;
247 	while (pos < p->line_end_pos)
248 	{
249 		gint matching_pos = SSM(p->sci, SCI_BRACEMATCH, pos, 0);
250 		if (matching_pos != -1)
251 		{
252 			SET_POS(p->sci, matching_pos, TRUE);
253 			return;
254 		}
255 		pos++;
256 	}
257 }
258 
259 
find_char(CmdContext * c,CmdParams * p,gboolean invert)260 static void find_char(CmdContext *c, CmdParams *p, gboolean invert)
261 {
262 	struct Sci_TextToFind ttf;
263 	gboolean forward;
264 	gint pos = p->pos;
265 	gint i;
266 
267 	if (!c->search_char)
268 		return;
269 
270 	forward = c->search_char[0] == 'f' || c->search_char[0] == 't';
271 	forward = !forward != !invert;
272 	ttf.lpstrText = c->search_char + 1;
273 
274 	for (i = 0; i < p->num; i++)
275 	{
276 		gint new_pos;
277 
278 		if (forward)
279 		{
280 			ttf.chrg.cpMin = NEXT(p->sci, pos);
281 			ttf.chrg.cpMax = p->line_end_pos;
282 		}
283 		else
284 		{
285 			ttf.chrg.cpMin = pos;
286 			ttf.chrg.cpMax = p->line_start_pos;
287 		}
288 
289 		new_pos = SSM(p->sci, SCI_FINDTEXT, 0, (sptr_t)&ttf);
290 		if (new_pos < 0)
291 			break;
292 		pos = new_pos;
293 	}
294 
295 	if (pos >= 0)
296 	{
297 		if (c->search_char[0] == 't')
298 			pos = PREV(p->sci, pos);
299 		else if (c->search_char[0] == 'T')
300 			pos = NEXT(p->sci, pos);
301 		SET_POS(p->sci, pos, TRUE);
302 	}
303 }
304 
305 
cmd_goto_next_char(CmdContext * c,CmdParams * p)306 void cmd_goto_next_char(CmdContext *c, CmdParams *p)
307 {
308 	g_free(c->search_char);
309 	c->search_char = g_strconcat("f", kp_to_str(p->last_kp), NULL);
310 	find_char(c, p, FALSE);
311 }
312 
313 
cmd_goto_prev_char(CmdContext * c,CmdParams * p)314 void cmd_goto_prev_char(CmdContext *c, CmdParams *p)
315 {
316 	g_free(c->search_char);
317 	c->search_char = g_strconcat("F", kp_to_str(p->last_kp), NULL);
318 	find_char(c, p, FALSE);
319 }
320 
321 
cmd_goto_next_char_before(CmdContext * c,CmdParams * p)322 void cmd_goto_next_char_before(CmdContext *c, CmdParams *p)
323 {
324 	g_free(c->search_char);
325 	c->search_char = g_strconcat("t", kp_to_str(p->last_kp), NULL);
326 	find_char(c, p, FALSE);
327 }
328 
329 
cmd_goto_prev_char_before(CmdContext * c,CmdParams * p)330 void cmd_goto_prev_char_before(CmdContext *c, CmdParams *p)
331 {
332 	g_free(c->search_char);
333 	c->search_char = g_strconcat("T", kp_to_str(p->last_kp), NULL);
334 	find_char(c, p, FALSE);
335 }
336 
337 
cmd_goto_char_repeat(CmdContext * c,CmdParams * p)338 void cmd_goto_char_repeat(CmdContext *c, CmdParams *p)
339 {
340 	find_char(c, p, FALSE);
341 }
342 
343 
cmd_goto_char_repeat_opposite(CmdContext * c,CmdParams * p)344 void cmd_goto_char_repeat_opposite(CmdContext *c, CmdParams *p)
345 {
346 	find_char(c, p, TRUE);
347 }
348 
349 
cmd_scroll_up(CmdContext * c,CmdParams * p)350 void cmd_scroll_up(CmdContext *c, CmdParams *p)
351 {
352 	SSM(p->sci, SCI_LINESCROLL, 0, -p->num);
353 }
354 
355 
cmd_scroll_down(CmdContext * c,CmdParams * p)356 void cmd_scroll_down(CmdContext *c, CmdParams *p)
357 {
358 	SSM(p->sci, SCI_LINESCROLL, 0, p->num);
359 }
360 
361 
scroll_to_line(CmdParams * p,gint offset,gboolean nonempty)362 static void scroll_to_line(CmdParams *p, gint offset, gboolean nonempty)
363 {
364 	gint column = SSM(p->sci, SCI_GETCOLUMN, p->pos, 0);
365 	gint line = p->line;
366 
367 	if (p->num_present)
368 		line = p->num - 1;
369 	if (nonempty)
370 		goto_nonempty(p->sci, line, FALSE);
371 	else
372 	{
373 		gint pos = SSM(p->sci, SCI_FINDCOLUMN, line, column);
374 		SET_POS_NOX(p->sci, pos, FALSE);
375 	}
376 	SSM(p->sci, SCI_SETFIRSTVISIBLELINE, line + offset, 0);
377 }
378 
379 
cmd_scroll_center(CmdContext * c,CmdParams * p)380 void cmd_scroll_center(CmdContext *c, CmdParams *p)
381 {
382 	scroll_to_line(p, - p->line_visible_num / 2, FALSE);
383 }
384 
385 
cmd_scroll_top(CmdContext * c,CmdParams * p)386 void cmd_scroll_top(CmdContext *c, CmdParams *p)
387 {
388 	scroll_to_line(p, 0, FALSE);
389 }
390 
391 
cmd_scroll_bottom(CmdContext * c,CmdParams * p)392 void cmd_scroll_bottom(CmdContext *c, CmdParams *p)
393 {
394 	scroll_to_line(p, - p->line_visible_num + 1, FALSE);
395 }
396 
397 
cmd_scroll_center_nonempty(CmdContext * c,CmdParams * p)398 void cmd_scroll_center_nonempty(CmdContext *c, CmdParams *p)
399 {
400 	scroll_to_line(p, - p->line_visible_num / 2, TRUE);
401 }
402 
403 
cmd_scroll_top_nonempty(CmdContext * c,CmdParams * p)404 void cmd_scroll_top_nonempty(CmdContext *c, CmdParams *p)
405 {
406 	scroll_to_line(p, 0, TRUE);
407 }
408 
409 
cmd_scroll_top_next_nonempty(CmdContext * c,CmdParams * p)410 void cmd_scroll_top_next_nonempty(CmdContext *c, CmdParams *p)
411 {
412 	if (p->num_present)
413 		cmd_scroll_top_nonempty(c, p);
414 	else
415 	{
416 		gint line = p->line_visible_first + p->line_visible_num;
417 		goto_nonempty(p->sci, line, FALSE);
418 		SSM(p->sci, SCI_SETFIRSTVISIBLELINE, line, 0);
419 	}
420 }
421 
422 
cmd_scroll_bottom_nonempty(CmdContext * c,CmdParams * p)423 void cmd_scroll_bottom_nonempty(CmdContext *c, CmdParams *p)
424 {
425 	scroll_to_line(p, - p->line_visible_num + 1, TRUE);
426 }
427