TerminalIOImpl.mesa
Copyright © 1983, 1986, 1987, 1988, 1992 by Xerox Corporation. All rights reserved.
Created by Christian Jacobi August 3, 1983 9:54 am
Last edited by: Christian Jacobi, March 27, 1992 3:15 pm PST
Sampath Rangarajan June 16, 1989 4:03:08 pm PDT
Bob Krivacic February 18, 1991 2:03 pm PST
DIRECTORY
BasicTime USING [earliestGMT, GMT, Now, Period],
Booting USING [CheckpointProc, RegisterProcs, RollbackProc],
Commander USING [CommandProc, Register],
Convert USING [IntFromRope, Error],
Icons USING [IconFlavor, NewIconFromFile],
InputFocus,
IO,
Menus,
PFS,
PFSCanonicalNames,
PFSNames,
PopUpSelection USING [Request],
Process,
Rope,
RuntimeError USING [UNCAUGHT],
SystemNames,
TerminalIO,
TiogaMenuOps USING [Open],
TypeScript USING [Create, PutRope],
UserProfile USING [Number],
ViewerClasses USING [Viewer],
ViewerIO USING [CreateViewerStreams, GetViewerFromStream],
ViewerOps USING [PaintViewer, OpenIcon, EnumerateViewers, EnumProc],
ViewerTools USING [SetSelection];
TerminalIOImpl:
CEDAR
MONITOR
IMPORTS BasicTime, <<Booting,>> Commander, Convert, Icons, InputFocus, IO, Menus, PFS, PFSCanonicalNames, PFSNames, PopUpSelection, Process, Rope, RuntimeError, SystemNames, TiogaMenuOps, TypeScript, UserProfile, ViewerIO, ViewerOps, ViewerTools
EXPORTS TerminalIO =
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 Viewer";
inputName: ROPE = "Terminal Viewer [Input Expected]";
normalIcon, inputIcon: Icons.IconFlavor;
del: ROPE = "\177";
log:
IO.
STREAM;
--using a backing file for viewer makes log unaccessible
shouldFlush:
BOOL ←
FALSE;
--whether log file should be flushed
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
--locking procedures
occupied: BOOL ← FALSE; --module occupied
next: CONDITION; -- 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;
Lock[];
Enter[];
v ← 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];
ViewerTools.SetSelection[v];--order! SetSelection fools with the InputFocus! a feature
};
LeaveReader:
PROC [] = {
v: ViewerClasses.Viewer ← viewer;
foc: InputFocus.Focus ← InputFocus.GetInputFocus[];
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];
};
IF foc#NIL AND foc.info = $TerminalIO THEN InputFocus.PopInputFocus[];
Leave[];
Free[];
};
ProcList: TYPE = LIST OF PROC;
lockList: ProcList ← NIL;
freeList: ProcList ← NIL;
AddLock:
PUBLIC
ENTRY
PROC [lock, free:
PROC] = {
ENABLE UNWIND => NULL;
IF free#NIL THEN freeList ← CONS[free, freeList];
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
};
Free:
PROC = {
FOR l: ProcList ← freeList, l.rest
WHILE l#
NIL
DO
l.first[! RuntimeError.UNCAUGHT => CONTINUE]
ENDLOOP
};
ReaderProtected:
PROC [p:
PROC] = {
ENABLE UNWIND => LeaveReader[];
EnterReader[]; p[]; LeaveReader[];
};
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
--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 [orphaned viewers 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 terminal viewer";
IO.PutRope[log, "terminal viewer orphaned\n"]; IO.Flush[log];
ViewerOps.PaintViewer[v, caption];
TypeScript.PutRope[v, "\n... terminal 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]];
Menus.AppendMenuEntry[v.menu, Menus.CreateEntry[
name: "Previous",
fork: TRUE,
proc: PreviousLog
]];
[in: in, out: out] ← ViewerIO.CreateViewerStreams[name: normalName, viewer: v, editedStream: TRUE];
viewer ← v;
IO.PutRope[log, "terminal viewer created\n"];
IO.Flush[log];
lastTimeMsg ← BasicTime.earliestGMT; --so the time will be printed
};
--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
BEGIN
IO.Flush[oldOut ! RuntimeError.UNCAUGHT => GOTO oops];
IO.Reset[oldOut ! RuntimeError.UNCAUGHT => GOTO oops];
IO.Reset[oldIn ! RuntimeError.UNCAUGHT => GOTO oops];
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 terminal viewer\n"]; IO.Flush[log];
RETURN;
};
EXITS oops => {}
END;
};
};
--try to mark other [split] viewers as bad
ViewerOps.EnumerateViewers[FindOldGuys ! RuntimeError.UNCAUGHT => CONTINUE];
--the actual creation
ReallyCreate[]
};
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
--short cuts
InternalPut:
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;
};
PutRope:
PUBLIC PROC [text:
ROPE] = {
ENABLE UNWIND => Leave[];
Enter[]; InternalPut[text]; Leave[]
};
PutRopes:
PUBLIC PROC [t1, t2, t3:
ROPE] = {
ENABLE UNWIND => Leave[];
Enter[]; InternalPut[t1]; InternalPut[t2]; InternalPut[t3]; Leave[]
};
PutF:
PUBLIC
PROC [format:
ROPE ←
NIL, v1, v2, v3:
IO.Value←[null[]]] = {
--actual usage of IO.PutF inside lock allows clients to use looks
ENABLE UNWIND => Leave[];
Enter[];
IF viewer=NIL OR viewer.destroyed THEN InternalCreate[FALSE];
IO.PutF[out, format, v1, v2, v3
!
IO.Error => {
IF ec=StreamClosed
THEN {
InternalCreate[TRUE];
IO.PutF[out, format, v1, v2, v3];
CONTINUE
}
}
];
IO.PutF[log, format, v1, v2, v3]; shouldFlush ← TRUE;
Leave[]
};
PutFL:
PUBLIC
PROC [format:
ROPE ←
NIL, list:
LIST
OF
IO.Value] = {
--actual usage of IO.PutFL inside lock allows clients to use looks
ENABLE UNWIND => Leave[];
Enter[];
IF viewer=NIL OR viewer.destroyed THEN InternalCreate[FALSE];
IO.PutFL[out, format, list
!
IO.Error => {
IF ec=StreamClosed
THEN {
InternalCreate[TRUE];
IO.PutFL[out, format, list];
CONTINUE
}
}
];
IO.PutFL[log, format, list]; shouldFlush ← TRUE;
Leave[]
};
PutF1:
PUBLIC
PROC [format:
ROPE ←
NIL, value:
IO.Value] = {
--actual usage of IO.PutF1 inside lock allows clients to use looks
ENABLE UNWIND => Leave[];
Enter[];
IF viewer=NIL OR viewer.destroyed THEN InternalCreate[FALSE];
IO.PutF1[out, format, value
!
IO.Error => {
IF ec=StreamClosed
THEN {
InternalCreate[TRUE];
IO.PutF1[out, format, value];
CONTINUE
}
}
];
IO.PutF1[log, format, value]; shouldFlush ← TRUE;
Leave[]
};
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
--input procedures
RequestRope:
PUBLIC
PROC [prompt:
ROPE ←
NIL, timeOut:
NAT]
RETURNS [
ROPE] = {
RETURN [IF timeOut=0 THEN RequestRope0[prompt] ELSE RequestRope1[prompt, timeOut]]
};
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};
IO.Rubout => {stateort; IO.Reset[in]; CONTINUE};
RuntimeError.UNCAUGHT => {state←repeat; CONTINUE};
};
IF viewer.iconic THEN ViewerOps.OpenIcon[viewer];
InternalPut[promp];
IO.Flush[out ! RuntimeError.UNCAUGHT => CONTINUE];
ViewerTools.SetSelection[viewer];
line ← IO.GetLineRope[in];
IF Rope.Find[line, del]>=0 THEN stateort;
};
RequestRope0:
PROC [prompt:
ROPE ←
NIL]
RETURNS [r:
ROPE] = {
state: IOState ← ok;
DoIt: PROC [] = {[r, state] ← InternalGetLine[prompt]};
DO
ReaderProtected[DoIt];
SELECT state
FROM
ok => RETURN;
repeat => PutRope["XXX\n"];
abort => {PutRope[" ..user abort\n"]; SIGNAL UserAbort} ;
ENDCASE => {PutRope[" ..??\n"]; SIGNAL UserAbort};
ENDLOOP;
};
xProcess: PROCESS;
xStateCondition: CONDITION;
xState: IOState ← undef;
xResult: ROPE;
RequestRope1:
PUBLIC
PROC [prompt:
ROPE, timeOut:
NAT]
RETURNS [result:
ROPE] = {
state: IOState ← ok;
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;
IF state=ok THEN IF Rope.Find[result, del]>=0 THEN state ← abort
};
DO
ReaderProtected[DoIt];
SELECT state
FROM
ok => RETURN;
repeat => PutRope["XXX\n"];
abort => {PutRope[" ..user abort\n"]; SIGNAL UserAbort} ;
undef => SIGNAL TimedOut
ENDCASE => {PutRope[" ..??\n"]; SIGNAL UserAbort};
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};
IO.Rubout => {dort; IO.Reset[in]; CONTINUE};
};
InternalPut[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 InternalPut["XXX\n"]
ENDLOOP;
};
ReallyTryToGetRope[prompt ! ABORTED => GOTO NeverMind]
EXITS NeverMind => NULL;
};
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
--stream input stuff
-- This is unprotected code, so you should be sure that no one else will be doing terminalIO while inputing from the stream
TIS:
PUBLIC
PROC []
RETURNS [stream:
IO.
STREAM] = {
RETURN[IO.CreateStream[streamProcs: myinputStreamProcs, streamData: NIL]];
};
myinputStreamProcs:
REF
IO.StreamProcs ←
IO.CreateStreamProcs[
variety: $input,
class: $TerminalIO,
getChar: MyGetChar,
endOf: MyEndOf,
charsAvail: MyCharsAvail,
close: MyClose
];
MyGetChar:
PROC [self:
IO.
STREAM]
RETURNS [char:
CHAR] = {
ENABLE UNWIND => Leave[];
Enter[];
IF viewer=NIL OR viewer.destroyed THEN InternalCreate[FALSE];
IF viewer.iconic THEN ViewerOps.OpenIcon[viewer];
IO.Flush[out ! RuntimeError.UNCAUGHT => CONTINUE];
ViewerTools.SetSelection[viewer];
char ← IO.GetChar[in];
Leave[];
};
MyEndOf:
PROC [self:
IO.
STREAM]
RETURNS [endOf:
BOOL ←
TRUE] = {
endOf ← FALSE;
};
MyCharsAvail:
PROC [self:
IO.
STREAM, wait:
BOOL ←
FALSE]
RETURNS [
INT] = {
RETURN[INT.LAST];
};
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
--stream output stuff
TOS:
PUBLIC
PROC []
RETURNS [stream:
IO.
STREAM] = {
RETURN[IO.CreateStream[streamProcs: myStreamProcs, streamData: NIL]];
};
myStreamProcs:
REF
IO.StreamProcs ←
IO.CreateStreamProcs[
variety: $output,
class: $TerminalIO,
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[];
IF viewer=NIL OR viewer.destroyed THEN InternalCreate[FALSE];
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[];
IF viewer=NIL OR viewer.destroyed THEN InternalCreate[FALSE];
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[];
IF viewer=NIL OR viewer.destroyed THEN InternalCreate[FALSE];
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] = {
IO.EraseChar[out, char];
IO.EraseChar[log, char];
};
MyClose:
PROC [self:
IO.
STREAM, abort:
BOOL] = {
IO.Flush[log];
};
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
--input conveniances
RequestInt:
PUBLIC
PROC [prompt: Rope.
ROPE ←
NIL, timeOut:
NAT ← 0]
RETURNS [i:
INT] = {
DO
ok: BOOL ← TRUE;
r: Rope.ROPE ← RequestRope[prompt, timeOut];
i ← Convert.IntFromRope[r ! RuntimeError.UNCAUGHT => {ok←FALSE; CONTINUE}];
IF ok THEN RETURN;
PutRope[" ?? (integer), please repeat: \n"];
ENDLOOP;
};
Confirm:
PUBLIC
PROC [text: Rope.
ROPE, help: Rope.
ROPE, timeOut:
NAT, dontLog:
BOOL]
RETURNS [yes:
BOOL] = {
Ask:
PROC
RETURNS [
BOOL] = {
RETURN [
PopUpSelection.Request[
header: "Confirm", headerDoc: "Confirm by hitting entry; discard by hitting outside",
choice: LIST[text],
choiceDoc: LIST[help],
timeOut: timeOut
]=1
];
};
AskLogged:
PROC = {
InternalPut["confirm: "]; InternalPut[text]; InternalPut[": "];
yes ← Ask[];
InternalPut[(IF yes THEN "yes\n" ELSE "no\n")];
};
IF dontLog THEN yes ← Ask[] ELSE ReaderProtected[AskLogged]
};
RequestSelection:
PUBLIC
PROC [
header: Rope.
ROPE ←
NIL, choice:
LIST
OF Rope.
ROPE,
headerDoc: Rope.
ROPE ←
NIL, choiceDoc:
LIST
OF Rope.
ROPE ←
NIL,
default:
NAT ← 0, timeOut:
NAT ← 0, position:
REF ←
NIL,
dontLog:
BOOL ←
FALSE
]
RETURNS [n:
INT ← 0] = {
Request:
PROC = {
IF header#
NIL
AND ~dontLog
THEN {
InternalPut["pop up selection: "]; InternalPut[header]; InternalPut["\n "];
};
n ← PopUpSelection.Request[header: header, headerDoc: headerDoc, choice: choice, choiceDoc: choiceDoc, timeOut: timeOut, default: default, position: position];
IF ~dontLog
THEN {
IF n<0 THEN InternalPut["timed out\n"]
ELSE IF n=0 THEN InternalPut["skipped\n"]
ELSE {c:
LIST
OF Rope.
ROPE ← choice;
WHILE n>1
DO
c ← c.rest; IF c=NIL THEN RETURN;
ENDLOOP;
InternalPut["select: "]; InternalPut[c.first]; InternalPut["\n"];
};
};
};
IF (choice=NIL) OR (choice.first=NIL) THEN ERROR;
ReaderProtected[Request]
};
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
--log and commands
LogFileName:
PROC []
RETURNS [Rope.
ROPE] = {
RETURN [PFS.RopeFromPath[PFS.GetInfo[PFS.OpenFileFromStream[log]].fullFName]]
};
CreateLog:
PROC [] = {
log ← PFS.StreamOpen[fileName: PFS.PathFromRope["-ux:/tmp/TerminalIO.log"], accessOptions: append, createOptions: [keep: 5, mutability: mutable]];
IO.SetIndex[log, 0];
IO.SetLength[log, 0];
IO.PutF1[log, "log file for terminal created %g\n", IO.time[]]
};
PreviousLog: Menus.ClickProc = {
fullName: Rope.ROPE ← LogFileName[];
filePath: PFSNames.PATH ← PFS.PathFromRope[fullName];
name: Rope.ROPE ← PFS.RopeFromPath[PFSNames.StripVersionNumber[filePath]];
versionRope: Rope.ROPE ← PFSCanonicalNames.UnparseVersion[PFSCanonicalNames.ParseVersion[fullName].version];
version: INT ← Convert.IntFromRope[versionRope ! Convert.Error => GOTO oops];
IF version<2 THEN GOTO oops;
name ← IO.PutFR["%g!%g", [rope[name]], [integer[version-1]]];
[] ← TiogaMenuOps.Open[fileName: name];
PutRope["previous log file opened\n"];
EXITS oops => PutRope["opening previous log file failed\n"];
};
Command: Commander.CommandProc = {
ENABLE Convert.Error => GOTO convertError;
IF Rope.Match["*open*", cmd.commandLine,
FALSE]
THEN {
PutRope["open terminal viewer\n"]; --side effect: creates viewer
ViewerOps.OpenIcon[viewer];
};
IF Rope.Match["*new*", cmd.commandLine,
FALSE]
OR Rope.Match["*close*", cmd.commandLine,
FALSE]
THEN {
oldLog: IO.STREAM ← log;
oldName: Rope.ROPE ← LogFileName[];
PutF1["start new terminal log file [%g]\n", IO.time[]];
CreateLog[];
PutF1["previous log file ""%g"" closed\n", IO.rope[oldName]];
IO.Close[oldLog];
IO.PutF1[cmd.out, "previous log file ""%g"" closed\n", IO.rope[oldName]];
};
IF Rope.Match["*time*", cmd.commandLine,
FALSE]
THEN {
time: INT;
timePos:
INT ← Rope.SkipTo[
s: cmd.commandLine,
pos: Rope.Index[s1: cmd.commandLine, s2: "time", case: FALSE],
skip: " "
];
time ← Convert.IntFromRope[Rope.Substr[base: cmd.commandLine, start: timePos]];
timeMsgInterval ← IF time<0 OR time>600 THEN 0 ELSE time*60;
lastTimeMsg ← BasicTime.earliestGMT;
};
EXITS convertError => IO.PutRope[cmd.err, "syntax error in time\n"];
};
CheckInhibitDestroy:
ENTRY
PROC [] = {
v: ViewerClasses.Viewer ← viewer;
IF v#NIL AND ~occupied THEN v.inhibitDestroy ← FALSE;
};
lastTimeMsg: BasicTime.GMT ← BasicTime.earliestGMT;
timeMsgInterval: INT ← 60*UserProfile.Number["TerminalIO.TimeMsg", 30];
Flusher:
PROC [] = {
--directly flushing after each output would make TerminalIO far to slow
--process also used to write periodic time mesages
flush: BOOL;
DO
Process.PauseMsec[32000]; --a little bit more than 30 seconds [so doesn't skip time msg]
--flushing
IF flush ← shouldFlush
THEN {
shouldFlush ← FALSE;
IO.Flush[log ! RuntimeError.UNCAUGHT => CONTINUE];
};
CheckInhibitDestroy[];
--time message
IF viewer#
NIL
AND ~occupied
AND ~viewer.destroyed
AND timeMsgInterval>0
AND (BasicTime.Period[from: lastTimeMsg, to: BasicTime.Now[]]>timeMsgInterval)
THEN {
lastTimeMsg ← BasicTime.Now[];
PutF["%l%g%l\n", [rope["i"]], [time[lastTimeMsg]], [rope[" "]]];
};
ENDLOOP
};
Checkpoint: Booting.CheckpointProc = {
stream: IO.STREAM ← log;
log ← IO.noWhereStream;
IF stream#NIL AND stream#IO.noWhereStream THEN IO.Close[stream];
};
Rollback: Booting.RollbackProc = {
CreateLog[];
IO.PutRope[log, "Rollback!\n"]
};
CreateLog[];
normalIcon ← Icons.NewIconFromFile["TerminalIO.icons", 0 ! RuntimeError.UNCAUGHT => CONTINUE];
inputIcon ← Icons.NewIconFromFile["TerminalIO.icons", 1 ! RuntimeError.UNCAUGHT => CONTINUE];
Commander.Register[Rope.Cat["///", SystemNames.ReleaseName[], "/Commands/TerminalIO"], Command, "usage {TerminalIO ~ | open | new}"];
TRUSTED {Process.Detach[FORK Flusher]};
Booting.RegisterProcs[c: Checkpoint, r: Rollback];
END.