<<-- TextEditImpl.mesa>> <<-- written by Bill Paxton, March 1981>> <<-- last edit by Bill Paxton, December 28, 1982 11:43 am>> <> DIRECTORY TextEdit, EditNotify, UndoEvent, NodeProps, RopeEdit, RopeFrom, RopeReader, TextLooks, TextNode, NodeAddrs, Atom, Rope, CheckNode; TextEditImpl: CEDAR MONITOR IMPORTS npI:NodeProps, RopeEdit, TextLooks, EditNotify, UndoEvent, NodeAddrs, TextNode, RopeFrom, RopeReader, Rope, Atom, CheckNode EXPORTS TextEdit = BEGIN OPEN TextEdit, EditNotify; Runs: TYPE = TextLooks.Runs; <<-- **** Cache of notify records ****>> scratch1, scratch2, scratch3: REF ChangingText Change; Create: PROC RETURNS [REF ChangingText Change] = { RETURN [TextNode.pZone.NEW[ChangingText Change]] }; Alloc: ENTRY PROC RETURNS [scratch: REF ChangingText Change] = { ENABLE UNWIND => NULL; IF scratch3 # NIL THEN { scratch _ scratch3; scratch3 _ NIL } ELSE IF scratch2 # NIL THEN { scratch _ scratch2; scratch2 _ NIL } ELSE IF scratch1 # NIL THEN { scratch _ scratch1; scratch1 _ NIL } ELSE scratch _ Create[] }; Free: ENTRY PROC [scratch: REF ChangingText Change] = { ENABLE UNWIND => NULL; IF scratch3 = scratch OR scratch2 = scratch OR scratch1 = scratch THEN ERROR; IF scratch3 = NIL THEN scratch3 _ scratch ELSE IF scratch2 = NIL THEN scratch2 _ scratch ELSE IF scratch1 = NIL THEN scratch1 _ scratch }; <<>> <<-- **** Text Editing Operations ****>> DoReplace: PROC [root: Ref, dest: RefTextNode, destStart, destLen, destSize: Offset, sourceRope: ROPE, sourceRuns: Runs, sourceStart, sourceLen: Offset, event: Event] RETURNS [resultStart, resultLen: Offset] = { destRope, replaceRope, newRope: ROPE; size: Offset; notify: REF ChangingText Change; alreadysaved: BOOLEAN; destRuns, replaceRuns, newRuns: Runs; special: BOOLEAN; size _ destSize-destLen+sourceLen; destRuns _ dest.runs; destRope _ dest.rope; notify _ IF (alreadysaved _ AlreadySaved[dest,event]) THEN Alloc[] ELSE TextNode.pZone.NEW[ChangingText Change]; notify^ _ [ChangingText[root,dest,destStart,sourceLen,destLen,destRope,destRuns]]; Notify[notify,before]; IF sourceLen > 0 THEN { replaceRope _ RopeEdit.Substr[sourceRope,sourceStart,sourceLen]; replaceRuns _ TextLooks.Substr[sourceRuns,sourceStart,sourceLen]}; special _ FALSE; IF destLen=0 THEN { -- doing an insert IF destStart=0 THEN { -- insert at start newRope _ RopeEdit.Concat[replaceRope,destRope,sourceLen,destSize]; newRuns _ TextLooks.Concat[replaceRuns,destRuns,sourceLen,destSize]; special _ TRUE } ELSE IF destStart=destSize THEN { -- insert at end newRope _ RopeEdit.Concat[destRope,replaceRope,destSize,sourceLen]; newRuns _ TextLooks.Concat[destRuns,replaceRuns,destSize,sourceLen]; special _ TRUE }} ELSE IF sourceLen=0 THEN { -- doing a delete IF destStart=0 THEN { -- delete from start newRope _ RopeEdit.Substr[destRope,destLen,size]; newRuns _ TextLooks.Substr[destRuns,destLen,size]; special _ TRUE } ELSE IF (destStart+destLen)=destSize THEN { -- delete from end newRope _ RopeEdit.Substr[destRope,0,size]; newRuns _ TextLooks.Substr[destRuns,0,size]; special _ TRUE }}; IF ~special THEN { newRope _ RopeEdit.Replace[destRope,destStart,destLen, replaceRope,destSize,sourceLen]; newRuns _ TextLooks.Replace[destRuns,destStart,destLen, replaceRuns,destSize,sourceLen] }; dest.rope _ newRope; dest.runs _ newRuns; NodeAddrs.Replace[dest,destStart,destLen,sourceLen]; BumpCount[dest]; Notify[notify,after]; IF alreadysaved THEN Free[notify] ELSE UndoEvent.Note[event,UndoChangeText,notify]; RETURN [destStart,sourceLen] }; MoveText: PUBLIC PROC [ destRoot, sourceRoot: Ref, dest: RefTextNode, destLoc: Offset _ 0, source: RefTextNode, start: Offset _ 0, len: Offset _ MaxLen, event: Event _ NIL] RETURNS [resultStart, resultLen: Offset] = { MoveAddrs: PROC [addr: REF, location: Offset] RETURNS [quit: BOOLEAN] = { IF location NOT IN [start..start+len) THEN RETURN [FALSE]; IF dest=source THEN { -- pin addr at new location new: Offset _ destLoc+location-start; IF destLoc > start THEN new _ new-len; -- moving toward end of node NodeAddrs.PutTextAddr[dest, addr, new]; NodeAddrs.PinTextAddr[dest, addr] -- so it won't be changed by Replace -- } ELSE NodeAddrs.MoveTextAddr[ -- move the addr to the new node from: source, to: dest, addr: addr, location: destLoc+location-start]; RETURN [FALSE] }; destSize, sourceSize: Offset; sourceSize _ RopeEdit.Size[source.rope]; start _ MIN[MAX[0,start],sourceSize]; len _ MIN[MAX[0,len],sourceSize-start]; destSize _ IF dest=source THEN sourceSize ELSE RopeEdit.Size[dest.rope]; destLoc _ MIN[MAX[0,destLoc],destSize]; IF source=dest AND destLoc IN [start..start+len] THEN RETURN [start,len]; IF len=0 THEN RETURN [destLoc,0]; [] _ NodeAddrs.MapTextAddrs[source, MoveAddrs]; [resultStart, resultLen] _ CopyText[destRoot, sourceRoot, dest, destLoc, source, start, len, event]; IF dest=source THEN { -- need to adjust locations IF destLoc > start THEN resultStart _ destLoc-len -- moving toward end of node ELSE start _ start+len -- moving toward start of node-- }; DeleteText[sourceRoot, source, start, len, event]; IF dest=source THEN NodeAddrs.UnpinAll[source] }; TransposeText: PUBLIC PROC [ alphaRoot, betaRoot: Ref, alpha: RefTextNode, alphaStart: Offset _ 0, alphaLen: Offset _ MaxLen, beta: RefTextNode, betaStart: Offset _ 0, betaLen: Offset _ MaxLen, event: Event _ NIL] RETURNS [alphaResultStart, alphaResultLen, betaResultStart, betaResultLen: Offset] = { SwitchResults: PROC = { start, len: Offset; start _ betaResultStart; len _ betaResultLen; betaResultStart _ alphaResultStart; betaResultLen _ alphaResultLen; alphaResultStart _ start; alphaResultLen _ len }; alphaSize, betaSize: Offset; switched: BOOLEAN _ FALSE; alphaSize _ RopeEdit.Size[alpha.rope]; alphaStart _ MIN[MAX[0,alphaStart],alphaSize]; alphaResultLen _ alphaLen _ MIN[MAX[0,alphaLen], alphaSize-alphaStart]; betaSize _ IF beta=alpha THEN alphaSize ELSE RopeEdit.Size[beta.rope]; betaStart _ MIN[MAX[0,betaStart],betaSize]; betaResultLen _ betaLen _ MIN[MAX[0,betaLen],betaSize-betaStart]; IF alpha=beta THEN { alphaEnd: Offset; IF alphaStart > betaStart THEN { -- switch them start: Offset _ alphaStart; len: Offset _ alphaLen; root: Ref _ alphaRoot; alphaStart _ betaStart; betaStart _ start; alphaResultLen _ alphaLen _ betaLen; alphaRoot _ betaRoot; betaRoot _ root; betaResultLen _ betaLen _ len; switched _ TRUE }; alphaEnd _ alphaStart+alphaLen; IF alphaEnd = betaStart THEN { -- turn into a move instead [] _ MoveText[alphaRoot,betaRoot,alpha,alphaStart,alpha,betaStart,betaLen,event]; betaResultStart _ alphaStart; alphaResultStart _ alphaStart+betaLen; IF switched THEN SwitchResults; RETURN }; IF alphaEnd > betaStart THEN { -- overlapping overlap, alphaHeadLen, betaTailLen: Offset; alphaHeadLen _ betaStart-alphaStart; betaTailLen _ betaStart+betaLen-alphaEnd; IF alphaHeadLen < 0 OR betaTailLen < 0 THEN RETURN [alphaStart,alphaLen,betaStart,betaLen]; overlap _ alphaEnd-betaStart; [alphaResultStart,alphaResultLen,betaResultStart,betaResultLen] _ TransposeText[alphaRoot,betaRoot,alpha,alphaStart,alphaHeadLen, alpha,alphaEnd,betaTailLen,event]; betaResultLen _ betaResultLen+overlap; alphaResultStart _ alphaResultStart-overlap; alphaResultLen _ alphaResultLen+overlap; IF switched THEN SwitchResults; RETURN }}; [alphaResultStart, alphaResultLen] _ MoveText[ -- move alpha after beta destRoot: betaRoot, sourceRoot: alphaRoot, dest: beta, destLoc: betaStart+betaLen, source: alpha, start: alphaStart, len: alphaLen, event: event]; IF alpha=beta THEN betaStart _ betaStart-alphaLen ELSE alphaResultStart _ alphaResultStart-betaLen; [betaResultStart, betaResultLen] _ MoveText[ -- move beta to old alpha location destRoot: alphaRoot, sourceRoot: betaRoot, dest: alpha, destLoc: alphaStart, source: beta, start: betaStart, len: betaLen, event: event]; IF switched THEN SwitchResults }; ReplaceByChar: PUBLIC PROC [ root: Ref, dest: RefTextNode, char: CHAR, start: Offset _ 0, len: Offset _ MaxLen, inherit: BOOLEAN _ TRUE, looks: Looks _ noLooks, event: Event _ NIL] RETURNS [resultStart, resultLen: Offset] = { <<-- if inherit is false, char gets specifed looks >> <<-- if inherit is true, char gets looks in following manner:>> <<-- if dest length is 0, then gets looks from argument list, else>> <<-- if start > 0, then looks and list of previous char,>> <<-- else looks of char following replacement>> notify: REF ChangingText Change; destRope, newRope: ROPE; destSize: Offset; alreadysaved: BOOLEAN; IF dest=NIL THEN RETURN [0,0]; destSize _ RopeEdit.Size[destRope _ dest.rope]; start _ MIN[MAX[0,start],destSize]; len _ MIN[MAX[0,len],destSize-start]; notify _ IF (alreadysaved _ AlreadySaved[dest,event]) THEN Alloc[] ELSE TextNode.pZone.NEW[ChangingText Change]; notify^ _ [ChangingText[root,dest,start,1,len,destRope,dest.runs]]; Notify[notify,before]; newRope _ RopeEdit.ReplaceByChar[destRope,char,start,len,destSize]; dest.runs _ TextLooks.ReplaceByRun[dest.runs,start,len,1,destSize,inherit,looks]; dest.rope _ newRope; NodeAddrs.Replace[dest,start,len,1]; BumpCount[dest]; Notify[notify,after]; IF alreadysaved THEN Free[notify] ELSE UndoEvent.Note[event,UndoChangeText,notify]; RETURN [start,1] }; ReplaceByString: PUBLIC PROC [ root: Ref, dest: RefTextNode, string: REF READONLY TEXT, stringStart: NAT _ 0, stringNum: NAT _ MaxNat, start: Offset _ 0, len: Offset _ MaxLen, inherit: BOOLEAN _ TRUE, looks: Looks _ noLooks, event: Event _ NIL] RETURNS [resultStart, resultLen: Offset] = { notify: REF ChangingText Change; destRope, newRope: ROPE; alreadysaved: BOOLEAN; destSize: Offset; IF dest=NIL THEN RETURN [0,0]; stringNum _ IF string=NIL OR stringStart >= string.length THEN 0 ELSE MIN[stringNum, string.length-stringStart]; destSize _ RopeEdit.Size[destRope _ dest.rope]; start _ MIN[MAX[0,start],destSize]; len _ MIN[MAX[0,len],destSize-start]; notify _ IF (alreadysaved _ AlreadySaved[dest,event]) THEN Alloc[] ELSE TextNode.pZone.NEW[ChangingText Change]; notify^ _ [ChangingText[root,dest,start,stringNum,len,destRope,dest.runs]]; Notify[notify,before]; newRope _ RopeEdit.ReplaceByString[destRope,string,stringStart,stringNum, start,len,destSize]; dest.runs _ TextLooks.ReplaceByRun[dest.runs,start,len,stringNum, destSize,inherit,looks]; dest.rope _ newRope; NodeAddrs.Replace[dest,start,len,stringNum]; BumpCount[dest]; Notify[notify,after]; IF alreadysaved THEN Free[notify] ELSE UndoEvent.Note[event,UndoChangeText,notify]; RETURN [start,stringNum] }; UndoChangeText: PROC [undoRef: REF, currentEvent: Event] = TRUSTED { saved: REF Change _ NARROW[undoRef]; WITH x:saved SELECT FROM ChangingText => [] _ ReplaceByText[x.root,x.text,0,MaxLen, x.oldRope,x.oldRuns,0,MaxLen,currentEvent]; <<-- must replace all of text since only save first change per event>> ENDCASE => ERROR }; ReplaceByText: PUBLIC PROC [ root: Ref, dest: RefTextNode, destStart: Offset _ 0, destLen: Offset _ MaxLen, sourceRope: ROPE, sourceRuns: Runs, sourceStart: Offset _ 0, sourceLen: Offset _ MaxLen, event: Event _ NIL] RETURNS [resultStart, resultLen: Offset] = { sourceSize, destSize: Offset; IF dest=NIL THEN RETURN [0,0]; sourceSize _ RopeEdit.Size[sourceRope]; sourceStart _ MIN[MAX[0,sourceStart],sourceSize]; IF sourceLen # 0 THEN { IF sourceStart >= sourceSize THEN sourceStart _ sourceSize; sourceLen _ MIN[MAX[0,sourceLen], sourceSize-sourceStart]}; destSize _ RopeEdit.Size[dest.rope]; destStart _ MIN[MAX[0,destStart],destSize]; destLen _ MIN[MAX[0,destLen],destSize-destStart]; IF destLen=0 AND sourceLen=0 THEN RETURN [destStart,0]; [resultStart,resultLen] _ DoReplace[root,dest,destStart,destLen,destSize, sourceRope,sourceRuns,sourceStart,sourceLen,event] }; ReplaceText: PUBLIC PROC [ destRoot, sourceRoot: Ref, dest: RefTextNode, destStart: Offset _ 0, destLen: Offset _ MaxLen, source: RefTextNode, sourceStart: Offset _ 0, sourceLen: Offset _ MaxLen, event: Event _ NIL] RETURNS [resultStart, resultLen: Offset] = { <<-- replace the dest text by a copy of the source text>> <<-- names that are in the replaced text move to destStart>> <<-- names that are after the replaced text are adjusted>> sourceRope: ROPE; sourceSize, destSize: Offset; sourceRuns: Runs; IF dest=NIL THEN RETURN [0,0]; IF source # NIL THEN { sourceRope _ source.rope; sourceRuns _ source.runs }; sourceSize _ RopeEdit.Size[sourceRope]; sourceStart _ MIN[MAX[0,sourceStart],sourceSize]; IF sourceLen # 0 THEN { IF sourceStart >= sourceSize THEN sourceStart _ sourceSize; sourceLen _ MIN[MAX[0,sourceLen], sourceSize-sourceStart]}; destSize _ RopeEdit.Size[dest.rope]; destStart _ MIN[MAX[0,destStart],destSize]; destLen _ MIN[MAX[0,destLen],destSize-destStart]; IF destLen=0 AND sourceLen=0 THEN RETURN [destStart,0]; [resultStart,resultLen] _ DoReplace[destRoot,dest,destStart,destLen,destSize, sourceRope,sourceRuns,sourceStart,sourceLen,event] }; DeleteText: PUBLIC OneSpanProc = { [] _ ReplaceText[root, NIL, text, start, len, NIL, 0, MaxLen, event] }; CopyText: PUBLIC DestSpanProc = { -- copy the specified text [resultStart,resultLen] _ ReplaceText[destRoot, sourceRoot, dest, destLoc, 0, source, start, len, event] }; MoveTextOnto: PUBLIC PROC [ destRoot, sourceRoot: Ref, dest: RefTextNode, destStart: Offset _ 0, destLen: Offset _ MaxLen, source: RefTextNode, sourceStart: Offset _ 0, sourceLen: Offset _ MaxLen, event: Event _ NIL] RETURNS [resultStart, resultLen: Offset] = { sourceSize, destSize, start, len, end, destEnd: Offset; start _ sourceStart; len _ sourceLen; sourceSize _ Size[source]; start _ MIN[MAX[0,start],sourceSize]; len _ MIN[MAX[0,len],sourceSize-start]; end _ start+len; destSize _ IF dest=source THEN sourceSize ELSE Size[dest]; destStart _ MIN[MAX[0,destStart],destSize]; destLen _ MIN[MAX[0,destLen],destSize-destStart]; destEnd _ destStart+destLen; IF source=dest THEN { -- check for overlapping or adjacent IF start IN [destStart..destEnd) THEN { IF end < destEnd THEN [] _ DeleteText[sourceRoot,source,end,destEnd-end,event]; IF start > destStart THEN [] _ DeleteText[sourceRoot,source,destStart,start-destStart,event]; RETURN [destStart,len] }; IF end IN (destStart..destEnd) THEN { [] _ DeleteText[sourceRoot,source,end,destEnd-end,event]; RETURN [start,len] }; IF start <= destStart AND end >= destEnd THEN RETURN [start,len]; IF end=destStart THEN { [] _ DeleteText[sourceRoot,source,destStart,destLen,event]; RETURN [start,len] }; IF start=destEnd THEN { [] _ DeleteText[sourceRoot,source,destStart,destLen,event]; RETURN [destStart,len] }}; [] _ DeleteText[destRoot,dest,destStart,destLen,event]; IF source=dest AND start > destStart THEN start _ start-destLen; [resultStart,resultLen] _ MoveText[destRoot,sourceRoot,dest,destStart,source,start,len,event] }; ReplaceByRope: PUBLIC PROC [ root: Ref, dest: RefTextNode, rope: ROPE, start: Offset _ 0, len: Offset _ MaxLen, inherit: BOOLEAN _ TRUE, looks: Looks _ noLooks, event: Event] RETURNS [resultStart, resultLen: Offset] = { notify: REF ChangingText Change; destRope, newRope: ROPE; alreadysaved: BOOLEAN; destSize, ropeSize: Offset; IF dest=NIL THEN RETURN [0,0]; ropeSize _ RopeEdit.Size[rope]; destSize _ RopeEdit.Size[destRope _ dest.rope]; start _ MIN[MAX[0,start],destSize]; len _ MIN[MAX[0,len],destSize-start]; notify _ IF (alreadysaved _ AlreadySaved[dest,event]) THEN Alloc[] ELSE TextNode.pZone.NEW[ChangingText Change]; notify^ _ [ChangingText[root,dest,start,ropeSize,len,destRope,dest.runs]]; Notify[notify,before]; newRope _ RopeEdit.Replace[destRope,start,len,rope,destSize,ropeSize]; dest.runs _ TextLooks.ReplaceByRun[dest.runs,start,len,ropeSize,destSize,inherit,looks]; dest.rope _ newRope; NodeAddrs.Replace[dest,start,len,ropeSize]; BumpCount[dest]; Notify[notify,after]; IF alreadysaved THEN Free[notify] ELSE UndoEvent.Note[event,UndoChangeText,notify]; RETURN [start,ropeSize] }; <<-- **** Operation to add or delete looks ****>> ChangeLooks: PUBLIC PROC [ root: Ref, text: RefTextNode, remove, add: Looks, start: Offset _ 0, len: Offset _ MaxOffset, event: Event _ NIL] = { <<-- first remove then add in the given range>> size: Offset; newRuns: Runs; IF text=NIL THEN RETURN; size _ RopeEdit.Size[text.rope]; start _ MIN[MAX[0,start],size]; len _ MIN[MAX[0,len],size-start]; IF len=0 THEN RETURN; newRuns _ TextLooks.ChangeLooks[text.runs,size,remove,add,start,len]; IF newRuns=text.runs THEN RETURN; -- no change DoChangeLooks[root,text,newRuns,start,len,event] }; DoChangeLooks: PROC [root: Ref, text: RefTextNode, newRuns: Runs, start, len: Offset, event: Event] = { oldRuns: Runs _ text.runs; notify: REF ChangingText Change; alreadysaved: BOOLEAN; notify _ IF (alreadysaved _ AlreadySaved[text,event]) THEN Alloc[] ELSE TextNode.pZone.NEW[ChangingText Change]; notify^ _ [ChangingText[root,text,start,len,len,text.rope,oldRuns]]; Notify[notify,before]; text.runs _ newRuns; BumpCount[text]; Notify[notify,after]; IF alreadysaved THEN Free[notify] ELSE UndoEvent.Note[event,UndoChangeText,notify] }; <<-- **** Changing Style / Type of node ****>> ChangeType: PUBLIC PROC [node: Ref, typeName: TextNode.TypeName, event: Event _ NIL, root: Ref _ NIL] = { notify: REF ChangingType Change; oldTypeName: TextNode.TypeName _ node.typename; IF node=NIL THEN RETURN; IF root=NIL THEN root _ TextNode.Root[node]; IF oldTypeName = typeName THEN RETURN; -- no change notify _ TextNode.pZone.NEW[ChangingType Change _ [ChangingType[root,node,typeName,oldTypeName]]]; Notify[notify,before]; node.typename _ typeName; Notify[notify,after]; IF event#NIL THEN UndoEvent.Note[event,UndoChangeType,notify] }; UndoChangeType: PROC [undoRef: REF, currentEvent: Event] = TRUSTED { saved: REF Change _ NARROW[undoRef]; WITH x:saved SELECT FROM ChangingType => ChangeType[x.node,x.oldTypeName,currentEvent,x.root]; ENDCASE => ERROR }; prefixAtom: ATOM = npI.PrefixAtom[]; prefixName: ROPE = npI.PrefixName[]; ChangeStyle: PUBLIC PROC [node: Ref, name: ROPE, event: Event _ NIL, root: Ref _ NIL] = { newprefix: ROPE; IF node=NIL THEN RETURN; IF RopeEdit.Size[name] > 0 THEN newprefix _ RopeEdit.Concat["\"",RopeEdit.Concat[name,"\" style"]]; PutProp[node,prefixName,newprefix,event,root] }; PutProp: PUBLIC PROC [node: Ref, name: ROPE, value: REF, event: Event _ NIL, root: Ref _ NIL] = { notify: REF ChangingProp Change; key: ATOM; oldval: REF; IF node=NIL THEN RETURN; IF root=NIL THEN root _ TextNode.Root[node]; key _ Atom.MakeAtom[name]; IF (oldval _ npI.GetProp[node,key]) = value THEN RETURN; notify _ TextNode.pZone.NEW[ChangingProp Change _ [ChangingProp[root,node,name,key,value,oldval]]]; Notify[notify,before]; npI.PutProp[node,key,value]; Notify[notify,after]; IF event#NIL THEN UndoEvent.Note[event,UndoPutProp,notify] }; UndoPutProp: PROC [undoRef: REF, currentEvent: Event] = TRUSTED { saved: REF Change _ NARROW[undoRef]; WITH x:saved SELECT FROM ChangingProp => PutProp[x.node,x.propName,x.oldval,currentEvent,x.root]; ENDCASE => ERROR }; GetProp: PUBLIC PROC [node: Ref, name: ROPE] RETURNS [value: REF] = { key: ATOM; IF node=NIL THEN RETURN; key _ Atom.MakeAtom[name]; RETURN [npI.GetProp[node,key]] }; <<-- **** Fetch info operations ****>> Fetch: PUBLIC PROC [text: RefTextNode, index: Offset] RETURNS [CHAR, Looks] = { <<-- fetches the indexed information>> <<-- use readers if want info from contiquous locations>> RETURN [RopeEdit.Fetch[text.rope,index], TextLooks.FetchLooks[text.runs,index]] }; FetchChar: PUBLIC PROC [text: RefTextNode, index: Offset] RETURNS [CHAR] = { <<-- fetches the indexed information>> <<-- use readers if want info from contiquous locations>> RETURN [RopeEdit.Fetch[text.rope,index]] }; FetchLooks: PUBLIC PROC [text: RefTextNode, index: Offset] RETURNS [Looks] = { <<-- returns the looks for the character at the given location>> <<-- use reader's if getting looks for sequence of locations>> RETURN [TextLooks.FetchLooks[text.runs,index]] }; GetRope: PUBLIC PROC [text: RefTextNode] RETURNS [ROPE] = { RETURN [IF text=NIL THEN NIL ELSE text.rope] }; GetRuns: PUBLIC PROC [text: RefTextNode] RETURNS [Runs] = { RETURN [IF text=NIL THEN NIL ELSE text.runs] }; Size: PUBLIC PROC [text: RefTextNode] RETURNS [Offset] = { RETURN [IF text=NIL THEN 0 ELSE RopeEdit.Size[text.rope]] }; <<-- **** Operations to create a text node ****>> FromRope: PUBLIC PROC [rope: ROPE] RETURNS [new: RefTextNode] = { <<-- create a text node with looks from a normal rope>> new _ TextNode.NewTextNode[]; new.rope _ rope; new.last _ TRUE; new.runs _ TextLooks.CreateRun[RopeEdit.Size[rope]] }; FromString: PUBLIC PROC [string: REF READONLY TEXT] RETURNS [new: RefTextNode] = { <<-- copies the contents of the string>> new _ TextNode.NewTextNode[]; new.last _ TRUE; new.rope _ RopeFrom.String[string]; new.runs _ TextLooks.CreateRun[RopeEdit.Size[new.rope]] }; DocFromNode: PUBLIC PROC [child: Ref] RETURNS [new: Ref] = { new _ TextNode.NewTextNode[]; new.child _ child; new.last _ TRUE; child.next _ new; child.last _ TRUE }; <<-- ***** Cap's and Lowercase>> ChangeCaps: PUBLIC PROC [ root: Ref, dest: RefTextNode, start: Offset _ 0, len: Offset _ MaxLen, how: CapChange _ allCaps, event: Event _ NIL] = { notify: REF ChangingText Change; oldRope, destRope: ROPE; rdr: RopeReader.Ref; initCap: BOOLEAN _ TRUE; destSize: Offset; alreadysaved: BOOLEAN; IF dest=NIL THEN RETURN; destSize _ RopeEdit.Size[oldRope _ destRope _ dest.rope]; start _ MIN[MAX[0,start],destSize]; IF (len _ MIN[MAX[0,len],destSize-start])=0 THEN RETURN; notify _ IF (alreadysaved _ AlreadySaved[dest,event]) THEN Alloc[] ELSE TextNode.pZone.NEW[ChangingText Change]; notify^ _ [ChangingText[root,dest,start,len,len,destRope,dest.runs]]; Notify[notify,before]; rdr _ RopeReader.GetRopeReader[]; UNTIL len=0 DO c: CHAR; maxText: NAT = 1000; num: NAT _ MIN[len, maxText]; new: REF TEXT _ TextNode.pZone.NEW[TEXT[num]]; RopeReader.SetPosition[rdr,destRope,start]; SELECT how FROM firstCap => IF num > 0 THEN { new[0] _ RopeEdit.UpperCase[RopeReader.Get[rdr]]; FOR i:NAT IN [1..num) DO new[i] _ RopeEdit.LowerCase[RopeReader.Get[rdr]]; ENDLOOP }; allCaps => FOR i:NAT IN [0..num) DO new[i] _ RopeEdit.UpperCase[RopeReader.Get[rdr]]; ENDLOOP; allLower => FOR i:NAT IN [0..num) DO new[i] _ RopeEdit.LowerCase[RopeReader.Get[rdr]]; ENDLOOP; initCaps => { init: BOOLEAN _ TRUE; FOR i:NAT IN [0..num) DO SELECT c _ RopeReader.Get[rdr] FROM IN ['a..'z] => { new[i] _ IF init THEN c-40B ELSE c; -- force first upper init _ FALSE }; IN ['A..'Z] => { new[i] _ IF init THEN c ELSE c+40B; -- force others lower init _ FALSE }; ENDCASE => { new[i] _ c; init _ c # '\' AND ~RopeEdit.AlphaNumericChar[c] }; ENDLOOP }; ENDCASE => ERROR; new.length _ num; destRope _ RopeEdit.ReplaceByString[destRope,new,0,num,start,num,destSize]; IF RopeEdit.Size[destRope] # destSize THEN ERROR; len _ len-num; start _ start+num; ENDLOOP; dest.rope _ destRope; RopeReader.FreeRopeReader[rdr]; Notify[notify,after]; IF alreadysaved THEN Free[notify] ELSE UndoEvent.Note[event,UndoChangeText,notify] }; <<-- ***** Miscellaneous>> BumpCount: PROC [text: RefTextNode] = { countLimit: Offset _ 20; count: [0..TextNode.countMax]; IF text=NIL THEN RETURN; IF(count_text.count) = TextNode.countMax OR (text.count _ count+1) >= countLimit THEN <<-- see if need to flatten>> IF Flatten[text] THEN text.count _ 0 ELSE text.count _ text.count/2 }; maxRopeDepth: Offset; debug: BOOL _ FALSE; Flatten: PUBLIC PROC [text: RefTextNode] RETURNS [BOOLEAN] = { ropeSize, ropePieces, ropeDepth: Offset; looksSize, looksPieces, looksDepth: Offset; ropeFlag, looksFlag: BOOLEAN; rope: ROPE _ text.rope; runs: Runs _ text.runs; [ropeSize,ropePieces,ropeDepth] _ RopeEdit.RopeStats[rope]; [looksSize,looksPieces,looksDepth] _ TextLooks.LooksStats[runs]; <<-- size = # of characters>> <<-- pieces = # terminal nodes in tree>> <<-- depth = max depth of tree>> IF ropeSize=0 AND looksSize=0 THEN RETURN [FALSE]; IF ropeDepth > maxRopeDepth THEN maxRopeDepth _ ropeDepth; -- for debugging ropeFlag _ (ropePieces > 20 AND ropeSize < 5*ropePieces) OR ropeDepth > 100 OR (SELECT ropePieces/8 FROM < 2 => ropeDepth/4 > ropePieces, < 6 => ropeDepth/2 > ropePieces, < 12 => ropeDepth > ropePieces, ENDCASE => ropeDepth > ropePieces/2); looksFlag _ (looksPieces > 20 AND looksSize < 5*looksPieces) OR looksDepth > 100 OR (SELECT looksPieces/8 FROM < 2 => looksDepth/4 > looksPieces, < 6 => looksDepth/2 > looksPieces, < 12 => looksDepth > looksPieces, ENDCASE => looksDepth > looksPieces/2); IF ropeFlag OR looksFlag THEN { IF debug THEN { CheckNode.CheckRope[rope]; CheckNode.CheckRuns[runs] }; text.rope _ IF ropeSize > 256 THEN RopeEdit.SemiFlatten[rope] ELSE RopeEdit.Flatten[rope]; text.runs _ TextLooks.Flatten[runs]; IF debug THEN { CheckNode.CheckRope[text.rope]; CheckNode.CheckRuns[text.runs] }; RETURN [TRUE] }; RETURN [FALSE] }; AlreadySaved: PUBLIC PROC [text: RefTextNode, event: Event] RETURNS [BOOLEAN] = TRUSTED { <<-- returns TRUE if there is already a ChangingText record for given node in the event >> IF event = NIL THEN RETURN [TRUE]; FOR l: UndoEvent.SubEvent _ event.subevents, l.next UNTIL l=NIL DO IF l.undoRef # NIL THEN WITH x: l.undoRef SELECT FROM ChangingText => IF x.text = text THEN RETURN[TRUE]; ENDCASE; ENDLOOP; RETURN [FALSE] }; <<-- ***** Initialization>> Start: PUBLIC PROC = { }; END.