TextEditImpl.mesa
Copyright Ó 1985, 1986, 1987, 1989, 1990, 1991, 1992 by Xerox Corporation. All rights reserved.
written by Bill Paxton, March 1981
last edit by Bill Paxton, December 28, 1982 11:43 am
Last Edited by: Maxwell, January 5, 1983 3:31 pm
Michael Plass, April 6, 1989 2:38:04 pm PDT
Christian Jacobi, May 2, 1989 6:03:06 pm PDT
Willie-s, February 13, 1991 6:46 pm PST
Doug Wyatt, June 19, 1992 3:56 pm PDT
DIRECTORY
Ascii USING [Lower, Upper],
CardTab USING [Ref, Create, Update, UpdateAction],
Char USING [XCHAR, CharSet, Make],
EditNotify USING [Change, Notify],
IO USING [STREAM, ROS, RopeFromROS, PutByte, PutRope],
NodeAddrs USING [MapTextAddrs, MoveTextAddr, PinTextAddr, PutTextAddr, Replace, UnpinAll],
NodeProps,
Prop USING [PropList, Get, Put],
Rope USING [ActionType, Fetch, FromChars, Map, Replace, ROPE, Size, SkipTo, Substr],
Rosary USING [CatSegments, Fetch, FetchRun, FromItem, FromRuns, MapRuns, ROSARY, Run, RunActionType, Size],
TextEdit,
TextEditBogus,
TextEditExtras,
TextLooks USING [ModifyLooks, Runs],
Tioga USING [Node, NodeRep, Looks, noLooks],
UndoEvent USING [Note, Event, EventRep, SubEvent];
=
BEGIN
ROPE: TYPE = Rope.ROPE;
ROSARY: TYPE = Rosary.ROSARY;
XCHAR: TYPE ~ Char.XCHAR;
CharSet: TYPE ~ Char.CharSet;
Node: TYPE = Tioga.Node;
Looks: TYPE = Tioga.Looks;
noLooks: Looks = Tioga.noLooks;
Runs: TYPE = TextLooks.Runs;
Event: TYPE = UndoEvent.Event;
EventRep: PUBLIC TYPE = UndoEvent.EventRep;
Change: TYPE = EditNotify.Change;
MaxLen: INT = LAST[INT];
CapChange: TYPE ~ TextEdit.CapChange;
DestSpanProc: TYPE ~ TextEdit.DestSpanProc;
OneSpanProc: TYPE ~ TextEdit.OneSpanProc;
MapPropsAction: TYPE ~ TextEdit.MapPropsAction;
ModifyPropsAction: TYPE ~ TextEdit.ModifyPropsAction;
Text Editing Operations
nilRosary:
ROSARY ~ Rosary.FromItem[
NIL,
INT.
LAST];
RosaryReplace:
PROC [dest:
ROSARY, destSize, destStart, destLen:
INT,
source:
ROSARY, sourceStart, sourceLen:
INT]
RETURNS [
ROSARY] ~ {
IF dest=NIL AND source=NIL THEN RETURN[NIL]
ELSE {
destRosary: ROSARY ~ IF dest=NIL THEN nilRosary ELSE dest;
sourceRosary: ROSARY ~ IF source=NIL THEN nilRosary ELSE source;
destEnd: INT ~ destStart+destLen;
result:
ROSARY ~ Rosary.CatSegments[
[destRosary, 0, destStart],
[sourceRosary, sourceStart, sourceLen],
[destRosary, destEnd, destSize-destEnd]
];
notNil: Rosary.RunActionType ~ { RETURN[item#NIL] };
RETURN[IF Rosary.MapRuns[[result], notNil] THEN result ELSE NIL];
};
};
RosaryFromItem:
PROC [item:
REF, repeat:
INT]
RETURNS [
ROSARY] ~ {
RETURN[IF item=NIL THEN NIL ELSE Rosary.FromItem[item, repeat]];
};
RosaryReplaceWithItem: PROC [dest: ROSARY, destSize, destStart, destLen: INT,
sourceItem: REF, sourceLen: INT] RETURNS [ROSARY] ~ {
IF dest=NIL AND sourceItem=NIL THEN RETURN[NIL]
ELSE {
destRosary: ROSARY ~ IF dest=NIL THEN nilRosary ELSE dest;
sourceRosary: ROSARY ~ IF sourceItem=NIL THEN nilRosary ELSE Rosary.FromItem[sourceItem, sourceLen];
destEnd: INT ~ destStart+destLen;
result: ROSARY ~ Rosary.CatSegments[
[destRosary, 0, destStart],
[sourceRosary, 0, sourceLen],
[destRosary, destEnd, destSize-destEnd]
];
notNil: Rosary.RunActionType ~ { RETURN[sourceItem#NIL] };
RETURN[IF Rosary.MapRuns[[result], notNil] THEN result ELSE NIL];
};
};
ReplaceByContents:
PUBLIC
PROC [root: Node, dest: Node, destStart, destLen:
INT,
sourceRope:
ROPE, sourceRuns, sourceCharSets, sourceCharProps:
ROSARY,
sourceStart, sourceLen:
INT, event: Event]
RETURNS [resultStart, resultLen:
INT] = {
destSize: INT ~ InlineSize[dest];
sourceSize: INT ~ Rope.Size[sourceRope];
changeAction:
PROC ~ {
dest.rope ¬ Rope.Replace[base: dest.rope, start: destStart, len: destLen,
with: Rope.Substr[sourceRope, sourceStart, sourceLen]];
dest.runs ¬ RosaryReplace[dest: dest.runs,
destSize: destSize, destStart: destStart, destLen: destLen,
source: sourceRuns, sourceStart: sourceStart, sourceLen: sourceLen];
dest.charSets ¬ RosaryReplace[dest: dest.charSets,
destSize: destSize, destStart: destStart, destLen: destLen,
source: sourceCharSets, sourceStart: sourceStart, sourceLen: sourceLen];
dest.charProps ¬ RosaryReplace[dest: dest.charProps,
destSize: destSize, destStart: destStart, destLen: destLen,
source: sourceCharProps, sourceStart: sourceStart, sourceLen: sourceLen];
NodeAddrs.Replace[dest, destStart, destLen, sourceLen];
};
sourceStart ¬ MIN[MAX[0, 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 OR sourceLen#0 THEN DoWithNotify[action: changeAction, event: event,
root: root, text: dest, start: destStart, newlen: sourceLen, oldlen: destLen];
RETURN [destStart, sourceLen];
};
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 RETURN [0, 0]
ELSE IF source=NIL THEN RETURN ReplaceByContents[
root: destRoot, dest: dest, destStart: destStart, destLen: destLen,
sourceRope: NIL, sourceRuns: NIL, sourceCharSets: NIL, sourceCharProps: NIL,
sourceStart: 0, sourceLen: 0, event: event]
ELSE RETURN ReplaceByContents[
root: destRoot, dest: dest, destStart: destStart, destLen: destLen,
sourceRope: source.rope, sourceRuns: source.runs,
sourceCharSets: source.charSets, sourceCharProps: source.charProps,
sourceStart: sourceStart, sourceLen: sourceLen, event: event];
DeleteText:
PUBLIC OneSpanProc = {
[] ¬ ReplaceText[destRoot: root, sourceRoot: NIL,
dest: text, destStart: start, destLen: len,
source: NIL, sourceStart: 0, sourceLen: 0,
event: event]
};
CopyText:
PUBLIC DestSpanProc = {
-- copy the specified text
RETURN ReplaceText[destRoot: destRoot, sourceRoot: sourceRoot,
dest: dest, destStart: destLoc, destLen: 0,
source: source, sourceStart: start, sourceLen: len,
event: event];
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 ~ InlineSize[source];
destSize: INT ~ IF dest=source THEN sourceSize ELSE InlineSize[dest];
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];
};
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;
RETURN MoveText[destRoot, sourceRoot, dest, destStart, source, start, len, event];
};
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 ¬ InlineSize[alpha];
alphaStart ¬ MIN[MAX[0, alphaStart], alphaSize];
alphaResultLen ¬ alphaLen ¬ MIN[MAX[0, alphaLen], alphaSize-alphaStart];
betaSize ¬ IF beta=alpha THEN alphaSize ELSE InlineSize[beta];
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;
};
looksTab: CardTab.Ref ~ CardTab.Create[];
ItemFromLooks:
PUBLIC
PROC [looks: Looks]
RETURNS [item:
REF ¬
NIL] ~ {
action: CardTab.UpdateAction ~ {
IF found THEN { item ¬ val; RETURN[none] }
ELSE { item ¬ NEW[Looks ¬ looks]; RETURN[store, item] }
};
IF looks#noLooks THEN CardTab.Update[looksTab, LOOPHOLE[looks], action];
};
LooksFromItem:
PUBLIC
PROC [item:
REF]
RETURNS [Looks] ~ {
WITH item
SELECT
FROM
item: REF Looks => RETURN[item];
ENDCASE => RETURN[noLooks];
};
charSetTab: CardTab.Ref ~ CardTab.Create[];
ItemFromCharSet:
PUBLIC
PROC [charSet: CharSet]
RETURNS [item:
REF ¬
NIL] ~ {
action: CardTab.UpdateAction ~ {
IF found THEN { item ¬ val; RETURN[none] }
ELSE { item ¬ NEW[CharSet ¬ charSet]; RETURN[store, item] }
};
IF charSet#0 THEN CardTab.Update[charSetTab, charSet, action];
};
CharSetFromItem:
PUBLIC
PROC [item:
REF]
RETURNS [CharSet] ~ {
WITH item
SELECT
FROM
item: REF CharSet => RETURN[item];
ENDCASE => RETURN[0];
};
ItemFromPropList:
PUBLIC
PROC [propList: PropList]
RETURNS [
REF] ~ {
RETURN[propList];
};
PropListFromItem:
PUBLIC
PROC [item:
REF]
RETURNS [PropList] ~ {
WITH item
SELECT
FROM
item: PropList => RETURN[item];
ENDCASE => RETURN[NIL];
};
ReplaceByRope:
PUBLIC
PROC [root: Node, dest: Node, start, len:
INT,
rope:
ROPE, looks: Looks, charSet: CharSet, charProps: PropList,
event: Event]
RETURNS [resultStart, resultLen:
INT ¬ 0] = {
IF dest#
NIL
THEN {
sourceLen: INT ~ Rope.Size[rope];
RETURN ReplaceByContents[root: root,
dest: dest, destStart: start, destLen: len, sourceRope: rope,
sourceRuns: RosaryFromItem[ItemFromLooks[looks], sourceLen],
sourceCharSets: RosaryFromItem[ItemFromCharSet[charSet], sourceLen],
sourceCharProps: RosaryFromItem[ItemFromPropList[charProps], sourceLen],
sourceStart: 0, sourceLen: sourceLen, event: event];
};
};
ReplaceByRope: PUBLIC PROC [root: Node, dest: Node, start, len: INT,
rope: ROPE, looks: Looks, charSet: CharSet, charProps: PropList,
event: Event] RETURNS [resultStart, resultLen: INT ¬ 0] = {
IF dest#NIL THEN {
destSize: INT ~ InlineSize[dest];
destStart: INT ~ MIN[MAX[0, start], destSize];
destLen: INT ~ MIN[MAX[0, len], destSize-destStart];
sourceLen: INT ~ Rope.Size[rope];
changeAction: PROC = {
dest.rope ¬ Rope.Replace[base: dest.rope, start: destStart, len: destLen, with: rope];
dest.runs ← RosaryReplaceWithItem[dest: dest.runs,
destSize: destSize, destStart: destStart, destLen: destLen,
sourceItem: ItemFromLooks[looks], sourceLen: sourceLen];
dest.charSets ← RosaryReplaceWithItem[dest: dest.charSets,
destSize: destSize, destStart: destStart, destLen: destLen,
sourceItem: ItemFromCharSet[charSet], sourceLen: sourceLen];
dest.charProps ← RosaryReplaceWithItem[dest: dest.charProps,
destSize: destSize, destStart: destStart, destLen: destLen,
sourceItem: charProps, sourceLen: sourceLen];
NodeAddrs.Replace[dest, destStart, destLen, sourceLen];
};
IF destLen#0 OR sourceLen#0 THEN DoWithNotify[action: changeAction, event: event,
root: root, text: dest, start: destStart, newlen: sourceLen, oldlen: destLen];
RETURN [destStart, sourceLen];
};
};
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 Prop.PropList; these property lists are immutable. The values on the property lists are externalized and internalized using the procedures registered with NodeProps, but no warrantee is expressed or implied for props that are not immutable.
PropList: TYPE ~ Prop.PropList;
GetPropFromList:
PUBLIC
PROC [propList: PropList, key:
ATOM]
RETURNS [value:
REF] ~ {
RETURN[Prop.Get[propList, key]];
};
PutPropOnList:
PUBLIC
PROC [propList: PropList, key:
ATOM, value:
REF]
RETURNS [PropList] ~ {
RETURN[Prop.Put[propList, key, value]];
};
GetCharPropList:
PUBLIC
PROC [node: Node, index:
INT]
RETURNS [PropList ¬
NIL] ~ {
IF node.charProps=
NIL
THEN { [] ¬ Basics.BoundsCheck[index, InlineSize[node]]; RETURN[NIL] }
ELSE RETURN[PropListFromItem[Rosary.Fetch[node.charProps, index]]];
};
PutCharPropList:
PUBLIC
PROC [node: Node, index:
INT, propList: PropList,
nChars:
INT, event: Event, root: Node] ~ {
destSize: INT ~ InlineSize[node];
destStart: INT ~ MIN[MAX[0, index], destSize];
destLen: INT ~ MIN[MAX[0, nChars], destSize-destStart];
changeAction:
PROC ~ {
source: ROSARY ~ RosaryFromItem[ItemFromPropList[propList], destLen];
node.charProps ¬ RosaryReplace[dest: node.charProps,
destSize: destSize, destStart: destStart, destLen: destLen,
source: source, sourceStart: 0, sourceLen: destLen];
};
IF destLen#0 THEN DoWithNotify[action: changeAction, event: event,
root: root, text: node, start: destStart, newlen: destLen, oldlen: destLen];
};
GetCharProp:
PUBLIC
PROC [node: Node, index:
INT, name:
ATOM]
RETURNS [value:
REF] ~ {
RETURN [Prop.Get[GetCharPropList[node, index], name]];
};
PutCharProp:
PUBLIC
PROC [node: Node, index:
INT, name:
ATOM, value:
REF,
nChars:
INT, event: Event, root: Node] ~ {
destSize: INT ~ InlineSize[node];
destStart: INT ~ MIN[MAX[0, index], destSize];
destLen: INT ~ MIN[MAX[0, nChars], destSize-destStart];
destRosary: ROSARY ~ IF node.charProps=NIL THEN nilRosary ELSE node.charProps;
p:
PROC [q:
PROC [item:
REF, repeat:
INT]] ~ {
putProp: Rosary.RunActionType ~ {
old: PropList ~ PropListFromItem[item];
new: PropList ~ Prop.Put[old, name, value];
q[new, repeat];
};
[] ¬ Rosary.MapRuns[[destRosary, destStart, destLen], putProp];
};
sourceRosary: ROSARY ~ Rosary.FromRuns[p];
changeAction:
PROC ~ {
node.charProps ¬ RosaryReplace[dest: node.charProps,
destSize: destSize, destStart: destStart, destLen: destLen,
source: sourceRosary, sourceStart: 0, sourceLen: destLen];
};
IF Rosary.Size[sourceRosary]#destLen THEN ERROR;
IF destLen#0 THEN DoWithNotify[action: changeAction, event: event,
root: root, text: node, start: destStart, newlen: destLen, oldlen: destLen];
};
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 ¬
FALSE] ~ {
destSize: INT ~ InlineSize[node];
destStart: INT ~ MIN[MAX[0, index], destSize];
destLen: INT ~ MIN[MAX[0, nChars], destSize-destStart];
r: ROSARY ~ IF node.charProps=NIL THEN nilRosary ELSE node.charProps;
prefixSize: INT ¬ INT.LAST;
i: INT ¬ index;
p:
PROC[q:
PROC[
REF,
INT]] ~ {
runAction: Rosary.RunActionType ~ {
oldList: PropList ~ PropListFromItem[item];
oldValue: REF ~ Prop.Get[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 Prop.Put[oldList, name, newValue];
q[newList, repeat];
};
i ¬ i + repeat;
IF stop THEN RETURN [TRUE];
};
quit ¬ Rosary.MapRuns[[r, destStart, destLen], runAction];
};
s: ROSARY ~ Rosary.FromRuns[p]; -- sets prefixSize, i, and quit as side effect
IF prefixSize #
INT.
LAST
THEN {
t: ROSARY ~ Rosary.CatSegments[[r, 0, prefixSize], [s], [r, i, destSize-i]];
len: INT ~ Rosary.Size[s];
changeAction: PROC ~ { node.charProps ¬ t };
IF prefixSize + len # i THEN ERROR;
DoWithNotify[action: changeAction, event: event,
root: root, text: node, start: prefixSize, newlen: len, oldlen: len];
};
};