<> 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 <> = BEGIN OPEN IO; Viewer: TYPE = ViewerClasses.Viewer; <> <> 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" ]; <> 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; }; <> }; -- 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; }; <> <> 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, <> 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 <> 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; <> 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 { <> 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 <> 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]; }; <> 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 <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <<>>