TiogaAccessImpl.mesa
Copyright (C) 1985, Xerox Corporation. All rights reserved.
Michael Plass, May 16, 1985 8:47:48 am PDT
Tim Diebert May 13, 1985 12:06:39 pm PDT
Russ Atkinson (RRA) July 2, 1985 11:08:52 am PDT
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],
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 [BOOLEANFALSE] ~ {
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;
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;
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: INTINT.FIRST, style: ROPENIL] 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[];
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]];
};
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.ROPENIL;
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+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];
};
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: TextNode.Level[reader.textNode.next]-TextNode.Level[reader.textNode],
propList: reader.nodeProps
];
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: ROPENIL] ~ {
IF reader.putback # NIL THEN {
Putback stuff is harder than all the rest!
s: IO.STREAMIO.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 + NodeSize[reader.textNode];
[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: 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.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: ROPENIL;
textRuns: TextLooks.Runs ← NIL;
textCharSets: ROSARYNIL;
textCharProps: ROSARYNIL;
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];
};
textCharSets ← Rosary.Concat[writer.charSets, textCharSets];
IF textCharProps # NIL AND writer.charProps = NIL THEN {
writer.charProps ← Rosary.FromItem[NIL, writer.nodeSize];
};
textCharProps ← 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[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 {
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[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] ~ {
RETURN [Rope.Length[textNode.rope]];
};
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: 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, 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.