<> <> <> <> <> <> <> <<>> DIRECTORY Atom USING [DottedPairNode, GetPName, PropList], Basics, CardTab, EditNotify USING [Change, Notify], NodeAddrs USING [MapTextAddrs, MoveTextAddr, PinTextAddr, PutTextAddr, Replace, UnpinAll], NodeProps USING [GetProp, PutProp], Rope USING [Cat, Fetch, FromChar, FromRefText, InlineSize, MaxLen, Replace, ROPE, Size, Substr], RopeEdit USING [AlphaNumericChar, Concat, LowerCase, MaxLen, MaxNat, ReplaceByTEXT, UpperCase], RopeReader USING [FreeRopeReader, Get, GetRope, GetRopeReader, Ref, SetPosition], Rosary USING [CatSegments, Fetch, FromItem, FromProcProc, MapRuns, ROSARY, RosaryRep, RunActionType, Size], TextEdit, TextLooks, TextNode USING [NewTextNode, Node, Root], UndoEvent USING [Note, Event, SubEvent]; TextEditImpl: CEDAR MONITOR IMPORTS Atom, Basics, CardTab, EditNotify, NodeAddrs, NodeProps, Rope, RopeEdit, RopeReader, Rosary, TextLooks, TextNode, UndoEvent EXPORTS TextEdit = BEGIN OPEN TextEdit; Node: TYPE = TextNode.Node; 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]; Event: TYPE = UndoEvent.Event; Change: TYPE = EditNotify.Change; Runs: TYPE = TextLooks.Runs; CharSet: TYPE ~ [0..256); <> scratch1, scratch2, scratch3: REF Change.ChangingText; Alloc: ENTRY PROC RETURNS [scratch: REF Change.ChangingText] = INLINE --gfi saver-- { ENABLE UNWIND => NULL; SELECT TRUE FROM (scratch3 # NIL) => { scratch _ scratch3; scratch3 _ NIL }; (scratch2 # NIL) => { scratch _ scratch2; scratch2 _ NIL }; (scratch1 # NIL) => { scratch _ scratch1; scratch1 _ NIL }; ENDCASE => { scratch _ NEW[Change.ChangingText] }; }; Free: ENTRY PROC [scratch: REF Change.ChangingText] = INLINE --gfi saver-- { ENABLE UNWIND => NULL; IF scratch3 = scratch OR scratch2 = scratch OR scratch1 = scratch THEN ERROR; SELECT TRUE FROM (scratch3 = NIL) => scratch3 _ scratch; (scratch2 = NIL) => scratch2 _ scratch; (scratch1 = NIL) => scratch1 _ scratch; ENDCASE => NULL; }; <<>> <> QuitIfNotNIL: PROC [item: REF, repeat: INT] RETURNS [quit: BOOL] ~ { quit _ item#NIL }; CheckAllNIL: PROC [rosary: ROSARY] RETURNS [ROSARY] = INLINE --gfi saver-- { IF Rosary.MapRuns[[rosary], QuitIfNotNIL].quit THEN RETURN [rosary] ELSE RETURN [NIL]; }; maxSearchDepth: NAT _ 20; AlreadySaved: PROC [text: Node, event: Event] RETURNS [BOOL] = INLINE --gfi saver-- { <<-- 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]; }; DoWithNotify: PROC [action: PROC, event: Event, root: Node, text: Node, start: INT, newlen: INT, oldlen: INT] = INLINE --gfi saver-- { alreadysaved: BOOL ~ AlreadySaved[text, event]; notify: REF Change.ChangingText = IF alreadysaved THEN Alloc[] ELSE NEW[Change.ChangingText]; notify^ _ [ChangingText[ root: root, text: text, start: start, newlen: newlen, oldlen: oldlen, oldRope: text.rope, oldRuns: text.charLooks, oldCharSets: text.charSets, oldCharProps: text.charProps ]]; EditNotify.Notify[notify, before]; action[]; EditNotify.Notify[notify, after]; IF alreadysaved THEN Free[notify] ELSE UndoEvent.Note[event, UndoChangeText, notify]; }; <> nilRosary: ROSARY ~ Rosary.FromItem[NIL, INT.LAST]; -- "infinite" rosary of NIL ItemNotNil: Rosary.RunActionType ~ { RETURN[quit: item#NIL] }; RosaryReplace: PROC [dest: ROSARY, size: INT, d1, d2: INT, source: ROSARY, s1, s2: INT] RETURNS [ROSARY] ~ { d: ROSARY ~ IF dest=NIL THEN nilRosary ELSE dest; s: ROSARY ~ IF source=NIL THEN nilRosary ELSE source; r: ROSARY ~ Rosary.CatSegments[[d, 0, d1], [s, s1, s2-s1], [d, d2, size-d2]]; RETURN[IF Rosary.MapRuns[[r], ItemNotNil] THEN r ELSE NIL]; }; DoReplace: PROC [root: Node, dest: Node, destStart, destLen: INT, sourceRope: ROPE, sourceRuns: Runs, sourceCharProps: ROSARY, sourceCharSets: ROSARY, sourceStart, sourceLen: INT, event: Event] RETURNS [resultStart, resultLen: INT] = { Inner: PROC = { replaceRope, newRope: ROPE; replaceRuns, newRuns: Runs; special: BOOL; destRope: ROPE ~ dest.rope; destSize: INT ~ Size[dest]; size: INT ~ destSize-destLen+sourceLen; destRuns: Runs ~ dest.charLooks; 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.charLooks _ newRuns; IF dest.charSets#NIL OR sourceCharSets#NIL THEN dest.charSets _ RosaryReplace[ dest.charSets, destSize, destStart, destStart+destLen, sourceCharSets, sourceStart, sourceStart+sourceLen]; IF dest.charProps#NIL OR sourceCharProps#NIL THEN dest.charProps _ RosaryReplace[ dest.charProps, destSize, destStart, destStart+destLen, sourceCharProps, sourceStart, sourceStart+sourceLen]; NodeAddrs.Replace[dest, destStart, destLen, sourceLen]; }; DoWithNotify[action: Inner, event: event, root: root, text: dest, start: destStart, newlen: sourceLen, oldlen: destLen]; RETURN [destStart, sourceLen]; }; MoveText: PUBLIC PROC [destRoot, sourceRoot: Node, dest: Node, destLoc: INT _ 0, source: Node, 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: Node, alpha: Node, alphaStart: INT _ 0, alphaLen: INT _ MaxLen, beta: Node, betaStart: INT _ 0, betaLen: INT _ MaxLen, event: Event _ NIL] RETURNS [alphaResultStart, alphaResultLen, betaResultStart, betaResultLen: INT] = { SwitchResults: PROC = INLINE --gfi saver-- { 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: Node _ 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: Node, dest: Node, 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: Node, dest: Node, string: REF READONLY TEXT, stringStart: NAT, stringNum: NAT, start: INT, len: INT, inherit: BOOL, looks: Looks, charSet: CharSet, event: Event] RETURNS [resultStart, resultLen: INT] = { stringStart _ MIN[string.length, stringStart]; stringNum _ MIN[string.length-stringStart, stringNum]; [resultStart, resultLen] _ ReplaceByRope[root: root, dest: dest, rope: Rope.FromRefText[s: string, start: stringStart, len: stringNum], 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: Node, sourceRoot: Node, dest: Node, destStart: INT, destLen: INT, source: Node, 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.charLooks; sourceCharProps: ROSARY ~ source.charProps; sourceCharSets: ROSARY ~ source.charSets; 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: node, 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: Node, dest: Node, destStart: INT _ 0, destLen: INT _ MaxLen, source: Node, sourceStart: INT _ 0, sourceLen: INT _ MaxLen, event: Event _ NIL] RETURNS [resultStart, resultLen: INT] = { sourceSize, destSize, start, len, end, destEnd: INT _ 0; 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]; }; refFromCharSetTab: CardTab.Ref ~ CardTab.Create[]; RefFromCharSet: PROC [charSet: CharSet] RETURNS [ref: REF CharSet _ NIL] ~ { refFromCharSetAction: CardTab.UpdateAction ~ { <> IF found THEN { ref _ NARROW[val]; RETURN[op: none] } ELSE { ref _ NEW[CharSet _ charSet]; RETURN[op: store, new: ref] }; }; IF charSet=0 THEN RETURN[NIL]; CardTab.Update[refFromCharSetTab, charSet, refFromCharSetAction]; IF ref^#charSet THEN ERROR; }; ReplaceByRope: PUBLIC PROC [root: Node, dest: Node, rope: ROPE, start: INT, len: INT, inherit: BOOL, looks: Looks, charSet: CharSet, event: Event] RETURNS [resultStart, resultLen: INT _ 0] = { IF dest#NIL THEN { destRope: ROPE ~ dest.rope; destSize: INT ~ Rope.Size[destRope]; ropeSize: INT ~ Rope.Size[rope]; Inner: PROC = { dest.rope _ Rope.Replace[base: destRope, start: start, len: len, with: rope]; dest.charLooks _ TextLooks.ReplaceByRun[dest.charLooks, start, len, ropeSize, destSize, inherit, looks]; NodeAddrs.Replace[dest, start, len, ropeSize]; IF dest.charSets#NIL OR charSet#0 THEN { sourceCharSets: ROSARY ~ IF charSet=0 THEN NIL ELSE Rosary.FromItem[RefFromCharSet[charSet], ropeSize]; dest.charSets _ RosaryReplace[dest.charSets, destSize, start, start+len, sourceCharSets, 0, ropeSize]; }; IF dest.charProps#NIL THEN { dest.charProps _ RosaryReplace[dest.charProps, destSize, start, start+len, NIL, 0, ropeSize]; }; resultStart _ start; resultLen _ ropeSize; }; start _ MIN[MAX[0, start], destSize]; len _ MIN[MAX[0, len], destSize-start]; DoWithNotify[action: Inner, event: event, root: root, text: dest, start: start, newlen: ropeSize, oldlen: len]; }; }; InsertChar: PUBLIC PROC [root: Node, dest: Node, 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: Node, dest: Node, 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: Node, dest: Node, 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: Node, dest: Node, 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: Node, dest: Node, 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: Node, dest: Node, rope: ROPE, inherit: BOOL, looks: Looks, charSet: CharSet, event: Event] RETURNS [resultStart, resultLen: INT] = { [resultStart, resultLen] _ InsertRope[root: root, dest: dest, rope: rope, destLoc: MaxLen, inherit: inherit, looks: looks, charSet: charSet, event: event] }; <> ChangeLooks: PUBLIC PROC [root: Node, node: Node, 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 node=NIL THEN RETURN; size _ Rope.Size[node.rope]; start _ MIN[MAX[0, start], size]; len _ MIN[MAX[0, len], size-start]; IF len=0 THEN RETURN; newRuns _ TextLooks.ChangeLooks[node.charLooks, size, remove, add, start, len]; IF newRuns=node.charLooks THEN RETURN; -- no change DoChangeLooks[root, node, newRuns, start, len, event]; }; DoChangeLooks: PROC [root: Node, text: Node, newRuns: Runs, start, len: INT, event: Event] = { Inner: PROC = { text.charLooks _ newRuns }; DoWithNotify[action: Inner, event: event, root: root, text: text, start: start, newlen: len, oldlen: len]; }; AddLooks: PUBLIC PROC [root: Node, text: Node, add: Looks, start: INT, len: INT, event: Event] = { ChangeLooks[root, text, noLooks, add, start, len, event] }; RemoveLooks: PUBLIC PROC [root: Node, text: Node, remove: Looks, start: INT, len: INT, event: Event] = { ChangeLooks[root, text, remove, noLooks, start, len, event] }; SetLooks: PUBLIC PROC [root: Node, text: Node, new: Looks, start: INT, len: INT, event: Event] = { ChangeLooks[root, text, allLooks, new, start, len, event] }; ClearLooks: PUBLIC PROC [root: Node, text: Node, start: INT, len: INT, event: Event] = { ChangeLooks[root, text, allLooks, noLooks, start, len, event] }; <> <> <> <<>> PropList: TYPE ~ Atom.PropList; <<>> 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] = { SELECT TRUE FROM (p = NIL) => RETURN [NIL]; (p.first.key = key) => RETURN [p.rest]; ENDCASE => { rest: PropList _ RemoveKeyFrom[p.rest]; RETURN [IF rest # p.rest THEN CONS[p.first, rest] ELSE p] }; }; new _ RemoveKeyFrom[propList]; IF value # NIL THEN new _ CONS[NEW[Atom.DottedPairNode _ [key, value]], new]; }; GetCharProp: PUBLIC PROC [node: Node, index: INT, name: ATOM] RETURNS [value: REF] ~ { RETURN [GetPropFromList[FetchCharPropList[node, index], name]]; }; MapCharProps: PUBLIC PROC [node: Node, index: INT, action: MapPropsAction] RETURNS [quit: BOOL] ~ { FOR list: PropList _ FetchCharPropList[node, index], list.rest UNTIL list=NIL DO IF action[NARROW[list.first.key], list.first.val].quit THEN RETURN[TRUE]; ENDLOOP; RETURN[FALSE]; }; ChangeCharProps: PROC [node: Node, size, start, len: INT, source: ROSARY, event: Event, root: Node] ~ { newCharProps: ROSARY ~ RosaryReplace[node.charProps, size, start, start+len, source, 0, len]; inner: PROC ~ { node.charProps _ newCharProps }; DoWithNotify[action: inner, event: event, root: root, text: node, start: start, newlen: len, oldlen: len]; }; PutCharProp: PUBLIC PROC [node: Node, index: INT, name: ATOM, value: REF, nChars: INT, event: Event, root: Node] ~ { size: INT ~ Rope.Size[node.rope]; start: INT ~ MAX[LONG[0], MIN[index, size]]; len: INT ~ start+MAX[LONG[0], MIN[nChars, size-start]]; r: ROSARY ~ IF node.charProps=NIL THEN nilRosary ELSE node.charProps; p: PROC[q: PROC[REF, INT]] ~ { action: Rosary.RunActionType ~ { oldList: PropList ~ NARROW[item]; q[PutPropOnList[oldList, name, value], repeat]; }; [] _ Rosary.MapRuns[[r, start, len], action]; }; s: ROSARY ~ Rosary.FromProcProc[p]; ChangeCharProps[node, size, start, len, s, event, root]; }; PutCharPropList: PUBLIC PROC [node: Node, index: INT, propList: PropList, nChars: INT, event: Event, root: Node] ~ { size: INT ~ Rope.Size[node.rope]; start: INT ~ MAX[LONG[0], MIN[index, size]]; len: INT ~ start+MAX[LONG[0], MIN[nChars, size-start]]; s: ROSARY ~ Rosary.FromItem[propList, len]; ChangeCharProps[node, size, start, len, s, event, root]; }; ModifyCharProps: PUBLIC PROC [node: Node, name: ATOM, index: INT, nChars: INT, action: ModifyPropsAction, event: Event, root: Node] RETURNS [quit: BOOL] ~ { size: INT ~ Rope.Size[node.rope]; start: INT ~ MAX[LONG[0], MIN[index, size]]; len: INT ~ start+MAX[LONG[0], MIN[nChars, size-start]]; r: ROSARY ~ IF node.charProps=NIL THEN nilRosary ELSE node.charProps; prefixSize: INT _ INT.LAST; i: INT _ start; 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, start, len], runAction]; }; s: ROSARY ~ Rosary.FromProcProc[p]; -- sets prefixSize and i as side effect IF prefixSize # INT.LAST THEN { sSize: INT ~ Rosary.Size[s]; IF (prefixSize+sSize)#i THEN ERROR; ChangeCharProps[node, size, prefixSize, sSize, s, 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] = { 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: Node, name: ATOM] RETURNS [value: REF] = { IF node=NIL THEN RETURN[NIL]; RETURN [NodeProps.GetProp[node, name]]; }; <> Size: PUBLIC PROC [node: Node] RETURNS [INT] ~ { RETURN [IF node=NIL THEN 0 ELSE Rope.InlineSize[node.rope]]; }; FetchChar: PUBLIC PROC [node: Node, index: INT] RETURNS [char: XChar _ [0, 0]] ~ { char.code _ ORD[Rope.Fetch[node.rope, index]]; IF node.charSets#NIL THEN { refSet: REF CharSet ~ NARROW[Rosary.Fetch[node.charSets, index]]; IF refSet#NIL THEN char.set _ refSet^; }; }; FetchLooks: PUBLIC PROC [node: Node, index: INT] RETURNS [Looks] ~ { charLooks: ROSARY ~ IF node=NIL THEN NIL ELSE node.charLooks; IF charLooks=NIL THEN { [] _ Basics.NonNegative[Size[node]-Basics.NonNegative[index]-1]; RETURN[noLooks]; } ELSE { refLooks: REF Looks ~ NARROW[Rosary.Fetch[charLooks, index]]; RETURN[IF refLooks=NIL THEN noLooks ELSE refLooks^]; }; }; FetchCharPropList: PUBLIC PROC [node: Node, index: INT] RETURNS [PropList] ~ { charProps: ROSARY ~ IF node=NIL THEN NIL ELSE node.charProps; IF charProps=NIL THEN { [] _ Basics.NonNegative[Size[node]-Basics.NonNegative[index]-1]; RETURN[NIL]; } ELSE { propList: PropList ~ NARROW[Rosary.Fetch[charProps, index]]; RETURN[propList]; }; }; ItemRun: TYPE ~ RECORD [item: REF, start, end: INT]; FetchItemRun: PROC [base: ROSARY, index: INT] RETURNS [ItemRun] ~ { origin: INT ~ Basics.NonNegative[index]; [] _ Basics.NonNegative[base.size-index-1]; -- bounds check DO WITH base SELECT FROM f: REF Rosary.RosaryRep.leaf => { start: INT ~ origin-index; RETURN [[f.item, start, start+f.size]]; }; c: REF Rosary.RosaryRep.concat => { pos: INT ~ c.base.size; IF index < pos THEN base _ c.base ELSE {base _ c.rest; index _ index-pos}; }; o: REF Rosary.RosaryRep.object => ERROR; -- not implemented ENDCASE => ERROR; -- malformed rosary ENDLOOP; }; FetchCharSetRun: PUBLIC PROC [node: Node, index: INT] RETURNS [CharSetRun] ~ { IF node=NIL OR node.charSets=NIL THEN RETURN[[charSet: 0, start: 0, end: Size[node]]] ELSE { run: ItemRun ~ FetchItemRun[node.charSets, index]; ref: REF CharSet ~ NARROW[run.item]; RETURN[[charSet: (IF ref=NIL THEN 0 ELSE ref^), start: run.start, end: run.end]] }; }; FetchLooksRun: PUBLIC PROC [node: Node, index: INT] RETURNS [LooksRun] ~ { IF node=NIL OR node.charLooks=NIL THEN RETURN[[looks: noLooks, start: 0, end: Size[node]]] ELSE { run: ItemRun ~ FetchItemRun[node.charLooks, index]; ref: REF Looks ~ NARROW[run.item]; RETURN[[looks: (IF ref=NIL THEN noLooks ELSE ref^), start: run.start, end: run.end]] }; }; FetchCharPropListRun: PUBLIC PROC [node: Node, index: INT] RETURNS [PropListRun] ~ { IF node=NIL OR node.charProps=NIL THEN RETURN[[propList: NIL, start: 0, end: Size[node]]] ELSE { run: ItemRun ~ FetchItemRun[node.charProps, index]; RETURN[[propList: NARROW[run.item], start: run.start, end: run.end]] }; }; <> 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.charLooks _ TextLooks.CreateRun[Rope.Size[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.charLooks _ 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: Node, start: INT, len: INT, how: CapChange, event: Event] = { destSize: INT = (IF dest = NIL THEN 0 ELSE Rope.Size[dest.rope]); Inner: PROC = { destRope: ROPE _ dest.rope; rdr: RopeReader.Ref = RopeReader.GetRopeReader[]; UNTIL len=0 DO 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; c: CHAR; 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]; }; start _ MIN[MAX[0, start], destSize]; len _ MIN[len, destSize-start]; IF len <= 0 THEN RETURN; DoWithNotify[action: Inner, event: event, root: root, text: dest, start: start, newlen: len, oldlen: len]; }; END.