TiogaAccessImpl.mesa
Copyright (C) 1985, Xerox Corporation. All rights reserved.
Michael Plass, March 5, 1985 10:19:49 am PST
Last Edited by: Diebert, March 5, 1985 9:39:55 am PST
DIRECTORY
Ascii USING [CR],
Atom USING [DottedPairNode, PropList],
EditSpan USING [CannotDoEdit, Transpose],
EditSpanSupport USING [CopySpan],
FS USING [ErrorGroup, OpenFile],
IO USING [PutChar, RopeFromROS, ROS, STREAM],
LooksReader USING [Body, BumpIndex, FreeLooksReader, Get, GetIndex, GetLooksReader, Peek, Ref, SetIndex, SetPosition],
MessageWindow USING [Append, Blink],
NameSymbolTable USING [MakeNameFromRope, RopeFromName],
NodeProps USING [DoSpecs, GetSpecs, MapProps, PutProp],
Process USING [Detach],
PutGet USING [FromFile, FromFileC, ToFile, ToFileC],
Rope USING [Concat, FromRefText, Length, ROPE, Substr],
RopeReader USING [Body, BumpIndex, FreeRopeReader, Get, GetIndex, GetRope, GetRopeReader, Peek, Ref, SetIndex, SetPosition],
RunReader USING [Get, GetIndex, GetRuns, Peek, SetIndex, SetPosition],
SafeStorage USING [CantEstablishFinalization, EnableFinalization, EstablishFinalization, FinalizationQueue, FQNext, NewFQ],
TEditDocument USING [Selection, TEditDocumentData],
TEditInput USING [currentEvent, FreeTree],
TEditInputOps USING [CallWithLocks, Delete],
TEditSelection USING [GetSelectionGrain, MakeSelection],
TextLooks USING [Concat, CreateRun, Looks, Runs, Size, Substr],
TextLooksSupport USING [],
TextNode USING [Body, FirstChild, Forward, LastLocWithin, LastWithin, Level, Location, LocOffset, LocWithin, MakeNodeLoc, MakeNodeSpan, NewTextNode, NodeItself, Offset, Parent, Ref, Root, Span, TextBody],
TiogaAccess USING [Buffer, BufferRep, CharWithLooks, Looks, NodeInfo],
ViewerClasses USING [Viewer],
ViewerOps USING [PaintViewer];
TiogaAccessImpl: CEDAR PROGRAM
IMPORTS EditSpan, EditSpanSupport, IO, LooksReader, MessageWindow, NameSymbolTable, NodeProps, Process, PutGet, Rope, RopeReader, SafeStorage, TEditInput, TEditInputOps, TEditSelection, TextLooks, TextNode, ViewerOps
EXPORTS TiogaAccess
~ BEGIN OPEN TiogaAccess;
ROPE: TYPE ~ Rope.ROPE;
ropePieces: NAT ~ 15;
textBufSize: NAT ← (574-SIZE[TEXT[0]])*2;
Largest size that will come from the small-grain allocator (see Allocator.maxSmallBlockSize)
PutBackBufferSize: NAT ← 100;
maxPieceSize: INT ~ (INT[64]*1024-8-SIZE[BufferRep[0]])/SIZE[CharWithLooks];
Reader: TYPE ~ REF ReaderRep;
ReaderRep: PUBLIC TYPE ~ RECORD [
root: TextNode.Ref,
textNode: TextNode.Ref,
textNodeIndex: INT,
nodeInfo: NodeInfo,
nodeLength: INT,
ropeReader: RopeReader.Ref,
looksReader: LooksReader.Ref,
end: BOOLEAN,
startingOffsetFromOriginalRoot: TextNode.Offset,
putback: LIST OF Buffer
Items are in the putback buffer in reverse order.
];
Error: PUBLIC ERROR [group: FS.ErrorGroup, expl: ROPE] ~ CODE;
ObtainBuffer: PUBLIC PROC [pieceSize: NAT] RETURNS [Buffer] ~ {
buffer: Buffer ← NEW[BufferRep[pieceSize]];
buffer.length ← 0;
buffer.next ← NIL;
RETURN [buffer]
};
NextPieceSize: PROC [pieceSize: NAT] RETURNS [NAT] ~ {
RETURN [MIN[maxPieceSize, 2*pieceSize]];
};
ReleaseBuffer: PUBLIC PROC [buffer: Buffer] ~ {
IF buffer # NIL THEN {
ReleaseBuffer[buffer.next];
buffer.next ← NIL;
};
};
BufferSize: PUBLIC PROC [buffer: Buffer] RETURNS [INT] ~ {
size: INT ← 0;
WHILE buffer # NIL DO
size ← size + buffer.length;
buffer ← buffer.next;
ENDLOOP;
RETURN [size];
};
SetBufferSize: PUBLIC PROC [buffer: Buffer, newSize: INT] ~ {
WHILE buffer.maxLength < newSize DO
buffer.length ← buffer.maxLength;
IF buffer.next = NIL THEN buffer.next ← ObtainBuffer[NextPieceSize[buffer.maxLength]];
newSize ← newSize - buffer.length;
buffer ← buffer.next;
ENDLOOP;
buffer.length ← newSize;
ReleaseBuffer[buffer.next];
buffer.next ← NIL;
};
Append: PUBLIC PROC [buffer: Buffer, value: CharWithLooks] ~ {
WHILE buffer.next # NIL DO
buffer ← buffer.next;
ENDLOOP;
IF buffer.length = buffer.maxLength THEN {
buffer.next ← ObtainBuffer[NextPieceSize[buffer.maxLength]];
buffer ← buffer.next;
buffer.length ← 0;
};
InlineAppend[buffer, value];
};
Store: PUBLIC PROC [buffer: Buffer, index: INT, value: CharWithLooks] ~ {
UNTIL buffer = NIL OR index < buffer.length DO
index ← index-buffer.length;
buffer ← buffer.next;
ENDLOOP;
IF buffer = NIL THEN Error[client, "Index exceeds buffer bounds"];
buffer[index] ← value;
};
Fetch: PUBLIC PROC [buffer: Buffer, index: INT] RETURNS [CharWithLooks] ~ {
UNTIL buffer = NIL OR index < buffer.length DO
index ← index-buffer.length;
buffer ← buffer.next;
ENDLOOP;
IF buffer = NIL THEN Error[client, "Index exceeds buffer bounds"];
RETURN [buffer[index]];
};
GetExternalProp: PUBLIC PROC [key: REF, value: REF] RETURNS [ROPE] ~ {
RETURN [NodeProps.GetSpecs[NARROW[key], value]]
};
GetInternalProp: PUBLIC PROC [key: REF, value: ROPE] RETURNS [REF] ~ {
RETURN [NodeProps.DoSpecs[NARROW[key], value]]
};
MakePropList: PROC [textNode: TextNode.Ref] RETURNS [props: Atom.PropList ← NIL] ~ {
Action: PROC [name: ATOM, value: REF] RETURNS [BOOLEANFALSE] ~ {
props ← CONS[NEW[Atom.DottedPairNode ← [key: name, val: NodeProps.GetSpecs[name, value]]], props];
};
[] ← NodeProps.MapProps[n: textNode, action: Action, typeFlag: FALSE, commentFlag: FALSE];
};
StartNewNode: PROC [reader: Reader, offset: INT ← 0] ~ {
IF reader.textNode = NIL THEN {
reader.nodeInfo.format ← NIL;
reader.nodeInfo.comment ← FALSE;
reader.nodeInfo.props ← NIL;
}
ELSE {
reader.nodeInfo.format ← NameSymbolTable.RopeFromName[reader.textNode.typename];
reader.nodeInfo.props ← MakePropList[reader.textNode];
};
WITH reader.textNode SELECT FROM
t: REF TextNode.Body.text => {
reader.nodeLength ← Rope.Length[t.rope];
reader.nodeInfo.comment ← t.comment;
RopeReader.SetPosition[reader.ropeReader, t.rope, offset];
LooksReader.SetPosition[reader.looksReader, t.runs, offset];
};
ENDCASE => {
reader.nodeLength ← 0;
reader.nodeInfo.comment ← TRUE;
RopeReader.SetPosition[reader.ropeReader, NIL];
LooksReader.SetPosition[reader.looksReader, NIL];
};
};
InitIndex: PROC [reader: Reader] ~ {
reader.textNodeIndex ← TextNode.LocOffset[loc1: [reader.root, 0], loc2: [reader.textNode, 0], skipCommentNodes: FALSE];
};
FromNode: PROC [textNode: TextNode.Ref, offset: TextNode.Offset ← INT.FIRST] RETURNS [Reader] ~ {
new: Reader ~ NEW[ReaderRep];
new.root ← TextNode.Root[textNode];
new.textNode ← textNode;
new.nodeInfo.level ← TextNode.Level[textNode];
new.ropeReader ← RopeReader.GetRopeReader[];
new.looksReader ← LooksReader.GetLooksReader[];
new.end ← (textNode = NIL);
StartNewNode[new];
InitIndex[new];
new.startingOffsetFromOriginalRoot ← 0;
IF offset # INT.FIRST THEN new.startingOffsetFromOriginalRoot ← offset-GetPosition[new];
SafeStorage.EnableFinalization[new];
RETURN [new];
};
FromNothing: PUBLIC PROC RETURNS [Reader] ~ {
RETURN [FromNode[NIL]]
};
FromFile: PUBLIC PROC [fileName: ROPE] RETURNS [Reader] ~ {
RETURN [FromNode[PutGet.FromFile[fileName]]]
};
FromOpenFile: PUBLIC PROC [openFile: FS.OpenFile] RETURNS [Reader] ~ {
RETURN [FromNode[PutGet.FromFileC[openFile]]]
};
NodeSpanFromSelection: PROC[sel: TEditDocument.Selection] RETURNS[span: TextNode.Span] = {
RETURN[TextNode.MakeNodeSpan[first: sel.start.pos.node, last: sel.end.pos.node]]
};
SelectionSpan: PROC[sel: TEditDocument.Selection] RETURNS[span: TextNode.Span] = {
IF TEditSelection.GetSelectionGrain[sel]=node THEN RETURN[NodeSpanFromSelection[sel]]
ELSE RETURN[[start: sel.start.pos, end: sel.end.pos]];
};
CreateChain: PROC [levels: INT] RETURNS [root, last: TextNode.Ref] ~ {
Creates root and descendants with no siblings, just to get the levels right.
IF levels <= 0 THEN RETURN [NIL, NIL];
root ← TextNode.NewTextNode[];
root.last ← TRUE;
last ← root;
THROUGH [0..levels) DO
n: REF TextNode.Body.text ← TextNode.NewTextNode[];
n.last ← TRUE;
last.child ← n;
last ← n;
ENDLOOP;
};
FromSelection: PUBLIC PROC RETURNS [Reader] ~ {
textNode: TextNode.Ref ← NIL;
myOffset: TextNode.Offset ← 0;
CopySelection: PROC [root: TextNode.Ref, tSel: TEditDocument.Selection] ~ {
IF tSel.granularity = point THEN NULL
ELSE {
span: TextNode.Span ← SelectionSpan[tSel];
copy: TextNode.Span ← EditSpanSupport.CopySpan[span];
myOffset ← TextNode.LocOffset[loc1: TextNode.MakeNodeLoc[root], loc2: span.start, skipCommentNodes: TRUE];
IF copy.end.where # TextNode.NodeItself THEN {
WITH copy.end.node SELECT FROM
t: REF TextNode.Body.text => {
t.rope ← Rope.Substr[t.rope, 0, copy.end.where+1];
t.runs ← TextLooks.Substr[t.runs, 0, copy.end.where+1];
};
ENDCASE => NULL;
};
IF copy.start.where > 0 THEN {
WITH copy.start.node SELECT FROM
t: REF TextNode.Body.text => {
t.rope ← Rope.Substr[t.rope, copy.start.where, INT.LAST];
t.runs ← TextLooks.Substr[t.runs, copy.start.where, INT.LAST];
copy.start.where ← 0;
};
ENDCASE => NULL;
};
textNode ← copy.start.node;
};
};
TEditInputOps.CallWithLocks[CopySelection, read];
RETURN [FromNode[textNode, myOffset]]
};
FromViewer: PUBLIC PROC [viewer: ViewerClasses.Viewer] RETURNS [Reader] ~ {
textNode: TextNode.Ref ← NIL;
WITH viewer.data SELECT FROM
tdd: TEditDocument.TEditDocumentData => {
r: TextNode.Ref ← tdd.text;
r ← TextNode.Root[
EditSpanSupport.CopySpan[ -- adds an extra r node
TextNode.MakeNodeSpan[r, TextNode.LastWithin[r]]
]
.start.node
];
textNode ← TextNode.FirstChild[r]; -- get rid of the extra r node
textNode.next ← NIL; -- no parent or siblings
r.child ← NIL; r ← NIL;-- don't want r any more
};
ENDCASE => NULL;
RETURN [FromNode[textNode]]
};
EndOf: PUBLIC PROC [reader: Reader] RETURNS [BOOLEAN] ~ {
RETURN [reader.putback = NIL AND reader.end]
};
Get: PUBLIC PROC [reader: Reader] RETURNS [CharWithLooks] ~ {
IF reader.putback # NIL THEN {
buf: Buffer ← reader.putback.first;
candl: CharWithLooks ← buf[buf.length-1];
IF (buf.length ← buf.length - 1) = 0 THEN {
t: LIST OF Buffer ← reader.putback.rest;
reader.putback.rest ← NIL;
reader.putback ← t;
ReleaseBuffer[buf];
};
RETURN [candl]
}
ELSE IF reader.end THEN {ERROR Error[client, "Attempt to read past end of document"]}
ELSE IF RopeReader.GetIndex[reader.ropeReader] = reader.nodeLength THEN {
SkipToNextNode[reader];
RETURN [[char: Ascii.CR, endOfNode: TRUE, looks: ALL[FALSE]]]
}
ELSE {
RETURN [[char: RopeReader.Get[reader.ropeReader], endOfNode: FALSE, looks: LooksReader.Get[reader.looksReader]]]
};
};
GetIndex: PUBLIC PROC [reader: Reader] RETURNS [index: INT] ~ {
index ← reader.textNodeIndex + reader.ropeReader.GetIndex;
index ← TextNode.LocOffset[loc1: [reader.root, 0], loc2: [reader.textNode, reader.ropeReader.GetIndex], skipCommentNodes: FALSE];
};
GetLength: PUBLIC PROC [reader: Reader] RETURNS [INT] ~ {
RETURN [TextNode.LocOffset[
loc1: [reader.root, 0],
loc2: TextNode.LastLocWithin[reader.root],
skipCommentNodes: FALSE
]+1];
};
GetPosition: PUBLIC PROC [reader: Reader] RETURNS [position: INT] ~ {
position ← TextNode.LocOffset[loc1: [reader.root, 0], loc2: [reader.textNode, reader.ropeReader.GetIndex], skipCommentNodes: TRUE] + reader.startingOffsetFromOriginalRoot;
};
SetIndex: PUBLIC PROC [reader: Reader, index: INT] ~ {
DiscardPutBack[reader];
IF index IN [reader.textNodeIndex..reader.textNodeIndex+NodeSize[reader.textNode]) THEN {
Fast case if in the current node.
offset: INT ← index - reader.textNodeIndex;
RopeReader.SetIndex[reader.ropeReader, offset];
LooksReader.SetIndex[reader.looksReader, offset];
}
ELSE
SetLoc[reader, TextNode.LocWithin[n: reader.root, count: index, skipCommentNodes: FALSE]]
};
SetPosition: PUBLIC PROC [reader: Reader, position: INT] ~ {
DiscardPutBack[reader];
SetLoc[reader, TextNode.LocWithin[n: reader.root, count: position - reader.startingOffsetFromOriginalRoot, skipCommentNodes: TRUE]];
};
SetLoc: PROC [reader: Reader, location: TextNode.Location] ~ {
reader.textNode ← location.node;
StartNewNode[reader, IF location.where = TextNode.NodeItself THEN 0 ELSE location.where];
InitIndex[reader];
};
GetNodeInfo: PUBLIC PROC [reader: Reader] RETURNS [NodeInfo] ~ {
RETURN [reader.nodeInfo]
};
GetBufferBlock: PROC [reader: Reader, buffer: Buffer, maxLength: INT] RETURNS [endOfNode: BOOLEAN] ~ {
n: NAT ~ MIN[maxLength, buffer.maxLength-buffer.length];
FOR i: NAT IN [buffer.length..buffer.length+n) DO
IF (buffer[i] ← Get[reader]).endOfNode THEN {
buffer.length ← i+1;
RETURN [TRUE]
};
ENDLOOP;
buffer.length ← buffer.length+n;
RETURN [FALSE];
};
GetBuffer: PUBLIC PROC [reader: Reader, buffer: Buffer, start: INT, maxLength: INT] RETURNS [endOfNode: BOOLEANFALSE] ~ {
WHILE buffer.maxLength <= start DO
buffer.length ← buffer.maxLength;
IF buffer.next = NIL THEN buffer.next ← ObtainBuffer[NextPieceSize[buffer.maxLength]];
start ← start - buffer.length;
buffer ← buffer.next;
ENDLOOP;
buffer.length ← start;
UNTIL maxLength = 0 DO
endOfNode ← GetBufferBlock[reader, buffer, maxLength].endOfNode;
IF endOfNode THEN EXIT;
maxLength ← maxLength - buffer.length;
IF buffer.next = NIL THEN buffer.next ← ObtainBuffer[NextPieceSize[buffer.maxLength]];
buffer ← buffer.next;
buffer.length ← 0;
ENDLOOP;
ReleaseBuffer[buffer.next];
buffer.next ← NIL;
};
Peek: PUBLIC PROC [reader: Reader] RETURNS [CharWithLooks] ~ {
IF reader.putback # NIL THEN {
buf: Buffer ← reader.putback.first;
RETURN [buf[buf.length-1]];
}
ELSE IF RopeReader.GetIndex[reader.ropeReader] = reader.nodeLength THEN {
RETURN [[char: Ascii.CR, endOfNode: TRUE, looks: ALL[FALSE]]]
}
ELSE {
RETURN [[char: RopeReader.Peek[reader.ropeReader], endOfNode: FALSE, looks: LooksReader.Peek[reader.looksReader]]]
};
};
PeekRope: PUBLIC PROC [reader: Reader] RETURNS [rope: ROPENIL] ~ {
IF reader.putback # NIL THEN {
Putback stuff is harder than all the rest!
s: IO.STREAMIO.ROS[];
FOR bl: LIST OF Buffer ← reader.putback, bl.rest UNTIL bl = NIL DO
buf: Buffer ← bl.first;
FOR i: NAT DECREASING IN [0..buf.length) DO
candl: CharWithLooks ← buf[i];
IF candl.endOfNode THEN RETURN [IO.RopeFromROS[s]];
IO.PutChar[s, candl.char];
ENDLOOP;
ENDLOOP;
rope ← IO.RopeFromROS[s];
};
rope ← Rope.Concat[rope, Rope.Substr[reader.ropeReader.GetRope, reader.ropeReader.GetIndex]];
};
PeekRuns: PROC [reader: Reader] RETURNS [runs: TextLooks.Runs ← NIL] ~ {
WITH reader.textNode SELECT FROM
t: REF TextNode.Body.text => {
start: INT ← reader.ropeReader.GetIndex;
end: INT ← Rope.Length[t.rope];
IF t.runs # NIL THEN runs ← TextLooks.Substr[t.runs, start, end-start];
};
ENDCASE => NULL;
};
SkipToNextNode: PUBLIC PROC [reader: Reader] ~ {
deltaLevel: INT;
IF reader.end THEN Error[client, "Attempt to read past end of document"];
reader.textNodeIndex ← reader.textNodeIndex + NodeSize[reader.textNode];
[reader.textNode, deltaLevel] ← TextNode.Forward[reader.textNode];
reader.nodeInfo.level ← reader.nodeInfo.level + deltaLevel;
reader.nodeInfo.props ← NIL;
reader.end ← (reader.nodeInfo.level = 0);
IF reader.end THEN NULL
ELSE StartNewNode[reader];
};
Full: PROC [buffer: Buffer] RETURNS [BOOLEAN]
~ INLINE {RETURN [buffer.length = buffer.maxLength]};
InlineAppend: PROC [buffer: Buffer, charWithLooks: CharWithLooks]
~ INLINE {buffer[(buffer.length ← buffer.length+1)-1] ← charWithLooks};
PutBack: PUBLIC PROC [reader: Reader, charWithLooks: CharWithLooks] ~ {
IF reader.putback = NIL OR Full[reader.putback.first] THEN {
buf: Buffer ← ObtainBuffer[100];
buf.length ← 0;
reader.putback ← CONS[buf, reader.putback];
};
InlineAppend[reader.putback.first, charWithLooks];
};
PutBackBuffer: PUBLIC PROC [reader: Reader, buffer: Buffer, start: INT, maxLength: INT] ~ {
end: NAT ← start + MIN[buffer.length-start, maxLength];
FOR i: INT IN [start..end) DO
PutBack[reader, Fetch[buffer, i]];
ENDLOOP;
};
DiscardPutBack: PROC [reader: Reader] ~ {
UNTIL reader.putback = NIL DO
t: LIST OF Buffer ← reader.putback.rest;
reader.putback.rest ← NIL;
reader.putback ← t;
ENDLOOP;
};
DoneWith: PUBLIC PROC [reader: Reader] ~ {
IF reader.root # NIL THEN {
TEditInput.FreeTree[reader.root]; reader.root ← reader.textNode ← NIL;
RopeReader.FreeRopeReader[reader.ropeReader]; reader.ropeReader ← NIL;
LooksReader.FreeLooksReader[reader.looksReader]; reader.looksReader ← NIL;
reader.nodeInfo ← [format: NIL, comment: FALSE, level: 0, props: NIL];
DiscardPutBack[reader];
};
};
Writer: TYPE ~ REF WriterRep;
WriterRep: PUBLIC TYPE ~ RECORD [
root: REF TextNode.TextBody,
first: TextNode.Ref,
last: TextNode.Ref,
lastLevel: INT,
nodeInfo: NodeInfo,
ropes: ARRAY [0..ropePieces) OF ROPE,
The contents of the current node is represented by the concatenation of ropes in order by decreasing index, together with the contents of textBuf. The size of ropes[i] is either 0 or approximately textBufSize*2**i; building the rope this way yields a balanced rope without a lot of flattening.
textBuf: REF TEXT,
runs: TextLooks.Runs,
runsSize: INT,
newRunLooks: TextLooks.Looks,
newRunSize: INT
];
Create: PUBLIC PROC RETURNS [Writer] ~ {
new: Writer ← NEW[WriterRep];
new.root ← NIL;
new.first ← new.last ← NIL;
new.lastLevel ← 0;
new.nodeInfo ← [format: NIL, comment: FALSE, level: 0, props: NIL];
new.textBuf ← NEW[TEXT[textBufSize]];
new.textBuf.length ← 0;
new.ropes ← ALL[NIL];
new.runs ← NIL;
new.runsSize ← 0;
new.newRunLooks ← ALL[FALSE];
new.newRunSize ← 0;
SafeStorage.EnableFinalization[new];
RETURN [new];
};
InsertRoot: PROC [writer: Writer, x: REF TextNode.Body.text] ~ {
IF Rope.Length[x.rope] # 0 THEN Error[client, "Root may not contain text"];
x.last ← TRUE;
writer.root ← x;
writer.last ← x;
};
CreateChainFromRoot: PROC [writer: Writer] ~ {
Creates root and descendants with no siblings, just to get the levels right.
writer.root ← TextNode.NewTextNode[];
writer.root.last ← TRUE;
writer.last ← writer.root;
writer.lastLevel ← 0;
UNTIL writer.nodeInfo.level = writer.lastLevel + 1 DO
InsertChild[writer, TextNode.NewTextNode[]];
ENDLOOP;
};
RunSize: PROC [runs: TextLooks.Runs] RETURNS [INT] ~ {
For debugging using the golldurn INLINE
RETURN [TextLooks.Size[runs]]
};
InsertSibling: PROC [writer: Writer, new: TextNode.Ref] ~ {
x: TextNode.Ref ~ writer.last;
new.next ← x.next;
new.last ← x.last;
x.next ← new;
x.last ← FALSE;
writer.last ← new;
};
InsertChild: PROC [writer: Writer, new: TextNode.Ref] ~ {
x: TextNode.Ref ~ writer.last;
IF x.child # NIL THEN ERROR;
new.next ← x;
new.last ← TRUE;
x.child ← new;
writer.last ← new;
writer.lastLevel ← writer.lastLevel + 1;
};
FoldText: PROC [writer: Writer] ~ {
FoldRope[writer, Rope.FromRefText[writer.textBuf]];
writer.textBuf.length ← 0;
};
FoldRope: PROC [writer: Writer, rope: ROPE] ~ {
i: NAT ← 0;
writer.textBuf.length ← 0;
DO
IF writer.ropes[i] = NIL THEN {writer.ropes[i] ← rope; EXIT};
rope ← Rope.Concat[writer.ropes[i], rope];
writer.ropes[i] ← NIL;
IF i < ropePieces-1 THEN i ← i + 1;
ENDLOOP;
};
GetRope: PROC [writer: Writer] RETURNS [rope: ROPENIL] ~ {
FoldText[writer];
FOR i: NAT IN [0..ropePieces) DO
IF writer.ropes[i] # NIL THEN {
rope ← Rope.Concat[writer.ropes[i], rope];
writer.ropes[i] ← NIL;
};
ENDLOOP;
};
FoldRuns: PROC [writer: Writer] ~ {
IF writer.newRunSize > 0 THEN writer.runs ← TextLooks.Concat[base: writer.runs, rest: TextLooks.CreateRun[writer.newRunSize, writer.newRunLooks], baseLen: writer.runsSize, restLen: writer.newRunSize];
writer.runsSize ← writer.runsSize + writer.newRunSize;
writer.newRunSize ← 0;
};
GetRuns: PROC [writer: Writer] RETURNS [runs: TextLooks.Runs] ~ {
FoldRuns[writer];
runs ← writer.runs;
writer.runs ← NIL;
writer.runsSize ← 0;
};
Put: PUBLIC PROC [writer: Writer, charWithLooks: CharWithLooks] ~ {
IF writer.nodeInfo.level < 0 THEN Error[client, "Level may not be negative"];
IF charWithLooks.endOfNode THEN {
new: REF TextNode.Body.text ← TextNode.NewTextNode[];
new.typename ← NameSymbolTable.MakeNameFromRope[writer.nodeInfo.format];
new.comment ← writer.nodeInfo.comment;
new.rope ← GetRope[writer];
new.runs ← GetRuns[writer];
FOR l: Atom.PropList ← writer.nodeInfo.props, l.rest UNTIL l = NIL DO
key: ATOM ~ NARROW[l.first.key];
rope: ROPE ~ NARROW[l.first.val];
NodeProps.PutProp[new, key, GetInternalProp[key, rope]];
ENDLOOP;
IF writer.root = NIL AND writer.nodeInfo.level = 0 THEN InsertRoot[writer, new]
ELSE {
IF writer.nodeInfo.level < 1 THEN Error[client, "Invalid nesting level"];
IF writer.root = NIL THEN CreateChainFromRoot[writer];
WHILE writer.nodeInfo.level < writer.lastLevel DO
writer.last ← TextNode.Parent[writer.last];
writer.lastLevel ← writer.lastLevel - 1;
ENDLOOP;
IF writer.nodeInfo.level = writer.lastLevel THEN InsertSibling[writer, new]
ELSE IF writer.nodeInfo.level = writer.lastLevel + 1 THEN InsertChild[writer, new]
ELSE Error[client, "Nesting level may not increase by more than one"];
};
IF writer.first = NIL THEN writer.first ← new;
writer.runsSize ← 0;
}
ELSE {
text: REF TEXT ~ writer.textBuf;
IF charWithLooks.looks # writer.newRunLooks THEN {
FoldRuns[writer];
writer.newRunLooks ← charWithLooks.looks;
};
writer.newRunSize ← writer.newRunSize + 1;
IF text.length = text.maxLength THEN FoldText[writer];
text[(text.length ← text.length+1)-1] ← charWithLooks.char;
};
};
GetNodeRefs: PUBLIC PROC [reader: Reader] RETURNS [root, current: REF, offset: INT] ~ {
root ← reader.root;
current ← reader.textNode;
offset ← reader.ropeReader.GetIndex;
};
CopyNode: PUBLIC PROC [writer: Writer, reader: Reader, maxLength: INT, copyInfo: BOOLEAN] RETURNS [endOfNode: BOOLEAN] ~ {
textRope: ROPENIL;
textRuns: TextLooks.Runs ← NIL;
textSize: INT ← 0;
IF copyInfo THEN PutNodeInfo[writer, GetNodeInfo[reader]];
WHILE maxLength > 0 AND reader.putback # NIL DO
candl: CharWithLooks ← Get[reader];
Put[writer, candl];
maxLength ← maxLength - 1;
IF candl.endOfNode THEN RETURN [TRUE];
ENDLOOP;
FoldText[writer];
FoldRuns[writer];
textRope ← PeekRope[reader];
textSize ← Rope.Length[textRope];
textRuns ← PeekRuns[reader];
endOfNode ← textSize < maxLength;
IF NOT endOfNode THEN {
textRope ← Rope.Substr[textRope, 0, maxLength];
textSize ← Rope.Length[textRope];
IF textRuns # NIL THEN textRuns ← TextLooks.Substr[textRuns, 0, textSize];
};
FoldRope[writer, textRope];
writer.runs ← TextLooks.Concat[base: writer.runs, rest: textRuns, baseLen: writer.runsSize, restLen: textSize];
writer.runsSize ← writer.runsSize + textSize;
RopeReader.BumpIndex[reader.ropeReader, textSize];
LooksReader.BumpIndex[reader.looksReader, textSize];
IF endOfNode THEN Put[writer, Get[reader]];
};
PutNodeInfo: PUBLIC PROC [writer: Writer, nodeInfo: NodeInfo] ~ {
writer.nodeInfo ← nodeInfo;
};
PutBuffer: PUBLIC PROC [writer: Writer, buffer: Buffer, start: INT, maxLength: INT] ~ {
WHILE buffer#NIL AND start >= buffer.length DO
start ← start - buffer.length;
buffer ← buffer.next;
ENDLOOP;
WHILE buffer#NIL AND maxLength > 0 DO
n: INTMIN[maxLength, buffer.length-start];
FOR i: NAT IN [NAT[start]..NAT[start+n]) DO
Put[writer, buffer[i]];
ENDLOOP;
maxLength ← maxLength - n;
start ← 0;
buffer ← buffer.next;
ENDLOOP;
};
SetSelectionFromSpan: PROC [tSel: TEditDocument.Selection, span: TextNode.Span] ~ {
IF span.start.node = span.end.node THEN {
IF (span.start.where = TextNode.NodeItself) # (span.end.where = TextNode.NodeItself) THEN ERROR; -- someone made a bogus span.
IF span.start.where = TextNode.NodeItself THEN {
span is exactly one node.
span.start.where ← 0;
span.end.where ← MAX[NodeSize[span.end.node]-1, 0];
tSel.granularity ← node;
}
ELSE {
part of a node
tSel.granularity ← MIN[tSel.granularity, word];
IF span.start.where > span.end.where THEN {
last before first means a point selection
tSel.granularity ← point;
span.end.where ← span.start.where;
};
};
}
ELSE {
crosses multiple nodes
IF span.start.where = TextNode.NodeItself AND span.end.where = TextNode.NodeItself THEN {
includes all entire nodes.
tSel.granularity ← MAX[tSel.granularity, node];
}
ELSE {
includes partial nodes.
tSel.granularity ← char;
};
IF span.start.where = TextNode.NodeItself THEN
span.start.where ← 0;
IF span.end.where = TextNode.NodeItself THEN
span.end.where ← MAX[NodeSize[span.end.node]-1, 0];
};
Now span has been sanitized of NodeItself flag, with the information (mostly) relayed to tSel.granularity. All that's left is to plug in the locations.
tSel.start.pos ← span.start;
tSel.end.pos ← span.end;
};
NodeSize: PROC [textNode: TextNode.Ref] RETURNS [INT] ~ {
WITH textNode SELECT FROM
t: REF TextNode.Body.text => RETURN [Rope.Length[t.rope]];
ENDCASE => RETURN [0];
};
FinishOff: PROC [writer: Writer] ~ {
Collects all the pieces together.
IF writer.ropes#ALL[NIL] OR writer.textBuf.length>0 THEN Put[writer, [Ascii.CR, TRUE, ALL[FALSE]]];
};
WriteFile: PUBLIC PROC [writer: Writer, fileName: ROPE] ~ {
FinishOff[writer];
[] ← PutGet.ToFile[fileName, writer.root];
Reset[writer];
};
WriteOpenFile: PUBLIC PROC [writer: Writer, openFile: FS.OpenFile] ~ {
FinishOff[writer];
[] ← PutGet.ToFileC[openFile, writer.root];
Reset[writer];
};
WriteSelection: PUBLIC PROC [writer: Writer] ~ {
FinishOff[writer];
IF writer.root = NIL THEN TEditInputOps.Delete[]
ELSE {
success: BOOLFALSE;
lastSize: INT ← NodeSize[writer.last];
startOffset: INTIF lastSize=0 AND writer.first=writer.last THEN TextNode.NodeItself ELSE 0;
endOffset: INTIF lastSize=0 THEN TextNode.NodeItself ELSE lastSize-1;
alpha: TextNode.Span ← [start: [node: writer.first, where: startOffset], end: [node: writer.last, where: endOffset]];
DoTranspose: PROC [root: TextNode.Ref, tSel: TEditDocument.Selection] ~ {
beta: TextNode.Span ← SelectionSpan[tSel];
documentRoot: TextNode.Ref ← TextNode.Root[beta.start.node];
IF tSel.granularity = node OR tSel.granularity = branch THEN {
alpha.start.where ← alpha.end.where ← TextNode.NodeItself;
};
success ← TRUE;
[alpha, beta] ← EditSpan.Transpose[alphaRoot: writer.root, betaRoot: documentRoot, alpha: alpha, beta: beta, words: FALSE, event: TEditInput.currentEvent ! EditSpan.CannotDoEdit => {success ← FALSE; CONTINUE}];
IF success THEN {
tSel.pendingDelete ← FALSE;
SetSelectionFromSpan[tSel, alpha];
TEditSelection.MakeSelection[new: tSel, selection: primary];
};
};
TEditInputOps.CallWithLocks[DoTranspose, write];
IF NOT success THEN {
MessageWindow.Append["Can't do it.", TRUE];
MessageWindow.Blink[];
};
};
Reset[writer];
};
WriteViewer: PUBLIC PROC [writer: Writer, viewer: ViewerClasses.Viewer] ~ {
FinishOff[writer];
IF viewer # NIL AND viewer.data # NIL AND viewer.class.set # NIL THEN {
viewer.class.set[viewer, writer.root, FALSE, $TiogaDocument];
writer.root ← NIL;
ViewerOps.PaintViewer[viewer, all];
};
Reset[writer];
};
WriteReader: PUBLIC PROC [writer: Writer] RETURNS [reader: Reader] ~ {
FinishOff[writer];
reader ← FromNode[writer.first];
writer.root ← NIL;
Reset[writer];
};
Reset: PUBLIC PROC [writer: Writer] ~ {
IF writer.root # NIL THEN {
TEditInput.FreeTree[writer.root];
writer.root ← NIL;
};
writer.first ← writer.last ← NIL;
writer.lastLevel ← 0;
writer.nodeInfo ← [format: NIL, comment: FALSE, level: 0, props: NIL];
writer.newRunLooks ← ALL[FALSE];
writer.newRunSize ← 0;
};
finalizationQueue: SafeStorage.FinalizationQueue ← SafeStorage.NewFQ[];
FinalizationProcess: PROCEDURE ~ {
DO
WITH SafeStorage.FQNext[finalizationQueue] SELECT FROM
reader: Reader => DoneWith[reader];
writer: Writer => Reset[writer];
ENDCASE => NULL;
ENDLOOP;
};
TRUSTED {
SafeStorage.EstablishFinalization[ReaderRep.CODE, 0, finalizationQueue ! SafeStorage.CantEstablishFinalization => GOTO Exit];
SafeStorage.EstablishFinalization[WriterRep.CODE, 0, finalizationQueue ! SafeStorage.CantEstablishFinalization => GOTO Exit];
Process.Detach[FORK FinalizationProcess];
EXITS Exit => NULL;
};
END.