TerminalIOImpl.mesa
Copyright © 1983, 1986 by Xerox Corporation. All rights reserved.
Created by Christian Jacobi August 3, 1983 9:54 am
Last edited by: Christian Jacobi, September 2, 1986 1:47:51 pm PDT
DIRECTORY
Ascii USING [DEL],
Commander USING [CommandProc, Register],
Convert,
EditedStream USING [Rubout],
FS USING [StreamOpen],
Icons USING [IconFlavor, NewIconFromFile],
InputFocus USING [Focus, GetInputFocus, PopInputFocus, PushInputFocus],
IO,
PopUpSelection USING [Request],
Process USING [Detach, Pause, MsecToTicks, SetTimeout, SecondsToTicks, Abort, CheckForAbort, DisableTimeout],
Rope USING [ROPE, Fetch, FromChar, Find, Length, Match, Equal],
RuntimeError USING [UNCAUGHT],
TerminalIO,
TerminalIOExtras,
TypeScript USING [Create, PutRope],
ViewerClasses USING [Viewer],
ViewerIO USING [CreateViewerStreams, GetViewerFromStream],
ViewerOps USING [PaintViewer, OpenIcon, EnumerateViewers, EnumProc],
ViewerTools USING [SetSelection];
TerminalIOImpl: CEDAR MONITOR
IMPORTS Commander, Convert, EditedStream, FS, Icons, InputFocus, IO, PopUpSelection, Process, Rope, RuntimeError, TypeScript, ViewerIO, ViewerOps, ViewerTools
EXPORTS TerminalIO, TerminalIOExtras =
BEGIN
ROPE: TYPE = Rope.ROPE;
UserAbort: PUBLIC SIGNAL = CODE;
TimedOut: PUBLIC SIGNAL = CODE;
viewer: ViewerClasses.Viewer ← NIL;
in, out: IO.STREAMNIL;
normalName: ROPE = "Terminal";
inputName: ROPE = "Terminal [Input Expected]";
normalIcon: Icons.IconFlavor;
inputIcon: Icons.IconFlavor;
del: ROPE = Rope.FromChar[Ascii.DEL];
log: IO.STREAM;
--using a backing file for viewer makes log unaccessible
shouldFlush: BOOLFALSE;
--whether log file should be flushed
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
--Terminals own locking procedures
occupied: BOOLFALSE; --module occupied
next: CONDITION ← [timeout: Process.MsecToTicks[4000]]; --another client might try entering
Enter: ENTRY PROC [] = {
ENABLE UNWIND => NULL;
WHILE occupied DO WAIT next ENDLOOP;
occupied ← TRUE;
};
Leave: ENTRY PROC [] = {
ENABLE UNWIND => NULL;
occupied ← FALSE;
BROADCAST next;
};
EnterReader: PROC [] = {
v: ViewerClasses.Viewer ← viewer;
WHILE v=NIL OR v.destroyed DO InternalCreate[FALSE]; v ← viewer ENDLOOP;
v.inhibitDestroy ← TRUE;
WHILE v=NIL OR v.destroyed DO InternalCreate[TRUE]; v ← viewer ENDLOOP;
v.icon ← inputIcon;
v.name ← inputName;
ViewerOps.PaintViewer[v, caption];
InputFocus.PushInputFocus[v, $TerminalIO];
};
LeaveReader: PROC [] = {
v: ViewerClasses.Viewer ← viewer;
foc: InputFocus.Focus ← InputFocus.GetInputFocus[];
IF foc#NIL AND foc.info=$TerminalIO THEN InputFocus.PopInputFocus[];
IF v#NIL THEN {
v.icon ← normalIcon;
v.name ← normalName;
v.inhibitDestroy ← FALSE; --conveniant here; usefull in all places where called
ViewerOps.PaintViewer[v, caption];
};
};
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
--Lock, UnLock mechanism
ProcList: TYPE = LIST OF PROC;
lockList: ProcList ← NIL;
unLockList: ProcList ← NIL;
AddLock: PUBLIC ENTRY PROC [lock, unLock: PROC] = {
ENABLE UNWIND => NULL;
IF unLock#NIL THEN unLockList ← CONS[unLock, unLockList];
IF lock#NIL THEN
IF lockList=NIL THEN lockList ← LIST[lock]
ELSE
FOR l: ProcList ← lockList, l.rest DO
IF l.rest=NIL THEN {l.rest ← LIST[lock]; EXIT}
ENDLOOP;
};
Lock: PROC = {
FOR l: ProcList ← lockList, l.rest WHILE l#NIL DO
l.first[! RuntimeError.UNCAUGHT => CONTINUE]
ENDLOOP
};
UnLock: PROC = {
FOR l: ProcList ← unLockList, l.rest WHILE l#NIL DO
l.first[! RuntimeError.UNCAUGHT => CONTINUE]
ENDLOOP
};
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
--creation of viewer
Create: PROC [protected: BOOL] = {
--its ok to risk creating accidently too many streams or viewer's
--since this won't cause any wedge but only discomfort [orphans lying around]
InternalCreate[protected]
};
InternalCreate: PROC [protected: BOOL] = {
--afterwards in any case: viewer #NIL (doesnt really mean good, but certainly not NIL)
FindOldGuys: ViewerOps.EnumProc = {
MakeOrphan: PROC [v: ViewerClasses.Viewer] = {
IF v#NIL THEN {
v.inhibitDestroy ← FALSE;
v.icon ← typescript;
v.name ← "orphaned split log viewer";
IO.PutRope[log, "log viewer orphaned\n"]; IO.Flush[log];
ViewerOps.PaintViewer[v, caption];
TypeScript.PutRope[v, "\n... log viewer orphaned ...\n"];
};
};
IF ~v.destroyed AND v.file=NIL AND v.class.flavor=$Typescript THEN
IF Rope.Equal[v.name, normalName] OR Rope.Equal[v.name, inputName] THEN
MakeOrphan[v ! RuntimeError.UNCAUGHT => CONTINUE];
};
ReallyCreate: PROC [] = {
v: ViewerClasses.Viewer ← TypeScript.Create[info: [name: normalName, inhibitDestroy: protected, iconic: FALSE, icon: normalIcon, column: right]];
[in: in, out: out] ← ViewerIO.CreateViewerStreams[name: normalName, viewer: v, editedStream: TRUE];
viewer ← v;
IO.PutRope[log, "log viewer created\n"];
IO.Flush[log];
};
--try re-using old pieces, but only if not from input
oldOut: IO.STREAM ← out;
oldIn: IO.STREAM ← in;
IF oldOut#NIL AND oldIn#NIL AND ~protected THEN {
v: ViewerClasses.Viewer;
err: BOOLFALSE;
v ← ViewerIO.GetViewerFromStream[oldOut];
IF v#NIL AND ~v.destroyed AND (v=ViewerIO.GetViewerFromStream[oldIn]) THEN {
--we dare trying to reuse a split viewer,
--but first we try whether it wont cause errors
IO.Flush[oldOut ! RuntimeError.UNCAUGHT => {err ← TRUE; CONTINUE}];
IF ~err THEN IO.Reset[oldOut ! RuntimeError.UNCAUGHT => {err ← TRUE; CONTINUE}];
IF ~err THEN IO.Reset[oldIn ! RuntimeError.UNCAUGHT => {err ← TRUE; CONTINUE}];
IF ~err THEN {
v.icon ← normalIcon;
IF ~v.destroyed THEN {
[in: in, out: out] ← ViewerIO.CreateViewerStreams[name: normalName, viewer: v, editedStream: TRUE];
viewer ← v;
IO.PutRope[log, "usage of different LogViewer viewer\n"]; IO.Flush[log];
RETURN;
};
};
};
};
--try to mark other [split] viewers as bad
ViewerOps.EnumerateViewers[FindOldGuys ! RuntimeError.UNCAUGHT => CONTINUE];
--the actual creation
ReallyCreate[]
};
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
--input procedures
IOState: TYPE = {ok, repeat, abort, undef};
InternalGetLine: PROC [promp: ROPE] RETURNS [line: ROPE, state: IOState←ok] = {
ENABLE {
IO.Error => IF ec=StreamClosed THEN {state←repeat; CONTINUE};
EditedStream.Rubout => {state�ort; IO.Reset[in]; CONTINUE};
RuntimeError.UNCAUGHT => {state←repeat; CONTINUE};
};
IF viewer.iconic THEN ViewerOps.OpenIcon[viewer];
InternalWrite[promp];
IO.Flush[out ! RuntimeError.UNCAUGHT => CONTINUE];
ViewerTools.SetSelection[viewer];
line ← IO.GetLineRope[in];
IF Rope.Find[line, del]>=0 THEN state�ort;
};
RequestRope: PUBLIC PROC [text: ROPENIL] RETURNS [r: ROPE] = {
DoIt: PROC [] = {[r, state] ← InternalGetLine[text]};
state: IOState;
DO
ReaderProtected[DoIt];
SELECT state FROM
ok => RETURN;
repeat => WriteRope["XXX\n"];
abort => {WriteRope[" ..user abort\n"]; SIGNAL UserAbort} ;
ENDCASE => {WriteRope[" ..??\n"]; SIGNAL UserAbort};
ENDLOOP;
};
RequestChar: PUBLIC PROC [text: ROPENIL] RETURNS [ch: CHAR] = {
DO
r: ROPE ← RequestRope[text];
i: INT ← Rope.Length[r];
IF i=0 THEN RETURN[15C];
IF i=1 THEN RETURN[Rope.Fetch[r, 0]];
WriteRope[" ?? (single char), please repeat: \n"];
ENDLOOP;
};
RequestInt: PUBLIC PROC [text: ROPENIL] RETURNS [i: INT] = {
DO
ok: BOOLTRUE;
r: ROPE ← RequestRope[text];
i ← Convert.IntFromRope[r ! RuntimeError.UNCAUGHT => {ok←FALSE; CONTINUE}];
IF ok THEN RETURN;
WriteRope[" ?? (integer), please repeat: \n"];
ENDLOOP;
};
RequestSelection: PUBLIC PROC [label: ROPE, choice: LIST OF ROPE, text: ROPENIL, default: NAT ← 0, timeOut: NAT] RETURNS [select: INT ← 0] = {
DoIt: PROC [] = {
IF text#NIL THEN InternalWrite[text];
IO.Flush[out ! RuntimeError.UNCAUGHT => CONTINUE];
Process.Pause[Process.MsecToTicks[5]]; --SILLY FLUSH DIDN'T WORK
select ← PopUpSelection.Request[label, choice, NIL, NIL, default, timeOut];
};
IF (choice=NIL) OR (choice.first=NIL) THEN ERROR;
ReaderProtected[DoIt];
};
Confirm: PUBLIC PROC [choice: ROPE, label: ROPENIL, timeOut: NAT ← 0, onTimeOut: BOOLFALSE] RETURNS [BOOL] = {
RETURN [
SELECT RequestSelection[label: label, choice: LIST[choice], timeOut: timeOut] FROM
1  => TRUE,
-1  => onTimeOut,
ENDCASE  => FALSE
]
};
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
--timed out input
xProcess: PROCESS;
xStateCondition: CONDITION;
xState: IOState ← undef;
xResult: ROPE;
RequestRopeWithTimeout: PUBLIC PROC [text: ROPENIL, timeOut: NAT ← 0] RETURNS [ROPE] = {
IF timeOut=0 THEN RETURN [RequestRope[text]];
RETURN [MyRequestRopeWithTimeout[text, timeOut]];
};
ReaderProtected: PROC [p: PROC] = {
ENABLE UNWIND => UnLock[];
Lock[]; p[]; UnLock[];
};
MyRequestRopeWithTimeout: PROC [prompt: ROPENIL, timeOut: NAT] RETURNS [result: ROPENIL] = {
state: IOState;
DoIt: PROC [] = {
xState ← undef;
IF timeOut=0 THEN TRUSTED {Process.DisableTimeout[@xStateCondition]}
ELSE TRUSTED {
Process.SetTimeout[@xStateCondition, Process.SecondsToTicks[timeOut]]
};
ForkRopeGetter[prompt];
TRUSTED {JOIN xProcess};
state ← xState;
result ← xResult;
};
DO
ReaderProtected[DoIt];
IF state=abort THEN SIGNAL UserAbort
ELSE IF Rope.Find[result, del]>=0 THEN SIGNAL UserAbort
ELSE IF state=undef THEN SIGNAL TimedOut
ELSE EXIT
ENDLOOP
};
ForkRopeGetter: ENTRY PROC [prompt: ROPENIL] = {
ENABLE UNWIND => NULL;
xProcess ← FORK ForkedTryToGetRope[prompt];
WAIT xStateCondition;
IF xState#ok AND xState#abort THEN TRUSTED {Process.Abort[xProcess]};
};
SetGlobalState: ENTRY PROC [s: IOState] = {
ENABLE UNWIND => NULL;
Process.CheckForAbort[];
xState ← s;
BROADCAST xStateCondition;
};
ForkedTryToGetRope: PROC [prompt: ROPENIL] = {
ReallyTryToGetRope: PROC [prompt: ROPE] = {
d: IOState;
DoIt: PROC [] = {
ENABLE {
UNWIND => IO.Reset[in];
IO.Error => IF ec=StreamClosed THEN {d←repeat; IO.Reset[in]; CONTINUE};
EditedStream.Rubout => {d�ort; IO.Reset[in]; CONTINUE};
};
InternalWrite[prompt];
IF viewer.iconic THEN ViewerOps.OpenIcon[viewer];
xResult ← IO.GetLineRope[in];
d ← ok;
};
DO
d ← undef;
DoIt[];
IF d=ok OR d=abort THEN {SetGlobalState[d]; RETURN};
IF d=repeat THEN InternalWrite["XXX\n"]
ENDLOOP;
};
ReallyTryToGetRope[prompt ! ABORTED => GOTO NeverMind]
EXITS NeverMind => NULL;
};
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
--output procedures
InternalWrite: PROC [text: ROPE] = {
IF viewer=NIL OR viewer.destroyed THEN InternalCreate[FALSE];
--asking for viewer.destroyed helps to discover bad splits, but is not mandatory
IO.PutRope[out, text ! IO.Error => {
IF ec=StreamClosed THEN {
InternalCreate[TRUE];
IO.PutRope[out, text];
CONTINUE
}
}];
IO.PutRope[log, text]; shouldFlush ← TRUE;
};
WriteRope: PUBLIC PROC [text: ROPE] = {
ENABLE UNWIND => Leave[];
Enter[];
InternalWrite[text];
Leave[]
};
WriteRopes: PUBLIC PROC [t1, t2, t3: ROPENIL] = {
ENABLE UNWIND => Leave[];
IF t1#NIL THEN InternalWrite[t1];
IF t2#NIL THEN InternalWrite[t2];
IF t3#NIL THEN InternalWrite[t3];
Leave[]
};
WriteLn: PUBLIC PROC [] = {
WriteRope["\n"];
};
WriteChar: PUBLIC PROC [ch: CHAR] = {
WriteRope[Rope.FromChar[ch]];
};
WriteInt: PUBLIC PROC [i: INT] = {
WriteRope[IO.PutFR1[format: " %g", value: IO.int[i]]];
};
Write: PUBLIC PROC [v1, v2, v3: IO.Value] = {
WriteRope[IO.PutR[v1, v2, v3]];
};
Write1: PUBLIC PROC [value: IO.Value] = {
WriteRope[IO.PutR1[value]];
};
WriteF: PUBLIC PROC [format: ROPENIL, v1, v2, v3, v4, v5: IO.Value] = {
--actual inline usage of WriteF to allow client usage of formatting
ENABLE UNWIND => Leave[];
Enter[];
IF viewer=NIL OR viewer.destroyed THEN InternalCreate[FALSE];
IO.PutF[out, format, v1, v2, v3, v4, v5
! IO.Error => {
IF ec=StreamClosed THEN {
InternalCreate[TRUE];
IO.PutF[out, format, v1, v2, v3, v4, v5];
CONTINUE
}
}
];
IO.PutF[log, format, v1, v2, v3, v4, v5]; shouldFlush ← TRUE;
Leave[]
};
WriteF1: PUBLIC PROC [format: ROPENIL, value: IO.Value] = {
WriteRope[IO.PutFR[format, value]];
};
TOS: PUBLIC PROC [] RETURNS [stream: IO.STREAM] = {
--an output stream which writes its output into the terminal
IF viewer=NIL OR viewer.destroyed THEN Create[FALSE];
RETURN[IO.CreateStream[streamProcs: myStreamProcs, streamData: NIL]];
};
myStreamProcs: REF IO.StreamProcs ← IO.CreateStreamProcs[
variety: $output,
class: $LogViewer,
putChar: MyPutChar,
putBlock: MyPutBlock,
unsafePutBlock: MyUnsafePutBlock,
flush: MyFlush,
eraseChar: MyEraseChar,
reset: MyFlush,
close: MyClose
];
MyPutChar: PROC [self: IO.STREAM, char: CHAR] = {
ENABLE UNWIND => Leave[];
Enter[];
IO.PutChar[out, char ! IO.Error => {
IF ec=StreamClosed THEN {
InternalCreate[TRUE];
IO.PutChar[out, char];
CONTINUE
}
}];
IO.PutChar[log, char]; shouldFlush ← TRUE;
Leave[];
};
MyPutBlock: PROC [self: IO.STREAM, block: REF READONLY TEXT, startIndex: NAT, count: NAT] = {
ENABLE UNWIND => Leave[];
Enter[];
IO.PutBlock[out, block, startIndex, count ! IO.Error => {
IF ec=StreamClosed THEN {
InternalCreate[TRUE];
IO.PutBlock[out, block, startIndex, count];
CONTINUE
}
}];
IO.PutBlock[log, block, startIndex, count]; shouldFlush ← TRUE;
Leave[];
};
MyUnsafePutBlock: PROC [self: IO.STREAM, block: IO.UnsafeBlock] = {
ENABLE UNWIND => Leave[];
Enter[];
IO.UnsafePutBlock[out, block ! IO.Error => {
IF ec=StreamClosed THEN {
InternalCreate[TRUE];
IO.UnsafePutBlock[out, block];
CONTINUE
}
}];
IO.UnsafePutBlock[log, block]; shouldFlush ← TRUE;
Leave[];
};
MyFlush: PROC [self: IO.STREAM] = {
IO.Flush[log];
};
MyEraseChar: PROC [self: IO.STREAM, char: CHAR] = {
};
MyClose: PROC [self: IO.STREAM, abort: BOOL] = {
IO.Flush[log];
};
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
CreateLog: PROC [] = {
log ← FS.StreamOpen[fileName: "TerminalIO.log", accessOptions: create, keep: 5];
IO.PutF1[log, "log file for TerminalIO created %g\n", IO.time[]]
};
Command: Commander.CommandProc = {
IF Rope.Match["*op*", cmd.commandLine, FALSE] THEN {
WriteRope["open TerminalIO viewer\n"];
ViewerOps.OpenIcon[viewer];
};
IF Rope.Match["*log*", cmd.commandLine, FALSE] THEN {
oldLog: IO.STREAM ← log;
WriteRope["terminate and start new TerminalIO.log file\n"];
CreateLog[];
IO.Close[oldLog];
};
};
CheckInhibitDestroy: ENTRY PROC [] = {
ENABLE UNWIND => NULL;
v: ViewerClasses.Viewer ← viewer;
IF v#NIL AND ~occupied THEN v.inhibitDestroy ← FALSE;
};
Flusher: PROC [] = {
--directly flushing after each output would make LogViewer far to slow
flush: BOOL;
DO
Process.Pause[Process.SecondsToTicks[60]];
IF flush ← shouldFlush THEN {
shouldFlush ← FALSE;
IO.Flush[log ! RuntimeError.UNCAUGHT => CONTINUE];
};
CheckInhibitDestroy[];
ENDLOOP
};
CreateLog[];
normalIcon ← Icons.NewIconFromFile["TerminalIO.icons", 0 ! RuntimeError.UNCAUGHT => CONTINUE];
inputIcon ← Icons.NewIconFromFile["TerminalIO.icons", 1 ! RuntimeError.UNCAUGHT => CONTINUE];
AddLock[Enter, Leave];
AddLock[EnterReader, LeaveReader];
Commander.Register["///Commands/TerminalIO", Command, "usage {~ | open | newlog}"];
TRUSTED {Process.Detach[FORK Flusher]};
END.