-- Copyright (C) 1986  by Xerox Corporation. All rights reserved. 
-- BWSCEnvUtil.mesa
-- DWR		17-Mar-86 20:27:30
-- NFS		27-May-86 13:16:03
-- MEW		 8-May-86  8:57:46

-- Implementation of CEnvUtil for BWS.

DIRECTORY
  Ascii,
  Atom,
--  BucketAlloc,
--  BWSCEnvUtilExtra,
  StartState,
  BWSUtil,
  CEnvUtil,
  Containee,
  Context,
  CRuntime,
  CString,
  CWindowLib,
  Display,
  Emulator,
  Format,
  FormWindow,
  Inline, 
  MLoader,
  MessageWindow,
  MFile,
  MFileOnNSFile,
  MStream,
  NSFile,
  NSString,
  OptionFile,
  Pipe,
  Process,
  Prototype,
  Runtime,
  Selection,
  SimpleTextFont,
  StarWindowShell,
  Stream,
  String,
  Time,
  TIP,
  TTY,
  TTYConstants,
  UserTerminal,
  Window,
  XChar,
  XString,
  XToken;
  
BWSCEnvUtil: MONITOR
 IMPORTS 
   Atom, --BucketAlloc, --BWSUtil, CEnvUtil, Containee, Context, CRuntime, CString,
   Display, Emulator, Format, FormWindow,  
   Inline, MessageWindow, MFile, MFileOnNSFile, MStream, NSFile, NSString, 
   OptionFile, Pipe, Process, Prototype, Runtime, Selection, 
   SimpleTextFont, StartState, StarWindowShell,
   Stream, String, Time, TIP,
   TTY, UserTerminal, XChar, XString, XToken
 EXPORTS CEnvUtil --, BWSCEnvUtilExtra -- SHARES XString = {
 OPEN CEnvUtil;


  Data:PUBLIC TYPE = --MACHINE DEPENDENT-- RECORD [
--      stdin(0): LONG STRING ← zone.NEW[StringBody[100]],
--      stdout(2): LONG STRING ← zone.NEW[StringBody[100]], 
--      stderr(4): LONG STRING ← zone.NEW[StringBody[100]],
      debug--(6)--:BOOLEAN ← FALSE,
      stopOnError--(7)--:BOOLEAN ← TRUE,
      endingToolInstance--(8)--:BOOLEAN ← FALSE,
      busy: BOOLEAN ← FALSE, -- for icon dropping in BWS
      iomode: INTEGER ← 0,
      cToolPart--(9)--:SELECT tool--(9)--:ToolType FROM
	ctool => [
	  msgSW--(10)--: Window.Handle ← NIL,
	  formSW--(12)--: Window.Handle ← NIL,
	  ttySW--(14)--: Window.Handle ← NIL,
	  p--(16)--:PROCESS,
	  emuPart--(17)--:SELECT tty--(17)--:* FROM
	    emulation => [
	      pH--(18)--:Pipe.Handle,
	      toBuffer--(20)--,ttyStream--(22)--:Stream.Handle,
	      modeVal--(24)--:LONG CARDINAL ← 0,
	      mode--(26)--:Stream.SubSequenceType ← TTYConstants.normal  ],
	    regular =>[logStream--(18)--:Stream.Handle ← NIL],
	    ENDCASE],
	cexec => [],
	temp => [],
	ENDCASE];
  
  Handle:TYPE = LONG POINTER TO Data;
  CTHandle:TYPE = LONG POINTER TO ctool Data;
  RCTHandle:TYPE = LONG POINTER TO regular ctool Data;
  ECTHandle:TYPE = LONG POINTER TO emulation ctool Data;
  
  FileHandle: PUBLIC TYPE = MFile.Handle;
  LoadHandle: PUBLIC TYPE = MLoader.Handle;
  
  FileError: PUBLIC ERROR[message:LONG STRING] = CODE;
  
  AcquireFile: PUBLIC PROCEDURE [name: LONG STRING] RETURNS[fh: FileHandle ← NIL] = {
    fh ← MFile.Acquire[name:name,access:readOnly,release:[]
      !MFile.Error => {
	msg:LONG STRING ← [100];
	MFile.AppendErrorMessage[msg,code,file];
	ERROR FileError[msg];}];
  };
 
  ReleaseFile: PUBLIC PROCEDURE [fh: FileHandle] = {
    MFile.Release[fh !MFile.Error => CONTINUE];
  };
  
   -- check for file,  file.archivebcd, file.bcd, file*.archivebcd, file*.bcd
  
  RemoveExtension: PROCEDURE [s: LONG STRING, ext: LONG STRING] = {
    IF ext = NIL THEN RETURN;
    s.length ← s.length - ext.length };
   

  UniqueFilePrefix: PUBLIC PROCEDURE [fileName: LONG STRING]
   RETURNS [matches: CARDINAL] = {
    -- complete the fileName and return whether it was unique
    -- assume fileName is long enough to be appended to
    -- FIX THIS TO USE MFILE ONLY !!!!!!!
    scope: NSFile.Scope ← [];
    selections: NSFile.Selections ← [interpreted: [name: TRUE]];

    Check: PROCEDURE [ext: LONG STRING]
      RETURNS [BOOLEAN] = {
      matchedName: LONG STRING ← [50];
      oldLength: CARDINAL = fileName.length;
      
      List: NSFile.AttributesProc = {
        matchedName.length ← 0;
	NSString.AppendToMesaString[matchedName, attributes.name];
	RETURN[(matches ← matches + 1) = 1] };
      
      EachDirectory: MFileOnNSFile.EachElementProc = {
	dfh: NSFile.Handle ← NSFile.OpenByReference [element];
	NSFile.List[dfh, List, selections, scope !
	  NSFile.Error => GOTO FileError];
	NSFile.Close[dfh];
	IF matches = 1 THEN { -- found a unique name
	  fileName.length ← 0;
	  String.AppendString[to: fileName, from: matchedName];
	};
	RETURN [matches # 0]; -- TRUE if we have a match or an ambiguous name
	EXITS FileError => { -- stop enumeration and indicate not found.
	  matches ← 0;
	  RETURN [TRUE];};
       };
      
      IF ext # NIL THEN String.AppendString[fileName, ext];
      scope.filter ← [matches[[name[NSString.StringFromMesaString[fileName]]]]];
      MFileOnNSFile.EnumeratePath [EachDirectory];
      IF matches # 1 THEN fileName.length ← oldLength; -- undo append;
      RETURN [matches # 0] };   -- TRUE if we have a match or an ambiguous name
    
    matches ← 0;
    IF Check[NIL] OR
      Check[".archivebcd"L] OR
      Check[".bcd"L] OR
      Check["*.archivebcd"L] OR
      Check["*.bcd"L] THEN NULL;
  };
 
  GetStdStreams: PUBLIC PROCEDURE [
    data: Handle, defaultStdin, defaultStdout, defaultStderr: Stream.Handle]
    RETURNS [stdin, stdout, stderr: Stream.Handle] = {
    h: ECTHandle = LOOPHOLE[data];
    rb, rb1: XString.ReaderBody;
    -- The following is a hack until we figure out what to do.
    -- We don't have the stdin,... fields in a data object, so
    -- we can't get the stream names from there. And when this
    -- is the temp variant, there's no form subwindow to use.
    WITH data SELECT FROM 
      temp => RETURN[defaultStdin, defaultStdout, defaultStderr]
    ENDCASE;
    rb ← FormWindow.LookAtTextItemValue[h.formSW, FormItems.stdin.ORD];
    IF XString.Empty[@rb] THEN {
      stdin ← defaultStdin;
      FormWindow.DoneLookingAtTextItemValue[h.formSW, FormItems.stdin.ORD];
    } ELSE {
      stdin ← BWSUtil.MakeStreamReader[file: @rb, access: readOnly];
      FormWindow.DoneLookingAtTextItemValue[h.formSW, FormItems.stdin.ORD];
      IF stdin = NIL THEN StdStreamError[stdin];
    };
    rb ← FormWindow.LookAtTextItemValue[h.formSW, FormItems.stdout.ORD];
    IF XString.Empty[@rb] THEN {
      stdout ← defaultStdout;
    } ELSE {
      stdout ← BWSUtil.MakeStreamReader[file: @rb, access: writeOnly];
      IF stdout = NIL THEN {
	Stream.Delete[stdin];
	FormWindow.DoneLookingAtTextItemValue[h.formSW, FormItems.stdout.ORD];
	ERROR StdStreamError[stdout];
      };
    };
    rb1 ← FormWindow.LookAtTextItemValue[h.formSW, FormItems.stderr.ORD];
    SELECT TRUE FROM
      XString.Empty[@rb1] => stderr ← defaultStderr;
      XString.Equivalent[@rb, @rb1] => stderr ← stdout;
    ENDCASE => {
      stderr ← BWSUtil.MakeStreamReader[file: @rb1, access: writeOnly];
      IF stderr = NIL THEN {
	Stream.Delete[stdin];
	Stream.Delete[stdout];
	FormWindow.DoneLookingAtTextItemValue[h.formSW, FormItems.stdout.ORD];
	FormWindow.DoneLookingAtTextItemValue[h.formSW, FormItems.stderr.ORD];
	ERROR StdStreamError[stderr];
      };
    };
    FormWindow.DoneLookingAtTextItemValue[h.formSW, FormItems.stdout.ORD];
    FormWindow.DoneLookingAtTextItemValue[h.formSW, FormItems.stderr.ORD];
  };
  
  StdStreamError: PUBLIC ERROR [whichStream: StandardStream] = CODE;
 
  GetFileInputStream: PUBLIC PROCEDURE [buffer: LONG STRING]
    RETURNS[sH: Stream.Handle ← NIL] = {
     fileName: XString.ReaderBody;
     rb: XString.ReaderBody ← XString.FromSTRING[buffer, TRUE];
     tH: XToken.Handle ← XToken.ReaderToHandle[@rb];
     XToken.Skip[tH, NIL, XToken.WhiteSpace, TRUE]; 
     fileName ← XToken.Item[tH];
     IF ~XString.Empty[@fileName] THEN {
       sH ← BWSUtil.MakeStreamReader[file: @fileName, access: readOnly];
       IF sH = NIL THEN { -- Try adding ".cscript" extension.
	 ext: XString.ReaderBody ← XString.FromSTRING[".cscript"L, TRUE];
	 extendedName: XString.WriterBody ← XString.NewWriterBody[
	   maxLength: XString.ByteLength[@fileName] + XString.ByteLength[@ext],
	   z: zone];
	 XString.AppendReader[@extendedName, @fileName];
	 IF XString.AppendExtensionIfNeeded[
	   to: @extendedName, extension: @ext] THEN
	     sH ← BWSUtil.MakeStreamReader[
	       file: XString.ReaderFromWriter[@extendedName],
	       access: readOnly];
	 XString.FreeWriterBytes[@extendedName];
       };
     };
     [] ← XToken.FreeTokenString[@fileName];
     [] ← XToken.FreeReaderHandle[tH];
  }; 
  
  AppendLine: PUBLIC PROCEDURE [
    line: LONG POINTER TO LONG STRING, getChar: PROC RETURNS [c: CHAR]] = {
     DO
       c: CHAR ← getChar[! MFile.Error, MStream.Error, Stream.EndOfStream => EXIT];
       String.AppendCharAndGrow[to: line, c: c, z: zone];
       IF c = Ascii.CR THEN EXIT;
       ENDLOOP;
    };

  contextType: Context.Type;
  
  ToolList: TYPE = LONG POINTER TO ToolInstance;
  ToolInstance: TYPE = RECORD [
    ref: NSFile.Reference,
    file: NSFile.Handle,
    bw: Window.Handle,
    sws: StarWindowShell.Handle,
    next: ToolList
  ];
  tools: ToolList ← NIL;
  
  OpenCTool: ENTRY PROCEDURE [data: Containee.DataHandle]
    RETURNS [sws: StarWindowShell.Handle, bw: Window.Handle] = {
    tool: ToolList ← FindRunningToolFromIcon[data];
    IF tool # NIL AND tool.sws # NIL THEN RETURN [tool.sws, tool.bw];
    BEGIN
      name: LONG STRING ← [60];
      xname: XString.ReaderBody;
      String.AppendString[name, "CTool  of "L];
      Time.Append[name, Time.Unpack[Runtime.GetBcdTime[]]];
      xname ← XString.FromSTRING[name, TRUE];
      sws ← StarWindowShell.Create[name: @xname, transitionProc: ClientTransition, sleeps: TRUE];
      StarWindowShell.SetIsCloseLegalProc[sws, IsCloseLegal];
      StarWindowShell.SetPreferredDims[sws, [h: windowDims.h, w: windowDims.w + 15]];
      IF tool = NIL THEN {
	bw ← MakeSWs[sws];
	AddRunningTool[data, sws, bw];
      } ELSE {
	StarWindowShell.InstallBody[sws, tool.bw];
	tool.sws ← sws;
	bw ← tool.bw;
      };
    END;
  };
  
  IsCloseLegal: ENTRY StarWindowShell.IsCloseLegalProc = {
    tool: ToolList = FindRunningToolFromSWS[sws];
    IF tool = NIL THEN RETURN [TRUE];
    StarWindowShell.DestallBody[tool.bw];
    tool.sws ← [NIL];
    RETURN [TRUE];
  };
  
  FindRunningToolFromIcon: INTERNAL PROCEDURE [data: Containee.DataHandle]
    RETURNS [t: ToolList] = {
    FOR t ← tools, t.next UNTIL t = NIL DO
      IF t.ref = data.reference THEN RETURN [t];
    ENDLOOP;
    RETURN [NIL];
  };
  
  FindRunningToolFromSWS: INTERNAL PROCEDURE [sws: StarWindowShell.Handle]
    RETURNS [t: ToolList] = {
    FOR t ← tools, t.next UNTIL t = NIL DO
      IF t.sws = sws THEN RETURN [t];
    ENDLOOP;
    RETURN [NIL];
  };
  
  FindSWSFromBW: ENTRY PROCEDURE [bw: Window.Handle]
    RETURNS [sws: StarWindowShell.Handle] = {
    FOR t: ToolList ← tools, t.next UNTIL t = NIL DO
      IF t.bw = bw THEN RETURN [t.sws];
    ENDLOOP;
    RETURN [[NIL]];
  };
  
  AddRunningTool: INTERNAL PROCEDURE [data: Containee.DataHandle,
    sws: StarWindowShell.Handle, bw: Window.Handle] = {
    tools ← zone.NEW[ToolInstance ← [
      ref: data.reference,
      bw: bw,
      sws: sws,
      file: NSFile.OpenByReference[data.reference], -- so icon won't be deleted
      next: tools]];
  };
  
  DeleteRunningTool: PROCEDURE [bw: Window.Handle] = {
    t: ToolList;
    sws: StarWindowShell.Handle;
    DeleteInstance: ENTRY PROC = {
      prev: LONG POINTER TO ToolList ← @tools;
      FOR t ← prev↑, prev↑ UNTIL t = NIL DO
	IF t.bw = bw THEN {
	  prev↑ ← t.next;
	  NSFile.Close[t.file];
	  zone.FREE[@t];
	  RETURN;
	};
	prev ← @t.next;
      ENDLOOP;
    };
    sws ← FindSWSFromBW[bw];
    IF sws # NIL THEN
      [] ← StarWindowShell.Pop[sws];
    DeleteInstance[];
    DestroyCToolInstance[bw];
  };
  
  DestroyInstance: FormWindow.CommandProc = {
    h: CTHandle;
    bw: Window.Handle;
    [h, bw] ← LocalFind[window];
    IF h = NIL THEN RETURN;
    IF h.busy THEN {
      rb: XString.ReaderBody ←
        XString.FromSTRING["CTool is busy.  Hit STOP to abort current program."L];
      MessageWindow.Post[h.msgSW, @rb, TRUE];
    } ELSE {
      DeleteRunningTool[bw];
    };
  };
   
  MakeSWs: INTERNAL PROCEDURE [sws: StarWindowShell.Handle]
    RETURNS [bw: Window.Handle] = { 
    h: ECTHandle ← zone.NEW[emulation ctool Data];
    tH: TTY.Handle;
    bw ← StarWindowShell.CreateBody[
      sws: sws,
      box: [place: [x: 0, y: 0], dims: windowDims]];
    FormWindow.Create[
      window: bw,
      makeItemsProc: MakeItems,
      layoutProc: DoLayout,
      clientData: h];
    Context.Create[contextType, h, FreeToolData, bw];
    [h:h.pH,producer:h.toBuffer, consumer:h.ttyStream] ← Pipe.Create[200];
    h.ttyStream.put ← WriteToWindow;
    h.ttyStream.putByte ← Stream.defaultObject.putByte;
    -- Pipe.Create set putByte to a proc. that raises Stream.InvalidOperation.
    h.ttyStream.setSST ← SetSST;
    h.ttyStream.clientData ← h;
    tH ← TTY.Create[name:NIL,backingStream:NIL,ttyImpl:h.ttyStream];
    TIP.SetAttention[h.ttySW, AbortProgram];
    h.p ← FORK ReadAndRunThenCleanUp[h,tH];
  };
   
  WriteToWindow:Stream.PutProcedure = {
    h:ECTHandle = sH.clientData;
    crMode: BOOLEAN ← Inline.BITAND[h.iomode, CWindowLib.crMode] #0;
    SELECT h.mode FROM
      TTYConstants.normal => {
        start: CARDINAL ← block.startIndex;
	stop: CARDINAL ← block.stopIndexPlusOne;
	IF crMode THEN Emulator.PutBlock[h.ttySW, [block.blockPointer, start, stop]]
        ELSE FOR i:CARDINAL IN [block.startIndex..block.stopIndexPlusOne) DO
	  IF block.blockPointer[i] # Ascii.CR.ORD THEN LOOP;
	  stop ← i + 1;
	  Emulator.PutBlock[h.ttySW, [block.blockPointer, start, stop]];
	  start ← stop;
	  Emulator.PutChar[h.ttySW,Ascii.LF];
	  REPEAT FINISHED => IF block.stopIndexPlusOne > start THEN 
	    Emulator.PutBlock[
	      h.ttySW, [block.blockPointer, start, block.stopIndexPlusOne]];
	  ENDLOOP;
	};
      TTYConstants.setBackingSize, TTYConstants.removeChars => {
        FOR i: CARDINAL IN [block.startIndex..block.stopIndexPlusOne) DO
	  h.modeVal ← h.modeVal*256 + block.blockPointer[i];
	  ENDLOOP;
      };
      ENDCASE;
  };
  
  SetSST:Stream.SetSSTProcedure = {
    h:ECTHandle = sH.clientData;
    s: LONG STRING ← [3];
    s[0] ← s[2] ← Ascii.BS;
    s[1] ← Ascii.SP;
    s.length ← 3;
    SELECT h.mode FROM
      TTYConstants.removeChars => 
        FOR i: CARDINAL IN [0..Inline.LowHalf[h.modeVal]) DO
	  Emulator.PutSTRING[h.ttySW,s];
	  ENDLOOP;
       TTYConstants.setBackingSize => {};
       TTYConstants.blinkDisplay => UserTerminal.BlinkDisplay[];
       ENDCASE;
     h.mode ← sst;
     h.modeVal ← 0;
   };
   
  ReadAndRunThenCleanUp:PROCEDURE[h:ECTHandle,tH:TTY.Handle] = {
    AppendCmd:AppendCmdProc = {RETURN[CallGetEditedString[tH,buffer]];};
    [] ← ReadAndRun[h,tH,AppendCmd];
    TTY.Destroy[tH,TRUE];
    Pipe.Delete[h.pH];
    Emulator.Destroy[h.ttySW];
  };
  
  FormItems: TYPE = {msg, form, tty, --another,-- destroy, debug, stopOnError, stdin, stdout, stderr};
  margin: INTEGER = 2;
  windowDims: Window.Dims = [w: 630, h: 500];
  msgWindowDims: Window.Dims = [w: windowDims.w - 2*margin, h: 20];
  msgWindowBox: Window.Box = [
    place: [
      x: margin,
      y: margin],
    dims: msgWindowDims];
  formWindowDims: Window.Dims = [w: windowDims.w - 2*margin, h: 46];
  formWindowBox: Window.Box = [
    place: [
      x: margin,
      y: msgWindowBox.place.y + msgWindowBox.dims.h + 2*margin],
    dims: formWindowDims];
  ttyWindowDims: Window.Dims = [w: windowDims.w - 2*margin, h: 300];   
  ttyWindowBox: Window.Box = [
     place: [
       x: margin,
       y: formWindowBox.place.y + formWindowBox.dims.h + 2*margin],
     dims: ttyWindowDims];   

  MakeItems: FormWindow.MakeItemsProc = {
    h: ECTHandle = clientData;
    h.msgSW ← FormWindow.MakeWindowItem[
      window: window,
      myKey: FormItems.msg.ORD,
      size: msgWindowDims];
    MessageWindow.Create[window: h.msgSW, lines: 1];
      
    h.formSW ← FormWindow.MakeWindowItem [ -- the formSW is inside a FormWindow window item
      window: window,
      myKey: FormItems.form.ORD,
      size: formWindowDims];
    FormWindow.Create[
      window: h.formSW,
      makeItemsProc: MakeFormItems,
      layoutProc: DoFormLayout,
      clientData: h];

    h.ttySW ← FormWindow.MakeWindowItem [
      window: window,
      myKey: FormItems.tty.ORD,
      size: ttyWindowDims];
    Emulator.Create[
      sw: h.ttySW, data:Emulator.table[vt100],
      typeIn:WriteToBuffer, otherData: Emulator.table[xvt52], refresh: never,
      logfile:NIL ,writeToLog:FALSE];
    Emulator.SetRefresh[h.ttySW, always];
  };
  
  WriteToBuffer:Emulator.TypeInProcType = {
    h: ECTHandle = LOOPHOLE[LocalFind[sw].h];
    IF h = NIL THEN RETURN;
    Stream.PutBlock[h.toBuffer,input];
  };
  
  FormTags: TYPE = ARRAY FormItems[destroy..stderr] OF XString.ReaderBody;
  
  MakeFormItems: FormWindow.MakeItemsProc = {
    h: ECTHandle = clientData;
    tags: FormTags ← [
      XString.FromSTRING["Destroy"L, TRUE],
      XString.FromSTRING["Debug"L, TRUE],
      XString.FromSTRING["StopScriptOnError"L, TRUE],
      XString.FromSTRING["stdin"L, TRUE],
      XString.FromSTRING["stdout"L, TRUE],
      XString.FromSTRING["stderr"L, TRUE]];
    FormWindow.MakeCommandItem[
      window: window,
      myKey: FormItems.destroy.ORD,
      commandProc: DestroyInstance,
      commandName: @tags[destroy]];
    FormWindow.MakeBooleanItem[
      window: window,
      myKey: FormItems.debug.ORD,
      changeProc: BooleanChange,
      label: [string[tags[debug]]],
      initBoolean: h.debug];
    FormWindow.MakeBooleanItem[
      window: window,
      myKey: FormItems.stopOnError.ORD,
      changeProc: BooleanChange,
      label: [string[tags[stopOnError]]],
      initBoolean: h.stopOnError];
    FormWindow.MakeTextItem[
      window: window,
      myKey: FormItems.stdin.ORD,
      width: 130,
      tag: @tags[stdin]];
    FormWindow.MakeTextItem[
      window: window,
      myKey: FormItems.stdout.ORD,
      width: 130,
      tag: @tags[stdout]];
    FormWindow.MakeTextItem[
      window: window,
      myKey: FormItems.stderr.ORD,
      width: 130,
      tag: @tags[stderr]];
  };
  
  LocalFind: ENTRY PROC [window: Window.Handle]
    RETURNS [h: CTHandle, bw: Window.Handle] = {
    sws: StarWindowShell.Handle = StarWindowShell.ShellFromChild[window];
    tool: ToolList = FindRunningToolFromSWS[sws];
    IF tool = NIL THEN RETURN [NIL, NIL]; 
    bw ← tool.bw;
    h ← Context.Find[contextType, bw];
  };

  BooleanChange: FormWindow.BooleanChangeProc = {
    h: CTHandle = LocalFind[window].h;
    IF h = NIL THEN RETURN;
    SELECT item FROM
      FormItems.debug.ORD => h.debug ← newValue;
      FormItems.stopOnError.ORD => h.stopOnError ← newValue;
    ENDCASE;
  };
      
  DoFormLayout: FormWindow.LayoutProc = {
    line: FormWindow.Line;
    line ← FormWindow.AppendLine[window, margin];
--    FormWindow.AppendItem[window, FormItems.another.ORD, line, 20];
    FormWindow.AppendItem[window, FormItems.destroy.ORD, line, 50];
    FormWindow.AppendItem[window, FormItems.debug.ORD, line, 50];
    FormWindow.AppendItem[window, FormItems.stopOnError.ORD, line, 50];
    line ← FormWindow.AppendLine[window, margin];
    FormWindow.AppendItem[window, FormItems.stdin.ORD, line, 10];
    FormWindow.AppendItem[window, FormItems.stdout.ORD, line, 10];
    FormWindow.AppendItem[window, FormItems.stderr.ORD, line, 10];
  };
  
  DoLayout: FormWindow.LayoutProc = { -- for three windows
    FormWindow.SetItemBox[window, FormItems.msg.ORD, msgWindowBox];
    FormWindow.SetItemBox[window, FormItems.form.ORD, formWindowBox];
    FormWindow.SetItemBox[window, FormItems.tty.ORD, ttyWindowBox];
  };
   
  CallReadAndRun: PROCEDURE [h:CTHandle, tH:TTY.Handle] = {
    AppendCmd:AppendCmdProc = {
      h.busy ← FALSE;
      Process.SetPriority[Process.priorityForeground];
      lineDeleted ← CallGetEditedString[tH, buffer];
      Process.SetPriority[Process.priorityBackground];
      h.busy ← TRUE;
    };
    [] ← ReadAndRun[h,tH,AppendCmd];
  };
   
  AbortProgram: TIP.AttentionProc = {
    h: CTHandle = LocalFind[window].h;
    IF h = NIL THEN RETURN;
    CRuntime.NoteAbortedProcess[h.p];
  };
   
  FreeToolData: PROCEDURE[d: Context.Data, bw: Window.Handle] = {
    h: CTHandle ← LOOPHOLE[d];
--  TTYStringWindow.Destroy[h.ttySW];
--    FormWindow.Destroy[h.formSW]; ++ maybe these are done by BWS
--    MessageWindow.Destroy[h.msgSW];
    zone.FREE[@h];
  };
  
  FreeData:PUBLIC PROCEDURE[h: Handle] = {};
   
  ClientTransition: StarWindowShell.TransitionProc = {};
  
  DestroyCToolInstance: PROCEDURE [bw: Window.Handle] = {
    h: CTHandle ← Context.Find[contextType, bw];
    h.endingToolInstance ← TRUE;
    Process.Abort[h.p];
    JOIN h.p;
    Context.Destroy[contextType, bw];
  };
    
   CallGetEditedString: PROCEDURE[
     tH: TTY.Handle, buffer: LONG POINTER TO LONG STRING]
     RETURNS[lineDeleted: BOOLEAN ← FALSE] = {
     CommandLineRead: PROCEDURE [c: CHAR] RETURNS [status: TTY.CharStatus] = {
       IF c = '\N THEN {
	 TTY.PutChar[tH,c];
	 RETURN[stop];}
       ELSE RETURN[ok];
     };
     [] ← TTY.GetEditedString[h:tH,s:buffer↑,t:CommandLineRead
       !TTY.LineOverflow => {
	   ns ← String.CopyToNewString[s:s,z:zone,longer:s.maxlength / 2];
	   String.FreeString[z:zone, s:s];
	   buffer↑ ← ns;
	   RESUME[ns];};
	 TTY.Rubout => {
	   TTY.PutString[tH," XXX"L];
	   lineDeleted ← TRUE;
	   CONTINUE;}];
    };
    
  RegisterItems: PUBLIC PROCEDURE = {};
  
  GetPrompt: PUBLIC PROCEDURE RETURNS [prompt: LONG STRING ← NIL] = {
    section, entry: XString.ReaderBody;
    CallBack: PROC [value: XString.Reader] = {
      ss: String.SubStringDescriptor;
      prompt ← zone.NEW[StringBody[XString.ByteLength[value]+1]];
      prompt.length ← 0;
      String.AppendChar[s: prompt, c: Ascii.CR];
      ss ← BWSUtil.SubStringFromXString[value];
      String.AppendSubString[prompt, @ss];
    };
    section ← XString.FromSTRING["CTool"L, TRUE];
    entry ← XString.FromSTRING["Prompt"L, TRUE];
    OptionFile.GetStringValue[@section, @entry, CallBack ! OptionFile.Error => CONTINUE];
    IF prompt = NIL THEN {
      prompt ← zone.NEW[StringBody[5]];
      prompt.length ← 0;
      String.AppendString[to:prompt, from:"\N>>> "L];
    };
  };
  
  Unload: PUBLIC PROCEDURE [cmdLine: LONG STRING, tty: TTY.Handle]
    RETURNS[outcome: Outcome ← normal] = {
    rb: XString.ReaderBody ← XString.FromSTRING[cmdLine];
    th: XToken.Handle ← NIL;
    item: XString.ReaderBody ← XString.nullReaderBody;
    sd: String.SubStringDescriptor;
--    fileName:LONG STRING ← zone.NEW[StringBody[100]];
    {ENABLE UNWIND => {
      IF item # XString.nullReaderBody THEN [] ← XToken.FreeTokenString[@item];
      IF th # NIL THEN [] ← XToken.FreeReaderHandle[th];};
    th ← XToken.ReaderToHandle[@rb];
    item ← XToken.Item[th]; -- Get past "Unload.~"
    [] ← XToken.FreeTokenString[@item];
    DO
      item ← XToken.Item[th];
      IF item = XString.nullReaderBody THEN EXIT;
      TTY.PutString[tty,"\NUnloading "L];
      sd ← BWSUtil.SubStringFromXString[@item];
      BEGIN
        lh: MLoader.Handle ←LOOPHOLE[
	  XString.ReaderToNumber[r: @item, radix: 8, signed: FALSE
	  ! XString.InvalidNumber => GOTO mustBeAFile], LONG POINTER];
	TTY.PutSubString[tty, @sd];
	TTY.PutString[tty,"..."L];
	StartState.Unload[lh ! StartState.UnloadError => {
	  outcome ← error;
	  Blink[];
	  TTY.PutString[tty, message];
	  GOTO done}];
	TTY.PutString[tty,"done."L];
      EXITS
        done => NULL;
	mustBeAFile => {
	  name: LONG STRING ← [50];
	  fh: MFile.Handle;
	  found: CARDINAL;
	  instancesUnloaded:CARDINAL;
	  String.AppendSubString[name, @sd];
	  found ← UniqueFilePrefix[name];
	  IF found # 1 THEN {
	    TTY.PutSubString[tty, @sd];
	    TTY.PutString[
	      tty,IF found = 0 THEN ": not found"L ELSE ": ambiguous"L];
	    GOTO done;
	  };
	  TTY.PutString[tty, name];
	  TTY.PutString[tty,"..."L];
	  fh ← MFile.Acquire[name, anchor, [] !MFile.Error => {
	      TTY.PutString[tty, "Could not aquire file.\n"L];
	      outcome ← error;
	      GOTO done;
	    }];
	  instancesUnloaded ← StartState.UnloadFromFile[fh ! StartState.UnloadError => {
	    outcome ← error;
	    Blink[];
	    TTY.PutString[tty, message];
	    instancesUnloaded ← instancesAlreadyUnloaded;
	    MFile.Release[fh];
	    GOTO done;}];
	  MFile.Release[fh];
	  TTY.PutCR[tty];
	  TTY.PutNumber[tty, instancesUnloaded, []];
	  TTY.PutString[tty," instance"L];
	  IF instancesUnloaded # 1 THEN
	    TTY.PutChar[tty, 's];
	  TTY.PutString[tty, " unloaded."L];
	EXITS
	  done => NULL;
	};
      END;
      [] ← XToken.FreeTokenString[@item];
    ENDLOOP;
    [] ← XToken.FreeTokenString[@item];
    th ← XToken.FreeReaderHandle[th];
    }; 
  };
  
  ShowHandles:PUBLIC PROCEDURE[cmdLine:LONG STRING,tty:TTY.Handle]
    RETURNS[outcome:Outcome ← normal] = {
    PrintLoadHandle:StartState.EnumerateProc = {
      Write:Format.StringProc = {TTY.PutString[tty,s];};
      Format.LongOctal[Write,mh];
      TTY.PutChar[tty,' ];
    };
    rb:XString.ReaderBody ← XString.FromSTRING[cmdLine];
    th:XToken.Handle ← NIL;
    item:XString.ReaderBody ← XString.nullReaderBody;
    {ENABLE UNWIND => {
      IF item # XString.nullReaderBody THEN [] ← XToken.FreeTokenString[@item];
      IF th # NIL THEN [] ← XToken.FreeReaderHandle[th];};
    th ← XToken.ReaderToHandle[@rb];
    item ← XToken.Item[th]; -- Get past "ShowHandles.~"
    [] ← XToken.FreeTokenString[@item];
    DO
     file:MFile.Handle;
     found: CARDINAL;
     name:LONG STRING ← zone.NEW[StringBody[100]];
     sd: String.SubStringDescriptor;
     item ← XToken.Item[th];
     IF item = XString.nullReaderBody THEN EXIT;
     sd ← BWSUtil.SubStringFromXString[@item];
     String.AppendSubString[name, @sd];
     found ← UniqueFilePrefix[name];
     IF found # 1 THEN {
       TTY.PutSubString[tty, @sd];
       TTY.PutString[tty, IF found = 0 THEN ": not found"L ELSE ": ambiguous"L];
       GOTO error;
     };
     TTY.PutChar[tty,'\N];
     TTY.PutString[tty,name];
     TTY.PutString[tty,": "L];
     file ← MFile.Acquire[name, anchor, []! MFile.Error => {
	      TTY.PutString[tty, "Unable to acquire file."L];
	      zone.FREE[@name];
	      outcome ← error;
	      GOTO Done;
	    }];
     StartState.EnumerateHandles[file,PrintLoadHandle];
     MFile.Release[file];
     zone.FREE[@name];
     [] ← XToken.FreeTokenString[@item];
     ENDLOOP;
   EXITS Done => NULL; error => outcome ← error;
   };
   th ← XToken.FreeReaderHandle[th];
 };


  ChangeStandardStreams: PUBLIC PROCEDURE [buffer: LONG STRING, data: Handle] = {
    rb: XString.ReaderBody ← XString.FromSTRING[buffer];
    tH: XToken.Handle = XToken.ReaderToHandle[@rb];
    token: XString.ReaderBody;
    XToken.Skip[tH, NIL, XToken.WhiteSpace, TRUE];
    -- stdin, stdout, stderr
    FOR std: FormItems IN [stdin..stderr] DO
      token ← XToken.Filtered[tH, NIL, FilterFileName];
      WITH hh:data SELECT FROM
	ctool => FormWindow.SetTextItemValue[hh.formSW, std.ORD, @token];
      ENDCASE;
      [] ← XToken.FreeTokenString[@token];
    ENDLOOP;
    [] ← XToken.FreeReaderHandle[tH];
  };
  
  FilterFileName: XToken.FilterProcType = {
    char: CHARACTER = LOOPHOLE[XChar.Code[c], CHARACTER];
    inClass ← char # Ascii.SP AND char # ', AND char # Ascii.CR AND char # Ascii.TAB;};
    
  ChangeSwitches: PUBLIC PROCEDURE [buffer: LONG STRING, data: Handle] = {
    rb: XString.ReaderBody ← XString.FromSTRING[buffer];
    tH: XToken.Handle = XToken.ReaderToHandle[@rb];
    switches: XString.ReaderBody;
    sense: BOOLEAN ← TRUE;
    XToken.Skip[tH, NIL, XToken.WhiteSpace, TRUE];
    switches ← XToken.Filtered[tH, NIL, XToken.Switches];
    DO
      SELECT XString.Lop[@switches] FROM
	  '~.ORD, '-.ORD => sense ← ~sense;
	  'd.ORD => {
	    data.debug ← sense;
	    WITH hh:data SELECT FROM
	      ctool => FormWindow.SetBooleanItemValue[hh.formSW, FormItems.debug.ORD, data.debug];
	    ENDCASE;
	    sense ← TRUE;};
	  's.ORD => {
	     data.stopOnError ← sense;
	     WITH hh:data SELECT FROM
	      ctool => FormWindow.SetBooleanItemValue[hh.formSW, FormItems.stopOnError.ORD, data.stopOnError];
	    ENDCASE;
	    sense ← TRUE;};
	  XChar.not => EXIT;
      ENDCASE;
    ENDLOOP;
    [] ← XToken.FreeTokenString[@switches];
    [] ← XToken.FreeReaderHandle[tH];
  };
   
  PrintStatus: PUBLIC PROCEDURE [data: Handle, tty: TTY.Handle] = {
    ss: String.SubStringDescriptor;
    rb: XString.ReaderBody;
    FOR std: FormItems IN [stdin..stderr] DO
      TTY.PutString[tty, (SELECT std FROM
	  stdin => "stdin: "L,
	  stdout => "\Nstdout: "L,
	  stderr => "\Nstderr: "L,
	ENDCASE => "bogus:"L)];
      WITH h: data SELECT FROM
	ctool => {
	  rb ← FormWindow.LookAtTextItemValue[h.formSW, std.ORD];
	  ss ← BWSUtil.SubStringFromXString[@rb];
	  IF ss.length = 0 THEN
	    TTY.PutString[tty, "window"L]
	  ELSE
	    TTY.PutSubString[tty, @ss];
	  FormWindow.DoneLookingAtTextItemValue[h.formSW, std.ORD];
	};
      ENDCASE;
    ENDLOOP;
    TTY.PutString[tty,"\NDebug: "L];
    TTY.PutString[tty,
      IF data.debug THEN "TRUE"L ELSE "FALSE"L];
    TTY.PutString[tty,"\NStopScriptOnError: "L];
    TTY.PutString[tty,
      IF data.stopOnError THEN "TRUE"L ELSE "FALSE"L];
  };
	 
  ParseLine: PUBLIC PROCEDURE [buffer: LONG STRING]
    RETURNS [name: LONG STRING ← NIL, argC: CARDINAL ← 1, argV: ArgSeq ← NIL] = {
    OPEN CString;
    initArgSeqLength:CARDINAL = 8;
    tH: XToken.Handle ← NIL;
    tempName: XString.ReaderBody ← XString.nullReaderBody;
    rb: XString.ReaderBody ← XString.FromSTRING[buffer];
    done: BOOLEAN ← FALSE;
    {ENABLE UNWIND => {
      IF tH # NIL THEN tH ← XToken.FreeReaderHandle[tH];
      IF tempName # XString.nullReaderBody THEN
        [] ← XToken.FreeTokenString[@tempName];};
    IF buffer.length = 0 THEN RETURN;
    tH ← XToken.ReaderToHandle[@rb];
    WHILE ~done DO
      tempName ← XToken.MaybeQuoted[tH, NIL !XToken.UnterminatedQuote => RESUME];
      IF ~XString.Empty[@tempName] THEN {
	ss: String.SubStringDescriptor ← BWSUtil.SubStringFromXString[@tempName];
	s: LONG STRING ← String.MakeString[z: zone, maxlength: ss.length];
	String.AppendSubString[s, @ss];
	IF argV = NIL THEN {
	  argV ← zone.NEW[StringSeq[initArgSeqLength]];
	  argV.length ← 0;
	  argC ← 0;
	  name ← String.CopyToNewString[s: s, z: zone, longer: 32];
	};
	argC ← argC + 1;
	IF argV.length < argV.maxlength THEN {
	  argV[argV.length] ← LongStringToCString[s, zone];
	  argV.length ← argV.length + 1;
	} ELSE {
	  oldArgV:ArgSeq ← argV;
	  {ENABLE UNWIND => {
	    IF argV # NIL THEN zone.FREE[@argV];
	    IF oldArgV # NIL THEN zone.FREE[@oldArgV];};
	  argV ← zone.NEW[StringSeq[oldArgV.maxlength * 2]];
	  FOR i:CARDINAL IN [0..oldArgV.length) DO
	    argV[i] ← oldArgV[i];
	  ENDLOOP;
	  argV.length ← oldArgV.length + 1;
	  argV[oldArgV.length] ← LongStringToCString[s, zone];
	  zone.FREE[@oldArgV];};
	};
	String.FreeString[z: zone, s: s];
      } ELSE {
        done ← TRUE;
      };
      [] ← XToken.FreeTokenString[@tempName];
    ENDLOOP;
    tH ← XToken.FreeReaderHandle[tH];
  };};
  
  GetFirstWord: PUBLIC PROCEDURE [buffer: LONG STRING]
    RETURNS[firstWord: LONG STRING ← NIL] = {
    rb: XString.ReaderBody ← XString.FromSTRING[buffer];
    th: XToken.Handle ← NIL;
    ts: XString.ReaderBody ← XString.nullReaderBody;
    {ENABLE UNWIND =>  {
      IF ts # XString.nullReaderBody THEN [] ← XToken.FreeTokenString[@ts];
      IF th # NIL THEN th ← XToken.FreeReaderHandle[th];};
    th ← XToken.ReaderToHandle[@rb];
    ts ← XToken.Item[th];
    IF ~XString.Empty[@ts] THEN {
      ss: String.SubStringDescriptor ← BWSUtil.SubStringFromXString[@ts];
      firstWord ← String.MakeString[z: zone, maxlength: ss.length];
      String.AppendSubString[firstWord, @ss];
    };
    [] ← XToken.FreeTokenString[@ts];
    th ← XToken.FreeReaderHandle[th];
    };
  };      
  
  Blink:PUBLIC PROCEDURE = {UserTerminal.BlinkDisplay[];};
  
  Print:PUBLIC PROCEDURE[msg: LONG STRING, data:Handle] = {
    rb: XString.ReaderBody ← XString.FromSTRING[msg, TRUE];
    WITH hh:data SELECT FROM
      ctool => MessageWindow.Post[window: hh.msgSW, r: @rb, clear: FALSE];
    ENDCASE;
  };
  
  <<PrintErrorReader: PUBLIC PROCEDURE [msg: XString.Reader, data:Handle, tH: TTY.Handle] = {
    ss: String.SubStringDescriptor ← BWSUtil.SubStringFromXString[msg];
    WITH hh:data SELECT FROM
      ctool => MessageWindow.Post[window: hh.msgSW, r: msg, clear: FALSE];
    ENDCASE;
  };>>
  
  PrintTTYReader:PUBLIC PROCEDURE[msg:XString.Reader,tH: TTY.Handle] = {
    ss: String.SubStringDescriptor ← BWSUtil.SubStringFromXString[msg];
    TTY.PutSubString[h: tH, ss: @ss];
  };
  
  ClearError:PUBLIC PROCEDURE[data:Handle] = {
    WITH hh:data SELECT FROM
      ctool => MessageWindow.Clear[hh.msgSW];
    ENDCASE;};
  
  Debugging:PUBLIC PROCEDURE[data:Handle] RETURNS[BOOLEAN] = {
    RETURN[data.debug];};
    
  StopOnError:PUBLIC PROCEDURE[data:Handle] RETURNS[BOOLEAN] = {
    RETURN[data.stopOnError];}; 
    
  EndingToolInstance:PUBLIC PROCEDURE[data:Handle] RETURNS[BOOLEAN] = {
    RETURN[data.endingToolInstance];};
  
  StopIfCExec:PUBLIC PROCEDURE[data:Handle] = {};
  
  UpdateLogReadLength:PUBLIC PROCEDURE[data:Handle] = {}; 
  
  MakeDataForSystemCall:PUBLIC PROCEDURE RETURNS[newData:Handle] = {
    newData ← zone.NEW[temp Data];
    newData.cToolPart ← temp[];
  }; 
    
  -- BWS Icon stuff follows;
  
  cToolIconType: NSFile.Type = 30304; -- one past compiler
  open, canYouTakeSelection, takeSelection, takeSelectionCopy: Atom.ATOM;
  old, new: Containee.Implementation ← [];
  ch: XChar.Character;
  true: BOOLEAN ← TRUE; -- for GenericProc
  false: BOOLEAN ← FALSE;
  Picture: TYPE = ARRAY [0..256) OF CARDINAL;
  
  MakeGenericProcAtoms: PROC = {
    open ← Atom.MakeAtom["Open"L];
    canYouTakeSelection ← Atom.MakeAtom["CanYouTakeSelection"L];
    takeSelection ← Atom.MakeAtom["TakeSelection"L];
    takeSelectionCopy ← Atom.MakeAtom["TakeSelectionCopy"L];
  };
  
  FindOrCreateIconFile: PROC = {
    name: XString.ReaderBody ← XString.FromSTRING["CTool Icon"L];
    version: CARDINAL = 0;
    IF (Prototype.Find[type: cToolIconType, version: version] = NSFile.nullReference)
      THEN  NSFile.Close[Prototype.Create [
        name: @name,
        type: cToolIconType,
        version: version] ];
  };
  
  SetImplementation: PROC = {
    old ← new ← Containee.GetImplementation[cToolIconType];
    new.genericProc ← GenericProc;
    new.pictureProc ← PictureProc;
    InitSmallPicture[];
    new.smallPictureProc ← SmallPictureProc;
    [] ← Containee.SetImplementation[cToolIconType, new];
  };
  
  TakeSelection: PROC [data: Containee.DataHandle]
    RETURNS [worked: LONG POINTER TO BOOLEAN ← @false] = {
    value: Selection.Value ← Selection.Convert[target: file];
    bw: Window.Handle = OpenCTool[data].bw; -- not literally, but it gets this one working
    h: ECTHandle = Context.Find[contextType, bw];
    IF ~h.busy AND value # Selection.nullValue THEN {
      rb: XString.ReaderBody;
      cr: XString.ReaderBody ← XString.FromSTRING["\n"L];
      ticket: Containee.Ticket;
      [rb, ticket] ← Containee.GetCachedName[
        LOOPHOLE[value.value, Containee.DataHandle]];
      Emulator.PutString[h.ttySW, @rb]; -- stuff in the name of the file to CTool
      Emulator.CR[h.ttySW];
      Containee.ReturnTicket[ticket];
      worked ← @true;
    };
    Selection.Free[@value];
  };
  
  GenericProc: Containee.GenericProc = {
    val: LONG UNSPECIFIED;
    SELECT atom FROM
      open => val ← OpenCTool[data].sws;
      canYouTakeSelection => val ← IF Selection.CanYouConvert[target: file] THEN @true ELSE @false;
      takeSelection, takeSelectionCopy => val ← TakeSelection[data];
    ENDCASE => RETURN old.genericProc[atom, data, changeProc, changeProcData];
    IF changeProc # NIL THEN
      changeProc[changeProcData: changeProcData, data: data, noChanges: TRUE];
    RETURN [val];
  };
 
  PictureProc: Containee.PictureProc = {
  <<[data: Containee.DataHandle, window: Window.Handle, box: Window.Box,
    old, new: Containee.PictureState]>>
    normalPic: Picture ← [
17777B, 177777B, 177777B, 177770B, 37777B, 177777B, 177777B, 177774B, 70000B, 17B, 177000B, 16B, 160000B, 377B, 177740B, 7B, 140000B, 1777B, 177770B, 3B, 140000B, 7777B, 177776B, 3B, 140000B, 37777B, 177777B, 100003B, 140000B, 77777B, 177777B, 140003B, 140001B, 177777B, 177777B, 170003B, 140003B, 177777B, 177777B, 174003B, 140007B, 177777B, 177777B, 176003B, 140017B, 177777B, 177777B, 177003B, 140037B, 177777B, 177777B, 177403B, 140077B, 177777B, 177777B, 177603B, 140177B, 177760B, 777B, 177703B, 140177B, 177700B, 177B, 177703B, 140377B, 177400B, 37B, 177743B, 140777B, 176000B, 7B, 177763B, 140777B, 174000B, 3B, 177763B, 141777B, 170000B, 1B, 177773B, 141777B, 160000B, 0B, 3B, 143777B, 140000B, 0B, 3B, 143777B, 140000B, 0B, 3B, 147777B, 100000B, 0B, 3B, 147777B, 100000B, 0B, 3B, 147777B, 0B, 0B, 3B, 147777B, 0B, 0B, 3B, 157776B, 0B, 0B, 3B, 157776B, 0B, 0B, 3B, 157776B, 0B, 0B, 3B, 157776B, 0B, 0B, 3B, 157776B, 0B, 0B, 3B, 157776B, 0B, 0B, 3B, 157776B, 0B, 0B, 3B, 157776B, 0B, 0B, 3B, 157776B, 0B, 0B, 3B, 157776B, 0B, 0B, 3B, 147777B, 0B, 0B, 3B, 147777B, 0B, 0B, 3B, 147777B, 100000B, 0B, 3B, 147777B, 100000B, 0B, 3B, 143777B, 140000B, 0B, 3B, 143777B, 140000B, 0B, 3B, 141777B, 160000B, 0B, 3B, 141777B, 170000B, 1B, 177773B, 140777B, 174000B, 3B, 177763B, 140777B, 176000B, 7B, 177763B, 140377B, 177400B, 37B, 177743B, 140177B, 177700B, 177B, 177703B, 140177B, 177760B, 777B, 177703B, 140077B, 177777B, 177777B, 177603B, 140037B, 177777B, 177777B, 177403B, 140017B, 177777B, 177777B, 177003B, 140007B, 177777B, 177777B, 176003B, 140003B, 177777B, 177777B, 174003B, 140001B, 177777B, 177777B, 170003B, 140000B, 77777B, 177777B, 140003B, 140000B, 37777B, 177777B, 100003B, 140000B, 7777B, 177776B, 3B, 140000B, 1777B, 177770B, 3B, 160000B, 377B, 177740B, 7B, 70000B, 17B, 177000B, 16B, 37777B, 177777B, 177777B, 177774B, 17777B, 177777B, 177777B, 177770B
    ];
    ghostPic: Picture ← [
17777B, 177777B, 177777B, 177770B, 37777B, 177777B, 177777B, 177774B, 70000B, 25B, 52400B, 16B, 160000B, 252B, 125240B, 7B, 140000B, 2525B, 52520B, 3B, 140000B, 5252B, 125252B, 3B, 140000B, 12525B, 52525B, 3B, 140000B, 25252B, 125252B, 100003B, 140001B, 52525B, 52525B, 50003B, 140002B, 125252B, 125252B, 124003B, 140005B, 52525B, 52525B, 52003B, 140012B, 125252B, 125252B, 125003B, 140025B, 52525B, 52525B, 52403B, 140052B, 125252B, 125252B, 125203B, 140125B, 52520B, 525B, 52403B, 140052B, 125200B, 52B, 125203B, 140125B, 52400B, 25B, 52503B, 140252B, 124000B, 2B, 125243B, 140525B, 50000B, 1B, 52523B, 141252B, 120000B, 0B, 125253B, 140525B, 40000B, 0B, 3B, 141252B, 100000B, 0B, 3B, 142525B, 40000B, 0B, 3B, 145252B, 100000B, 0B, 3B, 142525B, 0B, 0B, 3B, 145252B, 0B, 0B, 3B, 152525B, 0B, 0B, 3B, 145252B, 0B, 0B, 3B, 152524B, 0B, 0B, 3B, 145252B, 0B, 0B, 3B, 152524B, 0B, 0B, 3B, 145252B, 0B, 0B, 3B, 152524B, 0B, 0B, 3B, 145252B, 0B, 0B, 3B, 152524B, 0B, 0B, 3B, 145252B, 0B, 0B, 3B, 152524B, 0B, 0B, 3B, 145252B, 0B, 0B, 3B, 152525B, 0B, 0B, 3B, 145252B, 100000B, 0B, 3B, 142525B, 0B, 0B, 3B, 141252B, 100000B, 0B, 3B, 142525B, 40000B, 0B, 3B, 141252B, 120000B, 0B, 3B, 140525B, 50000B, 1B, 52523B, 140252B, 124000B, 2B, 125243B, 140525B, 52000B, 5B, 52523B, 140252B, 125000B, 12B, 125243B, 140125B, 52500B, 125B, 52503B, 140052B, 125240B, 252B, 125203B, 140025B, 52525B, 52525B, 52403B, 140012B, 125252B, 125252B, 125003B, 140005B, 52525B, 52525B, 52003B, 140002B, 125252B, 125252B, 124003B, 140001B, 52525B, 52525B, 50003B, 140000B, 125252B, 125252B, 120003B, 140000B, 52525B, 52525B, 40003B, 140000B, 25252B, 125252B, 100003B, 140000B, 2525B, 52524B, 3B, 140000B, 1252B, 125250B, 3B, 160000B, 125B, 52500B, 7B, 70000B, 12B, 125000B, 16B, 37777B, 177777B, 177777B, 177774B, 17777B, 177777B, 177777B, 177770B
    ];
    invertFlags: Display.BitBltFlags = [
      direction: forward,
      disjoint: TRUE,
      disjointItems: FALSE,
      gray: FALSE,
      srcFunc: complement,
      dstFunc: null,
      reserved: 0];
    replaceFlags: Display.BitBltFlags = [
      direction: forward,
      disjoint: TRUE,
      disjointItems: FALSE,
      gray: FALSE,
      srcFunc: null,
      dstFunc: null,
      reserved: 0];
    IF new = garbage THEN RETURN;
    box.dims ← [w: 64, h: 64];
    box.place ← [box.place.x + 4, box.place.y + 4];
    Display.Bitmap[
      window: window,
      box: box,
      address: [word: IF new = ghost THEN @ghostPic ELSE @normalPic, bit: 0],
      bitmapBitWidth: 64,
      flags: IF new = highlighted THEN invertFlags ELSE replaceFlags];
  };
  
  InitSmallPicture: PROCEDURE = {
    bits: ARRAY [0..16) OF WORD ← [
      0B, 0B, 3760B, 7770B, 17074B, 16034B, 34000B, 34000B,
      34000B, 34000B, 16034B, 17074B, 7770B, 3760B, 0B, 0B];
    ch ← SimpleTextFont.AddClientDefinedCharacter [
      width: 16, height: 16, bitsPerLine: 16, bits: @bits];
  };
    
  SmallPictureProc: Containee.SmallPictureProc = {RETURN[ch]};
  
  SetOutputMode: PUBLIC PROCEDURE [h: Handle, iomode: INTEGER] = {
    h.iomode ← iomode;
    };
    
    
  Init: PROC = {
    contextType ← Context.UniqueType[];
    MakeGenericProcAtoms[];
    FindOrCreateIconFile[];
    SetImplementation[];
  };
  
  -- mainline code
    
  Init[];
    
}.