Home | History | Annotate | Line # | Download | only in dist
      1 /*	$NetBSD: input.c,v 1.4 2023/10/06 05:49:49 simonb Exp $	*/
      2 
      3 /*
      4  * Copyright (C) 1984-2023  Mark Nudelman
      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 hshift;
     26 extern int quit_if_one_screen;
     27 extern int sigs;
     28 extern int ignore_eoi;
     29 extern int status_col;
     30 extern int wordwrap;
     31 extern POSITION start_attnpos;
     32 extern POSITION end_attnpos;
     33 #if HILITE_SEARCH
     34 extern int hilite_search;
     35 extern int size_linebuf;
     36 extern int show_attn;
     37 #endif
     38 
     39 /*
     40  * Set the status column.
     41  *  base  Position of first char in line.
     42  *  disp  First visible char.
     43  *        Different than base_pos if line is shifted.
     44  *  edisp Last visible char.
     45  *  eol   End of line. Normally the newline.
     46  *        Different than edisp if line is chopped.
     47  */
     48 static void init_status_col(POSITION base_pos, POSITION disp_pos, POSITION edisp_pos, POSITION eol_pos)
     49 {
     50 	int hl_before = (chop_line() && disp_pos != NULL_POSITION) ?
     51 	    is_hilited_attr(base_pos, disp_pos, TRUE, NULL) : 0;
     52 	int hl_after = (chop_line()) ?
     53 	    is_hilited_attr(edisp_pos, eol_pos, TRUE, NULL) : 0;
     54 	int attr;
     55 	char ch;
     56 
     57 	if (hl_before && hl_after)
     58 	{
     59 		attr = hl_after;
     60 		ch = '=';
     61 	} else if (hl_before)
     62 	{
     63 		attr = hl_before;
     64 		ch = '<';
     65 	} else if (hl_after)
     66 	{
     67 		attr = hl_after;
     68 		ch = '>';
     69 	} else
     70 	{
     71 		attr = is_hilited_attr(base_pos, eol_pos, TRUE, NULL);
     72 		ch = '*';
     73 	}
     74 	if (attr)
     75 		set_status_col(ch, attr);
     76 }
     77 
     78 /*
     79  * Get the next line.
     80  * A "current" position is passed and a "new" position is returned.
     81  * The current position is the position of the first character of
     82  * a line.  The new position is the position of the first character
     83  * of the NEXT line.  The line obtained is the line starting at curr_pos.
     84  */
     85 public POSITION forw_line_seg(POSITION curr_pos, int skipeol, int rscroll, int nochop)
     86 {
     87 	POSITION base_pos;
     88 	POSITION new_pos;
     89 	POSITION edisp_pos;
     90 	int c;
     91 	int blankline;
     92 	int endline;
     93 	int chopped;
     94 	int backchars;
     95 	POSITION wrap_pos;
     96 	int skipped_leading;
     97 
     98 get_forw_line:
     99 	if (curr_pos == NULL_POSITION)
    100 	{
    101 		null_line();
    102 		return (NULL_POSITION);
    103 	}
    104 #if HILITE_SEARCH
    105 	if (hilite_search == OPT_ONPLUS || is_filtering() || status_col)
    106 	{
    107 		/*
    108 		 * If we are ignoring EOI (command F), only prepare
    109 		 * one line ahead, to avoid getting stuck waiting for
    110 		 * slow data without displaying the data we already have.
    111 		 * If we're not ignoring EOI, we *could* do the same, but
    112 		 * for efficiency we prepare several lines ahead at once.
    113 		 */
    114 		prep_hilite(curr_pos, curr_pos + 3*size_linebuf,
    115 				ignore_eoi ? 1 : -1);
    116 		curr_pos = next_unfiltered(curr_pos);
    117 	}
    118 #endif
    119 	if (ch_seek(curr_pos))
    120 	{
    121 		null_line();
    122 		return (NULL_POSITION);
    123 	}
    124 
    125 	/*
    126 	 * Step back to the beginning of the line.
    127 	 */
    128 	base_pos = curr_pos;
    129 	for (;;)
    130 	{
    131 		if (ABORT_SIGS())
    132 		{
    133 			null_line();
    134 			return (NULL_POSITION);
    135 		}
    136 		c = ch_back_get();
    137 		if (c == EOI)
    138 			break;
    139 		if (c == '\n')
    140 		{
    141 			(void) ch_forw_get();
    142 			break;
    143 		}
    144 		--base_pos;
    145 	}
    146 
    147 	/*
    148 	 * Read forward again to the position we should start at.
    149 	 */
    150 	prewind();
    151 	plinestart(base_pos);
    152 	(void) ch_seek(base_pos);
    153 	new_pos = base_pos;
    154 	while (new_pos < curr_pos)
    155 	{
    156 		if (ABORT_SIGS())
    157 		{
    158 			null_line();
    159 			return (NULL_POSITION);
    160 		}
    161 		c = ch_forw_get();
    162 		backchars = pappend(c, new_pos);
    163 		new_pos++;
    164 		if (backchars > 0)
    165 		{
    166 			pshift_all();
    167 			if (wordwrap && (c == ' ' || c == '\t'))
    168 			{
    169 				do
    170 				{
    171 					new_pos++;
    172 					c = ch_forw_get();
    173 				} while (c == ' ' || c == '\t');
    174 				backchars = 1;
    175 			}
    176 			new_pos -= backchars;
    177 			while (--backchars >= 0)
    178 				(void) ch_back_get();
    179 		}
    180 	}
    181 	(void) pflushmbc();
    182 	pshift_all();
    183 
    184 	/*
    185 	 * Read the first character to display.
    186 	 */
    187 	c = ch_forw_get();
    188 	if (c == EOI)
    189 	{
    190 		null_line();
    191 		return (NULL_POSITION);
    192 	}
    193 	blankline = (c == '\n' || c == '\r');
    194 	wrap_pos = NULL_POSITION;
    195 	skipped_leading = FALSE;
    196 
    197 	/*
    198 	 * Read each character in the line and append to the line buffer.
    199 	 */
    200 	chopped = FALSE;
    201 	for (;;)
    202 	{
    203 		if (ABORT_SIGS())
    204 		{
    205 			null_line();
    206 			return (NULL_POSITION);
    207 		}
    208 		if (c == '\n' || c == EOI)
    209 		{
    210 			/*
    211 			 * End of the line.
    212 			 */
    213 			backchars = pflushmbc();
    214 			new_pos = ch_tell();
    215 			if (backchars > 0 && (nochop || !chop_line()) && hshift == 0)
    216 			{
    217 				new_pos -= backchars + 1;
    218 				endline = FALSE;
    219 			} else
    220 				endline = TRUE;
    221 			edisp_pos = new_pos;
    222 			break;
    223 		}
    224 		if (c != '\r')
    225 			blankline = 0;
    226 
    227 		/*
    228 		 * Append the char to the line and get the next char.
    229 		 */
    230 		backchars = pappend(c, ch_tell()-1);
    231 		if (backchars > 0)
    232 		{
    233 			/*
    234 			 * The char won't fit in the line; the line
    235 			 * is too long to print in the screen width.
    236 			 * End the line here.
    237 			 */
    238 			if (skipeol)
    239 			{
    240 				/* Read to end of line. */
    241 				edisp_pos = ch_tell();
    242 				do
    243 				{
    244 					if (ABORT_SIGS())
    245 					{
    246 						null_line();
    247 						return (NULL_POSITION);
    248 					}
    249 					c = ch_forw_get();
    250 				} while (c != '\n' && c != EOI);
    251 				new_pos = ch_tell();
    252 				endline = TRUE;
    253 				quit_if_one_screen = FALSE;
    254 				chopped = TRUE;
    255 			} else
    256 			{
    257 				if (!wordwrap)
    258 					new_pos = ch_tell() - backchars;
    259 				else
    260 				{
    261 					/*
    262 					 * We're word-wrapping, so go back to the last space.
    263 					 * However, if it's the space itself that couldn't fit,
    264 					 * simply ignore it and any subsequent spaces.
    265 					 */
    266 					if (c == ' ' || c == '\t')
    267 					{
    268 						do
    269 						{
    270 							new_pos = ch_tell();
    271 							c = ch_forw_get();
    272 						} while (c == ' ' || c == '\t');
    273 						if (c == '\r')
    274 							c = ch_forw_get();
    275 						if (c == '\n')
    276 							new_pos = ch_tell();
    277 					} else if (wrap_pos == NULL_POSITION)
    278 						new_pos = ch_tell() - backchars;
    279 					else
    280 					{
    281 						new_pos = wrap_pos;
    282 						loadc();
    283 					}
    284 				}
    285 				endline = FALSE;
    286 			}
    287 			break;
    288 		}
    289 		if (wordwrap)
    290 		{
    291 			if (c == ' ' || c == '\t')
    292 			{
    293 				if (skipped_leading)
    294 				{
    295 					wrap_pos = ch_tell();
    296 					savec();
    297 				}
    298 			} else
    299 				skipped_leading = TRUE;
    300 		}
    301 		c = ch_forw_get();
    302 	}
    303 
    304 #if HILITE_SEARCH
    305 	if (blankline && show_attn)
    306 	{
    307 		/* Add spurious space to carry possible attn hilite. */
    308 		pappend(' ', ch_tell()-1);
    309 	}
    310 #endif
    311 	pdone(endline, rscroll && chopped, 1);
    312 
    313 #if HILITE_SEARCH
    314 	if (is_filtered(base_pos))
    315 	{
    316 		/*
    317 		 * We don't want to display this line.
    318 		 * Get the next line.
    319 		 */
    320 		curr_pos = new_pos;
    321 		goto get_forw_line;
    322 	}
    323 	if (status_col)
    324 		init_status_col(base_pos, line_position(), edisp_pos, new_pos);
    325 #endif
    326 
    327 	if (squeeze && blankline)
    328 	{
    329 		/*
    330 		 * This line is blank.
    331 		 * Skip down to the last contiguous blank line
    332 		 * and pretend it is the one which we are returning.
    333 		 */
    334 		while ((c = ch_forw_get()) == '\n' || c == '\r')
    335 			if (ABORT_SIGS())
    336 			{
    337 				null_line();
    338 				return (NULL_POSITION);
    339 			}
    340 		if (c != EOI)
    341 			(void) ch_back_get();
    342 		new_pos = ch_tell();
    343 	}
    344 
    345 	return (new_pos);
    346 }
    347 
    348 public POSITION forw_line(POSITION curr_pos)
    349 {
    350 
    351 	return forw_line_seg(curr_pos, (chop_line() || hshift > 0), TRUE, FALSE);
    352 }
    353 
    354 /*
    355  * Get the previous line.
    356  * A "current" position is passed and a "new" position is returned.
    357  * The current position is the position of the first character of
    358  * a line.  The new position is the position of the first character
    359  * of the PREVIOUS line.  The line obtained is the one starting at new_pos.
    360  */
    361 public POSITION back_line(POSITION curr_pos)
    362 {
    363 	POSITION base_pos;
    364 	POSITION new_pos;
    365 	POSITION edisp_pos;
    366 	POSITION begin_new_pos;
    367 	int c;
    368 	int endline;
    369 	int chopped;
    370 	int backchars;
    371 	POSITION wrap_pos;
    372 	int skipped_leading;
    373 
    374 get_back_line:
    375 	if (curr_pos == NULL_POSITION || curr_pos <= ch_zero())
    376 	{
    377 		null_line();
    378 		return (NULL_POSITION);
    379 	}
    380 #if HILITE_SEARCH
    381 	if (hilite_search == OPT_ONPLUS || is_filtering() || status_col)
    382 		prep_hilite((curr_pos < 3*size_linebuf) ?
    383 				0 : curr_pos - 3*size_linebuf, curr_pos, -1);
    384 #endif
    385 	if (ch_seek(curr_pos-1))
    386 	{
    387 		null_line();
    388 		return (NULL_POSITION);
    389 	}
    390 
    391 	if (squeeze)
    392 	{
    393 		/*
    394 		 * Find out if the "current" line was blank.
    395 		 */
    396 		(void) ch_forw_get();    /* Skip the newline */
    397 		c = ch_forw_get();       /* First char of "current" line */
    398 		(void) ch_back_get();    /* Restore our position */
    399 		(void) ch_back_get();
    400 
    401 		if (c == '\n' || c == '\r')
    402 		{
    403 			/*
    404 			 * The "current" line was blank.
    405 			 * Skip over any preceding blank lines,
    406 			 * since we skipped them in forw_line().
    407 			 */
    408 			while ((c = ch_back_get()) == '\n' || c == '\r')
    409 				if (ABORT_SIGS())
    410 				{
    411 					null_line();
    412 					return (NULL_POSITION);
    413 				}
    414 			if (c == EOI)
    415 			{
    416 				null_line();
    417 				return (NULL_POSITION);
    418 			}
    419 			(void) ch_forw_get();
    420 		}
    421 	}
    422 
    423 	/*
    424 	 * Scan backwards until we hit the beginning of the line.
    425 	 */
    426 	for (;;)
    427 	{
    428 		if (ABORT_SIGS())
    429 		{
    430 			null_line();
    431 			return (NULL_POSITION);
    432 		}
    433 		c = ch_back_get();
    434 		if (c == '\n')
    435 		{
    436 			/*
    437 			 * This is the newline ending the previous line.
    438 			 * We have hit the beginning of the line.
    439 			 */
    440 			base_pos = ch_tell() + 1;
    441 			break;
    442 		}
    443 		if (c == EOI)
    444 		{
    445 			/*
    446 			 * We have hit the beginning of the file.
    447 			 * This must be the first line in the file.
    448 			 * This must, of course, be the beginning of the line.
    449 			 */
    450 			base_pos = ch_tell();
    451 			break;
    452 		}
    453 	}
    454 
    455 	/*
    456 	 * Now scan forwards from the beginning of this line.
    457 	 * We keep discarding "printable lines" (based on screen width)
    458 	 * until we reach the curr_pos.
    459 	 *
    460 	 * {{ This algorithm is pretty inefficient if the lines
    461 	 *    are much longer than the screen width,
    462 	 *    but I don't know of any better way. }}
    463 	 */
    464 	new_pos = base_pos;
    465 	if (ch_seek(new_pos))
    466 	{
    467 		null_line();
    468 		return (NULL_POSITION);
    469 	}
    470 	endline = FALSE;
    471 	prewind();
    472 	plinestart(new_pos);
    473     loop:
    474 	wrap_pos = NULL_POSITION;
    475 	skipped_leading = FALSE;
    476 	begin_new_pos = new_pos;
    477 	(void) ch_seek(new_pos);
    478 	chopped = FALSE;
    479 
    480 	for (;;)
    481 	{
    482 		c = ch_forw_get();
    483 		if (c == EOI || ABORT_SIGS())
    484 		{
    485 			null_line();
    486 			return (NULL_POSITION);
    487 		}
    488 		new_pos++;
    489 		if (c == '\n')
    490 		{
    491 			backchars = pflushmbc();
    492 			if (backchars > 0 && !chop_line() && hshift == 0)
    493 			{
    494 				backchars++;
    495 				goto shift;
    496 			}
    497 			endline = TRUE;
    498 			edisp_pos = new_pos;
    499 			break;
    500 		}
    501 		backchars = pappend(c, ch_tell()-1);
    502 		if (backchars > 0)
    503 		{
    504 			/*
    505 			 * Got a full printable line, but we haven't
    506 			 * reached our curr_pos yet.  Discard the line
    507 			 * and start a new one.
    508 			 */
    509 			if (chop_line() || hshift > 0)
    510 			{
    511 				endline = TRUE;
    512 				chopped = TRUE;
    513 				quit_if_one_screen = FALSE;
    514 				edisp_pos = new_pos;
    515 				break;
    516 			}
    517 		shift:
    518 			if (!wordwrap)
    519 			{
    520 				pshift_all();
    521 				new_pos -= backchars;
    522 			} else
    523 			{
    524 				if (c == ' ' || c == '\t')
    525 				{
    526 					for (;;)
    527 					{
    528 						c = ch_forw_get();
    529 						if (c == ' ' || c == '\t')
    530 							new_pos++;
    531 						else
    532 						{
    533 							if (c == '\r')
    534 							{
    535 								c = ch_forw_get();
    536 								if (c == '\n')
    537 									new_pos++;
    538 							}
    539 							if (c == '\n')
    540 								new_pos++;
    541 							break;
    542 						}
    543 					}
    544 					if (new_pos >= curr_pos)
    545 						break;
    546 					pshift_all();
    547 				} else
    548 				{
    549 					pshift_all();
    550 					if (wrap_pos == NULL_POSITION)
    551 						new_pos -= backchars;
    552 					else
    553 						new_pos = wrap_pos;
    554 				}
    555 			}
    556 			goto loop;
    557 		}
    558 		if (wordwrap)
    559 		{
    560 			if (c == ' ' || c == '\t')
    561 			{
    562 				if (skipped_leading)
    563 					wrap_pos = new_pos;
    564 			} else
    565 				skipped_leading = TRUE;
    566 		}
    567 		if (new_pos >= curr_pos)
    568 		{
    569 			edisp_pos = new_pos;
    570 			break;
    571 		}
    572 	}
    573 
    574 	pdone(endline, chopped, 0);
    575 
    576 #if HILITE_SEARCH
    577 	if (is_filtered(base_pos))
    578 	{
    579 		/*
    580 		 * We don't want to display this line.
    581 		 * Get the previous line.
    582 		 */
    583 		curr_pos = begin_new_pos;
    584 		goto get_back_line;
    585 	}
    586 	if (status_col)
    587 		init_status_col(base_pos, line_position(), edisp_pos, new_pos);
    588 #endif
    589 
    590 	return (begin_new_pos);
    591 }
    592 
    593 /*
    594  * Set attnpos.
    595  */
    596 public void set_attnpos(POSITION pos)
    597 {
    598 	int c;
    599 
    600 	if (pos != NULL_POSITION)
    601 	{
    602 		if (ch_seek(pos))
    603 			return;
    604 		for (;;)
    605 		{
    606 			c = ch_forw_get();
    607 			if (c == EOI)
    608 				break;
    609 			if (c == '\n' || c == '\r')
    610 			{
    611 				(void) ch_back_get();
    612 				break;
    613 			}
    614 			pos++;
    615 		}
    616 		end_attnpos = pos;
    617 		for (;;)
    618 		{
    619 			c = ch_back_get();
    620 			if (c == EOI || c == '\n' || c == '\r')
    621 				break;
    622 			pos--;
    623 		}
    624 	}
    625 	start_attnpos = pos;
    626 }
    627