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]
Viewer Stream
Types
ViewerStreamData: TYPE = REF ViewerStreamDataRecord;
ViewerStreamDataRecord:
TYPE =
RECORD[
viewer: Viewer,
echoTo: STREAM ← NIL
];
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:
ROPE ←
NIL, editedStream:
BOOL ←
TRUE]
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: NAT ← MIN[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 STREAM ← NARROW[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: ROPE ← NIL;
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[
BOOLEAN ←
FALSE] = {
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];
};
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.