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];
Text Editing Operations
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 => {
must replace all of text since only save first change per event
[] ← 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]];
Move empty source to destination.
IF source.node=dest.node
THEN {
-- move text within a single node
MoveAndPinAddrs:
PROC [addr:
REF, location:
INT]
RETURNS [quit:
BOOL ←
FALSE] = {
Move addr to new location and pin so it won't be changed by Replace.
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];
Source is already in the right place.
[] ← NodeAddrs.MapTextAddrs[dest.node, MoveAndPinAddrs];
result ← CopyText[destRoot, sourceRoot, dest, source, event];
IF dest.where<source.start
THEN source.start ← source.start+source.len
Move toward start of node: Copy has shifted the source to the right.
ELSE result.start ← dest.where-source.len;
Move toward end of node: Delete will shift the result to the left.
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<beta.start
OR (alpha.start=beta.start
AND alpha.end<=beta.end)
THEN NULL -- alpha is to the "left" of beta
ELSE { temp: Text ~ alpha; alpha ← beta; beta ← temp; switched ← TRUE };
SELECT
TRUE
FROM
alpha.end=beta.start => {
-- adjacent
betaResult ← MoveText[root, root, [node, alpha.start], beta, event];
alphaResult ← [node, alpha.start+beta.len, alpha.len];
};
beta.start<alpha.end => {
-- overlapping
IF beta.end<alpha.end
THEN { alphaResult ← alpha; betaResult ← beta } -- alpha contains beta
ELSE {
-- order is alpha.start, beta.start, alpha.end, beta.end
alphaHead: Text ~ [node, alpha.start, beta.start-alpha.start];
betaTail: Text ~ [node, alpha.end, beta.end-alpha.end];
[] ← TransposeText[root, root, alphaHead, betaTail, event];
alphaResult ← [node, alpha.start+betaTail.len, alpha.len];
betaResult ← [node, alpha.start, beta.len];
};
};
ENDCASE => {
-- 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<source.start
THEN {
-- delete part of dest before source
beforeStart: INT ~ dest.start;
beforeLen: INT ~ MIN[dest.end, source.start]-beforeStart;
DeleteText[root, [node, beforeStart, beforeLen], event];
source.start ← source.start-beforeLen; -- deleting before source shifts source left
};
IF dest.start IN[source.start..source.end] THEN result ← source
ELSE result ← MoveText[root, root, [node, dest.start], source, event];
}
ELSE {
-- different nodes
DeleteText[destRoot, dest, event];
result ← MoveText[destRoot, sourceRoot, [dest.node, dest.start], source, event];
};
CharSetsForReplace:
PROC [charSet: CharSet, replaceSize:
INT, destCharSets:
ROSARY, destSize, destStart, destLen:
INT]
RETURNS [
ROSARY] ~ {
IF charSet=0 THEN RETURN [NIL]
ELSE {
item: REF CharSet ← NIL;
IF destCharSets#
NIL
THEN {
Try to find a matching item in destCharSets adjacent to the replacement.
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] };
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 Atom.PropList, but don't use Atom.PutPropOnList, 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.
This property list mechanism regards a PropList as an immutable value. We don't use the procedures from Atom since 1) they might smash an existing PropList, and 2) they hold a global monitor in AtomImpl.
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];
};
};