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;
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 [
BOOLEAN ←
FALSE] ~ {
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:
BOOLEAN ←
FALSE] ~ {
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:
ROPE ←
NIL] ~ {
IF reader.putback #
NIL
THEN {
Putback stuff is harder than all the rest!
s: IO.STREAM ← IO.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:
ROPE ←
NIL] ~ {
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: ROPE ← NIL;
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: INT ← MIN[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: BOOL ← FALSE;
lastSize: INT ← NodeSize[writer.last];
startOffset: INT ← IF lastSize=0 AND writer.first=writer.last THEN TextNode.NodeItself ELSE 0;
endOffset: INT ← IF 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.