<> <> <> <> 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.STREAM _ NIL; 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: BOOL _ FALSE; <<--whether log file should be flushed>> <<>> <<-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -->> <<--Terminals own locking procedures>> occupied: BOOL _ FALSE; --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: BOOL _ FALSE; 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_abort; 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_abort; }; RequestRope: PUBLIC PROC [text: ROPE _ NIL] 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: ROPE _ NIL] 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: ROPE _ NIL] RETURNS [i: INT] = { DO ok: BOOL _ TRUE; 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: ROPE _ NIL, 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: ROPE _ NIL, timeOut: NAT _ 0, onTimeOut: BOOL _ FALSE] 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: ROPE _ NIL, 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: ROPE _ NIL, timeOut: NAT] RETURNS [result: ROPE_NIL] = { 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: ROPE_NIL] = { 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: ROPE_NIL] = { 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_abort; 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: ROPE_NIL] = { 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: ROPE _ NIL, 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: ROPE _ NIL, 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.