/*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 int 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)68bell();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, int nblank)221{222int nlines = 0;223lbool do_repaint;224lbool newline;225lbool first_line = TRUE;226227if (pos != NULL_POSITION)228pos = after_header_pos(pos);229squish_check();230231/*232* do_repaint tells us not to display anything till the end,233* then just repaint the entire screen.234* We repaint if we are supposed to display only the last235* screenful and the request is for more than a screenful.236* Also if the request exceeds the forward scroll limit237* (but not if the request is for exactly a screenful, since238* repainting itself involves scrolling forward a screenful).239*/240do_repaint = (only_last && n > sc_height-1) ||241(forw_scroll >= 0 && n > forw_scroll && n != sc_height-1);242if (!do_repaint)243{244if (top_scroll && n >= sc_height - 1 && pos != ch_length())245{246/*247* Start a new screen.248* {{ This is not really desirable if we happen249* to hit eof in the middle of this screen,250* but we don't yet know if that will happen. }}251*/252pos_clear();253force = TRUE;254if (less_is_more == 0) {255clear();256home();257}258}259260if (pos != position(BOTTOM_PLUS_ONE) || empty_screen())261{262/*263* This is not contiguous with what is264* currently displayed. Clear the screen image265* (position table) and start a new screen.266*/267pos_clear();268force = TRUE;269if (top_scroll)270{271clear();272home();273} else if (!first_time && !is_filtering() && full_screen)274{275putstr("...skipping...\n");276}277}278}279280while (--n >= 0)281{282POSITION linepos = NULL_POSITION;283/*284* Read the next line of input.285*/286if (nblank > 0)287{288/*289* Still drawing blanks; don't get a line290* from the file yet.291* If this is the last blank line, get ready to292* read a line starting at ch_zero() next time.293*/294if (--nblank == 0)295pos = ch_zero();296} else297{298/*299* Get the next line from the file.300*/301POSITION opos = pos;302pos = forw_line(pos, &linepos, &newline);303if (to_newline && !newline)304++n;305if (pos == NULL_POSITION)306{307/*308* End of file: stop here unless the top line309* is still empty, or "force" is true.310* Even if force is true, stop when the last311* line in the file reaches the top of screen.312*/313soft_eof = opos;314linepos = opos;315if (ABORT_SIGS() ||316(!force && position(TOP) != NULL_POSITION) ||317(!empty_lines(0, 0) && !empty_lines(1, 1) && empty_lines(2, sc_height-1)))318{319pos = opos;320break;321}322}323}324/*325* Add the position of the next line to the position table.326* Display the current line on the screen.327*/328add_forw_pos(linepos, first_line);329first_line = FALSE;330nlines++;331if (do_repaint)332continue;333/*334* If this is the first screen displayed and335* we hit an early EOF (i.e. before the requested336* number of lines), we "squish" the display down337* at the bottom of the screen.338* But don't do this if a + option or a -t option339* was given. These options can cause us to340* start the display after the beginning of the file,341* and it is not appropriate to squish in that case.342*/343if ((first_time || less_is_more) &&344pos == NULL_POSITION && !top_scroll &&345header_lines == 0 && header_cols == 0 &&346#if TAGS347tagoption == NULL &&348#endif349!plusoption)350{351squished = TRUE;352continue;353}354put_line(TRUE);355if (stop_on_form_feed && !do_repaint && line_is_ff() && position(TOP) != NULL_POSITION)356break;357forw_prompt = 1;358}359if (!first_line)360add_forw_pos(pos, FALSE);361if (nlines == 0 && !ignore_eoi)362eof_bell();363else if (do_repaint)364repaint();365else366{367overlay_header();368/* lower_left(); {{ considered harmful? }} */369}370first_time = FALSE;371(void) currline(BOTTOM);372}373374/*375* Display n lines, scrolling backward.376*/377public void back(int n, POSITION pos, lbool force, lbool only_last, lbool to_newline)378{379int nlines = 0;380lbool do_repaint;381lbool newline;382383squish_check();384do_repaint = (n > get_back_scroll() || (only_last && n > sc_height-1) || header_lines > 0);385386while (--n >= 0)387{388/*389* Get the previous line of input.390*/391pos = back_line(pos, &newline);392if (to_newline && !newline)393++n;394if (pos == NULL_POSITION)395{396/*397* Beginning of file: stop here unless "force" is true.398*/399if (!force)400break;401}402if (pos != after_header_pos(pos))403{404/*405* Don't allow scrolling back to before the current header line.406*/407break;408}409/*410* Add the position of the previous line to the position table.411* Display the line on the screen.412*/413add_back_pos(pos);414nlines++;415if (!do_repaint)416{417home();418add_line();419put_line(FALSE);420if (stop_on_form_feed && line_is_ff())421break;422}423}424if (nlines == 0)425eof_bell();426else if (do_repaint)427repaint();428else429{430overlay_header();431lower_left();432}433(void) currline(BOTTOM);434}435436/*437* Display n more lines, forward.438* Start just after the line currently displayed at the bottom of the screen.439*/440public void forward(int n, lbool force, lbool only_last, lbool to_newline)441{442POSITION pos;443444if (get_quit_at_eof() && eof_displayed(FALSE) && !(ch_getflags() & CH_HELPFILE))445{446/*447* If the -e flag is set and we're trying to go448* forward from end-of-file, go on to the next file.449*/450if (edit_next(1))451quit(QUIT_OK);452return;453}454455pos = position(BOTTOM_PLUS_ONE);456if (pos == NULL_POSITION && (!force || empty_lines(2, sc_height-1)))457{458if (ignore_eoi)459{460/*461* ignore_eoi is to support A_F_FOREVER.462* Back up until there is a line at the bottom463* of the screen.464*/465if (empty_screen())466pos = ch_zero();467else468{469do470{471back(1, position(TOP), TRUE, FALSE, FALSE);472pos = position(BOTTOM_PLUS_ONE);473} while (pos == NULL_POSITION && !ABORT_SIGS());474}475} else476{477eof_bell();478return;479}480}481forw(n, pos, force, only_last, to_newline, 0);482}483484/*485* Display n more lines, backward.486* Start just before the line currently displayed at the top of the screen.487*/488public void backward(int n, lbool force, lbool only_last, lbool to_newline)489{490POSITION pos;491492pos = position(TOP);493if (pos == NULL_POSITION && (!force || position(BOTTOM) == 0))494{495eof_bell();496return;497}498back(n, pos, force, only_last, to_newline);499}500501/*502* Get the backwards scroll limit.503* Must call this function instead of just using the value of504* back_scroll, because the default case depends on sc_height and505* top_scroll, as well as back_scroll.506*/507public int get_back_scroll(void)508{509if (no_back_scroll)510return (0);511if (back_scroll >= 0)512return (back_scroll);513if (top_scroll)514return (sc_height - 2);515return (10000); /* infinity */516}517518/*519* Will the entire file fit on one screen?520*/521public lbool get_one_screen(void)522{523int nlines;524POSITION pos = ch_zero();525lbool ret = FALSE;526527/* Disable polling until we know whether we will exit early due to -F. */528getting_one_screen = TRUE;529for (nlines = 0; nlines + shell_lines <= sc_height; nlines++)530{531pos = forw_line(pos, NULL, NULL);532if (ABORT_SIGS())533break;534if (pos == NULL_POSITION)535{536ret = TRUE;537break;538}539}540getting_one_screen = FALSE;541return ret;542}543544545