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