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