<> <> <> <> <> <> <> <<>> 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] = { <<-- create a text node with looks from a normal rope>> 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] = { <<-- returns the looks for the character at the given location>> <<-- use reader's if getting looks for sequence of locations>> 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] = { <<-- 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>> 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.where> ELSE result.start _ dest.where-source.len; <> DeleteText[world, sourceRoot, source]; UnpinAllAddrs[dest.node]; } ELSE { -- move text from one node to another MoveAddrs: PROC [addr: REF, location: INT] RETURNS [quit: BOOL _ FALSE] = { IF location IN [source.start..source.end) THEN { new: INT ~ location-source.start+dest.where; MoveTextAddr[from: source.node, to: dest.node, addr: addr, location: new]; }; }; [] _ MapTextAddrs[source.node, MoveAddrs]; result _ CopyText[world, destRoot, sourceRoot, dest, source]; DeleteText[world, sourceRoot, source]; }; }; TransposeText: PUBLIC PROC [world: World, alphaRoot, betaRoot: Node, alpha: Text, beta: Text] RETURNS [alphaResult, betaResult: Text] = { switched: BOOL _ FALSE; alpha _ ClipText[alpha, Size[alpha.node]]; beta _ ClipText[beta, Size[beta.node]]; IF alpha.node=beta.node THEN { -- transpose within the same node rootsMatch: Assertion ~ (alphaRoot=betaRoot); root: Node ~ alphaRoot; node: Node ~ alpha.node; IF beta.startsource.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> Try: TYPE ~ {before, after}; tries: ARRAY Try OF INT _ [before: destStart-1, after: destStart+destLen]; FOR try: Try IN Try WHILE item=NIL DO i: INT ~ tries[try]; IF i IN [0..destSize) THEN WITH Rosary.Fetch[destCharSets, i] SELECT FROM destItem: REF CharSet => 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] = { <<-- first remove then add in the given range>> 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 <<-- 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: 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]; <<-- 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 { 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.