DIRECTORY
EditNotify USING [Change, Notify],
EditSpan USING [ChangeNesting, Event, Location, Looks, Nest, NodeOrder, nullLocation, nullSpan, Place, Ref, RefTextNode, Span, UnNest],
EditSpanSupport USING [Apply, BadBand, CompareSliceOrder, CopySpan, CreateDest, DeletePrefix, DescribeBand, DestSlices, DoSplits, DoSplits2, FreeSlice, LastOfSlice, NeedNestingChange, NodeItself, NodeLoc, NodeSpan, ReMerge, ReplaceBand, Slice, SliceLength, SliceOrder, Splice, UndoSplits, UndoSplits2],
NodeProps USING [CopyInfo, MapProps, PutProp],
NodePropsExtras USING [Is],
TextEdit USING [CapChange, ChangeCaps, ChangeLooks, CopyText, DeleteText, DestSpanProc, MoveText, MoveTextOnto, ReplaceText, TransposeProc, TransposeText, TwoSpanProc],
TextNode USING [LastSibling, LastWithin, Level, MakeNodeLoc, MakeNodeSpan, NarrowToTextNode, NewTextNode, Parent, Previous, Root, StepBackward, StepForward],
UndoEvent USING [Note];
***** Editing operations on spans
CheckForNil:
PROC [span: Span]
RETURNS [
BOOLEAN] =
INLINE {
RETURN [span.start.node = NIL OR span.end.node = NIL]
};
Replace:
PUBLIC
PROC [destRoot, sourceRoot: Ref, dest, source: Span, saveForPaste:
BOOLEAN ←
TRUE, event: Event ←
NIL]
RETURNS [result: Span] = {
replace dest span by copy of source span
result is the new copy of source
newDest: Span;
IF TextSpans[dest, source]
THEN {
-- pure text replace
RETURN [TwoSpanText[destRoot, sourceRoot, dest, source, saveForPaste, TextEdit.ReplaceText, event]]
};
source ← EditSpanSupport.CopySpan[source];
sourceRoot ← TextNode.Root[source.start.node];
sourceRoot.deleted ← TRUE;
[result, newDest] ← Transpose[sourceRoot, destRoot, source, dest, event];
IF saveForPaste THEN SaveSpanForPaste[newDest, event]
};
TextSpans:
PROC [dest, source: Span]
RETURNS [
BOOLEAN] = {
RETURN [dest.start.node = dest.end.node
AND
dest.start.where # EditSpanSupport.NodeItself AND dest.end.where # EditSpanSupport.NodeItself AND
source.start.node = source.end.node AND
source.start.where # EditSpanSupport.NodeItself AND source.end.where # EditSpanSupport.NodeItself]
};
TwoSpanText:
PROC [destRoot, sourceRoot: Ref, dest, source: Span,
saveForPaste: BOOLEAN,
textProc: TextEdit.TwoSpanProc, event: Event]
RETURNS [result: Span] = {
destNode: RefTextNode ← TextNode.NarrowToTextNode[dest.start.node];
sourceNode: RefTextNode ← TextNode.NarrowToTextNode[source.start.node];
destStart: INT ← dest.start.where;
destLen: INT ← dest.end.where - destStart + 1;
sourceStart: INT ← source.start.where;
sourceLen: INT ← source.end.where - sourceStart + 1;
resultStart, resultLen: INT;
IF saveForPaste THEN SaveTextForPaste[destNode, destStart, destLen, event];
[resultStart, resultLen] ← textProc[destRoot, sourceRoot,
destNode, destStart, destLen, sourceNode, sourceStart, sourceLen, event];
RETURN [[[destNode, resultStart], [destNode, resultStart+resultLen-1]]]
};
SaveForPaste:
PUBLIC
PROC [span: Span, event: Event ←
NIL] = {
IF CheckForNil[span] THEN RETURN;
IF span.start.node = span.end.node AND
span.start.where # EditSpanSupport.NodeItself
AND span.end.where # EditSpanSupport.NodeItself
THEN {
-- pure text
node: RefTextNode ← TextNode.NarrowToTextNode[span.start.node];
start: INT ← span.start.where;
len: INT ← span.end.where-span.start.where+1;
SaveTextForPaste[node, start, len, event]
}
ELSE SaveSpanForPaste[EditSpanSupport.CopySpan[span], event]
};
Delete:
PUBLIC
PROC [root: Ref, del: Span, event: Event ←
NIL, saveForPaste:
BOOLEAN ←
TRUE] = {
IF CheckForNil[del] THEN RETURN;
IF del.start.node = del.end.node AND
del.start.where # EditSpanSupport.NodeItself
AND del.end.where # EditSpanSupport.NodeItself
THEN {
-- pure text
node: RefTextNode ← TextNode.NarrowToTextNode[del.start.node];
start: INT ← del.start.where;
len: INT ← del.end.where-del.start.where+1;
IF saveForPaste THEN SaveTextForPaste[node, start, len, event];
TextEdit.DeleteText[root, node, start, len, event]
}
ELSE {
d: Span ← MoveToLimbo[root, del, event];
IF saveForPaste THEN SaveSpanForPaste[d, event]
}
};
paste: REF Change.ChangingSpanForPaste;
SaveTextForPaste:
PROC [node: RefTextNode, start, len:
INT, event: Event] = {
span: Span ~ IF len = 0 THEN nullSpan ELSE [[node, start], [node, start+len-1]];
SaveSpanForPaste[EditSpanSupport.CopySpan[span], event];
};
SaveSpanForPaste:
PROC [span: Span, event: Event] = {
SaveOldPaste[event];
paste ← NEW[Change.ChangingSpanForPaste ← [ChangingSpanForPaste[span]]];
};
SaveOldPaste:
PROC [event: Event] = {
UndoEvent.Note[event, RestorePaste, paste]; paste ← NIL
};
RestorePaste:
PROC [undoRef:
REF Change, currentEvent: Event] = {
SaveOldPaste[currentEvent]; paste ← NARROW[undoRef]
};
SavedForPaste:
PUBLIC
PROC
RETURNS [span: Span] = {
savedPaste: REF Change ← paste;
IF savedPaste=NIL THEN RETURN [nullSpan];
WITH savedPaste
SELECT
FROM
x: REF Change.ChangingSpanForPaste => RETURN [x.span];
ENDCASE => ERROR;
};
MoveToLimbo:
PROC [root: Ref, span: Span, event: Event]
RETURNS [result: Span] = {
RETURN [Move[NIL, root, nullLocation, span, after, 1, event]]
};
Copy:
PUBLIC
PROC [destRoot, sourceRoot: Ref, dest: Location, source: Span,
where: Place ← after, nesting: INTEGER ← 0, event: Event ← NIL]
RETURNS [result: Span] = {
result is the new copy of source
IF CheckForNil[source] OR dest.node=NIL THEN RETURN [nullSpan];
IF TextLocationAndSpan[dest, source] THEN -- pure text copy
RETURN [DestSpanText[destRoot, sourceRoot, dest, source, TextEdit.CopyText, event]];
source ← EditSpanSupport.CopySpan[source];
sourceRoot ← TextNode.Root[source.start.node];
sourceRoot.deleted ← TRUE;
result ← Move[destRoot, sourceRoot, dest, source, where, nesting, event]
};
TextLocationAndSpan:
PROC [dest: Location, source: Span]
RETURNS [
BOOLEAN] = {
RETURN [dest.where # EditSpanSupport.NodeItself
AND
source.start.node = source.end.node AND
source.start.where # EditSpanSupport.NodeItself AND source.end.where # EditSpanSupport.NodeItself]
};
DestSpanText: PROC [destRoot, sourceRoot: Ref, dest: Location, source: Span, textProc: TextEdit.DestSpanProc, event: Event]
RETURNS [result: Span] = {
destNode: RefTextNode ← TextNode.NarrowToTextNode[dest.node];
sourceNode: RefTextNode ← TextNode.NarrowToTextNode[source.start.node];
destLoc: INT ← dest.where;
sourceStart: INT ← source.start.where;
sourceLen: INT ← source.end.where - sourceStart + 1;
resultStart, resultLen: INT;
[resultStart, resultLen] ← textProc[destRoot, sourceRoot,
destNode, destLoc, sourceNode, sourceStart, sourceLen, event];
RETURN [[[destNode, resultStart], [destNode, resultStart+resultLen-1]]]
};
Move:
PUBLIC
PROC [destRoot, sourceRoot: Ref, dest: Location, source: Span,
where: Place ← after, nesting: INTEGER ← 0, event: Event ← NIL]
RETURNS [result: Span] = {
dest cannot be within source
result is moved span
sBefore, sAfter, sTop, sBottom, dBefore, dAfter: EditSpanSupport.Slice;
sNesting, dNesting: INTEGER;
sDepth: NAT;
beforeSource, afterSource: Ref; -- nodes adjacent to source after do splits
afterLoc: Location;
FreeSlices:
PROC = {
EditSpanSupport.FreeSlice[sBefore]; EditSpanSupport.FreeSlice[sAfter]; EditSpanSupport.FreeSlice[sTop]; EditSpanSupport.FreeSlice[sBottom];
EditSpanSupport.FreeSlice[dBefore]; EditSpanSupport.FreeSlice[dAfter];
sBefore ← sAfter ← sTop ← sBottom ← dBefore ← dAfter ← NIL
};
ForcedUnNest:
PROC [afterNode: Ref] = {
span: Span = TextNode.MakeNodeSpan[afterNode, TextNode.LastWithin[afterNode]];
FreeSlices[];
[] ← UnNest[TextNode.Root[afterNode], span, event]
};
ForcedNest:
PROC = {
span: Span ← TextNode.MakeNodeSpan[sTop[sDepth], TextNode.LastWithin[sBottom[sDepth]]];
IF EditSpanSupport.SliceLength[sTop] = sDepth+1
THEN {
-- can't do it by calling Nest
IF nesting >= 1 THEN ERROR;
nesting ← nesting+1; -- move to a deeper position
FreeSlices[];
RETURN
};
FreeSlices[];
[] ← Nest[TextNode.Root[span.start.node], span, event]
};
IF CheckForNil[source] THEN RETURN [nullSpan];
IF TextLocationAndSpan[dest, source] THEN -- pure text move
RETURN [DestSpanText[destRoot, sourceRoot,
dest, source, TextEdit.MoveText, event]];
IF where=child THEN { where ← after; nesting ← nesting+1 }
ELSE
IF where=sibling
THEN {
newDest: Ref = TextNode.LastWithin[dest.node];
where ← after;
nesting ← nesting + TextNode.Level[dest.node] - TextNode.Level[newDest];
dest ← [newDest, EditSpanSupport.NodeItself];
};
split source and dest, if necessary, so can deal with entire nodes
[dest, source, where, nesting] ← EditSpanSupport.DoSplits2[dest, source, where, nesting, event];
beforeSource ← TextNode.StepBackward[source.start.node];
afterSource ← TextNode.StepForward[source.end.node];
afterLoc ← [afterSource, 0];
{
-- for exits
check for dest already in correct position
IF dest # nullLocation AND
((where = after
AND (dest.node = beforeSource
OR dest.node = source.end.node))
OR (where = before AND (dest.node = afterSource OR dest.node = source.start.node)))
THEN {
-- not going anywhere, but might be changing nesting
IF nesting > 0 THEN
FOR i:
INTEGER
IN [0..nesting)
DO
[] ← Nest[sourceRoot, EditSpanSupport.NodeSpan[source], event];
ENDLOOP
ELSE IF nesting < 0 THEN
FOR i:
INTEGER
IN [nesting..0)
DO
[] ← UnNest[sourceRoot, EditSpanSupport.NodeSpan[source], event];
ENDLOOP
}
ELSE
DO
-- repeat this loop only if have forced nest or unnest or source and dest in same tree
check for dest inside source
[sBefore, sAfter, sTop, sBottom, sNesting, sDepth] ←
EditSpanSupport.DescribeBand[source.start.node, source.end.node ! EditSpanSupport.BadBand =>
{ source ← nullSpan; GOTO ErrorReturn } ];
IF dest = nullLocation
THEN {
-- moving to limbo
dest ← EditSpanSupport.CreateDest[EditSpanSupport.SliceLength[sTop]-sDepth];
destRoot ← TextNode.Root[dest.node];
destRoot.deleted ← TRUE; -- so will free this when it falls off the edit history list
where ← after; nesting ← 1
};
[dBefore, dAfter, dNesting] ← EditSpanSupport.DestSlices[dest.node, where];
IF EditSpanSupport.CompareSliceOrder[dBefore, sBefore]=after AND
EditSpanSupport.CompareSliceOrder[dBefore, sBottom]=before THEN GOTO ErrorReturn;
dest inside source
IF dBefore[0] = sBefore[0]
THEN {
-- source and dest in same tree
span: Span = TextNode.MakeNodeSpan[source.start.node, source.end.node];
FreeSlices[];
[] ← MoveToLimbo[sourceRoot, span, event];
LOOP
};
dNesting ← dNesting + nesting;
SELECT EditSpanSupport.NeedNestingChange[dBefore, dAfter, sTop, sBottom, dNesting, sDepth]
FROM
needUnNest => { ForcedUnNest[EditSpanSupport.LastOfSlice[dAfter]]; LOOP };
needNest => { ForcedNest[]; LOOP };
ENDCASE;
IF EditSpanSupport.SliceLength[sAfter] > EditSpanSupport.SliceLength[sBefore]+1
THEN {
ForcedUnNest[EditSpanSupport.LastOfSlice[sAfter]]; LOOP
}
ELSE {
-- do it
notify: REF MovingNodes Change;
notify ←
NEW[MovingNodes Change ← [MovingNodes[
destRoot, sourceRoot, dest.node, source.start.node, source.end.node,
EditSpanSupport.LastOfSlice[sBefore], sNesting, (where # before)]]];
EditNotify.Notify[notify, before];
EditSpanSupport.DeletePrefix[sTop, sDepth]; EditSpanSupport.DeletePrefix[sBottom, sDepth];
EditSpanSupport.ReplaceBand[dBefore, dAfter, sTop, sBottom, dNesting, event];
EditSpanSupport.Splice[sBefore, sAfter];
FreeSlices[];
EditNotify.Notify[notify, after];
UndoEvent.Note[event, UndoMoveNodes, notify]
};
EXIT;
ENDLOOP;
IF dest.where # EditSpanSupport.NodeItself
THEN {
-- undo prior splits
start, end: BOOLEAN ← FALSE;
IF source.start.where # EditSpanSupport.NodeItself
THEN {
-- merge start of source with front of dest
start ← TRUE;
[source, ] ← EditSpanSupport.ReMerge[source, nullSpan, source.start.node, event]
};
IF source.end.where # EditSpanSupport.NodeItself
THEN {
-- merge end of source with tail of dest
end ← TRUE;
[source, ] ← EditSpanSupport.ReMerge[source, nullSpan, source.end.node, event, TRUE]
};
IF start
AND end
THEN {
-- merge before source with after source
afterLoc ← Merge[TextNode.Root[afterSource], afterSource, event]
}
}
ELSE
IF source.start.where # EditSpanSupport.NodeItself
AND source.end.where # EditSpanSupport.NodeItself
THEN {
afterLoc ← Merge[TextNode.Root[afterSource], afterSource, event]
};
afterMoved2 ← afterMoved1; -- save previous hint
afterMoved1 ← IF afterSource=NIL THEN [beforeSource, 0] ELSE afterLoc; -- hint for repaint
RETURN [source];
EXITS ErrorReturn => {
FreeSlices[];
[, source] ← EditSpanSupport.UndoSplits2[dest, source, event];
ERROR CannotDoEdit
}
}
};
UndoMoveNodes:
PROC [undoRef:
REF, currentEvent: Event] = {
saved: REF Change ← NARROW[undoRef];
WITH saved
SELECT
FROM
x:
REF Change.MovingNodes => {
[] ← Move[x.sourceRoot, x.destRoot,
TextNode.MakeNodeLoc[x.pred], TextNode.MakeNodeSpan[x.first, x.last],
after, x.nesting, currentEvent]
};
ENDCASE => ERROR
};
Transpose:
PUBLIC
PROC [alphaRoot, betaRoot: Ref,
alpha, beta: Span, event: Event ← NIL]
RETURNS [newAlpha, newBeta: Span] = {
newAlpha is new location of alpha span; ditto for newBeta
aBefore, aAfter, aTop, aBottom, bBefore, bAfter, bTop, bBottom: EditSpanSupport.Slice;
aNesting, bNesting: INTEGER;
aDepth, bDepth: NAT;
beforeAlpha, afterAlpha, beforeBeta, afterBeta: Ref; -- nodes adjacent after do splits
afterAlphaLoc, afterBetaLoc: Location;
FreeSlices:
PROC = {
EditSpanSupport.FreeSlice[aBefore]; EditSpanSupport.FreeSlice[aAfter]; EditSpanSupport.FreeSlice[aTop]; EditSpanSupport.FreeSlice[aBottom];
EditSpanSupport.FreeSlice[bBefore]; EditSpanSupport.FreeSlice[bAfter]; EditSpanSupport.FreeSlice[bTop]; EditSpanSupport.FreeSlice[bBottom];
aBefore ← aAfter ← aTop ← aBottom ← bBefore ← bAfter ← bTop ← bBottom ← NIL
};
{
-- for exit
IF CheckForNil[alpha] OR CheckForNil[beta] THEN
RETURN [nullSpan, nullSpan];
IF TextSpans[alpha, beta]
THEN {
-- pure text transpose
alphaNode: RefTextNode ← TextNode.NarrowToTextNode[alpha.start.node];
betaNode: RefTextNode ← TextNode.NarrowToTextNode[beta.start.node];
alphaStart: INT ← alpha.start.where;
alphaLen: INT ← alpha.end.where - alphaStart + 1;
betaStart: INT ← beta.start.where;
betaLen: INT ← beta.end.where - betaStart + 1;
alphaResultStart, alphaResultLen, betaResultStart, betaResultLen: INT;
[alphaResultStart, alphaResultLen, betaResultStart, betaResultLen] ← TextEdit.TransposeText[
alphaRoot, betaRoot, alphaNode, alphaStart, alphaLen, betaNode, betaStart, betaLen, event];
newAlpha ← [[betaNode, alphaResultStart], [betaNode, alphaResultStart+alphaResultLen-1]];
newBeta ← [[alphaNode, betaResultStart], [alphaNode, betaResultStart+betaResultLen-1]];
RETURN
};
[alpha, beta] ← EditSpanSupport.DoSplits[alpha, beta, event]; -- so can deal with entire nodes
beforeAlpha ← TextNode.StepBackward[alpha.start.node];
afterAlpha ← TextNode.StepForward[alpha.end.node];
afterAlphaLoc ← [afterAlpha, 0];
beforeBeta ← TextNode.StepBackward[beta.start.node];
afterBeta ← TextNode.StepForward[beta.end.node];
afterBetaLoc ← [afterBeta, 0];
now check for alpha beta adjacent or overlapping as special cases
IF afterAlpha = beta.start.node
THEN {
-- alpha just before beta
move beta nodes to before alpha nodes
[] ← Move[alphaRoot, betaRoot, EditSpanSupport.NodeLoc[alpha.start], EditSpanSupport.NodeSpan[beta], before, 0, event]
}
ELSE
IF afterBeta = alpha.start.node
THEN {
-- beta just before alpha
move alpha nodes to before beta nodes
[] ← Move[betaRoot, alphaRoot, EditSpanSupport.NodeLoc[beta.start], EditSpanSupport.NodeSpan[alpha], before, 0, event]
}
ELSE {
-- get slices describing the bands of nodes to be transposed
overlap: BOOLEAN;
head, tail: Span; -- sections of alpha or beta before and after the overlap
startOrder, endOrder: NodeOrder;
[aBefore, aAfter, aTop, aBottom, aNesting, aDepth] ←
EditSpanSupport.DescribeBand[alpha.start.node, alpha.end.node ! EditSpanSupport.BadBand =>
{ alpha ← beta ← nullSpan; GOTO ErrorReturn }];
[bBefore, bAfter, bTop, bBottom, bNesting, bDepth] ←
EditSpanSupport.DescribeBand[beta.start.node, beta.end.node ! EditSpanSupport.BadBand =>
{ alpha ← beta ← nullSpan; GOTO ErrorReturn }];
check for overlap
[overlap, head, tail, startOrder, endOrder] ←
EditSpanSupport.SliceOrder[alpha, beta, aBefore, aBottom, bBefore, bBottom];
IF overlap
THEN {
-- bands overlap
FreeSlices[];
IF head = nullSpan AND tail = nullSpan THEN NULL
ELSE
IF head = nullSpan
THEN {
--move tail to before alphastart
[] ← Move[alphaRoot, betaRoot,
EditSpanSupport.NodeLoc[alpha.start], EditSpanSupport.NodeSpan[tail], before, 0, event];
IF endOrder=before
THEN {
-- alpha end before beta end
beta.start ← tail.start; beta.end ← alpha.end
}
ELSE {
-- beta end before alpha end
alpha.start ← tail.start; alpha.end ← beta.end
}
}
ELSE
IF tail = nullSpan
THEN {
--move head to after alphaend
[] ← Move[alphaRoot, betaRoot,
EditSpanSupport.NodeLoc[alpha.end], EditSpanSupport.NodeSpan[head], after, 0, event];
IF startOrder=before
THEN {
-- alpha start before beta start
alpha.start ← beta.start; alpha.end ← head.end
}
ELSE {
-- beta start before alpha start
beta.start ← alpha.start; beta.end ← head.end
}
}
ELSE IF startOrder # endOrder THEN NULL -- one contained in the other
ELSE {
--transpose head and tail
[] ← Transpose[alphaRoot, betaRoot,
EditSpanSupport.NodeSpan[head], EditSpanSupport.NodeSpan[tail], event];
IF startOrder=before
THEN {
-- alpha start before beta start
alpha.start ← beta.start; alpha.end ← head.end;
beta.start ← tail.start; beta.end ← alpha.end
}
ELSE {
-- beta start before alpha start
beta.start ← alpha.start; beta.end ← head.end;
alpha.start ← tail.start; alpha.end ← beta.end
}
}
}
ELSE {
-- do transpose as two moves
aSpan, bSpan: Span;
after1, after2: Location;
bLoc: Location ← TextNode.MakeNodeLoc[EditSpanSupport.LastOfSlice[bBefore]];
aLoc: Location ← TextNode.MakeNodeLoc[EditSpanSupport.LastOfSlice[aBefore]];
FreeSlices[];
aSpan ← TextNode.MakeNodeSpan[alpha.start.node, alpha.end.node];
bSpan ← TextNode.MakeNodeSpan[beta.start.node, beta.end.node];
[] ← MoveToLimbo[alphaRoot, aSpan, event]; after1 ← afterMoved1; -- repaint hints
[] ← MoveToLimbo[betaRoot, bSpan, event]; after2 ← afterMoved1;
[] ← Move[betaRoot, TextNode.Root[aSpan.start.node],
bLoc, aSpan, after, bNesting, event];
[] ← Move[alphaRoot, TextNode.Root[bSpan.start.node],
aLoc, bSpan, after, aNesting, event];
afterMoved1 ← after1; afterMoved2 ← after2
}
};
IF alpha.start.where # EditSpanSupport.NodeItself
AND beta.start.where # EditSpanSupport.NodeItself
THEN {
-- remerge starts
[alpha, beta] ← EditSpanSupport.ReMerge[alpha, beta, alpha.start.node, event];
[alpha, beta] ← EditSpanSupport.ReMerge[alpha, beta, beta.start.node, event]
};
IF alpha.end.where # EditSpanSupport.NodeItself
AND beta.end.where # EditSpanSupport.NodeItself
THEN {
-- remerge ends
[alpha, beta] ← EditSpanSupport.ReMerge[alpha, beta, alpha.end.node, event, TRUE];
[alpha, beta] ← EditSpanSupport.ReMerge[alpha, beta, beta.end.node, event, TRUE];
afterAlphaLoc ← beta.end; afterBetaLoc ← alpha.end
};
afterMoved1 ← IF afterAlphaLoc.node=NIL THEN [beforeAlpha, 0] ELSE afterAlphaLoc; -- hint for repaint
afterMoved2 ← IF afterBetaLoc.node=NIL THEN [beforeBeta, 0] ELSE afterBetaLoc;
RETURN [alpha, beta];
EXITS ErrorReturn => {
FreeSlices[]; [alpha, beta] ← EditSpanSupport.UndoSplits[alpha, beta, event];
ERROR CannotDoEdit
}
}
};
MoveOnto:
PUBLIC
PROC [destRoot, sourceRoot: Ref, dest, source: Span,
saveForPaste: BOOLEAN ← TRUE, event: Event ← NIL]
RETURNS [result: Span] = {
like Replace, but moves source instead of copying it
result is moved span
overlap: BOOLEAN;
head, tail, newDest: Span; -- sections of alpha or beta before and after the overlap
startOrder, endOrder: NodeOrder;
aBefore, aAfter, aTop, aBottom, bBefore, bAfter, bTop, bBottom: EditSpanSupport.Slice;
aNesting, bNesting: INTEGER;
aDepth, bDepth: NAT;
FreeSlices:
PROC = {
EditSpanSupport.FreeSlice[aBefore]; EditSpanSupport.FreeSlice[aAfter]; EditSpanSupport.FreeSlice[aTop]; EditSpanSupport.FreeSlice[aBottom];
EditSpanSupport.FreeSlice[bBefore]; EditSpanSupport.FreeSlice[bAfter]; EditSpanSupport.FreeSlice[bTop]; EditSpanSupport.FreeSlice[bBottom];
aBefore ← aAfter ← aTop ← aBottom ← bBefore ← bAfter ← bTop ← bBottom ← NIL
};
{
-- for exit
IF CheckForNil[source] OR CheckForNil[dest] THEN RETURN [nullSpan];
IF TextSpans[dest, source]
THEN {
-- pure text move
RETURN [TwoSpanText[destRoot, sourceRoot, dest, source, saveForPaste,
TextEdit.MoveTextOnto, event]]
};
[dest, source] ← EditSpanSupport.DoSplits[dest, source, event];
[aBefore, aAfter, aTop, aBottom, aNesting, aDepth] ←
EditSpanSupport.DescribeBand[dest.start.node, dest.end.node ! EditSpanSupport.BadBand =>
{ dest ← source ← nullSpan; GOTO ErrorReturn }];
[bBefore, bAfter, bTop, bBottom, bNesting, bDepth] ←
EditSpanSupport.DescribeBand[source.start.node, source.end.node ! EditSpanSupport.BadBand =>
{ dest ← source ← nullSpan; GOTO ErrorReturn }];
get slices for dest and source
[overlap, head, tail, startOrder, endOrder] ←
EditSpanSupport.SliceOrder[dest, source, aBefore, aBottom, bBefore, bBottom];
FreeSlices[];
check for overlap
IF overlap
THEN {
-- bands overlap. modify dest so doesn't overlap
IF head = nullSpan AND tail = nullSpan THEN GOTO ErrorReturn;
IF head = nullSpan
THEN {
-- source start = dest start
IF endOrder=before THEN GOTO ErrorReturn -- dest end before source end
ELSE dest.start ← tail.start
}
ELSE
IF tail = nullSpan
THEN {
--source end = dest end
IF startOrder=before THEN
dest.end ← head.end
-- dest start before source start
ELSE GOTO ErrorReturn
} -- source start before dest start
ELSE {
-- have both head and tail
IF startOrder=before
AND endOrder=after
THEN {
[] ← Delete[destRoot, tail, event]; dest.end ← head.end
}
ELSE
IF startOrder=before
THEN dest.end ← head.end
ELSE IF endOrder=after THEN dest.start ← tail.start
ELSE GOTO ErrorReturn
}
};
[dest, source] ← EditSpanSupport.UndoSplits[dest, source, event];
source ← MoveToLimbo[sourceRoot, source, event];
sourceRoot ← TextNode.Root[source.start.node];
[result, newDest] ← Transpose[sourceRoot, destRoot, source, dest, event];
IF saveForPaste THEN SaveSpanForPaste[newDest, event];
RETURN;
EXITS ErrorReturn => {
[dest, source] ← EditSpanSupport.UndoSplits[dest, source, event]; ERROR CannotDoEdit
}
}
};
***** New nodes; split & merge
Insert:
PUBLIC
PROC [root, old: Ref, where: Place ← after, event: Event ←
NIL]
RETURNS [new: Ref] = {
empty copy of old node is inserted in tree in position determined by "where"
IF old=NIL THEN RETURN [NIL];
new ← TextNode.NewTextNode[];
Inherit[old, new];
DoInsertNode[root, old, new, where, event]
};
InsertTextNode:
PUBLIC
PROC [root, old: Ref, where: Place ← after, inherit:
BOOLEAN ←
FALSE, event: Event ←
NIL]
RETURNS [new: RefTextNode] = {
empty text node is inserted in tree
IF old=NIL THEN RETURN [NIL];
new ← TextNode.NewTextNode[];
IF inherit THEN Inherit[old, new];
DoInsertNode[root, old, new, where, event]
};
Inherit:
PUBLIC
PROC [old, new: Ref, allprops:
BOOL ←
FALSE] = {
CopyProp:
PROC [name:
ATOM, value:
REF]
RETURNS [
BOOL] = {
IF allprops
OR NodePropsExtras.Is[name, $Inheritable]
THEN {
newvalue: REF ~ NodeProps.CopyInfo[name, value];
IF newvalue # NIL THEN NodeProps.PutProp[new, name, newvalue];
};
RETURN [FALSE];
};
new.formatName ← old.formatName;
IF allprops OR old.hasprefix OR old.haspostfix OR old.hasstyledef THEN
[] ← NodeProps.MapProps[old, CopyProp, FALSE, FALSE];
IF new # NIL AND old # NIL THEN new.comment ← old.comment;
};
DoInsertNode:
PROC [root, old, new: Ref, where: Place, event: Event] = {
dest, parent: Ref;
child: BOOLEAN;
notify: REF InsertingNode Change;
IF new = NIL OR old = NIL THEN RETURN;
parent ← IF where = child THEN old ELSE TextNode.Parent[old];
IF where = sibling THEN { dest ← TextNode.LastWithin[old]; child ← FALSE }
ELSE IF where = after THEN { dest ← old; child ← FALSE }
ELSE IF where = child THEN { dest ← old; child ← TRUE }
ELSE {
IF parent.child = old THEN { dest ← parent; child ← TRUE }
ELSE { dest ← TextNode.LastWithin[TextNode.Previous[old, parent]]; child ← FALSE }
};
notify ← NEW[InsertingNode Change ← [InsertingNode[root, new, dest]]];
EditNotify.Notify[notify, before];
IF child
THEN {
-- insert as first child of dest
IF dest.child # NIL THEN { new.next ← dest.child; new.last ← FALSE }
ELSE { new.next ← dest; new.last ← TRUE };
dest.child ← new
}
ELSE
IF where = sibling
THEN {
-- insert as next sibling of old; don't adopt children
new.next ← old.next; new.last ← old.last;
old.last ← FALSE; old.next ← new
}
ELSE {
-- insert as next sibling of dest
new.next ← dest.next; new.last ← dest.last;
dest.last ← FALSE; dest.next ← new;
IF where = after
AND (new.child ← dest.child) #
NIL
THEN {
-- adopt dest's children
TextNode.LastSibling[new.child].next ← new;
dest.child ← NIL
}
};
EditNotify.Notify[notify, after];
UndoEvent.Note[event, UndoInsertNode, notify]
};
UndoInsertNode:
PROC [undoRef:
REF, currentEvent: Event] = {
saved: REF Change ← NARROW[undoRef];
WITH saved
SELECT
FROM
x: REF Change.InsertingNode => { [] ← Delete[x.root, TextNode.MakeNodeSpan[x.new, x.new], currentEvent] };
ENDCASE => ERROR
};
Split:
PUBLIC
PROC [root: Ref, loc: Location, event: Event ←
NIL]
RETURNS [new: Ref] = {
inserts copy of loc.node is inserted directly after loc.node (as sibling)
new adopts children of old (if any)
if loc.where # NodeItself and loc.node is a text node, then
text after loc.where moves to new node
text before loc.where stays in old node
returns the new node
old: Ref ← loc.node;
oldTxt, newTxt: RefTextNode;
IF old=NIL THEN RETURN [NIL];
new ← Insert[root, old, after, event];
IF loc.where # EditSpanSupport.NodeItself
AND (oldTxt ← TextNode.NarrowToTextNode[old]) #
NIL
THEN {
newTxt ← TextNode.NarrowToTextNode[new];
[] ← TextEdit.MoveText[destRoot: root, sourceRoot: root, dest: newTxt, destLoc: 0,
source: oldTxt, start: loc.where, event: event]
}
};
Merge:
PUBLIC
PROC [root, node: Ref, event: Event ←
NIL]
RETURNS [loc: Location] = {
copies text of node to end of previous node
then deletes node
returns location of join in the merged node
pred: RefTextNode ← TextNode.NarrowToTextNode[TextNode.StepBackward[node]];
txt: RefTextNode ← TextNode.NarrowToTextNode[node];
start: INT;
IF pred = NIL OR TextNode.Parent[pred] = NIL OR txt = NIL THEN ERROR CannotDoEdit;
[start, ] ← TextEdit.CopyText[root, root, pred, INT.LAST, txt, 0, INT.LAST, event];
[] ← Delete[root, TextNode.MakeNodeSpan[txt, txt], event];
RETURN [[pred, start]]
};