xref: /openbsd-src/usr.bin/less/input.c (revision c90a81c56dcebd6a1b73fe4aff9b03385b8e63b3)
1 /*
2  * Copyright (C) 1984-2012  Mark Nudelman
3  * Modified for use with illumos by Garrett D'Amore.
4  * Copyright 2014 Garrett D'Amore <garrett@damore.org>
5  *
6  * You may distribute under the terms of either the GNU General Public
7  * License or the Less License, as specified in the README file.
8  *
9  * For more information, see the README file.
10  */
11 
12 /*
13  * High level routines dealing with getting lines of input
14  * from the file being viewed.
15  *
16  * When we speak of "lines" here, we mean PRINTABLE lines;
17  * lines processed with respect to the screen width.
18  * We use the term "raw line" to refer to lines simply
19  * delimited by newlines; not processed with respect to screen width.
20  */
21 
22 #include "less.h"
23 
24 extern int squeeze;
25 extern int chopline;
26 extern int hshift;
27 extern int quit_if_one_screen;
28 extern volatile sig_atomic_t sigs;
29 extern int ignore_eoi;
30 extern int status_col;
31 extern off_t start_attnpos;
32 extern off_t end_attnpos;
33 extern int hilite_search;
34 extern int size_linebuf;
35 
36 /*
37  * Get the next line.
38  * A "current" position is passed and a "new" position is returned.
39  * The current position is the position of the first character of
40  * a line.  The new position is the position of the first character
41  * of the NEXT line.  The line obtained is the line starting at curr_pos.
42  */
43 off_t
44 forw_line(off_t curr_pos)
45 {
46 	off_t base_pos;
47 	off_t new_pos;
48 	int c;
49 	int blankline;
50 	int endline;
51 	int backchars;
52 
53 get_forw_line:
54 	if (curr_pos == -1) {
55 		null_line();
56 		return (-1);
57 	}
58 	if (hilite_search == OPT_ONPLUS || is_filtering() || status_col)
59 		/*
60 		 * If we are ignoring EOI (command F), only prepare
61 		 * one line ahead, to avoid getting stuck waiting for
62 		 * slow data without displaying the data we already have.
63 		 * If we're not ignoring EOI, we *could* do the same, but
64 		 * for efficiency we prepare several lines ahead at once.
65 		 */
66 		prep_hilite(curr_pos, curr_pos + 3*size_linebuf,
67 		    ignore_eoi ? 1 : -1);
68 	if (ch_seek(curr_pos)) {
69 		null_line();
70 		return (-1);
71 	}
72 
73 	/*
74 	 * Step back to the beginning of the line.
75 	 */
76 	base_pos = curr_pos;
77 	for (;;) {
78 		if (ABORT_SIGS()) {
79 			null_line();
80 			return (-1);
81 		}
82 		c = ch_back_get();
83 		if (c == EOI)
84 			break;
85 		if (c == '\n') {
86 			(void) ch_forw_get();
87 			break;
88 		}
89 		--base_pos;
90 	}
91 
92 	/*
93 	 * Read forward again to the position we should start at.
94 	 */
95 	prewind();
96 	plinenum(base_pos);
97 	(void) ch_seek(base_pos);
98 	new_pos = base_pos;
99 	while (new_pos < curr_pos) {
100 		if (ABORT_SIGS()) {
101 			null_line();
102 			return (-1);
103 		}
104 		c = ch_forw_get();
105 		backchars = pappend(c, new_pos);
106 		new_pos++;
107 		if (backchars > 0) {
108 			pshift_all();
109 			new_pos -= backchars;
110 			while (--backchars >= 0)
111 				(void) ch_back_get();
112 		}
113 	}
114 	(void) pflushmbc();
115 	pshift_all();
116 
117 	/*
118 	 * Read the first character to display.
119 	 */
120 	c = ch_forw_get();
121 	if (c == EOI) {
122 		null_line();
123 		return (-1);
124 	}
125 	blankline = (c == '\n' || c == '\r');
126 
127 	/*
128 	 * Read each character in the line and append to the line buffer.
129 	 */
130 	for (;;) {
131 		if (ABORT_SIGS()) {
132 			null_line();
133 			return (-1);
134 		}
135 		if (c == '\n' || c == EOI) {
136 			/*
137 			 * End of the line.
138 			 */
139 			backchars = pflushmbc();
140 			new_pos = ch_tell();
141 			if (backchars > 0 && !chopline && hshift == 0) {
142 				new_pos -= backchars + 1;
143 				endline = FALSE;
144 			} else
145 				endline = TRUE;
146 			break;
147 		}
148 		if (c != '\r')
149 			blankline = 0;
150 
151 		/*
152 		 * Append the char to the line and get the next char.
153 		 */
154 		backchars = pappend(c, ch_tell()-1);
155 		if (backchars > 0) {
156 			/*
157 			 * The char won't fit in the line; the line
158 			 * is too long to print in the screen width.
159 			 * End the line here.
160 			 */
161 			if (chopline || hshift > 0) {
162 				do {
163 					if (ABORT_SIGS()) {
164 						null_line();
165 						return (-1);
166 					}
167 					c = ch_forw_get();
168 				} while (c != '\n' && c != EOI);
169 				new_pos = ch_tell();
170 				endline = TRUE;
171 				quit_if_one_screen = FALSE;
172 			} else {
173 				new_pos = ch_tell() - backchars;
174 				endline = FALSE;
175 			}
176 			break;
177 		}
178 		c = ch_forw_get();
179 	}
180 
181 	pdone(endline, 1);
182 
183 	if (is_filtered(base_pos)) {
184 		/*
185 		 * We don't want to display this line.
186 		 * Get the next line.
187 		 */
188 		curr_pos = new_pos;
189 		goto get_forw_line;
190 	}
191 
192 	if (status_col && is_hilited(base_pos, ch_tell()-1, 1, NULL))
193 		set_status_col('*');
194 
195 	if (squeeze && blankline) {
196 		/*
197 		 * This line is blank.
198 		 * Skip down to the last contiguous blank line
199 		 * and pretend it is the one which we are returning.
200 		 */
201 		while ((c = ch_forw_get()) == '\n' || c == '\r')
202 			if (ABORT_SIGS()) {
203 				null_line();
204 				return (-1);
205 			}
206 		if (c != EOI)
207 			(void) ch_back_get();
208 		new_pos = ch_tell();
209 	}
210 
211 	return (new_pos);
212 }
213 
214 /*
215  * Get the previous line.
216  * A "current" position is passed and a "new" position is returned.
217  * The current position is the position of the first character of
218  * a line.  The new position is the position of the first character
219  * of the PREVIOUS line.  The line obtained is the one starting at new_pos.
220  */
221 off_t
222 back_line(off_t curr_pos)
223 {
224 	off_t new_pos, begin_new_pos, base_pos;
225 	int c;
226 	int endline;
227 	int backchars;
228 
229 get_back_line:
230 	if (curr_pos == -1 || curr_pos <= ch_zero()) {
231 		null_line();
232 		return (-1);
233 	}
234 	if (hilite_search == OPT_ONPLUS || is_filtering() || status_col)
235 		prep_hilite((curr_pos < 3*size_linebuf) ?
236 		    0 : curr_pos - 3*size_linebuf, curr_pos, -1);
237 	if (ch_seek(curr_pos-1)) {
238 		null_line();
239 		return (-1);
240 	}
241 
242 	if (squeeze) {
243 		/*
244 		 * Find out if the "current" line was blank.
245 		 */
246 		(void) ch_forw_get();	/* Skip the newline */
247 		c = ch_forw_get();	/* First char of "current" line */
248 		(void) ch_back_get();	/* Restore our position */
249 		(void) ch_back_get();
250 
251 		if (c == '\n' || c == '\r') {
252 			/*
253 			 * The "current" line was blank.
254 			 * Skip over any preceding blank lines,
255 			 * since we skipped them in forw_line().
256 			 */
257 			while ((c = ch_back_get()) == '\n' || c == '\r')
258 				if (ABORT_SIGS()) {
259 					null_line();
260 					return (-1);
261 				}
262 			if (c == EOI) {
263 				null_line();
264 				return (-1);
265 			}
266 			(void) ch_forw_get();
267 		}
268 	}
269 
270 	/*
271 	 * Scan backwards until we hit the beginning of the line.
272 	 */
273 	for (;;) {
274 		if (ABORT_SIGS()) {
275 			null_line();
276 			return (-1);
277 		}
278 		c = ch_back_get();
279 		if (c == '\n') {
280 			/*
281 			 * This is the newline ending the previous line.
282 			 * We have hit the beginning of the line.
283 			 */
284 			base_pos = ch_tell() + 1;
285 			break;
286 		}
287 		if (c == EOI) {
288 			/*
289 			 * We have hit the beginning of the file.
290 			 * This must be the first line in the file.
291 			 * This must, of course, be the beginning of the line.
292 			 */
293 			base_pos = ch_tell();
294 			break;
295 		}
296 	}
297 
298 	/*
299 	 * Now scan forwards from the beginning of this line.
300 	 * We keep discarding "printable lines" (based on screen width)
301 	 * until we reach the curr_pos.
302 	 *
303 	 * {{ This algorithm is pretty inefficient if the lines
304 	 *    are much longer than the screen width,
305 	 *    but I don't know of any better way. }}
306 	 */
307 	new_pos = base_pos;
308 	if (ch_seek(new_pos)) {
309 		null_line();
310 		return (-1);
311 	}
312 	endline = FALSE;
313 	prewind();
314 	plinenum(new_pos);
315 loop:
316 	begin_new_pos = new_pos;
317 	(void) ch_seek(new_pos);
318 
319 	do {
320 		c = ch_forw_get();
321 		if (c == EOI || ABORT_SIGS()) {
322 			null_line();
323 			return (-1);
324 		}
325 		new_pos++;
326 		if (c == '\n') {
327 			backchars = pflushmbc();
328 			if (backchars > 0 && !chopline && hshift == 0) {
329 				backchars++;
330 				goto shift;
331 			}
332 			endline = TRUE;
333 			break;
334 		}
335 		backchars = pappend(c, ch_tell()-1);
336 		if (backchars > 0) {
337 			/*
338 			 * Got a full printable line, but we haven't
339 			 * reached our curr_pos yet.  Discard the line
340 			 * and start a new one.
341 			 */
342 			if (chopline || hshift > 0) {
343 				endline = TRUE;
344 				quit_if_one_screen = FALSE;
345 				break;
346 			}
347 		shift:
348 			pshift_all();
349 			while (backchars-- > 0) {
350 				(void) ch_back_get();
351 				new_pos--;
352 			}
353 			goto loop;
354 		}
355 	} while (new_pos < curr_pos);
356 
357 	pdone(endline, 0);
358 
359 	if (is_filtered(base_pos)) {
360 		/*
361 		 * We don't want to display this line.
362 		 * Get the previous line.
363 		 */
364 		curr_pos = begin_new_pos;
365 		goto get_back_line;
366 	}
367 
368 	if (status_col && curr_pos > 0 &&
369 	    is_hilited(base_pos, curr_pos-1, 1, NULL))
370 		set_status_col('*');
371 
372 	return (begin_new_pos);
373 }
374 
375 /*
376  * Set attnpos.
377  */
378 void
379 set_attnpos(off_t pos)
380 {
381 	int c;
382 
383 	if (pos != -1) {
384 		if (ch_seek(pos))
385 			return;
386 		for (;;) {
387 			c = ch_forw_get();
388 			if (c == EOI)
389 				return;
390 			if (c != '\n' && c != '\r')
391 				break;
392 			pos++;
393 		}
394 	}
395 	start_attnpos = pos;
396 	for (;;) {
397 		c = ch_forw_get();
398 		pos++;
399 		if (c == EOI || c == '\n' || c == '\r')
400 			break;
401 	}
402 	end_attnpos = pos;
403 }
404