<> <> <> <> <> <> <> <<>> 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); <<-- **** Cache of notify records ****>> 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; }; <<>> <<-- **** Text Editing Operations ****>> 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] = { <<-- 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>> 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] }; <<-- **** Operation to add or delete looks ****>> ChangeLooks: PUBLIC PROC [ root: RefTextNode, text: RefTextNode, remove, add: Looks, start: INT _ 0, len: INT _ MaxOffset, event: Event _ NIL] = { <<-- first remove then add in the given range>> 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] }; <<-- **** Character Properties ****>> <> <> <<>> 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]; }; }; <<>> <<-- **** Changing Style / Format of node ****>> 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 info operations ****>> 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] = { <<-- 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 [INT] = { RETURN [IF text=NIL THEN 0 ELSE Rope.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[Rope.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 _ 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; }; <<-- ***** Caps and Lowercase>> 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] }; <<-- ***** Miscellaneous>> 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 <<-- see if need to flatten>> 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]; <<-- size = # of characters>> <<-- pieces = # terminal nodes in tree>> <<-- depth = max depth of tree>> 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] = { <<-- returns TRUE if there is already a ChangingText record for given node in the event >> 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.