(*[72457,2131]
BROWSE.PAS                13-Sep-85 40270              286

    Keywords: IBM BROWSE VIDEO FILES

    This is a fast file browser for IBM PCs and true compatibles. It displays
    two separate files in independent windows. It has pop-up help, horizontal
    and vertical scrolling, unlimited file size, and can invoke a DOS process
    from within the browser. It requires Turbo 3.0 to compile, and works with
    either mono or c/g graphics cards.
*)

  {$C-}

PROGRAM browse;

    {*************************************************************************}
    {*         Copyright (c) Kim Kokkonen, TurboPower Software, 1985         *}
    {*  Released to the public domain for personal, non-commercial use only  *}
    {*************************************************************************}

    {.F-}
    {
    BROWSE through two files simultaneously in two independent windows.
    writes directly to video memory, but with timing interlocks for c/g card.
    keys are set up to resemble WordStar cursor movements.
    see GETKEY to do keyboard customization.
    requires Turbo version 3.0 to compile as it stands.
    set min/max heap=$200/$200 paragraphs.
    written 8/2/85, Kim Kokkonen, 408-378-3672

    FEATURES:
        filenames may include drive and path
        unlimited size files (or 32767 lines of 127 chars each, anyway)
        two circular RAM buffers each of 127 text lines paged from disk
        full forward and reverse scrolling at high speed
        horizontal scrolling
        synchronized windows mode
        pop-up help (type ^H for help)
        seek to line number
        search for strings (forward, uppercase modes only)
        invoke DOS process and return to place in BROWSE
          (requires ~96K free RAM at entry to BROWSE to invoke DOS)
    }
    {.F+}

  CONST
    linesize = 127;           {max line length stored}
    bufsize = 127;            {lines of text (-1) in each textbuffer}
    linavail = 32;            {lines to always keep available at top of buffer}
    slines = 11;              {number of lines (-1) in each window}
    windcolor = 15;           {video attribute to use in text windows}

  TYPE
    Window = 0..1;
    pathstring = STRING[64];
    textfile = Text[1024];
    linebuffer = STRING[linesize];
    bytebuffer = ARRAY[0..linesize] OF Byte;
    textbuffer = ARRAY[0..bufsize] OF linebuffer;
    twotext = ARRAY[Window] OF textfile;
    twobuffer = ARRAY[Window] OF textbuffer;
    twopath = ARRAY[Window] OF pathstring;
    twostate = ARRAY[Window] OF Boolean;
    twoint = ARRAY[Window] OF Integer;
    fullscreen = ARRAY[1..4000] OF Byte;
    regpack = RECORD
                CASE Integer OF
                  1 : (ax, bx, cx, dx, bp, si, di, ds, es, flags : Integer);
                  2 : (al, ah, bl, bh, cl, ch, dl, dh : Byte);
              END;

  VAR
    f : twotext;
    b : twobuffer;
    p : twopath;
    notpadded, s : twostate;
    linnum, oldptr, botptr, topptr, bufptr, oldedge, leftedge : twoint;
    searchstring : linebuffer;
    seeknumber : Integer;

    {watch out -- the following l, bl, len, reg are used at all scope levels}
    l : linebuffer;
    bl : bytebuffer ABSOLUTE l;
    len : Byte ABSOLUTE l;
    reg : regpack;

    tmpwind, n : Window;      {which window we are in}
    bufmod, cursortype, vidstatport, vidmodeport, screenseg : Integer;
    savscr : fullscreen;
    ch : Byte;
    synced : Boolean;
    {BIOS stores most recent video mode select port value here}
    modeportdata : Byte ABSOLUTE $40 : $65;

  PROCEDURE cursoron(cursortype : Integer);
      {restore the stored cursor}
    BEGIN
      reg.cx := cursortype;
      reg.ah := 1;
      Intr($10, reg);
    END;                      {cursoron}

  PROCEDURE cursoroff(VAR cursortype : Integer);
      {save the current cursor and turn off the cursor}
    VAR
      doscursor : Integer ABSOLUTE $40 : $60;
    BEGIN
      cursortype := doscursor;
      reg.ch := $20;
      reg.ah := 1;
      Intr($10, reg);
    END;                      {cursoroff}

  FUNCTION which_screen(VAR cursortype, vidstatport, vidmodeport : Integer)
      : Integer;
      {-determines which screen TURBO is writing to}
      {-and gets a default cursor type and video port}
    VAR
      {holds video controller port base address}
      vid : Integer ABSOLUTE $40 : $63;
    BEGIN
      GoToXY(1, 1);
      Mem[$B000 : 0] := 0;
      Mem[$B800 : 0] := 0;
      Write(' ');
      IF Mem[$B000 : 0] = $20 THEN BEGIN
        {monochrome adapter}
        which_screen := $B000;
        cursortype := $B0C;
      END ELSE BEGIN
        {color/graphics adapter}
        which_screen := $B800;
        cursortype := $607;
      END;
      vidstatport := vid+6;   {video status port for either card}
      vidmodeport := vid+4;   {video mode port for either card}
    END;                      {which_screen}

  PROCEDURE drawcenterbar(i : Byte);
      {-center bar holds context sensitive "prompts"}
    BEGIN
      GoToXY(1, 13);
      IF i = 1 THEN
        Write('FMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM5')
      ELSE IF i = 2 THEN
        Write('FMMMMMMMMMMMMMMMMMMMMMMMMMMMM Press ^H for Help MMMMMMMMMMMMMMMMMMMMMMMMMMMMMM5')
      ELSE IF i = 3 THEN
        Write('FMMMMMMMMMMMMMMMMM Searches are "Forward, Uppercase" only MMMMMMMMMMMMMMMMMMMM5');
    END;                      {drawcenterbar}

  PROCEDURE drawborder;
      {-draw a border for each window}
    BEGIN
      TextColor(windcolor);
      {note following clrscr sets the attribute bytes for the text areas we later write to}
      ClrScr;
      LowVideo;
      GoToXY(1, 12);
      Write('ZD                                                                           D?');
      GoToXY(1, 14);
      Write('@D                                                                           DY');
    END;                      {drawborder}

  PROCEDURE clearstat(n : Window; l, r : Byte);
      {clear status line from col l to col r inclusive}
    BEGIN
      GoToXY(l, 12+2*n);
      Write(' ' : (r-l+1));
    END;

  PROCEDURE movetostat(n : Window; l : Byte);
      {-move to position on status line}
    BEGIN
      GoToXY(l, 12+2*n);
    END;                      {movetostat}

  PROCEDURE drawtitle(n : Window);
      {-show the fixed title info}
    BEGIN
      movetostat(n, 49); Write('Line ');
      movetostat(n, 67); Write('Col ');
    END;                      {drawtitle}

  PROCEDURE showwindow(n : Window);
      {-make obvious which window is active}
    BEGIN
      cursoron(cursortype);
      IF synced THEN BEGIN
        {show char in opposite window also}
        NormVideo;
        movetostat(1-n, 45);
        Write(Chr(25-n), Chr(25-n));
      END ELSE
        {erase char from previous window}
        clearstat(1-n, 45, 46);

      {put appropriate char in this window}
      NormVideo;
      movetostat(n, 45); Write(Chr(24+n), Chr(24+n));
      LowVideo;
      cursoroff(cursortype);
    END;                      {showwindow}

  PROCEDURE waiton(n : Window);
      {-show a wait symbol}
    BEGIN
      cursoron(cursortype);
      clearstat(n, 3, 43);
      movetostat(n, 16);
      TextColor(23);          {blinking low intensity}
      Write('--- W A I T ---');
      LowVideo;
      cursoroff(cursortype);
    END;                      {waiton}

  PROCEDURE waitoff(n : Window);
      {-turn off wait symbol and restore what it overwrote}
    BEGIN
      {put up file name again}
      cursoron(cursortype);
      clearstat(n, 3, 43);
      movetostat(n, 3);
      Write('File: ', p[n]);
      cursoroff(cursortype);
    END;                      {waitoff}

  FUNCTION buffprior(n : Window;
                     VAR botptr, bufptr : twoint) : Integer;
      {-return the number of lines to bottom of buffer}
    BEGIN
      IF botptr[n] > bufptr[n] THEN
        buffprior := bufptr[n]-botptr[n]+bufmod
      ELSE
        buffprior := bufptr[n]-botptr[n];
    END;                      {buffprior}

  FUNCTION buffafter(n : Window;
                     VAR topptr, bufptr : twoint) : Integer;
      {-return the number of lines to top of buffer}
    BEGIN
      IF topptr[n] < bufptr[n] THEN
        buffafter := topptr[n]+bufmod-bufptr[n]
      ELSE
        buffafter := topptr[n]-bufptr[n];
    END;                      {buffafter}

  FUNCTION currline(n : Window) : Integer;
      {-return the current line number of the file in the window}
    BEGIN
      {lines from bottom of buffer to current plus lines to bottom of file}
      currline := buffprior(n, botptr, bufptr)+linnum[n];
    END;                      {currline}

  PROCEDURE showlin(n : Window;
                    VAR linnum, botptr, bufptr : twoint);
      {-display the current range of lines}
    VAR
      lins : Integer;
    BEGIN
      {erase previous range}
      cursoron(cursortype);
      clearstat(n, 54, 64);
      lins := currline(n);
      {write new range}
      movetostat(n, 54); Write(lins, '-', lins+slines-1);
      {turn off cursor again}
      cursoroff(cursortype);
    END;                      {showlin}

  PROCEDURE showcol(n : Window; leftedge : twoint);
      {-show the current column range}
    VAR
      t : Integer;
    BEGIN
      {erase previous range}
      cursoron(cursortype);
      clearstat(n, 71, 77);
      t := leftedge[n];
      {write new range}
      movetostat(n, 71); Write(t, '-', t+79);
      cursoroff(cursortype);
    END;                      {showcol}

  FUNCTION modsucc(n : Integer) : Integer;
      {-increment the argument mod bufmod}
    BEGIN
      modsucc := (n+1) MOD bufmod;
    END;                      {modsucc}

  FUNCTION modpred(n : Integer) : Integer;
      {-decrement the argument mod bufmod}
    BEGIN
      IF n <= 0 THEN modpred := bufsize ELSE modpred := n-1;
    END;                      {modpred}

  PROCEDURE brkoff;
      {-shut off ctrl-break check to assure WordStar ^C gets through}
    BEGIN
      reg.ax := $3301;
      reg.dx := 0;
      MsDos(reg);
    END;                      {brkoff}

  PROCEDURE getfile(n : Window;
                    VAR f : twotext;
                    VAR p : twopath;
                    VAR s : twostate);
      {-open either of the files for read}
    VAR
      good : Boolean;
      ch : Char;
    BEGIN
      drawcenterbar(1);
      cursoron(cursortype);
      REPEAT
        clearstat(n, 3, 77);
        movetostat(n, 3);
        Write('Enter file for this window (<ret> for none): ');
        Read(p[n]);
        s[n] := (p[n] <> ''); {false means no file in window}
        clearstat(n, 3, 77);
        movetostat(n, 3);
        IF s[n] THEN BEGIN
          {see if file exists}
          Assign(f[n], p[n]);
          {$I-} Reset(f[n]);  {$I+}
          good := (IOResult = 0);
          IF good THEN
            Write('File: ', p[n])
          ELSE BEGIN
            Write(p[n], ' not found... press any key to try again');
            Read(Kbd, ch);
          END;
        END ELSE BEGIN
          good := True;
          Write('File: none');
        END;
      UNTIL good;
      drawcenterbar(2);
      cursoroff(cursortype);
    END;                      {getfile}

  PROCEDURE readandexpandline(VAR f : textfile; VAR nl : linebuffer);
      {-read a line from the file and expand tabs, returning a line}
    VAR
      i, o : Byte;
    BEGIN
      ReadLn(f, l);
      i := 1;
      o := 0;
      WHILE i <= len DO BEGIN
        IF l[i] = #9 THEN BEGIN
          {expand tabs}
          o := o+1;
          nl[o] := #32;
          WHILE (o MOD 8) <> 0 DO BEGIN
            o := o+1;
            nl[o] := #32;
          END;
        END ELSE BEGIN
          {insert regular character}
          {could insert a high bit or other filter here}
          o := o+1;
          nl[o] := l[i];
        END;
        i := i+1;
      END;
      {set length of nl}
      nl[0] := Chr(o);
    END;                      {readandexpandline}

  PROCEDURE padbuffer(n : Window;
                      VAR botptr, topptr, bufptr, linnum : twoint;
                      VAR notpadded : twostate);
      {-assure end of buffer will fill screen with blanks}
    VAR
      cnt : Integer;
    BEGIN
      cnt := 1;
      WHILE cnt < slines DO BEGIN
        {fill with empty lines}
        b[n][topptr[n]] := '';
        topptr[n] := modsucc(topptr[n]);
        IF topptr[n] = botptr[n] THEN BEGIN
          {buffer full, compensate}
          botptr[n] := modsucc(botptr[n]);
          linnum[n] := linnum[n]+1;
        END;
        cnt := cnt+1;
      END;
      notpadded[n] := False;
    END;                      {padbuffer}

  PROCEDURE fillbuff(n : Window;
                     linstart : Integer;
                     VAR botptr, topptr, bufptr, linnum : twoint;
                     VAR notpadded : twostate);
      {-fill the buffer from the beginning, referenced from linstart}
    VAR
      ch : Char;
    BEGIN
      notpadded[n] := True;
      botptr[n] := 0;
      bufptr[n] := 0;
      topptr[n] := 0;
      linnum[n] := linstart;  {always holds line num at bottom of buffer}
      IF s[n] THEN BEGIN
        WHILE NOT(EoF(f[n])) AND (topptr[n] < bufsize) DO BEGIN
          Readandexpandline(f[n], b[n][topptr[n]]);
          topptr[n] := topptr[n]+1;
          IF KeyPressed THEN Read(Kbd, ch);
        END;
        {pad buffer with blanks for short files}
        IF EoF(f[n]) THEN
          padbuffer(n, botptr, topptr, bufptr, linnum, notpadded);
      END;
    END;                      {fillbuff}

  FUNCTION calcscradd(r : Byte) : Integer;
      {-return the offset into the screen segment of a given row at column 1}
    VAR
      t : Integer;
    BEGIN
      {fast way of saying 160*(r-1)}
      t := (r-1) SHL 4;
      calcscradd := ((t SHL 2)+t) SHL 1;
    END;                      {calcscradd}

  PROCEDURE scrollwind(r1, r2, lines : Byte);
      {-scroll the region between rows r1 and r2 by lines lines}
      {lines>0 scrolls up, <0 scrolls down, =0 erases window}
    BEGIN
      reg.al := abs(lines);
      IF lines > 0 THEN reg.ah := 6 ELSE reg.ah := 7;
      reg.cx := (r1-1) SHL 8;
      reg.dx := ((r2-1) SHL 8) OR 79;
      reg.bh := windcolor;
      Intr($10, reg);
    END;                      {scollwind}

  PROCEDURE movetoscreen(n : Window; VAR leftedge, bufptr, oldptr : twoint);
      {-move buffer info to screen}
    VAR
      cnt, r, sadd, left : Integer;
      rt, rb, lines : Byte;

    PROCEDURE writeline(r, sadd, left : Integer);
        {-write the line of text ref'ed by r to video memory starting at sadd}
      BEGIN
        {assure l right padded with blanks}
        FillChar(l[1], linesize, #32);
        {put real data at beginning of l}
        l := Copy(b[n][r], left, 80);

        {write to video memory with timing interlocks}
        {.F-}
        INLINE(
          {get screenseg into es}
          $BB/screenseg/      {MOV    BX,offset(screenseg)}
          $8B/$07/            {MOV    AX,[BX]}
          $8E/$C0/            {MOV    ES,AX}

          {get screen offset sadd into di}
          $8B/$7E/$06/        {MOV    DI,[BP+06]}

          {get video status port into dx}
          $BB/vidstatport/    {MOV    BX,offset(vidstatport)}
          $8B/$17/            {MOV    DX,[BX]}

          {point to string data with si}
          $BB/l/              {MOV    BX,offset(l)}
          $43/                {INC    BX}
          $8B/$F3/            {MOV    SI,BX}

          {move 80 bytes to fill one row of screen}
          $B9/$50/$00/        {MOV    CX,0050}
          $FA/                {CLI    }

          {loop to write to screen}
{124:}
          $EC/                {IN     AL,DX}
          $A8/$01/            {TEST   AL,01}
          $75/$FB/            {JNZ    0124}

{12A:}
          $EC/                {IN     AL,DX}
          $A8/$01/            {TEST   AL,01}
          $74/$FB/            {JZ     012A}

          {write byte in "safe period"}
          $A4/                {MOVSB    }
          $47/                {INC    DI - skip attribute byte}
          $E2/$F2/            {LOOP   0124}

          {done now}
          $FB                 {STI    }

          {.F+}
          );                  {end of inline}

      END;                    {writeline}

    BEGIN
      r := bufptr[n];         {starting row in buffer}
      rt := 14*n+1;           {starting row on screen}
      lines := r-oldptr[n];   {vertical lines of scrolling}
      left := leftedge[n];    {first char of string that we display}

      IF abs(lines) = 1 THEN BEGIN

        {single line scroll, use BIOS scroll for speed}
        rb := rt+10;
        scrollwind(rt, rt+10, lines);
        IF lines = 1 THEN BEGIN
          {scroll up}
          sadd := calcscradd(rb);
          r := (r+slines-1) MOD bufmod;
          writeline(r, sadd, left);
        END ELSE BEGIN
          {scroll down}
          sadd := calcscradd(rt);
          writeline(r, sadd, left);
        END;

      END ELSE BEGIN

        {horizontal or multiline scroll, redraw entire window}
        sadd := calcscradd(rt); {starting address on screen for this window}
        FOR cnt := 1 TO slines DO BEGIN
          {loop through lines, all guaranteed to exist in buffer}
          writeline(r, sadd, left);
          {address of next line of screen}
          sadd := sadd+160;
          {next row of buffer}
          r := modsucc(r);
        END;

      END;
    END;                      {movetoscreen}

  PROCEDURE initwindow(n : Window;
                       VAR botptr, topptr, bufptr, oldptr,
                       leftedge, oldedge, linnum : twoint;
                       VAR notpadded : twostate);
      {-initialize everything for a single window}
    BEGIN
      notpadded[n] := True;
      fillbuff(n, 1, botptr, topptr, bufptr, linnum, notpadded);
      leftedge[n] := 1;
      oldedge[n] := 1;
      oldptr[n] := bufptr[n];
      movetoscreen(n, leftedge, bufptr, oldptr);
      drawtitle(n);
      showlin(n, linnum, botptr, bufptr);
      showcol(n, leftedge);
    END;                      {initwindow}

  PROCEDURE switchn(VAR n : Window);
      {-switch to opposite window}
    BEGIN
      IF s[1-n] THEN
        n := 1-n
      ELSE BEGIN
        {get another file for the unused window}
        getfile(1-n, f, p, s);
        IF s[1-n] THEN BEGIN
          {got a file, initialize it and window}
          n := 1-n;
          drawtitle(n);
          initwindow(n, botptr, topptr, bufptr, oldptr,
          leftedge, oldedge, linnum, notpadded);
        END;
      END;
      showwindow(n);
    END;                      {switchn}

  PROCEDURE updatebuff(n : Window;
                       VAR botptr, topptr, bufptr, linnum : twoint;
                       VAR notpadded : twostate);
      {-determine whether to add to buffer and do so}
    VAR
      buffleft : Integer;
    BEGIN
      {see if we are within linavail of top of buffer}
      IF buffafter(n, topptr, bufptr) < linavail THEN BEGIN
        {add linavail's worth of buffer}
        buffleft := linavail;
        WHILE NOT(EoF(f[n])) AND (buffleft > 0) DO BEGIN
          readandexpandline(f[n], b[n][topptr[n]]);
          buffleft := buffleft-1;
          topptr[n] := modsucc(topptr[n]);
          botptr[n] := modsucc(botptr[n]);
          linnum[n] := linnum[n]+1;
        END;
        IF EoF(f[n]) AND notpadded[n] THEN
          padbuffer(n, botptr, topptr, bufptr, linnum, notpadded);
      END;
    END;                      {updatebuff}

  PROCEDURE showright(n : Byte; VAR bufptr, leftedge : twoint);
      {-set leftedge so end of longest line shows}
    VAR
      maxlen, len, lin, cnt : Integer;
    BEGIN
      lin := bufptr[n];
      maxlen := 0;
      {get maximum line length in window}
      FOR cnt := 1 TO slines DO BEGIN
        len := Length(b[n][lin]);
        IF len > maxlen THEN maxlen := len;
        lin := modsucc(lin);
      END;
      {convert to a leftedge value}
      len := maxlen-79;
      IF len < 1 THEN len := 1;
      {reset leftedge if appropriate}
      IF leftedge[n] < len THEN leftedge[n] := len;
    END;                      {showright}

  PROCEDURE seekback(n : Window; backn : Integer;
                     VAR botptr, topptr, bufptr, oldptr, linnum : twoint);
      {-resynchronize buffer from beginning}
      {-backn is the number of lines to move final bufptr back}
    VAR
      linsave, linbegin, r : Integer;
      ch : Char;
    BEGIN

      {may take a while, put up a wait sign}
      waiton(n);

      {get and save current line number - note that bufprior=0 to arrive here}
      linsave := linnum[n];

      {new line to resynchronize beginning of buffer}
      linbegin := linsave-(bufsize SHR 1);
      IF linbegin < 1 THEN linbegin := 1;

      {reset file and leave file pointer ready to read linbegin}
      Reset(f[n]);
      Flush(f[n]);
      FOR r := 1 TO linbegin DO BEGIN
        ReadLn(f[n], l);
        {clear keyboard buffer of any strokes built up during delay}
        IF KeyPressed THEN Read(Kbd, ch);
      END;

      {fill buffer starting at linbegin}
      fillbuff(n, linbegin, botptr, topptr, bufptr, linnum, notpadded);

      {set bufptr to be at correct line}
      bufptr[n] := linsave-linbegin-backn;
      {guarantee we redisplay}
      oldptr[n] := -2;
      waitoff(n);
    END;                      {seekback}

  FUNCTION getlinenumber(n : Window) : Integer;
      {-return a legal line number to seek}
    VAR
      seeknum : Integer;
      good : Boolean;
      ch : Char;
    BEGIN
      {prompt for the number}
      drawcenterbar(1);
      cursoron(cursortype);
      REPEAT
        clearstat(n, 3, 43);
        movetostat(n, 3);
        Write('Enter line number: ');
        {$I-} Read(seeknum);  {$I+}
        good := (IOResult = 0);
        IF NOT(good) THEN BEGIN
          clearstat(n, 3, 43);
          movetostat(n, 3);
          Write('Illegal number... Press key to try again');
          Read(Kbd, ch);
        END ELSE BEGIN
          good := (seeknum >= 1);
          IF NOT(good) THEN BEGIN
            clearstat(n, 3, 43);
            movetostat(n, 3);
            Write('Illegal number... Press key to try again');
            Read(Kbd, ch);
          END;
        END;
      UNTIL good;
      drawcenterbar(2);
      cursoroff(cursortype);
      getlinenumber := seeknum;
    END;                      {getlinenumber}

  PROCEDURE seekline(n : Window; seeknum : Integer;
                     VAR botptr, topptr, bufptr, oldptr, linnum : twoint;
                     VAR notpadded : twostate);
      {-seek to a desired line number}
    VAR
      lin : Integer;

    PROCEDURE seekahead(n : Window; seeknum, lin : Integer;
                        VAR botptr, topptr, bufptr, linnum : twoint;
                        VAR notpadded : twostate);
        {-seek forward to find line seeknum, starting at line lin}
        {return buffers set up at line found, or at end of file}
      BEGIN
        WHILE (buffafter(n, topptr, bufptr) > slines) AND (lin < seeknum) DO BEGIN
          {update file buffer as needed}
          updatebuff(n, botptr, topptr, bufptr, linnum, notpadded);
          lin := lin+1;
          bufptr[n] := modsucc(bufptr[n]);
        END;
      END;                    {seekahead}

    BEGIN

      waiton(n);

      lin := currline(n);
      IF seeknum > lin THEN BEGIN
        {seeknum is ahead of us}
        seekahead(n, seeknum, lin, botptr, topptr, bufptr, linnum, notpadded);
      END ELSE IF seeknum >= linnum[n] THEN BEGIN
        {seeknum is behind us, but in current buffer}
        bufptr[n] := botptr[n];
        lin := linnum[n];
        seekahead(n, seeknum, lin, botptr, topptr, bufptr, linnum, notpadded);
      END ELSE BEGIN
        {seeknum is behind current buffer}
        {reset}
        Reset(f[n]);
        Flush(f[n]);
        notpadded[n] := True;
        {refill the buffer}
        fillbuff(n, 1, botptr, topptr, bufptr, linnum, notpadded);
        {now seeknum is ahead of us}
        seekahead(n, seeknum, 1, botptr, topptr, bufptr, linnum, notpadded);
      END;

      waitoff(n);
    END;                      {seekline}

  PROCEDURE seekstring(n : Window;
                       VAR botptr, topptr, bufptr, oldptr, linnum : twoint;
                       VAR notpadded : twostate;
                       VAR searchstring : linebuffer);
      {-search for a string}
    VAR
      notfound : Boolean;
      ch : Char;
      spos : Integer;

    FUNCTION stupcase(s : linebuffer) : linebuffer;
        {-return the uppercase of a string}
      VAR
        i : Byte;
      BEGIN
        FOR i := 1 TO Length(s) DO s[i] := UpCase(s[i]);
        stupcase := s;
      END;                    {stupcase}

    BEGIN
      IF searchstring = '' THEN BEGIN
        {get the search string}
        drawcenterbar(3);
        cursoron(cursortype);
        clearstat(n, 3, 43);
        movetostat(n, 3);
        Write('Search String: ');
        Read(searchstring);
        searchstring := stupcase(searchstring);
        drawcenterbar(2);
        waitoff(n);
      END;

      IF searchstring <> '' THEN BEGIN
        {do the search}
        waiton(n);
        notfound := True;
        WHILE notfound AND (buffafter(n, topptr, bufptr) > slines) DO BEGIN
          {scan forward through buffer and update buffer as necessary}
          bufptr[n] := modsucc(bufptr[n]);
          notfound := (Pos(searchstring, stupcase(b[n][bufptr[n]])) = 0);
          updatebuff(n, botptr, topptr, bufptr, linnum, notpadded);
        END;
        IF notfound THEN BEGIN
          {complain}
          clearstat(n, 3, 43);
          movetostat(n, 3);
          Write('String not found. Press any key...');
          Read(Kbd, ch);
        END ELSE BEGIN
          {make sure string found shows on screen}
          spos := Pos(searchstring, stupcase(b[n][bufptr[n]]));
          IF spos < leftedge[n] THEN
            {string is off left edge of screen}
            leftedge[n] := spos
          ELSE IF (spos+Length(searchstring)) > (leftedge[n]+79) THEN
            {string is off right edge of screen}
            leftedge[n] := spos+Length(searchstring)-80;
        END;
        waitoff(n);
      END;

    END;                      {seekstring}

  PROCEDURE initialize(VAR n : Window);
      {-set up everything}
    VAR
      w : Window;
    BEGIN
      {avoid problems with ^C char used for WordStar scrolling}
      brkoff;
      {some constants}
      bufmod := bufsize+1;
      searchstring := '';
      synced := False;

      {initialize selected windows}
      FOR w := 0 TO 1 DO IF s[w] THEN
        initwindow(w, botptr, topptr, bufptr, oldptr,
        leftedge, oldedge, linnum, notpadded);

      {pick initial window}
      IF s[0] THEN n := 0 ELSE n := 1;

      {show which window is active}
      showwindow(n);
    END;                      {initialize}

  PROCEDURE closeup;
      {-close up to quit}
    BEGIN
      IF s[0] THEN Close(f[0]);
      IF s[1] THEN Close(f[1]);
      GoToXY(1, 25);
      cursoron(cursortype);
      Halt;
    END;                      {closeup}

  PROCEDURE videooff;
      {-avoid snow writing full screen to c/g card}
    BEGIN
      {clear video enable bit}
      Port[vidmodeport] := modeportdata AND 247;
    END;

  PROCEDURE videoon;
      {-reenable video}
    BEGIN
      {set video enable bit}
      Port[vidmodeport] := modeportdata OR 8;
    END;

  PROCEDURE savescreen;
    BEGIN
      Move(Mem[screenseg : 0], savscr, 4000);
    END;                      {savescreen}

  PROCEDURE restorescreen;
    BEGIN
      Move(savscr, Mem[screenseg : 0], 4000);
    END;                      {restorescreen}

  PROCEDURE invokeDOS;
      {-start a new DOS shell, then return to browser}
    VAR
      save_ax : Integer;
      ch : Char;

    PROCEDURE execute_string(s : pathstring; VAR sav_ax : Integer);
        {-execute a command line}
        {-provided by russ nelson, potsdam, ny}
      VAR
        save_ax : Integer;
      CONST
        save_ss : Integer = 0;
        save_sp : Integer = 0;
      BEGIN
        s[Length(s)+1] := ^M;
        INLINE(
          $1E/                {push    ds}
          $55/                {push    bp}
          $2E/$8C/$16/save_ss/ {mov     cs:[save_ss],ss}
          $2E/$89/$26/save_sp/ {mov     cs:[save_sp],sp}
          $8C/$D0/            {mov     ax,ss}
          $8E/$D8/            {mov     ds,ax}
          $8D/$76/< s/        {lea     si,s[bp]}
          $CD/$2E/            {int     2Eh}
          $2E/$8E/$16/save_ss/ {mov     ss,cs:[save_ss]}
          $2E/$8B/$26/save_sp/ {mov     sp,cs:[save_sp]}
          $5D/                {pop     bp}
          $1F/                {pop     ds}
          $89/$46/< save_ax   {mov     save_ax[bp],ax}
          );
        sav_ax := save_ax;
      END;                    {execute_string}

    BEGIN
      {store screen}
      videooff;
      savescreen;
      videoon;
      cursoron(cursortype);
      LowVideo;

      {clear screen and put up help}
      ClrScr;
      WriteLn('Type EXIT to return to BROWSE...');

      {start new DOS shell}
      execute_string('command', save_ax);

      {restore screen}
      cursoroff(cursortype);
      videooff;
      restorescreen;
      videoon;

      {check errors - this particular interrupt doesn't provide many}
      IF save_ax <> 0 THEN BEGIN
        drawcenterbar(3);
        cursoron(cursortype);
        clearstat(n, 3, 43);
        movetostat(n, 3);
        Write('Can''t invoke DOS. Press any key...');
        Read(Kbd, ch);
        drawcenterbar(2);
        waitoff(n);
      END;

    END;                      {invokeDOS}

  PROCEDURE showhelp;
      {-provide a pop-up help screen}
    CONST
      helplines = 15;
      helpx = 11;
      helpy = 6;
    TYPE
      helparray = ARRAY[1..helplines] OF STRING[60];
    VAR
      ch : Char;
      i, r : Byte;
    CONST
      ha : helparray =
      (
      'IMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM;',
      ':         Window Browser - by TurboPower Software        :',
      ': ZDDDDDDDDDDDDDDDDDDDDDDDD? ZDDDDDDDDDDDDDDDDDDDDDDDDD? :',
      ': 3   ^Z,^X   line up      3 3  ^S     column left     3 :',
      ': 3   ^W,^E   line down    3 3  ^D     column right    3 :',
      ': 3   ^C      page up      3 3  ^Q^S   left of line    3 :',
      ': 3   ^R      page down    3 3  ^Q^D   right of line   3 :',
      ': 3   ^Q^R    file home    3 3  ^Q^N   seek line number3 :',
      ': 3   ^Q^C    file end     3 3  ^Q^F   string search   3 :',
      ': 3   ^K^R    new file     3 3  ^L     search again    3 :',
      ': 3   ^K^D    quit         3 3  ^N     switch windows  3 :',
      ': 3   ^K^I    invoke DOS   3 3  ^B     (un)sync windows3 :',
      ': @DDDDDDDDDDDDDDDDDDDDDDDDY @DDDDDDDDDDDDDDDDDDDDDDDDDY :',
      ':       Press any key to return to your browsing....     :',
      'HMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM<'
      );

    PROCEDURE setscreenattr(a : Byte);
        {-set entire screen to a given attribute}
      VAR
        i : Integer;
      BEGIN
        i := 1;
        WHILE i < 4000 DO BEGIN
          Mem[screenseg : i] := a;
          i := i+2;
        END;
      END;                    {setscreenattr}

    BEGIN
      videooff;
      savescreen;
      setscreenattr(7);       {put background in low video}
      videoon;
      {write help}
      cursoron(cursortype);
      TextColor(15);
      r := helpy;
      FOR i := 1 TO helplines DO BEGIN
        GoToXY(helpx, r);
        Write(ha[i]);
        r := r+1;
      END;
      LowVideo;
      cursoroff(cursortype);
      Read(Kbd, ch);
      videooff;
      restorescreen;
      videoon;
    END;                      {showhelp}

  FUNCTION getkey : Byte;
      {-return a keycode for a legal keystroke}
      {customize here for favorite cursor keys}
      {this is currently set up for WordStar-like keys only}
    VAR
      good : Boolean;
      ch : Char;
    BEGIN
      {don't let keystrokes get ahead of the action}
      WHILE KeyPressed DO Read(Kbd, ch);
      REPEAT
        Read(Kbd, ch);
        good := True;
        CASE ch OF
          ^S : getkey := 9;
          ^D : getkey := 10;
          ^W, ^E : getkey := 1;
          ^Z, ^X : getkey := 2;
          ^Q : BEGIN
                 Read(Kbd, ch);
                 CASE ch OF
                   ^R, 'r', 'R' : getkey := 5;
                   ^C, 'c', 'C' : getkey := 6;
                   ^S, 's', 'S' : getkey := 11;
                   ^D, 'd', 'D' : getkey := 12;
                   ^N, 'n', 'N' : getkey := 14;
                   ^F, 'f', 'F' : getkey := 16;
                 ELSE
                   good := False;
                 END;
               END;
          ^K : BEGIN
                 Read(Kbd, ch);
                 CASE ch OF
                   ^X, ^D, 'x', 'X', 'd', 'D' : getkey := 7;
                   ^R, 'r', 'R' : getkey := 13;
                   ^I, 'i', 'I' : getkey := 19;
                 ELSE
                   good := False;
                 END;
               END;
          ^N : getkey := 8;
          ^B : getkey := 18;
          ^R : getkey := 3;
          ^C : getkey := 4;
          ^H : getkey := 15;
          ^L : getkey := 17;
        ELSE
          good := False;
        END;
      UNTIL good;
    END;                      {getkey}

  PROCEDURE update(n : Window);
      {-update the screen and buffers for window n}
    BEGIN
      {update file buffer as needed}
      updatebuff(n, botptr, topptr, bufptr, linnum, notpadded);

      IF (oldptr[n] <> bufptr[n]) OR (oldedge[n] <> leftedge[n]) THEN BEGIN
        {update screen data}
        movetoscreen(n, leftedge, bufptr, oldptr);
        IF oldptr[n] <> bufptr[n] THEN BEGIN
          oldptr[n] := bufptr[n];
          {update line status}
          showlin(n, linnum, botptr, bufptr);
        END;
        IF oldedge[n] <> leftedge[n] THEN BEGIN
          oldedge[n] := leftedge[n];
          {update column status}
          showcol(n, leftedge);
        END;
      END;
    END;                      {update}

  PROCEDURE doop(VAR n : Window; ch : Byte);
    VAR
      temp : Integer;
    BEGIN
      {-operate based on key}
      CASE ch OF

        {scroll one line up}
        1 : IF buffprior(n, botptr, bufptr) > 0 THEN
              bufptr[n] := modpred(bufptr[n])
            ELSE IF linnum[n] > 1 THEN
              {need to rebuild buffer from beginning}
              seekback(n, 1, botptr, topptr, bufptr, oldptr, linnum);

        {scroll one line down}
        2 : IF buffafter(n, topptr, bufptr) > slines THEN
              bufptr[n] := modsucc(bufptr[n]);

        {scroll page up}
        3 : IF buffprior(n, botptr, bufptr) > slines THEN
              FOR temp := 1 TO slines DO bufptr[n] := modpred(bufptr[n])
            ELSE IF linnum[n] > 1 THEN
              {need to rebuild buffer from beginning}
              seekback(n, slines, botptr, topptr, bufptr, oldptr, linnum)
            ELSE
              {set to beginning of file}
              bufptr[n] := botptr[n];

        {scroll page down}
        4 : IF buffafter(n, topptr, bufptr) > slines THEN
              FOR temp := 1 TO slines DO
                IF buffafter(n, topptr, bufptr) > slines THEN
                  bufptr[n] := modsucc(bufptr[n]);

        {home}
        5 : IF linnum[n] > 1 THEN BEGIN
              {reset}
              waiton(n);
              Reset(f[n]);
              Flush(f[n]);
              leftedge[n] := 1;
              notpadded[n] := True;
              {refill the buffer}
              fillbuff(n, 1, botptr, topptr, bufptr, linnum, notpadded);
              oldptr[n] := -2; {guarantee redisplay}
              waitoff(n);
            END ELSE
              bufptr[n] := 0;

        {end}
        6 : BEGIN
              IF NOT(EoF(f[n])) THEN waiton(n);
              WHILE NOT(EoF(f[n])) DO BEGIN
                bufptr[n] := topptr[n];
                updatebuff(n, botptr, topptr, bufptr, linnum, notpadded);
              END;
              bufptr[n] := topptr[n];
              FOR temp := 1 TO slines DO
                bufptr[n] := modpred(bufptr[n]);
              {guarantee redisplay}
              oldptr[n] := -2;
              waitoff(n);
            END;

        {quit}
        7 : closeup;

        {switch windows}
        8 : switchn(n);

        {scroll left}
        9 : IF leftedge[n] > 1 THEN
              leftedge[n] := leftedge[n]-1;

        {scroll right}
        10 : IF leftedge[n] < linesize-79 THEN
               leftedge[n] := leftedge[n]+1;

        {set left edge to beginning of screen}
        11 : leftedge[n] := 1;

        {set right edge to show longest line}
        12 : showright(n, bufptr, leftedge);

        {open new file in current window}
        13 : BEGIN
               Close(f[n]);
               getfile(n, f, p, s);
               IF s[n] THEN BEGIN
                 {open the new window}
                 initwindow(n, botptr, topptr, bufptr, oldptr,
                 leftedge, oldedge, linnum, notpadded);
                 showwindow(n);
               END ELSE BEGIN
                 {clear the now-empty window}
                 scrollwind(1+14*n, 11+14*n, 0);
                 {make sure other window is open and switch to it}
                 IF s[1-n] THEN switchn(n) ELSE closeup;
               END;
             END;

        {seek to line number}
        14 : BEGIN
               seeknumber := getlinenumber(n);
               seekline(n, seeknumber,
               botptr, topptr, bufptr, oldptr, linnum, notpadded);
             END;

        15 : showhelp;

        {search for a new string}
        16 : BEGIN
               searchstring := '';
               seekstring(n, botptr, topptr, bufptr,
               oldptr, linnum, notpadded, searchstring);
             END;

        {search for prior string}
        17 : seekstring(n, botptr, topptr, bufptr,
          oldptr, linnum, notpadded, searchstring);

        19 : invokeDOS;

      END;                    {case}
    END;                      {doop}

  BEGIN                       {main}

    {get screen segment, cursortype, and video port addresses}
    screenseg := which_screen(cursortype, vidstatport, vidmodeport);
    {draw screen border and titles}
    drawborder;

    {get input files}
    getfile(0, f, p, s);
    getfile(1, f, p, s);

    {close up if no files}
    IF NOT(s[0] OR s[1]) THEN BEGIN
      GoToXY(1, 25);
      Halt;
    END;

    {initialize globals and turn off cursor, return active window}
    initialize(n);

    {main loop}
    WHILE True DO BEGIN

      {update screen and buffers}
      update(n);
      IF synced AND s[0] AND s[1] THEN update(1-n);

      {get new keystroke}
      ch := getkey;

      {operate based on keystroke}
      IF ch = 18 THEN BEGIN
        {handle window synchronization}
        synced := NOT(synced);
        showwindow(n);
      END ELSE BEGIN
        {do any other operation}
        doop(n, ch);
        IF synced AND s[0] AND s[1] AND (ch <> 15) AND (ch <> 19) THEN BEGIN
          IF ch = 14 THEN
            {seek to same line number as other window}
            seekline(1-n, seeknumber, botptr, topptr, bufptr, oldptr, linnum, notpadded)
          ELSE BEGIN
            {convert string search to one for same string in other window}
            IF ch = 16 THEN ch := 17;
            {do operation in other window}
            tmpwind := 1-n;
            doop(tmpwind, ch);
          END;
        END;
      END;

    END;                      {while true}

  END.
