<> <> <> <> <> <> <> <<>> DIRECTORY Atom USING [DottedPairNode, GetPName, PropList], CheckNode USING [CheckRope, CheckRuns], EditNotify USING [Change, Notify], NodeAddrs USING [MapTextAddrs, MoveTextAddr, PutTextAddr, Replace, UnpinAll], NodeProps USING [GetProp, PutProp], Rope USING [Balance, Cat, Concat, Fetch, Flatten, FromChar, FromProc, FromRefText, 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], TextEdit USING [CapChange, CharSet, end, MapPropsAction, ModifyPropsAction, String, Text], TextLooks USING [allLooks, ChangeLooks, Concat, CreateRun, FetchLooks, Flatten, Looks, LooksStats, noLooks, Replace, ReplaceByRun, Runs, Substr], TextNode USING [countMax, Location, NewTextNode, Node, Root], UndoEvent USING [Event, Note, SubEvent]; TextEditImpl: CEDAR MONITOR IMPORTS Atom, CheckNode, EditNotify, NodeAddrs, NodeProps, Rope, RopeEdit, RopeReader, Rosary, TextEdit, TextLooks, TextNode, UndoEvent EXPORTS TextEdit, UndoEvent = BEGIN Change: PUBLIC TYPE = EditNotify.Change; Node: TYPE = TextNode.Node; ROPE: TYPE = Rope.ROPE; ROSARY: TYPE = Rosary.ROSARY; Runs: TYPE = TextLooks.Runs; Looks: TYPE = TextLooks.Looks; noLooks: Looks = TextLooks.noLooks; allLooks: Looks = TextLooks.allLooks; Event: TYPE = UndoEvent.Event; CapChange: TYPE ~ TextEdit.CapChange; MapPropsAction: TYPE ~ TextEdit.MapPropsAction; ModifyPropsAction: TYPE ~ TextEdit.ModifyPropsAction; CharSet: TYPE ~ TextEdit.CharSet; Location: TYPE ~ TextNode.Location; Text: TYPE ~ TextEdit.Text; String: TYPE ~ TextEdit.String; maxLen: INT ~ INT.LAST; 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; }; <<>> <> Size: PUBLIC PROC [node: Node] RETURNS [INT] = { RETURN [IF node=NIL THEN 0 ELSE Rope.InlineSize[node.rope]] }; GetRope: PUBLIC PROC [text: 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] }; GetRosary: PROC [node: Node, name: ATOM] RETURNS [ROSARY] ~ { WITH NodeProps.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]; }; Fetch: PUBLIC PROC [text: Node, index: INT] RETURNS [charSet: CharSet, char: CHAR, looks: Looks] = { [charSet, char] _ FetchChar[text, index]; looks _ FetchLooks[text, index]; }; FetchChar: PUBLIC PROC [text: Node, index: INT] RETURNS [charSet: CharSet _ 0, char: CHAR] = { charSets: ROSARY ~ GetCharSets[text]; IF charSets#NIL THEN WITH Rosary.Fetch[charSets, index] SELECT FROM refCharSet: REF CharSet => charSet _ refCharSet^; ENDCASE; char _ Rope.Fetch[text.rope, index]; }; FetchLooks: PUBLIC PROC [text: 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[text.runs, index]]; }; <> 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]; }; ClipString: PROC [string: String] RETURNS [String] ~ INLINE { size: NAT ~ string.text.length; string.start _ MIN[string.start, size]; string.len _ MIN[string.len, size-string.start]; RETURN[string]; }; 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]; }; DoReplace: PROC [root: Node, dest: Text, sourceRope: ROPE, sourceRuns: Runs, sourceCharSets: ROSARY, sourceCharProps: ROSARY, sourceStart, sourceLen: INT, event: Event] 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[dest.node, event]; 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.Notify[notify, before]; IF sourceLen > 0 THEN { replaceRope _ Rope.Substr[sourceRope, sourceStart, sourceLen]; replaceRuns _ TextLooks.Substr[sourceRuns, sourceStart, sourceLen]; }; SELECT TRUE FROM (dest.len=0) => { -- doing an insert SELECT TRUE FROM (dest.start=0) => { -- insert at start newRope _ Rope.Concat[replaceRope, destRope]; newRuns _ TextLooks.Concat[replaceRuns, destRuns, sourceLen, destSize]; special _ TRUE; } (dest.start=destSize) => { -- insert at end newRope _ Rope.Concat[destRope, replaceRope]; newRuns _ TextLooks.Concat[destRuns, replaceRuns, destSize, sourceLen]; special _ TRUE; }; ENDCASE; (sourceLen=0) => { -- doing a delete SELECT TRUE FROM (dest.start=0) => { -- delete from start newRope _ Rope.Substr[destRope, dest.len, size]; newRuns _ TextLooks.Substr[destRuns, dest.len, size]; special _ TRUE; } (dest.end=destSize) => { -- delete from end newRope _ Rope.Substr[destRope, 0, size]; newRuns _ TextLooks.Substr[destRuns, 0, size]; special _ TRUE; }; ENDCASE; }; ENDCASE; 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 sourceCharSets#NIL OR destCharSets#NIL THEN { newCharSets: ROSARY ~ RosaryReplace[dest: destCharSets, destSize: destSize, destStart: dest.start, destLen: dest.len, source: sourceCharSets, sourceStart: sourceStart, sourceLen: sourceLen]; NodeProps.PutProp[dest.node, $CharSets, newCharSets]; }; IF sourceCharProps#NIL OR destCharProps#NIL THEN { newCharProps: ROSARY ~ RosaryReplace[dest: destCharProps, destSize: destSize, destStart: dest.start, destLen: dest.len, source: sourceCharProps, sourceStart: sourceStart, sourceLen: sourceLen]; NodeProps.PutProp[dest.node, $CharProps, newCharProps]; }; NodeAddrs.Replace[dest.node, dest.start, dest.len, sourceLen]; BumpCount[dest.node]; EditNotify.Notify[notify, after]; IF alreadysaved THEN Free[notify] ELSE UndoEvent.Note[event, UndoChangeText, notify]; RETURN [[dest.node, dest.start, sourceLen]]; }; UndoChangeText: PROC [undoRef: REF Change, currentEvent: Event] = { WITH undoRef SELECT FROM x: REF Change.ChangingText => { <> [] _ DoReplace[ root: x.root, dest: [x.text, 0, Size[x.text]], sourceRope: x.oldRope, sourceRuns: x.oldRuns, sourceCharSets: x.oldCharSets, sourceCharProps: x.oldCharProps, sourceStart: 0, sourceLen: Rope.Size[x.oldRope], event: currentEvent ]; }; ENDCASE => ERROR; }; DoDelete: PROC [root: Node, text: Text, event: Event] RETURNS [result: Text] ~ { IF text.len=0 THEN RETURN[text] ELSE RETURN DoReplace[ root: root, dest: text, sourceRope: NIL, sourceRuns: NIL, sourceCharSets: NIL, sourceCharProps: NIL, sourceStart: 0, sourceLen: 0, event: event ]; }; DeleteText: PUBLIC PROC [root: Node, text: Text, event: Event] = { text _ ClipText[text, Size[text.node]]; IF text.len=0 THEN NULL ELSE [] _ DoDelete[root, text, event]; }; ReplaceText: PUBLIC PROC [destRoot, sourceRoot: Node, dest: Text, source: Text, event: Event] 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 RETURN DoDelete[destRoot, dest, event] ELSE RETURN DoReplace[ root: destRoot, dest: dest, sourceRope: source.node.rope, sourceRuns: source.node.runs, sourceCharSets: GetCharSets[source.node], sourceCharProps: GetCharProps[source.node], sourceStart: source.start, sourceLen: source.len, event: event ]; }; CopyText: PUBLIC PROC [destRoot, sourceRoot: Node, dest: Location, source: Text, event: Event] RETURNS [result: Text] = { RETURN ReplaceText[destRoot: destRoot, sourceRoot: sourceRoot, dest: [dest.node, dest.where, 0], source: source, event: event]; }; MoveText: PUBLIC PROC [destRoot, sourceRoot: Node, dest: Location, source: Text, event: Event] 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 NodeAddrs.PutTextAddr[n: dest.node, addr: addr, location: new, pin: TRUE]; }; }; IF dest.where IN [source.start..source.end] THEN RETURN [source]; <> [] _ NodeAddrs.MapTextAddrs[dest.node, MoveAndPinAddrs]; result _ CopyText[destRoot, sourceRoot, dest, source, event]; IF dest.where> ELSE result.start _ dest.where-source.len; <> DeleteText[sourceRoot, source, event]; NodeAddrs.UnpinAll[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; NodeAddrs.MoveTextAddr[from: source.node, to: dest.node, addr: addr, location: new]; }; }; [] _ NodeAddrs.MapTextAddrs[source.node, MoveAddrs]; result _ CopyText[destRoot, sourceRoot, dest, source, event]; DeleteText[sourceRoot, source, event]; }; }; TransposeText: PUBLIC PROC [alphaRoot, betaRoot: Node, alpha: Text, beta: Text, event: Event] 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 alpha.start { -- adjacent betaResult _ MoveText[root, root, [node, alpha.start], beta, event]; alphaResult _ [node, alpha.start+beta.len, alpha.len]; }; beta.start { -- overlapping IF beta.end { -- disjoint alphaResult _ MoveText[root, root, [node, beta.end], alpha, event]; beta.start _ beta.start-alpha.len; -- moving alpha behind beta shifts beta left betaResult _ MoveText[root, root, [node, alpha.start], beta, event]; }; } ELSE { -- transpose between two different nodes alphaResult _ MoveText[betaRoot, alphaRoot, [beta.node, beta.start], alpha, event]; beta.start _ beta.start+alpha.len; -- moving alpha in front of beta shifts beta right betaResult _ MoveText[alphaRoot, betaRoot, [alpha.node, alpha.start], beta, event]; }; IF switched THEN RETURN[betaResult, alphaResult]; }; MoveTextOnto: PUBLIC PROC [destRoot, sourceRoot: Node, dest, source: Text, event: Event] RETURNS [result: Text] = { dest _ ClipText[dest, Size[dest.node]]; source _ ClipText[source, Size[source.node]]; IF dest.node=source.node THEN { -- same node, check for overlap rootsMatch: Assertion ~ (destRoot=sourceRoot); root: Node ~ destRoot; node: Node ~ dest.node; IF dest.end>source.end THEN { -- delete part of dest after source afterStart: INT ~ MAX[dest.start, source.end]; afterLen: INT ~ dest.end-afterStart; DeleteText[root, [node, afterStart, afterLen], event]; }; 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 [root: Node, dest: Text, rope: ROPE, inherit: BOOL, looks: Looks, charSet: CharSet, event: Event] 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[dest.node, event]; 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.Notify[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]; NodeAddrs.Replace[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]; NodeProps.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]; NodeProps.PutProp[dest.node, $CharProps, newCharProps]; }; EditNotify.Notify[notify, after]; IF alreadysaved THEN Free[notify] ELSE UndoEvent.Note[event, UndoChangeText, notify]; }; RETURN [[dest.node, dest.start, ropeSize]]; }; InsertRope: PUBLIC PROC [root: Node, dest: Location, rope: ROPE, inherit: BOOL _ TRUE, looks: Looks _ noLooks, charSet: CharSet _ 0, event: Event _ NIL] RETURNS [result: Text] = { RETURN ReplaceByRope[root: root, dest: [dest.node, dest.where, 0], rope: rope, inherit: inherit, looks: looks, charSet: charSet, event: event] }; AppendRope: PUBLIC PROC [root: Node, dest: Node, rope: ROPE, inherit: BOOL _ TRUE, looks: Looks _ noLooks, charSet: CharSet _ 0, event: Event _ NIL] RETURNS [result: Text] = { RETURN ReplaceByRope[root: root, dest: [dest, maxLen], rope: rope, inherit: inherit, looks: looks, charSet: charSet, event: event] }; ReplaceByChar: PUBLIC PROC [root: Node, dest: Text, char: CHAR, inherit: BOOL _ TRUE, looks: Looks _ noLooks, charSet: CharSet _ 0, event: Event _ NIL] RETURNS [result: Text] = { RETURN ReplaceByRope[root: root, dest: dest, rope: Rope.FromChar[char], inherit: inherit, looks: looks, charSet: charSet, event: event]; }; InsertChar: PUBLIC PROC [root: Node, dest: Location, char: CHAR, inherit: BOOL _ TRUE, looks: Looks _ noLooks, charSet: CharSet _ 0, event: Event _ NIL] RETURNS [result: Text] = { RETURN ReplaceByChar[root: root, dest: [dest.node, dest.where, 0], char: char, inherit: inherit, looks: looks, charSet: charSet, event: event] }; AppendChar: PUBLIC PROC [root: Node, dest: Node, char: CHAR, inherit: BOOL _ TRUE, looks: Looks _ noLooks, charSet: CharSet _ 0, event: Event _ NIL] RETURNS [result: Text] = { RETURN ReplaceByChar[root: root, dest: [dest, maxLen, 0], char: char, inherit: inherit, looks: looks, charSet: charSet, event: event] }; RopeFromString: PROC [string: String] RETURNS [rope: ROPE _ NIL] ~ { string _ ClipString[string]; IF string.len>0 THEN { i: NAT _ string.start; p: PROC RETURNS [CHAR] ~ { c: CHAR ~ string.text[i]; i _ i+1; RETURN[c] }; rope _ Rope.FromProc[string.len, p]; }; }; ReplaceByString: PUBLIC PROC [root: Node, dest: Text, string: String, inherit: BOOL _ TRUE, looks: Looks _ noLooks, charSet: CharSet _ 0, event: Event _ NIL] RETURNS [result: Text] = { RETURN ReplaceByRope[root: root, dest: dest, rope: RopeFromString[string], inherit: inherit, looks: looks, charSet: charSet, event: event]; }; InsertString: PUBLIC PROC [root: Node, dest: Location, string: String, inherit: BOOL _ TRUE, looks: Looks _ noLooks, charSet: CharSet _ 0, event: Event _ NIL] RETURNS [result: Text] = { RETURN ReplaceByString[root: root, dest: [dest.node, dest.where, 0], string: string, inherit: inherit, looks: looks, charSet: charSet, event: event] }; AppendString: PUBLIC PROC [root: Node, dest: Node, string: String, inherit: BOOL _ TRUE, looks: Looks _ noLooks, charSet: CharSet _ 0, event: Event _ NIL] RETURNS [result: Text] = { RETURN ReplaceByString[root: root, dest: [dest, maxLen, 0], string: string, inherit: inherit, looks: looks, charSet: charSet, event: event] }; <> ChangeLooks: PUBLIC PROC [root: Node, text: Text, remove, add: Looks, event: Event] = { <<-- 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[text.node, event]; 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.Notify[notify, before]; text.node.runs _ newRuns; BumpCount[text.node]; EditNotify.Notify[notify, after]; IF alreadysaved THEN Free[notify] ELSE UndoEvent.Note[event, UndoChangeText, notify]; }; }; }; AddLooks: PUBLIC PROC [root: Node, text: Text, add: Looks, event: Event] = { ChangeLooks[root, text, noLooks, add, event] }; RemoveLooks: PUBLIC PROC [root: Node, text: Text, remove: Looks, event: Event] = { ChangeLooks[root, text, remove, noLooks, event] }; SetLooks: PUBLIC PROC [root: Node, text: Text, new: Looks, event: Event] = { ChangeLooks[root, text, allLooks, new, event] }; ClearLooks: PUBLIC PROC [root: Node, text: Text, event: Event] = { ChangeLooks[root, text, allLooks, noLooks, event] }; <> <> <> <<>> PropList: TYPE ~ Atom.PropList; <<>> GetCharProp: PUBLIC PROC [node: Node, index: INT, name: ATOM] RETURNS [value: REF] ~ { RETURN [GetPropFromList[GetCharPropList[node, index], name]]; }; PutCharProp: PUBLIC PROC [node: Node, index: INT, name: ATOM, value: REF, nChars: INT, event: Event, root: Node] ~ { size: INT ~ Size[node]; oldCharProps: ROSARY ~ GetCharProps[node]; p: PROC[q: PROC[REF, INT]] ~ { action: Rosary.RunActionType ~ { oldList: PropList ~ NARROW[item]; q[PutPropOnList[oldList, name, value], repeat]; }; RosaryMapRuns[oldCharProps, index, nChars, action]; }; replaceCharProps: ROSARY ~ Rosary.FromProcProc[p]; newCharProps: ROSARY ~ RosaryReplace[dest: oldCharProps, destSize: size, destStart: index, destLen: nChars, source: replaceCharProps, sourceStart: 0, sourceLen: nChars]; PutProp[node, $CharProps, newCharProps, event, 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]]]; }; PutCharPropList: PUBLIC PROC [node: Node, index: INT, propList: PropList, nChars: INT, event: Event, root: Node] ~ { size: INT ~ Size[node]; oldCharProps: ROSARY ~ GetCharProps[node]; replaceCharProps: ROSARY ~ Rosary.FromItem[propList, nChars]; newCharProps: ROSARY ~ RosaryReplace[dest: oldCharProps, destSize: size, destStart: index, destLen: nChars, source: replaceCharProps, sourceStart: 0, sourceLen: nChars]; PutProp[node, $CharProps, newCharProps, 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: Node, 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: Node, name: ATOM, index: INT, nChars: INT, action: ModifyPropsAction, event: Event, root: Node] RETURNS [quit: BOOL] ~ { prefixNIL: INT ~ INT.LAST; prefixSize: INT _ prefixNIL; oldCharProps: ROSARY ~ GetCharProps[node]; 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 = prefixNIL THEN prefixSize _ i; IF prefixSize # prefixNIL THEN { newList: PropList ~ IF newValue = oldValue THEN oldList ELSE PutPropOnList[oldList, name, newValue]; q[newList, repeat]; }; i _ i + repeat; IF stop THEN RETURN [TRUE]; }; RosaryMapRuns[oldCharProps, index, nChars, 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[node], destStart: prefixSize, destLen: replaceSize, source: replaceCharProps, sourceStart: 0, sourceLen: replaceSize]; IF Rosary.Size[replaceCharProps] # replaceSize THEN ERROR; PutProp[node, $CharProps, newCharProps, event, root]; }; }; <> ChangeFormat: PUBLIC PROC [node: Node, formatName: ATOM, event: Event _ NIL, root: Node _ 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: Node, name: ROPE, event: Event _ NIL, root: Node _ 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: Node, name: ATOM, value: REF, event: Event _ NIL, root: Node _ NIL] = { IF node=NIL THEN NULL ELSE { notify: REF Change.ChangingProp; oldval: REF ~ NodeProps.GetProp[node, name]; IF oldval=value THEN RETURN; IF root=NIL THEN root _ TextNode.Root[node]; 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 Change, currentEvent: Event] = { WITH undoRef SELECT FROM x: REF Change.ChangingProp => PutProp[node: x.node, name: x.propAtom, value: x.oldval, event: currentEvent, root: x.root]; ENDCASE => ERROR; }; GetProp: PUBLIC PROC [node: Node, name: ATOM] RETURNS [value: REF] = { RETURN [NodeProps.GetProp[node, name]]; }; <> FromRope: PUBLIC PROC [rope: ROPE] RETURNS [new: Node] = { <<-- 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[new.rope]]; }; FromString: PUBLIC PROC [string: REF READONLY TEXT] RETURNS [new: Node] = { <<-- 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: Node] RETURNS [new: Node] = { new _ TextNode.NewTextNode[]; new.child _ child; new.last _ TRUE; child.next _ new; child.last _ TRUE; }; <> ChangeCaps: PUBLIC PROC [root: Node, dest: Text, how: CapChange, event: Event] = { notify: REF Change.ChangingText; oldRope, destRope: ROPE; rdr: RopeReader.Ref; initCap: BOOL _ TRUE; destSize, start, len: INT; alreadysaved: BOOL; destSize _ Rope.Size[oldRope _ destRope _ dest.node.rope]; dest _ ClipText[dest, destSize]; start _ dest.start; IF (len _ dest.len)=0 THEN RETURN; notify _ IF (alreadysaved _ AlreadySaved[dest.node, event]) THEN Alloc[] ELSE NEW[Change.ChangingText]; notify^ _ [ChangingText[root, dest.node, start, len, len, destRope, dest.node.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.node.rope _ destRope; RopeReader.FreeRopeReader[rdr]; EditNotify.Notify[notify, after]; IF alreadysaved THEN Free[notify] ELSE UndoEvent.Note[event, UndoChangeText, notify]; }; AllCaps: PUBLIC PROC [root: Node, dest: Text, event: Event] = { ChangeCaps[root, dest, allCaps, event] }; AllLower: PUBLIC PROC [root: Node, dest: Text, event: Event] = { ChangeCaps[root, dest, allLower, event] }; InitialCaps: PUBLIC PROC [root: Node, dest: Text, event: Event] = { ChangeCaps[root, dest, initCaps, event] }; <> BumpCount: PROC [text: Node] = { 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: 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 { 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: Node, 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 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.