home *** CD-ROM | disk | FTP | other *** search
/ Columbia Kermit / kermit.zip / perqb / pq2mut.pas < prev    next >
Pascal/Delphi Source File  |  2020-01-01  |  43KB  |  1,293 lines

  1. module MenuUtils;
  2.  
  3. { Abstract:
  4.  
  5. {       The procedure GetPList invokes the menues, starting with the 
  6. {       root menu, and returns a 'parse list' containing the 
  7. {       selections the user has made when traversing the menu tree 
  8. {       out to a leaf.
  9.  
  10. {       The user may enter the selections either by typing the commands,
  11. {       or by invoking PopUp-menues.  Online help will always be available,
  12. {       and the user will never have committed himself to any choice before
  13. {       the last choice (i.e. the leaf) has been done.
  14. }
  15.  
  16. {==============================} exports {===================================}
  17.  
  18. imports PopUp from PopUp;
  19.  
  20.  
  21. type
  22.         NodeType     =  ( MenuNode, ParmNode, EndNode );
  23.  
  24.         HelpAddress  =  record
  25.                             BlockNo : integer;
  26.                             Offset  : integer;
  27.                         end;
  28.  
  29.         pMenuEntry   =  ^MenuEntry;     { Pointer to menu hierarchy }
  30.         MenuEntry    =  record
  31.                                 { Where to find help on this item }
  32.                             Help        : HelpAddress;
  33.                                 { How to prompt for next selection }
  34.                             Prompt                  : S25;
  35.                             case Node   : NodeType of
  36.  
  37.                                 MenuNode:   { A real menu } 
  38.                                        (MPtr      : pNameDesc;
  39.                                         NextLevel : array [1..1] 
  40.                                                             of pMenuEntry);
  41.  
  42.                                 ParmNode:   { A leaf, expecting a parameter } 
  43.                                        ();
  44.                                         
  45.                                 EndNode:    { A leaf, no parameter }
  46.                                        ()
  47.                          end;
  48.  
  49.         
  50.         pPListEntry  =  ^PListEntry;    { Parse list pointer }
  51.         PListEntry   =  record          { Parse list item }
  52.                             PrevPList   : pPListEntry;
  53.                             CurrMenu    : pMenuEntry;
  54.                             CmdI        : integer;
  55.                             case Node   : NodeType of
  56.                                         { Menu selection }
  57.                                 MenuNode    : ( NextPList   : pPListEntry;
  58.                                                 Selection   : integer);
  59.                                         { The possible tails of the list }
  60.                                 ParmNode    : ( Arg         : String );
  61.                                 EndNode     : ()
  62.                         end;
  63.                  
  64. procedure   InitMenues;
  65. procedure   DestroyMenues;
  66. function    GetMenu( MenuFName, HelpFName : String ) : pMenuEntry;
  67.  
  68. exception   NoMenuFile( MenuFName : String );  
  69. exception   BadMenuFile( AtLine : Integer );
  70.  
  71. function    GetMenuAnswer(MPtr:pNameDesc;  NPix:integer):integer;
  72. function    PushCmdFile( FileName : String ) : Boolean;
  73. procedure   GetPList( root : pMenuEntry; var PListPtr : pPListEntry );
  74. procedure   DestroyPList( var PListPtr : pPListEntry );
  75.  
  76. {===========================================================================}
  77. {==============================} private {==================================}
  78.  
  79.     
  80.  
  81. imports Memory from Memory;
  82. imports FileSystem from FileSystem;
  83. imports System from System;
  84. imports Screen from Screen;
  85. imports Perq_String from Perq_String;
  86. imports MultiRead from MultiRead;
  87. imports IO_Unit from IO_Unit;
  88. imports IO_Others from IO_Others;
  89. imports IOErrors from IOErrors;
  90. imports Stream from Stream;  
  91.  
  92. const
  93.         HelpCommand = 'HELP';
  94.  
  95.         DefSeg          =     0;
  96.         UseCursorPos    =    -1;
  97.         NotList         = false;
  98.         ColWidth        =     8;
  99.         ScreenWidth     =    75;
  100.         MenuSize        =   200;  { Max. height of menu }
  101.         CommentChar     =   '!';
  102.         NumLevels       =    20;
  103.         Fold            =  true;
  104.         MaxCLine        =   132;  { Max. length of command line }
  105.         TabKey          =   Chr(128);        
  106.         CR              =   Chr( 13);
  107.         Escape          =   Chr( 27);
  108.         BS              =   Chr(  8);
  109.         DEL             =   Chr(127);
  110.         CtrlU           =   Chr( 21);
  111.         CtrlW           =   Chr( 23); 
  112.         CtrlX           =   Chr( 24);
  113.  
  114.         KeyChar         =   Chr( 24);
  115.         CmdFChar        =   Chr( 26);
  116.  
  117. type
  118.     pInt = ^Integer;
  119.  
  120.     CLine = packed array [1..MaxCLine] of char; 
  121.     CBuff = record
  122.                 Prompt      : String;
  123.                 Cmd         : CLine;
  124.                 BufCur      : 0..MaxCLine;  { character index in buffer}
  125.                 CurrPList   : pPListEntry;  { last entry in parse list }
  126.                 Comment     : Boolean;
  127.                 CommPos,
  128.                 HelpPos     : Integer;    
  129.             end;
  130.                 
  131.     ParseResult =
  132.         ( ParsedOK, WantHelp, NotFound, NotUnique );
  133.  
  134.  
  135. var
  136.     NullMenu        : pNameDesc;
  137.     ShowMenues      : boolean;
  138.     CmdStack        : Array [1..NumLevels] of text;
  139.     CmdLevel        : 0..NumLevels;
  140.     PromptChar      : Char;
  141.  
  142.     EndMenu,
  143.     ParmMenu        : pNameDesc;
  144.  
  145. {===========================================================================}
  146.  
  147. procedure RefreshCBuff( VAR CB : CBuff );
  148. VAR I : Integer;
  149. begin
  150.     with CB do begin
  151.         write( Prompt, PromptChar );
  152.         for I := 1 to BufCur-1 do write( Cmd[I] );
  153.     end;
  154. end;
  155.  
  156. {===========================================================================}
  157.  
  158. function CmdEndCBuff( VAR CB : CBuff ) : integer;
  159. VAR I : Integer;
  160. begin
  161.     with CB do 
  162.         if CurrPList=NIL then 
  163.             CmdEndCBuff := 1
  164.         else begin
  165.             I := CurrPList^.CmdI;
  166.             while (Cmd[i]<>' ') and (Cmd[i]<>CR) and 
  167.                   (Cmd[i]<>CommentChar) and (I<BufCur) do 
  168.                 I := I + 1;
  169.             CmdEndCBuff := I;
  170.         end;
  171. end;
  172.  
  173. {===========================================================================}
  174.  
  175. function    PushCmdFile( FileName : String ) : Boolean;
  176.  
  177.         handler ResetError( FileName : PathName );
  178.         begin
  179.              PushCmdFile := False;
  180.              exit( PushCmdFile );
  181.         end;
  182.  
  183. begin
  184.     PushCmdFile := True;
  185.     if CmdLevel<NumLevels then begin
  186.         Reset( CmdStack[CmdLevel+1], FileName );
  187.         CmdLevel := CmdLevel + 1;
  188.         PromptChar := CmdFChar;
  189.     end;
  190. end;
  191.  
  192. {===========================================================================}
  193.  
  194. function GetChar : Char;
  195. var C       : Char;
  196.     Done    : Boolean;
  197. begin
  198.     if CmdLevel=0 then begin
  199.         SCurOn;
  200.         Done := False;
  201.         while not Done do begin
  202.             if (IOCRead( TransKey, C )=IOEIOC) then begin
  203.                 Done := True;
  204.             end else if TabSwitch then begin
  205.                 Done := True;
  206.                 C := TabKey; 
  207.             end;
  208.         end;
  209.         SCurOff;
  210.     end else begin
  211.         if EOF( CmdStack[CmdLevel] ) then begin     { Pop stack }
  212.             Close( CmdStack[CmdLevel] );
  213.             CmdLevel := CmdLevel - 1;
  214.             if CmdLevel=0 then PromptChar := KeyChar;
  215.             C := CR;
  216.         end else
  217.             if EOLn( CmdStack[CmdLevel] ) then begin
  218.                 Read( CmdStack[CmdLevel] , C );
  219.                 C := CR;
  220.             end else
  221.                 Read( CmdStack[CmdLevel], C );
  222.     end;
  223.     GetChar := C;
  224. end;   { GetChar } 
  225.  
  226. {=============================================================================}
  227.  
  228. function FieldWidth( L : integer ):integer;
  229. begin
  230.     FieldWidth := (( L + ColWidth ) div ColWidth ) * ColWidth;
  231. end;
  232.  
  233. {===========================================================================}
  234.  
  235. procedure PushPList( VAR CB : CBuff; NewMenu : PMenuEntry );
  236. var P : pPListEntry;
  237.     I : Integer;
  238. begin
  239.     with CB do begin
  240.         case NewMenu^.Node of
  241.             MenuNode:   New( P, MenuNode );
  242.             ParmNode:   New( P, ParmNode );
  243.             EndNode:    New( P, EndNode );
  244.         end;
  245.         with P^ do begin
  246.             Node := NewMenu^.Node;
  247.             CurrMenu := NewMenu;
  248.             PrevPList := CurrPList;
  249.             I := CmdEndCBuff( CB );
  250.             while ((Cmd[i]=' ') or (Cmd[i]=CR)) and (I<BufCur) do I := I + 1;
  251.             CmdI := I;
  252.             if Node=MenuNode then begin
  253.                 NextPList := NIL;
  254.                 Selection := 0;
  255.             end else if Node=ParmNode then
  256.                 Arg := '';
  257.         end;
  258.         if CurrPList<>NIL then
  259.             CurrPList^.NextPList := P;
  260.         CurrPList := P;
  261.     end;
  262. end; 
  263.  
  264. {===========================================================================}
  265.  
  266. procedure InitCBuff( VAR CB : CBuff; M : pMenuEntry );
  267. begin
  268.     with CB do begin
  269.         Prompt      := M^.Prompt;
  270.         BufCur      := 1;
  271.         CurrPList   := NIL;
  272.         Comment     := False;
  273.         CommPos     := 0;
  274.         HelpPos     := 0;
  275.     end;
  276.     PushPList( CB, M );
  277. end;
  278.  
  279. {===========================================================================}
  280.  
  281. function CComp( C1, C2 : Char ) : Boolean;
  282. begin
  283.     if C1=C2 then
  284.         CComp := true
  285.     else 
  286.         if not Fold then
  287.             CComp := false
  288.         else begin
  289.             if (C1>='a') and (C1<='z') then
  290.                 C1 := Chr( Ord(C1)-Ord('a')+Ord('A') );
  291.             if (C2>='a') and (C2<='z') then
  292.                 C2 := Chr( Ord(C2)-Ord('a')+Ord('A') );
  293.             CComp := C1=C2;
  294.         end;
  295. end;
  296.  
  297. {===========================================================================}
  298.  
  299. procedure IntoCBuff( VAR CB : CBuff;  C : Char );
  300. begin
  301.     with CB do begin
  302.         if BufCur<MaxCLine then begin
  303.             Cmd[BufCur] := C;
  304.             if C>=' ' then      { Echo character }
  305.                 write(C);
  306.             with CurrPList^ do
  307.                 if (CmdI=BufCur) and (C=' ') then
  308.                     CmdI := CmdI + 1;
  309.             BufCur := BufCur + 1;
  310.         end;
  311.     end;
  312. end;
  313.  
  314. {===========================================================================}
  315.  
  316. procedure BackCBuff( VAR CB : CBuff; ToPos : Integer );
  317. VAR I : Integer;
  318. begin
  319.     with CB do begin
  320.         if ToPos>BufCur then ToPos := BufCur;
  321.         if ToPos<1 then ToPos := 1;
  322.         
  323.         if Comment and (ToPos<=CommPos) then
  324.             Comment := False;
  325.  
  326.         for I := BufCur-1 downto ToPos do begin
  327.             if Cmd[I]>=' ' then             { Character was echoed to screen }
  328.                 SClearChar( Cmd[I], RXor );
  329.         end;
  330.         BufCur := ToPos;
  331.  
  332.             { Pop the last entries off the parse list, if necessary }
  333.         while (CurrPList^.CmdI>BufCur) and (CurrPList^.PrevPList<>NIL) do begin
  334.             CurrPList := CurrPList^.PrevPList;
  335.         end;
  336.     
  337.         with CurrPList^ do begin
  338.             if CmdI>BufCur then         { Could not pop last item }
  339.                 CmdI := BufCur;         { Just note that there are no chars }
  340.             if (NextPList<>NIL) and (Node=MenuNode) then begin
  341.                 Selection := 0;
  342.                 DestroyPList( NextPList );
  343.                 NextPList := NIL;
  344.             end;
  345.         end;
  346.         if ToPos<=HelpPos then
  347.             HelpPos := 0;
  348.     end;
  349. end;
  350.  
  351. {===========================================================================}
  352.  
  353. procedure NextCmdCBuff( VAR CB : CBuff );
  354. { Push to next command in buffer }
  355. VAR I : Integer;
  356. begin
  357.     with CB, CurrPList^, CurrMenu^ do begin
  358.         I := CmdEndCBuff( CB );
  359.         if (I<BufCur) then
  360.             if (Selection>1) and (Selection<=MPtr^.NumCommands) then
  361.             begin
  362.                 {$Range-}
  363.                 PushPList( CB, NextLevel[Selection] );
  364.                 {$Range=}
  365.             end else if Selection=1 then begin
  366.                 if HelpPos=0 then
  367.                     HelpPos := CurrPList^.CmdI;
  368.                 PushPList( CB, CurrMenu );
  369.             end;
  370.     end;
  371. end;
  372.  
  373. {===========================================================================}
  374.  
  375. function FindMatch( VAR CB  : CBuff; 
  376.                     VAR Pos : integer ) : Boolean;
  377.  
  378. { Abbreviated command lookup.  Starting from "Pos", see if any command in   }
  379. { command table matches the word starting at CmdI in CB and ending at       }
  380. { BufCur -1 or first space or other delimiting character.                   }
  381.  
  382. var GiveUp                  : Boolean;
  383.     CmdEnd, CmdLen, I, J    : Integer;
  384. begin
  385.     with CB, CurrPList^.CurrMenu^.MPtr^ do begin
  386.  
  387.         CmdEnd := CmdEndCBuff( CB );
  388.         GiveUp := True;
  389.         while (Pos<NumCommands) and (GiveUp) do begin
  390.  
  391.                 { Look if Cmd matches command in table }
  392.             Pos := Pos + 1; 
  393.             I := CurrPList^.CmdI;
  394.             J := 1;
  395.             {$Range-}
  396.             CmdLen := Length(Commands[Pos]);
  397.             GiveUp := False;
  398.             while (I<CmdEnd)  and (not GiveUp) do begin
  399.                 if CComp( Commands[Pos][J], Cmd[I] ) then begin
  400.                     J := J+1;           { Matching characters, step both }
  401.                     I := I+1;           { indices forward in commands    }
  402.                     if (J>CmdLen) and (I<CmdEnd) then 
  403.                         GiveUp := True;
  404.                 end else 
  405.                     if Cmd[I]='-' then begin { Cmd is abbreviated, just  }
  406.                         J := J+1;       { step the other index forward   }
  407.                         if J>CmdLen then        { Need something to match }
  408.                             GiveUp := True;     { this character to!      }
  409.                     end else begin
  410.                         GiveUp := True;
  411.                     end;  
  412.             end;
  413.             {$Range=}
  414.         end;
  415.         
  416.         FindMatch := not GiveUp;
  417.     end;
  418. end;    { FindMatch }
  419.  
  420. {===========================================================================}
  421.  
  422. procedure ShowWord( VAR CB : CBuff );
  423. VAR I : Integer;
  424. begin
  425.     with CB do begin
  426.         write('''');
  427.         I := CurrPList^.CmdI;
  428.         while (Cmd[I]<>' ') and (I<BufCur) do begin
  429.             write(Cmd[I]);
  430.             I := I + 1;
  431.         end;
  432.         write('''');
  433.     end;
  434. end;
  435.  
  436. {===========================================================================}
  437.  
  438. function ParseCBuff( VAR CB : CBuff ) : ParseResult;
  439. VAR I, J : Integer;
  440. begin
  441.     with CB, CurrPList^ do
  442.  
  443.     Case Node of 
  444.         MenuNode:
  445.             begin
  446.                 I := 0; 
  447.                 if not FindMatch( CB, I ) then begin
  448.                     ParseCBuff := NotFound;
  449.                     CurrPList^.Selection := 0;
  450.                 end else begin
  451.                     CurrPList^.Selection := I;
  452.                     J := I;
  453.                     if FindMatch( CB, J ) then begin
  454.                         ParseCBuff := NotUnique;
  455.                     end else begin
  456.                         NextCmdCBuff( CB );
  457.                         ParseCBuff := ParsedOK;
  458.                     end;
  459.                 end;            
  460.             end;
  461.  
  462.         ParmNode:
  463.             begin
  464.                 if BufCur>1 then
  465.                     if (Cmd[BufCur-1]=CR) or (Cmd[BufCur-1]=' ') then begin
  466.                         Adjust( Arg, BufCur-1-CurrPList^.CmdI );
  467.                         I := 1;
  468.                         for J := CurrPList^.CmdI to BufCur-2 do begin
  469.                             Arg[I] := Cmd[J];
  470.                             I := I + 1;
  471.                         end;
  472.                     end;
  473.                 ParseCBuff := ParsedOK;
  474.             end;
  475.             
  476.         EndNode:
  477.             begin
  478.                 if BufCur>1 then
  479.                     if Cmd[BufCur-1]=CR then 
  480.                         if BufCur>CurrPList^.CmdI then begin
  481.                             writeln;
  482.                             write('?Garbage at end of line, ignored ''');
  483.                             for I := CurrPList^.CmdI to BufCur-2 do 
  484.                                 write( Cmd[I] );
  485.                             writeln('''');
  486.                             RefreshCBuff( CB );
  487.                         end;
  488.                 ParseCBuff := ParsedOK;
  489.             end;
  490.     end;
  491. end;
  492.  
  493. {===========================================================================}
  494.  
  495. function    ParseAll( VAR CB : CBuff ) : ParseResult;
  496. { -- Reparse command buffer as far as possible }
  497. var PRes        : ParseResult;
  498.     PrevCmdI,
  499.     TempPos     : Integer;
  500.     TempChar    : Char;
  501. begin
  502.     with CB do begin
  503.         if Comment then begin
  504.             TempPos := BufCur;
  505.             BufCur := CommPos + 1;
  506.             TempChar := Cmd[CommPos];
  507.             Cmd[CommPos] := ' ';
  508.         end;
  509.         if (CmdEndCBuff(CB)<>CurrPList^.CmdI) then begin
  510.             repeat
  511.                 PrevCmdI := CurrPList^.CmdI;
  512.                 PRes := ParseCBuff(CB);
  513.             until (PRes<>ParsedOK) or (PrevCmdI=CurrPList^.CmdI)
  514.                         or (CmdEndCBuff(CB)=CurrPList^.CmdI);
  515.             ParseAll := PRes;
  516.         end else
  517.             ParseAll := ParsedOK;
  518.  
  519.         if Comment then begin
  520.             Cmd[CommPos] := TempChar;
  521.             BufCur := TempPos; 
  522.         end;
  523.     end;    
  524. end;
  525.  
  526. {===========================================================================}
  527.  
  528. procedure ParseCommand(     root        : pMenuEntry; 
  529.                         var PListPtr    : pPListEntry;
  530.                             HelpMode,
  531.                             RootLevel   : Boolean );
  532.  
  533. const
  534.     MoreInfo = 'More info on:';
  535.     SelPrompt = 'Select item:';
  536.     SelectOne = 'Select one of the following: ';
  537.     CommNotUnique = '?Command is not unique: ';
  538. var
  539.     C                   : Char;
  540.     Done, QuestionMark  : boolean;
  541.     NextMatch,
  542.     I, J, CmdEnd        : integer;
  543.     Matching            : S25;
  544.     CB                  : CBuff;       { Command buffer to use}
  545.     TabPress            : Boolean;     { Select done by menu? }
  546.     PRes                : ParseResult;
  547.     Dummy, ArgEntry     : pPListEntry;
  548.  
  549.     HelpFile    : pInt;
  550.     HelpFID     : integer;
  551.     HFBuff      : pDirBlk;
  552.     HFAddr      : HelpAddress;
  553.     MM          : MMPointer;
  554.  
  555.  
  556.     handler HelpKey( var retStr : Sys9s );
  557.     begin
  558.         retStr := 'HELP';
  559.     end;
  560.     
  561.     {------------------------------------------------------------------------}
  562.  
  563.     procedure PrintHelpText;
  564.     var PrevCR      : boolean;
  565.     begin
  566.         if HelpFID=0 then 
  567.             writeln('No helptext found!')
  568.         else
  569.             with CB.CurrPList^.CurrMenu^ do begin
  570.                 if HFaddr.BlockNo<>Help.BlockNo then
  571.                     FSBlkRead( HelpFID, Help.BlockNo, HFBuff ); 
  572.                 HFAddr := Help;
  573.                 PrevCR := true;
  574.                 with HFAddr, HFBuff^ do 
  575.                     while not( PrevCR and (ByteBuffer[Offset]=ord('>'))) do
  576.                     begin
  577.                         PrevCR := ByteBuffer[Offset]=13;
  578.                         write( chr(ByteBuffer[Offset]) );
  579.                         if PrevCR then write( chr(10) );
  580.                         Offset := Offset+1;
  581.                         if Offset>511 then begin
  582.                             Offset := 0;
  583.                             BlockNo := BlockNo + 1;
  584.                             FSBlkRead( HelpFID, BlockNo, HFBuff );
  585.                         end;
  586.                     end;
  587.             end;
  588.     end;  { PrintHelpText }  
  589.  
  590.     {------------------------------------------------------------------------}
  591.  
  592.     procedure PrintAlts;
  593.     var i,l,w,s         : integer;
  594.         Matching        : S25;
  595.     begin
  596.         L := 0;
  597.         with CB.CurrPList^.CurrMenu^, MPtr^ do 
  598.         if Node=MenuNode then begin
  599.  
  600.             if HelpMode then 
  601.                 writeln( MoreInfo )
  602.             else
  603.                 writeln( SelectOne );
  604.             for i := 2 to NumCommands do begin
  605.                 {$range-}
  606.                 Matching := Commands[i];
  607.                 S := Length( Matching );
  608.                 W := FieldWidth( S );
  609.                 L := L+W;
  610.                 if L < ScreenWidth then
  611.                     write( Matching, ' ':(W-S) )
  612.                 else if L = ScreenWidth then begin
  613.                     writeln( Matching ); 
  614.                     L := 0;
  615.                 end else begin
  616.                     writeln;
  617.                     write( Matching, ' ':(W-S) );
  618.                     L := W;
  619.                 end;
  620.                 {$range=} 
  621.             end;
  622.  
  623.         end;
  624.         if L<>0 then writeln;
  625.     end;
  626.  
  627.     {------------------------------------------------------------------------}
  628.     
  629.     procedure PrintMatching;
  630.     var i,l,w,s         : integer;
  631.         Matching        : S25;
  632.     begin
  633.         L := 0;
  634.         I := 0;
  635.         writeln( SelectOne );
  636.         with CB.CurrPList^.CurrMenu^.MPtr^ do 
  637.             while FindMatch( CB, I ) do begin
  638.                 {$Range-}
  639.                 Matching := Commands[I];
  640.                 {$Range=}
  641.                 S := Length( Matching );
  642.                 W := FieldWidth( S );
  643.                 L := L+W;
  644.                 if L < ScreenWidth then
  645.                     write( Matching, ' ':(W-S) )
  646.                 else if L = ScreenWidth then begin
  647.                     writeln( Matching ); 
  648.                     L := 0;
  649.                 end else begin
  650.                     writeln;
  651.                     write( Matching, ' ':(W-S) );
  652.                     L := W;
  653.                 end;
  654.             end;
  655.         if L<>0 then writeln;
  656.     end;
  657.  
  658.     {------------------------------------------------------------------------}
  659.  
  660.     procedure DoHelp;
  661.     begin
  662.         writeln;
  663.         writeln;
  664.         PrintHelpText;
  665.         writeln;
  666.         PrintAlts;
  667.         writeln;
  668.     end;
  669.  
  670.     {------------------------------------------------------------------------}
  671.     
  672.     procedure ExplainHelp;
  673.     begin
  674.         writeln;
  675.         writeln;
  676.         write('HELP - online help facility');
  677.         writeln;
  678.         writeln('Use the "HELP" command to obtain command explanations');
  679.         writeln('"HELP" may replace any command, and the effect will be to');
  680.         writeln('explain this command and list the various alternatives.');
  681.         writeln;
  682.         writeln('"HELP" may be used in different ways: ');
  683.         writeln('"HELP" as the last command on the line, before RETURN, will');
  684.         writeln('enter the help mode, where every command entered not is ');
  685.         writeln('executed, but explained.  Exit help mode by entering an ');
  686.         writeln('empty line.');
  687.         writeln('When the "HELP" command is not at the end of the line, ');
  688.         writeln('the result will be to explain the commands after HELP ');
  689.         writeln('and then continue entering commands to execute.'); 
  690.         writeln;
  691.         writeln('Function keys:');
  692.         writeln('RETURN (CR) terminates the command and executes it.  If ');
  693.         writeln('   the command is partially entered, the command tail will ');
  694.         writeln('   be prompted for.  The command may then be aborted by ');
  695.         writeln('   entering a blank line.');
  696.         writeln('INS (ESC) expands the last command on the line, if it is ');
  697.         writeln('   abbreviated, and it is unique.  Use to check if a valid');
  698.         writeln('   command is entered, and that the abbreviation really');
  699.         writeln('   identifies the correct command.');
  700.         writeln('''?'' lists the commands that matches an abbreviation. ');
  701.         writeln('''??'' enters help mode. ');
  702.         writeln('''!'' is a comment delimiter.  (Most useful in command ');
  703.         writeln('   files.)  Everything between ''!'' and end of line is ');
  704.         writeln('   ignored.'); 
  705.         writeln('BACKSPACE, DEL deletes the last character on the line.');
  706.         writeln('OOPS, Ctrl-U, Ctrl-X deletes the whole line.');
  707.         writeln('Ctrl-W deletes the last word (back to previous space) ');
  708.         writeln;
  709.     end;    { ExplainHelp }
  710.  
  711.     {------------------------------------------------------------------------}
  712.  
  713.  
  714. begin  { GetPList } 
  715.     MM := recast( Root, MMPointer );
  716.     HelpFile := MakePtr( MM.Segmen, 0, pInt );
  717.     HelpFID := HelpFile^;
  718.     HFAddr.BlockNo := -1;       { Note help buffer is empty }
  719.     new( HFBuff);
  720.  
  721.     Done := false;
  722.     InitCBuff( CB, Root );
  723.     if HelpMode then begin
  724.         DoHelp;
  725.         CB.Prompt := SelPrompt;
  726.     end;
  727.     RefreshCBuff( CB );
  728.     PListPtr := CB.CurrPList; 
  729.     QuestionMark := False;
  730.  
  731.     with CB do
  732.       while not Done do begin
  733.       
  734.         C := GetChar;
  735.  
  736.         if (C=TabKey) then begin                { Insert dummy space to     }
  737.             IntoCBuff( CB, ' ' );               { make parse go all the way }
  738.             PRes := ParseAll(CB);               { to the end of buffer.     }
  739.             BackCBuff( CB, BufCur-1 );          { Remove the dummy space.   }
  740.             if BufCur>CurrPList^.CmdI then
  741.                 BackCBuff( CB, CurrPList^.CmdI );      { ..partial command }
  742.             Dummy := CurrPList;
  743.             repeat
  744.                 case CurrPList^.Node of
  745.                 
  746.                 MenuNode:
  747.                     begin
  748.                         I := GetMenuAnswer( CurrPList^.CurrMenu^.MPtr, 
  749.                                                 MenuSize );
  750.                         if I>1 then begin
  751.                             CurrPList^.Selection := I;
  752.                             {$Range-}
  753.                             Matching := CurrPList^.CurrMenu^.MPtr^.Commands[i];
  754.                             {$Range=}
  755.                             for J := 1 to length(Matching) do begin
  756.                                 IntoCBuff(CB,Matching[j]);
  757.                             end;
  758.                             IntoCBuff(CB, ' ');
  759.                             NextCmdCBuff(CB); 
  760.                         end;
  761.                     end;
  762.  
  763.                 EndNode:
  764.                     begin
  765.                         if HelpMode then begin
  766.                             I := 1;
  767.                         end else 
  768.                             I := GetMenuAnswer( EndMenu, MenuSize );
  769.                         if I=2 then I := -1;
  770.                     end;
  771.                 
  772.                 ParmNode:
  773.                     begin 
  774.                         if HelpMode then begin
  775.                             I := 1;
  776.                         end else 
  777.                             I := GetMenuAnswer( ParmMenu, MenuSize );
  778.                         if I=2 then begin
  779.                             writeln;
  780.                             ParseCommand( CurrPList^.CurrMenu, ArgEntry, 
  781.                                     HelpMode, false );
  782.                             CurrPList^.Arg := ArgEntry^.Arg;
  783.                             DestroyPList( ArgEntry );
  784.                             I := -1;
  785.                         end else if I=3 then begin
  786.                             CurrPList^.Arg := '';
  787.                             I := -1;
  788.                         end;
  789.                     end;
  790.                 end;
  791.  
  792.                 if I=1 then begin 
  793.                     writeln;
  794.                     writeln;
  795.                     PrintHelpText;
  796.                     writeln;
  797.                     write('Press tabswitch to get menu back: ');
  798.                     while TabSwitch do ;
  799.                     while not TabSwitch do ;
  800.                     writeln(CR,'                                     ' );
  801.                     RefreshCBuff(CB);
  802.                 end;
  803.  
  804.                 if (I=0) or ((I=1) and (CurrPList^.Node<>MenuNode))
  805.                 then begin       { Pop off command }
  806.                     if CurrPList<>Dummy then begin
  807.                         BackCBuff( CB, CurrPList^.PrevPList^.CmdI );
  808.                     end;
  809.                 end;
  810.  
  811.                 if (I=-1) and not HelpMode then begin
  812.                     writeln;
  813.                     Done := True;
  814.                 end;
  815.  
  816.             until Done or (CurrPList=Dummy);
  817.  
  818.         end else
  819.         
  820.         if (C=CommentChar) then begin
  821.             if not Comment then begin
  822.                 Comment := True;
  823.                 CommPos := BufCur;
  824.             end;
  825.             IntoCBuff( CB, C );
  826.         end else 
  827.  
  828.         if (C=CR) then
  829.         begin
  830.             IntoCBuff( CB, ' ' );
  831.             case ParseAll( CB ) of
  832.  
  833.             ParsedOK:
  834.                 if HelpMode then begin
  835.                     Done := CurrPList^.PrevPList=NIL;
  836.                     if CurrPList^.Selection=1 then 
  837.                         ExplainHelp
  838.                     else begin
  839.                         writeln;
  840.                         if not Done then begin
  841.                             DoHelp;
  842.                             if CurrPList^.Node<>MenuNode then
  843.                                 BackCBuff( CB, CurrPList^.PrevPList^.CmdI )
  844.                             else 
  845.                                 BackCBuff( CB, BufCur-1 );
  846.                             RefreshCBuff(CB); 
  847.                         end;
  848.                     end;
  849.                 
  850.                 end else begin
  851.                     writeln;
  852.                     with CurrPList^ do
  853.  
  854.                     if HelpPos>0 then begin
  855.                         if PrevPList^.CmdI=HelpPos then begin { HELP last com.}
  856.                             writeln;
  857.                             ParseCommand( CurrPList^.CurrMenu, Dummy, 
  858.                                     True, false );
  859.                             DestroyPList( Dummy );
  860.                         end else begin
  861.                             writeln;
  862.                             PrintHelpText;
  863.                             writeln;
  864.                             if Node=MenuNode then begin
  865.                                 PrintMatching;
  866.                                 writeln;
  867.                             end;
  868.                         end;
  869.                         RefreshCBuff(CB);
  870.  
  871.                     end else if (CurrMenu=Root) and (Node=MenuNode) then
  872.                         PListPtr := NIL      { Nothing parsed (or a new}
  873.                                              { entry would have been pushed)}
  874.                     else begin           
  875.                         if Node=MenuNode then begin
  876.                               { OK so far, but haven't got all of command }
  877.                             ParseCommand( CurrMenu, Dummy, 
  878.                                     false, false );
  879.                             if (Dummy=NIL) then begin   { Quit command }
  880.                                 DestroyPList(PListPtr);
  881.                                 PListPtr := NIL;
  882.                             end else begin              { link in cmd tail }
  883.                                 CurrPList^.PrevPList^.NextPList := Dummy;
  884.                                 Dummy^.PrevPList := CurrPList^.PrevPList;
  885.                                 DestroyPList(CurrPList);
  886.                                 CurrPList := Dummy;
  887.                             end;
  888.                         end;
  889.                     end;
  890.                     if HelpPos>0 then
  891.                         BackCBuff( CB, HelpPos )
  892.                     else
  893.                         Done := true;
  894.                 end;
  895.  
  896.             NotUnique:
  897.                 begin
  898.                     BackCBuff( CB, BufCur-1 );
  899.                     writeln;
  900.                     write( CommNotUnique );
  901.                     ShowWord( CB ); 
  902.                     writeln;
  903.                     PrintMatching;
  904.                     if CmdLevel>0 then begin
  905.                         RefreshCBuff( CB );
  906.                         BackCBuff( CB, 1 )
  907.                     end else begin
  908.                         BackCBuff(CB, CmdEndCBuff(CB));
  909.                         RefreshCBuff( CB );
  910.                     end;
  911.                 end;
  912.  
  913.             NotFound:
  914.                 begin
  915.                     BackCBuff( CB, BufCur-1 );
  916.                     writeln;
  917.                     write('?No match for word: ');
  918.                     ShowWord(CB);
  919.                     writeln; 
  920.                     PrintAlts;
  921.                     RefreshCBuff( CB );  { ... and start over }
  922.                     if CmdLevel>0 then
  923.                         BackCBuff( CB, 1 );
  924.                 end;
  925.  
  926.             end;
  927.             QuestionMark := false;
  928.  
  929.         end else
  930.  
  931.         if (C='?') and (not Comment) then begin
  932.  
  933.             PRes := ParseAll( CB );
  934.             if QuestionMark and not HelpMode then begin
  935.                 writeln;
  936.                 ParseCommand( CurrPList^.CurrMenu, Dummy, True, false );
  937.                 DestroyPList( Dummy );
  938.                 QuestionMark := False;
  939.                 RefreshCBuff( CB );
  940.  
  941.             end else begin
  942.  
  943.                 case PRes of 
  944.                 
  945.                 ParsedOK:
  946.                     if HelpMode then begin
  947.                         writeln('?');
  948.                         DoHelp;
  949.                         RefreshCBuff(CB);
  950.                     end else if BufCur=CurrPList^.CmdI then
  951.                     begin
  952.                         writeln('?');
  953.                         PrintAlts;
  954.                         RefreshCBuff(CB);
  955.                     end;
  956.                 
  957.                 NotFound:
  958.                     begin
  959.                         writeln('?');
  960.                         write('?No match for word: ');
  961.                         ShowWord(CB);
  962.                         writeln; 
  963.                         if CmdLevel>0 then begin
  964.                             RefreshCBuff( CB );
  965.                             BackCBuff( CB, 1 )
  966.                         end else begin
  967.                             PrintAlts;
  968.                             RefreshCBuff( CB );  { ... and start over }
  969.                         end;
  970.                     end;
  971.                     
  972.                 NotUnique:
  973.                     begin
  974.                         writeln('?');
  975.                         PrintMatching;
  976.                         QuestionMark := True;
  977.                         if CmdLevel>0 then begin
  978.                             RefreshCBuff( CB );
  979.                             BackCBuff( CB, 1 );
  980.                         end else begin
  981.                             BackCBuff(CB, CmdEndCBuff(CB));
  982.                             RefreshCBuff( CB );
  983.                         end;
  984.                     end;
  985.                 end;
  986.                 
  987.                 QuestionMark := True;
  988.             end;
  989.  
  990.         end else
  991.  
  992.         if (C=Escape) and (not Comment) then begin
  993.              
  994.             QuestionMark := False;
  995.  
  996.             if BufCur>CurrPList^.CmdI then begin
  997.  
  998.                 PRes := ParseAll(CB);
  999.                 case PRes of
  1000.         
  1001.                 ParsedOK:
  1002.                     begin
  1003.                         CmdEnd := CmdEndCBuff(CB);
  1004.                         if CmdEnd=BufCur then
  1005.                             with CurrPList^ do begin
  1006.                                 {$Range-}
  1007.                                 Matching := 
  1008.                                     CurrMenu^.MPtr^.Commands[Selection];
  1009.                                 {$Range=}
  1010.                                 I := CmdI;
  1011.                                 J := 1;
  1012.                                 while (I<CmdEnd) and (J<=Length(Matching)) 
  1013.                                 do begin
  1014.                                     if CComp( Matching[J], Cmd[I] ) then begin
  1015.                                         J := J+1;
  1016.                                         I := I+1;
  1017.                                     end else begin
  1018.                                         if Cmd[I]='-' then begin
  1019.                                             J := J+1;
  1020.                                         end;
  1021.                                     end;
  1022.                                 end;
  1023.                                 for I := J to Length(Matching) do begin
  1024.                                     IntoCBuff( CB, Matching[I] );
  1025.                                 end;
  1026.                                 if PRes=ParsedOK then { expect more commands }
  1027.                                 begin
  1028.                                     IntoCBuff( CB, ' ' );
  1029.                                 end;        
  1030.                             end;
  1031.                     end;
  1032.                     
  1033.                 NotFound:
  1034.                     begin
  1035.                         write('?No match for word: ');
  1036.                         ShowWord(CB); 
  1037.                         writeln;
  1038.                         if CmdLevel>0 then begin
  1039.                             RefreshCBuff( CB );  { ... and start over }
  1040.                             BackCBuff( CB, 1 );
  1041.                         end else begin
  1042.                             PrintAlts;
  1043.                             RefreshCBuff( CB );  { ... and start over }
  1044.                         end;
  1045.                     end;
  1046.                     
  1047.                 NotUnique:
  1048.                     begin 
  1049.                         writeln;
  1050.                         write(CommNotUnique);
  1051.                         ShowWord(CB);
  1052.                         writeln;
  1053.                         if CmdLevel>0 then begin
  1054.                             RefreshCBuff( CB );
  1055.                             BackCBuff( CB, 1 )
  1056.                         end else begin
  1057.                             BackCBuff(CB, CmdEndCBuff(CB));
  1058.                             PrintMatching;
  1059.                             RefreshCBuff( CB );
  1060.                         end;
  1061.                     end;
  1062.  
  1063.                 end;
  1064.             end;
  1065.         end else
  1066.         
  1067.         if (C=BS) or (C=DEL) then begin
  1068.             if BufCur=1 then
  1069.                 write( chr(7) )
  1070.             else
  1071.                 BackCBuff( CB, BufCur-1 );
  1072.             QuestionMark := False;
  1073.         end else 
  1074.  
  1075.         if (C=CtrlW) then begin
  1076.             if (CurrPList^.CmdI=BufCur) then begin
  1077.                 if CurrPList^.PrevPList<>NIL then
  1078.                     BackCBuff( CB, CurrPList^.PrevPList^.CmdI );
  1079.             end else 
  1080.                 BackCBuff(CB, CurrPList^.CmdI );
  1081.             QuestionMark := False;
  1082.         end else
  1083.  
  1084.         if (C=CtrlX) or (C=CtrlU) then begin
  1085.             BackCBuff( CB, 1 );
  1086.             QuestionMark := False;
  1087.         end else 
  1088.  
  1089.         begin { normal character }
  1090.             QuestionMark := False;
  1091.             if (C>=' ') and (C<DEL) then begin
  1092.                 IntoCBuff( CB, C );
  1093.             end;       
  1094.         end;
  1095.  
  1096.     end { while };
  1097.     dispose( HFBuff );
  1098.     
  1099. end;    { ParseCommand }
  1100.  
  1101. {===========================================================================}
  1102.  
  1103. function    GetMenuAnswer( MPtr:pNameDesc;  NPix:integer ):integer;
  1104.     {   Returns 0 for press outside menu }
  1105. var     ResPtr  :   ResRes;
  1106.  
  1107.     Handler OutSide;
  1108.     begin
  1109.         ResPtr:=NIL;
  1110.         exit(Menu);
  1111.     end;  { OutSide }
  1112.  
  1113. begin { GetMenuAnswer }
  1114.     Menu(   MPtr,
  1115.             NotList,
  1116.             1,
  1117.             MPtr^.NumCommands,
  1118.             UseCursorPos,
  1119.             UseCursorPos,
  1120.             NPix,   {Number of pixels (height)}
  1121.             ResPtr);
  1122.     if ResPtr <> NIL then begin
  1123.         GetMenuAnswer := ResPtr^.Indices[1];
  1124.         DestroyRes( ResPtr );
  1125.     end
  1126.     else
  1127.         GetMenuAnswer := 0;
  1128. end; { GetMenuAnswer }
  1129.  
  1130. {=============================================================================}
  1131.  
  1132. procedure   DestroyPList( var PListPtr : pPListEntry );
  1133. var Trail : pPListEntry;
  1134. begin
  1135.     while PListPtr<>NIL do begin
  1136.         Trail := PListPtr;
  1137.         case Trail^.Node of
  1138.         
  1139.             EndNode:
  1140.                 begin
  1141.                     PListPtr := NIL;
  1142.                     dispose( Trail, EndNode );
  1143.                 end;
  1144.             
  1145.             ParmNode:
  1146.                 begin
  1147.                     PListPtr := NIl;
  1148.                     dispose( Trail, ParmNode );
  1149.                 end;
  1150.                
  1151.             MenuNode:
  1152.                 begin
  1153.                     PListPtr := Trail^.NextPList;
  1154.                     Trail^.NextPList := NIL;
  1155.                     dispose( Trail, MenuNode );
  1156.                 end;
  1157.         end;
  1158.     end;
  1159. end;  
  1160.     
  1161. {=============================================================================}
  1162.  
  1163. procedure   GetPList(           Root : pMenuEntry; 
  1164.                         var PListPtr : pPListEntry ); 
  1165.  
  1166. begin
  1167.     SCurOn;
  1168.     PListPtr := NIL;
  1169.     ParseCommand( Root, PListPtr, false, true );
  1170.     SCurOff;
  1171. end;
  1172.  
  1173. {=============================================================================}
  1174.  
  1175. function GetMenu( MenuFName, HelpFName : String ) : pMenuEntry;
  1176.  
  1177. VAR MenuFile            : Text;
  1178.     Blk, Bits           : Integer;
  1179.     SegSize             : Integer;
  1180.     MenuF               : FileID;
  1181.     Root                : pMenuEntry;
  1182.     MMP                 : MMPointer;
  1183.     HelpFile            : pInt;
  1184.     MenuSeg, OldSeg     : SegmentNumber;                      
  1185.  
  1186.     exception BadMenuSeg;
  1187.  
  1188.     handler BadMenuSeg;
  1189.     begin
  1190.         GetMenu := NIL;
  1191.         exit( GetMenu );
  1192.     end;
  1193.     
  1194.     procedure FixPointer( var ME : pMenuEntry );
  1195.     var MME : record case boolean of
  1196.                 true:   ( MM : MMPointer);
  1197.                 false:  ( E  : pMenuEntry);
  1198.              end;
  1199.     begin
  1200.         with MME do begin
  1201.             E := ME;
  1202.             with MM do begin
  1203.                 if (Segmen<>OldSeg) or (Offset>SegSize) then
  1204.                     raise BadMenuSeg;
  1205.                 Segmen := MenuSeg;
  1206.             end;
  1207.             ME := E;
  1208.         end;
  1209.     end;
  1210.                       
  1211.     procedure ValidatePtrs( ME : pMenuEntry );
  1212.     var i       : integer;
  1213.         TME     : pMenuEntry;
  1214.     begin
  1215.         with ME^ do begin
  1216.             case Node of            
  1217.                 MenuNode:
  1218.                     begin
  1219.                         TME := recast( MPtr, pMenuEntry );
  1220.                         FixPointer( TME );
  1221.                         MPtr := recast( TME, pNameDesc );
  1222.                         for i := 2 to MPtr^.NumCommands do begin 
  1223.                             {$range-}
  1224.                             FixPointer( NextLevel[i] );
  1225.                             ValidatePtrs( NextLevel[i] );
  1226.                             {$range=}
  1227.                         end;
  1228.                     end;
  1229.        
  1230.                 EndNode, ParmNode:
  1231.                     ;
  1232.             end;
  1233.         end;
  1234.     end;
  1235.  
  1236. begin
  1237.     MenuF := FSLookUp( MenuFName, Blk, Bits );
  1238.     if MenuF=0 then
  1239.         raise NoMenuFile( MenuFName )
  1240.     else begin
  1241.         CreateSegment( MenuSeg, Blk, 1, Blk );
  1242.         SegSize := (Blk-1)*256 + (Bits div 16);
  1243.         Root := MakePtr( MenuSeg, WordSize( integer ), pMenuEntry );
  1244.         MultiRead( MenuF, MakePtr( MenuSeg, 0, pDirBlk ), 0, Blk ); 
  1245.         MMP := recast( Root^.MPtr, MMPointer );
  1246.         OldSeg := MMP.Segmen;
  1247.         ValidatePtrs( Root );
  1248.         HelpFile := MakePtr( MenuSeg, 0, pInt );
  1249.         HelpFile^ := FSLookUp( HelpFName, Blk, Bits );  
  1250.     end;
  1251.     GetMenu := Root;
  1252. end;
  1253.  
  1254. {=============================================================================}
  1255.  
  1256. procedure InitMenues;
  1257. begin
  1258.     {$Range-}
  1259.     AllocNameDesc( 1, DefSeg, NullMenu );
  1260.     with NullMenu^ do begin
  1261.         Header := 'Confirm:';
  1262.         Commands[1] := '?';
  1263.     end;
  1264.     AllocNameDesc( 2, DefSeg, EndMenu );
  1265.     with EndMenu^ do begin
  1266.         Header := 'Confirm selection:';
  1267.         Commands[1] := '?';
  1268.         Commands[2] := 'Perform command';
  1269.     end;
  1270.     AllocNameDesc( 3, DefSeg, ParmMenu );
  1271.     with ParmMenu^ do begin
  1272.         Header := 'Command arguments:';
  1273.         Commands[1] := '?';
  1274.         Commands[2] := 'Enter arguments';
  1275.         Commands[3] := 'No arguments';
  1276.     end;
  1277.     {$Range=}
  1278.     InitPopUp;
  1279.     IOCursorMode(TrackCursor);
  1280.     CmdLevel := 0;
  1281.     PromptChar := KeyChar;
  1282. end;
  1283.  
  1284.  
  1285. {=============================================================================}
  1286.  
  1287.     
  1288. procedure DestroyMenues;
  1289. var CI : integer;
  1290. begin
  1291.     DestroyNameDescr( NullMenu );
  1292. end.
  1293.