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]; EditSpanImpl: CEDAR PROGRAM IMPORTS EditSpan, EditSpanSupport, EditNotify, NodeProps, NodePropsExtras, TextEdit, TextNode, UndoEvent EXPORTS EditSpan = BEGIN OPEN EditSpan; 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: Ref, 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 # 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] = { 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] = { 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]; }; [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 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 [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[ 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] = { 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]; 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: 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 }]; [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] = { 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 }]; [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 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 } } }; ChangeNesting: PUBLIC PROC [root: Ref, 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: Ref = 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[ root, span.start.node, span.end.node, 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, currentEvent: Event] = { saved: REF Change _ NARROW[undoRef]; WITH saved SELECT FROM x: REF Change.NodeNesting => { [] _ ChangeNesting[x.root, TextNode.MakeNodeSpan[x.first, x.last], -x.change, currentEvent] }; ENDCASE => ERROR }; Insert: PUBLIC PROC [root, old: Ref, where: Place _ after, event: Event _ NIL] RETURNS [new: Ref] = { 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] = { 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] = { 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] = { 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]] }; ChangeLooks: PUBLIC PROC [root: Ref, span: Span, remove, add: Looks, event: Event _ NIL] = { DoChange: PROC [node: RefTextNode, start, len: INT] RETURNS [stop: BOOLEAN] = { TextEdit.ChangeLooks[root, node, remove, add, start, len, event]; RETURN [FALSE] }; EditSpanSupport.Apply[span, DoChange] }; ChangeCaps: PUBLIC PROC [root: Ref, span: Span, how: TextEdit.CapChange _ allCaps, event: Event _ NIL] = { DoChange: PROC [node: RefTextNode, start, len: INT] RETURNS [stop: BOOLEAN] = { TextEdit.ChangeCaps[root, node, start, len, how, event]; RETURN [FALSE] }; IF CheckForNil[span] THEN RETURN; EditSpanSupport.Apply[span, DoChange] }; END. ŽEditSpanImpl.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. 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 Last Edited by: Paul Rovner, August 10, 1983 4:25 pm Michael Plass, May 9, 1986 12:03:23 pm PDT Doug Wyatt, March 3, 1985 6:25:23 pm PST ***** Editing operations on spans replace dest span by copy of source span result is the new copy of source result is the new copy of source dest cannot be within source result is moved span split source and dest, if necessary, so can deal with entire nodes check for dest already in correct position check for dest inside source dest inside source newAlpha is new location of alpha span; ditto for newBeta now check for alpha beta adjacent or overlapping as special cases move beta nodes to before alpha nodes move alpha nodes to before beta nodes check for overlap like Replace, but moves source instead of copying it result is moved span get slices for dest and source check for overlap ***** Nesting moves span to a deeper nesting level in tree ***** New nodes; split & merge empty copy of old node is inserted in tree in position determined by "where" empty text node is inserted in tree 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 copies text of node to end of previous node then deletes node returns location of join in the merged node **** Change looks **** ***** Caps and Lowercase Κ˜šœ™Jšœ Οmœ1™Jšžœžœžœ˜!Jšžœ!ž˜&šœ.žœ-žœ‘ ˜qJ˜?Jšœžœ˜Jšœžœ%˜-J˜*J˜—Jšžœ9˜=J˜—J˜š  œžœžœ'žœžœžœ˜`Jšžœžœžœ˜ Jšžœž˜$šœ-žœ,žœ‘ ˜oJ˜>Jšœžœ˜Jšœžœ#˜+Jšžœžœ+˜?J˜3J˜—šžœ˜J˜(Jšžœžœ˜0J˜—J˜—J˜Jšœžœ˜'š œžœ!žœ˜MJšœ žœ žœ žœ&˜PJ˜9J˜—J˜š œžœ˜5J˜Jšœžœ=˜HJ˜—J˜š  œžœ˜%Jšœ4žœ˜8J˜—J˜š  œžœ žœ!˜AJšœ$žœ ˜4J˜—J˜š  œžœžœžœ˜3Jšœ žœ˜Jšžœ žœžœžœ ˜)šžœ žœž˜Jšœžœ žœ ˜6Jšžœžœ˜—J˜—J˜š  œžœ'žœ˜RJšžœžœ.˜>J˜—J˜š œžœžœ;˜LJšœžœžœ˜?Jšžœ˜Jšœ ™ Jš žœžœ žœžœžœ ˜?Jšžœ#žœ‘˜;JšžœN˜TJ˜*J˜.Jšœžœ˜J˜IJ˜—J˜š œžœ žœžœ˜Nšžœ*ž˜3Jšœ$ž˜'Jšœ0žœ0˜c—J˜—J˜Jš  œžœi˜{šžœ˜J˜=J˜GJšœ žœ˜Jšœ žœ˜&Jšœ žœ&˜4Jšœžœ˜˜:J˜>—JšžœB˜HJ˜—J˜š œžœžœ;˜LJšœžœžœ˜?Jšžœ˜Jšœ™Jšœ™J˜GJšœžœ˜Jšœžœ˜ Jšœ ‘+˜KJ˜J˜š  œžœ˜J˜ŒJ˜FJšœ7žœ˜;J˜—J˜š  œžœ˜'J˜NJ˜ J˜3J˜—J˜š  œžœ˜J˜Wšžœ.žœ‘˜UJšžœžœžœ˜Jšœ‘˜1J˜ Jšžœ˜J˜—J˜ J˜7J˜—J˜Jšžœžœžœ ˜.J˜Jšžœ#žœ‘˜;šžœ%˜+J˜)—J˜Jšžœ žœ'˜:šžœžœžœ˜J˜.J˜J˜HJ˜-J˜—J˜JšœB™BJ˜`J˜8J˜4J˜J˜šœ‘ ˜Jšœ*™*Jšžœž˜šœžœžœ˜NJšžœžœžœ!˜S—šžœ‘4˜;Jšžœ ž˜šžœžœžœž˜ J˜?Jšž˜—Jšžœžœ ž˜šžœžœžœž˜ J˜AJšžœ˜—J˜—šžœžœ‘V˜_Jšœ™˜4J˜\Jšœžœ˜*—šžœžœ‘˜0J˜LJ˜$Jšœžœ‘<˜UJ˜J˜—J˜KJšžœ;žœ˜AJšœ;žœžœ ˜QJšœ™šžœžœ‘˜AJ˜GJ˜ J˜*Jšžœ˜J˜—J˜šžœUž˜_JšœCžœ˜JJšœžœ˜#Jšžœ˜—šžœNžœ˜VJšœ3žœ˜8J˜—šžœ‘˜Jšœžœ˜šœ žœ#˜/J˜EJ˜D—J˜"J˜ZJ˜MJ˜(J˜ J˜!J˜-J˜—Jšžœ˜Jšžœ˜—J˜šžœ)žœ‘˜FJšœ žœžœ˜šžœ1žœ‘+˜eJšœžœ˜ J˜QJ˜—šžœ/žœ‘(˜`Jšœžœ˜ JšœOžœ˜UJ˜—šžœžœžœ‘(˜@J˜AJ˜—J˜—šžœžœ1žœ/žœ˜pJ˜AJ˜—J˜Jšœ‘˜0Jš œžœ žœžœžœ ‘˜ZJ˜Jšžœ ˜J˜šžœ˜J˜ J˜>Jšžœ˜J˜—J˜—J˜—J˜š  œžœ žœ˜;Jšœžœ žœ ˜$šžœžœž˜šœžœ˜˜$J˜GJ˜ —J˜—Jšžœžœ˜—J˜—J˜š  œžœžœ˜2Jšœ"žœ˜&Jšžœ˜%Jšœ9™9J˜J˜VJšœžœ˜Jšœžœ˜Jšœ5‘!˜VJ˜&J˜š  œžœ˜J˜ŒJ˜‹JšœHžœ˜LJ˜—J˜šœ‘ ˜ J˜Jšžœžœž˜/Jšžœ˜J˜šžœžœ‘˜7J˜EJ˜CJšœ žœ˜$Jšœ žœ$˜1Jšœ žœ˜"Jšœ žœ"˜.JšœBžœ˜F˜\J˜[—J˜YJ˜WJšžœ˜J˜—J˜Jšœ>‘ ˜^J˜J˜6J˜2J˜ J˜4J˜0J˜J˜J˜JšœA™Ašžœžœ‘˜@Jšœ%™%J˜wJ˜—šžœžœžœ‘˜EJšœ%™%J˜wJ˜—šžœ‘<˜DJšœ žœ˜Jšœ‘9˜KJ˜ ˜4J˜[Jšœžœ˜/—˜4J˜XJšœžœ˜/—Jšœ™˜-J˜L—šžœ žœ‘˜"J˜ Jšžœžœžœž˜0šžœžœžœ‘ ˜?˜J˜X—šžœžœ‘˜6J˜.J˜—šžœ‘˜#J˜/J˜—J˜—šžœžœžœ‘˜<˜J˜U—šžœžœ‘ ˜JšœA‘˜QJ˜?˜5J˜%—˜6J˜%—J˜+J˜—J˜—J˜šžœ0žœ/žœ‘˜|J˜NJ˜MJ˜—šžœ.žœ-žœ‘˜vJšœLžœ˜RJšœKžœ˜QJ˜3J˜—J˜Jš œžœžœžœžœ‘˜eJš œžœžœžœžœ˜NJ˜Jšžœ˜J˜šžœ˜J˜MJšžœ˜J˜—J˜—J˜—J˜š œžœžœ1˜FJšœžœžœžœ˜1Jšžœ˜Jšœ4™4Jšœ™Jšœ žœ˜Jšœ‘9˜TJ˜ J˜VJšœžœ˜Jšœžœ˜J˜š  œžœ˜J˜ŒJ˜‹JšœHžœ˜LJ˜—J˜šœ‘ ˜ J˜Jšžœžœžœžœ ˜CJ˜šžœžœ‘˜3šžœ@˜FJ˜—J˜—J˜J˜?J˜˜4J˜YJšœžœ˜0—˜4J˜\Jšœžœ˜0—J˜Jšœ™˜-J˜M—J˜ J˜Jšœ™šžœ žœ‘0˜BJšžœžœžœžœ ˜=šžœžœ‘˜6Jšžœžœžœ ‘˜FJšžœ˜J˜—šžœžœžœ‘˜6Jšžœž˜šœ‘!˜5Jšžœžœ ˜—Jšœ‘!˜#—šžœ‘˜!šžœžœžœ˜.J˜8J˜—šžœžœžœ˜2Jšžœžœžœ˜3Jšžœžœ ˜—J˜—J˜—J˜J˜AJ˜0J˜.J˜IJšžœžœ"˜6Jšžœ˜J˜šžœ˜JšœBžœ˜UJ˜—J˜—J˜—J˜—šœ ™ š   œžœžœ!žœžœ˜WJšžœ˜Jšœ,™,J˜2Jšœ žœ˜Jšœžœ˜ J˜š  œžœ˜J˜ˆJšœ žœ˜$J˜—J˜šœ‘ ˜ J˜Jšžœžœžœ ˜,J˜Jšœ<‘ ˜\J˜šžœ‘3˜6˜.J˜XJšœžœ˜(—Jšžœžœžœ‘˜<šžœVž˜`˜J˜4J˜NJ˜ J˜Jšžœ˜J˜—˜ J˜SJšžœ,žœžœ ˜DJ˜ J˜Jšžœ˜J˜—Jšžœ‘˜Jšœžœ˜šœ žœ#˜/J˜0—J˜"J˜)J˜,J˜OJ˜ J˜!J˜1J˜—Jšžœ˜Jšžœ˜—J˜Jšžœ˜J˜šžœ˜JšœLžœ˜_J˜—J˜—J˜—J˜š œžœ žœ˜?Jšœžœ žœ ˜$šžœžœž˜šœžœ˜J˜\J˜—Jšžœžœ˜—J˜—J˜J˜—šœ™š  œžœžœ7žœžœ˜eJšœL™LJš žœžœžœžœžœ˜J˜J˜J˜+J˜—J˜š œžœžœ1žœžœžœžœ˜Jšœ#™#Jš žœžœžœžœžœ˜J˜Jšžœ žœ˜"J˜+J˜—J˜š  œžœžœžœžœ˜@š  œžœžœ žœžœžœ˜:codešžœ žœ(žœ˜Kšœ˜—Jšžœžœ˜J˜—J˜ Jš žœ žœžœžœž˜FJšœ'žœžœ˜5Jš žœžœžœžœžœ˜:J˜—J˜š  œžœ6˜HJ˜Jšœžœ˜Jšœžœ˜!Jš žœžœžœžœžœžœ˜&Jšœ žœžœžœ˜=Jšžœžœ,žœ˜JJšžœžœžœžœ˜8Jšžœžœžœžœ˜7šžœ˜Jšžœžœžœ˜:JšžœGžœ˜RJ˜—Jšœ žœ:˜FJ˜"šžœžœ‘ ˜0Jšžœžœžœ%žœ˜DJšžœžœ˜*J˜J˜—šžœžœžœ‘6˜UJ˜)Jšœ žœ˜!J˜—šžœ‘!˜(J˜+Jšœ žœ˜#š žœžœžœžœ‘˜SJ˜+Jšœ žœ˜J˜—J˜—J˜!J˜.J˜—J˜š œžœ žœ˜