<<>> <> <> <> <> <> <> <> <> <> <<>> DIRECTORY EditNotify USING [Change, Notify], EditSpan USING [Nest, UnNest], EditSpanSupport USING [AdoptChildren, Apply, BadBand, CompareSliceOrder, CopySpan, CreateDest, DeletePrefix, DescribeBand, DestSlices, DoSplits, DoSplits2, FreeSlice, LastOfSlice, NeedNestingChange, NodeLoc, NodeSpan, ReMerge, ReplaceBand, Slice, SliceLength, SliceOrder, Splice, UndoSplits, UndoSplits2], NodeProps USING [CopyInfo, Is, MapProps, PutProp], TextEdit USING [ChangeCaps, ChangeLooks, CopyText, DeleteText, DestSpanProc, MoveText, MoveTextOnto, ReplaceText, TransposeProc, TransposeText, TwoSpanProc], TextNode USING [LastWithin, Level, MakeNodeLoc, MakeNodeSpan, NewTextNode, Parent, Previous, Root, StepBackward, StepForward], Tioga USING [CapChange, Location, Looks, Node, NodeItself, nullLocation, nullSpan, Order, Place, Span], UndoEvent USING [Event, EventRep, Note]; EditSpanImpl: CEDAR PROGRAM IMPORTS EditSpan, EditSpanSupport, EditNotify, NodeProps, TextEdit, TextNode, UndoEvent EXPORTS EditSpan, Tioga = BEGIN OPEN EditSpan, Tioga; Event: TYPE ~ UndoEvent.Event; EventRep: PUBLIC TYPE ~ UndoEvent.EventRep; Change: TYPE ~ EditNotify.Change; CannotDoEdit: PUBLIC ERROR = CODE; afterMoved1: PUBLIC Location; afterMoved2: PUBLIC Location; <<>> <> CheckForNil: PROC [span: Span] RETURNS [BOOLEAN] = INLINE { RETURN [span.start.node = NIL OR span.end.node = NIL] }; Replace: PUBLIC PROC [destRoot, sourceRoot: Node, dest, source: Span, saveForPaste: BOOLEAN ¬ TRUE, event: Event ¬ NIL] RETURNS [result: Span] = { <> <> 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 # 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: Node, dest, source: Span, saveForPaste: BOOLEAN, textProc: TextEdit.TwoSpanProc, event: Event] RETURNS [result: Span] = { destNode: Node ¬ dest.start.node; sourceNode: Node ¬ 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 # NodeItself AND span.end.where # NodeItself THEN { -- pure text node: Node ¬ 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: Node, 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: Node ¬ 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 ¬ NIL; SaveTextForPaste: PROC [node: Node, 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.ChangingSpanForPaste ~ paste; RETURN[IF savedPaste=NIL THEN nullSpan ELSE savedPaste.span]; }; MoveToLimbo: PROC [root: Node, span: Span, event: Event] RETURNS [result: Span] = { RETURN [Move[NIL, root, nullLocation, span, after, 1, event]] }; Copy: PUBLIC PROC [destRoot, sourceRoot: Node, dest: Location, source: Span, where: Place ¬ after, nesting: INTEGER ¬ 0, event: Event ¬ NIL] RETURNS [result: Span] = { <> 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 # NodeItself AND source.start.node = source.end.node AND source.start.where # NodeItself AND source.end.where # NodeItself] }; DestSpanText: PROC [destRoot, sourceRoot: Node, dest: Location, source: Span, textProc: TextEdit.DestSpanProc, event: Event] RETURNS [result: Span] = { destNode: Node ¬ dest.node; sourceNode: Node ¬ 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: Node, dest: Location, source: Span, where: Place ¬ after, nesting: INTEGER ¬ 0, event: Event ¬ NIL] RETURNS [result: Span] = { <> <> sBefore, sAfter, sTop, sBottom, dBefore, dAfter: EditSpanSupport.Slice; sNesting, dNesting: INTEGER; sDepth: NAT; beforeSource, afterSource: Node; -- 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: Node] = { 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 { <> RETURN [DestSpanText[destRoot, sourceRoot, dest, source, TextEdit.MoveText, event]]; }; SELECT where FROM child => { where ¬ after; nesting ¬ nesting+1 }; sibling => { newDest: Node = TextNode.LastWithin[dest.node]; where ¬ after; nesting ¬ nesting + TextNode.Level[dest.node] - TextNode.Level[newDest]; dest ¬ [newDest, NodeItself]; }; ENDCASE => NULL; <> [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 <> 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 SELECT nesting FROM > 0 => { FOR i:INTEGER IN [0..nesting) DO [] ¬ Nest[sourceRoot, EditSpanSupport.NodeSpan[source], event]; ENDLOOP }; < 0 => { FOR i:INTEGER IN [nesting..0) DO [] ¬ UnNest[sourceRoot, EditSpanSupport.NodeSpan[source], event]; ENDLOOP }; ENDCASE => NULL; } ELSE { DO -- repeat this loop only if have forced nest or unnest or source and dest in same tree <> [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; <> 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[ dest: dest.node, first: source.start.node, last: source.end.node, pred: EditSpanSupport.LastOfSlice[sBefore], nesting: sNesting, afterDest: (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 # 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, ] ¬ EditSpanSupport.ReMerge[source, nullSpan, source.start.node, event] }; IF source.end.where # 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 # NodeItself AND source.end.where # NodeItself THEN { afterLoc ¬ Merge[TextNode.Root[afterSource], afterSource, event] }; afterMoved2 ¬ afterMoved1; -- save previous hint <> IF afterSource=NIL THEN afterMoved1 ¬ [beforeSource, 0] ELSE afterMoved1 ¬ afterLoc; -- hint for repaint RETURN [source]; EXITS ErrorReturn => { FreeSlices[]; [, source] ¬ EditSpanSupport.UndoSplits2[dest, source, event]; ERROR CannotDoEdit } } }; UndoMoveNodes: PROC [undoRef: REF Change, currentEvent: Event] = { WITH undoRef SELECT FROM x: REF Change.MovingNodes => { [] ¬ Move[destRoot: TextNode.Root[x.pred], sourceRoot: TextNode.Root[x.first], dest: TextNode.MakeNodeLoc[x.pred], source: TextNode.MakeNodeSpan[x.first, x.last], where: after, nesting: x.nesting, event: currentEvent] }; ENDCASE => ERROR }; Transpose: PUBLIC PROC [alphaRoot, betaRoot: Node, alpha, beta: Span, event: Event ¬ NIL] RETURNS [newAlpha, newBeta: Span] = { <> aBefore, aAfter, aTop, aBottom, bBefore, bAfter, bTop, bBottom: EditSpanSupport.Slice; aNesting, bNesting: INTEGER; aDepth, bDepth: NAT; beforeAlpha, afterAlpha, beforeBeta, afterBeta: Node; -- 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: Node ¬ alpha.start.node; betaNode: Node ¬ 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]; <> IF afterAlpha = beta.start.node THEN { -- alpha just before beta <> [] ¬ 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[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: Order; [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 }]; <> [overlap, head, tail, startOrder, endOrder] ¬ EditSpanSupport.SliceOrder[alpha, beta, aBefore, aBottom, bBefore, bBottom]; IF overlap THEN { -- bands overlap FreeSlices[]; SELECT TRUE FROM head = nullSpan AND tail = nullSpan => NULL; head = nullSpan => { --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 } }; tail = nullSpan => { --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 } }; startOrder # endOrder => NULL; -- one contained in the other ENDCASE => { --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 { -- no overlap, 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 # NodeItself AND beta.start.where # 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 # NodeItself AND beta.end.where # 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 }; <> IF afterAlphaLoc.node=NIL THEN afterMoved1 ¬ [beforeAlpha, 0] ELSE afterMoved1 ¬ afterAlphaLoc; -- hint for repaint <> IF afterBetaLoc.node=NIL THEN afterMoved2 ¬ [beforeBeta, 0] ELSE afterMoved2 ¬ afterBetaLoc; RETURN [alpha, beta]; EXITS ErrorReturn => { FreeSlices[]; [alpha, beta] ¬ EditSpanSupport.UndoSplits[alpha, beta, event]; ERROR CannotDoEdit } } }; MoveOnto: PUBLIC PROC [destRoot, sourceRoot: Node, dest, source: Span, saveForPaste: BOOLEAN ¬ TRUE, event: Event ¬ NIL] RETURNS [result: Span] = { <> <> overlap: BOOLEAN; head, tail, newDest: Span; -- sections of alpha or beta before and after the overlap startOrder, endOrder: Order; 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 }]; <> [overlap, head, tail, startOrder, endOrder] ¬ EditSpanSupport.SliceOrder[dest, source, aBefore, aBottom, bBefore, bBottom]; FreeSlices[]; <> IF overlap THEN { -- bands overlap. modify dest so doesn't overlap SELECT TRUE FROM head = nullSpan AND tail = nullSpan => GOTO ErrorReturn; head = nullSpan => { -- source start = dest start IF endOrder=before THEN {GOTO ErrorReturn} -- dest end before source end ELSE {dest.start ¬ tail.start} }; tail = nullSpan => { --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 -- }; ENDCASE => { -- have both head and tail SELECT TRUE FROM startOrder=before AND endOrder=after => { [] ¬ Delete[destRoot, tail, event]; dest.end ¬ head.end }; startOrder=before => dest.end ¬ head.end; endOrder=after => dest.start ¬ tail.start; ENDCASE => 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 } } }; <> ChangeNesting: PUBLIC PROC [root: Node, span: Span, change: INTEGER, event: Event ¬ NIL] RETURNS [new: Span] = { <> before, after, top, bottom: EditSpanSupport.Slice; nesting: INTEGER; depth: NAT; FreeSlices: PROC = { EditSpanSupport.FreeSlice[before]; EditSpanSupport.FreeSlice[after]; EditSpanSupport.FreeSlice[top]; EditSpanSupport.FreeSlice[bottom]; before ¬ after ¬ top ¬ bottom ¬ NIL }; { -- for exit IF CheckForNil[span] THEN RETURN [nullSpan]; [span, ] ¬ EditSpanSupport.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] ¬ EditSpanSupport.DescribeBand[span.start.node, span.end.node ! EditSpanSupport.BadBand => { span ¬ nullSpan; GOTO ErrorReturn } ]; IF nesting+change > 1 THEN GOTO ErrorReturn; -- cannot do it SELECT EditSpanSupport.NeedNestingChange[before, after, top, bottom, nesting+change, depth] FROM needUnNest => { afterNode: Node = EditSpanSupport.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 EditSpanSupport.SliceLength[top] = depth+1 THEN GOTO ErrorReturn; FreeSlices[]; [] ¬ Nest[root, span, event]; LOOP }; ENDCASE => { -- do it notify: REF NodeNesting Change; notify ¬ NEW[NodeNesting Change ¬ [NodeNesting[ first: span.start.node, last: span.end.node, change: change]]]; EditNotify.Notify[notify, before]; EditSpanSupport.DeletePrefix[top, depth]; EditSpanSupport.DeletePrefix[bottom, depth]; EditSpanSupport.ReplaceBand[before, after, top, bottom, nesting+change, event]; FreeSlices[]; EditNotify.Notify[notify, after]; UndoEvent.Note[event, UndoChangeNesting, notify] }; EXIT; ENDLOOP; RETURN [span]; EXITS ErrorReturn => { FreeSlices[]; [span, ] ¬ EditSpanSupport.UndoSplits[span, nullSpan, event]; ERROR CannotDoEdit } } }; UndoChangeNesting: PROC [undoRef: REF Change, currentEvent: Event] = { WITH undoRef SELECT FROM x: REF Change.NodeNesting => [] ¬ ChangeNesting[root: TextNode.Root[x.first], span: TextNode.MakeNodeSpan[x.first, x.last], change: -x.change, event: currentEvent]; ENDCASE => ERROR }; <> Insert: PUBLIC PROC [root, old: Node, where: Place ¬ after, event: Event ¬ NIL] RETURNS [new: Node] = { <> IF old=NIL THEN RETURN [NIL]; new ¬ TextNode.NewTextNode[]; Inherit[old, new]; DoInsertNode[root, old, new, where, event] }; InsertTextNode: PUBLIC PROC [root, old: Node, where: Place ¬ after, inherit: BOOLEAN ¬ FALSE, event: Event ¬ NIL] RETURNS [new: Node] = { <> 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: Node, allprops: BOOL ¬ FALSE] = { CopyProp: PROC [name: ATOM, value: REF] RETURNS [BOOL] = { IF allprops OR NodeProps.Is[name, $Inheritable] THEN { newvalue: REF ~ NodeProps.CopyInfo[name, value]; IF newvalue # NIL THEN NodeProps.PutProp[new, name, newvalue]; }; RETURN [FALSE]; }; new.format ¬ old.format; new.comment ¬ old.comment; IF allprops OR old.hasPrefix OR old.hasPostfix OR old.hasStyleDef THEN [] ¬ NodeProps.MapProps[old, CopyProp, FALSE, FALSE]; }; DoInsertNode: PROC [root, old, new: Node, where: Place, event: Event] = { dest, parent: Node; child: BOOLEAN ¬ FALSE; notify: REF InsertingNode Change; IF new = NIL OR old = NIL THEN RETURN; parent ¬ IF where = child THEN old ELSE TextNode.Parent[old]; SELECT where FROM sibling => { dest ¬ TextNode.LastWithin[old]; child ¬ FALSE }; after => { dest ¬ old; child ¬ FALSE }; child => { dest ¬ old; child ¬ TRUE }; ENDCASE => { IF parent.child = old THEN { dest ¬ parent; child ¬ TRUE } ELSE { dest ¬ TextNode.LastWithin[TextNode.Previous[old, parent]]; child ¬ FALSE } }; notify ¬ NEW[InsertingNode Change ¬ [InsertingNode[new: new, dest: dest]]]; EditNotify.Notify[notify, before]; SELECT TRUE FROM child => { -- insert as first child of dest new.next ¬ dest.child; new.parent ¬ dest; dest.child ¬ new; }; where = sibling => { -- insert as next sibling of old; don't adopt children new.next ¬ old.next; new.parent ¬ old.parent; old.next ¬ new; }; ENDCASE => { -- insert as next sibling of dest new.next ¬ dest.next; new.parent ¬ dest.parent; dest.next ¬ new; IF where=after THEN { EditSpanSupport.AdoptChildren[node: new, child: dest.child]; dest.child ¬ NIL; }; }; EditNotify.Notify[notify, after]; UndoEvent.Note[event, UndoInsertNode, notify] }; UndoInsertNode: PROC [undoRef: REF Change, currentEvent: Event] = { WITH undoRef SELECT FROM x: REF Change.InsertingNode => [] ¬ Delete[root: TextNode.Root[x.new], del: TextNode.MakeNodeSpan[x.new, x.new], event: currentEvent]; ENDCASE => ERROR; }; Split: PUBLIC PROC [root: Node, loc: Location, event: Event ¬ NIL] RETURNS [new: Node] = { <> <> <> <> <> <> old: Node = loc.node; IF old=NIL THEN RETURN [NIL]; new ¬ Insert[root, old, after, event]; IF loc.where # NodeItself AND old # NIL THEN { [] ¬ TextEdit.MoveText[destRoot: root, sourceRoot: root, dest: new, destLoc: 0, source: old, start: loc.where, event: event] } }; Merge: PUBLIC PROC [root, node: Node, event: Event ¬ NIL] RETURNS [loc: Location] = { <> <> <> pred: Node ¬ TextNode.StepBackward[node]; start: INT; IF pred = NIL OR TextNode.Parent[pred] = NIL OR node = NIL THEN ERROR CannotDoEdit; [start, ] ¬ TextEdit.CopyText[root, root, pred, INT.LAST, node, 0, INT.LAST, event]; [] ¬ Delete[root, TextNode.MakeNodeSpan[node, node], event]; RETURN [[pred, start]] }; <> ChangeLooks: PUBLIC PROC [root: Node, span: Span, remove, add: Looks, event: Event ¬ NIL] = { DoChange: PROC [node: Node, start, len: INT] RETURNS [stop: BOOL ¬ FALSE] = { TextEdit.ChangeLooks[root, node, remove, add, start, len, event]; }; EditSpanSupport.Apply[span, DoChange] }; <> ChangeCaps: PUBLIC PROC [root: Node, span: Span, how: CapChange ¬ allCaps, event: Event ¬ NIL] = { DoChange: PROC [node: Node, start, len: INT] RETURNS [stop: BOOL ¬ FALSE] = { TextEdit.ChangeCaps[root, node, start, len, how, event]; }; IF CheckForNil[span] THEN RETURN; EditSpanSupport.Apply[span, DoChange] }; END.