DIRECTORY
Ascii,
Atom,
FS USING [StreamOpen],
EditedStream,
IO,
IOClasses USING [CreateDribbleOutputStream],
IOUtils,
MessageWindow USING [Append, Clear],
RefText,
Rope,
TiogaOps USING [GetRope, GetTextKey, LastLocWithin, PutTextKey, ViewerDoc, Location, Offset, Ref],
TypeScript USING [BackSpace, CharsAvailable, ChangeLooks, Create, Flush, GetChar, PutChar, Reset, WaitUntilCharsAvail, IsATypeScript, Destroyed, TypeIn],
ViewerClasses USING [Viewer],
ViewerEvents USING [EventProc, RegisterEventProc],
ViewerIO USING [],
ViewerIOExtras USING [],
ViewerOps USING [AddProp, FetchProp];
ViewerIOImpl:
CEDAR
PROGRAM
IMPORTS Atom, EditedStream, FS, IO, IOClasses, IOUtils, MessageWindow, RefText, Rope, TiogaOps, TypeScript, ViewerEvents, ViewerOps
EXPORTS ViewerIO, ViewerIOExtras
= BEGIN
STREAM: TYPE = IO.STREAM;
ROPE: TYPE = Rope.ROPE;
Viewer: TYPE = ViewerClasses.Viewer;
StreamProcs: TYPE = IO.StreamProcs;
DeliverWhenProc: TYPE = EditedStream.DeliverWhenProc;
TypeOfGetDeliverWhen: TYPE = PROC [self: STREAM] RETURNS [proc: DeliverWhenProc, context: REF ANY];
TypeOfSetDeliverWhen: TYPE = PROC [self: STREAM, proc: DeliverWhenProc, context: REF ANY];
TypeOfAppendBufferChars: TYPE = PROC [stream: STREAM, chars: ROPE];
TypeOfUnAppendBufferChars: TYPE = PROC [stream: STREAM, nChars: NAT];
TypeOfSetMode:
TYPE =
PROC [stream:
STREAM, stuff:
ROPE, pendingDelete:
BOOL,
echoAsterisks: BOOL];
Edited Viewer Input Stream (Editable Typescript)
Location: TYPE = TiogaOps.Location;
Offset: TYPE = TiogaOps.Offset;
EditedViewerStreamData: TYPE = REF EditedViewerStreamRecord;
EditedViewerStreamRecord:
TYPE =
RECORD[
ready: REF TEXT,
readyPos: INT ← 0, -- ready[readyPos .. ready.length) are the already-activated characters
buffer: REF TEXT,
echoStream: STREAM,
deliverWhen: DeliverWhenProc,
context: REF ANY,
bufferIsConsistent: BOOL ← FALSE,
viewer: Viewer,
node: TiogaOps.Ref
];
EditedViewerStreamProcs: REF StreamProcs;
CreateEditedViewerStream:
PROC [
in: STREAM, echoTo: STREAM, deliverWhen: DeliverWhenProc, context: REF ANY ← NIL]
RETURNS [STREAM] = {
in is a Viewer input stream and EditedStream.GetEcho[in] = NIL.
echoTo is a Viewer output stream for the same viewer as in, or is a dribble stream.
data: EditedViewerStreamData;
data ←
NEW[EditedViewerStreamRecord ← [
buffer: NEW[TEXT[256]],
ready: NEW[TEXT[256]],
readyPos: 0,
echoStream: echoTo,
deliverWhen: deliverWhen, context: context,
viewer: NARROW[in.streamData, ViewerStreamData].viewer,
node: NIL]];
RETURN [IO.CreateStream[
streamProcs: EditedViewerStreamProcs, streamData: data, backingStream: in]]
};
GetBuffer:
PUBLIC PROC [editedViewerStream:
STREAM]
RETURNS [
REF
TEXT] = {
data: EditedViewerStreamData = NARROW[editedViewerStream.streamData];
GetCurrentBuffer1[data];
RETURN [data.buffer];
};
TypeChars:
PUBLIC PROC [editedViewerStream:
STREAM, chars:
ROPE] = {
data: EditedViewerStreamData = NARROW[editedViewerStream.streamData];
TypeScript.TypeIn[data.viewer, chars];
};
GetCurrentBuffer:
PROC [data: EditedViewerStreamData] =
INLINE {
Called from EditedViewerStreamAppendBufferChars, EditedViewerStreamUnAppendBufferChars, EditedViewerStreamGetChar
Makes data.buffer consistent with current state of the underlying viewer
This is the only proc that tests the state of data.bufferIsConsistent
IF data.bufferIsConsistent THEN RETURN ELSE GetCurrentBuffer1[data];
};
GetCurrentBuffer1:
PROC [data: EditedViewerStreamData] = {
r: ROPE; loc: Location;
data.buffer.length ← 0;
data.bufferIsConsistent ← TRUE;
IF data.node = NIL THEN RETURN;
TypeScript.Flush[data.viewer];
r ← TiogaOps.GetRope[data.node];
loc ← TiogaOps.GetTextKey[node: data.node, key: data];
IF loc.node # data.node THEN ERROR;
data.buffer ← RefText.AppendRope[to: data.buffer, from: r, start: loc.where + 1];
};
EditedViewerStreamAppendBufferChars:
PROC [stream:
STREAM, chars:
ROPE] = {
data: EditedViewerStreamData = NARROW[stream.streamData];
Append1:
PROC [c:
CHAR]
RETURNS [quit:
BOOL] = {
AppendBufferChar[data, c]; RETURN [quit: FALSE] };
GetCurrentBuffer1[data];
[] ← chars.Map[action: Append1];
};
AppendBufferChar:
PROC [data: EditedViewerStreamData, char:
CHAR] =
INLINE {
Assumes data.buffer is consistent
loc: Location;
data.buffer ← RefText.InlineAppendChar[data.buffer, char];
IF data.node =
NIL
THEN {
TypeScript.Flush[data.viewer];
loc ← TiogaOps.LastLocWithin[TiogaOps.ViewerDoc[data.viewer]];
What if node goes away?
IF loc.where = 0 THEN data.echoStream.PutChar[' ] ELSE loc.where ← loc.where - 1;
};
data.echoStream.PutChar[char];
IF data.node =
NIL
THEN {
TypeScript.Flush[data.viewer];
TiogaOps.PutTextKey[node: loc.node, where: loc.where, key: data];
What if node goes away?
data.node ← loc.node;
};
};
EditedViewerStreamUnAppendBufferChars:
PROC [stream:
STREAM, nChars:
NAT] = {
data: EditedViewerStreamData = NARROW[stream.streamData];
GetCurrentBuffer1[data];
FOR i:
NAT
IN [0 ..
MIN[nChars, data.buffer.length])
DO
UnAppendBufferChar[data]
ENDLOOP;
};
UnAppendBufferChar:
PROC [data: EditedViewerStreamData] =
INLINE {
Assumes data.buffer is consistent
char: CHAR = data.buffer[data.buffer.length - 1];
data.echoStream.EraseChar[char];
data.buffer.length ← data.buffer.length - 1;
IF data.buffer.length = 0 THEN data.node ← NIL;
};
EditedViewerStreamSetMode:
PROC [stream:
STREAM, stuff:
ROPE, pendingDelete:
BOOL, echoAsterisks:
BOOL] = {
data: EditedViewerStreamData = NARROW[stream.streamData];
data.buffer.length ← 0;
data.readyPos ← data.ready.length;
EditedViewerStreamAppendBufferChars[stream, stuff];
};
EditedViewerStreamGetDeliverWhen:
PROC [self:
STREAM]
RETURNS [proc: DeliverWhenProc, context:
REF ANY] = {
data: EditedViewerStreamData = NARROW[self.streamData];
RETURN [data.deliverWhen, data.context];
};
EditedViewerStreamSetDeliverWhen:
PROC [self:
STREAM, proc: DeliverWhenProc, context:
REF ANY] = {
data: EditedViewerStreamData = NARROW[self.streamData];
data.deliverWhen ← proc; data.context ← context;
};
EditedViewerStreamGetChar:
PROC [self:
STREAM]
RETURNS [char:
CHAR] = {
data: EditedViewerStreamData = NARROW[self.streamData];
IsEditCommand:
PROC [char:
CHAR]
RETURNS [
BOOL] = {
RETURN [
SELECT char
FROM
Ascii.DEL, Ascii.ControlA, Ascii.BS, Ascii.ControlW, Ascii.ControlQ => TRUE,
ENDCASE => FALSE];
};
BackChar:
PROC = {
erases last character, if any, in buffer
IF data.buffer.length > 0
THEN {
UnAppendBufferChar[data];
}
};
BackWord:
PROC = {
erases last "word" (consecutive run of letters and numbers), if any, in buffer
alphaSeen: BOOL ← FALSE;
UNTIL data.buffer.length = 0
DO
SELECT data.buffer[data.buffer.length - 1]
FROM
IN ['A..'Z], IN ['a..'z], IN ['0..'9] => alphaSeen ← TRUE;
ENDCASE => IF alphaSeen THEN EXIT;
UnAppendBufferChar[data];
ENDLOOP;
};
BackLine:
PROC = {
erases buffer back to (not including) previous CR, if any
UNTIL data.buffer.length = 0
DO
IF data.buffer[data.buffer.length - 1] = IO.CR THEN EXIT;
UnAppendBufferChar[data];
ENDLOOP;
};
DO
IF data.readyPos < data.ready.length
THEN {
char ← data.ready[data.readyPos];
data.readyPos ← data.readyPos + 1;
RETURN [char];
};
{
appendChar, activate: BOOL;
char ← self.backingStream.GetChar[ !
IO.EndOfStream =>
IF data.buffer.length = 0 THEN REJECT ELSE GOTO activateBuffer];
-- GetCurrentBuffer[data];
[appendChar: appendChar, activate: activate] ←
data.deliverWhen[char, NIL --data.buffer--, self, data.context];
IF appendChar
THEN {
SELECT char
FROM
Ascii.
DEL => {
ENABLE
UNWIND => data.buffer.length ← 0;
ERROR EditedStream.Rubout[self];
};
Ascii.ControlA, Ascii.BS => { GetCurrentBuffer1[data]; BackChar[] };
Ascii.ControlW => { GetCurrentBuffer1[data]; BackWord[] };
Ascii.ControlQ => { GetCurrentBuffer1[data]; BackLine[] };
Ascii.
ESC => {
GetCurrentBuffer1[data];
IF data.buffer.length = 0
THEN {
FOR i:
NAT
IN [0..data.ready.length-1)
DO
AppendBufferChar[data, data.ready[i]];
ENDLOOP
}
};
ENDCASE => {
IF data.buffer.length = 0 THEN GetCurrentBuffer1[data];
AppendBufferChar[data, char];
}
};
IF activate THEN GOTO activateBuffer;
EXITS activateBuffer => {
GetCurrentBuffer1[data];
data.ready.length ← 0;
data.ready ← RefText.Append[data.ready, data.buffer];
data.readyPos ← 0;
data.buffer.length ← 0;
data.node ← NIL;
}
}
ENDLOOP;
};
EditedViewerStreamEndOf:
PROC [self:
STREAM]
RETURNS [
BOOL] = {
data: EditedViewerStreamData = NARROW[self.streamData];
RETURN[data.readyPos = data.ready.length AND self.backingStream.EndOf[]];
};
EditedViewerStreamCharsAvail:
PROC [self:
STREAM, wait:
BOOL]
RETURNS [
INT] = {
data: EditedViewerStreamData = NARROW[self.streamData];
IF data.readyPos < data.ready.length THEN RETURN [data.ready.length-data.readyPos];
RETURN[self.backingStream.CharsAvail[wait]];
};
EditedViewerStreamBackup:
PROC [self:
STREAM, char:
CHAR] = {
looks wrong ... why can't you backup past ready chars?
data: EditedViewerStreamData = NARROW[self.streamData];
IF data.readyPos = 0
OR data.ready[data.readyPos - 1] # char
THEN
ERROR IO.Error[$IllegalBackup, self];
data.readyPos ← data.readyPos - 1;
};
EditedViewerStreamSetEcho:
PROC [self:
STREAM, echoTo:
STREAM] = {
data: EditedViewerStreamData = NARROW[self.streamData];
IF echoTo = NIL THEN RETURN; -- ignore request to turn off echoing
IF data.echoStream # echoTo
THEN
ERROR
IO.Error[$Failure, self];
Can't change echo stream, because "truth" about buffered characters is stored in Tioga document behind echo stream.
};
EditedViewerStreamGetEcho:
PROC [self:
STREAM]
RETURNS [
STREAM] = {
data: EditedViewerStreamData = NARROW[self.streamData];
RETURN [data.echoStream];
};
EditedViewerStreamReset:
PROC [self:
STREAM] = {
data: EditedViewerStreamData = NARROW[self.streamData];
data.node ← NIL;
data.buffer.length ← 0;
data.ready.length ← 0;
data.readyPos ← 0;
self.backingStream.Reset[];
data.echoStream.Reset[];
};
Module Initialization
[] ← ViewerEvents.RegisterEventProc [
proc: WasAStreamViewerDestroyed, filter: $Typescript, event: $destroy];
ViewerInStreamProcs ← EditedStream.AddStreamProcs[to: IO.CreateStreamProcs[
variety: $input, class: $ViewersInput,
getChar: ViewerGetChar,
endOf: ViewerEndOf,
charsAvail: ViewerCharsAvail],
setEcho: ViewerSetEcho,
getEcho: ViewerGetEcho];
ViewerOutStreamProcs ← IO.CreateStreamProcs[
variety: $output, class: $ViewersOutput,
putChar: ViewerPutChar,
flush: ViewerFlush,
eraseChar: ViewerEraseChar];
ViewerOutPFProcs ← IOUtils.CopyPFProcs[NIL];
[] ← IOUtils.SetPFCodeProc[ViewerOutPFProcs, 'l, ViewerSetLooks];
EditedViewerStreamProcs
← EditedStream.AddStreamProcs[to: IO.CreateStreamProcs[
variety: $input, class: $EditedViewer,
getChar: EditedViewerStreamGetChar,
endOf: EditedViewerStreamEndOf,
charsAvail: EditedViewerStreamCharsAvail,
backup: EditedViewerStreamBackup,
reset: EditedViewerStreamReset],
setEcho: EditedViewerStreamSetEcho,
getEcho: EditedViewerStreamGetEcho];
IOUtils.StoreProc[EditedViewerStreamProcs,
$GetDeliverWhen, NEW[TypeOfGetDeliverWhen ← EditedViewerStreamGetDeliverWhen]];
IOUtils.StoreProc[EditedViewerStreamProcs,
$SetDeliverWhen, NEW[TypeOfSetDeliverWhen ← EditedViewerStreamSetDeliverWhen]];
IOUtils.StoreProc[EditedViewerStreamProcs,
$AppendBufferChars, NEW[TypeOfAppendBufferChars ← EditedViewerStreamAppendBufferChars]];
IOUtils.StoreProc[EditedViewerStreamProcs,
$UnAppendBufferChars, NEW[TypeOfUnAppendBufferChars ← EditedViewerStreamUnAppendBufferChars]];
IOUtils.StoreProc[EditedViewerStreamProcs,
$SetMode, NEW[TypeOfSetMode ← EditedViewerStreamSetMode]];
MessageWindowStreamProcs ← IO.CreateStreamProcs[
variety: $output, class: $MessageWindow,
flush: MessageWindowStreamFlush,
reset: MessageWindowStreamReset
];
END.