DIRECTORY
Atom USING [DottedPairNode, GetPName, PropList],
EditSpan USING [CannotDoEdit, Transpose],
EditSpanSupport USING [CopySpan],
FS USING [ErrorGroup, OpenFile],
IO USING [PutChar, RopeFromROS, ROS, STREAM],
LooksReader USING [Body, BumpIndex, FreeLooksReader, Get, GetLooksReader, Peek, Ref, SetIndex, SetPosition],
MessageWindow USING [Append, Blink],
NodeProps USING [CopyInfo, DoSpecs, GetProp, GetSpecs, MapProps, PutProp],
NodeStyleOps USING [StyleNameForNode],
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],
Rosary USING [Concat, FromItem, ROSARY, Size, Substr],
SafeStorage USING [CantEstablishFinalization, EnableFinalization, EstablishFinalization, FinalizationQueue, FQNext, NewFQ],
TEditDocument USING [Selection, TEditDocumentData],
TEditInput USING [currentEvent, FreeTree],
TEditInputOps USING [CallWithLocks, Delete],
TEditSelection USING [GetSelectionGrain, MakeSelection],
TextEdit USING [ChangeStyle, CharSet, Fetch, GetCharPropList, Size],
TextLooks USING [Concat, CreateRun, Looks, Runs, Substr],
TextNode USING [FirstChild, Forward, LastLocWithin, LastWithin, Level, Location, LocOffset, LocWithin, MakeNodeLoc, MakeNodeSpan, NewTextNode, NodeItself, Parent, Ref, Root, Span],
TiogaAccess USING [CharSet, TiogaChar],
TiogaAccessPrivate USING [Reader, ReaderRep, ropePieces, Writer, WriterRep],
ViewerClasses USING [Viewer],
ViewerOps USING [PaintViewer];
TiogaAccessImpl:
CEDAR PROGRAM
IMPORTS Atom, EditSpan, EditSpanSupport, IO, LooksReader, MessageWindow, NodeProps, NodeStyleOps, Process, PutGet, Rope, RopeReader, Rosary, SafeStorage, TEditInput, TEditInputOps, TEditSelection, TextEdit, TextLooks, TextNode, ViewerOps
EXPORTS TiogaAccess
~ BEGIN OPEN TiogaAccess;
ROPE: TYPE ~ Rope.ROPE;
ROSARY:
TYPE ~ Rosary.
ROSARY;
Reader: TYPE ~ TiogaAccessPrivate.Reader;
ReaderRep: PUBLIC TYPE ~ TiogaAccessPrivate.ReaderRep;
ropePieces: NAT ~ TiogaAccessPrivate.ropePieces;
textBufSize:
NAT ← (574-
SIZE[
TEXT[0]])*2;
Largest size that will come from the small-grain allocator (see Allocator.maxSmallBlockSize)
Error:
PUBLIC
ERROR [group:
FS.ErrorGroup, expl:
ROPE] ~
CODE;
GetExternalProp:
PUBLIC
PROC [key:
ATOM, value:
REF]
RETURNS [
ROPE] ~ {
RETURN [NodeProps.GetSpecs[key, value]]
};
GetInternalProp:
PUBLIC
PROC [key:
ATOM, value:
ROPE]
RETURNS [
REF] ~ {
RETURN [NodeProps.DoSpecs[key, value]]
};
MakePropList:
PROC [textNode: TextNode.Ref]
RETURNS [props: Atom.PropList ←
NIL] ~ {
Action:
PROC [name:
ATOM, value:
REF]
RETURNS [
BOOLEAN ←
FALSE] ~ {
IF name = $CharSets OR name = $CharProps THEN RETURN;
value ← NodeProps.CopyInfo[name: name, value: value];
IF value #
NIL
THEN {
props ← CONS[NEW[Atom.DottedPairNode ← [key: name, val: value]], props];
};
};
[] ← NodeProps.MapProps[n: textNode, action: Action, formatFlag: FALSE, commentFlag: FALSE];
};
StartNewNode:
PROC [reader: Reader, offset:
INT ← 0] ~ {
IF reader.textNode =
NIL
THEN {
reader.nodeLength ← 0;
reader.format ← NIL;
reader.comment ← FALSE;
reader.nodeProps ← NIL;
reader.end ← TRUE;
RopeReader.SetPosition[reader.ropeReader, NIL, 0];
LooksReader.SetPosition[reader.looksReader, NIL, 0];
}
ELSE {
t: TextNode.Ref ~ reader.textNode;
reader.format ← t.formatName;
reader.nodeProps ← MakePropList[t];
reader.nodeLength ← Rope.Length[t.rope];
reader.comment ← t.comment;
reader.end ← FALSE;
RopeReader.SetPosition[reader.ropeReader, t.rope, offset];
LooksReader.SetPosition[reader.looksReader, t.runs, offset];
};
};
InitIndex:
PROC [reader: Reader] ~ {
reader.textNodeIndex ← TextNode.LocOffset[loc1: [reader.root, 0], loc2: [reader.textNode, 0], skipCommentNodes: FALSE];
};
FromNode:
PROC [textNode: TextNode.Ref, offset:
INT ←
INT.
FIRST, style:
ROPE ←
NIL]
RETURNS [Reader] ~ {
new: Reader ~ NEW[ReaderRep];
new.root ← TextNode.Root[textNode];
IF style # NIL THEN TextEdit.ChangeStyle[new.root, style];
new.textNode ← textNode;
new.level ← TextNode.Level[textNode];
new.ropeReader ← RopeReader.GetRopeReader[];
new.looksReader ← LooksReader.GetLooksReader[];
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]];
};
TakeNodeSubstr:
PROC [t: TextNode.Ref, start:
INT, length:
INT] = {
t.rope ← Rope.Substr[t.rope, start, length];
t.runs ← TextLooks.Substr[t.runs, start, length];
IF t.hascharsets
THEN {
WITH NodeProps.GetProp[t, $CharSets]
SELECT
FROM
r:
ROSARY => {
NodeProps.PutProp[t, $CharSets, Rosary.Substr[r, start, length]];
};
ENDCASE => NULL;
};
IF t.hascharprops
THEN {
WITH NodeProps.GetProp[t, $CharProps]
SELECT
FROM
r:
ROSARY => {
NodeProps.PutProp[t, $CharProps, Rosary.Substr[r, start, length]];
};
ENDCASE => NULL;
};
};
FromSelection:
PUBLIC
PROC
RETURNS [Reader] ~ {
textNode: TextNode.Ref ← NIL;
myOffset: INT ← 0;
style: Rope.ROPE ← NIL;
CopySelection:
PROC [root: TextNode.Ref, tSel: TEditDocument.Selection] ~ {
style ← Atom.GetPName[NodeStyleOps.StyleNameForNode[root]];
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
AND copy.end.node #
NIL
THEN {
TakeNodeSubstr[copy.end.node, 0, copy.end.where+1];
};
IF copy.start.where > 0
THEN {
TakeNodeSubstr[copy.start.node, copy.start.where, INT.LAST];
copy.start.where ← 0;
};
textNode ← copy.start.node;
};
};
TEditInputOps.CallWithLocks[CopySelection, read];
RETURN [FromNode[textNode, myOffset, style]]
};
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 [TiogaChar] ~ {
IF reader.putback #
NIL
THEN {
t: LIST OF TiogaChar ~ reader.putback;
reader.putback ← t.rest;
t.rest ← NIL;
RETURN [t.first];
}
ELSE IF reader.end THEN {ERROR Error[client, "Attempt to read past end of document"]}
ELSE
IF RopeReader.GetIndex[reader.ropeReader] = reader.nodeLength
THEN {
t: TiogaChar ← [
charSet: 0,
char: '\n,
looks: ALL[FALSE],
format: reader.format,
comment: reader.comment,
endOfNode: TRUE,
deltaLevel: 0,
propList: reader.nodeProps
];
t.deltaLevel ← SkipToNextNode[reader];
RETURN [t];
}
ELSE {
offset: INT ~ reader.ropeReader.GetIndex;
t: TiogaChar ~ [
charSet: (
IF reader.textNode.hascharsets
THEN TextEdit.Fetch[reader.textNode, offset].charSet
ELSE 0),
char: RopeReader.Get[reader.ropeReader],
looks: LooksReader.Get[reader.looksReader],
format: reader.format,
comment: reader.comment,
endOfNode: FALSE,
deltaLevel: 0,
propList: (
IF reader.textNode.hascharprops
THEN TextEdit.GetCharPropList[reader.textNode, offset]
ELSE NIL)
];
RETURN [t]
};
};
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+TextEdit.Size[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, IF index = 0 THEN [reader.root, 0] ELSE 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];
};
GetNodeProps:
PUBLIC
PROC [reader: Reader]
RETURNS [Atom.PropList] ~ {
RETURN [reader.nodeProps]
};
Peek:
PUBLIC
PROC [reader: Reader]
RETURNS [TiogaChar] ~ {
IF reader.putback #
NIL
THEN {
RETURN [reader.putback.first];
}
ELSE
IF RopeReader.GetIndex[reader.ropeReader] = reader.nodeLength
THEN {
t: TiogaChar ← [
charSet: 0,
char: '\n,
looks: ALL[FALSE],
format: reader.format,
comment: reader.comment,
endOfNode: TRUE,
deltaLevel: 0,
propList: reader.nodeProps
];
t.deltaLevel ← TextNode.Forward[reader.textNode].levelDelta;
RETURN [t];
}
ELSE {
offset: INT ~ reader.ropeReader.GetIndex;
t: TiogaChar ~ [
charSet: (
IF reader.textNode.hascharsets
THEN TextEdit.Fetch[reader.textNode, offset].charSet
ELSE 0),
char: RopeReader.Peek[reader.ropeReader],
looks: LooksReader.Peek[reader.looksReader],
format: reader.format,
comment: reader.comment,
endOfNode: FALSE,
deltaLevel: 0,
propList: (
IF reader.textNode.hascharprops
THEN TextEdit.GetCharPropList[reader.textNode, offset]
ELSE NIL)
];
RETURN [t];
};
};
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 t:
LIST
OF TiogaChar ← reader.putback, t.rest
UNTIL t =
NIL
DO
IF t.first.endOfNode THEN RETURN [IO.RopeFromROS[s]];
IO.PutChar[s, t.first.char];
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] ~ {
t: TextNode.Ref ~ reader.textNode;
start: INT ~ reader.ropeReader.GetIndex;
end: INT ~ Rope.Length[t.rope];
IF t.runs # NIL THEN runs ← TextLooks.Substr[t.runs, start, end-start];
};
PeekCharSets:
PROC [reader: Reader]
RETURNS [charSets:
ROSARY] ~ {
t: TextNode.Ref ~ reader.textNode;
start: INT ~ reader.ropeReader.GetIndex;
end: INT ~ Rope.Length[t.rope];
IF t.hascharsets
THEN charSets ← Rosary.Substr[NARROW[NodeProps.GetProp[t, $CharSets]], start, end-start]
ELSE charSets ← Rosary.FromItem[NIL, end-start];
};
PeekCharProps:
PROC [reader: Reader]
RETURNS [charProps:
ROSARY] ~ {
t: TextNode.Ref ~ reader.textNode;
start: INT ~ reader.ropeReader.GetIndex;
end: INT ~ Rope.Length[t.rope];
IF t.hascharprops
THEN charProps ← Rosary.Substr[NARROW[NodeProps.GetProp[t, $CharProps]], start, end-start]
ELSE charProps ← Rosary.FromItem[NIL, end-start];
};
SkipToNextNode:
PUBLIC
PROC [reader: Reader]
RETURNS [deltaLevel:
INT] ~ {
IF reader.end THEN Error[client, "Attempt to read past end of document"];
reader.textNodeIndex ← reader.textNodeIndex + TextEdit.Size[reader.textNode] + 1;
[reader.textNode, deltaLevel] ← TextNode.Forward[reader.textNode];
reader.level ← reader.level + deltaLevel;
reader.end ← (reader.level = 0);
IF reader.end THEN NULL ELSE StartNewNode[reader];
};
PutBack:
PUBLIC
PROC [reader: Reader, tiogaChar: TiogaChar] ~ {
reader.putback ← CONS[tiogaChar, reader.putback];
};
DiscardPutBack:
PROC [reader: Reader] ~ {
UNTIL reader.putback =
NIL
DO
t: LIST OF TiogaChar ← 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.format ← NIL;
reader.nodeProps ← NIL;
DiscardPutBack[reader];
};
};
Writer: TYPE ~ TiogaAccessPrivate.Writer;
WriterRep:
PUBLIC
TYPE ~ TiogaAccessPrivate.WriterRep;
Create:
PUBLIC
PROC
RETURNS [Writer] ~ {
new: Writer ← NEW[WriterRep];
new.textBuf ← NEW[TEXT[textBufSize]];
Reset[new];
SafeStorage.EnableFinalization[new];
RETURN [new];
};
InsertRoot:
PROC [writer: Writer, x: TextNode.Ref] ~ {
IF Rope.Length[x.rope] # 0 THEN Error[client, "Root may not contain text"];
x.last ← TRUE;
writer.root ← x;
writer.last ← x;
writer.lastLevel ← 0;
};
CreateOneMoreLevel:
PROC [writer: Writer] ~ {
IF writer.root =
NIL
THEN {
InsertRoot[writer, TextNode.NewTextNode[]];
writer.level ← writer.level + 1;
}
ELSE {
new: TextNode.Ref ← TextNode.NewTextNode[];
new^ ← writer.root^;
writer.root.props ← NIL;
writer.root.hasstyledef ← writer.root.hasprefix ← writer.root.haspostfix ← writer.root.hascharprops ← writer.root.hascharsets ← writer.root.hasartwork ← FALSE;
new.last ← TRUE;
new.child ← writer.root;
new.next ← NIL;
writer.root.next ← new;
writer.root ← new;
writer.lastLevel ← writer.lastLevel + 1;
writer.level ← writer.level + 1;
};
};
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.newLooksRepeat > 0 THEN writer.runs ← TextLooks.Concat[base: writer.runs, rest: TextLooks.CreateRun[writer.newLooksRepeat, writer.newLooks], baseLen: writer.nodeSize-writer.newLooksRepeat, restLen: writer.newLooksRepeat];
writer.newLooksRepeat ← 0;
writer.newLooks ← ALL[FALSE];
};
GetRuns:
PROC [writer: Writer]
RETURNS [runs: TextLooks.Runs] ~ {
FoldRuns[writer];
runs ← writer.runs;
writer.runs ← NIL;
};
FoldCharSets:
PROC [writer: Writer] ~ {
IF writer.newCharSetRepeat > 0
THEN {
IF writer.charSets = NIL AND writer.newCharSet = NIL THEN NULL
ELSE
IF writer.charSets =
NIL
THEN {
writer.charSets ← Rosary.Concat[Rosary.FromItem[NIL, writer.nodeSize-writer.newCharSetRepeat], Rosary.FromItem[writer.newCharSet, writer.newCharSetRepeat]];
}
ELSE {
writer.charSets ← Rosary.Concat[writer.charSets, Rosary.FromItem[writer.newCharSet, writer.newCharSetRepeat]];
};
};
writer.newCharSet ← NIL;
writer.newCharSetRepeat ← 0;
};
GetCharSets:
PROC [writer: Writer]
RETURNS [charSets:
ROSARY] ~ {
FoldCharSets[writer];
charSets ← writer.charSets;
writer.charSets ← NIL;
writer.newCharSet ← NIL;
writer.newCharSetRepeat ← 0;
IF Rosary.Size[charSets] = 0 THEN charSets ← NIL;
};
FoldCharProps:
PROC [writer: Writer] ~ {
IF writer.newCharPropRepeat > 0
THEN {
IF writer.charProps = NIL AND writer.newCharProp = NIL THEN NULL
ELSE
IF writer.charProps =
NIL
THEN {
writer.charProps ← Rosary.Concat[Rosary.FromItem[NIL, writer.nodeSize-writer.newCharPropRepeat], Rosary.FromItem[writer.newCharProp, writer.newCharPropRepeat]];
}
ELSE {
writer.charProps ← Rosary.Concat[writer.charProps, Rosary.FromItem[writer.newCharProp, writer.newCharPropRepeat]];
};
};
writer.newCharProp ← NIL;
writer.newCharPropRepeat ← 0;
};
GetCharProps:
PROC [writer: Writer]
RETURNS [charProps:
ROSARY] ~ {
FoldCharProps[writer];
charProps ← writer.charProps;
writer.charProps ← NIL;
writer.newCharProp ← NIL;
writer.newCharPropRepeat ← 0;
IF Rosary.Size[charProps] = 0 THEN charProps ← NIL;
};
Put:
PUBLIC
PROC [writer: Writer, tiogaChar: TiogaChar] ~ {
IF tiogaChar.endOfNode
THEN {
new: TextNode.Ref ← TextNode.NewTextNode[];
new.formatName ← tiogaChar.format;
new.comment ← tiogaChar.comment;
new.rope ← GetRope[writer];
new.runs ← GetRuns[writer];
FOR l: Atom.PropList ← tiogaChar.propList, l.rest
UNTIL l =
NIL
DO
key: ATOM ~ NARROW[l.first.key];
NodeProps.PutProp[new, key, l.first.val];
ENDLOOP;
NodeProps.PutProp[new, $CharProps, GetCharProps[writer]];
NodeProps.PutProp[new, $CharSets, GetCharSets[writer]];
IF writer.root = NIL AND writer.level=0 AND tiogaChar.deltaLevel=1 AND Rope.Length[new.rope] = 0 THEN InsertRoot[writer, new]
ELSE {
WHILE writer.level < 1
DO
CreateOneMoreLevel[writer];
ENDLOOP;
WHILE writer.level < writer.lastLevel
DO
writer.last ← TextNode.Parent[writer.last];
writer.lastLevel ← writer.lastLevel - 1;
ENDLOOP;
IF writer.level = writer.lastLevel THEN InsertSibling[writer, new]
ELSE IF writer.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.nodeSize ← 0;
IF writer.lastLevel # writer.level THEN ERROR;
writer.level ← writer.lastLevel + tiogaChar.deltaLevel;
}
ELSE {
text: REF TEXT ~ writer.textBuf;
IF tiogaChar.looks # writer.newLooks
THEN {
FoldRuns[writer];
writer.newLooks ← tiogaChar.looks;
};
writer.newLooksRepeat ← writer.newLooksRepeat + 1;
IF
NOT SameCharSet[tiogaChar.charSet, writer.newCharSet]
THEN {
FoldCharSets[writer];
IF tiogaChar.charSet # 0 THEN writer.newCharSet ← NEW[CharSet ← tiogaChar.charSet];
};
writer.newCharSetRepeat ← writer.newCharSetRepeat + 1;
IF tiogaChar.propList # writer.newCharProp
THEN {
FoldCharProps[writer];
writer.newCharProp ← tiogaChar.propList;
};
writer.newCharPropRepeat ← writer.newCharPropRepeat + 1;
IF text.length = text.maxLength THEN FoldText[writer];
text[(text.length ← text.length+1)-1] ← tiogaChar.char;
writer.nodeSize ← writer.nodeSize + 1;
};
};
SameCharSet:
PROC [a: CharSet, b:
REF CharSet]
RETURNS [
BOOL] ~
INLINE {
IF b = NIL THEN RETURN [a=0]
ELSE RETURN [a=b^]
};
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]
RETURNS [nodeEnd:
BOOLEAN] ~ {
textRope: ROPE ← NIL;
textRuns: TextLooks.Runs ← NIL;
textCharSets: ROSARY ← NIL;
textCharProps: ROSARY ← NIL;
textSize: INT ← 0;
WHILE maxLength > 0
AND reader.putback #
NIL
DO
tchar: TiogaChar ← Get[reader];
Put[writer, tchar];
maxLength ← maxLength - 1;
IF tchar.endOfNode THEN RETURN [TRUE];
ENDLOOP;
FoldText[writer];
FoldRuns[writer];
FoldCharSets[writer];
FoldCharProps[writer];
textRope ← PeekRope[reader];
textSize ← Rope.Length[textRope];
textRuns ← PeekRuns[reader];
IF writer.charSets # NIL OR reader.textNode.hascharsets THEN textCharSets ← PeekCharSets[reader];
IF writer.charProps # NIL OR reader.textNode.hascharprops THEN textCharProps ← PeekCharProps[reader];
nodeEnd ← textSize < maxLength;
IF
NOT nodeEnd
THEN {
textRope ← Rope.Substr[textRope, 0, maxLength];
textSize ← Rope.Length[textRope];
IF textRuns # NIL THEN textRuns ← TextLooks.Substr[textRuns, 0, textSize];
IF textCharSets # NIL THEN textCharSets ← Rosary.Substr[textCharSets, 0, textSize];
IF textCharProps # NIL THEN textCharSets ← Rosary.Substr[textCharProps, 0, textSize];
};
FoldRope[writer, textRope];
writer.runs ← TextLooks.Concat[base: writer.runs, rest: textRuns, baseLen: writer.nodeSize, restLen: textSize];
IF textCharSets #
NIL
AND writer.charSets =
NIL
THEN {
writer.charSets ← Rosary.FromItem[NIL, writer.nodeSize];
};
writer.charSets ← Rosary.Concat[writer.charSets, textCharSets];
IF textCharProps #
NIL
AND writer.charProps =
NIL
THEN {
writer.charProps ← Rosary.FromItem[NIL, writer.nodeSize];
};
writer.charProps ← Rosary.Concat[writer.charProps, textCharProps];
RopeReader.BumpIndex[reader.ropeReader, textSize];
LooksReader.BumpIndex[reader.looksReader, textSize];
writer.nodeSize ← writer.nodeSize + textSize;
IF nodeEnd THEN Put[writer, Get[reader]];
};
Nest:
PUBLIC
PROC [writer: Writer, delta:
INT] ~ {
level: INT ~ writer.level + delta;
IF level > writer.lastLevel+1 THEN Error[client, "Nesting level may not increase by more than one"];
writer.level ← level;
};
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[TextEdit.Size[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 {
tSel.granularity ← MAX[tSel.granularity, char];
}
ELSE {
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[TextEdit.Size[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;
};
FinishOff:
PROC [writer: Writer] ~ {
Collects all the pieces together.
IF writer.ropes#
ALL[
NIL]
OR writer.textBuf.length>0
THEN Put[writer, [
charSet: 0,
char: '\n,
looks: ALL[FALSE],
format: NIL,
comment: FALSE,
endOfNode: TRUE,
deltaLevel: 0,
propList: NIL
]];
};
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 ← TextEdit.Size[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, 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.level ← 0;
writer.lastLevel ← 0;
writer.nodeSize ← 0;
writer.ropes ← ALL[NIL];
writer.textBuf.length ← 0;
writer.runs ← NIL;
writer.newLooks ← ALL[FALSE];
writer.newLooksRepeat ← 0;
writer.charSets ← NIL;
writer.newCharSet ← NIL;
writer.newCharSetRepeat ← 0;
writer.charProps ← NIL;
writer.newCharProp ← NIL;
writer.newCharPropRepeat ← 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.