edited by Teitelman April 19, 1983 2:37 pm
DIRECTORY
Ascii USING [BS, ControlA, ControlW, ControlQ, CR, ESC, DEL],
Atom USING [GetPName],
FileIO USING [Open],
IO USING [ChangeDeliverWhen, CreateDribbleStream, CreateProcsStream, CreateRefStreamProcs, DeliverWhenProc, EndOf, EraseChar, Error, GetChar, GetOutputStreamRope, IsACR, PFCodeProc, PutChar, Reset, ROS, ROPE, SetEcho, SetPFCodeProc, Signal, STREAM, StoreData, StreamProcs, UncheckedImplements, Zone, UserAborted, GetBufferContents],
IOExtras USING [WaitUntilCharsAvail],
MessageWindow USING [Append, Clear],
RefText USING [TrustTextAsRope],
Rope USING [Equal, Fetch, Find, FromChar, FromRefText, Length, Replace, Substr],
TiogaOps USING [GetRope, GetTextKey, LastLocWithin, PutTextKey, ViewerDoc, Location, Offset, Ref],
TypeScript USING [BackSpace, CharsAvailable, ChangeLooks, Create, Flush, GetChar, PutChar, Reset, WaitUntilCharsAvail],
ViewerAbort USING [UserAbort, SetUserAbort, ResetUserAbort],
ViewerClasses USING [Viewer],
ViewerEvents USING [EventProc, RegisterEventProc],
ViewerIO USING [],
ViewerOps USING [AddProp, FetchProp]
;
ViewerIOImpl: CEDAR PROGRAM
IMPORTS Atom, IO, IOExtras, FileIO, MessageWindow, RefText, Rope, TiogaOps, TypeScript, ViewerAbort, ViewerEvents, ViewerOps
EXPORTS ViewerIO
SHARES IO
IO: UncheckedImplements, Zone
= BEGIN OPEN IO;
Viewer: TYPE = ViewerClasses.Viewer;
Viewer Stream
Types
ViewerStreamData: TYPE = REF ViewerStreamDataRecord;
ViewerStreamDataRecord: TYPE = RECORD[
viewer: Viewer,
echoTo: STREAMNIL
];
ViewerInStreamProcs: REF StreamProcs ← CreateRefStreamProcs[
getChar: ViewerGetChar,
endOf: ViewerEndOf,
charsAvail: ViewerCharsAvail,
setEcho: ViewerSetEcho,
userAbort: ViewerUserAbort,
setUserAbort: ViewerSetUserAbort,
resetUserAbort: ViewerResetUserAbort,
name: "Viewers Input"
];
ViewerOutStreamProcs: REF StreamProcs ← CreateRefStreamProcs[
putChar: ViewerPutChar,
putBlock: ViewerPutBlock,
endOf: ViewerEndOf,
flush: ViewersFlush,
eraseChar: ViewerEraseChar,
userAbort: ViewerUserAbort,
setUserAbort: ViewerSetUserAbort,
resetUserAbort: ViewerResetUserAbort,
name: "Viewers Output"
];
if stream is connected to a viewer, i.e. stream, or some chain of backing streams is one of the values returned from CreateViewerStreams, return corresponding viewer, otherwise NIL.
GetViewerFromStream: PUBLIC PROC [stream: STREAM] RETURNS[ViewerClasses.Viewer] = {
DO
IF stream = NIL THEN RETURN[NIL];
WITH stream.streamData SELECT FROM
r: ViewerStreamData => RETURN[r.viewer]
ENDCASE => IF stream.backingStream # NIL THEN stream ← stream.backingStream ELSE RETURN[NIL];
ENDLOOP;
};
CreateViewerStreams: PUBLIC PROC [name: ROPE, viewer: ViewerClasses.Viewer ← NIL, backingFile: ROPENIL, editedStream: BOOLTRUE] RETURNS [in: STREAM, out: STREAM] = {
OPEN IO;
streams: LIST OF STREAM;
IF viewer = NIL THEN viewer ← TypeScript.Create[info: [name: name, iconic: FALSE]];
in ← CreateProcsStream[streamProcs: ViewerInStreamProcs, streamData: NEW[ViewerStreamDataRecord ← [viewer: viewer ]] ];
IO.StoreData[in, $Name, name];
UncheckedImplements[
self: in,
operation: IOExtras.WaitUntilCharsAvail,
via: WaitUntilCharsAvail1,
procRef: Zone.NEW[PROC [self: STREAM] ← WaitUntilCharsAvail1],
key: $WaitUntilCharsAvail];
out ← CreateProcsStream[streamProcs: ViewerOutStreamProcs, streamData: NEW[ViewerStreamDataRecord ← [viewer: viewer]] ];
IO.StoreData[out, $Name, name];
streams ← NARROW[ViewerOps.FetchProp[viewer: viewer, prop: $Streams]];
streams ← CONS[in, CONS[out, streams]];
ViewerOps.AddProp[viewer: viewer, prop: $Streams, val: streams]; -- enables going from viewer to attached streams, e.g. so that when a split viewer is destroyed, can "promote" one of its children
out.SetPFCodeProc['l, SetLooks];
IF backingFile # NIL THEN out ← CreateDribbleStream[stream: out, dribbleTo: FileIO.Open[fileName: backingFile, accessOptions: overwrite], flushEveryNChars: 256];
[] ← SetEcho[in, out];
IF editedStream THEN in ← CreateEditedViewerStream[in: in, echoTo: out] ;
};
ViewerGetChar: SAFE PROCEDURE [self: STREAM] RETURNS [char: CHARACTER] = {
data: ViewerStreamData = NARROW[self.streamData];
IF data.viewer.destroyed THEN Error[StreamClosed, self];
char ← TypeScript.GetChar[data.viewer ! ABORTED =>
IF data.viewer.destroyed THEN Error[StreamClosed, self] -- a split viewer can be destroyed while inside of a wait. In this case, viewers will raise the ABORTED to alert the caller. However, data.viewer may have been replaced by one of the links (see WasAStreamViewerDestroyed below), and if so, simply try again.
ELSE RETRY];
IF data.echoTo # NIL THEN PutChar[data.echoTo, char]
ELSE IF ViewerUserAbort[self] THEN ERROR IO.UserAborted[self];
}; -- of ViewerGetChar
ViewerPutChar: SAFE PROCEDURE[self: STREAM, char: CHARACTER] = {
data: ViewerStreamData = NARROW[self.streamData];
IF data.viewer.destroyed THEN Error[StreamClosed, self];
IF ViewerUserAbort[self] THEN ERROR IO.UserAborted[self];
TypeScript.PutChar[data.viewer, char ! ABORTED =>
IF data.viewer.destroyed THEN ERROR Error[StreamClosed, self]
ELSE RETRY];
}; -- of ViewerPutChar
ViewerPutBlock: SAFE PROCEDURE[self: STREAM, block: REF READONLY TEXT, startIndex: NAT, stopIndexPlusOne: NAT] = {
data: ViewerStreamData = NARROW[self.streamData];
IF block # NIL THEN
{end: NATMIN[block.length, stopIndexPlusOne];
FOR i: NAT IN [startIndex..end) DO
ViewerPutChar[self, block[i]];
ENDLOOP;
};
TypeScript.PutText not used in order that I can check for aborting.
}; -- of ViewerPutChar
ViewerReset: SAFE PROCEDURE[self: STREAM] = {
data: ViewerStreamData = NARROW[self.streamData];
TypeScript.Reset[data.viewer];
}; -- of ViewerClose
ViewerEndOf: SAFE PROCEDURE[self: STREAM] RETURNS [BOOLEAN] = CHECKED {
data: ViewerStreamData = NARROW[self.streamData];
IF data.viewer.destroyed THEN Error[StreamClosed, self];
RETURN[FALSE];
}; -- ViewerEndOf
ViewersFlush: SAFE PROCEDURE[self: STREAM] = {
data: ViewerStreamData = NARROW[self.streamData];
IF data.viewer.destroyed THEN Error[StreamClosed, self];
TypeScript.Flush[data.viewer];
}; -- ViewersFlush
ViewerCharsAvail: SAFE PROCEDURE[self: STREAM] RETURNS [BOOLEAN] = {
data: ViewerStreamData = NARROW[self.streamData];
IF data.viewer.destroyed THEN Error[StreamClosed, self];
RETURN[TypeScript.CharsAvailable[data.viewer]];
}; -- ViewerCharsAvail
WaitUntilCharsAvail1: PROC [self: STREAM] = {
data: ViewerStreamData = NARROW[self.streamData];
IF data.viewer.destroyed THEN Error[StreamClosed, self];
TypeScript.WaitUntilCharsAvail[data.viewer ! ABORTED =>
IF data.viewer.destroyed THEN Error[StreamClosed, self] -- a split viewer can be destroyed while inside of a wait. In this case, viewers will raise the ABORTED to alert the caller. However, data.viewer may have been replaced by one of the links (see WasAStreamViewerDestroyed below), and if so, simply try again.
ELSE RETRY];
};
ViewerEraseChar: SAFE PROCEDURE[self: STREAM, char: CHARACTER] = {
data: ViewerStreamData = NARROW[self.streamData];
WHILE data.viewer.destroyed DO
IF data.viewer.link # NIL THEN data.viewer ← data.viewer.link
ELSE Error[StreamClosed, self];
ENDLOOP;
TypeScript.BackSpace[data.viewer];
}; -- of ViewerEraseChar
ViewerSetEcho: SAFE PROCEDURE[self: STREAM, echoTo: STREAM] RETURNS[oldEcho: STREAM] = CHECKED {
data: ViewerStreamData = NARROW[self.streamData];
IF data.viewer.destroyed THEN Error[StreamClosed, self];
oldEcho ← data.echoTo;
data.echoTo ← echoTo;
}; -- of ViewerSetEcho
ViewerUserAbort: SAFE PROC [self: STREAM] RETURNS [abort: BOOLEAN] = {
data: ViewerStreamData = NARROW[self.streamData];
RETURN[ViewerAbort.UserAbort[data.viewer]];
};
ViewerSetUserAbort: SAFE PROC [self: STREAM] = {
data: ViewerStreamData = NARROW[self.streamData];
ViewerAbort.SetUserAbort[data.viewer];
};
ViewerResetUserAbort: SAFE PROC [self: STREAM] = {
data: ViewerStreamData = NARROW[self.streamData];
ViewerAbort.ResetUserAbort[data.viewer];
};
WasAStreamViewerDestroyed: ViewerEvents.EventProc -- [viewer: ViewerClasses.Viewer, event: ViewerEvent] -- = {
streams: LIST OF STREAMNARROW[ViewerOps.FetchProp[viewer, $Streams]];
v: Viewer ← viewer;
IF streams = NIL THEN RETURN;
WHILE (v ← v.link) # NIL AND (v # viewer) DO -- split viewer
IF NOT v.destroyed THEN -- make all streams attached to the viewer about to be destroyed attached to this undestroyed child
{FOR l: LIST OF STREAM ← streams, l.rest UNTIL l = NIL DO
data: ViewerStreamData = NARROW[l.first.streamData];
data.viewer ← v;
ENDLOOP;
ViewerOps.AddProp[v, $Streams, streams];
RETURN;
};
ENDLOOP;
};
IsAViewerStream: PROC [self: STREAM] RETURNS [BOOL] = {
RETURN[ISTYPE[self.streamData, ViewerStreamData]];
};
SetLooks: PFCodeProc -- [stream: STREAM, val: Value, format: Format, char: CHAR] -- = {
viewer: ViewerClasses.Viewer = GetViewerFromStream[stream];
looks: ROPENIL;
IF viewer = NIL THEN Error[NotImplementedForThisStream, stream];
TRUSTED {WITH val SELECT FROM
null => NULL;
atom => looks ← Atom.GetPName[value];
rope => looks ← value;
character => looks ← Rope.FromChar[value];
ENDCASE => Error[NotImplementedForThisStream, stream]};
FOR i: INT IN [0..Rope.Length[looks]) DO
TypeScript.ChangeLooks[viewer, Rope.Fetch[looks, i]];
ENDLOOP;
};
Edited Viewer Stream
creates an edited stream on top of a viewer stream. implements ^A, ^W same as IO.CreateEditedStream. In addition, if editable typescripts are enabled, the user can edit that material not yet delivered.
Location: TYPE = TiogaOps.Location;
Offset: TYPE = TiogaOps.Offset;
NotAllowed: PUBLIC ERROR [stream: STREAM] = CODE;
EditedViewerStreamData: TYPE = REF EditedViewerStreamRecord;
EditedViewerStreamRecord: TYPE = RECORD[
node: TiogaOps.Ref ← NIL, -- this slot is filled with the tioga node, and a PutTextKey is done using this node and the stream itself as the key.
buffer: REF TEXT, -- for use when echoing is off, primarily to enable operations like StuffBuffer in the userexec. See comments in .
ready: REF TEXT, -- contains characters already delivered, i.e. if there are any characters in here, GetChar immediately returns the next one.
readyPos: INT ← 0,
used to use ready.length and get the characters from the end, and reset the length, i.e. in reverse order. This change was made so that could define a GetIndex and SetIndex, which was done in order to allow calls to MesaScanner on an edited stream.
lastReadyLength: INT, -- for ESC
deliverWhen: DeliverWhenProc,
echoTo: STREAM,
viewer: Viewer
];
EditedViewerStreamProcs: REF StreamProcs ← NIL;
CreateEditedViewerStream: PUBLIC PROCEDURE [in: STREAM, echoTo: STREAM, deliverWhen: DeliverWhenProc ← IsACR] RETURNS [h: STREAM] = {
viewer: Viewer;
data: EditedViewerStreamData;
IF EditedViewerStreamProcs = NIL THEN EditedViewerStreamProcs ← CreateRefStreamProcs[
getChar: EditedViewerStreamGetChar,
reset: EditedViewerStreamReset,
endOf: EditedViewerStreamEndOf,
charsAvail: EditedViewerStreamAvail,
getIndex: EditedViewerStreamGetIndex,
setIndex: EditedViewerStreamSetIndex,
setEcho: EditedViewerStreamSetEcho,
backup: EditedViewerStreamBackup,
eraseChar: EditedViewerStreamEraseChar,
name: "Edited Viewer"
];
viewer ← GetViewerFromStream[in];
IF viewer = NIL OR viewer # GetViewerFromStream[echoTo] THEN NotAllowed[in];
data ← Zone.NEW[EditedViewerStreamRecord ← [
ready: NEW[TEXT[256]],
buffer: NEW[TEXT[256]],
lastReadyLength: 0,
deliverWhen: deliverWhen]];
h ← CreateProcsStream[
streamProcs: EditedViewerStreamProcs,
streamData: data,
backingStream: in
];
UncheckedImplements[
self: h,
operation: ChangeDeliverWhen,
via: ChangeDeliverWhen1,
procRef: Zone.NEW[PROC [self: STREAM, proc: DeliverWhenProc] RETURNS [oldProc: DeliverWhenProc] ← ChangeDeliverWhen1],
key: $ChangeDeliverWhen];
UncheckedImplements[
self: h,
operation: IO.GetBufferContents,
via: GetBufferContents1,
procRef: Zone.NEW[PROC [self: STREAM] RETURNS [buffer: ROPE] ← GetBufferContents1],
key: $GetBufferContents];
[] ← SetEcho[in, NIL ! Error => CONTINUE];
[] ← SetEcho[h, echoTo]; -- echoing done at this level since dont want control-a's w's etc to be echoed, and also characters should be echoed when they are inserted in the buffer, not when they are returned as value of getchar.
}; -- of CreateEditedViewerStream
FlushTypeScript: PROC [self: STREAM] = INLINE {
-- flush gets called only to make sure all of the characters have gone to Tioga. Don't want to flush any buffer or dribble streams above.
TypeScript.Flush[GetViewerFromStream[self]];
};
ChangeDeliverWhen1: PROC [self: STREAM, proc: DeliverWhenProc] RETURNS [oldProc: DeliverWhenProc] = {
data: EditedViewerStreamData = NARROW[self.streamData];
oldProc ← data.deliverWhen;
data.deliverWhen ← proc;
};
GetBufferContents1: PROC [self: STREAM] RETURNS [buffer: ROPE] = {
data: EditedViewerStreamData = NARROW[self.streamData];
r: ROPE;
loc: Location;
IF data.node = NIL THEN RETURN[Rope.FromRefText[data.ready]];
FlushTypeScript[data.echoTo];
r ← TiogaOps.GetRope[data.node];
loc ← TiogaOps.GetTextKey[node: data.node, key: self];
IF loc.node # data.node THEN ERROR;
RETURN[Rope.Substr[base: r, start: loc.where + 1, len: Rope.Length[r] - loc.where - 1]]; -- the sticky address was associated with the character BEFORE the first one typed.
}; -- of GetBufferContents1
EditedViewerStreamReset: PROCEDURE[self: STREAM] = {
data: EditedViewerStreamData = NARROW[self.streamData];
data.node ← NIL;
data.buffer.length ← 0;
data.ready.length ← 0;
data.readyPos ← 0;
Reset[self.backingStream];
IF data.echoTo # NIL THEN Reset[data.echoTo];
}; -- of EditedViewerStreamReset
EditedViewerStreamFlush: PROCEDURE[self: STREAM] = {
data: EditedViewerStreamData = NARROW[self.streamData];
data.node ← NIL;
data.buffer.length ← 0;
data.ready.length ← 0;
data.readyPos ← 0;
IF data.echoTo # NIL THEN FlushTypeScript[data.echoTo];
}; -- of EditedViewerStreamFlush
EditedViewerStreamEndOf: PROCEDURE[self: STREAM] RETURNS [BOOLEAN] = {
data: EditedViewerStreamData = NARROW[self.streamData];
RETURN[data.readyPos >= data.ready.length AND EndOf[self.backingStream]];
}; -- of EditedViewerStreamEndOf
EditedViewerStreamGetIndex: PROCEDURE[self: STREAM] RETURNS [INT] = {
data: EditedViewerStreamData = NARROW[self.streamData];
RETURN[data.readyPos];
}; -- of EditedViewerStreamGetIndex
EditedViewerStreamSetIndex: PROCEDURE[self: STREAM, index: INT] = {
data: EditedViewerStreamData = NARROW[self.streamData];
IF index > data.ready.length OR index < 0 THEN ERROR IO.Error[BadIndex, self];
data.readyPos ← index;
}; -- of EditedViewerStreamSetIndex
EditedViewerStreamSetEcho: PROCEDURE[self: STREAM, echoTo: STREAM] RETURNS [oldEcho: STREAM] = {
data: EditedViewerStreamData = NARROW[self.streamData];
IF echoTo # NIL AND (GetViewerFromStream[echoTo] # GetViewerFromStream[self]) THEN NotAllowed[echoTo];
oldEcho ← data.echoTo;
data.echoTo ← echoTo;
IF oldEcho = NIL AND echoTo # NIL AND data.buffer.length > 0 THEN {-- see comment in AppendChar
loc: Location;
r: ROPE;
FlushTypeScript[echoTo];
loc ← TiogaOps.LastLocWithin[TiogaOps.ViewerDoc[GetViewerFromStream[self]]];
r ← TiogaOps.GetRope[loc.node];
IF NOT Rope.Equal[RefText.TrustTextAsRope[data.buffer], Rope.Substr[base: r, start: Rope.Length[r] - data.buffer.length]] THEN NotAllowed[self];
TiogaOps.PutTextKey[node: loc.node, where: loc.where - data.buffer.length - 1, key: self];
data.buffer.length ← 0;
data.node ← loc.node;
};
}; -- of EditedStreamSetEcho
EditedViewerStreamAvail: PROCEDURE[self: STREAM] RETURNS [BOOLEAN] = {
data: EditedViewerStreamData = NARROW[self.streamData];
RETURN[data.readyPos < data.ready.length -- OR CharsAvail[self.backingStream] --]; -- chars available now
}; -- of EditedViewerStreamAvail
erases last character from buffer, and if being echoed, from echo stream
EditedViewerStreamEraseChar: PROCEDURE[self: STREAM, char: CHARACTER] = {
data: EditedViewerStreamData = NARROW[self.streamData];
r: ROPE;
loc: Location;
offset: Offset;
i: INT;
IF data.echoTo = NIL THEN NotAllowed[self];
IF data.node = NIL THEN {-- Signal[EmptyBuffer, self]; -- RETURN};
FlushTypeScript[data.echoTo];
r ← TiogaOps.GetRope[data.node];
loc ← TiogaOps.GetTextKey[node: data.node, key: self];
IF loc.node # data.node THEN ERROR;
offset ← loc.where;
i ← Rope.Length[r] - 1;
data.echoTo.EraseChar[Rope.Fetch[r, i]];
i ← i - 1;
IF i <= offset THEN data.node ← NIL; -- means have just erased the last character you can. also, date.node must be recomputed after next character is typed
}; -- of EditedViewerStreamEraseChar
EditedViewerStreamGetChar: PROCEDURE[self: STREAM] RETURNS[char: CHARACTER] = {
data: EditedViewerStreamData = NARROW[self.streamData];
GetCharFromBuffer: PROC = INLINE {
char ← data.ready[data.readyPos];
data.readyPos ← data.readyPos + 1;
}; -- of GetCharFromBuffer
DO
IF data.readyPos < data.ready.length THEN {GetCharFromBuffer[]; RETURN};
char ← GetChar[self.backingStream]; -- gets character from stream underneath. Don't want to be in monitor at this point.
IF EditedViewerStreamAppend[self, char] AND data.deliverWhen[char, self] THEN EditedViewerStreamDeliver[self]; -- EditedViewerStreamAppend appends it to the buffer, checking for control-a etc., echoes the character. Value of FALSE means doesnt make any sense to check this char, e.g. was ^A or whatever.
ENDLOOP;
}; -- of EditedViewerStreamGetChar
EditedViewerStreamBackup: PROCEDURE[self: STREAM, char: CHARACTER] = {
data: EditedViewerStreamData = NARROW[self.streamData];
IF data.readyPos <= 0 OR data.ready[data.readyPos - 1] # char THEN Error[IllegalBackup, self];
data.readyPos ← data.readyPos - 1;
}; -- of EditedViewerStreamBackup
EditedViewerStreamDeliver: PROC [self: STREAM] = {
data: EditedViewerStreamData = NARROW[self.streamData];
nChars: INT;
readyLength: INT ← data.ready.length;
readyPos: INT ← data.readyPos;
r: ROPE;
loc: Location;
offset: Offset;
IF data.echoTo = NIL OR data.node = NIL THEN RETURN; -- latter can occur if DEL typed as first thing.
FlushTypeScript[data.echoTo];
r ← TiogaOps.GetRope[data.node];
loc ← TiogaOps.GetTextKey[node: data.node, key: self];
IF loc.node # data.node THEN ERROR;
offset ← loc.where + 1; -- the sticky address was associated with the character BEFORE the first one typed.
nChars ← Rope.Length[r] - offset;
move any unread characters in ready over.
FOR i: NAT IN [readyPos..readyLength) DO
data.ready[i - readyPos] ← data.ready[i];
ENDLOOP;
readyLength ← readyLength - readyPos;
data.readyPos ← readyPos ← 0;
WHILE nChars + readyLength >= data.ready.maxLength DO
data.ready ← -- SIGNAL BufferOverFlow -- EnlargeBuffer[data.ready];
ENDLOOP;
FOR i: NAT IN [0..nChars) DO
data.ready[readyLength + i] ← Rope.Fetch[r, offset + i];
ENDLOOP;
data.node ← NIL;
data.ready.length ← nChars + readyLength;
data.lastReadyLength ← data.ready.length;
}; -- of EditedViewerStreamDeliver
EditedViewerStreamAppend: PROCEDURE[self: STREAM, char: CHARACTER] RETURNS[BOOLEANFALSE] = {
data: EditedViewerStreamData = NARROW[self.streamData];
EraseWord: PROC = {
state: {between, alpha, done} ← between;  
r: ROPE;
loc: Location;
offset: Offset;
i: INT;
IF data.echoTo = NIL THEN NotAllowed[self];
IF data.node = NIL THEN {-- Signal[EmptyBuffer, self]; -- RETURN};
FlushTypeScript[data.echoTo];
r ← TiogaOps.GetRope[data.node];
loc ← TiogaOps.GetTextKey[node: data.node, key: self];
IF loc.node # data.node THEN ERROR;
offset ← loc.where;
i ← Rope.Length[r] - 1;
DO
char: CHARACTER ← Rope.Fetch[r, i];
SELECT char FROM
IN ['A..'Z], IN ['a..'z], IN ['0..'9] =>
state ← alpha;
ENDCASE => IF state = alpha THEN state ← done;
IF state = done THEN EXIT;
data.echoTo.EraseChar[char];
i ← i - 1;
IF i <= offset THEN
{data.node ← NIL; -- means have just erased the last character you can. also, date.node must be recomputed after next character is typed
EXIT;
}
ENDLOOP;
}; -- of EraseWord
EraseLine: PROC = {
r: ROPE;
loc: Location;
offset: Offset;
i: INT;
IF data.echoTo = NIL THEN NotAllowed[self];
IF data.node = NIL THEN {-- Signal[EmptyBuffer, self]; -- RETURN}; -- nothing typed yet
FlushTypeScript[data.echoTo];
r ← TiogaOps.GetRope[data.node];
loc ← TiogaOps.GetTextKey[node: data.node, key: self];
IF loc.node # data.node THEN ERROR;
offset ← loc.where;
i ← Rope.Length[r] - 1;
DO -- deletes entire line. for buffered streams that accept more than one line of input.
char: CHARACTER ← Rope.Fetch[r, i];
IF char = Ascii.CR THEN EXIT;
data.echoTo.EraseChar[char];
i ← i - 1;
IF i <= offset THEN
{data.node ← NIL; -- means have just erased the last character you can. also, date.node must be recomputed after next character is typed
EXIT;
}
ENDLOOP;
}; -- of EraseLine
AppendChar: PROC [self: STREAM] = {
loc: Location;
IF data.echoTo = NIL THEN {
The situation where this occurs is the client, e.g. the userexec, wants to get the buffer back into synch with whats already on the screen. For example, the user types mumble{esc}. These characters are read from the screen, and now the userexec wants to add some more characters such that it looks to the user like all of the characters are part of the same input operation, i.e. he can controlA or even edit on the line. In order to decouple this from the particular stream that is used, this is accomplished in the userexec by turning echoing off, doing an append streams, and turning echoing back on. This works fine for an IO.EditedStream, since the characters that are read while echoing is off still go into its internal buffer. For a viewer edited stream, the characters are also stored in the internal buffer, and then when echoing goes back on, if the buffer agrees with the last n characters in the viewer, the sticky address is backed up. Otherwise, give an error.
WHILE data.buffer.length >= data.buffer.maxLength DO
data.buffer ← -- SIGNAL BufferOverFlow -- EnlargeBuffer[data.buffer];
ENDLOOP;
data.buffer[data.buffer.length] ← char;
data.buffer.length ← data.buffer.length + 1;
RETURN
};
IF data.node = NIL THEN {
FlushTypeScript[data.echoTo];
loc ← TiogaOps.LastLocWithin[TiogaOps.ViewerDoc[GetViewerFromStream[self]]];
IF loc.where = 0 THEN PutChar[data.echoTo, ' ]
ELSE loc.where ← loc.where - 1; -- sticky address associated with character before the first one typed so that if user inserts in front of first thing type, will be after the sticky address.
};
PutChar[data.echoTo, char];
IF data.node = NIL THEN
{FlushTypeScript[data.echoTo];
TiogaOps.PutTextKey[node: loc.node, where: loc.where, key: self]; -- NEED TO WORRY ABOUT WHAT HAPPENS IF THE NODE GOES AWAY.
data.node ← loc.node;
};
}; -- of AppendChar
SELECT char FROM
Ascii.DEL => {EditedViewerStreamDeliver[self]; Signal[Rubout, self]};
Ascii.ControlA, Ascii.BS => EditedViewerStreamEraseChar[self, char];
Ascii.ControlW => EraseWord[];
Ascii.ControlQ => EraseLine[];
ENDCASE =>
IF char = Ascii.ESC AND data.node = NIL THEN
FOR i: NAT IN [0..data.lastReadyLength-1) DO -- skip the character that caused the deliver, typically CR.
char ← data.ready[i];
AppendChar[self];
ENDLOOP
ELSE
{AppendChar[self];
RETURN[TRUE];
};
RETURN[FALSE];
}; -- of EditedViewerStreamAppend
decided not to raise signal but to call this procedure instead.
EnlargeBuffer: PROC [text: REF TEXT] RETURNS[newBuffer: REF TEXT] = {
newBuffer ← NEW[TEXT[2 * text.maxLength]];
FOR i: NAT IN [0..text.maxLength) DO
newBuffer[i] ← text[i];
ENDLOOP;
newBuffer.length ← text.maxLength;
RETURN[newBuffer];
};
Message Window Stream
MessageWindowStreamProcs: REF StreamProcs ← NIL;
CreateMessageWindowStream: PUBLIC PROCEDURE [] RETURNS [IO.STREAM] = {
IF MessageWindowStreamProcs = NIL THEN MessageWindowStreamProcs ← CreateRefStreamProcs[
flush: MessageWindowStreamFlush,
reset: MessageWindowStreamReset,
name: "Message Window"
];
RETURN[IO.CreateProcsStream[streamProcs: MessageWindowStreamProcs, backingStream: IO.ROS[], streamData: NIL]];
}; -- of CreateMessageWindowStream
MessageWindowStreamFlush: PROC[self: IO.STREAM] = {
r: ROPE ← self.backingStream.GetOutputStreamRope[];
i: INT ← 0;
self.backingStream.Reset[];
WHILE (i ← Rope.Find[s1: r, s2: "\n", pos1: i]) # -1 DO
r ← Rope.Replace[base: r, start: i, len: 1, with: " "];
ENDLOOP;
MessageWindow.Append[message: r, clearFirst: TRUE]
}; -- of MessageWindowStreamFlush
MessageWindowStreamReset: PROC[self: IO.STREAM] = {
self.backingStream.Reset[];
MessageWindow.Clear[]
}; -- of MessageWindowStreamReset
[] ← ViewerEvents.RegisterEventProc[proc: WasAStreamViewerDestroyed, filter: $Typescript, event: destroy];
END.
March 24, 1982 7:46 pm W. Teitelman. Fixed Close to check if viewer already destroyed. Added name argument to CreateRefStreamProcs.
13-Apr-82 14:05:22 W. Teitelman. Added checks for whether viewer was destroyed, and if so, raised IO error
May 27, 1982 2:49 pm W. Teitelman. Changed name of IOStream to Made changes relating to IO being in the Safe language
June 1, 1982 6:01 pm W. Teitelman. Added CheckForAbort.
August 16, 1982 11:37 am fixed PutChar to check for abort. Deimplemented the auttomatic destruction of the viewer.
September 27, 1982 2:25 pm made changes to accomodate splitting a viewer to which a stream is attached, and then destroying the top viewer.
September 28, 1982 2:03 pm added facility for PutF to change looks.
October 1, 1982 1:11 pm added Flush
Edited on December 13, 1982 11:02 am, by Teitelman
changes to: DIRECTORY, NotAllowed
Edited on December 20, 1982 12:02 pm, by Teitelman
fixed SetLooks to do a RemoveLooks for capital letters.
changes to: DIRECTORY, SetLooks
Edited on February 3, 1983 12:54 pm, by Teitelman
changes to: DIRECTORY, CreateViewerStreams, CreateEditedViewerStream
Edited on April 13, 1983 11:14 am, by Teitelman
activate on DEL so client can flush unwanted characters by doing a WHILE CharsAvail DO GetChar. Also removed hack in EditedViewerStreamDeliver which inserted a character when
changes to: EditedViewerStreamGetChar, EditedViewerStreamDeliver, AppendChar (local of EditedViewerStreamAppend), EditedViewerStreamAppend
Edited on April 15, 1983 11:10 am, by Teitelman
changes to: EditedViewerStreamAvail
Edited on April 19, 1983 2:37 pm, by Teitelman
changes to: EditedViewerStreamDeliver
Edited on May 2, 1983 8:52 pm, by Teitelman
removed viewer field from data for editedstream. instead recompute using GetViewerFromStream. Reason is that if you have a split viewer and an edited stream on it, and the top viewer destroyed, then the edited stream is confused about what viewer to obtain its stuff from.
changes to: CreateEditedViewerStream , EditedViewerStreamSetEcho , AppendChar (local of EditedViewerStreamAppend)