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; 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] = { 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; 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] = { 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] = { 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]; }; [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 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 [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; 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] = { 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]; IF afterAlpha = beta.start.node THEN { -- alpha just before beta [] _ Move[alphaRoot,betaRoot,NodeLoc[alpha.start],NodeSpan[beta],FALSE,before,0,event] } ELSE IF afterBeta = alpha.start.node THEN { -- beta just before alpha [] _ 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 }]; [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] = { 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 }]; [overlap,head,tail,startOrder,endOrder] _ SliceOrder[dest,source,aBefore,aBottom,bBefore,bBottom]; FreeSlices[]; 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 }}}; ChangeNesting: PUBLIC PROC [root: Ref, span: Span, change: INTEGER, event: Event _ NIL] RETURNS [new: Span] = { 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 }; Insert: PUBLIC PROC [root, old: Ref, where: Place _ after, event: Event _ NIL] RETURNS [new: Ref] = TRUSTED { 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] = { 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] = { 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] = { 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] = { 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]] }; 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] }; 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] }; Start: PUBLIC PROC = {}; -- for initialization only END. Ì-- 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 -- ***** Editing operations on spans -- replace dest span by copy of source span -- result is the new copy of source --really REF ChangingTextForPaste Change OR ChangingSpanForPaste Change -- 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 -- empty "other" kind of 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 **** -- ***** Cap's and Lowercase -- ***** Initialization ʶ˜JšÏc™Jš$™$Jš6™6J™1JšÏk ˜ J˜ J˜J˜ J˜ J˜J˜ J˜ J˜ J˜ J˜šœž œ˜JšžœP˜WJšžœ ˜—Jšž˜Jšžœ'˜+J˜Jšœžœžœžœ˜"Jšœžœ ˜*J˜Jš$™$J˜š Ïn œžœžœžœžœ˜;Jšžœžœžœžœ˜8J˜—šŸœžœžœ8žœžœžœžœžœ˜ŽJšžœ˜Jš+™+Jš#™#J˜šžœžœ˜5šžœA˜GJ˜5——J˜J˜.Jšœžœ˜J˜JJšžœžœ#˜7J˜—šŸ œžœžœžœ˜:šžœ"ž˜+Jšœžœž˜AJšœ$ž˜'Jšœ žœ"˜EJ˜——šŸ œžœ0˜AJšœžœ˜J˜7Jšžœ˜J˜CJ˜GJ˜%J˜1J˜)J˜7J˜Jšœ!žœžœ žœ ˜FJšžœžœ4˜H˜7J˜C—JšžœA˜GJ˜—šŸ œžœžœžœ˜>Jšžœžœžœ˜!šžœ!ž˜&šœžœžœ ˜QJ˜?J˜!˜0J˜(———Jšžœ*˜.J˜—š Ÿœžœžœ'žœžœžœ˜`Jšžœžœžœ˜ šžœž˜$šœžœžœ ˜OJ˜>J˜ J˜.Jšžœžœ(˜J˜—šŸ œžœ˜'J˜MJ˜ J˜3J˜—šŸ œžœ˜J˜Všžœžœ˜EJšžœžœžœ˜Jšœ˜1J˜ Jšžœ˜ —J˜ J˜7J˜—Jšžœžœžœ˜7J˜šžœ"žœ˜:šžœ#˜)J˜?J˜——Jšžœ žœ'˜:šžœžœžœ˜J˜.J˜J˜HJ˜J˜—J˜JšE™EJ˜IJ˜8J˜4J˜J˜Jšœ ˜Jš-™-šžœž˜#šœžœžœ˜NJšžœžœžœ!˜S—šžœ4˜;šžœ ž˜šžœžœžœž˜ J˜-Jšž˜——šžœžœ ž˜šžœžœžœž˜ J˜/Jšžœ˜ ————šžœžœV˜_Jš™˜/˜;Jšœžœ˜3——šžœžœ˜9J˜,J˜$Jšœžœ<˜UJ˜—J˜8šžœ*žœ˜0šœ*žœžœ ˜@Jš™——šžœžœ˜AJ˜FJ˜ J˜(Jšžœ˜—J˜šžœ@ž˜JJšœ3žœ˜:Jšœžœ˜#Jšžœ˜—šžœ.žœ˜6Jšœ#žœ˜)—šžœ˜Jšœžœ˜šœžœ#˜>J˜@J˜2—J˜J˜8J˜8J˜J˜ J˜J˜-—Jšžœ˜Jšžœ˜J˜—šžœžœ˜6Jšœ žœžœ˜šžœ!žœ+˜UJšœžœ˜ J˜?—šžœžœ(˜PJšœžœ˜ Jšœ:žœ˜B—šžœžœžœ(˜@J˜A——šžœžœ!žœžœ˜PJ˜AJ˜—Jšžœžœ1˜>J˜Jšœ˜0Jš œžœ žœžœžœ ˜YJ˜Jšžœ ˜J˜šžœ˜J˜ J˜+Jšžœ˜J˜——šŸ œžœ žœžœ˜CJšœžœ žœ ˜$Jšžœ žœž˜˜˜#J˜DJšžœ$˜)——Jšžœžœ˜J˜—šŸ œžœžœ˜1Jšœžœžœžœ˜>Jšžœ˜%Jš<™J˜0—J˜J˜J˜J˜:J˜ J˜J˜1—Jšžœ˜Jšžœ˜J˜Jšžœ˜J˜šžœ˜Jšœ9žœ˜PJ˜——šŸœžœ žœžœ˜GJšœžœ žœ ˜$Jšžœ žœž˜˜J˜Z—Jšžœžœ˜J˜J˜—Jš!™!J˜šŸœžœžœ7žœ˜NJšžœžœ˜JšO™OJš žœžœžœžœžœ˜šžœžœž˜J˜%J˜'Jšžœžœ˜—J˜J˜)J˜—šŸœžœž˜Jšœ0žœžœžœ˜TJšžœ˜Jš&™&Jš žœžœžœžœžœ˜J˜Jšžœ žœ˜!J˜)J˜—šŸœžœž˜Jšœ0žœžœžœ˜TJšžœ˜Jš1™1Jš žœžœžœžœžœ˜J˜Jšžœ žœ˜!J˜)J˜—š Ÿœžœžœžœžœ˜@J˜š Ÿœžœžœ žœžœžœ˜:Jšœ žœ˜šžœ žœžœž˜"Jšœ žœ˜AJšžœžœžœ˜6—šžœ/žœž˜9Jšœ%˜%—Jšžœžœ˜—J˜š žœ žœžœžœž˜FJšœ'žœžœ˜5—šžœ-žœž˜6šœ,žœž˜4J˜"J˜———šŸ œžœ6˜HJ˜Jšœžœ˜Jšœžœ˜!Jš žœžœžœžœžœžœ˜&Jšœ žœžœžœ˜=Jšžœžœ,žœ˜JJšžœžœžœžœ˜8Jšžœžœžœžœ˜7šžœ˜Jšžœžœžœ˜:JšžœFžœ˜S—Jšœžœ8˜SJ˜šžœžœ ˜0Jšžœžœžœ%žœ˜DJšžœžœ˜*J˜—šžœžœžœ6˜UJšœ)˜)Jšœ žœ˜"—šžœ!˜(J˜+Jšœ žœ˜#š žœžœžœžœ˜SJ˜+Jšœ žœ˜——J˜J˜.J˜—šŸœžœ žœžœ˜DJšœžœ žœ ˜$Jšžœ žœž˜J˜YJšžœžœ˜J˜—š Ÿœžœžœ+žœžœ˜XšL™LJš&™&—š>™>Jš)™)Jš*™*—Jš™J˜J˜Jš žœžœžœžœžœ˜J˜#šžœžœ-žœžœ˜TJ˜(˜RJ˜3J˜———š Ÿœžœžœ"žœžœ˜TJš.™.Jš™Jš.™.Jš™J˜KJ˜3J˜Jšžœžœžœžœžœžœžœžœ˜RJ˜GJ˜7Jšžœ˜J˜J˜—Jš™J˜šŸ œžœžœ<žœ˜\šŸœžœ)žœžœ˜RJšœ<žœžœ˜M—J˜J˜J˜—Jš™J˜šŸ œžœžœKžœ˜jšŸœžœ)žœžœ˜RJšœ4žœžœ˜E—Jšžœžœžœ˜!J˜J˜—Jš™J˜JšŸœžœžœ˜3J˜Jšžœ˜J˜—…—hè‡j