module MenuUtils;
 
{ Abstract:

{       The procedure GetPList invokes the menues, starting with the 
{       root menu, and returns a 'parse list' containing the 
{       selections the user has made when traversing the menu tree 
{       out to a leaf.

{       The user may enter the selections either by typing the commands,
{       or by invoking PopUp-menues.  Online help will always be available,
{       and the user will never have committed himself to any choice before
{       the last choice (i.e. the leaf) has been done.
}

{==============================} exports {===================================}

imports PopUp from PopUp;


type
        NodeType     =  ( MenuNode, ParmNode, EndNode );

        HelpAddress  =  record
                            BlockNo : integer;
                            Offset  : integer;
                        end;

        pMenuEntry   =  ^MenuEntry;     { Pointer to menu hierarchy }
        MenuEntry    =  record
                                { Where to find help on this item }
                            Help        : HelpAddress;
                                { How to prompt for next selection }
                            Prompt                  : S25;
                            case Node   : NodeType of

                                MenuNode:   { A real menu } 
                                       (MPtr      : pNameDesc;
                                        NextLevel : array [1..1] 
                                                            of pMenuEntry);

                                ParmNode:   { A leaf, expecting a parameter } 
                                       ();
                                        
                                EndNode:    { A leaf, no parameter }
                                       ()
                         end;

        
        pPListEntry  =  ^PListEntry;    { Parse list pointer }
        PListEntry   =  record          { Parse list item }
                            PrevPList   : pPListEntry;
                            CurrMenu    : pMenuEntry;
                            CmdI        : integer;
                            case Node   : NodeType of
                                        { Menu selection }
                                MenuNode    : ( NextPList   : pPListEntry;
                                                Selection   : integer);
                                        { The possible tails of the list }
                                ParmNode    : ( Arg         : String );
                                EndNode     : ()
                        end;
                 
procedure   InitMenues;
procedure   DestroyMenues;
function    GetMenu( MenuFName, HelpFName : String ) : pMenuEntry;

exception   NoMenuFile( MenuFName : String );  
exception   BadMenuFile( AtLine : Integer );

function    GetMenuAnswer(MPtr:pNameDesc;  NPix:integer):integer;
function    PushCmdFile( FileName : String ) : Boolean;
procedure   GetPList( root : pMenuEntry; var PListPtr : pPListEntry );
procedure   DestroyPList( var PListPtr : pPListEntry );

{===========================================================================}
{==============================} private {==================================}
 
    

imports Memory from Memory;
imports FileSystem from FileSystem;
imports System from System;
imports Screen from Screen;
imports Perq_String from Perq_String;
imports MultiRead from MultiRead;
imports IO_Unit from IO_Unit;
imports IO_Others from IO_Others;
imports IOErrors from IOErrors;
imports Stream from Stream;  

const
        HelpCommand = 'HELP';

        DefSeg          =     0;
        UseCursorPos    =    -1;
        NotList         = false;
        ColWidth        =     8;
        ScreenWidth     =    75;
        MenuSize        =   200;  { Max. height of menu }
        CommentChar     =   '!';
        NumLevels       =    20;
        Fold            =  true;
        MaxCLine        =   132;  { Max. length of command line }
        TabKey          =   Chr(128);        
        CR              =   Chr( 13);
        Escape          =   Chr( 27);
        BS              =   Chr(  8);
        DEL             =   Chr(127);
        CtrlU           =   Chr( 21);
        CtrlW           =   Chr( 23); 
        CtrlX           =   Chr( 24);

        KeyChar         =   Chr( 24);
        CmdFChar        =   Chr( 26);

type
    pInt = ^Integer;

    CLine = packed array [1..MaxCLine] of char; 
    CBuff = record
                Prompt      : String;
                Cmd         : CLine;
                BufCur      : 0..MaxCLine;  { character index in buffer}
                CurrPList   : pPListEntry;  { last entry in parse list }
                Comment     : Boolean;
                CommPos,
                HelpPos     : Integer;    
            end;
                
    ParseResult =
        ( ParsedOK, WantHelp, NotFound, NotUnique );


var
    NullMenu        : pNameDesc;
    ShowMenues      : boolean;
    CmdStack        : Array [1..NumLevels] of text;
    CmdLevel        : 0..NumLevels;
    PromptChar      : Char;

    EndMenu,
    ParmMenu        : pNameDesc;

{===========================================================================}

procedure RefreshCBuff( VAR CB : CBuff );
VAR I : Integer;
begin
    with CB do begin
        write( Prompt, PromptChar );
        for I := 1 to BufCur-1 do write( Cmd[I] );
    end;
end;

{===========================================================================}

function CmdEndCBuff( VAR CB : CBuff ) : integer;
VAR I : Integer;
begin
    with CB do 
        if CurrPList=NIL then 
            CmdEndCBuff := 1
        else begin
            I := CurrPList^.CmdI;
            while (Cmd[i]<>' ') and (Cmd[i]<>CR) and 
                  (Cmd[i]<>CommentChar) and (I<BufCur) do 
                I := I + 1;
            CmdEndCBuff := I;
        end;
end;

{===========================================================================}

function    PushCmdFile( FileName : String ) : Boolean;

        handler ResetError( FileName : PathName );
        begin
             PushCmdFile := False;
             exit( PushCmdFile );
        end;

begin
    PushCmdFile := True;
    if CmdLevel<NumLevels then begin
        Reset( CmdStack[CmdLevel+1], FileName );
        CmdLevel := CmdLevel + 1;
        PromptChar := CmdFChar;
    end;
end;

{===========================================================================}

function GetChar : Char;
var C       : Char;
    Done    : Boolean;
begin
    if CmdLevel=0 then begin
        SCurOn;
        Done := False;
        while not Done do begin
            if (IOCRead( TransKey, C )=IOEIOC) then begin
                Done := True;
            end else if TabSwitch then begin
                Done := True;
                C := TabKey; 
            end;
        end;
        SCurOff;
    end else begin
        if EOF( CmdStack[CmdLevel] ) then begin     { Pop stack }
            Close( CmdStack[CmdLevel] );
            CmdLevel := CmdLevel - 1;
            if CmdLevel=0 then PromptChar := KeyChar;
            C := CR;
        end else
            if EOLn( CmdStack[CmdLevel] ) then begin
                Read( CmdStack[CmdLevel] , C );
                C := CR;
            end else
                Read( CmdStack[CmdLevel], C );
    end;
    GetChar := C;
end;   { GetChar } 

{=============================================================================}

function FieldWidth( L : integer ):integer;
begin
    FieldWidth := (( L + ColWidth ) div ColWidth ) * ColWidth;
end;

{===========================================================================}

procedure PushPList( VAR CB : CBuff; NewMenu : PMenuEntry );
var P : pPListEntry;
    I : Integer;
begin
    with CB do begin
        case NewMenu^.Node of
            MenuNode:   New( P, MenuNode );
            ParmNode:   New( P, ParmNode );
            EndNode:    New( P, EndNode );
        end;
        with P^ do begin
            Node := NewMenu^.Node;
            CurrMenu := NewMenu;
            PrevPList := CurrPList;
            I := CmdEndCBuff( CB );
            while ((Cmd[i]=' ') or (Cmd[i]=CR)) and (I<BufCur) do I := I + 1;
            CmdI := I;
            if Node=MenuNode then begin
                NextPList := NIL;
                Selection := 0;
            end else if Node=ParmNode then
                Arg := '';
        end;
        if CurrPList<>NIL then
            CurrPList^.NextPList := P;
        CurrPList := P;
    end;
end; 

{===========================================================================}

procedure InitCBuff( VAR CB : CBuff; M : pMenuEntry );
begin
    with CB do begin
        Prompt      := M^.Prompt;
        BufCur      := 1;
        CurrPList   := NIL;
        Comment     := False;
        CommPos     := 0;
        HelpPos     := 0;
    end;
    PushPList( CB, M );
end;

{===========================================================================}

function CComp( C1, C2 : Char ) : Boolean;
begin
    if C1=C2 then
        CComp := true
    else 
        if not Fold then
            CComp := false
        else begin
            if (C1>='a') and (C1<='z') then
                C1 := Chr( Ord(C1)-Ord('a')+Ord('A') );
            if (C2>='a') and (C2<='z') then
                C2 := Chr( Ord(C2)-Ord('a')+Ord('A') );
            CComp := C1=C2;
        end;
end;

{===========================================================================}

procedure IntoCBuff( VAR CB : CBuff;  C : Char );
begin
    with CB do begin
        if BufCur<MaxCLine then begin
            Cmd[BufCur] := C;
            if C>=' ' then      { Echo character }
                write(C);
            with CurrPList^ do
                if (CmdI=BufCur) and (C=' ') then
                    CmdI := CmdI + 1;
            BufCur := BufCur + 1;
        end;
    end;
end;

{===========================================================================}

procedure BackCBuff( VAR CB : CBuff; ToPos : Integer );
VAR I : Integer;
begin
    with CB do begin
        if ToPos>BufCur then ToPos := BufCur;
        if ToPos<1 then ToPos := 1;
        
        if Comment and (ToPos<=CommPos) then
            Comment := False;

        for I := BufCur-1 downto ToPos do begin
            if Cmd[I]>=' ' then             { Character was echoed to screen }
                SClearChar( Cmd[I], RXor );
        end;
        BufCur := ToPos;

            { Pop the last entries off the parse list, if necessary }
        while (CurrPList^.CmdI>BufCur) and (CurrPList^.PrevPList<>NIL) do begin
            CurrPList := CurrPList^.PrevPList;
        end;
    
        with CurrPList^ do begin
            if CmdI>BufCur then         { Could not pop last item }
                CmdI := BufCur;         { Just note that there are no chars }
            if (NextPList<>NIL) and (Node=MenuNode) then begin
                Selection := 0;
                DestroyPList( NextPList );
                NextPList := NIL;
            end;
        end;
        if ToPos<=HelpPos then
            HelpPos := 0;
    end;
end;

{===========================================================================}

procedure NextCmdCBuff( VAR CB : CBuff );
{ Push to next command in buffer }
VAR I : Integer;
begin
    with CB, CurrPList^, CurrMenu^ do begin
        I := CmdEndCBuff( CB );
        if (I<BufCur) then
            if (Selection>1) and (Selection<=MPtr^.NumCommands) then
            begin
                {$Range-}
                PushPList( CB, NextLevel[Selection] );
                {$Range=}
            end else if Selection=1 then begin
                if HelpPos=0 then
                    HelpPos := CurrPList^.CmdI;
                PushPList( CB, CurrMenu );
            end;
    end;
end;

{===========================================================================}

function FindMatch( VAR CB  : CBuff; 
                    VAR Pos : integer ) : Boolean;

{ Abbreviated command lookup.  Starting from "Pos", see if any command in   }
{ command table matches the word starting at CmdI in CB and ending at       }
{ BufCur -1 or first space or other delimiting character.                   }
 
var GiveUp                  : Boolean;
    CmdEnd, CmdLen, I, J    : Integer;
begin
    with CB, CurrPList^.CurrMenu^.MPtr^ do begin

        CmdEnd := CmdEndCBuff( CB );
        GiveUp := True;
        while (Pos<NumCommands) and (GiveUp) do begin

                { Look if Cmd matches command in table }
            Pos := Pos + 1; 
            I := CurrPList^.CmdI;
            J := 1;
            {$Range-}
            CmdLen := Length(Commands[Pos]);
            GiveUp := False;
            while (I<CmdEnd)  and (not GiveUp) do begin
                if CComp( Commands[Pos][J], Cmd[I] ) then begin
                    J := J+1;           { Matching characters, step both }
                    I := I+1;           { indices forward in commands    }
                    if (J>CmdLen) and (I<CmdEnd) then 
                        GiveUp := True;
                end else 
                    if Cmd[I]='-' then begin { Cmd is abbreviated, just  }
                        J := J+1;       { step the other index forward   }
                        if J>CmdLen then        { Need something to match }
                            GiveUp := True;     { this character to!      }
                    end else begin
                        GiveUp := True;
                    end;  
            end;
            {$Range=}
        end;
        
        FindMatch := not GiveUp;
    end;
end;    { FindMatch }

{===========================================================================}

procedure ShowWord( VAR CB : CBuff );
VAR I : Integer;
begin
    with CB do begin
        write('''');
        I := CurrPList^.CmdI;
        while (Cmd[I]<>' ') and (I<BufCur) do begin
            write(Cmd[I]);
            I := I + 1;
        end;
        write('''');
    end;
end;

{===========================================================================}

function ParseCBuff( VAR CB : CBuff ) : ParseResult;
VAR I, J : Integer;
begin
    with CB, CurrPList^ do

    Case Node of 
        MenuNode:
            begin
                I := 0; 
                if not FindMatch( CB, I ) then begin
                    ParseCBuff := NotFound;
                    CurrPList^.Selection := 0;
                end else begin
                    CurrPList^.Selection := I;
                    J := I;
                    if FindMatch( CB, J ) then begin
                        ParseCBuff := NotUnique;
                    end else begin
                        NextCmdCBuff( CB );
                        ParseCBuff := ParsedOK;
                    end;
                end;            
            end;

        ParmNode:
            begin
                if BufCur>1 then
                    if (Cmd[BufCur-1]=CR) or (Cmd[BufCur-1]=' ') then begin
                        Adjust( Arg, BufCur-1-CurrPList^.CmdI );
                        I := 1;
                        for J := CurrPList^.CmdI to BufCur-2 do begin
                            Arg[I] := Cmd[J];
                            I := I + 1;
                        end;
                    end;
                ParseCBuff := ParsedOK;
            end;
            
        EndNode:
            begin
                if BufCur>1 then
                    if Cmd[BufCur-1]=CR then 
                        if BufCur>CurrPList^.CmdI then begin
                            writeln;
                            write('?Garbage at end of line, ignored ''');
                            for I := CurrPList^.CmdI to BufCur-2 do 
                                write( Cmd[I] );
                            writeln('''');
                            RefreshCBuff( CB );
                        end;
                ParseCBuff := ParsedOK;
            end;
    end;
end;

{===========================================================================}

function    ParseAll( VAR CB : CBuff ) : ParseResult;
{ -- Reparse command buffer as far as possible }
var PRes        : ParseResult;
    PrevCmdI,
    TempPos     : Integer;
    TempChar    : Char;
begin
    with CB do begin
        if Comment then begin
            TempPos := BufCur;
            BufCur := CommPos + 1;
            TempChar := Cmd[CommPos];
            Cmd[CommPos] := ' ';
        end;
        if (CmdEndCBuff(CB)<>CurrPList^.CmdI) then begin
            repeat
                PrevCmdI := CurrPList^.CmdI;
                PRes := ParseCBuff(CB);
            until (PRes<>ParsedOK) or (PrevCmdI=CurrPList^.CmdI)
                        or (CmdEndCBuff(CB)=CurrPList^.CmdI);
            ParseAll := PRes;
        end else
            ParseAll := ParsedOK;

        if Comment then begin
            Cmd[CommPos] := TempChar;
            BufCur := TempPos; 
        end;
    end;    
end;

{===========================================================================}

procedure ParseCommand(     root        : pMenuEntry; 
                        var PListPtr    : pPListEntry;
                            HelpMode,
                            RootLevel   : Boolean );

const
    MoreInfo = 'More info on:';
    SelPrompt = 'Select item:';
    SelectOne = 'Select one of the following: ';
    CommNotUnique = '?Command is not unique: ';
var
    C                   : Char;
    Done, QuestionMark  : boolean;
    NextMatch,
    I, J, CmdEnd        : integer;
    Matching            : S25;
    CB                  : CBuff;       { Command buffer to use}
    TabPress            : Boolean;     { Select done by menu? }
    PRes                : ParseResult;
    Dummy, ArgEntry     : pPListEntry;

    HelpFile    : pInt;
    HelpFID     : integer;
    HFBuff      : pDirBlk;
    HFAddr      : HelpAddress;
    MM          : MMPointer;


    handler HelpKey( var retStr : Sys9s );
    begin
        retStr := 'HELP
';
    end;
    
    {------------------------------------------------------------------------}

    procedure PrintHelpText;
    var PrevCR      : boolean;
    begin
        if HelpFID=0 then 
            writeln('No helptext found!')
        else
            with CB.CurrPList^.CurrMenu^ do begin
                if HFaddr.BlockNo<>Help.BlockNo then
                    FSBlkRead( HelpFID, Help.BlockNo, HFBuff ); 
                HFAddr := Help;
                PrevCR := true;
                with HFAddr, HFBuff^ do 
                    while not( PrevCR and (ByteBuffer[Offset]=ord('>'))) do
                    begin
                        PrevCR := ByteBuffer[Offset]=13;
                        write( chr(ByteBuffer[Offset]) );
                        if PrevCR then write( chr(10) );
                        Offset := Offset+1;
                        if Offset>511 then begin
                            Offset := 0;
                            BlockNo := BlockNo + 1;
                            FSBlkRead( HelpFID, BlockNo, HFBuff );
                        end;
                    end;
            end;
    end;  { PrintHelpText }  

    {------------------------------------------------------------------------}

    procedure PrintAlts;
    var i,l,w,s         : integer;
        Matching        : S25;
    begin
        L := 0;
        with CB.CurrPList^.CurrMenu^, MPtr^ do 
        if Node=MenuNode then begin

            if HelpMode then 
                writeln( MoreInfo )
            else
                writeln( SelectOne );
            for i := 2 to NumCommands do begin
                {$range-}
                Matching := Commands[i];
                S := Length( Matching );
                W := FieldWidth( S );
                L := L+W;
                if L < ScreenWidth then
                    write( Matching, ' ':(W-S) )
                else if L = ScreenWidth then begin
                    writeln( Matching ); 
                    L := 0;
                end else begin
                    writeln;
                    write( Matching, ' ':(W-S) );
                    L := W;
                end;
                {$range=} 
            end;

        end;
        if L<>0 then writeln;
    end;

    {------------------------------------------------------------------------}
    
    procedure PrintMatching;
    var i,l,w,s         : integer;
        Matching        : S25;
    begin
        L := 0;
        I := 0;
        writeln( SelectOne );
        with CB.CurrPList^.CurrMenu^.MPtr^ do 
            while FindMatch( CB, I ) do begin
                {$Range-}
                Matching := Commands[I];
                {$Range=}
                S := Length( Matching );
                W := FieldWidth( S );
                L := L+W;
                if L < ScreenWidth then
                    write( Matching, ' ':(W-S) )
                else if L = ScreenWidth then begin
                    writeln( Matching ); 
                    L := 0;
                end else begin
                    writeln;
                    write( Matching, ' ':(W-S) );
                    L := W;
                end;
            end;
        if L<>0 then writeln;
    end;

    {------------------------------------------------------------------------}

    procedure DoHelp;
    begin
        writeln;
        writeln;
        PrintHelpText;
        writeln;
        PrintAlts;
        writeln;
    end;

    {------------------------------------------------------------------------}
    
    procedure ExplainHelp;
    begin
        writeln;
        writeln;
        write('HELP - online help facility');
        writeln;
        writeln('Use the "HELP" command to obtain command explanations');
        writeln('"HELP" may replace any command, and the effect will be to');
        writeln('explain this command and list the various alternatives.');
        writeln;
        writeln('"HELP" may be used in different ways: ');
        writeln('"HELP" as the last command on the line, before RETURN, will');
        writeln('enter the help mode, where every command entered not is ');
        writeln('executed, but explained.  Exit help mode by entering an ');
        writeln('empty line.');
        writeln('When the "HELP" command is not at the end of the line, ');
        writeln('the result will be to explain the commands after HELP ');
        writeln('and then continue entering commands to execute.'); 
        writeln;
        writeln('Function keys:');
        writeln('RETURN (CR) terminates the command and executes it.  If ');
        writeln('   the command is partially entered, the command tail will ');
        writeln('   be prompted for.  The command may then be aborted by ');
        writeln('   entering a blank line.');
        writeln('INS (ESC) expands the last command on the line, if it is ');
        writeln('   abbreviated, and it is unique.  Use to check if a valid');
        writeln('   command is entered, and that the abbreviation really');
        writeln('   identifies the correct command.');
        writeln('''?'' lists the commands that matches an abbreviation. ');
        writeln('''??'' enters help mode. ');
        writeln('''!'' is a comment delimiter.  (Most useful in command ');
        writeln('   files.)  Everything between ''!'' and end of line is ');
        writeln('   ignored.'); 
        writeln('BACKSPACE, DEL deletes the last character on the line.');
        writeln('OOPS, Ctrl-U, Ctrl-X deletes the whole line.');
        writeln('Ctrl-W deletes the last word (back to previous space) ');
        writeln;
    end;    { ExplainHelp }

    {------------------------------------------------------------------------}


begin  { GetPList } 
    MM := recast( Root, MMPointer );
    HelpFile := MakePtr( MM.Segmen, 0, pInt );
    HelpFID := HelpFile^;
    HFAddr.BlockNo := -1;       { Note help buffer is empty }
    new( HFBuff);

    Done := false;
    InitCBuff( CB, Root );
    if HelpMode then begin
        DoHelp;
        CB.Prompt := SelPrompt;
    end;
    RefreshCBuff( CB );
    PListPtr := CB.CurrPList; 
    QuestionMark := False;

    with CB do
      while not Done do begin
      
        C := GetChar;

        if (C=TabKey) then begin                { Insert dummy space to     }
            IntoCBuff( CB, ' ' );               { make parse go all the way }
            PRes := ParseAll(CB);               { to the end of buffer.     }
            BackCBuff( CB, BufCur-1 );          { Remove the dummy space.   }
            if BufCur>CurrPList^.CmdI then
                BackCBuff( CB, CurrPList^.CmdI );      { ..partial command }
            Dummy := CurrPList;
            repeat
                case CurrPList^.Node of
                
                MenuNode:
                    begin
                        I := GetMenuAnswer( CurrPList^.CurrMenu^.MPtr, 
                                                MenuSize );
                        if I>1 then begin
                            CurrPList^.Selection := I;
                            {$Range-}
                            Matching := CurrPList^.CurrMenu^.MPtr^.Commands[i];
                            {$Range=}
                            for J := 1 to length(Matching) do begin
                                IntoCBuff(CB,Matching[j]);
                            end;
                            IntoCBuff(CB, ' ');
                            NextCmdCBuff(CB); 
                        end;
                    end;

                EndNode:
                    begin
                        if HelpMode then begin
                            I := 1;
                        end else 
                            I := GetMenuAnswer( EndMenu, MenuSize );
                        if I=2 then I := -1;
                    end;
                
                ParmNode:
                    begin 
                        if HelpMode then begin
                            I := 1;
                        end else 
                            I := GetMenuAnswer( ParmMenu, MenuSize );
                        if I=2 then begin
                            writeln;
                            ParseCommand( CurrPList^.CurrMenu, ArgEntry, 
                                    HelpMode, false );
                            CurrPList^.Arg := ArgEntry^.Arg;
                            DestroyPList( ArgEntry );
                            I := -1;
                        end else if I=3 then begin
                            CurrPList^.Arg := '';
                            I := -1;
                        end;
                    end;
                end;

                if I=1 then begin 
                    writeln;
                    writeln;
                    PrintHelpText;
                    writeln;
                    write('Press tabswitch to get menu back: ');
                    while TabSwitch do ;
                    while not TabSwitch do ;
                    writeln(CR,'                                     ' );
                    RefreshCBuff(CB);
                end;

                if (I=0) or ((I=1) and (CurrPList^.Node<>MenuNode))
                then begin       { Pop off command }
                    if CurrPList<>Dummy then begin
                        BackCBuff( CB, CurrPList^.PrevPList^.CmdI );
                    end;
                end;

                if (I=-1) and not HelpMode then begin
                    writeln;
                    Done := True;
                end;

            until Done or (CurrPList=Dummy);

        end else
        
        if (C=CommentChar) then begin
            if not Comment then begin
                Comment := True;
                CommPos := BufCur;
            end;
            IntoCBuff( CB, C );
        end else 

        if (C=CR) then
        begin
            IntoCBuff( CB, ' ' );
            case ParseAll( CB ) of
 
            ParsedOK:
                if HelpMode then begin
                    Done := CurrPList^.PrevPList=NIL;
                    if CurrPList^.Selection=1 then 
                        ExplainHelp
                    else begin
                        writeln;
                        if not Done then begin
                            DoHelp;
                            if CurrPList^.Node<>MenuNode then
                                BackCBuff( CB, CurrPList^.PrevPList^.CmdI )
                            else 
                                BackCBuff( CB, BufCur-1 );
                            RefreshCBuff(CB); 
                        end;
                    end;
                
                end else begin
                    writeln;
                    with CurrPList^ do

                    if HelpPos>0 then begin
                        if PrevPList^.CmdI=HelpPos then begin { HELP last com.}
                            writeln;
                            ParseCommand( CurrPList^.CurrMenu, Dummy, 
                                    True, false );
                            DestroyPList( Dummy );
                        end else begin
                            writeln;
                            PrintHelpText;
                            writeln;
                            if Node=MenuNode then begin
                                PrintMatching;
                                writeln;
                            end;
                        end;
                        RefreshCBuff(CB);

                    end else if (CurrMenu=Root) and (Node=MenuNode) then
                        PListPtr := NIL      { Nothing parsed (or a new}
                                             { entry would have been pushed)}
                    else begin           
                        if Node=MenuNode then begin
                              { OK so far, but haven't got all of command }
                            ParseCommand( CurrMenu, Dummy, 
                                    false, false );
                            if (Dummy=NIL) then begin   { Quit command }
                                DestroyPList(PListPtr);
                                PListPtr := NIL;
                            end else begin              { link in cmd tail }
                                CurrPList^.PrevPList^.NextPList := Dummy;
                                Dummy^.PrevPList := CurrPList^.PrevPList;
                                DestroyPList(CurrPList);
                                CurrPList := Dummy;
                            end;
                        end;
                    end;
                    if HelpPos>0 then
                        BackCBuff( CB, HelpPos )
                    else
                        Done := true;
                end;

            NotUnique:
                begin
                    BackCBuff( CB, BufCur-1 );
                    writeln;
                    write( CommNotUnique );
                    ShowWord( CB ); 
                    writeln;
                    PrintMatching;
                    if CmdLevel>0 then begin
                        RefreshCBuff( CB );
                        BackCBuff( CB, 1 )
                    end else begin
                        BackCBuff(CB, CmdEndCBuff(CB));
                        RefreshCBuff( CB );
                    end;
                end;

            NotFound:
                begin
                    BackCBuff( CB, BufCur-1 );
                    writeln;
                    write('?No match for word: ');
                    ShowWord(CB);
                    writeln; 
                    PrintAlts;
                    RefreshCBuff( CB );  { ... and start over }
                    if CmdLevel>0 then
                        BackCBuff( CB, 1 );
                end;

            end;
            QuestionMark := false;

        end else

        if (C='?') and (not Comment) then begin

            PRes := ParseAll( CB );
            if QuestionMark and not HelpMode then begin
                writeln;
                ParseCommand( CurrPList^.CurrMenu, Dummy, True, false );
                DestroyPList( Dummy );
                QuestionMark := False;
                RefreshCBuff( CB );

            end else begin

                case PRes of 
                
                ParsedOK:
                    if HelpMode then begin
                        writeln('?');
                        DoHelp;
                        RefreshCBuff(CB);
                    end else if BufCur=CurrPList^.CmdI then
                    begin
                        writeln('?');
                        PrintAlts;
                        RefreshCBuff(CB);
                    end;
                
                NotFound:
                    begin
                        writeln('?');
                        write('?No match for word: ');
                        ShowWord(CB);
                        writeln; 
                        if CmdLevel>0 then begin
                            RefreshCBuff( CB );
                            BackCBuff( CB, 1 )
                        end else begin
                            PrintAlts;
                            RefreshCBuff( CB );  { ... and start over }
                        end;
                    end;
                    
                NotUnique:
                    begin
                        writeln('?');
                        PrintMatching;
                        QuestionMark := True;
                        if CmdLevel>0 then begin
                            RefreshCBuff( CB );
                            BackCBuff( CB, 1 );
                        end else begin
                            BackCBuff(CB, CmdEndCBuff(CB));
                            RefreshCBuff( CB );
                        end;
                    end;
                end;
                
                QuestionMark := True;
            end;

        end else

        if (C=Escape) and (not Comment) then begin
             
            QuestionMark := False;
 
            if BufCur>CurrPList^.CmdI then begin

                PRes := ParseAll(CB);
                case PRes of
        
                ParsedOK:
                    begin
                        CmdEnd := CmdEndCBuff(CB);
                        if CmdEnd=BufCur then
                            with CurrPList^ do begin
                                {$Range-}
                                Matching := 
                                    CurrMenu^.MPtr^.Commands[Selection];
                                {$Range=}
                                I := CmdI;
                                J := 1;
                                while (I<CmdEnd) and (J<=Length(Matching)) 
                                do begin
                                    if CComp( Matching[J], Cmd[I] ) then begin
                                        J := J+1;
                                        I := I+1;
                                    end else begin
                                        if Cmd[I]='-' then begin
                                            J := J+1;
                                        end;
                                    end;
                                end;
                                for I := J to Length(Matching) do begin
                                    IntoCBuff( CB, Matching[I] );
                                end;
                                if PRes=ParsedOK then { expect more commands }
                                begin
                                    IntoCBuff( CB, ' ' );
                                end;        
                            end;
                    end;
                    
                NotFound:
                    begin
                        write('?No match for word: ');
                        ShowWord(CB); 
                        writeln;
                        if CmdLevel>0 then begin
                            RefreshCBuff( CB );  { ... and start over }
                            BackCBuff( CB, 1 );
                        end else begin
                            PrintAlts;
                            RefreshCBuff( CB );  { ... and start over }
                        end;
                    end;
                    
                NotUnique:
                    begin 
                        writeln;
                        write(CommNotUnique);
                        ShowWord(CB);
                        writeln;
                        if CmdLevel>0 then begin
                            RefreshCBuff( CB );
                            BackCBuff( CB, 1 )
                        end else begin
                            BackCBuff(CB, CmdEndCBuff(CB));
                            PrintMatching;
                            RefreshCBuff( CB );
                        end;
                    end;

                end;
            end;
        end else
        
        if (C=BS) or (C=DEL) then begin
            if BufCur=1 then
                write( chr(7) )
            else
                BackCBuff( CB, BufCur-1 );
            QuestionMark := False;
        end else 

        if (C=CtrlW) then begin
            if (CurrPList^.CmdI=BufCur) then begin
                if CurrPList^.PrevPList<>NIL then
                    BackCBuff( CB, CurrPList^.PrevPList^.CmdI );
            end else 
                BackCBuff(CB, CurrPList^.CmdI );
            QuestionMark := False;
        end else

        if (C=CtrlX) or (C=CtrlU) then begin
            BackCBuff( CB, 1 );
            QuestionMark := False;
        end else 

        begin { normal character }
            QuestionMark := False;
            if (C>=' ') and (C<DEL) then begin
                IntoCBuff( CB, C );
            end;       
        end;

    end { while };
    dispose( HFBuff );
    
end;    { ParseCommand }

{===========================================================================}

function    GetMenuAnswer( MPtr:pNameDesc;  NPix:integer ):integer;
    {   Returns 0 for press outside menu }
var     ResPtr  :   ResRes;

    Handler OutSide;
    begin
        ResPtr:=NIL;
        exit(Menu);
    end;  { OutSide }

begin { GetMenuAnswer }
    Menu(   MPtr,
            NotList,
            1,
            MPtr^.NumCommands,
            UseCursorPos,
            UseCursorPos,
            NPix,   {Number of pixels (height)}
            ResPtr);
    if ResPtr <> NIL then begin
        GetMenuAnswer := ResPtr^.Indices[1];
        DestroyRes( ResPtr );
    end
    else
        GetMenuAnswer := 0;
end; { GetMenuAnswer }

{=============================================================================}

procedure   DestroyPList( var PListPtr : pPListEntry );
var Trail : pPListEntry;
begin
    while PListPtr<>NIL do begin
        Trail := PListPtr;
        case Trail^.Node of
        
            EndNode:
                begin
                    PListPtr := NIL;
                    dispose( Trail, EndNode );
                end;
            
            ParmNode:
                begin
                    PListPtr := NIl;
                    dispose( Trail, ParmNode );
                end;
               
            MenuNode:
                begin
                    PListPtr := Trail^.NextPList;
                    Trail^.NextPList := NIL;
                    dispose( Trail, MenuNode );
                end;
        end;
    end;
end;  
    
{=============================================================================}

procedure   GetPList(           Root : pMenuEntry; 
                        var PListPtr : pPListEntry ); 

begin
    SCurOn;
    PListPtr := NIL;
    ParseCommand( Root, PListPtr, false, true );
    SCurOff;
end;

{=============================================================================}

function GetMenu( MenuFName, HelpFName : String ) : pMenuEntry;

VAR MenuFile            : Text;
    Blk, Bits           : Integer;
    SegSize             : Integer;
    MenuF               : FileID;
    Root                : pMenuEntry;
    MMP                 : MMPointer;
    HelpFile            : pInt;
    MenuSeg, OldSeg     : SegmentNumber;                      

    exception BadMenuSeg;

    handler BadMenuSeg;
    begin
        GetMenu := NIL;
        exit( GetMenu );
    end;
    
    procedure FixPointer( var ME : pMenuEntry );
    var MME : record case boolean of
                true:   ( MM : MMPointer);
                false:  ( E  : pMenuEntry);
             end;
    begin
        with MME do begin
            E := ME;
            with MM do begin
                if (Segmen<>OldSeg) or (Offset>SegSize) then
                    raise BadMenuSeg;
                Segmen := MenuSeg;
            end;
            ME := E;
        end;
    end;
                      
    procedure ValidatePtrs( ME : pMenuEntry );
    var i       : integer;
        TME     : pMenuEntry;
    begin
        with ME^ do begin
            case Node of            
                MenuNode:
                    begin
                        TME := recast( MPtr, pMenuEntry );
                        FixPointer( TME );
                        MPtr := recast( TME, pNameDesc );
                        for i := 2 to MPtr^.NumCommands do begin 
                            {$range-}
                            FixPointer( NextLevel[i] );
                            ValidatePtrs( NextLevel[i] );
                            {$range=}
                        end;
                    end;
       
                EndNode, ParmNode:
                    ;
            end;
        end;
    end;

begin
    MenuF := FSLookUp( MenuFName, Blk, Bits );
    if MenuF=0 then
        raise NoMenuFile( MenuFName )
    else begin
        CreateSegment( MenuSeg, Blk, 1, Blk );
        SegSize := (Blk-1)*256 + (Bits div 16);
        Root := MakePtr( MenuSeg, WordSize( integer ), pMenuEntry );
        MultiRead( MenuF, MakePtr( MenuSeg, 0, pDirBlk ), 0, Blk ); 
        MMP := recast( Root^.MPtr, MMPointer );
        OldSeg := MMP.Segmen;
        ValidatePtrs( Root );
        HelpFile := MakePtr( MenuSeg, 0, pInt );
        HelpFile^ := FSLookUp( HelpFName, Blk, Bits );  
    end;
    GetMenu := Root;
end;

{=============================================================================}

procedure InitMenues;
begin
    {$Range-}
    AllocNameDesc( 1, DefSeg, NullMenu );
    with NullMenu^ do begin
        Header := 'Confirm:';
        Commands[1] := '?';
    end;
    AllocNameDesc( 2, DefSeg, EndMenu );
    with EndMenu^ do begin
        Header := 'Confirm selection:';
        Commands[1] := '?';
        Commands[2] := 'Perform command';
    end;
    AllocNameDesc( 3, DefSeg, ParmMenu );
    with ParmMenu^ do begin
        Header := 'Command arguments:';
        Commands[1] := '?';
        Commands[2] := 'Enter arguments';
        Commands[3] := 'No arguments';
    end;
    {$Range=}
    InitPopUp;
    IOCursorMode(TrackCursor);
    CmdLevel := 0;
    PromptChar := KeyChar;
end;


{=============================================================================}

    
procedure DestroyMenues;
var CI : integer;
begin
    DestroyNameDescr( NullMenu );
end.
