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