-- EditSpanImpl.mesa
-- written by Bill Paxton, June 1981
-- last edit by Bill Paxton, December 23, 1982 2:47 pm
Last Edited by: Maxwell, January 5, 1983 12:46 pm
DIRECTORY
EditSpan,
EditSpanSupport,
EditNotify,
NodeProps,
Rope,
UndoEvent,
TextEdit,
TextLooks,
TextNode;
EditSpanImpl:
CEDAR PROGRAM
IMPORTS NodeProps, TextEdit, TextNode, UndoEvent, EditSpan, EditSpanSupport, EditNotify
EXPORTS EditSpan =
BEGIN
OPEN EditSpan, EditSpanSupport, EditNotify;
CannotDoEdit: PUBLIC ERROR = CODE;
afterMoved1, afterMoved2: PUBLIC Location;
-- ***** 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, words:
BOOLEAN ←
FALSE, 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,words,saveForPaste,
TextEdit.ReplaceWords,TextEdit.ReplaceText,event]] };
source ← CopySpan[source];
sourceRoot ← TextNode.Root[source.start.node];
sourceRoot.deleted ← TRUE;
[result,newDest] ← Transpose[sourceRoot,destRoot,source,dest,words,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 # NodeItself AND dest.end.where # NodeItself AND
source.start.node = source.end.node AND
source.start.where # NodeItself AND source.end.where # NodeItself] };
TwoSpanText:
PROC [destRoot, sourceRoot: Ref, dest, source: Span,
words, saveForPaste: BOOLEAN,
wordProc, textProc: TextEdit.TwoSpanProc, event: Event]
RETURNS [result: Span] = {
destNode: RefTextNode ← TextNode.NarrowToTextNode[dest.start.node];
sourceNode: RefTextNode ← TextNode.NarrowToTextNode[source.start.node];
destStart: Offset ← dest.start.where;
destLen: Offset ← dest.end.where - destStart + 1;
sourceStart: Offset ← source.start.where;
sourceLen: Offset ← source.end.where - sourceStart + 1;
resultStart, resultLen: Offset;
editProc: TextEdit.TwoSpanProc ← IF words THEN wordProc ELSE textProc;
IF saveForPaste THEN SaveTextForPaste[destNode,destStart,destLen,event];
[resultStart,resultLen] ← editProc[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 # NodeItself
AND span.end.where # NodeItself
THEN {
-- pure text
node: RefTextNode ← TextNode.NarrowToTextNode[span.start.node];
start: Offset ← span.start.where;
len: Offset ← span.end.where-span.start.where+1;
SaveTextForPaste[node,start,len,event] }
ELSE SaveSpanForPaste[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 # NodeItself
AND del.end.where # NodeItself
THEN {
-- pure text
node: RefTextNode ← TextNode.NarrowToTextNode[del.start.node];
start: Offset ← del.start.where;
len: Offset ← 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;
--really REF ChangingTextForPaste Change OR ChangingSpanForPaste Change
SaveTextForPaste:
PROC [node: RefTextNode, start, len: Offset, event: Event] = {
SaveOldPaste[event];
paste ← TextNode.pZone.
NEW[ChangingTextForPaste Change ←
[ChangingTextForPaste[node.rope, node.runs, start, len]]];
};
SaveSpanForPaste:
PROC [span: Span, event: Event] = {
SaveOldPaste[event];
paste ← TextNode.pZone.
NEW[ChangingSpanForPaste Change ←
[ChangingSpanForPaste[span]]];
};
SaveOldPaste:
PROC [event: Event] = {
UndoEvent.Note[event, RestorePaste, paste]; paste ← NIL };
RestorePaste:
PROC [undoRef:
REF Change, currentEvent: UndoEvent.Ref] = {
SaveOldPaste[currentEvent]; paste ← undoRef };
SavedForPaste:
PUBLIC
PROC
RETURNS [span: Span] =
TRUSTED {
savedPaste: REF Change ← paste;
IF savedPaste=NIL THEN RETURN [TextNode.nullSpan];
WITH x: savedPaste SELECT FROM
ChangingTextForPaste => {
-- convert saved text to a span
node: RefTextNode;
span: Span;
IF x.len <= 0 THEN RETURN [TextNode.nullSpan];
node ← TextNode.NewTextNode[];
[] ← TextEdit.ReplaceByText[root: node, dest: node,
sourceRope: x.rope, sourceRuns: x.runs,
sourceStart: x.start, sourceLen: x.len];
span ← [[node,0],[node,x.len-1]];
paste ← TextNode.pZone.
NEW[ChangingSpanForPaste Change ←
[ChangingSpanForPaste[span]]];
RETURN [span] };
ChangingSpanForPaste => RETURN [x.span];
ENDCASE => ERROR;
};
MoveToLimbo:
PROC [root: Ref, span: Span, event: Event]
RETURNS [result: Span] =
INLINE {
RETURN [Move[NIL,root,TextNode.nullLocation,span,FALSE,after,1,event]] };
Copy:
PUBLIC
PROC [destRoot, sourceRoot: Ref, dest: Location, source: Span, words:
BOOLEAN ←
FALSE,
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 [TextNode.nullSpan];
IF TextLocationAndSpan[dest,source]
THEN
-- pure text copy
RETURN [DestSpanText[destRoot,sourceRoot,dest,source,words,
TextEdit.CopyWords,TextEdit.CopyText,event]];
source ← CopySpan[source];
sourceRoot ← TextNode.Root[source.start.node];
sourceRoot.deleted ← TRUE;
result ← Move[destRoot,sourceRoot,dest,source,words,where,nesting,event] };
TextLocationAndSpan:
PROC [dest: Location, source: Span]
RETURNS [
BOOLEAN] = {
RETURN [dest.where # NodeItself
AND
source.start.node = source.end.node
AND
source.start.where # NodeItself AND source.end.where # NodeItself] };
DestSpanText:
PROC [destRoot, sourceRoot: Ref, dest: Location, source: Span, words:
BOOLEAN,
wordProc, textProc: TextEdit.DestSpanProc, event: Event]
RETURNS [result: Span] = {
destNode: RefTextNode ← TextNode.NarrowToTextNode[dest.node];
sourceNode: RefTextNode ← TextNode.NarrowToTextNode[source.start.node];
destLoc: Offset ← dest.where;
sourceStart: Offset ← source.start.where;
sourceLen: Offset ← source.end.where - sourceStart + 1;
resultStart, resultLen: Offset;
editProc: TextEdit.DestSpanProc ← IF words THEN wordProc ELSE textProc;
[resultStart,resultLen] ← editProc[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,
words: BOOLEAN ← FALSE, 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: Slice;
sNesting, dNesting: INTEGER;
sDepth: NAT;
beforeSource, afterSource: Ref; -- nodes adjacent to source after do splits
afterLoc: Location;
FreeSlices:
PROC = {
FreeSlice[sBefore]; FreeSlice[sAfter]; FreeSlice[sTop]; FreeSlice[sBottom];
FreeSlice[dBefore]; 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 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 [TextNode.nullSpan];
IF TextLocationAndSpan[dest,source]
THEN
-- pure text move
RETURN [DestSpanText[destRoot,sourceRoot,
dest,source,words,TextEdit.MoveWords,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, NodeItself];
};
-- split source and dest, if necessary, so can deal with entire nodes
[dest,source,where,nesting] ← 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 # TextNode.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,NodeSpan[source],event];
ENDLOOP
ELSE
IF nesting < 0
THEN
FOR i:
INTEGER
IN [nesting..0)
DO
[] ← UnNest[sourceRoot,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] ←
DescribeBand[source.start.node,source.end.node ! BadBand =>
{ source ← TextNode.nullSpan; GOTO ErrorReturn } ];
IF dest = TextNode.nullLocation
THEN {
-- moving to limbo
dest ← CreateDest[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] ← DestSlices[dest.node,where];
IF CompareSliceOrder[dBefore,sBefore]=after
AND
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 NeedNestingChange[dBefore,dAfter,sTop,sBottom,dNesting,sDepth]
FROM
needUnNest => { ForcedUnNest[LastOfSlice[dAfter]]; LOOP };
needNest => { ForcedNest[]; LOOP };
ENDCASE;
IF SliceLength[sAfter] > SliceLength[sBefore]+1
THEN {
ForcedUnNest[LastOfSlice[sAfter]]; LOOP }
ELSE {
-- do it
notify: REF MovingNodes Change;
notify ← TextNode.pZone.
NEW[MovingNodes Change ← [MovingNodes[
destRoot,sourceRoot,dest.node,source.start.node,source.end.node,
LastOfSlice[sBefore],sNesting,(where # before)]]];
Notify[notify,before];
DeletePrefix[sTop,sDepth]; DeletePrefix[sBottom,sDepth];
ReplaceBand[dBefore,dAfter,sTop,sBottom,dNesting,event];
Splice[sBefore,sAfter];
FreeSlices[];
Notify[notify,after];
UndoEvent.Note[event,UndoMoveNodes,notify] };
EXIT;
ENDLOOP;
IF dest.where # NodeItself
THEN {
-- undo prior splits
start, end: BOOLEAN ← FALSE;
IF source.start.where # NodeItself
THEN {
-- merge start of source with front of dest
start ← TRUE;
[source,] ← ReMerge[source,nullSpan,source.start.node,event] };
IF source.end.where # NodeItself
THEN {
-- merge end of source with tail of dest
end ← TRUE;
[source,] ← 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 # NodeItself
AND source.end.where # NodeItself
THEN {
afterLoc ← Merge[TextNode.Root[afterSource],afterSource,event] };
IF words THEN [source,] ← FixWordSpans[source,nullSpan,event];
afterMoved2 ← afterMoved1; -- save previous hint
afterMoved1 ← IF afterSource=NIL THEN [beforeSource,0] ELSE afterLoc; -- hint for repaint
RETURN [source];
EXITS ErrorReturn => {
FreeSlices[];
[,source] ← UndoSplits2[dest,source,event];
ERROR CannotDoEdit }}};
UndoMoveNodes:
PROC [undoRef:
REF, currentEvent: Event] =
TRUSTED {
saved: REF Change ← NARROW[undoRef];
WITH x:saved SELECT FROM
MovingNodes => {
[] ← Move[x.sourceRoot, x.destRoot,
TextNode.MakeNodeLoc[x.pred], TextNode.MakeNodeSpan[x.first,x.last],
FALSE, after, x.nesting, currentEvent] };
ENDCASE => ERROR };
Transpose:
PUBLIC
PROC [alphaRoot, betaRoot: Ref,
alpha, beta: Span, words: BOOLEAN ← FALSE, 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: Slice;
aNesting, bNesting: INTEGER;
aDepth, bDepth: NAT;
beforeAlpha, afterAlpha, beforeBeta, afterBeta: Ref; -- nodes adjacent after do splits
afterAlphaLoc, afterBetaLoc: Location;
FreeSlices:
PROC = {
FreeSlice[aBefore]; FreeSlice[aAfter]; FreeSlice[aTop]; FreeSlice[aBottom];
FreeSlice[bBefore]; FreeSlice[bAfter]; FreeSlice[bTop]; FreeSlice[bBottom];
aBefore ← aAfter ← aTop ← aBottom ← bBefore ← bAfter ← bTop ← bBottom ← NIL };
{ -- for exit
IF CheckForNil[alpha]
OR CheckForNil[beta]
THEN
RETURN [TextNode.nullSpan, TextNode.nullSpan];
IF TextSpans[alpha,beta]
THEN {
-- pure text transpose
alphaNode: RefTextNode ← TextNode.NarrowToTextNode[alpha.start.node];
betaNode: RefTextNode ← TextNode.NarrowToTextNode[beta.start.node];
alphaStart: Offset ← alpha.start.where;
alphaLen: Offset ← alpha.end.where - alphaStart + 1;
betaStart: Offset ← beta.start.where;
betaLen: Offset ← beta.end.where - betaStart + 1;
alphaResultStart, alphaResultLen, betaResultStart, betaResultLen: Offset;
editProc: TextEdit.TransposeProc ←
IF words THEN TextEdit.TransposeWords ELSE TextEdit.TransposeText;
[alphaResultStart,alphaResultLen,betaResultStart,betaResultLen] ← editProc[
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] ← 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,NodeLoc[alpha.start],NodeSpan[beta],FALSE,before,0,event] }
ELSE
IF afterBeta = alpha.start.node
THEN {
-- beta just before alpha
-- move alpha nodes to before beta nodes
[] ← Move[betaRoot,alphaRoot,NodeLoc[beta.start],NodeSpan[alpha],FALSE,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] ←
DescribeBand[alpha.start.node,alpha.end.node ! BadBand =>
{ alpha ← beta ← TextNode.nullSpan; GOTO ErrorReturn }];
[bBefore,bAfter,bTop,bBottom,bNesting,bDepth] ←
DescribeBand[beta.start.node,beta.end.node ! BadBand =>
{ alpha ← beta ← TextNode.nullSpan; GOTO ErrorReturn }];
-- check for overlap
[overlap,head,tail,startOrder,endOrder] ←
SliceOrder[alpha,beta,aBefore,aBottom,bBefore,bBottom];
IF overlap
THEN {
-- bands overlap
FreeSlices[];
IF head = TextNode.nullSpan AND tail = TextNode.nullSpan THEN NULL
ELSE
IF head = TextNode.nullSpan
THEN {
--move tail to before alphastart
[] ← Move[alphaRoot,betaRoot,
NodeLoc[alpha.start],NodeSpan[tail],FALSE,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 = TextNode.nullSpan
THEN {
--move head to after alphaend
[] ← Move[alphaRoot,betaRoot,
NodeLoc[alpha.end],NodeSpan[head],FALSE,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,
NodeSpan[head],NodeSpan[tail],FALSE,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[LastOfSlice[bBefore]];
aLoc: Location ← TextNode.MakeNodeLoc[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,FALSE,after,bNesting,event];
[] ← Move[alphaRoot,TextNode.Root[bSpan.start.node],
aLoc,bSpan,FALSE,after,aNesting,event];
afterMoved1 ← after1; afterMoved2 ← after2 }};
IF alpha.start.where # NodeItself
AND beta.start.where # NodeItself
THEN {
-- remerge starts
[alpha,beta] ← ReMerge[alpha,beta,alpha.start.node,event];
[alpha,beta] ← ReMerge[alpha,beta,beta.start.node,event] };
IF alpha.end.where # NodeItself
AND beta.end.where # NodeItself
THEN {
-- remerge ends
[alpha,beta] ← ReMerge[alpha,beta,alpha.end.node,event,TRUE];
[alpha,beta] ← ReMerge[alpha,beta,beta.end.node,event,TRUE];
afterAlphaLoc ← beta.end; afterBetaLoc ← alpha.end };
IF words THEN [alpha,beta] ← FixWordSpans[alpha,beta,event];
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] ← UndoSplits[alpha,beta,event];
ERROR CannotDoEdit }}};
MoveOnto:
PUBLIC
PROC [destRoot, sourceRoot: Ref, dest, source: Span,
words: BOOLEAN ← FALSE, 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: Slice;
aNesting, bNesting: INTEGER;
aDepth, bDepth: NAT;
FreeSlices:
PROC = {
FreeSlice[aBefore]; FreeSlice[aAfter]; FreeSlice[aTop]; FreeSlice[aBottom];
FreeSlice[bBefore]; FreeSlice[bAfter]; FreeSlice[bTop]; FreeSlice[bBottom];
aBefore ← aAfter ← aTop ← aBottom ← bBefore ← bAfter ← bTop ← bBottom ← NIL };
{ -- for exit
IF CheckForNil[source] OR CheckForNil[dest] THEN RETURN [TextNode.nullSpan];
IF TextSpans[dest,source]
THEN {
-- pure text move
RETURN [TwoSpanText[destRoot,sourceRoot,dest,source,words,saveForPaste,
TextEdit.MoveWordsOnto,TextEdit.MoveTextOnto,event]] };
[dest,source] ← DoSplits[dest,source,event];
[aBefore,aAfter,aTop,aBottom,aNesting,aDepth] ←
DescribeBand[dest.start.node,dest.end.node ! BadBand =>
{ dest ← source ← TextNode.nullSpan; GOTO ErrorReturn }];
[bBefore,bAfter,bTop,bBottom,bNesting,bDepth] ←
DescribeBand[source.start.node,source.end.node ! BadBand =>
{ dest ← source ← TextNode.nullSpan; GOTO ErrorReturn }];
-- get slices for dest and source
[overlap,head,tail,startOrder,endOrder] ←
SliceOrder[dest,source,aBefore,aBottom,bBefore,bBottom];
FreeSlices[];
-- check for overlap
IF overlap
THEN {
-- bands overlap. modify dest so doesn't overlap
IF head = TextNode.nullSpan AND tail = TextNode.nullSpan THEN GOTO ErrorReturn;
IF head = TextNode.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 = TextNode.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] ← UndoSplits[dest,source,event];
source ← MoveToLimbo[sourceRoot,source,event];
sourceRoot ← TextNode.Root[source.start.node];
[result,newDest] ← Transpose[sourceRoot,destRoot,source,dest,words,event];
IF saveForPaste THEN SaveSpanForPaste[newDest,event];
RETURN;
EXITS ErrorReturn => {
[dest,source] ← UndoSplits[dest,source,event]; ERROR CannotDoEdit }}};
-- ***** Nesting
ChangeNesting:
PUBLIC
PROC [root: Ref, span: Span, change:
INTEGER, event: Event ←
NIL]
RETURNS [new: Span] = {
-- moves span to a deeper nesting level in tree
before, after, top, bottom: Slice;
nesting: INTEGER;
depth: NAT;
FreeSlices:
PROC = {
FreeSlice[before]; FreeSlice[after]; FreeSlice[top]; FreeSlice[bottom];
before ← after ← top ← bottom ← NIL };
{ -- for exit
IF CheckForNil[span] THEN RETURN [TextNode.nullSpan];
[span,] ← DoSplits[span,nullSpan,event]; -- so can deal with entire nodes
DO -- only repeat this loop if have forced nest/unnest
[before,after,top,bottom,nesting,depth] ←
DescribeBand[span.start.node,span.end.node ! BadBand =>
{ span ← nullSpan; GOTO ErrorReturn } ];
IF nesting+change > 1 THEN GOTO ErrorReturn; -- cannot do it
SELECT NeedNestingChange[before,after,top,bottom,nesting+change,depth] FROM
needUnNest => {
afterNode: Ref = LastOfSlice[after];
span: Span = TextNode.MakeNodeSpan[afterNode,TextNode.LastWithin[afterNode]];
FreeSlices[];
[] ← UnNest[root,span,event];
LOOP };
needNest => {
span: Span = TextNode.MakeNodeSpan[top[depth],TextNode.LastWithin[bottom[depth]]];
IF SliceLength[top] = depth+1 THEN GOTO ErrorReturn;
FreeSlices[];
[] ← Nest[root,span,event];
LOOP };
ENDCASE => {
-- do it
notify: REF NodeNesting Change;
notify ← TextNode.pZone.
NEW[NodeNesting Change ← [NodeNesting[
root, span.start.node, span.end.node, change]]];
Notify[notify,before];
DeletePrefix[top,depth];
DeletePrefix[bottom,depth];
ReplaceBand[before,after,top,bottom,nesting+change,event];
FreeSlices[];
Notify[notify,after];
UndoEvent.Note[event,UndoChangeNesting,notify] };
EXIT;
ENDLOOP;
RETURN [span];
EXITS ErrorReturn => {
FreeSlices[]; [span,] ← UndoSplits[span,nullSpan,event]; ERROR CannotDoEdit }}};
UndoChangeNesting:
PROC [undoRef:
REF, currentEvent: Event] =
TRUSTED {
saved: REF Change ← NARROW[undoRef];
WITH x:saved SELECT FROM
NodeNesting => {
[] ← ChangeNesting[x.root,TextNode.MakeNodeSpan[x.first,x.last],-x.change,currentEvent] };
ENDCASE => ERROR };
-- ***** New nodes; split & merge
Insert:
PUBLIC
PROC [root, old: Ref, where: Place ← after, event: Event ←
NIL]
RETURNS [new: Ref] = TRUSTED {
-- empty copy of old node is inserted in tree in position determined by "where"
IF old=NIL THEN RETURN [NIL];
WITH x:old
SELECT
FROM
text => new ← TextNode.NewTextNode[];
other => new ← TextNode.NewOtherNode[];
ENDCASE => ERROR;
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] };
InsertOtherNode:
PUBLIC
PROC
[root, old: Ref, where: Place ← after, inherit: BOOLEAN ← FALSE, event: Event ← NIL]
RETURNS [new: RefOtherNode] = {
-- empty "other" kind of node is inserted in tree
IF old=NIL THEN RETURN [NIL];
new ← TextNode.NewOtherNode[];
IF inherit THEN Inherit[old,new];
DoInsertNode[root,old,new,where,event] };
Inherit:
PUBLIC
PROC [old, new: Ref, allprops:
BOOL ←
FALSE] = {
newtxt, oldtxt: RefTextNode;
CopyProp:
PROC [name:
ATOM, value:
REF]
RETURNS [
BOOL] = {
newvalue: REF;
IF ~allprops
THEN
SELECT name
FROM
$Prefix, $Postfix, $StyleDef => NULL; -- these control formatting
ENDCASE => RETURN [FALSE]; -- don't copy anything else
IF (newvalue ← NodeProps.CopyInfo[name,value]) #
NIL
THEN
NodeProps.PutProp[new,name,newvalue];
RETURN [FALSE] };
new.typename ← old.typename;
IF allprops
OR old.hasprefix
OR old.haspostfix
OR old.hasstyledef
THEN
[] ← NodeProps.MapProps[old, CopyProp, FALSE, FALSE];
IF (newtxt ← TextNode.NarrowToTextNode[new]) #
NIL
AND
(oldtxt ← TextNode.NarrowToTextNode[old]) #
NIL
THEN
newtxt.comment ← oldtxt.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 ← TextNode.pZone.NEW[InsertingNode Change ← [InsertingNode[root,new,dest]]];
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 }};
Notify[notify,after];
UndoEvent.Note[event,UndoInsertNode,notify] };
UndoInsertNode:
PROC [undoRef:
REF, currentEvent: Event] =
TRUSTED {
saved: REF Change ← NARROW[undoRef];
WITH x:saved SELECT FROM
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 # 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: Offset;
IF pred = NIL OR TextNode.Parent[pred] = NIL OR txt = NIL THEN ERROR CannotDoEdit;
[start,] ← TextEdit.CopyText[root,root,pred,MaxLen,txt,0,MaxLen,event];
[] ← Delete[root,TextNode.MakeNodeSpan[txt,txt],event];
RETURN [[pred,start]] };
-- **** Change looks ****
ChangeLooks:
PUBLIC
PROC [root: Ref, span: Span, remove, add: Looks, event: Event ←
NIL] = {
DoChange:
PROC [node: RefTextNode, start, len: Offset]
RETURNS [stop:
BOOLEAN] = {
TextEdit.ChangeLooks[root,node,remove,add,start,len,event]; RETURN [FALSE] };
Apply[span,DoChange] };
-- ***** Cap's and Lowercase
ChangeCaps:
PUBLIC
PROC [root: Ref, span: Span, how: TextEdit.CapChange ← allCaps, event: Event ←
NIL] = {
DoChange:
PROC [node: RefTextNode, start, len: Offset]
RETURNS [stop:
BOOLEAN] = {
TextEdit.ChangeCaps[root,node,start,len,how,event]; RETURN [FALSE] };
IF CheckForNil[span] THEN RETURN;
Apply[span,DoChange] };
-- ***** Initialization
Start: PUBLIC PROC = {}; -- for initialization only
END.