DIRECTORY Atom, CheckNode USING [CheckRope, CheckRuns], EditNotify USING [Change, Notify], NodeAddrs USING [MapTextAddrs, MoveTextAddr, PinTextAddr, PutTextAddr, Replace, UnpinAll], NodeProps USING [GetProp, PutProp], Rope, RopeEdit, RopeReader USING [FreeRopeReader, Get, GetRope, GetRopeReader, Ref, SetPosition], Rosary, TextEdit, TextLooks, TextNode USING [countMax, NewTextNode, Ref, Root], UndoEvent USING [Note, Ref, SubEvent]; TextEditImpl: CEDAR MONITOR IMPORTS Atom, CheckNode, EditNotify, NodeAddrs, NodeProps, Rope, RopeEdit, RopeReader, Rosary, TextLooks, TextNode, UndoEvent EXPORTS TextEdit = BEGIN RefTextNode: TYPE = TextNode.Ref; ROPE: TYPE = Rope.ROPE; ROSARY: TYPE = Rosary.ROSARY; Looks: TYPE = TextLooks.Looks; noLooks: Looks = TextLooks.noLooks; allLooks: Looks = TextLooks.allLooks; MaxLen, MaxOffset: INT = LAST[INT]; MaxNat: NAT = LAST[NAT]; Event: TYPE = UndoEvent.Ref; CapChange: TYPE ~ TextEdit.CapChange; DestSpanProc: TYPE ~ TextEdit.DestSpanProc; OneSpanProc: TYPE ~ TextEdit.OneSpanProc; MapPropsAction: TYPE ~ TextEdit.MapPropsAction; ModifyPropsAction: TYPE ~ TextEdit.ModifyPropsAction; Change: TYPE = EditNotify.Change; Runs: TYPE = TextLooks.Runs; CharSet: TYPE ~ [0..256); scratch1, scratch2, scratch3: REF Change.ChangingText; Create: PROC RETURNS [REF Change.ChangingText] = { RETURN [NEW[Change.ChangingText]]; }; Alloc: ENTRY PROC RETURNS [scratch: REF Change.ChangingText] = { 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 Change.ChangingText] = { 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; }; CheckAllNIL: PROC [rosary: ROSARY] RETURNS [ROSARY] = { action: PROC [item: REF, repeat: INT] RETURNS [quit: BOOL] ~ { quit _ item#NIL; }; IF Rosary.MapRuns[[rosary], action].quit THEN RETURN [rosary] ELSE RETURN [NIL]; }; DoReplace: PROC [root: RefTextNode, dest: RefTextNode, destStart, destLen: INT, sourceRope: ROPE, sourceRuns: Runs, sourceCharProps: ROSARY, sourceCharSets: ROSARY, sourceStart, sourceLen: INT, event: Event] RETURNS [resultStart, resultLen: INT] = { replaceRope, newRope: ROPE; replaceRuns, newRuns: Runs; special: BOOL; destRope: ROPE ~ dest.rope; destSize: INT ~ Rope.Size[destRope]; size: INT ~ destSize-destLen+sourceLen; destRuns: Runs ~ dest.runs; alreadysaved: BOOL ~ AlreadySaved[dest, event]; notify: REF Change.ChangingText _ IF alreadysaved THEN Alloc[] ELSE NEW[Change.ChangingText]; notify^ _ [ChangingText[ root: root, text: dest, start: destStart, newlen: sourceLen, oldlen: destLen, oldRope: destRope, oldRuns: destRuns, oldCharSets: IF dest.hascharsets THEN GetProp[dest, $CharSets] ELSE NIL, oldCharProps: IF dest.hascharprops THEN GetProp[dest, $CharProps] ELSE NIL ]]; EditNotify.Notify[notify, before]; IF sourceLen > 0 THEN { replaceRope _ Rope.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 _ Rope.Substr[destRope, destLen, size]; newRuns _ TextLooks.Substr[destRuns, destLen, size]; special _ TRUE; } ELSE IF (destStart+destLen)=destSize THEN { -- delete from end newRope _ Rope.Substr[destRope, 0, size]; newRuns _ TextLooks.Substr[destRuns, 0, size]; special _ TRUE; }; }; IF NOT special THEN { newRope _ Rope.Replace[base: destRope, start: destStart, len: destLen, with: Rope.Substr[replaceRope, 0, sourceLen]]; newRuns _ TextLooks.Replace[base: destRuns, start: destStart, len: destLen, replace: replaceRuns, baseSize: destSize, repSize: sourceLen] }; dest.rope _ newRope; dest.runs _ newRuns; IF dest.hascharsets OR sourceCharSets # NIL THEN { r: ROSARY _ GetPropsRosary[dest, $CharSets, destSize]; IF sourceCharSets = NIL THEN sourceCharSets _ rosaryOfNil; r _ Rosary.CatSegments[[r, 0, destStart], [sourceCharSets, sourceStart, sourceLen], [r, destStart+destLen]]; NodeProps.PutProp[dest, $CharSets, CheckAllNIL[r]]; }; IF dest.hascharprops OR sourceCharProps # NIL THEN { r: ROSARY _ GetPropsRosary[dest, $CharProps, destSize]; IF sourceCharProps = NIL THEN sourceCharProps _ rosaryOfNil; r _ Rosary.CatSegments[[r, 0, destStart], [sourceCharProps, sourceStart, sourceLen], [r, destStart+destLen]]; NodeProps.PutProp[dest, $CharProps, CheckAllNIL[r]]; }; NodeAddrs.Replace[dest, destStart, destLen, sourceLen]; BumpCount[dest]; EditNotify.Notify[notify, after]; IF alreadysaved THEN Free[notify] ELSE UndoEvent.Note[event, UndoChangeText, notify]; RETURN [destStart, sourceLen]; }; charSetsZero: ROSARY ~ Rosary.FromItem[NEW[CharSet _ 0], MaxLen]; rosaryOfNil: ROSARY ~ Rosary.FromItem[NIL, MaxLen]; MoveText: PUBLIC PROC [destRoot, sourceRoot: RefTextNode, dest: RefTextNode, destLoc: INT _ 0, source: RefTextNode, start: INT _ 0, len: INT _ MaxLen, event: Event _ NIL] RETURNS [resultStart, resultLen: INT] = { MoveAddrs: PROC [addr: REF, location: INT] RETURNS [quit: BOOL] = { IF location NOT IN [start..start+len) THEN RETURN [FALSE]; IF dest=source THEN { -- pin addr at new location new: INT _ 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]; }; sourceSize: INT ~ Rope.Size[source.rope]; destSize: INT ~ IF dest=source THEN sourceSize ELSE Rope.Size[dest.rope]; start _ MIN[MAX[0, start], sourceSize]; len _ MIN[MAX[0, len], sourceSize-start]; 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: RefTextNode, alpha: RefTextNode, alphaStart: INT _ 0, alphaLen: INT _ MaxLen, beta: RefTextNode, betaStart: INT _ 0, betaLen: INT _ MaxLen, event: Event _ NIL] RETURNS [alphaResultStart, alphaResultLen, betaResultStart, betaResultLen: INT] = { SwitchResults: PROC = { start, len: INT; start _ betaResultStart; len _ betaResultLen; betaResultStart _ alphaResultStart; betaResultLen _ alphaResultLen; alphaResultStart _ start; alphaResultLen _ len }; alphaSize, betaSize: INT; switched: BOOL _ FALSE; alphaSize _ Rope.Size[alpha.rope]; alphaStart _ MIN[MAX[0, alphaStart], alphaSize]; alphaResultLen _ alphaLen _ MIN[MAX[0, alphaLen], alphaSize-alphaStart]; betaSize _ IF beta=alpha THEN alphaSize ELSE Rope.Size[beta.rope]; betaStart _ MIN[MAX[0, betaStart], betaSize]; betaResultLen _ betaLen _ MIN[MAX[0, betaLen], betaSize-betaStart]; IF alpha=beta THEN { alphaEnd: INT; IF alphaStart > betaStart THEN { -- switch them start: INT _ alphaStart; len: INT _ alphaLen; root: RefTextNode _ 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: INT; 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: RefTextNode, dest: RefTextNode, char: CHAR, start: INT, len: INT, inherit: BOOL, looks: Looks, charSet: CharSet, event: Event] RETURNS [resultStart, resultLen: INT] = { [resultStart, resultLen] _ ReplaceByRope[root: root, dest: dest, rope: Rope.FromChar[char], start: start, len: len, inherit: inherit, looks: looks, charSet: charSet, event: event]; }; ReplaceByString: PUBLIC PROC [root: RefTextNode, dest: RefTextNode, string: REF READONLY TEXT, stringStart: NAT, stringNum: NAT, start: INT, len: INT, inherit: BOOL, looks: Looks, charSet: CharSet, event: Event] RETURNS [resultStart, resultLen: INT] = { Chars: PROC RETURNS [CHAR] ~ {c: CHAR _ string[i]; i _ i + 1; RETURN [c]}; i: NAT _ stringStart _ MIN[string.length, stringStart]; stringNum _ MIN[string.length-stringStart, stringNum]; [resultStart, resultLen] _ ReplaceByRope[root: root, dest: dest, rope: Rope.FromProc[stringNum, Chars], start: start, len: len, inherit: inherit, looks: looks, charSet: charSet, event: event]; }; UndoChangeText: PROC [undoRef: REF, currentEvent: Event] = { WITH undoRef SELECT FROM x: REF Change.ChangingText => { destSize: INT ~ Rope.Size[x.text.rope]; sourceSize: INT ~ Rope.Size[x.oldRope]; [] _ DoReplace[ root: x.root, dest: x.text, destStart: 0, destLen: destSize, sourceRope: x.oldRope, sourceRuns: x.oldRuns, sourceCharProps: NARROW[x.oldCharProps], sourceCharSets: NARROW[x.oldCharSets], sourceStart: 0, sourceLen: sourceSize, event: currentEvent ]; }; ENDCASE => ERROR; }; ReplaceText: PUBLIC PROC [destRoot: RefTextNode, sourceRoot: RefTextNode, dest: RefTextNode, destStart: INT, destLen: INT, source: RefTextNode, sourceStart: INT, sourceLen: INT, event: Event] RETURNS [resultStart, resultLen: INT] = { IF dest = NIL THEN {resultStart _ resultLen _ 0} ELSE IF source = NIL THEN {[resultStart, resultLen] _ ReplaceByRope[root: destRoot, dest: dest, rope: NIL, start: destStart, len: destLen, inherit: FALSE, looks: ALL[FALSE], charSet: 0, event: event]} ELSE { sourceRope: ROPE ~ source.rope; sourceSize: INT ~ Rope.Size[sourceRope]; destSize: INT ~ Rope.Size[dest.rope]; sourceRuns: Runs ~ source.runs; sourceCharProps: ROSARY ~ IF source#NIL AND source.hascharprops THEN GetPropsRosary[source, $CharProps, sourceSize] ELSE NIL; sourceCharSets: ROSARY ~ IF source#NIL AND source.hascharsets THEN GetPropsRosary[source, $CharSets, sourceSize] ELSE NIL; sourceStart _ MIN[MAX[0, sourceStart], sourceSize]; IF sourceLen # 0 THEN { IF sourceStart >= sourceSize THEN sourceStart _ sourceSize; sourceLen _ MIN[MAX[0, sourceLen], sourceSize-sourceStart]; }; 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: destRoot, dest: dest, destStart: destStart, destLen: destLen, sourceRope: sourceRope, sourceRuns: sourceRuns, sourceCharProps: sourceCharProps, sourceCharSets: sourceCharSets, sourceStart: sourceStart, sourceLen: sourceLen, event: event ]; }; }; DeleteText: PUBLIC OneSpanProc = { [] _ ReplaceByRope[root: root, dest: text, rope: NIL, start: start, len: len, inherit: FALSE, looks: ALL[FALSE], charSet: 0, event: event]; }; CopyText: PUBLIC DestSpanProc = { -- copy the specified text [resultStart, resultLen] _ ReplaceText[destRoot: destRoot, sourceRoot: sourceRoot, dest: dest, destStart: destLoc, destLen: 0, source: source, sourceStart: start, sourceLen: len, event: event]; }; MoveTextOnto: PUBLIC PROC [ destRoot, sourceRoot: RefTextNode, dest: RefTextNode, destStart: INT _ 0, destLen: INT _ MaxLen, source: RefTextNode, sourceStart: INT _ 0, sourceLen: INT _ MaxLen, event: Event _ NIL] RETURNS [resultStart, resultLen: INT] = { sourceSize, destSize, start, len, end, destEnd: INT; 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: RefTextNode, dest: RefTextNode, rope: ROPE, start: INT, len: INT, inherit: BOOL, looks: Looks, charSet: CharSet, event: Event] RETURNS [resultStart, resultLen: INT] = { IF dest=NIL THEN RETURN [0, 0] ELSE { destRope: ROPE ~ dest.rope; destSize: INT ~ Rope.Size[destRope]; ropeSize: INT ~ Rope.Size[rope]; alreadysaved: BOOL ~ AlreadySaved[dest, event]; notify: REF Change.ChangingText ~ ( IF alreadysaved THEN Alloc[] ELSE NEW[Change.ChangingText] ); start _ MIN[MAX[0, start], destSize]; len _ MIN[MAX[0, len], destSize-start]; notify^ _ [ChangingText[ root: root, text: dest, start: start, newlen: ropeSize, oldlen: len, oldRope: destRope, oldRuns: dest.runs, oldCharSets: IF dest.hascharsets THEN GetProp[dest, $CharSets] ELSE NIL, oldCharProps: IF dest.hascharprops THEN GetProp[dest, $CharProps] ELSE NIL ]]; EditNotify.Notify[notify, before]; dest.rope _ Rope.Replace[base: destRope, start: start, len: len, with: rope]; dest.runs _ TextLooks.ReplaceByRun[dest.runs, start, len, ropeSize, destSize, inherit, looks]; NodeAddrs.Replace[dest, start, len, ropeSize]; BumpCount[dest]; IF dest.hascharsets OR charSet # 0 THEN { old: ROSARY ~ GetPropsRosary[dest, $CharSets, destSize]; RosaryFromCharSet: PROC [cs: CharSet] RETURNS [ROSARY] ~ { IF cs = 0 THEN RETURN [rosaryOfNil] ELSE { item: REF CharSet _ NIL; IF start-1 IN [0..destSize) THEN item _ NARROW[Rosary.Fetch[old, start-1]]; IF (item = NIL OR item^ # cs) AND start+len < destSize THEN { item _ NARROW[Rosary.Fetch[old, start+len]]; }; IF (item = NIL OR item^ # cs) THEN item _ NEW[CharSet _ cs]; RETURN [Rosary.FromItem[item, ropeSize]]; }; }; sourceCharSets: ROSARY ~ RosaryFromCharSet[charSet]; new: ROSARY ~ Rosary.CatSegments[[old, 0, start], [sourceCharSets, 0, ropeSize], [old, start+len]]; NodeProps.PutProp[dest, $CharSets, CheckAllNIL[new]]; }; IF dest.hascharprops THEN { old: ROSARY ~ GetPropsRosary[dest, $CharProps, destSize]; new: ROSARY ~ Rosary.CatSegments[[old, 0, start], [rosaryOfNil, 0, ropeSize], [old, start+len]]; NodeProps.PutProp[dest, $CharProps, CheckAllNIL[new]]; }; EditNotify.Notify[notify, after]; IF alreadysaved THEN Free[notify] ELSE UndoEvent.Note[event, UndoChangeText, notify]; RETURN [start, ropeSize]; }; }; InsertChar: PUBLIC PROC [root: RefTextNode, dest: RefTextNode, char: CHAR, destLoc: INT, inherit: BOOL, looks: Looks, charSet: CharSet, event: Event] RETURNS [resultStart, resultLen: INT] = { [resultStart, resultLen] _ ReplaceByChar[root: root, dest: dest, char: char, start: destLoc, len: 0, inherit: inherit, looks: looks, charSet: charSet, event: event] }; AppendChar: PUBLIC PROC [root: RefTextNode, dest: RefTextNode, char: CHAR, inherit: BOOL, looks: Looks, charSet: CharSet, event: Event] RETURNS [resultStart, resultLen: INT] = { [resultStart, resultLen] _ InsertChar[root, dest, char, MaxLen, inherit, looks, charSet, event] }; InsertString: PUBLIC PROC [root: RefTextNode, dest: RefTextNode, string: REF READONLY TEXT, stringStart: NAT, stringNum: NAT, destLoc: INT, inherit: BOOL, looks: Looks, charSet: CharSet, event: Event] RETURNS [resultStart, resultLen: INT] = { [resultStart, resultLen] _ ReplaceByString[root: root, dest: dest, string: string, stringStart: stringStart, stringNum: stringNum, start: destLoc, len: 0, inherit: inherit, looks: looks, charSet: charSet, event: event] }; AppendString: PUBLIC PROC [root: RefTextNode, dest: RefTextNode, string: REF READONLY TEXT, stringStart: NAT, stringNum: NAT, inherit: BOOL, looks: Looks, charSet: CharSet, event: Event] RETURNS [resultStart, resultLen: INT] = { [resultStart, resultLen] _ InsertString[root: root, dest: dest, string: string, stringStart: stringStart, stringNum: stringNum, destLoc: MaxLen, inherit: inherit, looks: looks, charSet: charSet, event: event] }; InsertRope: PUBLIC PROC [root: RefTextNode, dest: RefTextNode, rope: ROPE, destLoc: INT, inherit: BOOL, looks: Looks, charSet: CharSet, event: Event] RETURNS [resultStart, resultLen: INT] = { [resultStart, resultLen] _ ReplaceByRope[root: root, dest: dest, rope: rope, start: destLoc, len: 0, inherit: inherit, looks: looks, charSet: charSet, event: event] }; AppendRope: PUBLIC PROC [root: RefTextNode, dest: RefTextNode, rope: ROPE, inherit: BOOL, looks: Looks, charSet: CharSet, event: Event] RETURNS [resultStart, resultLen: INT] = { [resultStart, resultLen] _ InsertRope[root: root, dest: dest, rope: rope, destLoc: MaxLen, inherit: inherit, looks: looks, charSet: charSet, event: event] }; ChangeLooks: PUBLIC PROC [ root: RefTextNode, text: RefTextNode, remove, add: Looks, start: INT _ 0, len: INT _ MaxOffset, event: Event _ NIL] = { size: INT; newRuns: Runs; IF text=NIL THEN RETURN; size _ Rope.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: RefTextNode, text: RefTextNode, newRuns: Runs, start, len: INT, event: Event] = { oldRuns: Runs _ text.runs; notify: REF Change.ChangingText; alreadysaved: BOOL; notify _ IF (alreadysaved _ AlreadySaved[text, event]) THEN Alloc[] ELSE NEW[Change.ChangingText]; notify^ _ [ChangingText[root, text, start, len, len, text.rope, oldRuns]]; EditNotify.Notify[notify, before]; text.runs _ newRuns; BumpCount[text]; EditNotify.Notify[notify, after]; IF alreadysaved THEN Free[notify] ELSE UndoEvent.Note[event, UndoChangeText, notify]; }; AddLooks: PUBLIC PROC [root: RefTextNode, text: RefTextNode, add: Looks, start: INT, len: INT, event: Event] = { ChangeLooks[root, text, noLooks, add, start, len, event] }; RemoveLooks: PUBLIC PROC [root: RefTextNode, text: RefTextNode, remove: Looks, start: INT, len: INT, event: Event] = { ChangeLooks[root, text, remove, noLooks, start, len, event] }; SetLooks: PUBLIC PROC [root: RefTextNode, text: RefTextNode, new: Looks, start: INT, len: INT, event: Event] = { ChangeLooks[root, text, allLooks, new, start, len, event] }; ClearLooks: PUBLIC PROC [root: RefTextNode, text: RefTextNode, start: INT, len: INT, event: Event] = { ChangeLooks[root, text, allLooks, noLooks, start, len, event] }; PropList: TYPE ~ Atom.PropList; GetCharProp: PUBLIC PROC [node: RefTextNode, index: INT, name: ATOM] RETURNS [value: REF] ~ { RETURN [GetPropFromList[GetCharPropList[node, index], name]]; }; PutCharProp: PUBLIC PROC [node: RefTextNode, index: INT, name: ATOM, value: REF, nChars: INT, event: Event, root: RefTextNode] ~ { r: ROSARY ~ GetPropsRosary[node, $CharProps, Rope.Size[node.rope]]; s: ROSARY ~ Rosary.FromProcProc[p]; p: PROC[q: PROC[REF, INT]] ~ { action: Rosary.RunActionType ~ { oldList: PropList ~ NARROW[item]; q[PutPropOnList[oldList, name, value], repeat]; }; [] _ Rosary.MapRuns[[r, index, nChars], action]; }; t: ROSARY ~ Rosary.CatSegments[[r, 0, index], [s], [r, index + nChars]]; PutProp[node, $CharProps, CheckAllNIL[t], event, root]; }; GetCharPropList: PUBLIC PROC [node: RefTextNode, index: INT] RETURNS [PropList _ NIL] ~ { IF node.hascharprops THEN { WITH GetProp[node, $CharProps] SELECT FROM rosary: ROSARY => { WITH Rosary.Fetch[rosary, index] SELECT FROM p: PropList => RETURN [p]; ENDCASE => NULL; }; ENDCASE => NULL; }; }; PutCharPropList: PUBLIC PROC [node: RefTextNode, index: INT, propList: PropList, nChars: INT, event: Event, root: RefTextNode] ~ { r: ROSARY _ GetPropsRosary[node, $CharProps, Rope.Size[node.rope]]; r _ Rosary.CatSegments[[r, 0, index], [Rosary.FromItem[propList, nChars]], [r, index + nChars]]; PutProp[node, $CharProps, CheckAllNIL[r], event, root]; }; GetPropFromList: PUBLIC PROC [propList: PropList, key: ATOM] RETURNS [value: REF] ~ { FOR list: PropList _ propList, list.rest UNTIL list=NIL DO IF list.first.key=key THEN RETURN[list.first.val]; ENDLOOP; RETURN[NIL]; }; PutPropOnList: PUBLIC PROC [propList: PropList, key: ATOM, value: REF] RETURNS [new: PropList] ~ { RemoveKeyFrom: PROC [p: PropList] RETURNS [PropList] = { IF p = NIL THEN RETURN [NIL] ELSE IF p.first.key = key THEN RETURN [p.rest] ELSE { rest: PropList _ RemoveKeyFrom[p.rest]; IF rest # p.rest THEN RETURN [CONS[p.first, rest]] ELSE RETURN [p] } }; new _ RemoveKeyFrom[propList]; IF value # NIL THEN new _ CONS[NEW[Atom.DottedPairNode _ [key, value]], new]; }; MapCharProps: PUBLIC PROC [node: RefTextNode, index: INT, action: MapPropsAction] RETURNS [quit: BOOL] ~ { FOR list: PropList _ GetCharPropList[node, index], list.rest UNTIL list=NIL DO IF action[NARROW[list.first.key], list.first.val].quit THEN RETURN[TRUE]; ENDLOOP; RETURN[FALSE]; }; ModifyCharProps: PUBLIC PROC [node: RefTextNode, name: ATOM, index: INT, nChars: INT, action: ModifyPropsAction, event: Event, root: RefTextNode] RETURNS [quit: BOOL] ~ { prefixSize: INT _ INT.LAST; r: ROSARY ~ GetPropsRosary[node, $CharProps, Rope.Size[node.rope]]; i: INT _ index; p: PROC[q: PROC[REF, INT]] ~ { runAction: Rosary.RunActionType ~ { oldList: PropList ~ NARROW[item]; oldValue: REF ~ GetPropFromList[oldList, name]; stop: BOOL _ FALSE; newValue: REF _ NIL; [stop, newValue] _ action[oldValue, i, repeat]; IF newValue # oldValue AND prefixSize = INT.LAST THEN prefixSize _ i; IF prefixSize # INT.LAST THEN { newList: PropList ~ IF newValue = oldValue THEN oldList ELSE PutPropOnList[oldList, name, newValue]; q[newList, repeat]; }; i _ i + repeat; IF stop THEN RETURN [TRUE]; }; [] _ Rosary.MapRuns[[r, index, nChars], runAction]; }; s: ROSARY ~ Rosary.FromProcProc[p]; -- sets prefixSize and i as side effect IF prefixSize # INT.LAST THEN { t: ROSARY ~ Rosary.CatSegments[[r, 0, prefixSize], [s], [r, i]]; IF prefixSize + Rosary.Size[s] # i THEN ERROR; PutProp[node, $CharProps, CheckAllNIL[t], event, root]; }; }; ChangeFormat: PUBLIC PROC [node: RefTextNode, formatName: ATOM, event: Event _ NIL, root: RefTextNode _ NIL] = { notify: REF Change.ChangingFormat; oldFormatName: ATOM _ node.formatName; IF node=NIL THEN RETURN; IF root=NIL THEN root _ TextNode.Root[node]; IF oldFormatName = formatName THEN RETURN; -- no change notify _ NEW[Change.ChangingFormat _ [ChangingFormat[root, node, formatName, oldFormatName]]]; EditNotify.Notify[notify, before]; node.formatName _ formatName; EditNotify.Notify[notify, after]; IF event#NIL THEN UndoEvent.Note[event, UndoChangeFormat, notify]; }; UndoChangeFormat: PROC [undoRef: REF, currentEvent: Event] = { WITH undoRef SELECT FROM x: REF Change.ChangingFormat => { ChangeFormat[x.node, x.oldFormatName, currentEvent, x.root]; }; ENDCASE => ERROR; }; quote: ROPE ~ "\""; ChangeStyle: PUBLIC PROC [node: RefTextNode, name: ROPE, event: Event _ NIL, root: RefTextNode _ NIL] = { newprefix: ROPE _ NIL; IF node=NIL THEN RETURN; IF Rope.Size[name] > 0 THEN newprefix _ Rope.Cat[quote, name, quote, " style"]; PutProp[node, $Prefix, newprefix, event, root]; }; PutProp: PUBLIC PROC [node: RefTextNode, name: ATOM, value: REF, event: Event _ NIL, root: RefTextNode _ NIL] = { notify: REF Change.ChangingProp; oldval: REF; IF node=NIL THEN RETURN; IF root=NIL THEN root _ TextNode.Root[node]; IF (oldval _ NodeProps.GetProp[node, name]) = value THEN RETURN; notify _ NEW[Change.ChangingProp_[ChangingProp[root, node, Atom.GetPName[name], name, value, oldval]]]; EditNotify.Notify[notify, before]; NodeProps.PutProp[node, name, value]; EditNotify.Notify[notify, after]; IF event#NIL THEN UndoEvent.Note[event, UndoPutProp, notify]; }; UndoPutProp: PROC [undoRef: REF, currentEvent: Event] = { WITH undoRef SELECT FROM x: REF Change.ChangingProp => { PutProp[x.node, x.propAtom, x.oldval, currentEvent, x.root]; }; ENDCASE => ERROR; }; GetProp: PUBLIC PROC [node: RefTextNode, name: ATOM] RETURNS [value: REF] = { IF node=NIL THEN RETURN[NIL]; RETURN [NodeProps.GetProp[node, name]]; }; GetPropsRosary: PROC [node: RefTextNode, propName: ATOM, wantedSize: INT] RETURNS [ROSARY] ~ { WITH GetProp[node, propName] SELECT FROM rosary: ROSARY => { rosarySize: INT ~ Rosary.Size[rosary]; zero: [0..0] ~ wantedSize-rosarySize; RETURN [rosary]; }; ENDCASE => NULL; RETURN [Rosary.FromItem[NIL, wantedSize]]; }; Fetch: PUBLIC PROC [text: RefTextNode, index: INT] RETURNS [charSet: CharSet, char: CHAR, looks: Looks] = { [charSet, char] _ FetchChar[text, index]; looks _ FetchLooks[text, index]; }; FetchChar: PUBLIC PROC [text: RefTextNode, index: INT] RETURNS [charSet: CharSet _ 0, char: CHAR] = { char _ Rope.Fetch[text.rope, index]; IF text.hascharsets THEN { WITH GetProp[text, $CharSets] SELECT FROM rosary: ROSARY => { WITH Rosary.Fetch[rosary, index] SELECT FROM cs: REF CharSet => charSet _ cs^; ENDCASE => NULL; }; ENDCASE => NULL; }; }; FetchLooks: PUBLIC PROC [text: RefTextNode, index: INT] RETURNS [Looks] = { 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 [INT] = { RETURN [IF text=NIL THEN 0 ELSE Rope.Size[text.rope]] }; FromRope: PUBLIC PROC [rope: ROPE] RETURNS [new: RefTextNode] = { new _ TextNode.NewTextNode[]; new.rope _ rope; new.last _ TRUE; new.runs _ TextLooks.CreateRun[Rope.Size[rope]]; }; FromString: PUBLIC PROC [string: REF READONLY TEXT] RETURNS [new: RefTextNode] = { new _ TextNode.NewTextNode[]; new.last _ TRUE; new.rope _ Rope.FromRefText[string]; new.runs _ TextLooks.CreateRun[Rope.Size[new.rope]]; }; DocFromNode: PUBLIC PROC [child: RefTextNode] RETURNS [new: RefTextNode] = { new _ TextNode.NewTextNode[]; new.child _ child; new.last _ TRUE; child.next _ new; child.last _ TRUE; }; ChangeCaps: PUBLIC PROC [ root: RefTextNode, dest: RefTextNode, start: INT _ 0, len: INT _ MaxLen, how: CapChange _ allCaps, event: Event _ NIL] = { notify: REF Change.ChangingText; oldRope, destRope: ROPE; rdr: RopeReader.Ref; initCap: BOOL _ TRUE; destSize: INT; alreadysaved: BOOL; IF dest=NIL THEN RETURN; destSize _ Rope.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 NEW[Change.ChangingText]; notify^ _ [ChangingText[root, dest, start, len, len, destRope, dest.runs]]; EditNotify.Notify[notify, before]; rdr _ RopeReader.GetRopeReader[]; UNTIL len=0 DO c: CHAR; maxText: NAT = 1000; num: NAT _ MIN[len, maxText]; new: REF TEXT _ 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: BOOL _ 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.ReplaceByTEXT[destRope, new, 0, num, start, num, destSize]; IF Rope.Size[destRope] # destSize THEN ERROR; len _ len-num; start _ start+num; ENDLOOP; dest.rope _ destRope; RopeReader.FreeRopeReader[rdr]; EditNotify.Notify[notify, after]; IF alreadysaved THEN Free[notify] ELSE UndoEvent.Note[event, UndoChangeText, notify]; }; AllCaps: PUBLIC PROC [root: RefTextNode, dest: RefTextNode, start: INT, len: INT, event: Event] = { ChangeCaps[root, dest, start, len, allCaps, event] }; AllLower: PUBLIC PROC [root: RefTextNode, dest: RefTextNode, start: INT, len: INT, event: Event] = { ChangeCaps[root, dest, start, len, allLower, event] }; InitialCaps: PUBLIC PROC [root: RefTextNode, dest: RefTextNode, start: INT, len: INT, event: Event] = { ChangeCaps[root, dest, start, len, initCaps, event] }; BumpCount: PROC [text: RefTextNode] = { countLimit: INT _ 20; count: [0..TextNode.countMax]; IF text=NIL THEN RETURN; IF (count_text.count) = TextNode.countMax OR (text.count _ count+1) >= countLimit THEN IF Flatten[text] THEN text.count _ 0 ELSE text.count _ text.count/2; }; debug: BOOL _ FALSE; Flatten: PUBLIC PROC [text: RefTextNode] RETURNS [BOOL] = { looksSize, looksPieces, looksDepth: INT _ 0; ropeFlag, looksFlag: BOOL _ FALSE; rope: ROPE _ text.rope; runs: Runs _ text.runs; ropeSize: INT ~ Rope.Size[rope]; [looksSize, looksPieces, looksDepth] _ TextLooks.LooksStats[runs]; IF ropeSize=0 AND looksSize=0 THEN RETURN [FALSE]; 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 Rope.Balance[rope] ELSE Rope.Flatten[rope]; text.runs _ TextLooks.Flatten[runs]; IF debug THEN { CheckNode.CheckRope[text.rope]; CheckNode.CheckRuns[text.runs] }; RETURN [TRUE]; }; RETURN [FALSE]; }; maxSearchDepth: NAT _ 20; AlreadySaved: PUBLIC PROC [text: RefTextNode, event: Event] RETURNS [BOOL] = { k: NAT _ maxSearchDepth; IF event = NIL THEN RETURN [TRUE]; FOR l: UndoEvent.SubEvent _ event.subevents, l.next UNTIL l=NIL DO IF l.undoRef # NIL THEN WITH l.undoRef SELECT FROM x: REF Change.ChangingText => IF x.text = text THEN RETURN[TRUE]; ENDCASE; IF (k _ k-1) = 0 THEN EXIT; ENDLOOP; RETURN [FALSE]; }; END. *TextEditImpl.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. written by Bill Paxton, March 1981 last edit by Bill Paxton, December 28, 1982 11:43 am Last Edited by: Maxwell, January 5, 1983 3:31 pm Michael Plass, April 1, 1985 4:14:29 pm PST Doug Wyatt, March 4, 1985 1:16:05 pm PST -- **** Cache of notify records **** -- **** Text Editing Operations **** must replace all of text since only save first change per event -- 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 -- **** Operation to add or delete looks **** -- first remove then add in the given range -- **** Character Properties **** Note: Character properties are implemented using a ROSARY (an immutable sequence of REFs; see Rosary.mesa), appearing in the CharProps property of the node. The values in this rosary are of type Atom.PropList, but don't use Atom.PutPropOnList, since we regard these lists as immutable. The values on the property lists are externalized and internalized using the procedures registered with NodeProps, but no warrantee is expressed or implied for props that are not immutable. This property list mechanism regards a PropList as an immutable value. We don't use the procedures from Atom since 1) they might smash an existing PropList, and 2) they hold a global monitor in AtomImpl. -- **** Changing Style / Format of node **** -- **** Fetch info operations **** -- returns the looks for the character at the given location -- use reader's if getting looks for sequence of locations -- **** Operations to create a text node **** -- create a text node with looks from a normal rope -- copies the contents of the string -- ***** Caps and Lowercase -- ***** Miscellaneous -- see if need to flatten -- size = # of characters -- pieces = # terminal nodes in tree -- depth = max depth of tree -- returns TRUE if there is already a ChangingText record for given node in the event Don't keep searching too long; give up and make a new event instead. - mfp Ê'a˜codešœ™Kšœ Ïmœ1™Jšœ žœ˜Jšœ˜—Kšžœ'žœžœ ˜=Kšžœžœžœ˜Kšœ˜K˜—š  œžœ<žœžœ%žœžœžœžœžœ˜ùKšœžœ˜K˜Kšœ žœ˜Kšœ žœ ˜Kšœ žœ˜$Kšœžœ˜'K˜Kšœžœ˜/šœžœ˜!Kšžœžœžœžœ˜;—šœ˜Kšœ ˜ Kšœ ˜ Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kš œ žœžœžœžœ˜HKšœžœžœžœž˜JKšœ˜—K˜"šžœžœ˜K˜>K˜CK˜—Kšœ žœ˜šžœ žœÏc˜&šžœ žœ¡˜(K˜FK˜GKšœ žœ˜Kšœ˜—šžœžœžœ¡˜2K˜FK˜GKšœ žœ˜Kšœ˜—Kšœ˜—šžœžœ žœ¡˜,šžœ žœ¡˜*K˜/K˜4Kšœ žœ˜Kšœ˜—šžœžœžœ¡˜>K˜)K˜.Kšœ žœ˜Kšœ˜—Kšœ˜—šžœžœ žœ˜K•StartOfExpansionM[base: ROPE, start: INT _ 0, len: INT _ 2147483647, with: ROPE _ NIL]˜uK–†[base: TextLooks.Runs, start: INT, len: INT, replace: TextLooks.Runs, baseSize: INT, repSize: INT, tryFlat: BOOL _ TRUE]˜‰K˜—K˜K˜šžœžœžœžœ˜2Jšœžœ-˜6Jšžœžœžœ˜:Jšœl˜lJšœ3˜3Jšœ˜—šžœžœžœžœ˜4Jšœžœ.˜7Jšžœžœžœ˜Kšœ"žœžœ ˜DKšœžœ˜Kšžœžœ˜)Kšœ0žœ˜4K˜%K˜Kšœžœžœ˜'Kšœžœžœ˜)K˜Kšœ žœ žœ žœ ˜:Kšœ žœžœ˜-Kšœ žœžœ"˜3K˜šžœ žœ¡$˜:šžœžœžœ˜'šžœž˜K˜=—šžœž˜K˜G—Kšžœ˜—šžœžœžœ˜%K˜=Kšžœ˜—Kšžœžœžœžœ˜Bšžœžœ˜K˜?Kšžœ˜—šžœžœ˜K˜?Kšžœ˜——K˜;Kšžœ žœžœ˜@˜K˜K—˜K˜——š  œžœžœ.žœ žœžœ žœ/˜ Kšžœžœ˜)Kšžœžœžœžœ˜šžœ˜Kšœ žœ ˜Kšœ žœ˜$Kšœ žœ˜ Kšœžœ˜/šœžœ˜#Kšžœžœ˜Kšžœžœ˜Kšœ˜—Kšœžœžœ˜%Kšœžœžœ˜'šœ˜Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ˜Kšœ ˜ Kšœ˜Kšœ˜Kš œ žœžœžœžœ˜HKšœžœžœžœž˜JKšœ˜—K˜"K˜MK˜^K˜.K˜šžœžœ žœ˜)Jšœžœ-˜8š œžœžœžœ˜:Jšžœžœžœ˜#šžœ˜Jšœžœ žœ˜Jšžœ žœžœžœ˜Kš žœ žœžœ žœžœ˜=Jšœžœ˜,Jšœ˜—Jš žœ žœžœ žœžœ˜žœžœ˜ªJšœ žœžœžœ˜Jšœžœ:˜CJšœžœ ˜š œžœžœžœžœ˜šœ#˜#Jšœžœ˜!Jšœ žœ"˜/Jš œžœžœ žœžœ˜(Jšœ/˜/Jš žœžœžœžœžœ˜Ešžœžœžœžœ˜Jšœžœžœ žœ(˜dJšœ˜Jšœ˜—Jšœ˜Jšžœžœžœžœ˜Jšœ˜—Jšœ3˜3Jšœ˜—Jšœžœ¡'˜Kšžœžœžœžœ˜Jšœžœ7˜@Jšžœ!žœžœ˜.Jšœ7˜7Jšœ˜—Jšœ˜K˜—K˜K™—šœ,™,K˜š   œžœžœ!žœžœžœ˜pKšœžœ˜"Kšœžœ˜&Kšžœžœžœžœ˜Kšžœžœžœ˜,Kšžœžœžœ¡ ˜7šœ žœ˜$K˜9—K˜"K˜K˜!Kšžœžœžœ1˜BKšœ˜K˜—š œžœ žœ˜>šžœ žœž˜šœžœ˜!Jšœ<˜