(* AMIS keybord input module. *)	(* -*-PASCAL-*- *)

(****************************************************************************)
(*									    *)
(*  Copyright (C) 1980, 1981, 1982, 1983, 1984, 1985, 1986, 1987 by	    *)
(*  Stacken, Royal Institute Of Technology, Stockholm, Sweden.		    *)
(*  All rights reserved.						    *)
(* 									    *)
(*  This software is furnished under a license and may be used and copied   *)
(*  only in  accordance with  the  terms  of  such  license  and with the   *)
(*  inclusion of the above copyright notice. This software or  any  other   *)
(*  copies thereof may not be provided or otherwise made available to any   *)
(*  other person.  No title to and ownership of  the  software is  hereby   *)
(*  transferred.							    *)
(* 									    *)
(*  The information in this software is  subject to change without notice   *)
(*  and should not be construed as a commitment by Stacken.		    *)
(*									    *)
(*  Stacken assumes no responsibility for the use or reliability of its     *)
(*  software on equipment which is not supported by Stacken.                *)
(* 									    *)
(****************************************************************************)

(*$E+,T-*)			(* Compiler directives. *)

module input;

const
  CtrlAtSign = 0;	    CtrlA = 1;		      CtrlB = 2;
  CtrlC = 3;		    CtrlD = 4;		      CtrlE = 5;
  CtrlF = 6;		    CtrlG = 7;		      CtrlH = 8;
  CtrlI = 9;		    CtrlJ = 10;		      CtrlK = 11;
  CtrlL = 12;		    CtrlM = 13;		      CtrlN = 14;
  CtrlO = 15;		    CtrlP = 16;		      CtrlQ = 17;
  CtrlR = 18;		    CtrlS = 19;		      CtrlT = 20;
  CtrlU = 21;		    CtrlV = 22;		      CtrlW = 23;
  CtrlX = 24;		    CtrlY = 25;		      CtrlZ = 26;
  CtrlLeftBracket = 27;     CtrlBackSlash = 28;	      CtrlRightBracket = 29;
  CtrlUpArrow = 30;	    CtrlUnderScore = 31;      RubOut = 127;

  Null = CtrlAtSign;	    Bell = CtrlG;	      BackSpace = CtrlH;
  HorizontalTab = CtrlI;    LineFeed = CtrlJ;	      FormFeed = CtrlL;
  CarriageReturn = CtrlM;   Escape = CtrlLeftBracket;
			
  HelpChar = CtrlUnderScore;

  CSI = 155;			(* Command Seq. introducer. *)

  StrSize = 40;

  (*@VMS: MacMax = 200; *)	(* Max length of Keyboard macro *)
  (*@TOPS: MacMax = 50; *)	(* Max length of Keyboard macro *)

type
  string = packed array [1..StrSize] of char;

  RefMacro = ^MacroRecord;
  MacroRecord = packed record
    Left, Right	: RefMacro;	(* Links in linked list *)
    Number	: integer;	(* Number of macro *)
    Size	: integer;	(* Number of chars in macro *)
    MacString	: array [1..MacMax] of integer;	(* Macro itself. *)
    Running	: boolean;	(* If this macro is running *)
    LoopCount	: integer;	(* Loopcounter for this run *)
    Pos		: integer;	(* Position for this loop *)
  end;

var
  LastChar	: char;         (* The last character read *)
  MetaChar	: boolean;	(* The last character was a meta-character *)
  CsiTerminator	: char;		(* Character terminating CSI seq. *)
  CsiArgument	: integer;	(* Numeric arg in CSI seq. *)
  Again		: boolean;      (* We should read the last character again *)
  Filing	: boolean;	(* We are reading from the init file *)
  FilPos	: integer;	(* Position in init file *)
  FilSiz	: integer;	(* Size of init file. *)
  MacroRunning	: boolean;	(* A Keyboard macro is running *)
  DefiningMacro	: boolean;	(* A Keyboard macro is being defined *)
  ZeroMacro	: RefMacro;	(* Points to head of kbd macro linked list *)
  RecLevel	: integer;	(* Recursion level for keyboard macros *)
  LastFrozen	: integer;	(* Number of last frozen keybord macro *)
  CurrentMacro	: RefMacro;	(* Points to currently running kbd macro *)

  AutoBuffer	: string;	(* Auto echo buffer. *)
  AutoCount	: integer;	(* Count of chars in auto echo buffer. *)
  AutoStarted	: boolean;	(* Auto echo has started. *)
  AutoEchoed	: boolean;	(* Current char is queued for echoing. *)

(*---------------------------------------------------------------------------*)

function  TtyRead: integer; external;
procedure TtyFlush; external;
function  TtyCheck(Seconds: integer): boolean; external;
procedure NoInt; external;
procedure OKInt; external;

procedure Bug(Msg: string); external;
procedure Error(Msg: string); external;

procedure DefMode(flag: boolean); external;
procedure CommandLoop(RecursiveName: string); external;
procedure TopLoop; external;

procedure EchoUpDate; external;
procedure WinUpDate; external;
procedure EchoSize(var x, y: integer); external;
procedure EchoPos(x, y: integer); external;
procedure EchoEol; external;
procedure EchoArrow(c: char); external;
procedure EchoString(s: string); external;
procedure WinOverWrite(c: char); external;
procedure OvWString(s: string); external;
procedure OvWLine; external;

function  QGetChar(reg, dot: integer): char; external;
function  QGetSize(reg: integer): integer; external;
procedure QX(reg, count: integer); external;

procedure EchoClear; forward;	(* Until it is moved to SCREEN. *)

(*---------------------------------------------------------------------------*)
(*  Automagical echo routines.                                               *)

procedure AutoDump;
var
  i: integer;
begin
  for i := 1 to AutoCount do begin
    AutoStarted := true;
    EchoArrow(AutoBuffer[i])
  end;
  AutoCount := 0;
end;

(*@VMS: [global] *)
procedure AutoImmediate;
begin
  if AutoCount > 0 then AutoDump;
  AutoStarted := true;
end;

(*@VMS: [global] *)
procedure AutoReset;
begin
  if AutoStarted		(* If any echoing was written out *)
  then EchoClear;		(* Clear the echo area *)
  AutoStarted := false;		(* Reset flag *)
  AutoCount := 0;		(* Reset buffer count *)
end;

(*@VMS: [global] *)
procedure AutoChar(c: char);
begin
  if AutoCount >= StrSize then AutoDump;
  if AutoStarted then begin
    EchoArrow(c);
  end else begin
    AutoCount := AutoCount + 1;
    AutoBuffer [AutoCount] := c;
  end;
end;

(*@VMS: [global] *)
procedure AutoLast(expand: boolean);

type word = packed array[1..10] of char;

  procedure AutoWord(w: word);
  var
    i: integer;
  begin
    for i := 1 to 10 do if w[i] <> ' ' then AutoChar(w[i]);
    AutoChar(' ');
  end;

begin
  if not AutoEchoed then begin
    AutoEchoed := true;
    if MetaChar then begin
      AutoChar('M'); AutoChar('-')
    end;
    if LastChar = chr(BackSpace) then AutoWord('Backspace ')
    else if LastChar = chr(HorizontalTab) then AutoWord('Tab       ')
    else if LastChar = chr(LineFeed) then AutoWord('Linefeed  ')
    else if LastChar = chr(CarriageReturn) then AutoWord('Return    ')
    else if LastChar = chr(Escape) then AutoWord('Escape    ')
    else if LastChar = chr(HelpChar) then AutoWord('Help      ')
    else if LastChar = ' ' then AutoWord('Space     ')
    else if LastChar = chr(Rubout) then AutoWord('Rubout    ')
    else if (LastChar < ' ') and expand then begin
      AutoChar('C'); AutoChar('-');
      AutoChar(chr(ord(LastChar) + 64)); AutoChar(' ');
    end else begin
      AutoChar(LastChar); AutoChar(' ');
    end;
    if AutoStarted then EchoUpDate;
  end;
end;

(*---------------------------------------------------------------------------*)
(*  Check for TTY input                                                      *)

(*@VMS: [global] *)
function Check(Time: integer): boolean;
begin
  Check := Again or filing or MacroRunning or TtyCheck(Time);
end;

(*---------------------------------------------------------------------------*)

(*@VMS: [global] *)
procedure SetFile;
begin
  FilPos:=0;
  FilSiz:=QGetSize(-39);
  Filing:=FilSiz>0;
end;

function nextfile: integer;
var
  i: integer;
begin
  i:=ord(QGetChar(-39,FilPos));
  FilPos:=FilPos+1;
  nextfile:=i;
  if i = 13 then begin
    if FilSiz>FilPos then i:=ord(QGetChar(-39,FilPos));
    if i = 10 then FilPos:=FilPos+1;
  end;
  filing:=FilSiz>FilPos;
  if not filing then QX(-39,0);
end;

(*---------------------------------------------------------------------------*)
(*  The function NextMacro returns the next eight bit character from the     *)
(*  current running keybord macro.					     *)

function NextMacro: integer;
begin
  with CurrentMacro^ do begin
    if Pos > Size
    then Bug('INPUT: No end of Keyboard macro.        ');
    NextMacro := MacString [Pos];
    Pos := Pos + 1;
  end; (* with *)
end;

(*---------------------------------------------------------------------------*)
(*  The procedure NoMacDef aborts the current macro definition, if any.      *)

procedure NoMacDef;
begin
  DefMode(false);
  DefiningMacro := false;
end;

(*---------------------------------------------------------------------------*)
(*  The procedure KbdStart implements the function ^R Start Kbd Macro.       *)

(*@VMS: [global] *)
procedure KbdStart(Arg: integer);
begin
  if DefiningMacro or MacroRunning
  then begin			(* May not be started inside another *)
    NoMacDef;
    Error('RKM? Recursive Keyboard macro           ')
  end;
  if RecLevel > 0		(* Not allowed below a Kbd Macro Query *)
  then Error('MIR? But the macro is running           ');
  with ZeroMacro^ do begin
    Size := 0;
    LoopCount := arg;
  end; (* with *)
  CurrentMacro := ZeroMacro;
  DefiningMacro := true;	(* Now we are definig a macro. *)
  defmode(true);		(* ... tell MAIN for the modeline *)
end;

(*---------------------------------------------------------------------------*)
(*  The procedure KbdEnd implements the function ^R End Kbd Macro.           *)

(*@VMS: [global] *)
procedure KbdEnd(Arg: integer; ArgGiven: boolean);
begin
  if not (MacroRunning or DefiningMacro)
  then Error('NIB? Not inside a macro.                ');
  with CurrentMacro^ do begin
    if (DefiningMacro and ArgGiven)
    then LoopCount := Arg;
    Pos := 1;			(* Restart the macro *)
    LoopCount := LoopCount - 1;
    MacroRunning := (LoopCount <> 0);
  end; (* with *)
  NoMacDef;
end;

(*---------------------------------------------------------------------------*)

function FindKbdMacro(Number: integer): RefMacro;
var
  Test: RefMacro;
begin
  Test := ZeroMacro;
  if Number <> 0
  then repeat
    Test := Test^.Right;
    if Test = ZeroMacro
    then Bug('FindKbdMacro: Illegal argument.         ');
  until Test^.Number = Number;
  FindKbdMacro := Test;
end;

(*---------------------------------------------------------------------------*)
(*  This procedure does the job of the function ^R Execute Kbd Macro.        *)

(*@VMS: [global] *)
procedure KbdExecute(Number, Arg: integer);
begin
  if DefiningMacro or MacroRunning
  then begin
    NoMacDef;
    Error('RKM? Recursive Keyboard macro           ');
  end;
  if RecLevel > 0
  then Error('MIR? But the macro is running           ');
  CurrentMacro := FindKbdMacro(Number);
  with CurrentMacro^ do begin
    if Size = 0 then Error('NMD? No Keyboard Macro is defined.      ');
    Pos := 1;
    LoopCount := Arg;		(* Do it arg times *)
    MacroRunning := true;
  end; (* with *)
end;

(*---------------------------------------------------------------------------*)
(*  The procedure KbdView implements the function View Kbd Macro.            *)

(*@VMS: [global] *)
procedure KbdView(Number: integer);
var
  i: integer;
  c: char;
  m: RefMacro;
begin
  OvWString('Definition of keyboard macro:           ');
  OvWLine; OvWLine;
  m := FindKbdMacro(Number);
  with m^ do begin
    for i := 1 to Size do begin
      c := chr(MacString [i] mod 128);
      if (c < ' ') and (c <> chr(Escape)) and (c <> chr(CarriageReturn)) and
	 (c <> chr(HorizontalTab)) then
      begin
	WinOverWrite('C'); WinOverWrite('-');
	c := chr(ord(c) + 64);	(* Convert to writeable character *)
      end;
      if macstring [i] > 127
      then begin
	WinOverWrite('M'); WinOverWrite('-')
      end;
      if c = chr(Escape)
      then OvWString('Escape                                  ')
      else if c = ' '
      then OvWString('Space                                   ')
      else if c = chr(CarriageReturn)
      then OvWString('Return                                  ')
      else if c = chr(HorizontalTab)
      then OvWString('Tab                                     ')
      else if c = chr(Rubout)
      then OvWString('Rubout                                  ')
      else begin
	WinOverWrite(c); WinOverWrite(' ');
      end;
    end; (* for *)
  end; (* with *)
  OvWLine;
end;

(*---------------------------------------------------------------------------*)
(*  The function KbdFreeze saves the currently defined macro, and returns    *)
(*  a handle for later access.						     *)

(*@VMS: [global] *)
function KbdFreeze: integer;
var
  ThisMacro: RefMacro;
begin (* KbdFreeze *)
  NoInt;			(* Lock out interrupts *)
  LastFrozen := LastFrozen + 1;
  new(ThisMacro);
  with ThisMacro^ do begin
    Left := ZeroMacro;
    Right := ZeroMacro^.Right;
    MacString := ZeroMacro^.MacString;
    Size := ZeroMacro^.Size;
    Number := LastFrozen;
  end; (* with *)
  ZeroMacro^.Right := ThisMacro;
  ZeroMacro^.Right^.Left := ThisMacro;
  KbdFreeze := LastFrozen;
  OKInt;			(* Allow interrupts again *)
end; (* KbdFreeze *)

(*---------------------------------------------------------------------------*)
(*  The function KbdStop aborts macro definition, if any, and returns true   *)
(*  if there was a macro being defined.					     *)

(*@VMS: [global] *)
function KbdStop: boolean;
begin
  NoMacDef;
  RecLevel := 0;
  KbdStop := MacroRunning;
  MacroRunning := false;
end;

(*---------------------------------------------------------------------------*)
(*  The function KbdRunning checks if a Keybord macro is running.	     *)

(*@VMS: [global] *)
function KbdRunning: boolean;
begin
  KbdRunning := MacroRunning;
end;

(*---------------------------------------------------------------------------*)

(*@VMS: [global] *)
procedure GetCsiData(var arg: integer; var tc: char);
begin
  arg := CsiArgument;
  tc := CsiTerminator;
end;

(*---------------------------------------------------------------------------*)
(*  This function is the basic character reading routine.                    *)

(*@VMS: [global] *)
function QReadC: char;
var
  i: integer;

  procedure MacroSave;
  begin
    with ZeroMacro^ do begin
      Size := Size + 1;
      if Size > MacMax then begin (* Flush this macro if too long. *)
	Size := 0;
	Error('TLK? Too Long Keyboard macro            ');
      end;
      MacString [Size] := i;
    end; (* with *)
  end;

begin
  if not Again then begin
    if MacroRunning
    then i := nextmacro
    else begin
      if filing
      then i := nextfile
      else begin
	if AutoCount > 0 then	(* Any lazy echo? *)
	if not check(1) then begin (* Yes, input within a second? *)
	  AutoDump;		(* No, dump out all echos *)
	  EchoUpDate;		(* Force it out *)
	end;
	i := TtyRead;		(* Get a character from the user *)
      end;
      if DefiningMacro then MacroSave;
    end;
    if i = CSI then begin	(* Parse CSI arguments, if any. *)
      CsiArgument := 0;
      while QReadC in ['0'..'9'] do begin
	CsiArgument := CsiArgument * 10 + ord(LastChar) - ord('0');
      end;
      CsiTerminator := LastChar;
    end;
    AutoEchoed := false;	(* This char is not echoed yet. *)
    LastChar := chr(i mod 256);	(* Split into character and meta bit. *)
    MetaChar := i >= 256;
  end;
  Again := false;		(* Don't read this again unless told so *)
  QReadC := LastChar;
end;

(*---------------------------------------------------------------------------*)

(*@VMS: [global] *)
function ReadC: char;
begin
  ReadC := QReadC;		(* Read a character *)
  if LastChar = chr(CtrlG)	(* Is it a ^G? *)
  then begin			(* Yes, abort if not meta bit on *)
    if not MetaChar
    then TopLoop;
  end;
end;

(*---------------------------------------------------------------------------*)

(*@VMS: [global] *)
function MetaBit: boolean;
begin
  MetaBit := MetaChar;
end;

(*---------------------------------------------------------------------------*)

(*@VMS: [global] *)
procedure ReRead;
begin				(* Read last character again *)
  Again := true;
end;

(*---------------------------------------------------------------------------*)

(*@VMS: [global] *)
procedure Flush;
begin                           (* Flush all TTY buffers *)
  Again := false;
  TtyFlush;
end;

(*---------------------------------------------------------------------------*)
(*  This procedure implements the function ^R Kbd Macro Query                *)

(*@VMS: [global] *)
procedure KbdQuery;
var
  c: char;
begin (* KbdQuery *)
  if MacroRunning then		(* NOOP unless a macro is running *)
  begin
    MacroRunning := false;	(* Stop the macro temporarily *)
    EchoClear;
    EchoString('Kbd Query                               ');
    WinUpDate;
    c := QReadC;		(* Wait for the user to type something *)
    EchoClear;
    while ((c = Chr(HelpChar)) or (c = Chr(CtrlR)) or (c = Chr(CtrlL)))
	   and not metabit do
    begin
      if c = Chr(HelpChar)
      then begin		(* Print help for Keyboard Macro Query *)
	OvWString('You are entering an argument to         ');
	OvWString('a Keyboard Macro Query.                 '); OvWLine;
	OvWString('Enter one of the following:             '); OvWLine;
	OvWString('Space   => Continue                     '); OvWLine;
	OvWString('Escape  => Stop the Macro               '); OvWLine;
	OvWString('Rubout  => Skip the rest of the Macro   '); OvWLine;
	OvWString('C-R     => Enter recursive editing      '); OvWLine;
	OvWString('C-L     => Redisplay the screen         '); OvWLine;
	OvWString('Any other character will stop the Macro ');
	OvWString('and be reread again.                    '); OvWLine;
      end else begin
	if c = chr(CtrlR) then begin
	  RecLevel := RecLevel + 1; (* Enter recursive editing *)
	  CommandLoop('Kbd Query.                              ');
	  RecLevel := RecLevel - 1;
	  EchoClear;
	end;
	WinUpDate;
      end;
      EchoString('Kbd Query                               ');
      EchoUpDate;
      c := QReadC;
      EchoClear;
    end;
    if metabit then
      reread
    else
    if c = ' ' then		(* Space => continue *)
      MacroRunning := true
    else
    if c = Chr(rubout) then	(* Rubout => skip the rest *)
    begin
      MacroRunning := true;
      kbdend(0, false)
    end
    else
    if c <> Chr(escape) then	(* Escape => Stop the macro *)
      reread;
  end;
end; (* KbeQuery *)

(*---------------------------------------------------------------------------*)
(*  This procedure initializes the INPUT module.                             *)

(*@VMS: [global] *)
procedure InInit(Total: boolean);
begin
  Again := false;		(* Turn off character reread flag *)
  filing := false;		(* Not handling any init file *)
  MacroRunning := false;
  RecLevel := 0;		(* No recursion at initialization time *)
  DefiningMacro := false;	(* Halt macro definition if reinitialized *)
  AutoStarted := false;		(* Set lazy echo flags to something sensible *)
  AutoCount := 0;
  if Total then begin		(* First time? *)
    new(ZeroMacro);		(* Yes, set up macro header *)
    ZeroMacro^.Left := ZeroMacro;
    ZeroMacro^.Right := ZeroMacro;
    ZeroMacro^.Size := 0;
    ZeroMacro^.Number := 0;
    LastFrozen := 0;		(* Start freeze at zero *)
  end;
end;

(*---------------------------------------------------------------------------*)

(* ==> SCREEN *)

(*@VMS: [global] *)
procedure EchoClear;
var
  x, y: integer;
  i: integer;
begin
  EchoSize(x, y);		(* Get height of echo area *)
  for i := 0 to x-1 do
  begin
    EchoPos(i, 0);		(* Position cursor *)
    EchoEol;			(* Clear line *)
  end;
  EchoPos(0,0); 		(* Position at the echo area *)
  AutoStarted := false;
  AutoCount := 0;
end;

(*---------------------------------------------------------------------------*)

(*@TOPS: begin end. *)
(*@VMS: end. *)
