<> <> <> <> DIRECTORY EditSpan, EditSpanSupport, EditNotify, Rope, UndoEvent, TextEdit, TreeSlice, TiogaLooks, TiogaNode, TiogaNodeOps, TiogaTreeOps; EditSpanImpl: CEDAR PROGRAM IMPORTS TextEdit, TiogaNodeOps, TiogaTreeOps, UndoEvent, EditSpan, EditSpanSupport, TreeSlice, EditNotify EXPORTS EditSpan SHARES TiogaNode = BEGIN OPEN EditSpan, EditSpanSupport, TreeSlice, EditNotify; CannotDoEdit: PUBLIC ERROR = CODE; afterMoved1, afterMoved2: PUBLIC TreeLoc; Slice: TYPE = TreeSlice.Slice; nullSpan: TreeSpan = TiogaTreeOps.nullSpan; nullBranchSpan: BranchSpan = TiogaTreeOps.nullBranchSpan; <<***** Editing operations on spans>> Move: PUBLIC PROC [destRoot, sourceRoot: RefBranchNode, dest: TreeLoc, source: TreeSpan, where: Place _ after, nesting: INTEGER _ 0, event: Event _ NIL] RETURNS [result: TreeSpan] = { <> <> sBefore, sAfter, sTop, sBottom, dBefore, dAfter: Slice; sNesting, dNesting: INTEGER; sDepth: NAT; beforeSource, afterSource: RefBranchNode; -- nodes adjacent to source after do splits afterLoc: TreeLoc; d: BranchLoc; s: BranchSpan; FreeSlices: PROC = { FreeSlice[sBefore]; FreeSlice[sAfter]; FreeSlice[sTop]; FreeSlice[sBottom]; FreeSlice[dBefore]; FreeSlice[dAfter]; sBefore _ sAfter _ sTop _ sBottom _ dBefore _ dAfter _ NIL }; ForcedUnNest: PROC [after: Ref] = { OPEN TiogaTreeOps; afterNode: RefBranchNode = TiogaNodeOps.NarrowToBranchNode[after]; span: TreeSpan = MakeTreeSpan[afterNode, LastBranchWithin[afterNode]]; IF afterNode=NIL THEN ERROR; FreeSlices[]; [] _ UnNest[Root[afterNode], span, event] }; ForcedNest: PROC = { OPEN TiogaTreeOps; bottom: RefBranchNode = TiogaNodeOps.NarrowToBranchNode[sBottom[sDepth]]; span: TreeSpan _ MakeTreeSpan[sTop[sDepth], LastBranchWithin[bottom]]; IF bottom=NIL THEN ERROR; IF TreeSlice.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[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 { parentBr, childBr: RefBranchNode; parentBr _ TiogaTreeOps.ContainingStatement[dest.node]; childBr _ TiogaTreeOps.LastBranchWithin[parentBr]; dest _ [childBr, NodeItself]; where _ after; nesting _ nesting + TiogaTreeOps.Level[parentBr] - TiogaTreeOps.Level[childBr] }; <> [d, s] _ DoSplitsForMove[dest, source, where, event]; beforeSource _ TiogaTreeOps.Backward[s.start.node].back; afterSource _ TiogaTreeOps.Forward[s.end.node].nx; afterLoc _ [afterSource, NodeItself]; { -- for exits <> IF d # TiogaTreeOps.nullBranchLoc AND ((where = after AND (d.node = beforeSource OR d.node = s.end.node)) OR (where = before AND (d.node = afterSource OR d.node = s.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[s], event]; ENDLOOP ELSE IF nesting < 0 THEN FOR i:INTEGER IN [nesting..0) DO [] _ UnNest[sourceRoot, NodeSpan[s], event]; ENDLOOP } ELSE DO -- repeat this loop only if have forced nest or unnest or s and d in same tree [sBefore, sAfter, sTop, sBottom, sNesting, sDepth] _ DescribeBand[s.start.node, s.end.node ! BadBand => { s _ nullBranchSpan; GOTO ErrorReturn } ]; IF d = TiogaTreeOps.nullBranchLoc THEN { -- moving to limbo d _ CreateDest[TreeSlice.SliceLength[sTop]-sDepth]; destRoot _ TiogaTreeOps.Root[d.node]; destRoot.deleted _ TRUE; -- so will free this when it falls off the edit history list where _ after; nesting _ 1 }; [dBefore, dAfter, dNesting] _ DestSlices[d.node, where]; IF TreeSlice.CompareSliceOrder[dBefore, sBefore]=after AND TreeSlice.CompareSliceOrder[dBefore, sBottom]=before THEN GOTO ErrorReturn; -- d inside s IF ~TiogaNodeOps.IsBranch[dBefore[0]] AND BadDest[dBefore[0], sBefore, sBottom] THEN GOTO ErrorReturn; <> IF dBefore[0] = sBefore[0] THEN { -- s and d in same tree; too hard, so move s out first. span: TreeSpan = NodeSpan[s]; FreeSlices[]; [] _ MoveToLimbo[sourceRoot, span, event]; LOOP }; dNesting _ dNesting + nesting; SELECT NeedNestingChange[dBefore, dAfter, sTop, sBottom, dNesting, sDepth] FROM needUnNest => { ForcedUnNest[TreeSlice.LastOfSlice[dAfter]]; LOOP }; needNest => { ForcedNest[]; LOOP }; ENDCASE; IF TreeSlice.SliceLength[sAfter] > TreeSlice.SliceLength[sBefore]+1 THEN { ForcedUnNest[TreeSlice.LastOfSlice[sAfter]]; LOOP } ELSE { -- do it notify: REF MovingNodes Change; notify _ NEW[MovingNodes Change _ [MovingNodes[ destRoot, sourceRoot, d.node, s.start.node, s.end.node, TreeSlice.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 d.where # NodeItself THEN { -- undo prior splits start, end: BOOLEAN _ FALSE; IF s.start.where # NodeItself THEN { -- merge start of s with front of dest start _ TRUE; [s, ] _ ReMerge[s, nullBranchSpan, s.start.node, event] }; IF s.end.where # NodeItself THEN { -- merge end of s with tail of dest end _ TRUE; [s, ] _ ReMerge[s, nullBranchSpan, s.end.node, event, TRUE] }; IF start AND end THEN { -- merge before s with after s afterLoc _ JoinStatements[TiogaTreeOps.Root[afterSource], afterSource, event] }} ELSE IF s.start.where # NodeItself AND s.end.where # NodeItself THEN { afterLoc _ JoinStatements[TiogaTreeOps.Root[afterSource], afterSource, event] }; afterMoved2 _ afterMoved1; -- save previous hint afterMoved1 _ IF afterSource=NIL THEN [beforeSource, 0] ELSE afterLoc; -- hint for repaint source.start.node _ s.start.node; source.end.node _ s.end.node; source.start.where _ source.end.where _ NodeItself; RETURN [source]; EXITS ErrorReturn => { FreeSlices[]; UndoSplitsForMove[d, s, where, 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, TiogaTreeOps.MakeTreeLoc[x.pred], TiogaTreeOps.MakeTreeSpan[x.first, x.last], after, x.nesting, currentEvent] }; ENDCASE => ERROR }; BadDest: PROC [top: Ref, sBefore, sBottom: Slice] RETURNS [error: BOOL] = { DO -- check for dest inside source s: RefBranchNode _ TiogaTreeOps.ContainingStatement[top]; before, after: Slice; [before, after] _ MakeSlices[s]; top _ before[0]; error _ TreeSlice.CompareSliceOrder[before, sBefore]=after AND TreeSlice.CompareSliceOrder[before, sBottom]=before; FreeSlice[before]; FreeSlice[after]; IF error THEN RETURN; IF TiogaNodeOps.IsBranch[top] THEN RETURN; ENDLOOP }; Transpose: PUBLIC PROC [alphaRoot, betaRoot: RefBranchNode, alpha, beta: TreeSpan, event: Event _ NIL] RETURNS [newAlpha, newBeta: TreeSpan] = { <> aBefore, aAfter, aTop, aBottom, bBefore, bAfter, bTop, bBottom: Slice; aNesting, bNesting: INTEGER; aDepth, bDepth: NAT; beforeAlpha, afterAlpha, beforeBeta, afterBeta: RefBranchNode; -- nodes adjacent after do splits afterAlphaLoc, afterBetaLoc: TreeLoc; a, b: BranchSpan; 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 [nullSpan, nullSpan]; IF TextSpans[alpha, beta] THEN { -- pure text transpose alphaNode: RefTextNode _ TiogaNodeOps.NarrowToTextNode[alpha.start.node]; betaNode: RefTextNode _ TiogaNodeOps.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; [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 }; [a, b] _ DoSplits[alpha, beta, event]; -- so can deal with entire nodes beforeAlpha _ TiogaTreeOps.Backward[a.start.node].back; afterAlpha _ TiogaTreeOps.Forward[a.end.node].nx; afterAlphaLoc _ [afterAlpha, 0]; beforeBeta _ TiogaTreeOps.Backward[b.start.node].back; afterBeta _ TiogaTreeOps.Forward[b.end.node].nx; afterBetaLoc _ [afterBeta, 0]; <> IF afterAlpha = b.start.node THEN { -- a just before b <> [] _ Move[alphaRoot, betaRoot, NodeLoc[a.start], NodeSpan[b], before, 0, event] } ELSE IF afterBeta = a.start.node THEN { -- b just before a <> [] _ Move[betaRoot, alphaRoot, NodeLoc[b.start], NodeSpan[a], before, 0, event] } ELSE { -- get slices describing the bands of nodes to be transposed overlap: BOOLEAN; head, tail: BranchSpan; -- sections of a or b before and after the overlap startOrder, endOrder: Order; [aBefore, aAfter, aTop, aBottom, aNesting, aDepth] _ DescribeBand[a.start.node, a.end.node ! BadBand => { a _ b _ nullBranchSpan; GOTO ErrorReturn }]; [bBefore, bAfter, bTop, bBottom, bNesting, bDepth] _ DescribeBand[b.start.node, b.end.node ! BadBand => { a _ b _ nullBranchSpan; GOTO ErrorReturn }]; IF ~TiogaNodeOps.IsBranch[aBefore[0]] AND BadDest[aBefore[0], bBefore, bBottom] THEN GOTO ErrorReturn; -- a inside statement inside b IF ~TiogaNodeOps.IsBranch[bBefore[0]] AND BadDest[bBefore[0], aBefore, aBottom] THEN GOTO ErrorReturn; -- b inside statement inside a <> [overlap, head, tail, startOrder, endOrder] _ SliceOrder[a, b, aBefore, aBottom, bBefore, bBottom]; IF overlap THEN { -- bands overlap FreeSlices[]; IF head = nullBranchSpan AND tail = nullBranchSpan THEN NULL ELSE IF head = nullBranchSpan THEN { --move tail to before alphastart [] _ Move[alphaRoot, betaRoot, NodeLoc[a.start], NodeSpan[tail], before, 0, event]; IF endOrder=before THEN { -- a end before b end b.start _ tail.start; b.end _ a.end } ELSE { -- b end before a end a.start _ tail.start; a.end _ b.end }} ELSE IF tail = nullBranchSpan THEN { --move head to after alphaend [] _ Move[alphaRoot, betaRoot, NodeLoc[a.end], NodeSpan[head], after, 0, event]; IF startOrder=before THEN { -- a start before b start a.start _ b.start; a.end _ head.end } ELSE { -- b start before a start b.start _ a.start; b.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], event]; IF startOrder=before THEN { -- a start before b start a.start _ b.start; a.end _ head.end; b.start _ tail.start; b.end _ a.end } ELSE { -- b start before a start b.start _ a.start; b.end _ head.end; a.start _ tail.start; a.end _ b.end }}} ELSE { -- do transpose as two moves aSpan, bSpan: TreeSpan; after1, after2: TreeLoc; bLoc: TreeLoc _ TiogaTreeOps.MakeTreeLoc[TreeSlice.LastOfSlice[bBefore]]; aLoc: TreeLoc _ TiogaTreeOps.MakeTreeLoc[TreeSlice.LastOfSlice[aBefore]]; FreeSlices[]; aSpan _ TiogaTreeOps.MakeTreeSpan[a.start.node, a.end.node]; bSpan _ TiogaTreeOps.MakeTreeSpan[b.start.node, b.end.node]; [] _ MoveToLimbo[alphaRoot, aSpan, event]; after1 _ afterMoved1; -- repaint hints [] _ MoveToLimbo[betaRoot, bSpan, event]; after2 _ afterMoved1; [] _ Move[betaRoot, TiogaTreeOps.Root[aSpan.start.node], bLoc, aSpan, after, bNesting, event]; [] _ Move[alphaRoot, TiogaTreeOps.Root[bSpan.start.node], aLoc, bSpan, after, aNesting, event]; afterMoved1 _ after1; afterMoved2 _ after2 }}; IF a.start.where # NodeItself AND b.start.where # NodeItself THEN { -- remerge starts [a, b] _ ReMerge[a, b, a.start.node, event]; [a, b] _ ReMerge[a, b, b.start.node, event] }; IF a.end.where # NodeItself AND b.end.where # NodeItself THEN { -- remerge ends [a, b] _ ReMerge[a, b, a.end.node, event, TRUE]; [a, b] _ ReMerge[a, b, b.end.node, event, TRUE]; afterAlphaLoc.node _ b.end.node; afterBetaLoc.node _ a.end.node }; afterMoved1 _ IF afterAlphaLoc.node=NIL THEN [beforeAlpha, 0] ELSE afterAlphaLoc; -- hint for repaint afterMoved2 _ IF afterBetaLoc.node=NIL THEN [beforeBeta, 0] ELSE afterBetaLoc; alpha.start _ [a.start.node, NodeItself]; alpha.end _ [a.end.node, NodeItself]; beta.start _ [b.start.node, NodeItself]; beta.end _ [b.end.node, NodeItself]; RETURN [alpha, beta]; EXITS ErrorReturn => { FreeSlices[]; UndoSplits[a, b, event]; ERROR CannotDoEdit }}}; MoveOnto: PUBLIC PROC [destRoot, sourceRoot: RefBranchNode, dest, source: TreeSpan, saveForPaste: BOOLEAN _ TRUE, event: Event _ NIL] RETURNS [result: TreeSpan] = { <> <> overlap: BOOLEAN; newDest: TreeSpan; head, tail: BranchSpan; -- sections of alpha or beta before and after the overlap startOrder, endOrder: Order; aBefore, aAfter, aTop, aBottom, bBefore, bAfter, bTop, bBottom: Slice; aNesting, bNesting: INTEGER; aDepth, bDepth: NAT; d, s: BranchSpan; 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 [nullSpan]; IF TextSpans[dest, source] THEN { -- pure text move RETURN [TwoSpanText[destRoot, sourceRoot, dest, source, saveForPaste, TextEdit.MoveTextOnto, event]] }; [d, s] _ DoSplits[dest, source, event]; <> [aBefore, aAfter, aTop, aBottom, aNesting, aDepth] _ DescribeBand[d.start.node, d.end.node ! BadBand => { d _ s _ nullBranchSpan; GOTO ErrorReturn }]; [bBefore, bAfter, bTop, bBottom, bNesting, bDepth] _ DescribeBand[s.start.node, s.end.node ! BadBand => { d _ s _ nullBranchSpan; GOTO ErrorReturn }]; IF ~TiogaNodeOps.IsBranch[aBefore[0]] AND BadDest[aBefore[0], bBefore, bBottom] THEN GOTO ErrorReturn; -- d inside statement inside s [overlap, head, tail, startOrder, endOrder] _ SliceOrder[d, s, aBefore, aBottom, bBefore, bBottom]; FreeSlices[]; <> IF overlap THEN { -- bands overlap. modify d so doesn't overlap IF head = nullBranchSpan AND tail = nullBranchSpan THEN GOTO ErrorReturn; IF head = nullBranchSpan THEN { -- s start = d start IF endOrder=before THEN GOTO ErrorReturn -- d end before s end ELSE d.start _ tail.start } ELSE IF tail = nullBranchSpan THEN { --s end = d end IF startOrder=before THEN d.end _ head.end -- d start before s start ELSE GOTO ErrorReturn } -- s start before d start ELSE { -- have both head and tail IF startOrder=before AND endOrder=after THEN { [] _ Delete[destRoot, NodeSpan[tail], event]; d.end _ head.end } ELSE IF startOrder=before THEN d.end _ head.end ELSE IF endOrder=after THEN d.start _ tail.start ELSE GOTO ErrorReturn }}; source _ MoveToLimbo[sourceRoot, NodeSpan[s], event]; sourceRoot _ TiogaTreeOps.Root[source.start.node]; [result, newDest] _ Transpose[sourceRoot, destRoot, source, NodeSpan[d], event]; IF saveForPaste THEN SaveSpanForPaste[newDest, event]; RETURN; EXITS ErrorReturn => { UndoSplits[d, s, event]; ERROR CannotDoEdit }}}; Replace: PUBLIC PROC [ destRoot, sourceRoot: RefBranchNode, dest, source: TreeSpan, saveForPaste: BOOL _ TRUE, event: Event _ NIL] RETURNS [result: TreeSpan] = { <> <> newDest: TreeSpan; IF TextSpans[dest, source] THEN { -- pure text replace RETURN [TwoSpanText[destRoot, sourceRoot, dest, source, saveForPaste, TextEdit.ReplaceText, event]] }; source _ CopySpan[source]; sourceRoot _ TiogaTreeOps.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: TreeSpan] RETURNS [BOOLEAN] = { RETURN [dest.start.node = dest.end.node AND dest.start.where # NodeItself AND dest.end.where # NodeItself AND TiogaNodeOps.IsText[dest.start.node] AND source.start.node = source.end.node AND source.start.where # NodeItself AND source.end.where # NodeItself AND TiogaNodeOps.IsText[source.start.node]] }; TwoSpanText: PROC [destRoot, sourceRoot: RefBranchNode, dest, source: TreeSpan, saveForPaste: BOOLEAN, textProc: TextEdit.TwoSpanProc, event: Event] RETURNS [result: TreeSpan] = { destNode: RefTextNode _ TiogaNodeOps.NarrowToTextNode[dest.start.node]; sourceNode: RefTextNode _ TiogaNodeOps.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; 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: TreeSpan, event: Event _ NIL] = { node: RefTextNode; IF CheckForNil[span] THEN RETURN; IF span.start.node = span.end.node AND span.start.where # NodeItself AND span.end.where # NodeItself AND (node _ TiogaNodeOps.NarrowToTextNode[span.start.node]) # NIL THEN { -- pure text 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: RefBranchNode, del: TreeSpan, event: Event _ NIL, saveForPaste: BOOL _ TRUE] = { node: RefTextNode; IF CheckForNil[del] THEN RETURN; IF del.start.node = del.end.node AND del.start.where # NodeItself AND del.end.where # NodeItself AND (node _ TiogaNodeOps.NarrowToTextNode[del.start.node]) # NIL THEN { -- pure text 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: TreeSpan _ MoveToLimbo[root, del, event]; IF saveForPaste THEN SaveSpanForPaste[d, event] }}; paste: REF Change; <<--really REF ChangingTextForPaste/ChangingSpanForPaste Change>> SaveTextForPaste: PROC [node: RefTextNode, start, len: Offset, event: Event] = { SaveOldPaste[event]; paste _ NEW[ChangingTextForPaste Change _ [ChangingTextForPaste[node.rope, node.runs, start, len]]] }; SaveSpanForPaste: PROC [span: TreeSpan, event: Event] = { SaveOldPaste[event]; paste _ NEW[ChangingSpanForPaste Change _ [ChangingSpanForPaste[span]]] }; RecordGroupForPaste: PUBLIC PROC [start, end: Ref, event: Event] = { span: TreeSpan; span.start _ [start, NodeItself]; span.end _ [TiogaTreeOps.LastWithin[end], NodeItself]; SaveSpanForPaste[span, event] }; 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: TreeSpan] = TRUSTED { savedPaste: REF Change _ paste; IF savedPaste=NIL THEN RETURN [nullSpan]; WITH x: savedPaste SELECT FROM ChangingTextForPaste => { -- convert saved text to a span node: RefTextNode; root: RefBranchNode; span: TreeSpan; IF x.len <= 0 THEN RETURN [nullSpan]; node _ TiogaNodeOps.NewTextNode[TiogaNode.defaultTextClassID]; root _ TiogaNodeOps.DocFromNode[node]; [] _ TextEdit.ReplaceByText[root: root, dest: node, sourceRope: x.rope, sourceRuns: x.runs, sourceStart: x.start, sourceLen: x.len]; span _ [[node, 0], [node, x.len-1]]; paste _ NEW[ChangingSpanForPaste Change _ [ChangingSpanForPaste[span]]]; RETURN [span] }; ChangingSpanForPaste => RETURN [x.span]; ENDCASE => ERROR; }; MoveToLimbo: PROC [root: RefBranchNode, span: TreeSpan, event: Event] RETURNS [result: TreeSpan] = INLINE { RETURN [Move[NIL, root, TiogaTreeOps.nullLoc, span, after, 1, event]] }; Copy: PUBLIC PROC [ destRoot, sourceRoot: RefBranchNode, dest: TreeLoc, source: TreeSpan, where: Place _ after, nesting: INTEGER _ 0, event: Event _ NIL] RETURNS [result: TreeSpan] = { <> 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 _ CopySpan[source]; sourceRoot _ TiogaTreeOps.Root[source.start.node]; sourceRoot.deleted _ TRUE; result _ Move[destRoot, sourceRoot, dest, source, where, nesting, event] }; TextLocationAndSpan: PROC [dest: TreeLoc, source: TreeSpan] RETURNS [BOOLEAN] = { RETURN [dest.where # NodeItself AND source.start.node = source.end.node AND source.start.where # NodeItself AND source.end.where # NodeItself AND TiogaNodeOps.NarrowToTextNode[source.start.node] # NIL] }; DestSpanText: PROC [ destRoot, sourceRoot: RefBranchNode, dest: TreeLoc, source: TreeSpan, textProc: TextEdit.DestSpanProc, event: Event] RETURNS [result: TreeSpan] = { destNode: RefTextNode _ TiogaNodeOps.NarrowToTextNode[dest.node]; sourceNode: RefTextNode _ TiogaNodeOps.NarrowToTextNode[source.start.node]; destLoc: Offset _ dest.where; sourceStart: Offset _ source.start.where; sourceLen: Offset _ source.end.where - sourceStart + 1; resultStart, resultLen: Offset; [resultStart, resultLen] _ textProc[destRoot, sourceRoot, destNode, destLoc, sourceNode, sourceStart, sourceLen, event]; RETURN [[[destNode, resultStart], [destNode, resultStart+resultLen-1]]] }; <<***** Nesting>> ChangeNesting: PUBLIC PROC [ root: RefBranchNode, span: TreeSpan, change: INTEGER, event: Event _ NIL] RETURNS [new: TreeSpan] = { <> before, after, top, bottom: Slice; nesting: INTEGER; depth: NAT; s: BranchSpan; FreeSlices: PROC = { FreeSlice[before]; FreeSlice[after]; FreeSlice[top]; FreeSlice[bottom]; before _ after _ top _ bottom _ NIL }; { -- for exit IF CheckForNil[span] THEN RETURN [nullSpan]; [s, ] _ 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[s.start.node, s.end.node ! BadBand => { s _ nullBranchSpan; GOTO ErrorReturn } ]; IF nesting+change > 1 THEN GOTO ErrorReturn; -- cannot do it SELECT NeedNestingChange[before, after, top, bottom, nesting+change, depth] FROM needUnNest => { afterNode: RefBranchNode = TiogaNodeOps.NarrowToBranchNode[TreeSlice.LastOfSlice[after]]; last: RefBranchNode = TiogaTreeOps.LastBranchWithin[afterNode]; s: TreeSpan = TiogaTreeOps.MakeTreeSpan[afterNode, last]; IF afterNode=NIL THEN ERROR; FreeSlices[]; [] _ UnNest[root, s, event]; LOOP }; needNest => { last: RefBranchNode = TiogaTreeOps.LastBranchWithin[ TiogaNodeOps.NarrowToBranchNode[bottom[depth]]]; s: TreeSpan = TiogaTreeOps.MakeTreeSpan[top[depth], last]; IF last=NIL THEN ERROR; IF TreeSlice.SliceLength[top] = depth+1 THEN GOTO ErrorReturn; FreeSlices[]; [] _ Nest[root, s, event]; LOOP }; ENDCASE => { -- do it notify: REF NodeNesting Change; notify _ NEW[NodeNesting Change _ [NodeNesting[root, s.start.node, s.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; span.start _ [s.start.node, NodeItself]; span.end _ [s.end.node, NodeItself]; RETURN [span]; EXITS ErrorReturn => { FreeSlices[]; UndoSplits[s, nullBranchSpan, event]; ERROR CannotDoEdit }}}; UndoChangeNesting: PROC [undoRef: REF, currentEvent: Event] = TRUSTED { saved: REF Change _ NARROW[undoRef]; WITH x:saved SELECT FROM NodeNesting => { [] _ ChangeNesting[x.root, TiogaTreeOps.MakeTreeSpan[x.first, x.last], -x.change, currentEvent] }; ENDCASE => ERROR }; <<***** Miscellaneous>> CheckForNil: PROC [span: TreeSpan] RETURNS [BOOLEAN] = INLINE { RETURN [span.start.node = NIL OR span.end.node = NIL] }; NodeSpan: PROC [span: BranchSpan] RETURNS [TreeSpan] = INLINE { RETURN [[[span.start.node, NodeItself], [span.end.node, NodeItself]]] }; NodeLoc: PROC [loc: BranchLoc] RETURNS [TreeLoc] = INLINE { RETURN [[loc.node, NodeItself]] }; END....