xref: /freebsd/contrib/less/jump.c (revision dece5b05f577fc4c6b89af7ad48dfbdf7cc4945c)
1 /*
2  * Copyright (C) 1984-2026  Mark Nudelman
3  *
4  * You may distribute under the terms of either the GNU General Public
5  * License or the Less License, as specified in the README file.
6  *
7  * For more information, see the README file.
8  */
9 
10 
11 /*
12  * Routines which jump to a new location in the file.
13  */
14 
15 #include "less.h"
16 #include "position.h"
17 
18 extern int jump_sline;
19 extern lbool squished;
20 extern int sc_width, sc_height;
21 extern int show_attn;
22 extern int top_scroll;
23 extern int quit_if_one_screen;
24 extern lbool one_screen;
25 extern lbool full_screen;
26 extern POSITION header_start_pos;
27 extern POSITION soft_eof;
28 
29 /*
30  * Jump to the end of the file.
31  */
32 public void jump_forw(void)
33 {
34 	POSITION pos;
35 	POSITION end_pos;
36 	POSITION bot_pos;
37 
38 	if (ch_end_seek())
39 	{
40 		error(LM(Cannot_seek_to_end_of_file), NULL_PARG);
41 		return;
42 	}
43 	end_pos = ch_tell();
44 	bot_pos = position(BOTTOM_PLUS_ONE);
45 	if (bot_pos == end_pos || (bot_pos == soft_eof && soft_eof != NULL_POSITION))
46 	{
47 		eof_bell();
48 		return;
49 	}
50 	/*
51 	 * Note; lastmark will be called later by jump_loc, but it fails
52 	 * because the position table has been cleared by pos_clear below.
53 	 * So call it here before calling pos_clear.
54 	 */
55 	lastmark();
56 	/*
57 	 * Position the last line in the file at the last screen line.
58 	 * Go back one line from the end of the file
59 	 * to get to the beginning of the last line.
60 	 */
61 	pos_clear();
62 	pos = back_line(end_pos, &soft_eof, NULL);
63 	if (pos == NULL_POSITION)
64 		jump_loc(ch_zero(), sc_height-1);
65 	else
66 	{
67 		jump_loc(pos, sc_height-1);
68 		if (position(sc_height-1) != end_pos)
69 			repaint();
70 	}
71 }
72 
73 /*
74  * Jump to the last buffered line in the file.
75  */
76 public void jump_forw_buffered(void)
77 {
78 	POSITION end;
79 
80 	if (ch_end_buffer_seek())
81 	{
82 		error(LM(Cannot_seek_to_end_of_buffers), NULL_PARG);
83 		return;
84 	}
85 	end = ch_tell();
86 	if (end != NULL_POSITION && end > 0)
87 		jump_line_loc(end-1, sc_height-1);
88 }
89 
90 /*
91  * Jump to line n in the file.
92  */
93 public void jump_back(LINENUM linenum)
94 {
95 	POSITION pos;
96 	PARG parg;
97 
98 	/*
99 	 * Find the position of the specified line.
100 	 * If we can seek there, just jump to it.
101 	 * If we can't seek, but we're trying to go to line number 1,
102 	 * use ch_beg_seek() to get as close as we can.
103 	 */
104 	pos = find_pos(linenum);
105 	if (pos != NULL_POSITION && ch_seek(pos) == 0)
106 	{
107 		if (show_attn)
108 			set_attnpos(pos);
109 		jump_loc(pos, jump_sline);
110 	} else if (linenum <= 1 && ch_beg_seek() == 0)
111 	{
112 		jump_loc(ch_tell(), jump_sline);
113 		error(LM(Cannot_seek_to_beginning_of_file), NULL_PARG);
114 	} else
115 	{
116 		parg.p_linenum = linenum;
117 		error(LM(Cannot_seek_to_line_number_X), &parg);
118 	}
119 }
120 
121 /*
122  * Repaint the screen.
123  */
124 public void repaint(void)
125 {
126 	struct scrpos scrpos;
127 	/*
128 	 * Start at the line currently at the top of the screen
129 	 * and redisplay the screen.
130 	 */
131 	get_scrpos(&scrpos, TOP);
132 	pos_clear();
133 	if (scrpos.pos == NULL_POSITION)
134 		/* Screen hasn't been drawn yet. */
135 		jump_loc(ch_zero(), 1);
136 	else
137 		jump_loc(scrpos.pos, scrpos.ln);
138 }
139 
140 /*
141  * Jump to a specified percentage into the file.
142  */
143 public void jump_percent(int percent, long fraction)
144 {
145 	POSITION pos, len;
146 
147 	/*
148 	 * Determine the position in the file
149 	 * (the specified percentage of the file's length).
150 	 */
151 	if ((len = ch_length()) == NULL_POSITION)
152 	{
153 		ierror(LM(Determining_length_of_file), NULL_PARG);
154 		ch_end_seek();
155 	}
156 	if ((len = ch_length()) == NULL_POSITION)
157 	{
158 		error(LM(Dont_know_length_of_file), NULL_PARG);
159 		return;
160 	}
161 	pos = percent_pos(len, percent, fraction);
162 	if (pos >= len)
163 		pos = len-1;
164 
165 	jump_line_loc(pos, jump_sline);
166 }
167 
168 /*
169  * Jump to a specified position in the file.
170  * Like jump_loc, but the position need not be
171  * the first character in a line.
172  */
173 public void jump_line_loc(POSITION pos, int sline)
174 {
175 	int c;
176 
177 	if (ch_seek(pos) == 0)
178 	{
179 		/*
180 		 * Back up to the beginning of the line.
181 		 */
182 		while ((c = ch_back_get()) != '\n' && c != EOI)
183 			;
184 		if (c == '\n')
185 			(void) ch_forw_get();
186 		pos = ch_tell();
187 	}
188 	if (show_attn)
189 		set_attnpos(pos);
190 	jump_loc(pos, sline);
191 }
192 
193 static void after_header_message(void)
194 {
195 #if HAVE_TIME
196 #define MSG_FREQ 1 /* seconds */
197 	static time_type last_msg = (time_type) 0;
198 	time_type now = get_time();
199 	if (now < last_msg + MSG_FREQ)
200 		return;
201 	last_msg = now;
202 #endif
203 	lbell();
204 	/* {{ This message displays before the file text is updated, which is not a good UX. }} */
205 	/** error("Cannot display text before header; use --header=- to disable header", NULL_PARG); */
206 }
207 
208 /*
209  * Ensure that a position is not before the header.
210  * If it is, print a message and return the position of the start of the header.
211  * {{ This is probably not being used correctly in all cases.
212  *    It does not account for the location of pos on the screen,
213  *    so lines before pos could be displayed. }}
214  */
215 public POSITION after_header_pos(POSITION pos)
216 {
217 	if (header_start_pos != NULL_POSITION && pos < header_start_pos)
218 	{
219 		after_header_message();
220 		pos = header_start_pos;
221 	}
222 	return pos;
223 }
224 
225 /*
226  * Jump to a specified position in the file.
227  * The position must be the first character in a line.
228  * Place the target line on a specified line on the screen.
229  */
230 public void jump_loc(POSITION pos, int sline)
231 {
232 	int nline;
233 	int sindex;
234 	POSITION tpos;
235 	POSITION bpos;
236 
237 	/*
238 	 * Normalize sline.
239 	 */
240 	pos = after_header_pos(pos);
241 	pos = next_unfiltered(pos);
242 	sindex = sindex_from_sline(sline);
243 
244 	if (!full_screen && !(quit_if_one_screen && one_screen))
245 	{
246 		/* If not full screen, can't rely on scrolling logic below, since
247 		 * "scrolling" may just print lines in the unused part of the screen. */
248 		pos_clear();
249 		lclear();
250 	}
251 
252 	if ((nline = onscreen(pos)) >= 0)
253 	{
254 		/*
255 		 * The line is currently displayed.
256 		 * Just scroll there.
257 		 */
258 		nline -= sindex;
259 		if (nline > 0)
260 			forw(nline, position(BOTTOM_PLUS_ONE), TRUE, FALSE, FALSE, FALSE, 0);
261 		else
262 			back(-nline, position(TOP), TRUE, FALSE, FALSE, FALSE);
263 #if HILITE_SEARCH
264 		if (show_attn)
265 			repaint_hilite(TRUE);
266 #endif
267 		return;
268 	}
269 
270 	/*
271 	 * Line is not on screen.
272 	 * Seek to the desired location.
273 	 */
274 	if (ch_seek(pos))
275 	{
276 		error(LM(Cannot_seek_to_that_file_position), NULL_PARG);
277 		return;
278 	}
279 
280 	/*
281 	 * See if the desired line is before or after
282 	 * the currently displayed screen.
283 	 */
284 	tpos = position(TOP);
285 	bpos = position(BOTTOM_PLUS_ONE);
286 	if (tpos == NULL_POSITION || pos >= tpos)
287 	{
288 		/*
289 		 * The desired line is after the current screen.
290 		 * Move back in the file far enough so that we can
291 		 * call forw() and put the desired line at the
292 		 * sline-th line on the screen.
293 		 */
294 		for (nline = 0;  nline < sindex;  nline++)
295 		{
296 			if (bpos != NULL_POSITION && pos <= bpos)
297 			{
298 				/*
299 				 * Surprise!  The desired line is
300 				 * close enough to the current screen
301 				 * that we can just scroll there after all.
302 				 */
303 				forw(sc_height-sindex+nline-1, bpos, TRUE, FALSE, FALSE, FALSE, 0);
304 #if HILITE_SEARCH
305 				if (show_attn)
306 					repaint_hilite(TRUE);
307 #endif
308 				return;
309 			}
310 			pos = back_line(pos, NULL, NULL);
311 			if (pos == NULL_POSITION)
312 			{
313 				/*
314 				 * Oops.  Ran into the beginning of the file.
315 				 * Exit the loop here and rely on forw()
316 				 * below to draw the required number of
317 				 * blank lines at the top of the screen.
318 				 */
319 				break;
320 			}
321 		}
322 		lastmark();
323 		squished = FALSE;
324 		screen_trashed_num(0);
325 		forw(sc_height-1, pos, TRUE, FALSE, FALSE, FALSE, sindex-nline);
326 	} else
327 	{
328 		/*
329 		 * The desired line is before the current screen.
330 		 * Move forward in the file far enough so that we
331 		 * can call back() and put the desired line at the
332 		 * sindex-th line on the screen.
333 		 */
334 		for (nline = sindex;  nline < sc_height - 1;  nline++)
335 		{
336 			POSITION linepos;
337 			pos = forw_line(pos, &linepos, NULL);
338 			if (pos == NULL_POSITION)
339 			{
340 				/*
341 				 * Ran into end of file.
342 				 * This shouldn't normally happen,
343 				 * but may if there is some kind of read error.
344 				 */
345 				break;
346 			}
347 			if (linepos >= tpos)
348 			{
349 				/*
350 				 * Surprise!  The desired line is
351 				 * close enough to the current screen
352 				 * that we can just scroll there after all.
353 				 */
354 				back(nline, tpos, TRUE, FALSE, FALSE, FALSE);
355 #if HILITE_SEARCH
356 				if (show_attn)
357 					repaint_hilite(TRUE);
358 #endif
359 				return;
360 			}
361 		}
362 		lastmark();
363 		if (!top_scroll)
364 			lclear();
365 		else
366 			home();
367 		screen_trashed_num(0);
368 		add_back_pos(pos);
369 		back(sc_height-1, pos, TRUE, FALSE, FALSE, FALSE);
370 	}
371 }
372