/*1* Copyright (C) 1984-2025 Mark Nudelman2*3* You may distribute under the terms of either the GNU General Public4* License or the Less License, as specified in the README file.5*6* For more information, see the README file.7*/8910/*11* Primitives for displaying the file on the screen,12* scrolling either forward or backward.13*/1415#include "less.h"16#include "position.h"1718extern int less_is_more;1920public lbool squished;21public int no_back_scroll = 0;22public int forw_prompt;23public lbool first_time = TRUE; /* We're printing the first screen of output */24public int shell_lines = 1;25/* soft_eof is set as end-of-file when a read attempt returns EOF. This can26* differ from actual EOF (ch_length()) if & filtering is in effect. */27public POSITION soft_eof = NULL_POSITION;2829extern int sigs;30extern int top_scroll;31extern int quiet;32extern int sc_width, sc_height;33extern int hshift;34extern int auto_wrap;35extern lbool plusoption;36extern int forw_scroll;37extern int back_scroll;38extern lbool ignore_eoi;39extern int header_lines;40extern int header_cols;41extern int full_screen;42extern int stop_on_form_feed;43extern POSITION header_start_pos;44extern lbool getting_one_screen;45#if HILITE_SEARCH46extern int hilite_search;47extern int status_col;48#endif49#if TAGS50extern char *tagoption;51#endif5253/*54* Sound the bell to indicate user is trying to move past end of file.55*/56public void eof_bell(void)57{58#if HAVE_TIME59{60static time_type last_eof_bell = 0;61time_type now = get_time();62if (now == last_eof_bell) /* max once per second */63return;64last_eof_bell = now;65}66#endif67if (quiet == NOT_QUIET)68lbell();69else70vbell();71}7273/*74* Check to see if the end of file is currently displayed.75*/76public lbool eof_displayed(lbool offset)77{78POSITION pos;7980if (ignore_eoi)81return (FALSE);8283if (ch_length() == NULL_POSITION)84/*85* If the file length is not known,86* we can't possibly be displaying EOF.87*/88return (FALSE);8990/*91* If the bottom line is empty, we are at EOF.92* If the bottom line ends at the file length,93* we must be just at EOF.94*/95pos = position(offset ? BOTTOM_OFFSET : BOTTOM_PLUS_ONE);96return (pos == NULL_POSITION || pos == ch_length() || pos == soft_eof);97}9899/*100* Check to see if the entire file is currently displayed.101*/102public lbool entire_file_displayed(void)103{104POSITION pos;105106/* Make sure last line of file is displayed. */107if (!eof_displayed(TRUE))108return (FALSE);109110/* Make sure first line of file is displayed. */111pos = position(0);112return (pos == NULL_POSITION || pos == 0);113}114115/*116* If the screen is "squished", repaint it.117* "Squished" means the first displayed line is not at the top118* of the screen; this can happen when we display a short file119* for the first time.120*/121public void squish_check(void)122{123if (!squished)124return;125squished = FALSE;126repaint();127}128129/*130* Read the first pfx columns of the next line.131* If skipeol==0 stop there, otherwise read and discard chars to end of line.132*/133static POSITION forw_line_pfx(POSITION pos, int pfx, int skipeol)134{135int save_sc_width = sc_width;136int save_auto_wrap = auto_wrap;137int save_hshift = hshift;138/* Set fake sc_width to force only pfx chars to be read. */139sc_width = pfx + line_pfx_width();140auto_wrap = 0;141hshift = 0;142pos = forw_line_seg(pos, skipeol, FALSE, FALSE, NULL, NULL);143sc_width = save_sc_width;144auto_wrap = save_auto_wrap;145hshift = save_hshift;146return pos;147}148149/*150* Set header text color.151* Underline last line of headers, but not at header_start_pos152* (where there is no gap between the last header line and the next line).153*/154static void set_attr_header(int ln)155{156set_attr_line(AT_COLOR_HEADER);157if (ln+1 == header_lines && position(0) != header_start_pos)158set_attr_line(AT_UNDERLINE);159}160161/*162* Display file headers, overlaying text already drawn163* at top and left of screen.164*/165public int overlay_header(void)166{167int ln;168lbool moved = FALSE;169170if (header_lines > 0)171{172/* Draw header_lines lines from start of file at top of screen. */173POSITION pos = header_start_pos;174home();175for (ln = 0; ln < header_lines; ++ln)176{177pos = forw_line(pos, NULL, NULL);178set_attr_header(ln);179clear_eol();180put_line(FALSE);181}182moved = TRUE;183}184if (header_cols > 0)185{186/* Draw header_cols columns at left of each line. */187POSITION pos = header_start_pos;188home();189for (ln = 0; ln < sc_height-1; ++ln)190{191if (ln >= header_lines) /* switch from header lines to normal lines */192pos = position(ln);193if (pos == NULL_POSITION)194putchr('\n');195else196{197/* Need skipeol for all header lines except the last one. */198pos = forw_line_pfx(pos, header_cols, ln+1 < header_lines);199set_attr_header(ln);200put_line(FALSE);201}202}203moved = TRUE;204}205if (moved)206lower_left();207return moved;208}209210/*211* Display n lines, scrolling forward,212* starting at position pos in the input file.213* "force" means display the n lines even if we hit end of file.214* "only_last" means display only the last screenful if n > screen size.215* "nblank" is the number of blank lines to draw before the first216* real line. If nblank > 0, the pos must be NULL_POSITION.217* The first real line after the blanks will start at ch_zero().218* "to_newline" means count file lines rather than screen lines.219*/220public void forw(int n, POSITION pos, lbool force, lbool only_last, lbool to_newline, lbool do_stop_on_form_feed, int nblank)221{222int nlines = 0;223lbool do_repaint;224lbool newline;225lbool first_line = TRUE;226lbool need_home = FALSE;227228if (pos != NULL_POSITION)229pos = after_header_pos(pos);230squish_check();231232/*233* do_repaint tells us not to display anything till the end,234* then just repaint the entire screen.235* We repaint if we are supposed to display only the last236* screenful and the request is for more than a screenful.237* Also if the request exceeds the forward scroll limit238* (but not if the request is for exactly a screenful, since239* repainting itself involves scrolling forward a screenful).240*/241do_repaint = (only_last && n > sc_height-1) ||242(forw_scroll >= 0 && n > forw_scroll && n != sc_height-1);243if (!do_repaint)244{245if (top_scroll && n >= sc_height - 1 && pos != ch_length())246{247/*248* Start a new screen.249* {{ This is not really desirable if we happen250* to hit eof in the middle of this screen,251* but we don't yet know if that will happen. }}252*/253pos_clear();254force = TRUE;255need_home = (less_is_more == 0) ? TRUE : FALSE;256}257258if (pos != position(BOTTOM_PLUS_ONE) || empty_screen())259{260/*261* This is not contiguous with what is262* currently displayed. Clear the screen image263* (position table) and start a new screen.264*/265pos_clear();266force = TRUE;267if (top_scroll)268{269need_home = TRUE;270} else if (!first_time && !is_filtering() && full_screen)271{272putstr("...skipping...\n");273}274}275}276277while (--n >= 0)278{279POSITION linepos = NULL_POSITION;280/*281* Read the next line of input.282*/283if (nblank > 0)284{285/*286* Still drawing blanks; don't get a line287* from the file yet.288* If this is the last blank line, get ready to289* read a line starting at ch_zero() next time.290*/291if (--nblank == 0)292pos = ch_zero();293} else294{295/*296* Get the next line from the file.297*/298POSITION opos = pos;299pos = forw_line(pos, &linepos, &newline);300if (to_newline && !newline)301++n;302if (pos == NULL_POSITION)303{304/*305* End of file: stop here unless the top line306* is still empty, or "force" is true.307* Even if force is true, stop when the last308* line in the file reaches the top of screen.309*/310soft_eof = opos;311linepos = opos;312if (ABORT_SIGS() ||313(!force && position(TOP) != NULL_POSITION) ||314(!empty_lines(0, 0) && !empty_lines(1, 1) && empty_lines(2, sc_height-1)))315{316pos = opos;317break;318}319}320}321/*322* Add the position of the next line to the position table.323* Display the current line on the screen.324*/325add_forw_pos(linepos, first_line);326first_line = FALSE;327nlines++;328if (do_repaint)329continue;330if (need_home)331{332lclear();333home();334need_home = FALSE;335}336/*337* If this is the first screen displayed and338* we hit an early EOF (i.e. before the requested339* number of lines), we "squish" the display down340* at the bottom of the screen.341* But don't do this if a + option or a -t option342* was given. These options can cause us to343* start the display after the beginning of the file,344* and it is not appropriate to squish in that case.345*/346if ((first_time || less_is_more) &&347pos == NULL_POSITION && !top_scroll &&348header_lines == 0 && header_cols == 0 &&349#if TAGS350tagoption == NULL &&351#endif352!plusoption)353{354squished = TRUE;355continue;356}357put_line(TRUE);358if (do_stop_on_form_feed && !do_repaint && line_is_ff() && position(TOP) != NULL_POSITION)359break;360forw_prompt = 1;361}362if (!first_line)363add_forw_pos(pos, FALSE);364if (nlines == 0 && !ignore_eoi)365eof_bell();366else if (do_repaint)367repaint();368else369{370overlay_header();371/* lower_left(); {{ considered harmful? }} */372}373first_time = FALSE;374(void) currline(BOTTOM);375}376377/*378* Display n lines, scrolling backward.379*/380public void back(int n, POSITION pos, lbool force, lbool only_last, lbool to_newline, lbool do_stop_on_form_feed)381{382int nlines = 0;383lbool do_repaint;384lbool newline;385386squish_check();387do_repaint = (n > get_back_scroll() || (only_last && n > sc_height-1) || header_lines > 0);388389while (--n >= 0)390{391/*392* Get the previous line of input.393*/394pos = back_line(pos, &newline);395if (to_newline && !newline)396++n;397if (pos == NULL_POSITION)398{399/*400* Beginning of file: stop here unless "force" is true.401*/402if (!force)403break;404}405if (pos != after_header_pos(pos))406{407/*408* Don't allow scrolling back to before the current header line.409*/410break;411}412/*413* Add the position of the previous line to the position table.414* Display the line on the screen.415*/416add_back_pos(pos);417nlines++;418if (!do_repaint)419{420home();421add_line();422put_line(FALSE);423if (do_stop_on_form_feed && line_is_ff())424break;425}426}427if (nlines == 0)428eof_bell();429else if (do_repaint)430repaint();431else432{433overlay_header();434lower_left();435}436(void) currline(BOTTOM);437}438439/*440* Display n more lines, forward.441* Start just after the line currently displayed at the bottom of the screen.442*/443public void forward(int n, lbool force, lbool only_last, lbool to_newline)444{445POSITION pos;446447if (get_quit_at_eof() && eof_displayed(FALSE) && !(ch_getflags() & CH_HELPFILE))448{449/*450* If the -e flag is set and we're trying to go451* forward from end-of-file, go on to the next file.452*/453if (edit_next(1))454quit(QUIT_OK);455return;456}457458pos = position(BOTTOM_PLUS_ONE);459if (pos == NULL_POSITION && (!force || empty_lines(2, sc_height-1)))460{461if (ignore_eoi)462{463/*464* ignore_eoi is to support A_F_FOREVER.465* Back up until there is a line at the bottom466* of the screen.467*/468if (empty_screen())469pos = ch_zero();470else471{472do473{474back(1, position(TOP), TRUE, FALSE, FALSE, stop_on_form_feed);475pos = position(BOTTOM_PLUS_ONE);476} while (pos == NULL_POSITION && !ABORT_SIGS());477}478} else479{480eof_bell();481return;482}483}484forw(n, pos, force, only_last, to_newline, stop_on_form_feed, 0);485}486487/*488* Display n more lines, backward.489* Start just before the line currently displayed at the top of the screen.490*/491public void backward(int n, lbool force, lbool only_last, lbool to_newline)492{493POSITION pos;494495pos = position(TOP);496if (pos == NULL_POSITION && (!force || position(BOTTOM) == 0))497{498eof_bell();499return;500}501back(n, pos, force, only_last, to_newline, stop_on_form_feed);502}503504/*505* Get the backwards scroll limit.506* Must call this function instead of just using the value of507* back_scroll, because the default case depends on sc_height and508* top_scroll, as well as back_scroll.509*/510public int get_back_scroll(void)511{512if (no_back_scroll)513return (0);514if (back_scroll >= 0)515return (back_scroll);516if (top_scroll)517return (sc_height - 2);518return (10000); /* infinity */519}520521/*522* Will the entire file fit on one screen?523*/524public lbool get_one_screen(void)525{526int nlines;527POSITION pos = ch_zero();528lbool ret = FALSE;529530/* Disable polling until we know whether we will exit early due to -F. */531getting_one_screen = TRUE;532for (nlines = 0; nlines + shell_lines <= sc_height; nlines++)533{534pos = forw_line(pos, NULL, NULL);535if (ABORT_SIGS())536break;537if (pos == NULL_POSITION)538{539ret = TRUE;540break;541}542}543getting_one_screen = FALSE;544return ret;545}546547548