Chat.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Larry Stewart, January 6, 1984 1:28 pm
Warren Teitelman, November 3, 1982 5:53 pm
Last Edited by: Maxwell, January 25, 1983 3:09 pm
Last Edited by: Paul Rovner, November 29, 1983 10:56 am
Last Edited by: Pavel Curtis on September 12, 1985 12:44:59 pm PDT
Last Edited by: Russ Atkinson (RRA) April 26, 1985 7:10:14 pm PST
Last Edited by: Doug Wyatt, April 8, 1985 6:48:35 pm PST
Last Edited by: Hal Murray June 15, 1985 6:15:32 pm PDT
Last Edited by: John Larson, August 7, 1985 3:56:06 pm PDT
DIRECTORY
Ascii,
Basics USING [BITAND],
Commander USING [CommandProc, Register],
CommandTool USING [ArgumentVector, Failed, Parse],
DefaultRemoteNames USING [Get],
EditedStream USING [SetEcho],
FS USING [Error, StreamOpen],
IO,
IOClasses USING [CreateDribbleOutputStream],
List USING [Length, Remove],
Loader USING [BCDBuildTime],
Menus USING [AppendMenuEntry, CreateEntry, FindEntry, MenuEntry, MenuProc, ReplaceMenuEntry],
Process USING [Detach],
PupDefs USING [PupAddress, PupPackageMake, PupPackageDestroy],
PupStream USING [ConsumeMark, GetPupAddress, PupByteStreamCreate, PupNameTrouble, SecondsToTocks, SendAttention, SendMark, StreamClosing, TimeOut],
PupTypes USING [telnetSoc],
Rope USING [Cat, Concat, Fetch, Find, Length, ROPE, Substr],
TiogaOps USING [GetCaret, GetRope, Location],
TIPUser USING [InstantiateNewTIPTable, RegisterTIPPredicate, TIPPredicate, TIPTable],
TypeScript USING [BackSpace, ChangeLooks, Create, InsertCharAtFrontOfBuffer, TS],
UserCredentials USING [Get],
UserProfile USING [Boolean, Token],
ViewerClasses,
ViewerEvents USING [EventProc, EventRegistration, RegisterEventProc, UnRegisterEventProc],
ViewerIO USING [CreateViewerStreams],
ViewerOps USING [AddProp, BlinkViewer, DestroyViewer, FetchProp, PaintViewer],
ViewerTools USING [GetSelectedViewer, GetSelectionContents, SetSelection];
Chat: CEDAR MONITOR LOCKS h.LOCK USING h: Handle
IMPORTS Basics, Commander, CommandTool, DefaultRemoteNames, EditedStream, FS, IO, IOClasses, List, Loader, Menus, Process, PupDefs, PupStream, Rope, TiogaOps, TIPUser, TypeScript, UserCredentials, UserProfile, ViewerEvents, ViewerIO, ViewerOps, ViewerTools
SHARES Menus, ViewerClasses
= {
Handle: TYPE = REF ChatInstanceRecord;
State: TYPE = {idle, starting, running, closing, destroy};
DisconnectChar: CHAR = 220C;
AbortChar: CHAR = 221C;
ConnectChar: CHAR = 222C;
LoginChar: CHAR = 223C;
RemoteCloseChar: CHAR = 224C;
MarkByte: TYPE = [0..256);
syncMark: MarkByte = 1;
setLineWidth: MarkByte = 2;
setPageLength: MarkByte = 3;
timingMark: MarkByte = 5;
timingMarkReply: MarkByte = 6;
ChatInstanceRecord: TYPE = MONITORED RECORD [
ts: TypeScript.TS, -- the primary typescript
state: State ← idle,
lorc: CHAR ← 'c,
logFileName: Rope.ROPE,
logStream: IO.STREAM,
debugging: BOOLFALSE,
rawLogStream: IO.STREAM,
keyFile: Rope.ROPE,
argv: CommandTool.ArgumentVector,
pleaseStop: BOOLFALSE,
uToSStopped: BOOLFALSE,
sToUStopped: BOOLFALSE,
inDestroy: BOOLFALSE,
serverToUserProcess: PROCESS,
userToServerProcess: PROCESS,
serverName: Rope.ROPE ← "Ivy",
useOldHost: BOOLFALSE,
keyStream: IO.STREAM,
destroyOnClose: BOOLFALSE,
synchronous: BOOLFALSE,
in: IO.STREAM,
origOut: IO.STREAM,
out: IO.STREAM,
tipTable: TIPUser.TIPTable,
serverStream: IO.STREAMNIL,
oldSplit: Menus.MenuEntry ← NIL
];
chatInstanceList: LIST OF REF ANYNIL;
destroyEvent: ViewerEvents.EventRegistration ← NIL;
closeEvent: ViewerEvents.EventRegistration ← NIL;
chatTipTable: TIPUser.TIPTable;
ServerToUser: PROC [h: Handle] = {
This procedure is FORKed by the UserToServer process at the time a connection is opened. It is JOINED whenever the connection is closed. It also goes away if the Chat viewer is destroyed.
c: CHAR;
mySST: MarkByte;
DO
ENABLE
{
ABORTED =>
GOTO Cleanup;
PupStream.StreamClosing =>
{
IF NOT h.pleaseStop THEN
TypeScript.InsertCharAtFrontOfBuffer[ts: h.ts, char: RemoteCloseChar];
GOTO Cleanup;
};
IO.Error =>
GOTO Cleanup;
};
IF h.serverStream.EndOf[] THEN
{
mySST ← PupStream.ConsumeMark[h.serverStream];
IF mySST = timingMark THEN PupStream.SendMark[h.serverStream, timingMarkReply];
};
c ← h.serverStream.GetChar[!
PupStream.TimeOut =>
{
IF h.pleaseStop OR h.state # running THEN
GOTO Cleanup
ELSE
RESUME;
};
IO.EndOfStream =>
{ LOOP; }
];
IF h.pleaseStop OR h.state # running THEN
GOTO Cleanup;
IF h.debugging THEN {
h.rawLogStream.PutChar[c];
h.rawLogStream.Flush[];
};
c ← LOOPHOLE[Basics.BITAND[LOOPHOLE[c, CARDINAL], 177B], CHAR];
SELECT c FROM
Ascii.BEL => Flash[h];
Ascii.ControlA, Ascii.BS => TypeScript.BackSpace[h.ts];
Ascii.TAB, IN[Ascii.SP..0176C] => h.out.PutChar[c];
Ascii.LF => h.out.PutChar[Ascii.CR];
ENDCASE => NULL;
ENDLOOP;
EXITS
Cleanup => h.sToUStopped ← TRUE;
};
Flash: PROC [h: Handle] = {
viewer: ViewerClasses.Viewer ~ h.ts;
ViewerOps.BlinkViewer[viewer, 100];
};
InitialNegotiations: PROC [h: Handle] = TRUSTED {
out: IO.STREAM ← h.serverStream;
PupStream.SendMark[h.serverStream, setLineWidth];
out.PutChar[0C];
out.Flush[];
IF h.lorc='l OR h.lorc='x THEN {
name, password: Rope.ROPENIL;
name ← UserProfile.Token[key: "Chat.Name", default: NIL];
IF name = NIL THEN
name ← UserProfile.Token[key: Rope.Cat["Chat.", h.serverName, ".Name"], default: NIL];
IF name = NIL THEN {
name ← UserCredentials.Get[].name;
IF Rope.Find[s1: name, s2: "."] = -1 THEN
name ← Rope.Cat[name, ".", DefaultRemoteNames.Get[].registry];
};
out.PutRope["Login "];
out.PutRope[name];
IF UserProfile.Boolean[Rope.Cat["Chat.", h.serverName, ".usePassword"], TRUE] THEN {
Use the current user's logged-in password
out.PutRope[" "];
out.PutRope[UserCredentials.Get[].password];
};
out.PutRope[" \n"];
out.Flush[];
};
};
FinishStringWithErrorMsg: PROC [h: Handle, errorMsg: Rope.ROPE] = {
IF errorMsg # NIL
THEN h.out.PutF[": %s.\n", [rope[errorMsg]]]
ELSE h.out.PutF[".\n"];
};
UserAbort is active because Chat.TIP is not activated until h.state = running (due to the TIP predicate), so control-DEL sets UserAbort. However, while in this routine, UserAbort is used only to stop taking characters from the keyStream, not to close the connection. If the user types control-DEL here, she will have to type it again to really close the connection.
FromKeys: PROC [h: Handle] = TRUSTED {
count: NAT ← 0;
{
WHILE h.keyStream # NIL AND ~h.keyStream.EndOf[] DO
IF h.pleaseStop OR h.state # starting THEN GOTO CloseKeyStream;
h.serverStream.PutChar[h.keyStream.GetChar[]];
count ← count + 1;
IF count >= 50 THEN {
h.serverStream.Flush[];
count ← 0;
};
ENDLOOP;
GOTO CloseKeyStream;
EXITS
CloseKeyStream => {
IF h.keyStream # NIL THEN{
h.keyStream.Close[];
h.keyStream ← NIL;
};
IF count # 0 THEN h.serverStream.Flush[];
};
};
};
StartUp: PROC [h: Handle] = {
ENABLE UNWIND => h.state ← idle;
h.state ← starting;
IF NOT h.useOldHost OR h.serverName.Length[] = 0 THEN h.serverName ← FindHostName[h];
h.useOldHost ← FALSE;
h.pleaseStop ← FALSE;
h.sToUStopped ← FALSE;
h.uToSStopped ← FALSE;
ViewerTools.SetSelection[h.ts, NIL];
h.out.PutF["\nViewers Chat of %t.\n", [time[Loader.BCDBuildTime[FindHostName]]]];
IF h.logStream # NIL THEN h.out.PutF["Log file: %g\n", IO.rope[h.logFileName]]
ELSE h.out.PutF["No log file.\n"];
OpenConnection[h];
IF h.serverStream = NIL THEN {
h.state ← idle;
RETURN;
};
SetName[h, Rope.Concat["Chat ", h.serverName]];
InitialNegotiations[h];
FromKeys[h];
h.state ← running;
h.serverToUserProcess ← FORK ServerToUser[h];
};
FindHostName: PROC [h: Handle] RETURNS [host: Rope.ROPE] = {
caret: TiogaOps.Location;
r: Rope.ROPE;
i: INT;
r ← ViewerTools.GetSelectionContents[];
IF r.Length[] > 1 THEN RETURN[r];
caret ← TiogaOps.GetCaret[];
r ← TiogaOps.GetRope[caret.node];
i ← caret.where;
WHILE i > 0 DO
char: CHARACTER = Rope.Fetch[r, i - 1];
IF -- char # '* AND -- NOT ChatTokenProc[char] = other THEN EXIT;
i ← i -1;
ENDLOOP;
host ← Rope.Substr[base: r, start: i, len: caret.where - i];
};
ChatTokenProc: IO.BreakProc = TRUSTED {
[char: CHAR] RETURNS [IO.CharClass]
This procedure includes '+ in order to handle Pup names of the form "dls+100004"
IF IO.TokenProc[char] = other THEN RETURN [other];
IF char = '+ THEN RETURN [other];
RETURN [sepr];
};
OpenConnection: PROC [h: Handle] = TRUSTED {
addr: PupDefs.PupAddress;
PupDefs.PupPackageMake[];
{
h.out.PutF["Opening connection to %g ... ", [rope[h.serverName]]];
addr ← PupStream.GetPupAddress[PupTypes.telnetSoc, h.serverName
! PupStream.PupNameTrouble => {
h.out.PutF["PUP name error"];
FinishStringWithErrorMsg[h, e];
GOTO Return;
}];
h.serverStream ← PupStream.PupByteStreamCreate
[addr, PupStream.SecondsToTocks[1]
! PupStream.StreamClosing => {
h.out.PutF["Can't connect"];
FinishStringWithErrorMsg[h, text];
GOTO Return;
}];
h.out.PutF["open.\n"];
EXITS Return => NULL;
};
};
CloseConnection: PROC [h: Handle, print: BOOL] = TRUSTED {
h.pleaseStop ← TRUE;
IF h.serverStream # NIL THEN {
IF print THEN h.out.PutF["\nClosing connection to %s", IO.rope[h.serverName] ! IO.Error => CONTINUE];
h.serverStream.Close[];
h.serverStream ← NIL; -- could cause pointer faults!
IF print THEN h.out.PutF[" ... Closed\n" ! IO.Error => CONTINUE];
PupDefs.PupPackageDestroy[];
};
IF h.keyStream # NIL THEN {
h.keyStream.Close[];
h.keyStream ← NIL;
};
IF h.state = running THEN JOIN h.serverToUserProcess;
IF h.logStream # NIL THEN h.logStream.Flush[];
h.state ← idle;
SetName[h, "Chat"];
};
SetName: PROC [h: Handle, r: Rope.ROPE] = {
InternalSetName: PROC [v: ViewerClasses.Viewer] = {
v.name ← r;
ViewerOps.PaintViewer[viewer: v, hint: caption];
};
EnumerateSplits[h.ts, InternalSetName ! ANY => CONTINUE];
};
ChatMain: Commander.CommandProc = TRUSTED {
h: Handle ← NEW[ChatInstanceRecord ← []];
execOut: IO.STREAM ← cmd.out;
switchChar: CHAR;
i: NAT ← 2;
h.argv ← CommandTool.Parse[cmd ! CommandTool.Failed => {msg ← errorMsg; CONTINUE; }];
IF h.argv = NIL THEN RETURN;
h.lorc ← 'l; -- default GV login
WHILE i < h.argv.argc DO
IF h.argv[i].Length[] > 1 THEN
SELECT h.argv[i].Fetch[0] FROM
'- => {
switchChar ← h.argv[i].Fetch[1];
SELECT switchChar FROM
'd => h.destroyOnClose ← TRUE;
'k => {
IF i+1 < h.argv.argc THEN {
h.keyStream ← IO.RIS[h.argv[i+1]];
i ← i + 1;
};
};
's => h.synchronous ← TRUE;
'D => {
h.debugging ← TRUE;
h.rawLogStream ←
FS.StreamOpen[fileName: "Chat.rawLog", accessOptions: $create
! FS.Error =>
IF error.group = user THEN {
execOut.PutF["Chat: Cannot open raw log Chat.rawLog\n"];
CONTINUE;
}
];
};
ENDCASE => h.lorc ← switchChar;
};
'> => h.logFileName ← Rope.Substr[base: h.argv[i], start: 1];
'< => h.keyFile ← Rope.Substr[base: h.argv[i], start: 1];
ENDCASE => execOut.PutF["chat: unknown command: %s.\n", IO.rope[h.argv[i]]];
i ← i + 1;
ENDLOOP;
IF h.logFileName.Length[] = 0 THEN h.logFileName ← "Chat.log";
IF h.keyFile.Length[] > 0 THEN h.keyStream ← FS.StreamOpen[fileName: h.keyFile, keep: 2
! FS.Error => IF error.group = user THEN {
execOut.PutF["Chat: Cannot open %s\n", IO.rope[h.keyFile]];
CONTINUE;
}];
iconic unless command line not empty
h.ts ← TypeScript.Create[info: [name: "Chat", iconic: h.argv.argc <= 1 OR h.lorc = 'i], paint: TRUE];
TypeScript.ChangeLooks[h.ts, 'f];
h.ts.file ← h.logFileName;
h.ts.icon ← typescript;
create log file
h.logStream ← FS.StreamOpen[fileName: h.logFileName, accessOptions: $create
! FS.Error => IF error.group = user THEN {
execOut.PutF["Chat: Cannot open %s\n", IO.rope[h.logFileName]];
CONTINUE;
}];
plug in Chat TIP table.
chatTipTable.link ← h.ts.tipTable;
chatTipTable.opaque ← FALSE;
h.tipTable ← h.ts.tipTable ← chatTipTable;
[in: h.in, out: h.origOut] ← ViewerIO.CreateViewerStreams[name: "Chat.log", viewer: h.ts, editedStream: FALSE];
h.out ← h.origOut;
IF h.logStream # NIL THEN
h.out ← IOClasses.CreateDribbleOutputStream[output1: h.origOut, output2: h.logStream];
IF h.debugging THEN
h.out.PutF["Debugging Mode On ... raw log on Chat.rawLog\n"];
EditedStream.SetEcho[h.in, NIL];
IF h.argv.argc > 1 THEN {
h.serverName ← h.argv[1];
h.useOldHost ← TRUE;
IF -i then we are just initializing the host name, else really connect
IF h.lorc = 'i
THEN h.lorc ← 'c
ELSE TypeScript.InsertCharAtFrontOfBuffer[ts: h.ts, char: IF h.lorc = 'c THEN ConnectChar ELSE LoginChar];
};
IF List.Length[chatInstanceList] = 0 THEN {
closeEvent ← ViewerEvents.RegisterEventProc[proc: MyClose, event: close];
destroyEvent ← ViewerEvents.RegisterEventProc[proc: MyDestroy, event: destroy]
};
chatInstanceList ← CONS[h, chatInstanceList];
Menus.AppendMenuEntry[menu: h.ts.menu, entry: Menus.CreateEntry[name: "Disconnect", proc: MyDisconnect, clientData: h, fork: TRUE, documentation: "Close Ethernet connection"]];
Menus.AppendMenuEntry[menu: h.ts.menu, entry: Menus.CreateEntry[name: "Login", proc: MyLogin, clientData: h, documentation: "Open Ethernet Connection to selected host and send Login sequence"]];
Menus.AppendMenuEntry[menu: h.ts.menu, entry: Menus.CreateEntry[name: "Connect", proc: MyConnect, clientData: h, documentation: "Open Ethernet Connection to selected host"]];
Menus.AppendMenuEntry[menu: h.ts.menu, entry: Menus.CreateEntry[name: "BreakKey", proc: MyBreakKey, clientData: h, documentation: "Transmit Ascii.NULL (DLS interprets as RS-232 Line Break)"]];
Menus.AppendMenuEntry[menu: h.ts.menu, entry: Menus.CreateEntry[name: "Interrupt", proc: MyInterrupt, clientData: h, documentation: "Send Interrupt sequence (STOP, or ^C)"]];
Menus.AppendMenuEntry[menu: h.ts.menu, entry: Menus.CreateEntry[name: "FlushLog", proc: MyFlushLog, clientData: h, documentation: "Flush log file to disk."]];
h.oldSplit ← Menus.FindEntry[menu: h.ts.menu, entryName: "Split"];
Menus.ReplaceMenuEntry[menu: h.ts.menu, oldEntry: h.oldSplit, newEntry: Menus.CreateEntry[name: "Split", proc: MySplit, fork: TRUE, clientData: h, documentation: "Split window"]];
ViewerOps.AddProp[h.ts, $ChatToolData, h];
ViewerOps.PaintViewer[viewer: h.ts, hint: all];
IF h.synchronous
THEN UserToServer[h]
ELSE TRUSTED {
Process.Detach[h.userToServerProcess ← FORK UserToServer[h]];
};
};
UserToServer: PROC [h: Handle] = TRUSTED {
char: CHAR;
count: NAT ← 0;
DO
ENABLE {
ABORTED => ERROR; -- ever happen?
IO.Error => {
last viewer destroyed !
Close connection and exit
CloseConnection[h, FALSE];
GOTO Die;
};
PupStream.StreamClosing => {
IF NOT h.inDestroy THEN {
h.out.PutF["\nConnection being closed by %s", IO.rope[h.serverName]];
FinishStringWithErrorMsg[h, text];
};
h.sToUStopped ← TRUE;
CONTINUE;
};
};
IF h.sToUStopped THEN {
IF h.serverStream # NIL THEN CloseConnection[h, FALSE];
IF h.destroyOnClose THEN {
ViewerOps.DestroyViewer[h.ts];
GOTO Die};
};
IF h.inDestroy THEN {
CloseConnection[h, FALSE];
GOTO Die;
};
char ← h.in.GetChar[];
SELECT char FROM
AbortChar => {
IF h.state = running THEN CloseConnection[h, TRUE];
};
DisconnectChar => {
IF h.state = running THEN CloseConnection[h, TRUE];
};
RemoteCloseChar => {
ERROR PupStream.StreamClosing[why: remoteClose, text: NIL];
};
ConnectChar => {
IF h.state = idle THEN {
h.lorc ← 'c;
StartUp[h];
count ← 0;
};
};
LoginChar => {
IF h.state = idle THEN {
h.lorc ← 'l;
StartUp[h];
count ← 0;
};
};
ENDCASE => {
SELECT h.state FROM
running => {
h.serverStream.PutChar[char];
IF count >= 50 OR h.in.CharsAvail[] = 0 THEN {
h.serverStream.Flush[];
count ← 0;
}
ELSE count ← count + 1;
};
ENDCASE => h.out.PutChar[char];
};
ENDLOOP;
EXITS
Die => {
IF h.logStream # NIL THEN h.logStream.Close[];
IF h.debugging THEN h.rawLogStream.Close[];
};
};
MyDestroy: ViewerEvents.EventProc = {
This EventProc exits only to keep h.ts pointing to a valid copy of the typescript viewer. It is only needed for the use of TypeScript.InsertCharAtFrontOfBuffer.
h: Handle ← NARROW[ViewerOps.FetchProp[viewer, $ChatToolData]];
IF h = NIL THEN RETURN;
SELECT NumSplit[viewer] FROM
0 => {
Race?
};
1 => {
h.inDestroy ← TRUE;
chatInstanceList ← List.Remove[h, chatInstanceList];
IF List.Length[chatInstanceList] = 0 THEN {
ViewerEvents.UnRegisterEventProc[proc: destroyEvent, event: destroy];
ViewerEvents.UnRegisterEventProc[proc: closeEvent, event: close];
};
}
ENDCASE => {
IF viewer = h.ts THEN {
Another: PROC [v: ViewerClasses.Viewer] = {
IF v # viewer THEN h.ts ← v;
};
EnumerateSplits[viewer, Another];
};
};
};
MyClose: ViewerEvents.EventProc = {
h: Handle ← NARROW[ViewerOps.FetchProp[viewer, $ChatToolData]];
IF h = NIL THEN RETURN;
TypeScript.InsertCharAtFrontOfBuffer[ts: h.ts, char: DisconnectChar];
};
MyConnect: Menus.MenuProc = {
viewer: TypeScript.TSNARROW[parent];
h: Handle ← NARROW[clientData];
h.ts ← viewer; -- "primary" copy
h.useOldHost ← mouseButton # red;
TypeScript.InsertCharAtFrontOfBuffer[ts: h.ts, char: ConnectChar];
};
MyLogin: Menus.MenuProc = {
viewer: TypeScript.TSNARROW[parent];
h: Handle ← NARROW[clientData];
h.ts ← viewer; -- "primary" copy
h.useOldHost ← mouseButton # red;
TypeScript.InsertCharAtFrontOfBuffer[ts: h.ts, char: LoginChar];
};
MyDisconnect: Menus.MenuProc = {
h: Handle ← NARROW[clientData];
TypeScript.InsertCharAtFrontOfBuffer[ts: h.ts, char: DisconnectChar];
};
MyBreakKey: Menus.MenuProc = TRUSTED {
h: Handle ← NARROW[clientData];
IF h.state # running THEN RETURN;
IF h.serverStream # NIL THEN {
ENABLE PupStream.StreamClosing => CONTINUE;
h.serverStream.PutChar['\000];
h.serverStream.Flush[];
};
};
MyInterrupt: Menus.MenuProc = TRUSTED {
h: Handle ← NARROW[clientData];
IF h.state # running THEN RETURN;
IF h.serverStream # NIL THEN {
ENABLE PupStream.StreamClosing => CONTINUE;
PupStream.SendAttention[h.serverStream];
PupStream.SendMark[h.serverStream, syncMark]; };
};
MyFlushLog: Menus.MenuProc = {
h: Handle ← NARROW[clientData];
IF h.logStream # NIL THEN h.logStream.Flush[];
};
MySplit: Menus.MenuProc = {
h: Handle ← NARROW[clientData];
CheckChatProperties: PROC [v: ViewerClasses.Viewer] = {
IF ViewerOps.FetchProp[v, $ChatToolData] = NIL THEN ViewerOps.AddProp[v, $ChatToolData, h];
v.tipTable ← h.tipTable;
};
h.oldSplit.proc[parent: parent, clientData: h.oldSplit.clientData, mouseButton: mouseButton, shift: shift, control: control];
EnumerateSplits[NARROW[parent, ViewerClasses.Viewer], CheckChatProperties];
};
ConnectionOpen: TIPUser.TIPPredicate = {
PROC [] RETURNS [BOOL]
h: Handle;
viewer: ViewerClasses.Viewer ← ViewerTools.GetSelectedViewer[];
IF viewer=NIL THEN RETURN [FALSE]; -- no primary selection
h ← NARROW[ViewerOps.FetchProp[viewer, $ChatToolData]];
IF h=NIL THEN RETURN [FALSE]; -- not a chat tool
RETURN [h.state = running]; -- connection open?
};
EnumerateSplits: PROC [v: ViewerClasses.Viewer, p: PROC [v: ViewerClasses.Viewer]] = {
v2: ViewerClasses.Viewer ← v;
IF v = NIL THEN RETURN;
DO
p[v2];
IF v2.link = NIL OR v2.link = v THEN RETURN;
v2 ← v2.link;
ENDLOOP;
};
NumSplit: PROC [v: ViewerClasses.Viewer] RETURNS [count: INT ← 0] = {
Counter: PROC [v2: ViewerClasses.Viewer] = {
count ← count + 1;
};
EnumerateSplits[v, Counter];
};
Init: PROC = {
Commander.Register[key: "Chat", proc: ChatMain, doc: "Pup User Telnet, see ChatDoc.tioga", interpreted: FALSE];
chatTipTable ← TIPUser.InstantiateNewTIPTable["Chat.TIP"];
TIPUser.RegisterTIPPredicate[$ConnectionOpen, ConnectionOpen];
};
main program for chat
Init[];
}.
March 31, 1982 9:27 pm, Stewart, copied from Laurel Chat
April 1, 1982 4:06 pm, Stewart, own viewer class & TIP table
April 4, 1982 5:18 pm, Stewart, Menu
April 6, 1982 10:27 pm, Stewart, command line stuff
18-Apr-82 17:42:24, Use TypeScript again
June 6, 1982 4:44 pm, Stewart, fix Menu instantiation to add Split
June 6, 1982 8:08 pm, Stewart, using own Split code until viewers copies PropList
September 21, 1982 4:26 pm, Stewart, Cedar 3.4
November 3, 1982 5:53 pm, Warren Teitelman
January 23, 1983 10:18 pm, Stewart, Cedar 3.5, major rework
January 24, 1983 5:49 pm, Stewart, Cedar 3.6
January 25, 1983 3:09 pm, Maxwell
April 6, 1983 6:43 pm, Larry Stewart
June 13, 1983 1:41 pm, Stewart, Cedar 4.2, added synchronous feature
September 7, 1983 6:19 pm, Stewart, Cedar 5
January 6, 1984 1:13 pm, Stewart, TypeScript.BackSpace on Control-A and BS
March 4, 1984 4:15:26 pm PST, Pavel, Cleanup, flash on Control-G, grab input focus on start up and connection.
March 11, 1984 5:59:56 pm PST, Pavel, Map received back-quotes (140C) into normal single quotes.
March 12, 1984 1:14:30 pm PST, Pavel, Gacha font changed to add a back-quote, so previous hack removed.
February 26, 1985 2:50:10 pm PST, Pavel, Added -D switch for making a raw log of characters received from the server. Also changed it to make a newline on receipt of LF instead of CR
May 31, 1985 0:52:39 am PDT, JLarson. Put <PostCedar5.2> features back in (ie. Vaxc auto-login, -D switch above)
August 4, 1985 4:23:31 pm PDT, JLarson. Fixed Monitor lock problem.
September 12, 1985 12:35:51 pm PDT, Pavel, Put newline change from February 26, 1985 back in; it seems to have been lost at some point.