DIRECTORY Atom USING [GetPName], Rope USING [Balance, Cat, Concat, Fetch, Flatten, FromChar, FromProc, InlineSize, Replace, ROPE, Size, Substr], RopeEdit USING [AlphaNumericChar, LowerCase, ReplaceByTEXT, UpperCase], RopeReader USING [FreeRopeReader, Get, GetRope, GetRopeReader, Ref, SetPosition], Rosary USING [CatSegments, Fetch, FromItem, FromProcProc, MapRuns, ROSARY, RunActionType, Size], TextLooks USING [ChangeLooks, Concat, CreateRun, FetchLooks, Flatten, Looks, LooksStats, Replace, ReplaceByRun, Runs, Substr], Tioga, TiogaPrivate; TextEditImpl: CEDAR MONITOR IMPORTS Atom, Rope, RopeEdit, RopeReader, Rosary, TextLooks, Tioga, TiogaPrivate EXPORTS Tioga, TiogaPrivate ~ BEGIN OPEN TiogaPrivate, Tioga; ROPE: TYPE ~ Rope.ROPE; Looks: TYPE ~ TextLooks.Looks; Runs: TYPE ~ TextLooks.Runs; ROSARY: TYPE ~ Rosary.ROSARY; World: TYPE ~ TiogaPrivate.World; WorldRep: PUBLIC TYPE ~ TiogaPrivate.WorldRep; Assertion: TYPE ~ BOOL[TRUE..TRUE]; scratch1, scratch2, scratch3: REF Change.ChangingText _ NIL; 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 _ NEW[Change.ChangingText]; }; 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; }; RopeFromString: PUBLIC PROC [string: REF READONLY TEXT, start: NAT, len: NAT] RETURNS [ROPE] ~ { size: NAT ~ string.length; start _ MIN[start, size]; len _ MIN[len, size-start]; IF len=0 THEN RETURN [NIL] ELSE { i: NAT _ start; p: PROC RETURNS [CHAR] ~ { c: CHAR ~ string.text[i]; i _ i+1; RETURN[c] }; RETURN [Rope.FromProc[len, p]]; }; }; NodeFromRope: PUBLIC PROC [rope: ROPE] RETURNS [new: Node] = { new _ NewNode[]; new.rope _ rope; new.runs _ TextLooks.CreateRun[Rope.Size[new.rope]]; }; DocFromNode: PUBLIC PROC [child: Node] RETURNS [new: Node] = { new _ NewNode[]; new.child _ child; child.next _ new; child.last _ TRUE; }; Size: PUBLIC PROC [node: Node] RETURNS [INT] = { RETURN [IF node=NIL THEN 0 ELSE Rope.InlineSize[node.rope]]; }; Fetch: PUBLIC PROC [node: Node, index: INT] RETURNS [char: XChar, looks: Looks] = { char _ FetchChar[node, index]; looks _ FetchLooks[node, index]; }; FetchChar: PUBLIC PROC [node: Node, index: INT] RETURNS [char: XChar _ [0, 0]] = { char.code _ ORD[Rope.Fetch[node.rope, index]]; IF node.hascharsets THEN { charSets: ROSARY ~ GetCharSets[node]; IF charSets#NIL THEN WITH Rosary.Fetch[charSets, index] SELECT FROM refCharSet: REF CharSet => char.set _ refCharSet^; ENDCASE; }; }; FetchLooks: PUBLIC PROC [node: Node, index: INT] RETURNS [Looks] = { RETURN [TextLooks.FetchLooks[node.runs, index]]; }; GetRope: PUBLIC PROC [node: Node] RETURNS [ROPE] = { RETURN [IF node=NIL THEN NIL ELSE node.rope]; }; GetRuns: PUBLIC PROC [node: Node] RETURNS [Runs] = { RETURN [IF node=NIL THEN NIL ELSE node.runs]; }; GetFormat: PUBLIC PROC [node: Node] RETURNS [ATOM] ~ { RETURN[node.formatName]; }; GetComment: PUBLIC PROC [node: Node] RETURNS [BOOL] ~ { RETURN[node.comment]; }; ClipLocation: PROC [loc: Location, size: INT] RETURNS [Location] ~ INLINE { loc.where _ MIN[MAX[0, loc.where], size]; RETURN[loc]; }; ClipText: PROC [text: Text, size: INT] RETURNS [Text] ~ INLINE { text.start _ MIN[MAX[0, text.start], size]; text.len _ MIN[MAX[0, text.len], size-text.start]; RETURN[text]; }; rosaryOfNil: ROSARY ~ Rosary.FromItem[NIL, maxLen]; RosaryReplace: PROC [dest: ROSARY, destSize: INT, destStart: INT, destLen: INT, source: ROSARY, sourceStart: INT, sourceLen: INT] RETURNS [rosary: ROSARY] ~ { notNil: Rosary.RunActionType ~ { quit _ item#NIL }; d: ROSARY ~ IF dest=NIL THEN rosaryOfNil ELSE dest; d0: INT ~ 0; d1: INT ~ destStart; d2: INT ~ d1+destLen; d3: INT ~ destSize; s: ROSARY ~ IF source=NIL THEN rosaryOfNil ELSE source; s0: INT ~ sourceStart; s1: INT ~ s0+sourceLen; rosary _ Rosary.CatSegments[[d, d0, d1-d0], [s, s0, s1-s0], [d, d2, d3-d2]]; IF NOT Rosary.MapRuns[[rosary], notNil] THEN rosary _ NIL; }; RosaryMapRuns: PROC [rosary: ROSARY, start, len: INT, action: Rosary.RunActionType] ~ { IF rosary=NIL THEN [] _ action[NIL, len] ELSE [] _ Rosary.MapRuns[[rosary, start, len], action]; }; GetRosary: PROC [node: Node, name: ATOM] RETURNS [ROSARY] ~ { WITH GetProp[node, name] SELECT FROM rosary: ROSARY => RETURN[rosary]; ENDCASE => RETURN[NIL]; }; GetCharSets: PROC [node: Node] RETURNS [ROSARY] ~ INLINE { RETURN[IF node.hascharsets THEN GetRosary[node, $CharSets] ELSE NIL]; }; GetCharProps: PROC [node: Node] RETURNS [ROSARY] ~ INLINE { RETURN[IF node.hascharprops THEN GetRosary[node, $CharProps] ELSE NIL]; }; DoReplace: PROC [world: World, root: Node, dest: Text, sourceRope: ROPE, sourceRuns: Runs, sourceCharProps: ROSARY, sourceCharSets: ROSARY, sourceStart, sourceLen: INT] RETURNS [result: Text] = { destRope: ROPE ~ dest.node.rope; destRuns: Runs ~ dest.node.runs; destCharSets: ROSARY ~ GetCharSets[dest.node]; destCharProps: ROSARY ~ GetCharProps[dest.node]; destSize: INT ~ Size[dest.node]; -- node size before replacement size: INT ~ destSize-dest.len+sourceLen; -- node size after replacement replaceRope, newRope: ROPE _ NIL; replaceRuns, newRuns: Runs _ NIL; special: BOOL _ FALSE; alreadysaved: BOOL ~ AlreadySaved[world, dest.node]; notify: REF Change.ChangingText _ IF alreadysaved THEN Alloc[] ELSE NEW[Change.ChangingText]; notify^ _ [ChangingText[ root: root, text: dest.node, start: dest.start, newlen: sourceLen, oldlen: dest.len, oldRope: destRope, oldRuns: destRuns, oldCharSets: destCharSets, oldCharProps: destCharProps ]]; EditNotify[world, notify, before]; IF sourceLen > 0 THEN { replaceRope _ Rope.Substr[sourceRope, sourceStart, sourceLen]; replaceRuns _ TextLooks.Substr[sourceRuns, sourceStart, sourceLen]; }; IF dest.len=0 THEN { -- doing an insert IF dest.start=0 THEN { -- insert at start newRope _ Rope.Concat[replaceRope, destRope]; newRuns _ TextLooks.Concat[replaceRuns, destRuns, sourceLen, destSize]; special _ TRUE; } ELSE IF dest.start=destSize THEN { -- insert at end newRope _ Rope.Concat[destRope, replaceRope]; newRuns _ TextLooks.Concat[destRuns, replaceRuns, destSize, sourceLen]; special _ TRUE; }; } ELSE IF sourceLen=0 THEN { -- doing a delete IF dest.start=0 THEN { -- delete from start newRope _ Rope.Substr[destRope, dest.len, size]; newRuns _ TextLooks.Substr[destRuns, dest.len, size]; special _ TRUE; } ELSE IF dest.end=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: dest.start, len: dest.len, with: replaceRope]; newRuns _ TextLooks.Replace[base: destRuns, start: dest.start, len: dest.len, replace: replaceRuns, baseSize: destSize, repSize: sourceLen] }; dest.node.rope _ newRope; dest.node.runs _ newRuns; IF dest.node.hascharsets OR sourceCharSets#NIL THEN { newCharSets: ROSARY ~ RosaryReplace[dest: destCharSets, destSize: destSize, destStart: dest.start, destLen: dest.len, source: sourceCharSets, sourceStart: sourceStart, sourceLen: sourceLen]; PutProp[dest.node, $CharSets, newCharSets]; }; IF dest.node.hascharprops OR sourceCharProps#NIL THEN { newCharProps: ROSARY ~ RosaryReplace[dest: destCharProps, destSize: destSize, destStart: dest.start, destLen: dest.len, source: sourceCharProps, sourceStart: sourceStart, sourceLen: sourceLen]; PutProp[dest.node, $CharProps, newCharProps]; }; AddrReplace[dest.node, dest.start, dest.len, sourceLen]; BumpCount[dest.node]; EditNotify[world, notify, after]; IF alreadysaved THEN Free[notify] ELSE NoteEvent[world, UndoChangeText, notify]; RETURN [[dest.node, dest.start, sourceLen]]; }; UndoChangeText: PROC [world: World, undoRef: REF Change] = { x: REF Change.ChangingText ~ NARROW[undoRef]; [] _ DoReplace[ world: world, root: x.root, dest: [x.text, 0, Size[x.text]], sourceRope: x.oldRope, sourceRuns: x.oldRuns, sourceCharProps: x.oldCharProps, sourceCharSets: x.oldCharSets, sourceStart: 0, sourceLen: Rope.Size[x.oldRope] ]; }; ReplaceText: PUBLIC PROC [world: World, destRoot, sourceRoot: Node, dest: Text, source: Text] RETURNS [result: Text] = { dest _ ClipText[dest, Size[dest.node]]; source _ ClipText[source, Size[source.node]]; IF source.len=0 THEN { IF dest.len#0 THEN DeleteText[world, destRoot, dest]; RETURN [[dest.node, dest.start, 0]]; } ELSE RETURN DoReplace[ world: world, root: destRoot, dest: dest, sourceRope: source.node.rope, sourceRuns: source.node.runs, sourceCharProps: GetCharProps[source.node], sourceCharSets: GetCharSets[source.node], sourceStart: source.start, sourceLen: source.len ]; }; DeleteText: PUBLIC PROC [world: World, root: Node, text: Text] = { [] _ ReplaceByRope[world: world, root: root, dest: text, rope: NIL, inherit: FALSE, looks: ALL[FALSE], charSet: 0]; }; CopyText: PUBLIC PROC [world: World, destRoot, sourceRoot: Node, dest: Location, source: Text] RETURNS [result: Text] = { RETURN ReplaceText[world: world, destRoot: destRoot, sourceRoot: sourceRoot, dest: [dest.node, dest.where, 0], source: source]; }; MoveText: PUBLIC PROC [world: World, destRoot, sourceRoot: Node, dest: Location, source: Text] RETURNS [result: Text] = { dest _ ClipLocation[dest, Size[dest.node]]; source _ ClipText[source, Size[source.node]]; IF source.len=0 THEN RETURN [[dest.node, dest.where, 0]]; IF source.node=dest.node THEN { -- move text within a single node MoveAndPinAddrs: PROC [addr: REF, location: INT] RETURNS [quit: BOOL _ FALSE] = { IF location IN [source.start..source.end) THEN { new: INT _ location-source.start+dest.where; IF dest.where>source.start THEN new _ new-source.len; -- move toward end of node PutTextAddr[n: dest.node, addr: addr, location: new, pin: TRUE]; }; }; IF dest.where IN [source.start..source.end] THEN RETURN [source]; [] _ MapTextAddrs[dest.node, MoveAndPinAddrs]; result _ CopyText[world, destRoot, sourceRoot, dest, source]; IF dest.wheresource.end THEN { -- delete part of dest after source afterStart: INT ~ MAX[dest.start, source.end]; afterLen: INT ~ dest.end-afterStart; DeleteText[world, root, [node, afterStart, afterLen]]; }; IF dest.start IF destItem^=charSet THEN item _ destItem; ENDCASE; ENDLOOP; }; IF item=NIL THEN item _ NEW[CharSet _ charSet]; RETURN [Rosary.FromItem[item, replaceSize]]; }; }; ReplaceByRope: PUBLIC PROC [world: World, root: Node, dest: Text, rope: ROPE, inherit: BOOL, looks: Looks, charSet: CharSet] RETURNS [result: Text] = { destSize: INT ~ Size[dest.node]; ropeSize: INT ~ Rope.Size[rope]; dest _ ClipText[dest, destSize]; IF dest.len#0 OR ropeSize#0 THEN { destRope: ROPE ~ dest.node.rope; destRuns: Runs ~ dest.node.runs; destCharSets: ROSARY ~ GetCharSets[dest.node]; destCharProps: ROSARY ~ GetCharProps[dest.node]; alreadysaved: BOOL ~ AlreadySaved[world, dest.node]; notify: REF Change.ChangingText ~ ( IF alreadysaved THEN Alloc[] ELSE NEW[Change.ChangingText] ); notify^ _ [ChangingText[ root: root, text: dest.node, start: dest.start, newlen: ropeSize, oldlen: dest.len, oldRope: destRope, oldRuns: destRuns, oldCharSets: destCharSets, oldCharProps: destCharProps ]]; EditNotify[world, notify, before]; dest.node.rope _ Rope.Replace[base: destRope, start: dest.start, len: dest.len, with: rope]; dest.node.runs _ TextLooks.ReplaceByRun[destRuns, dest.start, dest.len, ropeSize, destSize, inherit, looks]; AddrReplace[dest.node, dest.start, dest.len, ropeSize]; BumpCount[dest.node]; IF dest.node.hascharsets OR charSet#0 THEN { sourceCharSets: ROSARY ~ IF charSet=0 THEN NIL ELSE CharSetsForReplace[charSet: charSet, replaceSize: ropeSize, destCharSets: destCharSets, destSize: destSize, destStart: dest.start, destLen: dest.len]; newCharSets: ROSARY ~ RosaryReplace[dest: destCharSets, destSize: destSize, destStart: dest.start, destLen: dest.len, source: sourceCharSets, sourceStart: 0, sourceLen: ropeSize]; PutProp[dest.node, $CharSets, newCharSets]; }; IF dest.node.hascharprops THEN { newCharProps: ROSARY ~ RosaryReplace[dest: destCharProps, destSize: destSize, destStart: dest.start, destLen: dest.len, source: NIL, sourceStart: 0, sourceLen: ropeSize]; PutProp[dest.node, $CharProps, newCharProps]; }; EditNotify[world, notify, after]; IF alreadysaved THEN Free[notify] ELSE NoteEvent[world, UndoChangeText, notify]; }; RETURN [[dest.node, dest.start, ropeSize]]; }; ReplaceByChar: PUBLIC PROC [world: World, root: Node, dest: Text, char: CHAR, inherit: BOOL _ TRUE, looks: Looks _ noLooks, charSet: CharSet _ 0 ] RETURNS [result: Text] = { RETURN ReplaceByRope[world: world, root: root, dest: dest, rope: Rope.FromChar[char], inherit: inherit, looks: looks, charSet: charSet]; }; ChangeTextLooks: PUBLIC PROC [world: World, root: Node, text: Text, remove, add: Looks] = { size: INT ~ Size[text.node]; text _ ClipText[text, size]; IF text.len>0 THEN { oldRuns: Runs ~ text.node.runs; newRuns: Runs ~ TextLooks.ChangeLooks[oldRuns, size, remove, add, text.start, text.len]; IF newRuns=oldRuns THEN NULL -- no change ELSE { alreadysaved: BOOL ~ AlreadySaved[world, text.node]; notify: REF Change.ChangingText ~ IF alreadysaved THEN Alloc[] ELSE NEW[Change.ChangingText]; notify^ _ [ChangingText[root, text.node, text.start, text.len, text.len, text.node.rope, oldRuns, GetCharSets[text.node], GetCharProps[text.node]]]; EditNotify[world, notify, before]; text.node.runs _ newRuns; BumpCount[text.node]; EditNotify[world, notify, after]; IF alreadysaved THEN Free[notify] ELSE NoteEvent[world, UndoChangeText, notify]; }; }; }; PropListGet: PUBLIC PROC [propList: PropList, name: ATOM] RETURNS [value: REF] ~ { FOR list: PropList _ propList, list.rest UNTIL list=NIL DO prop: Prop ~ list.first; IF prop.name=name THEN RETURN[prop.value]; ENDLOOP; RETURN[NIL]; }; PropListPut: PUBLIC PROC [propList: PropList, name: ATOM, value: REF] RETURNS [PropList] ~ { IF PropListGet[propList, name]=value THEN RETURN [propList] ELSE { rem: PropList ~ PropListRem[propList, name]; IF value=NIL THEN RETURN [rem] ELSE RETURN [CONS[NEW[PropRep _ [name, value]], rem]]; }; }; PropListRem: PROC [propList: PropList, name: ATOM] RETURNS [PropList] ~ { IF propList=NIL THEN RETURN [NIL] ELSE IF propList.first.name=name THEN RETURN [propList.rest] ELSE { remRest: PropList ~ PropListRem[propList.rest, name]; IF propList.rest=remRest THEN RETURN [propList] ELSE RETURN [CONS[propList.first, remRest]]; }; }; PropListMap: PUBLIC PROC [propList: PropList, action: MapPropsAction] RETURNS [quit: BOOL] ~ { FOR list: PropList _ propList, list.rest UNTIL list=NIL DO prop: Prop ~ list.first; IF action[prop.name, prop.value] THEN RETURN[TRUE]; ENDLOOP; RETURN [FALSE]; }; GetCharProp: PUBLIC PROC [node: Node, index: INT, name: ATOM] RETURNS [value: REF] ~ { RETURN [PropListGet[GetCharPropList[node, index], name]]; }; SetCharProp: PUBLIC PROC [world: World, text: Text, name: ATOM, value: REF, root: Node] ~ { size: INT ~ Size[text.node]; text _ ClipText[text, size]; IF text.len#0 THEN { oldCharProps: ROSARY ~ GetCharProps[text.node]; p: PROC[q: PROC[REF, INT]] ~ { action: Rosary.RunActionType ~ { oldList: PropList ~ NARROW[item]; q[PropListPut[oldList, name, value], repeat]; }; RosaryMapRuns[oldCharProps, text.start, text.len, action]; }; replaceCharProps: ROSARY ~ Rosary.FromProcProc[p]; newCharProps: ROSARY ~ RosaryReplace[dest: oldCharProps, destSize: size, destStart: text.start, destLen: text.len, source: replaceCharProps, sourceStart: 0, sourceLen: text.len]; SetProp[world, text.node, $CharProps, newCharProps, root]; }; }; GetCharPropList: PUBLIC PROC [node: Node, index: INT] RETURNS [PropList] ~ { charProps: ROSARY ~ GetCharProps[node]; IF charProps=NIL THEN RETURN[NIL] ELSE RETURN[NARROW[Rosary.Fetch[charProps, index]]]; }; SetCharPropList: PUBLIC PROC [world: World, text: Text, propList: PropList, root: Node] ~ { size: INT ~ Size[text.node]; text _ ClipText[text, size]; IF text.len#0 THEN { oldCharProps: ROSARY ~ GetCharProps[text.node]; replaceCharProps: ROSARY ~ Rosary.FromItem[propList, text.len]; newCharProps: ROSARY ~ RosaryReplace[dest: oldCharProps, destSize: size, destStart: text.start, destLen: text.len, source: replaceCharProps, sourceStart: 0, sourceLen: text.len]; SetProp[world, text.node, $CharProps, newCharProps, root]; }; }; MapCharProps: PUBLIC PROC [node: Node, index: INT, action: MapPropsAction] RETURNS [quit: BOOL] ~ { RETURN PropListMap[GetCharPropList[node, index], action]; }; ModifyCharProps: PUBLIC PROC [world: World, text: Text, name: ATOM, action: ModifyPropsAction, root: Node _ NIL] RETURNS [quit: BOOL] ~ { size: INT ~ Size[text.node]; text _ ClipText[text, size]; IF text.len#0 THEN { prefixNIL: INT ~ INT.LAST; prefixSize: INT _ prefixNIL; oldCharProps: ROSARY ~ GetCharProps[text.node]; i: INT _ text.start; p: PROC[q: PROC[REF, INT]] ~ { runAction: Rosary.RunActionType ~ { oldList: PropList ~ NARROW[item]; oldValue: REF ~ PropListGet[oldList, name]; stop: BOOL _ FALSE; newValue: REF _ NIL; [stop, newValue] _ action[oldValue, i, repeat]; IF newValue # oldValue AND prefixSize = prefixNIL THEN prefixSize _ i; IF prefixSize # prefixNIL THEN { newList: PropList ~ IF newValue = oldValue THEN oldList ELSE PropListPut[oldList, name, newValue]; q[newList, repeat]; }; i _ i + repeat; IF stop THEN RETURN [TRUE]; }; RosaryMapRuns[oldCharProps, text.start, text.len, runAction]; }; replaceCharProps: ROSARY ~ Rosary.FromProcProc[p]; -- sets prefixSize and i as side effect IF prefixSize # prefixNIL THEN { replaceSize: INT ~ i-prefixSize; newCharProps: ROSARY ~ RosaryReplace[dest: oldCharProps, destSize: size, destStart: prefixSize, destLen: replaceSize, source: replaceCharProps, sourceStart: 0, sourceLen: replaceSize]; IF Rosary.Size[replaceCharProps] # replaceSize THEN ERROR; SetProp[world, text.node, $CharProps, newCharProps, root]; }; }; }; SetFormat: PUBLIC PROC [world: World, node: Node, formatName: ATOM, root: Node _ NIL] = { oldFormatName: ATOM ~ node.formatName; IF root=NIL THEN root _ Root[node]; IF oldFormatName#formatName THEN { notify: REF Change.ChangingFormat ~ NEW[Change.ChangingFormat _ [ChangingFormat[root, node, formatName, oldFormatName]]]; EditNotify[world, notify, before]; node.formatName _ formatName; EditNotify[world, notify, after]; NoteEvent[world, UndoSetFormat, notify]; }; }; UndoSetFormat: PROC [world: World, undoRef: REF Change] = { x: REF Change.ChangingFormat ~ NARROW[undoRef]; SetFormat[world, x.node, x.oldFormatName, x.root]; }; quote: ROPE ~ "\""; ChangeStyle: PUBLIC PROC [world: World, node: Node, name: ROPE, root: Node _ NIL] = { newprefix: ROPE _ NIL; IF node=NIL THEN RETURN; IF Rope.Size[name] > 0 THEN newprefix _ Rope.Cat[quote, name, quote, " style"]; SetProp[world, node, $Prefix, newprefix, root]; }; SetProp: PUBLIC PROC [world: World, node: Node, name: ATOM, value: REF, root: Node _ NIL] = { IF node=NIL THEN NULL ELSE { notify: REF Change.ChangingProp; oldval: REF ~ GetProp[node, name]; IF oldval=value THEN RETURN; IF root=NIL THEN root _ Root[node]; notify _ NEW[Change.ChangingProp _ [ChangingProp[root, node, Atom.GetPName[name], name, value, oldval]]]; EditNotify[world, notify, before]; PutProp[node, name, value]; EditNotify[world, notify, after]; NoteEvent[world, UndoSetProp, notify]; }; }; UndoSetProp: PROC [world: World, undoRef: REF Change] = { x: REF Change.ChangingProp ~ NARROW[undoRef]; SetProp[world: world, node: x.node, name: x.propAtom, value: x.oldval, root: x.root]; }; ChangeTextCaps: PUBLIC PROC [world: World, root: Node, text: Text, how: CapChange] = { oldRope: ROPE ~ dest.node.rope; destRope: ROPE _ oldRope; destSize: INT ~ Rope.Size[destRope]; dest: Text ~ ClipText[text, destSize]; start: INT _ dest.start; len: INT _ dest.len; notify: REF Change.ChangingText; rdr: RopeReader.Ref; initCap: BOOL _ TRUE; alreadysaved: BOOL; IF len=0 THEN RETURN; notify _ IF (alreadysaved _ AlreadySaved[world, dest.node]) THEN Alloc[] ELSE NEW[Change.ChangingText]; notify^ _ [ChangingText[root, dest.node, start, len, len, destRope, dest.node.runs]]; EditNotify[world, 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.node.rope _ destRope; RopeReader.FreeRopeReader[rdr]; EditNotify[world, notify, after]; IF alreadysaved THEN Free[notify] ELSE NoteEvent[world, UndoChangeText, notify]; }; BumpCount: PROC [text: Node] = { countLimit: INT _ 20; count: [0..countMax]; IF text=NIL THEN RETURN; IF (count_text.count) = 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: Node] 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 { CheckRope[rope]; CheckRuns[runs] }; text.rope _ IF ropeSize>256 THEN Rope.Balance[rope] ELSE Rope.Flatten[rope]; text.runs _ TextLooks.Flatten[runs]; IF debug THEN { CheckRope[text.rope]; CheckRuns[text.runs] }; RETURN [TRUE]; }; RETURN [FALSE]; }; maxSearchDepth: NAT _ 20; AlreadySaved: PUBLIC PROC [world: World, text: Node] RETURNS [BOOL] = { IF world=NIL THEN RETURN [TRUE] ELSE { event: Event ~ world.currentEvent; k: NAT _ maxSearchDepth; IF event = NIL THEN RETURN [TRUE]; FOR l: SubEvent _ event.subevents, l.next UNTIL l=NIL DO undoRef: REF Change ~ l.undoRef; WITH 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. 6TextEditImpl.mesa Copyright c 1985, 1986 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, September 22, 1986 2:47:33 pm PDT Cache of notify records Operations to create a text node -- create a text node with looks from a normal rope Fetch info operations -- returns the looks for the character at the given location -- use reader's if getting looks for sequence of locations 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 -- addrs that are in the replaced text move to destStart -- addrs that are after the replaced text are adjusted Move empty source to destination. Move addr to new location and pin so it won't be changed by Replace. Source is already in the right place. Move toward start of node: CopyText has shifted the source to the right. Move toward end of node: DeleteText will shift the result to the left. Try to find a matching item in destCharSets adjacent to the replacement. 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 PropList. Note that we do not use Atom.PropList, 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 warranty is expressed or implied for props that are not immutable. Changing Style / Format of node 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 Ê!5˜codešœ™Kšœ Ïmœ7™BKšœ"™"Kšœ4™4K™0K™+K™-—K™šÏk ˜ Kšœžœ ˜KšœžœQžœ˜oKšœ žœ9˜GKšœ žœA˜QKšœžœ7žœ˜`Kšœ žœo˜~Kšœ˜Kšœ ˜ —K˜KšÐbl œžœž˜KšžœI˜PKšžœ˜šœžœžœ˜!K˜Kšžœžœžœ˜Kšœžœ˜Kšœžœ˜Kšžœžœ žœ˜Kšœžœ˜!Kšœ žœžœ˜.Kš œ žœžœžœžœ˜#—headšœ™šœžœžœ˜Kšœ3™3K˜Kšœ˜K˜4K˜K˜—š  œžœžœžœ˜>Kšœ˜Kšœ˜Kšœžœ˜$Kšœ˜K˜——šœ™š  œžœžœžœžœ˜0Kš žœžœžœžœžœ˜K˜CK˜—šžœ žœ¡˜'šžœžœ¡˜)K˜-K˜GKšœ žœ˜Kšœ˜—šžœžœžœ¡˜3K˜-K˜GKšœ žœ˜Kšœ˜—Kšœ˜—šžœžœ žœ¡˜,šžœžœ¡˜+K˜0K˜5Kšœ žœ˜Kšœ˜—šžœžœžœ¡˜3K˜)K˜.Kšœ žœ˜Kšœ˜—Kšœ˜—šžœžœ žœ˜K•StartOfExpansionM[base: ROPE, start: INT _ 0, len: INT _ 2147483647, with: ROPE _ NIL]˜\K–†[base: TextLooks.Runs, start: INT, len: INT, replace: TextLooks.Runs, baseSize: INT, repSize: INT, tryFlat: BOOL _ TRUE]˜‹K˜—K˜K˜šžœžœžœžœ˜5Kšœ žœ«˜¾Kšœ+˜+Kšœ˜—šžœžœžœžœ˜7Kšœžœ­˜ÁKšœ-˜-Kšœ˜—K˜8K˜K˜!Kšžœžœ ˜!Kšžœ*˜.Kšžœ&˜,Kšœ˜K˜—š œžœžœ ˜K˜7Kšœ;˜;K˜:K˜+K˜—Kšœ˜—šžœ¡˜%KšœC˜CKšœ#¡,˜OKšœD˜DK˜—Kšœ˜—šžœ¡(˜/KšœS˜SKšœ#¡2˜UKšœS˜SK˜—Kšžœ žœžœ˜1Kšœ˜K˜—š  œžœžœ@žœ˜sK˜Ušžœžœ¡˜?Kšœ.˜.Kšœ.˜.šžœžœ¡#˜AKšœ žœžœ˜.Kšœ žœ˜$Kšœ6˜6K˜—šžœžœ¡$˜FKšœ žœ˜Kšœ žœžœ%˜9Kšœ8˜8Kšœ'¡,˜SK˜—Kšžœ žœžœ˜?KšžœB˜FK˜—šžœ¡˜Kšœ"˜"KšœP˜PK˜—˜K˜——K˜š œžœ!žœžœ žœžœžœ˜‹Kšžœ žœžœžœ˜šžœ˜Kšœžœ žœ˜šžœžœžœ˜KšœH™HKšœžœ˜Kšœžœžœžœ3˜Jš žœ žœžœžœž˜%Kšœžœ˜š žœžœžœžœžœž˜IKšœ žœ žœžœ˜CKšžœ˜—Kšžœ˜—Kšœ˜—Kšžœžœžœžœ˜/Kšžœ&˜,K˜—Kšœ˜K˜—š   œžœžœ.žœ žœ"žœ˜—Kšœ žœ˜ Kšœ žœ˜ K˜ šžœ žœ žœ˜"Kšœ žœ˜ Kšœ ˜ Kšœžœ˜.Kšœžœ˜0Kšœžœ"˜4šœžœ˜#Kšžœžœ˜Kšžœžœ˜Kšœ˜—šœ˜Kšœ ˜ Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜—K˜"K˜\K˜lK˜7K˜šžœžœ žœ˜,Kš œžœžœ žœžœžœ—˜ÊKšœ žœ ˜³Kšœ+˜+Kšœ˜K˜—šžœžœ˜ Kšœžœlžœ'˜ªKšœ-˜-Kšœ˜—K˜!Kšžœžœ ˜!Kšžœ*˜.K˜—Kšžœ%˜+Kšœ˜K˜—š  œžœžœ.žœ žœžœ1žœ˜®Kšžœ‚˜ˆKšœ˜K˜——šœ ™ š œžœžœ?˜[Kšœ+™+Kšœžœ˜K˜šžœ žœ˜K˜K˜YKšžœžœžœ¡ ˜)šžœ˜Kšœžœ"˜4Kš œžœžœžœ žœžœ˜]K˜”K˜"K˜K˜K˜!Kšžœžœ ˜!Kšžœ*˜.Kšœ˜—K˜—K˜K˜——šœ™Kšœ3žœ¤™ÝK™š   œžœžœžœžœ žœ˜Ršžœ&žœžœž˜:K˜Kšžœžœžœ ˜*Kšžœ˜—Kšžœžœ˜ K˜K˜—š   œžœžœžœ žœžœ˜\Kšžœ#žœžœ ˜;šžœ˜Kšœ,˜,Kšžœžœžœžœ˜Kšžœžœžœžœ!˜6K˜—K˜K˜—š  œžœžœžœ˜IKš žœ žœžœžœžœ˜!Kšžœžœžœžœ˜<šžœ˜Kšœ5˜5Kšžœžœžœ ˜/Kšžœžœžœ˜,K˜—Kšœ˜K˜—š   œžœžœ.žœžœ˜^šžœ&žœžœž˜:K˜Kšžœžœžœžœ˜3Kšžœ˜—Kšžœžœ˜Kšœ˜K˜—K˜š  œžœžœžœžœžœ žœ˜VKšžœ3˜9Kšœ˜K˜—š   œžœžœ"žœ žœ˜[Kšœžœ˜K˜šžœ žœ˜Kšœžœ˜/š œžœžœžœžœ˜šœ ˜ Kšœžœ˜!Kšœ-˜-Kšœ˜—Kšœ:˜:Kšœ˜—Kšœžœ˜2Kšœžœž˜²Kšœ:˜:K˜—Kšœ˜K˜—š  œžœžœžœžœ˜LKšœ žœ˜'Kš žœ žœžœžœžœ˜!Kšžœžœžœ"˜4Kšœ˜K˜—š œžœžœ?˜[Kšœžœ˜K˜šžœ žœ˜Kšœžœ˜/Kšœžœ'˜?Kšœžœž˜²Kšœ:˜:K˜—Kšœ˜K˜—š   œžœžœžœžœžœ˜cKšžœ3˜9Kšœ˜K˜—š œžœžœ"žœ*žœžœžœ˜‰Kšœžœ˜K˜šžœ žœ˜Kšœ žœžœžœ˜Kšœ žœ ˜Kšœžœ˜/Kšœžœ˜š œžœžœžœžœ˜šœ#˜#Kšœžœ˜!Kšœ žœ˜+Kš œžœžœ žœžœ˜(Kšœ/˜/Kšžœžœžœ˜Fšžœžœ˜ Kšœžœžœ žœ&˜bKšœ˜Kšœ˜—Kšœ˜Kšžœžœžœžœ˜Kšœ˜—Kšœ=˜=Kšœ˜—Kšœžœ¡'˜Zšžœžœ˜ Kšœ žœ˜ Kšœžœ¤˜¸Kšžœ-žœžœ˜:Kšœ:˜:Kšœ˜—K˜—Kšœ˜K˜——šœ™š   œžœžœ(žœžœ˜YKšœžœ˜&Kšžœžœžœ˜#šžœžœ˜"šœžœžœ˜?K˜9—K˜"K˜K˜!Kšœ(˜(K˜—Kšœ˜K˜—š  œžœžœ ˜;Kšœžœžœ ˜/Kšœ2˜2Kšœ˜K˜—šœžœ˜K˜—š   œžœžœ"žœžœ˜UKšœ žœžœ˜Kšžœžœžœžœ˜Kšžœžœ4˜OKšœ/˜/K˜K˜—š  œžœžœ"žœ žœžœ˜]Kšžœžœžœž˜šžœ˜Kšœžœ˜ Kšœžœ˜"Kšžœžœžœ˜Kšžœžœžœ˜#Kšœ žœ]˜iKšœ"˜"Kšœ˜Kšœ!˜!Kšœ&˜&K˜—Kšœ˜K˜—š  œžœžœ ˜9Kšœžœžœ ˜-KšœU˜UKšœ˜K˜——šœ™š œžœžœ;˜VKšœ žœ˜Kšœ žœ ˜Kšœ žœ˜$K˜&Kšœžœ˜Kšœžœ ˜Kšœžœ˜ Kšœ˜Kšœ žœžœ˜Kšœžœ˜Kšžœžœžœ˜šœ žœ1žœ˜HKšžœžœ˜—K˜UK˜"K˜!šžœž˜Kšœžœ˜Kšœ žœ˜Kšœžœžœ˜Kš œžœžœžœžœ˜K˜-Kšžœž˜šœ žœ žœ˜K˜1šžœžœžœ ž˜K˜1Kšžœ˜ ——šœ žœžœžœ ž˜#K˜1Kšžœ˜—šœ žœžœžœ ž˜$K˜1Kšžœ˜—˜ Kšœžœžœ˜šžœžœžœ ž˜šžœž˜#šžœ˜Kšœ žœžœžœ¡˜8Kšœžœ˜—šžœ˜Kšœ žœžœžœ¡˜9Kšœžœ˜—šžœ˜ K˜ Kšœžœ!˜3——Kšžœ˜ ——Kšžœžœ˜K˜K˜KšœO˜OKšžœ žœžœ˜-K˜!Kšžœ˜—K˜K˜K˜!Kšžœžœ ˜!Kšžœ*˜.Kšœ˜K˜——šœ ™ š  œžœ˜ Kšœ žœ˜K˜Kšžœžœžœžœ˜šžœžœ&ž˜MKšœ™Kšžœžœ˜$Kšžœ˜—šœ˜K˜——šœžœžœ˜K˜—š  œžœžœžœžœ˜4Kšœ$žœ˜,Kšœžœžœ˜"Kšœžœ ˜K˜Kšœ žœ˜ ˜BKšœ™Kšœ$™$Kšœ™—Kš žœ žœ žœžœžœ˜2šœžœžœž˜Sšœžœž˜K˜#K˜#K˜"Kšžœ ˜'——šžœ žœ žœ˜Kšžœžœ&˜3Kšœ žœžœžœ˜LK˜$Kšžœžœ0˜=Kšžœžœ˜Kšœ˜—Kšžœžœ˜Kšœ˜K˜—šœžœ˜K˜—š   œžœžœžœžœ˜GKšœS™SKš žœžœžœžœžœ˜šžœ˜Kšœ"˜"Kšœžœ˜Kš žœ žœžœžœžœ˜"šžœ'žœžœž˜8Kšœ žœ˜ šžœ žœž˜Kš œžœžœžœžœžœ˜AKšžœ˜—šžœžœžœ˜K™J—Kšžœ˜—Kšžœžœ˜K˜—Kšœ˜K˜——K˜Kšžœ˜—…—j “u