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];
Text Editing Operations
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 => {
must replace all of text since only save first change per event
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 ~ {
PROC [found: BOOL, val: Val] RETURNS [op: UpdateOperation ← none, new: Val ← NIL]
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] };
Operation to add or delete looks
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] };
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 warrantee 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;
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];
};
};