home *** CD-ROM | disk | FTP | other *** search
/ Programmer 7500 / MAX_PROGRAMMERS.iso / INFO / TURBOPAS / MISC.ZIP / BROWSE.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1985-10-06  |  40.1 KB  |  1,249 lines

  1.   {$C-}
  2.  
  3. PROGRAM browse;
  4.  
  5.     {*************************************************************************}
  6.     {*         Copyright (c) Kim Kokkonen, TurboPower Software, 1985         *}
  7.     {*  Released to the public domain for personal, non-commercial use only  *}
  8.     {*************************************************************************}
  9.  
  10.     {.F-}
  11.     {
  12.     --- IBM PC SPECIFIC ---
  13.     BROWSE through two files simultaneously in two independent windows.
  14.  
  15.     writes directly to video memory, but with timing interlocks for c/g card.
  16.     keys are set up to resemble WordStar cursor movements.
  17.     see GETKEY to do keyboard customization.
  18.     requires Turbo version 3.0 to compile as it stands.
  19.     set min/max heap=$200/$200 paragraphs.
  20.     written 8/2/85, Kim Kokkonen, 408-378-3672, CompuServe [72457,2131]
  21.     updated 10/3/85 for faster video writes.
  22.  
  23.     FEATURES:
  24.         filenames may include drive and path
  25.         unlimited size files (or 32767 lines of 199 chars each, anyway)
  26.         two circular RAM buffers each of 127 text lines paged from disk
  27.         full forward and reverse scrolling at high speed
  28.         horizontal scrolling
  29.         synchronized windows mode
  30.         pop-up help (type ^H for help)
  31.         seek to line number
  32.         search for strings (forward, uppercase modes only)
  33.         invoke DOS process and return to place in BROWSE
  34.           (requires ~96K free RAM at entry to BROWSE to invoke DOS)
  35.     }
  36.     {.F+}
  37.  
  38.   CONST
  39.     LineSize = 199;           {max line length stored, longer lines truncated}
  40.     BufSize = 127;            {lines of text (-1) in each textbuffer}
  41.     LinAvail = 32;            {lines to always keep available at top of buffer}
  42.     slines = 11;              {number of lines (-1) in each window}
  43.     WindColor = 15;           {video attribute to use in text windows}
  44.     GoodColorCard = True;     {set false for IBM CGA, which requires
  45.                               timing interlocks to avoid snow on screen.
  46.                               Compaq graphics card among others allows
  47.                               this constant to be TRUE, and faster as a
  48.                               result. GoodColorCard is ignored if mono
  49.                               adapter is in use.}
  50.   TYPE
  51.     Window = 0..1;
  52.     PathString = STRING[64];
  53.     TextFile = Text[1024];
  54.     LineBuffer = STRING[LineSize];
  55.     ByteBuffer = ARRAY[0..LineSize] OF Byte;
  56.     TextBuffer = ARRAY[0..BufSize] OF LineBuffer;
  57.     TwoText = ARRAY[Window] OF TextFile;
  58.     TwoBuffer = ARRAY[Window] OF TextBuffer;
  59.     TwoPath = ARRAY[Window] OF PathString;
  60.     TwoState = ARRAY[Window] OF Boolean;
  61.     TwoInt = ARRAY[Window] OF Integer;
  62.     FullScreen = ARRAY[1..4000] OF Byte;
  63.     regpack = RECORD
  64.                 CASE Integer OF
  65.                   1 : (ax, bx, cx, dx, bp, si, di, ds, es, flags : Integer);
  66.                   2 : (al, ah, bl, bh, cl, ch, dl, dh : Byte);
  67.               END;
  68.  
  69.   VAR
  70.     f : TwoText;
  71.     b : TwoBuffer;
  72.     p : TwoPath;
  73.     NotPadded, s : TwoState;
  74.     LinNum, OldPtr, BotPtr, TopPtr, BufPtr, OldEdge, LeftEdge : TwoInt;
  75.     SearchString : LineBuffer;
  76.     SeekNumber : Integer;
  77.  
  78.     {watch out -- the following l, bl, len, reg are used at all scope levels}
  79.     l : LineBuffer;
  80.     bl : ByteBuffer ABSOLUTE l;
  81.     len : Byte ABSOLUTE l;
  82.     reg : regpack;
  83.  
  84.     TmpWind, N : Window;      {which window we are in}
  85.     Bufmod, CursorType, VidStatPort, VidModePort, ScreenSeg : Integer;
  86.     SavScr : FullScreen;
  87.     ch : Byte;
  88.     NoWait, Synced : Boolean;
  89.     {BIOS stores most recent video mode select port value here}
  90.     ModePortData : Byte ABSOLUTE $40 : $65;
  91.  
  92.   PROCEDURE CursorOn(CursorType : Integer);
  93.       {restore the stored cursor}
  94.     BEGIN
  95.       reg.cx := CursorType;
  96.       reg.ah := 1;
  97.       Intr($10, reg);
  98.     END;                      {cursoron}
  99.  
  100.   PROCEDURE CursorOff(VAR CursorType : Integer);
  101.       {save the current cursor and turn off the cursor}
  102.     VAR
  103.       DOScursor : Integer ABSOLUTE $40 : $60;
  104.     BEGIN
  105.       CursorType := DOScursor;
  106.       reg.ch := $20;
  107.       reg.ah := 1;
  108.       Intr($10, reg);
  109.     END;                      {cursoroff}
  110.  
  111.   FUNCTION which_screen(VAR CursorType, VidStatPort, VidModePort : Integer)
  112.       : Integer;
  113.       {-determines which screen TURBO is writing to}
  114.       {-and gets a default cursor type and video port}
  115.     VAR
  116.       {holds video controller port base address}
  117.       vid : Integer ABSOLUTE $40 : $63;
  118.     BEGIN
  119.       GoToXY(1, 1);
  120.       Mem[$B000 : 0] := 0;
  121.       Mem[$B800 : 0] := 0;
  122.       Write(' ');
  123.       IF Mem[$B000 : 0] = $20 THEN BEGIN
  124.         {monochrome adapter}
  125.         which_screen := $B000;
  126.         CursorType := $B0C;
  127.         NoWait := True;
  128.       END ELSE BEGIN
  129.         {color/graphics adapter}
  130.         which_screen := $B800;
  131.         CursorType := $607;
  132.         NoWait := False;
  133.       END;
  134.       VidStatPort := vid+6;   {video status port for either card}
  135.       VidModePort := vid+4;   {video mode port for either card}
  136.       NoWait := NoWait OR GoodColorCard;
  137.     END;                      {which_screen}
  138.  
  139.   PROCEDURE DrawCenterBar(i : Byte);
  140.       {-center bar holds context sensitive "prompts"}
  141.     BEGIN
  142.       GoToXY(1, 13);
  143.       IF i = 1 THEN
  144.         Write('═══════════════════════════════════════════════════════════════════════════════')
  145.       ELSE IF i = 2 THEN
  146.         Write('═════════════════════════════ Press ^H for Help ═══════════════════════════════')
  147.       ELSE IF i = 3 THEN
  148.         Write('══════════════════ Searches are "Forward, Uppercase" only ═════════════════════');
  149.     END;                      {drawcenterbar}
  150.  
  151.   PROCEDURE DrawBorder;
  152.       {-draw a border for each window}
  153.     BEGIN
  154.       TextColor(WindColor);
  155.       {note following clrscr sets the attribute bytes for the text areas we later write to}
  156.       ClrScr;
  157.       TextColor(0);
  158.       TextBackground(7);
  159.       GoToXY(1, 12);
  160.       Write('' : 79);
  161.       GoToXY(1, 14);
  162.       Write('' : 79);
  163.     END;                      {drawborder}
  164.  
  165.   PROCEDURE ClearStat(N : Window; l, r : Byte);
  166.       {clear status line from col l to col r inclusive}
  167.     BEGIN
  168.       GoToXY(l, 12+2*N);
  169.       Write(' ' : (r-l+1));
  170.     END;
  171.  
  172.   PROCEDURE MoveToStat(N : Window; l : Byte);
  173.       {-move to position on status line}
  174.     BEGIN
  175.       GoToXY(l, 12+2*N);
  176.     END;                      {movetostat}
  177.  
  178.   PROCEDURE DrawTitle(N : Window);
  179.       {-show the fixed title info}
  180.     BEGIN
  181.       MoveToStat(N, 49); Write('Line ');
  182.       MoveToStat(N, 67); Write('Col ');
  183.     END;                      {drawtitle}
  184.  
  185.   PROCEDURE ShowWindow(N : Window);
  186.       {-make obvious which window is active}
  187.     BEGIN
  188.       CursorOn(CursorType);
  189.       IF Synced THEN BEGIN
  190.         {show char in opposite window also}
  191.         MoveToStat(1-N, 45);
  192.         Write(Chr(25-N), Chr(25-N));
  193.       END ELSE
  194.         {erase char from previous window}
  195.         ClearStat(1-N, 45, 46);
  196.  
  197.       {put appropriate char in this window}
  198.       MoveToStat(N, 45); Write(Chr(24+N), Chr(24+N));
  199.       CursorOff(CursorType);
  200.     END;                      {showwindow}
  201.  
  202.   PROCEDURE WaitOn(N : Window);
  203.       {-show a wait symbol}
  204.     BEGIN
  205.       CursorOn(CursorType);
  206.       ClearStat(N, 3, 43);
  207.       MoveToStat(N, 16);
  208.       TextColor(16);          {blinking reverse video}
  209.       Write('--- W A I T ---');
  210.       TextColor(0);
  211.       CursorOff(CursorType);
  212.     END;                      {waiton}
  213.  
  214.   PROCEDURE WaitOff(N : Window);
  215.       {-turn off wait symbol and restore what it overwrote}
  216.     BEGIN
  217.       {put up file name again}
  218.       CursorOn(CursorType);
  219.       ClearStat(N, 3, 43);
  220.       MoveToStat(N, 3);
  221.       Write('File: ', p[N]);
  222.       CursorOff(CursorType);
  223.     END;                      {waitoff}
  224.  
  225.   FUNCTION BuffPrior(N : Window;
  226.                      VAR BotPtr, BufPtr : TwoInt) : Integer;
  227.       {-return the number of lines to bottom of buffer}
  228.     BEGIN
  229.       IF BotPtr[N] > BufPtr[N] THEN
  230.         BuffPrior := BufPtr[N]-BotPtr[N]+Bufmod
  231.       ELSE
  232.         BuffPrior := BufPtr[N]-BotPtr[N];
  233.     END;                      {buffprior}
  234.  
  235.   FUNCTION BuffAfter(N : Window;
  236.                      VAR TopPtr, BufPtr : TwoInt) : Integer;
  237.       {-return the number of lines to top of buffer}
  238.     BEGIN
  239.       IF TopPtr[N] < BufPtr[N] THEN
  240.         BuffAfter := TopPtr[N]+Bufmod-BufPtr[N]
  241.       ELSE
  242.         BuffAfter := TopPtr[N]-BufPtr[N];
  243.     END;                      {buffafter}
  244.  
  245.   FUNCTION CurrLine(N : Window) : Integer;
  246.       {-return the current line number of the file in the window}
  247.     BEGIN
  248.       {lines from bottom of buffer to current plus lines to bottom of file}
  249.       CurrLine := BuffPrior(N, BotPtr, BufPtr)+LinNum[N];
  250.     END;                      {currline}
  251.  
  252.   PROCEDURE ShowLin(N : Window;
  253.                     VAR LinNum, BotPtr, BufPtr : TwoInt);
  254.       {-display the current range of lines}
  255.     VAR
  256.       lins : Integer;
  257.     BEGIN
  258.       {erase previous range}
  259.       CursorOn(CursorType);
  260.       ClearStat(N, 54, 64);
  261.       lins := CurrLine(N);
  262.       {write new range}
  263.       MoveToStat(N, 54); Write(lins, '-', lins+slines-1);
  264.       {turn off cursor again}
  265.       CursorOff(CursorType);
  266.     END;                      {showlin}
  267.  
  268.   PROCEDURE ShowCol(N : Window; LeftEdge : TwoInt);
  269.       {-show the current column range}
  270.     VAR
  271.       t : Integer;
  272.     BEGIN
  273.       {erase previous range}
  274.       CursorOn(CursorType);
  275.       ClearStat(N, 71, 77);
  276.       t := LeftEdge[N];
  277.       {write new range}
  278.       MoveToStat(N, 71); Write(t, '-', t+79);
  279.       CursorOff(CursorType);
  280.     END;                      {showcol}
  281.  
  282.   FUNCTION ModSucc(N : Integer) : Integer;
  283.       {-increment the argument mod bufmod}
  284.     BEGIN
  285.       ModSucc := (N+1) MOD Bufmod;
  286.     END;                      {modsucc}
  287.  
  288.   FUNCTION ModPred(N : Integer) : Integer;
  289.       {-decrement the argument mod bufmod}
  290.     BEGIN
  291.       IF N <= 0 THEN ModPred := BufSize ELSE ModPred := N-1;
  292.     END;                      {modpred}
  293.  
  294.   PROCEDURE BrkOff;
  295.       {-shut off ctrl-break check to assure WordStar ^C gets through}
  296.     BEGIN
  297.       reg.ax := $3301;
  298.       reg.dx := 0;
  299.       MsDos(reg);
  300.     END;                      {brkoff}
  301.  
  302.   PROCEDURE GetFile(N : Window;
  303.                     VAR f : TwoText;
  304.                     VAR p : TwoPath;
  305.                     VAR s : TwoState);
  306.       {-open either of the files for read}
  307.     VAR
  308.       good : Boolean;
  309.       ch : Char;
  310.     BEGIN
  311.       DrawCenterBar(1);
  312.       CursorOn(CursorType);
  313.       REPEAT
  314.         ClearStat(N, 3, 77);
  315.         MoveToStat(N, 3);
  316.         Write('Enter file for this window (<ret> for none): ');
  317.         Read(p[N]);
  318.         s[N] := (p[N] <> ''); {false means no file in window}
  319.         ClearStat(N, 3, 77);
  320.         MoveToStat(N, 3);
  321.         IF s[N] THEN BEGIN
  322.           {see if file exists}
  323.           Assign(f[N], p[N]);
  324.           {$I-} Reset(f[N]);  {$I+}
  325.           good := (IOResult = 0);
  326.           IF good THEN
  327.             Write('File: ', p[N])
  328.           ELSE BEGIN
  329.             Write(p[N], ' not found... press any key to try again');
  330.             Read(Kbd, ch);
  331.           END;
  332.         END ELSE BEGIN
  333.           good := True;
  334.           Write('File: none');
  335.         END;
  336.       UNTIL good;
  337.       DrawCenterBar(2);
  338.       CursorOff(CursorType);
  339.     END;                      {getfile}
  340.  
  341.   PROCEDURE ReadAndExpandLine(VAR f : TextFile; VAR nl : LineBuffer);
  342.       {-read a line from the file and expand tabs, returning a line}
  343.     VAR
  344.       i, o : Byte;
  345.     BEGIN
  346.       ReadLn(f, l);
  347.       i := 1;
  348.       o := 0;
  349.       WHILE i <= len DO BEGIN
  350.         IF l[i] = #9 THEN BEGIN
  351.           {expand tabs}
  352.           o := o+1;
  353.           nl[o] := #32;
  354.           WHILE (o MOD 8) <> 0 DO BEGIN
  355.             o := o+1;
  356.             nl[o] := #32;
  357.           END;
  358.         END ELSE BEGIN
  359.           {insert regular character}
  360.           {could insert a high bit or other filter here}
  361.           o := o+1;
  362.           nl[o] := l[i];
  363.         END;
  364.         i := i+1;
  365.       END;
  366.       {set length of nl}
  367.       nl[0] := Chr(o);
  368.     END;                      {readandexpandline}
  369.  
  370.   PROCEDURE PadBuffer(N : Window;
  371.                       VAR BotPtr, TopPtr, BufPtr, LinNum : TwoInt;
  372.                       VAR NotPadded : TwoState);
  373.       {-assure end of buffer will fill screen with blanks}
  374.     VAR
  375.       cnt : Integer;
  376.     BEGIN
  377.       cnt := 1;
  378.       WHILE cnt < slines DO BEGIN
  379.         {fill with empty lines}
  380.         b[N][TopPtr[N]] := '';
  381.         TopPtr[N] := ModSucc(TopPtr[N]);
  382.         IF TopPtr[N] = BotPtr[N] THEN BEGIN
  383.           {buffer full, compensate}
  384.           BotPtr[N] := ModSucc(BotPtr[N]);
  385.           LinNum[N] := LinNum[N]+1;
  386.         END;
  387.         cnt := cnt+1;
  388.       END;
  389.       NotPadded[N] := False;
  390.     END;                      {padbuffer}
  391.  
  392.   PROCEDURE FillBuff(N : Window;
  393.                      linstart : Integer;
  394.                      VAR BotPtr, TopPtr, BufPtr, LinNum : TwoInt;
  395.                      VAR NotPadded : TwoState);
  396.       {-fill the buffer from the beginning, referenced from linstart}
  397.     VAR
  398.       ch : Char;
  399.     BEGIN
  400.       NotPadded[N] := True;
  401.       BotPtr[N] := 0;
  402.       BufPtr[N] := 0;
  403.       TopPtr[N] := 0;
  404.       LinNum[N] := linstart;  {always holds line num at bottom of buffer}
  405.       IF s[N] THEN BEGIN
  406.         WHILE NOT(EoF(f[N])) AND (TopPtr[N] < BufSize) DO BEGIN
  407.           ReadAndExpandLine(f[N], b[N][TopPtr[N]]);
  408.           TopPtr[N] := TopPtr[N]+1;
  409.           IF KeyPressed THEN Read(Kbd, ch);
  410.         END;
  411.         {pad buffer with blanks for short files}
  412.         IF EoF(f[N]) THEN
  413.           PadBuffer(N, BotPtr, TopPtr, BufPtr, LinNum, NotPadded);
  414.       END;
  415.     END;                      {fillbuff}
  416.  
  417.   FUNCTION CalcScrAdd(r : Byte) : Integer;
  418.       {-return the offset into the screen segment of a given row at column 1}
  419.     VAR
  420.       t : Integer;
  421.     BEGIN
  422.       {fast way of saying 160*(r-1)}
  423.       t := (r-1) SHL 4;
  424.       CalcScrAdd := ((t SHL 2)+t) SHL 1;
  425.     END;                      {calcscradd}
  426.  
  427.   PROCEDURE ScrollWind(r1, r2, lines : Byte);
  428.       {-scroll the region between rows r1 and r2 by lines lines}
  429.       {lines>0 scrolls up, <0 scrolls down, =0 erases window}
  430.     BEGIN
  431.       reg.al := Abs(lines);
  432.       IF lines > 0 THEN reg.ah := 6 ELSE reg.ah := 7;
  433.       reg.cx := (r1-1) SHL 8;
  434.       reg.dx := ((r2-1) SHL 8) OR 79;
  435.       reg.bh := WindColor;
  436.       Intr($10, reg);
  437.     END;                      {scollwind}
  438.  
  439.   PROCEDURE MoveToScreen(N : Window; VAR LeftEdge, BufPtr, OldPtr : TwoInt);
  440.       {-move buffer info to screen}
  441.     VAR
  442.       cnt, r, sadd, left : Integer;
  443.       rt, rb, lines : Byte;
  444.  
  445.     PROCEDURE WriteLine(r, sadd, left : Integer);
  446.         {-write the line of text ref'ed by r to video memory starting at sadd}
  447.       BEGIN
  448.         {assure l right padded with blanks}
  449.         FillChar(l[1], LineSize, #32);
  450.         {put real data at beginning of l}
  451.         l := Copy(b[N][r], left, 80);
  452.  
  453.         IF NoWait THEN BEGIN
  454.           {write to video, no timing interlocks}
  455.           INLINE(
  456.             $A1/ScreenSeg/    {MOV    AX,screenseg}
  457.             $8E/$C0/          {MOV    ES,AX}
  458.             $8B/$7E/$06/      {MOV    DI,[BP+06]}
  459.             $BE/l/            {MOV    SI,offset(l)}
  460.             $46/              {INC    SI, skip length byte}
  461.             $B9/$50/$00/      {MOV    CX,0050}
  462.             $FC/              {CLD    }
  463.             $A4/              {MOVSB    }
  464.             $47/              {INC    DI, skip attribute}
  465.             $E2/$FC           {LOOP    0116}
  466.             );
  467.         END ELSE BEGIN
  468.           {write to video memory with timing interlocks}
  469.           {.F+}
  470.           INLINE(
  471.             {get screenseg into es}
  472.             $A1/ScreenSeg/    {MOV    AX,screenseg}
  473.             $8E/$C0/          {MOV    ES,AX}
  474.  
  475.             {get screen offset sadd into di}
  476.             $8B/$7E/$06/      {MOV    DI,[BP+06]}
  477.  
  478.             {get video status port into dx}
  479.             $8B/$16/VidStatPort/ {MOV    DX,vidstatport}
  480.  
  481.             {point to string data with si, string is in dataseg}
  482.             $BE/l/            {MOV    SI,offset(l)}
  483.             $46/              {INC    SI}
  484.  
  485.             {move 80 bytes to fill one row of screen}
  486.             $B9/$50/$00/      {MOV    CX,0050}
  487.             $FC/              {CLD    }
  488.  
  489.             {loop to write to screen}
  490.             $AC/              {LODSB    }
  491.             $8A/$D8/          {MOV    BL,AL}
  492.             $B4/$09/          {MOV    AH,09}
  493.             $FA/              {CLI    }
  494.             $EC/              {IN    AL,DX}
  495.             $D0/$D8/          {RCR    AL,1}
  496.             $72/$FB/          {JB    0106}
  497.             $EC/              {IN    AL,DX}
  498.             $20/$E0/          {AND    AL,AH}
  499.             $75/$FB/          {JNZ    010B}
  500.             $8A/$C3/          {MOV    AL,BL}
  501.             $AA/              {STOSB    }
  502.             $47/              {INC    DI}
  503.             $FB/              {STI    }
  504.             $E2/$E9           {LOOP    0100}
  505.             {.F+}
  506.             );                {end of inline}
  507.         END;
  508.       END;                    {writeline}
  509.  
  510.     BEGIN
  511.       r := BufPtr[N];         {starting row in buffer}
  512.       rt := 14*N+1;           {starting row on screen}
  513.       left := LeftEdge[N];    {first char of string that we display}
  514.  
  515.       {redraw entire window}
  516.       sadd := CalcScrAdd(rt); {starting address on screen for this window}
  517.       FOR cnt := 1 TO slines DO BEGIN
  518.         {loop through lines, all guaranteed to exist in buffer}
  519.         WriteLine(r, sadd, left);
  520.         {address of next line of screen}
  521.         sadd := sadd+160;
  522.         {next row of buffer}
  523.         r := ModSucc(r);
  524.       END;
  525.  
  526.     END;                      {movetoscreen}
  527.  
  528.   PROCEDURE InitWindow(N : Window;
  529.                        VAR BotPtr, TopPtr, BufPtr, OldPtr,
  530.                        LeftEdge, OldEdge, LinNum : TwoInt;
  531.                        VAR NotPadded : TwoState);
  532.       {-initialize everything for a single window}
  533.     BEGIN
  534.       NotPadded[N] := True;
  535.       FillBuff(N, 1, BotPtr, TopPtr, BufPtr, LinNum, NotPadded);
  536.       LeftEdge[N] := 1;
  537.       OldEdge[N] := 1;
  538.       OldPtr[N] := BufPtr[N];
  539.       MoveToScreen(N, LeftEdge, BufPtr, OldPtr);
  540.       DrawTitle(N);
  541.       ShowLin(N, LinNum, BotPtr, BufPtr);
  542.       ShowCol(N, LeftEdge);
  543.     END;                      {initwindow}
  544.  
  545.   PROCEDURE SwitchN(VAR N : Window);
  546.       {-switch to opposite window}
  547.     BEGIN
  548.       IF s[1-N] THEN
  549.         N := 1-N
  550.       ELSE BEGIN
  551.         {get another file for the unused window}
  552.         GetFile(1-N, f, p, s);
  553.         IF s[1-N] THEN BEGIN
  554.           {got a file, initialize it and window}
  555.           N := 1-N;
  556.           DrawTitle(N);
  557.           InitWindow(N, BotPtr, TopPtr, BufPtr, OldPtr,
  558.           LeftEdge, OldEdge, LinNum, NotPadded);
  559.         END;
  560.       END;
  561.       ShowWindow(N);
  562.     END;                      {switchn}
  563.  
  564.   PROCEDURE UpdateBuff(N : Window;
  565.                        VAR BotPtr, TopPtr, BufPtr, LinNum : TwoInt;
  566.                        VAR NotPadded : TwoState);
  567.       {-determine whether to add to buffer and do so}
  568.     VAR
  569.       buffleft : Integer;
  570.     BEGIN
  571.       {see if we are within linavail of top of buffer}
  572.       IF BuffAfter(N, TopPtr, BufPtr) < LinAvail THEN BEGIN
  573.         {add linavail's worth of buffer}
  574.         buffleft := LinAvail;
  575.         WHILE NOT(EoF(f[N])) AND (buffleft > 0) DO BEGIN
  576.           ReadAndExpandLine(f[N], b[N][TopPtr[N]]);
  577.           buffleft := buffleft-1;
  578.           TopPtr[N] := ModSucc(TopPtr[N]);
  579.           BotPtr[N] := ModSucc(BotPtr[N]);
  580.           LinNum[N] := LinNum[N]+1;
  581.         END;
  582.         IF EoF(f[N]) AND NotPadded[N] THEN
  583.           PadBuffer(N, BotPtr, TopPtr, BufPtr, LinNum, NotPadded);
  584.       END;
  585.     END;                      {updatebuff}
  586.  
  587.   PROCEDURE ShowRight(N : Byte; VAR BufPtr, LeftEdge : TwoInt);
  588.       {-set leftedge so end of longest line shows}
  589.     VAR
  590.       maxlen, len, lin, cnt : Integer;
  591.     BEGIN
  592.       lin := BufPtr[N];
  593.       maxlen := 0;
  594.       {get maximum line length in window}
  595.       FOR cnt := 1 TO slines DO BEGIN
  596.         len := Length(b[N][lin]);
  597.         IF len > maxlen THEN maxlen := len;
  598.         lin := ModSucc(lin);
  599.       END;
  600.       {convert to a leftedge value}
  601.       len := maxlen-79;
  602.       IF len < 1 THEN len := 1;
  603.       {reset leftedge if appropriate}
  604.       IF LeftEdge[N] < len THEN LeftEdge[N] := len;
  605.     END;                      {showright}
  606.  
  607.   PROCEDURE SeekBack(N : Window; backn : Integer;
  608.                      VAR BotPtr, TopPtr, BufPtr, OldPtr, LinNum : TwoInt);
  609.       {-resynchronize buffer from beginning}
  610.       {-backn is the number of lines to move final bufptr back}
  611.     VAR
  612.       linsave, linbegin, r : Integer;
  613.       ch : Char;
  614.     BEGIN
  615.  
  616.       {may take a while, put up a wait sign}
  617.       WaitOn(N);
  618.  
  619.       {get and save current line number - note that bufprior=0 to arrive here}
  620.       linsave := LinNum[N];
  621.  
  622.       {new line to resynchronize beginning of buffer}
  623.       linbegin := linsave-(BufSize SHR 1);
  624.       IF linbegin < 1 THEN linbegin := 1;
  625.  
  626.       {reset file and leave file pointer ready to read linbegin}
  627.       Reset(f[N]);
  628.       Flush(f[N]);
  629.       FOR r := 1 TO linbegin DO BEGIN
  630.         ReadLn(f[N], l);
  631.         {clear keyboard buffer of any strokes built up during delay}
  632.         IF KeyPressed THEN Read(Kbd, ch);
  633.       END;
  634.  
  635.       {fill buffer starting at linbegin}
  636.       FillBuff(N, linbegin, BotPtr, TopPtr, BufPtr, LinNum, NotPadded);
  637.  
  638.       {set bufptr to be at correct line}
  639.       BufPtr[N] := linsave-linbegin-backn;
  640.       {guarantee we redisplay}
  641.       OldPtr[N] := -2;
  642.       WaitOff(N);
  643.     END;                      {seekback}
  644.  
  645.   FUNCTION GetLineNumber(N : Window) : Integer;
  646.       {-return a legal line number to seek}
  647.     VAR
  648.       seeknum : Integer;
  649.       good : Boolean;
  650.       ch : Char;
  651.     BEGIN
  652.       {prompt for the number}
  653.       DrawCenterBar(1);
  654.       CursorOn(CursorType);
  655.       REPEAT
  656.         ClearStat(N, 3, 43);
  657.         MoveToStat(N, 3);
  658.         Write('Enter line number: ');
  659.         {$I-} Read(seeknum);  {$I+}
  660.         good := (IOResult = 0);
  661.         IF NOT(good) THEN BEGIN
  662.           ClearStat(N, 3, 43);
  663.           MoveToStat(N, 3);
  664.           Write('Illegal number... Press key to try again');
  665.           Read(Kbd, ch);
  666.         END ELSE BEGIN
  667.           good := (seeknum >= 1);
  668.           IF NOT(good) THEN BEGIN
  669.             ClearStat(N, 3, 43);
  670.             MoveToStat(N, 3);
  671.             Write('Illegal number... Press key to try again');
  672.             Read(Kbd, ch);
  673.           END;
  674.         END;
  675.       UNTIL good;
  676.       DrawCenterBar(2);
  677.       CursorOff(CursorType);
  678.       GetLineNumber := seeknum;
  679.     END;                      {getlinenumber}
  680.  
  681.   PROCEDURE SeekLine(N : Window; seeknum : Integer;
  682.                      VAR BotPtr, TopPtr, BufPtr, OldPtr, LinNum : TwoInt;
  683.                      VAR NotPadded : TwoState);
  684.       {-seek to a desired line number}
  685.     VAR
  686.       lin : Integer;
  687.  
  688.     PROCEDURE SeekAhead(N : Window; seeknum, lin : Integer;
  689.                         VAR BotPtr, TopPtr, BufPtr, LinNum : TwoInt;
  690.                         VAR NotPadded : TwoState);
  691.         {-seek forward to find line seeknum, starting at line lin}
  692.         {return buffers set up at line found, or at end of file}
  693.       BEGIN
  694.         WHILE (BuffAfter(N, TopPtr, BufPtr) > slines) AND (lin < seeknum) DO BEGIN
  695.           {update file buffer as needed}
  696.           UpdateBuff(N, BotPtr, TopPtr, BufPtr, LinNum, NotPadded);
  697.           lin := lin+1;
  698.           BufPtr[N] := ModSucc(BufPtr[N]);
  699.         END;
  700.       END;                    {seekahead}
  701.  
  702.     BEGIN
  703.  
  704.       WaitOn(N);
  705.  
  706.       lin := CurrLine(N);
  707.       IF seeknum > lin THEN BEGIN
  708.         {seeknum is ahead of us}
  709.         SeekAhead(N, seeknum, lin, BotPtr, TopPtr, BufPtr, LinNum, NotPadded);
  710.       END ELSE IF seeknum >= LinNum[N] THEN BEGIN
  711.         {seeknum is behind us, but in current buffer}
  712.         BufPtr[N] := BotPtr[N];
  713.         lin := LinNum[N];
  714.         SeekAhead(N, seeknum, lin, BotPtr, TopPtr, BufPtr, LinNum, NotPadded);
  715.       END ELSE BEGIN
  716.         {seeknum is behind current buffer}
  717.         {reset}
  718.         Reset(f[N]);
  719.         Flush(f[N]);
  720.         NotPadded[N] := True;
  721.         {refill the buffer}
  722.         FillBuff(N, 1, BotPtr, TopPtr, BufPtr, LinNum, NotPadded);
  723.         {now seeknum is ahead of us}
  724.         SeekAhead(N, seeknum, 1, BotPtr, TopPtr, BufPtr, LinNum, NotPadded);
  725.       END;
  726.  
  727.       WaitOff(N);
  728.     END;                      {seekline}
  729.  
  730.   PROCEDURE SeekString(N : Window;
  731.                        VAR BotPtr, TopPtr, BufPtr, OldPtr, LinNum : TwoInt;
  732.                        VAR NotPadded : TwoState;
  733.                        VAR SearchString : LineBuffer);
  734.       {-search for a string}
  735.     VAR
  736.       notfound : Boolean;
  737.       ch : Char;
  738.       spos : Integer;
  739.  
  740.     FUNCTION StUpcase(s : LineBuffer) : LineBuffer;
  741.         {-return the uppercase of a string}
  742.       VAR
  743.         i : Byte;
  744.       BEGIN
  745.         FOR i := 1 TO Length(s) DO s[i] := UpCase(s[i]);
  746.         StUpcase := s;
  747.       END;                    {stupcase}
  748.  
  749.     BEGIN
  750.       IF SearchString = '' THEN BEGIN
  751.         {get the search string}
  752.         DrawCenterBar(3);
  753.         CursorOn(CursorType);
  754.         ClearStat(N, 3, 43);
  755.         MoveToStat(N, 3);
  756.         Write('Search String: ');
  757.         Read(SearchString);
  758.         SearchString := StUpcase(SearchString);
  759.         DrawCenterBar(2);
  760.         WaitOff(N);
  761.       END;
  762.  
  763.       IF SearchString <> '' THEN BEGIN
  764.         {do the search}
  765.         WaitOn(N);
  766.         notfound := True;
  767.         WHILE notfound AND (BuffAfter(N, TopPtr, BufPtr) > slines) DO BEGIN
  768.           {scan forward through buffer and update buffer as necessary}
  769.           BufPtr[N] := ModSucc(BufPtr[N]);
  770.           notfound := (Pos(SearchString, StUpcase(b[N][BufPtr[N]])) = 0);
  771.           UpdateBuff(N, BotPtr, TopPtr, BufPtr, LinNum, NotPadded);
  772.         END;
  773.         IF notfound THEN BEGIN
  774.           {complain}
  775.           ClearStat(N, 3, 43);
  776.           MoveToStat(N, 3);
  777.           Write('String not found. Press any key...');
  778.           Read(Kbd, ch);
  779.         END ELSE BEGIN
  780.           {make sure string found shows on screen}
  781.           spos := Pos(SearchString, StUpcase(b[N][BufPtr[N]]));
  782.           IF spos < LeftEdge[N] THEN
  783.             {string is off left edge of screen}
  784.             LeftEdge[N] := spos
  785.           ELSE IF (spos+Length(SearchString)) > (LeftEdge[N]+79) THEN
  786.             {string is off right edge of screen}
  787.             LeftEdge[N] := spos+Length(SearchString)-80;
  788.         END;
  789.         WaitOff(N);
  790.       END;
  791.  
  792.     END;                      {seekstring}
  793.  
  794.   PROCEDURE initialize(VAR N : Window);
  795.       {-set up everything}
  796.     VAR
  797.       w : Window;
  798.     BEGIN
  799.       {avoid problems with ^C char used for WordStar scrolling}
  800.       BrkOff;
  801.       {some constants}
  802.       Bufmod := BufSize+1;
  803.       SearchString := '';
  804.       Synced := False;
  805.  
  806.       {initialize selected windows}
  807.       FOR w := 0 TO 1 DO IF s[w] THEN
  808.         InitWindow(w, BotPtr, TopPtr, BufPtr, OldPtr,
  809.         LeftEdge, OldEdge, LinNum, NotPadded);
  810.  
  811.       {pick initial window}
  812.       IF s[0] THEN N := 0 ELSE N := 1;
  813.  
  814.       {show which window is active}
  815.       ShowWindow(N);
  816.     END;                      {initialize}
  817.  
  818.   PROCEDURE CloseUp;
  819.       {-close up to quit}
  820.     BEGIN
  821.       IF s[0] THEN Close(f[0]);
  822.       IF s[1] THEN Close(f[1]);
  823.       GoToXY(1, 25);
  824.       CursorOn(CursorType);
  825.       TextColor(7);
  826.       TextBackground(0);
  827.       Halt;
  828.     END;                      {closeup}
  829.  
  830.   PROCEDURE VideoOff;
  831.       {-avoid snow writing full screen to c/g card}
  832.     BEGIN
  833.       {clear video enable bit}
  834.       Port[VidModePort] := ModePortData AND 247;
  835.     END;
  836.  
  837.   PROCEDURE VideoOn;
  838.       {-reenable video}
  839.     BEGIN
  840.       {set video enable bit}
  841.       Port[VidModePort] := ModePortData OR 8;
  842.     END;
  843.  
  844.   PROCEDURE SaveScreen;
  845.     BEGIN
  846.       Move(Mem[ScreenSeg : 0], SavScr, 4000);
  847.     END;                      {savescreen}
  848.  
  849.   PROCEDURE RestoreScreen;
  850.     BEGIN
  851.       Move(SavScr, Mem[ScreenSeg : 0], 4000);
  852.     END;                      {restorescreen}
  853.  
  854.   PROCEDURE invokeDOS;
  855.       {-start a new DOS shell, then return to browser}
  856.     VAR
  857.       save_ax : Integer;
  858.       ch : Char;
  859.  
  860.     PROCEDURE execute_string(s : PathString; VAR sav_ax : Integer);
  861.         {-execute a command line}
  862.         {-provided by russ nelson, potsdam, ny}
  863.       VAR
  864.         save_ax : Integer;
  865.       CONST
  866.         save_ss : Integer = 0;
  867.         save_sp : Integer = 0;
  868.       BEGIN
  869.         s[Length(s)+1] := ^M;
  870.         INLINE(
  871.           $1E/                {push    ds}
  872.           $55/                {push    bp}
  873.           $2E/$8C/$16/save_ss/ {mov     cs:[save_ss],ss}
  874.           $2E/$89/$26/save_sp/ {mov     cs:[save_sp],sp}
  875.           $8C/$D0/            {mov     ax,ss}
  876.           $8E/$D8/            {mov     ds,ax}
  877.           $8D/$76/< s/        {lea     si,s[bp]}
  878.           $CD/$2E/            {int     2Eh}
  879.           $2E/$8E/$16/save_ss/ {mov     ss,cs:[save_ss]}
  880.           $2E/$8B/$26/save_sp/ {mov     sp,cs:[save_sp]}
  881.           $5D/                {pop     bp}
  882.           $1F/                {pop     ds}
  883.           $89/$46/< save_ax   {mov     save_ax[bp],ax}
  884.           );
  885.         sav_ax := save_ax;
  886.       END;                    {execute_string}
  887.  
  888.     BEGIN
  889.       {store screen}
  890.       IF NOT(NoWait) THEN VideoOff;
  891.       SaveScreen;
  892.       IF NOT(NoWait) THEN VideoOn;
  893.       CursorOn(CursorType);
  894.       TextColor(7);
  895.       TextBackground(0);
  896.  
  897.       {clear screen and put up help}
  898.       ClrScr;
  899.       WriteLn('Type EXIT to return to BROWSE...');
  900.  
  901.       {start new DOS shell}
  902.       execute_string('command', save_ax);
  903.  
  904.       {restore screen}
  905.       TextColor(0);
  906.       TextBackground(7);
  907.       CursorOff(CursorType);
  908.       VideoOff;
  909.       RestoreScreen;
  910.       VideoOn;
  911.  
  912.       {check errors - this particular interrupt doesn't provide many}
  913.       IF save_ax <> 0 THEN BEGIN
  914.         DrawCenterBar(3);
  915.         CursorOn(CursorType);
  916.         ClearStat(N, 3, 43);
  917.         MoveToStat(N, 3);
  918.         Write('Can''t invoke DOS. Press any key...');
  919.         Read(Kbd, ch);
  920.         DrawCenterBar(2);
  921.         WaitOff(N);
  922.       END;
  923.  
  924.     END;                      {invokeDOS}
  925.  
  926.   PROCEDURE ShowHelp;
  927.       {-provide a pop-up help screen}
  928.     CONST
  929.       helpLines = 15;
  930.       helpX = 11;
  931.       helpY = 6;
  932.     TYPE
  933.       HelpArray = ARRAY[1..helpLines] OF STRING[60];
  934.     VAR
  935.       ch : Char;
  936.       i, r : Byte;
  937.     CONST
  938.       ha : HelpArray =
  939.       (
  940.       '╔════════════════════════════════════════════════════════╗',
  941.       '║         Window Browser - by TurboPower Software        ║',
  942.       '║ ┌────────────────────────┐ ┌─────────────────────────┐ ║',
  943.       '║ │   ^Z,^X   line up      │ │  ^S     column left     │ ║',
  944.       '║ │   ^W,^E   line down    │ │  ^D     column right    │ ║',
  945.       '║ │   ^C      page up      │ │  ^Q^S   left of line    │ ║',
  946.       '║ │   ^R      page down    │ │  ^Q^D   right of line   │ ║',
  947.       '║ │   ^Q^R    file home    │ │  ^Q^N   seek line number│ ║',
  948.       '║ │   ^Q^C    file end     │ │  ^Q^F   string search   │ ║',
  949.       '║ │   ^K^R    new file     │ │  ^L     search again    │ ║',
  950.       '║ │   ^K^D    quit         │ │  ^N     switch windows  │ ║',
  951.       '║ │   ^K^I    invoke DOS   │ │  ^B     (un)sync windows│ ║',
  952.       '║ └────────────────────────┘ └─────────────────────────┘ ║',
  953.       '║       Press any key to return to your browsing....     ║',
  954.       '╚════════════════════════════════════════════════════════╝'
  955.       );
  956.  
  957.     PROCEDURE SetScreenAttr(a : Byte);
  958.         {-set entire screen to a given attribute}
  959.       VAR
  960.         i : Integer;
  961.       BEGIN
  962.         i := 1;
  963.         WHILE i < 4000 DO BEGIN
  964.           Mem[ScreenSeg : i] := a;
  965.           i := i+2;
  966.         END;
  967.       END;                    {setscreenattr}
  968.  
  969.     BEGIN
  970.       IF NOT(NoWait) THEN VideoOff;
  971.       SaveScreen;
  972.       SetScreenAttr(7);       {put background in low video}
  973.       IF NOT(NoWait) THEN VideoOn;
  974.       {write help}
  975.       CursorOn(CursorType);
  976.       TextColor(15);
  977.       TextBackground(0);
  978.       r := helpY;
  979.       FOR i := 1 TO helpLines DO BEGIN
  980.         GoToXY(helpX, r);
  981.         Write(ha[i]);
  982.         r := r+1;
  983.       END;
  984.       TextColor(0);
  985.       TextBackground(7);
  986.       CursorOff(CursorType);
  987.       Read(Kbd, ch);
  988.       IF NOT(NoWait) THEN VideoOff;
  989.       RestoreScreen;
  990.       IF NOT(NoWait) THEN VideoOn;
  991.     END;                      {showhelp}
  992.  
  993.   FUNCTION GetKey : Byte;
  994.       {-return a keycode for a legal keystroke}
  995.       {customize here for favorite cursor keys}
  996.       {this is currently set up for WordStar-like keys only}
  997.     VAR
  998.       good : Boolean;
  999.       ch : Char;
  1000.     BEGIN
  1001.       {don't let keystrokes get ahead of the action}
  1002.       WHILE KeyPressed DO Read(Kbd, ch);
  1003.       REPEAT
  1004.         Read(Kbd, ch);
  1005.         good := True;
  1006.         CASE ch OF
  1007.           ^S : GetKey := 9;
  1008.           ^D : GetKey := 10;
  1009.           ^W, ^E : GetKey := 1;
  1010.           ^Z, ^X : GetKey := 2;
  1011.           ^Q : BEGIN
  1012.                  Read(Kbd, ch);
  1013.                  CASE ch OF
  1014.                    ^R, 'r', 'R' : GetKey := 5;
  1015.                    ^C, 'c', 'C' : GetKey := 6;
  1016.                    ^S, 's', 'S' : GetKey := 11;
  1017.                    ^D, 'd', 'D' : GetKey := 12;
  1018.                    ^N, 'n', 'N' : GetKey := 14;
  1019.                    ^F, 'f', 'F' : GetKey := 16;
  1020.                  ELSE
  1021.                    good := False;
  1022.                  END;
  1023.                END;
  1024.           ^K : BEGIN
  1025.                  Read(Kbd, ch);
  1026.                  CASE ch OF
  1027.                    ^X, ^D, 'x', 'X', 'd', 'D' : GetKey := 7;
  1028.                    ^R, 'r', 'R' : GetKey := 13;
  1029.                    ^I, 'i', 'I' : GetKey := 19;
  1030.                  ELSE
  1031.                    good := False;
  1032.                  END;
  1033.                END;
  1034.           ^N : GetKey := 8;
  1035.           ^B : GetKey := 18;
  1036.           ^R : GetKey := 3;
  1037.           ^C : GetKey := 4;
  1038.           ^H : GetKey := 15;
  1039.           ^L : GetKey := 17;
  1040.         ELSE
  1041.           good := False;
  1042.         END;
  1043.       UNTIL good;
  1044.     END;                      {getkey}
  1045.  
  1046.   PROCEDURE update(N : Window);
  1047.       {-update the screen and buffers for window n}
  1048.     BEGIN
  1049.       {update file buffer as needed}
  1050.       UpdateBuff(N, BotPtr, TopPtr, BufPtr, LinNum, NotPadded);
  1051.  
  1052.       IF (OldPtr[N] <> BufPtr[N]) OR (OldEdge[N] <> LeftEdge[N]) THEN BEGIN
  1053.         {update screen data}
  1054.         MoveToScreen(N, LeftEdge, BufPtr, OldPtr);
  1055.         IF OldPtr[N] <> BufPtr[N] THEN BEGIN
  1056.           OldPtr[N] := BufPtr[N];
  1057.           {update line status}
  1058.           ShowLin(N, LinNum, BotPtr, BufPtr);
  1059.         END;
  1060.         IF OldEdge[N] <> LeftEdge[N] THEN BEGIN
  1061.           OldEdge[N] := LeftEdge[N];
  1062.           {update column status}
  1063.           ShowCol(N, LeftEdge);
  1064.         END;
  1065.       END;
  1066.     END;                      {update}
  1067.  
  1068.   PROCEDURE doOp(VAR N : Window; ch : Byte);
  1069.     VAR
  1070.       temp : Integer;
  1071.     BEGIN
  1072.       {-operate based on key}
  1073.       CASE ch OF
  1074.  
  1075.         {scroll one line up}
  1076.         1 : IF BuffPrior(N, BotPtr, BufPtr) > 0 THEN
  1077.               BufPtr[N] := ModPred(BufPtr[N])
  1078.             ELSE IF LinNum[N] > 1 THEN
  1079.               {need to rebuild buffer from beginning}
  1080.               SeekBack(N, 1, BotPtr, TopPtr, BufPtr, OldPtr, LinNum);
  1081.  
  1082.         {scroll one line down}
  1083.         2 : IF BuffAfter(N, TopPtr, BufPtr) > slines THEN
  1084.               BufPtr[N] := ModSucc(BufPtr[N]);
  1085.  
  1086.         {scroll page up}
  1087.         3 : IF BuffPrior(N, BotPtr, BufPtr) > slines THEN
  1088.               FOR temp := 1 TO slines DO BufPtr[N] := ModPred(BufPtr[N])
  1089.             ELSE IF LinNum[N] > 1 THEN
  1090.               {need to rebuild buffer from beginning}
  1091.               SeekBack(N, slines, BotPtr, TopPtr, BufPtr, OldPtr, LinNum)
  1092.             ELSE
  1093.               {set to beginning of file}
  1094.               BufPtr[N] := BotPtr[N];
  1095.  
  1096.         {scroll page down}
  1097.         4 : IF BuffAfter(N, TopPtr, BufPtr) > slines THEN
  1098.               FOR temp := 1 TO slines DO
  1099.                 IF BuffAfter(N, TopPtr, BufPtr) > slines THEN
  1100.                   BufPtr[N] := ModSucc(BufPtr[N]);
  1101.  
  1102.         {home}
  1103.         5 : IF LinNum[N] > 1 THEN BEGIN
  1104.               {reset}
  1105.               WaitOn(N);
  1106.               Reset(f[N]);
  1107.               Flush(f[N]);
  1108.               LeftEdge[N] := 1;
  1109.               NotPadded[N] := True;
  1110.               {refill the buffer}
  1111.               FillBuff(N, 1, BotPtr, TopPtr, BufPtr, LinNum, NotPadded);
  1112.               OldPtr[N] := -2; {guarantee redisplay}
  1113.               WaitOff(N);
  1114.             END ELSE
  1115.               BufPtr[N] := 0;
  1116.  
  1117.         {end}
  1118.         6 : BEGIN
  1119.               IF NOT(EoF(f[N])) THEN WaitOn(N);
  1120.               WHILE NOT(EoF(f[N])) DO BEGIN
  1121.                 BufPtr[N] := TopPtr[N];
  1122.                 UpdateBuff(N, BotPtr, TopPtr, BufPtr, LinNum, NotPadded);
  1123.               END;
  1124.               BufPtr[N] := TopPtr[N];
  1125.               FOR temp := 1 TO slines DO
  1126.                 BufPtr[N] := ModPred(BufPtr[N]);
  1127.               {guarantee redisplay}
  1128.               OldPtr[N] := -2;
  1129.               WaitOff(N);
  1130.             END;
  1131.  
  1132.         {quit}
  1133.         7 : CloseUp;
  1134.  
  1135.         {switch windows}
  1136.         8 : SwitchN(N);
  1137.  
  1138.         {scroll left}
  1139.         9 : IF LeftEdge[N] > 1 THEN
  1140.               LeftEdge[N] := LeftEdge[N]-1;
  1141.  
  1142.         {scroll right}
  1143.         10 : IF LeftEdge[N] < LineSize-79 THEN
  1144.                LeftEdge[N] := LeftEdge[N]+1;
  1145.  
  1146.         {set left edge to beginning of screen}
  1147.         11 : LeftEdge[N] := 1;
  1148.  
  1149.         {set right edge to show longest line}
  1150.         12 : ShowRight(N, BufPtr, LeftEdge);
  1151.  
  1152.         {open new file in current window}
  1153.         13 : BEGIN
  1154.                Close(f[N]);
  1155.                GetFile(N, f, p, s);
  1156.                IF s[N] THEN BEGIN
  1157.                  {open the new window}
  1158.                  InitWindow(N, BotPtr, TopPtr, BufPtr, OldPtr,
  1159.                  LeftEdge, OldEdge, LinNum, NotPadded);
  1160.                  ShowWindow(N);
  1161.                END ELSE BEGIN
  1162.                  {clear the now-empty window}
  1163.                  ScrollWind(1+14*N, 11+14*N, 0);
  1164.                  {make sure other window is open and switch to it}
  1165.                  IF s[1-N] THEN SwitchN(N) ELSE CloseUp;
  1166.                END;
  1167.              END;
  1168.  
  1169.         {seek to line number}
  1170.         14 : BEGIN
  1171.                SeekNumber := GetLineNumber(N);
  1172.                SeekLine(N, SeekNumber,
  1173.                BotPtr, TopPtr, BufPtr, OldPtr, LinNum, NotPadded);
  1174.              END;
  1175.  
  1176.         15 : ShowHelp;
  1177.  
  1178.         {search for a new string}
  1179.         16 : BEGIN
  1180.                SearchString := '';
  1181.                SeekString(N, BotPtr, TopPtr, BufPtr,
  1182.                OldPtr, LinNum, NotPadded, SearchString);
  1183.              END;
  1184.  
  1185.         {search for prior string}
  1186.         17 : SeekString(N, BotPtr, TopPtr, BufPtr,
  1187.           OldPtr, LinNum, NotPadded, SearchString);
  1188.  
  1189.         19 : invokeDOS;
  1190.  
  1191.       END;                    {case}
  1192.     END;                      {doop}
  1193.  
  1194.   BEGIN                       {main}
  1195.  
  1196.     {get screen segment, cursortype, and video port addresses}
  1197.     ScreenSeg := which_screen(CursorType, VidStatPort, VidModePort);
  1198.     {draw screen border and titles}
  1199.     DrawBorder;
  1200.  
  1201.     {get input files}
  1202.     GetFile(0, f, p, s);
  1203.     GetFile(1, f, p, s);
  1204.  
  1205.     {close up if no files}
  1206.     IF NOT(s[0] OR s[1]) THEN BEGIN
  1207.       GoToXY(1, 25);
  1208.       Halt;
  1209.     END;
  1210.  
  1211.     {initialize globals and turn off cursor, return active window}
  1212.     initialize(N);
  1213.  
  1214.     {main loop}
  1215.     WHILE True DO BEGIN
  1216.  
  1217.       {update screen and buffers}
  1218.       update(N);
  1219.       IF Synced AND s[0] AND s[1] THEN update(1-N);
  1220.  
  1221.       {get new keystroke}
  1222.       ch := GetKey;
  1223.  
  1224.       {operate based on keystroke}
  1225.       IF ch = 18 THEN BEGIN
  1226.         {handle window synchronization}
  1227.         Synced := NOT(Synced);
  1228.         ShowWindow(N);
  1229.       END ELSE BEGIN
  1230.         {do any other operation}
  1231.         doOp(N, ch);
  1232.         IF Synced AND s[0] AND s[1] AND (ch <> 15) AND (ch <> 19) THEN BEGIN
  1233.           IF ch = 14 THEN
  1234.             {seek to same line number as other window}
  1235.             SeekLine(1-N, SeekNumber, BotPtr, TopPtr, BufPtr, OldPtr, LinNum, NotPadded)
  1236.           ELSE BEGIN
  1237.             {convert string search to one for same string in other window}
  1238.             IF ch = 16 THEN ch := 17;
  1239.             {do operation in other window}
  1240.             TmpWind := 1-N;
  1241.             doOp(TmpWind, ch);
  1242.           END;
  1243.         END;
  1244.       END;
  1245.  
  1246.     END;                      {while true}
  1247.  
  1248.   END.
  1249.