-- **** Text Editing Operations ****
CheckAllNIL:
PROC [rosary:
ROSARY]
RETURNS [
ROSARY] = {
action:
PROC [item:
REF, repeat:
INT]
RETURNS [quit:
BOOL] ~ {
quit ← item#NIL;
};
IF Rosary.MapRuns[[rosary], action].quit THEN RETURN [rosary]
ELSE RETURN [NIL];
};
DoReplace:
PROC [root: RefTextNode, dest: RefTextNode, destStart, destLen:
INT, sourceRope:
ROPE, sourceRuns: Runs, sourceCharProps:
ROSARY, sourceCharSets:
ROSARY, sourceStart, sourceLen:
INT, event: Event]
RETURNS [resultStart, resultLen:
INT] = {
replaceRope, newRope: ROPE;
replaceRuns, newRuns: Runs;
special: BOOL;
destRope: ROPE ~ dest.rope;
destSize: INT ~ Rope.Size[destRope];
size: INT ~ destSize-destLen+sourceLen;
destRuns: Runs ~ dest.runs;
alreadysaved: BOOL ~ AlreadySaved[dest, event];
notify:
REF Change.ChangingText ←
IF alreadysaved THEN Alloc[] ELSE NEW[Change.ChangingText];
notify^ ← [ChangingText[
root: root,
text: dest,
start: destStart,
newlen: sourceLen,
oldlen: destLen,
oldRope: destRope,
oldRuns: destRuns,
oldCharSets: IF dest.hascharsets THEN GetProp[dest, $CharSets] ELSE NIL,
oldCharProps: IF dest.hascharprops THEN GetProp[dest, $CharProps] ELSE NIL
]];
EditNotify.Notify[notify, before];
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.runs ← newRuns;
IF dest.hascharsets
OR sourceCharSets #
NIL
THEN {
r: ROSARY ← GetPropsRosary[dest, $CharSets, destSize];
IF sourceCharSets = NIL THEN sourceCharSets ← rosaryOfNil;
r ← Rosary.CatSegments[[r, 0, destStart], [sourceCharSets, sourceStart, sourceLen], [r, destStart+destLen]];
NodeProps.PutProp[dest, $CharSets, CheckAllNIL[r]];
};
IF dest.hascharprops
OR sourceCharProps #
NIL
THEN {
r: ROSARY ← GetPropsRosary[dest, $CharProps, destSize];
IF sourceCharProps = NIL THEN sourceCharProps ← rosaryOfNil;
r ← Rosary.CatSegments[[r, 0, destStart], [sourceCharProps, sourceStart, sourceLen], [r, destStart+destLen]];
NodeProps.PutProp[dest, $CharProps, CheckAllNIL[r]];
};
NodeAddrs.Replace[dest, destStart, destLen, sourceLen];
BumpCount[dest];
EditNotify.Notify[notify, after];
IF alreadysaved THEN Free[notify]
ELSE UndoEvent.Note[event, UndoChangeText, notify];
RETURN [destStart, sourceLen];
};
charSetsZero: ROSARY ~ Rosary.FromItem[NEW[CharSet ← 0], MaxLen];
rosaryOfNil:
ROSARY ~ Rosary.FromItem[
NIL, MaxLen];
MoveText:
PUBLIC
PROC [destRoot, sourceRoot: RefTextNode, dest: RefTextNode, destLoc:
INT ← 0, source: RefTextNode, 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: RefTextNode, alpha: RefTextNode, alphaStart:
INT ← 0, alphaLen:
INT ← MaxLen, beta: RefTextNode, betaStart:
INT ← 0, betaLen:
INT ← MaxLen, event: Event ←
NIL]
RETURNS [alphaResultStart, alphaResultLen, betaResultStart, betaResultLen:
INT] = {
SwitchResults:
PROC = {
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: RefTextNode ← 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: RefTextNode, dest: RefTextNode, 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: RefTextNode, dest: RefTextNode, string:
REF
READONLY
TEXT, stringStart:
NAT, stringNum:
NAT, start:
INT, len:
INT, inherit:
BOOL, looks: Looks, charSet: CharSet, event: Event]
RETURNS [resultStart, resultLen:
INT] = {
Chars: PROC RETURNS [CHAR] ~ {c: CHAR ← string[i]; i ← i + 1; RETURN [c]};
i: NAT ← stringStart ← MIN[string.length, stringStart];
stringNum ← MIN[string.length-stringStart, stringNum];
[resultStart, resultLen] ← ReplaceByRope[root: root, dest: dest, rope: Rope.FromProc[stringNum, Chars], 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: RefTextNode, sourceRoot: RefTextNode, dest: RefTextNode, destStart:
INT, destLen:
INT, source: RefTextNode, 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.runs;
sourceCharProps: ROSARY ~ IF source#NIL AND source.hascharprops THEN GetPropsRosary[source, $CharProps, sourceSize] ELSE NIL;
sourceCharSets: ROSARY ~ IF source#NIL AND source.hascharsets THEN GetPropsRosary[source, $CharSets, sourceSize] ELSE NIL;
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: text, 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: RefTextNode,
dest: RefTextNode, destStart: INT ← 0, destLen: INT ← MaxLen,
source: RefTextNode, sourceStart: INT ← 0, sourceLen: INT ← MaxLen,
event: Event ← NIL]
RETURNS [resultStart, resultLen: INT] = {
sourceSize, destSize, start, len, end, destEnd: INT;
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];
ReplaceByRope:
PUBLIC
PROC [root: RefTextNode, dest: RefTextNode, rope:
ROPE, start:
INT, len:
INT, inherit:
BOOL, looks: Looks, charSet: CharSet, event: Event]
RETURNS [resultStart, resultLen: INT] = {
IF dest=NIL THEN RETURN [0, 0]
ELSE {
destRope: ROPE ~ dest.rope;
destSize: INT ~ Rope.Size[destRope];
ropeSize: INT ~ Rope.Size[rope];
alreadysaved: BOOL ~ AlreadySaved[dest, event];
notify:
REF Change.ChangingText ~ (
IF alreadysaved THEN Alloc[]
ELSE NEW[Change.ChangingText]
);
start ← MIN[MAX[0, start], destSize];
len ← MIN[MAX[0, len], destSize-start];
notify^ ← [ChangingText[
root: root,
text: dest,
start: start,
newlen: ropeSize,
oldlen: len,
oldRope: destRope,
oldRuns: dest.runs,
oldCharSets: IF dest.hascharsets THEN GetProp[dest, $CharSets] ELSE NIL,
oldCharProps: IF dest.hascharprops THEN GetProp[dest, $CharProps] ELSE NIL
]];
EditNotify.Notify[notify, before];
dest.rope ← Rope.Replace[base: destRope, start: start, len: len, with: rope];
dest.runs ← TextLooks.ReplaceByRun[dest.runs, start, len, ropeSize, destSize, inherit, looks];
NodeAddrs.Replace[dest, start, len, ropeSize];
BumpCount[dest];
IF dest.hascharsets
OR charSet # 0
THEN {
old: ROSARY ~ GetPropsRosary[dest, $CharSets, destSize];
RosaryFromCharSet:
PROC [cs: CharSet]
RETURNS [
ROSARY] ~ {
IF cs = 0 THEN RETURN [rosaryOfNil]
ELSE {
item: REF CharSet ← NIL;
IF start-1 IN [0..destSize) THEN item ← NARROW[Rosary.Fetch[old, start-1]];
IF (item =
NIL
OR item^ # cs)
AND start+len < destSize
THEN {
item ← NARROW[Rosary.Fetch[old, start+len]];
};
IF (item = NIL OR item^ # cs) THEN item ← NEW[CharSet ← cs];
RETURN [Rosary.FromItem[item, ropeSize]];
};
};
sourceCharSets: ROSARY ~ RosaryFromCharSet[charSet];
new: ROSARY ~ Rosary.CatSegments[[old, 0, start], [sourceCharSets, 0, ropeSize], [old, start+len]];
NodeProps.PutProp[dest, $CharSets, CheckAllNIL[new]];
};
IF dest.hascharprops
THEN {
old: ROSARY ~ GetPropsRosary[dest, $CharProps, destSize];
new: ROSARY ~ Rosary.CatSegments[[old, 0, start], [rosaryOfNil, 0, ropeSize], [old, start+len]];
NodeProps.PutProp[dest, $CharProps, CheckAllNIL[new]];
};
EditNotify.Notify[notify, after];
IF alreadysaved THEN Free[notify]
ELSE UndoEvent.Note[event, UndoChangeText, notify];
RETURN [start, ropeSize];
};
};
InsertChar:
PUBLIC
PROC [root: RefTextNode, dest: RefTextNode, 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: RefTextNode, dest: RefTextNode, 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: RefTextNode, dest: RefTextNode, 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: RefTextNode, dest: RefTextNode, 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: RefTextNode, dest: RefTextNode, 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: RefTextNode, dest: RefTextNode, 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: RefTextNode, text: RefTextNode, 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 text=NIL THEN RETURN;
size ← Rope.Size[text.rope];
start ← MIN[MAX[0, start], size];
len ← MIN[MAX[0, len], size-start];
IF len=0 THEN RETURN;
newRuns ← TextLooks.ChangeLooks[text.runs, size, remove, add, start, len];
IF newRuns=text.runs THEN RETURN; -- no change
DoChangeLooks[root, text, newRuns, start, len, event];
};
DoChangeLooks:
PROC [root: RefTextNode, text: RefTextNode,
newRuns: Runs, start, len: INT, event: Event] = {
oldRuns: Runs ← text.runs;
notify: REF Change.ChangingText;
alreadysaved: BOOL;
notify ←
IF (alreadysaved ← AlreadySaved[text, event])
THEN Alloc[]
ELSE NEW[Change.ChangingText];
notify^ ← [ChangingText[root, text, start, len, len, text.rope, oldRuns]];
EditNotify.Notify[notify, before];
text.runs ← newRuns;
BumpCount[text];
EditNotify.Notify[notify, after];
IF alreadysaved THEN Free[notify]
ELSE UndoEvent.Note[event, UndoChangeText, notify];
};
AddLooks:
PUBLIC
PROC [root: RefTextNode, text: RefTextNode, add: Looks,
start:
INT, len:
INT, event: Event]
= { ChangeLooks[root, text, noLooks, add, start, len, event] };
RemoveLooks:
PUBLIC
PROC [root: RefTextNode, text: RefTextNode, remove: Looks,
start:
INT, len:
INT, event: Event]
= { ChangeLooks[root, text, remove, noLooks, start, len, event] };
SetLooks:
PUBLIC
PROC [root: RefTextNode, text: RefTextNode, new: Looks,
start:
INT, len:
INT, event: Event]
= { ChangeLooks[root, text, allLooks, new, start, len, event] };
ClearLooks:
PUBLIC
PROC [root: RefTextNode, text: RefTextNode,
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;
GetCharProp:
PUBLIC
PROC [node: RefTextNode, index:
INT, name:
ATOM]
RETURNS [value:
REF] ~ {
RETURN [GetPropFromList[GetCharPropList[node, index], name]];
};
PutCharProp:
PUBLIC
PROC [node: RefTextNode, index:
INT, name:
ATOM, value:
REF, nChars:
INT, event: Event, root: RefTextNode] ~ {
r: ROSARY ~ GetPropsRosary[node, $CharProps, Rope.Size[node.rope]];
s: ROSARY ~ Rosary.FromProcProc[p];
p:
PROC[q:
PROC[
REF,
INT]] ~ {
action: Rosary.RunActionType ~ {
oldList: PropList ~ NARROW[item];
q[PutPropOnList[oldList, name, value], repeat];
};
[] ← Rosary.MapRuns[[r, index, nChars], action];
};
t: ROSARY ~ Rosary.CatSegments[[r, 0, index], [s], [r, index + nChars]];
PutProp[node, $CharProps, CheckAllNIL[t], event, root];
};
GetCharPropList:
PUBLIC
PROC [node: RefTextNode, index:
INT]
RETURNS [PropList ←
NIL] ~ {
IF node.hascharprops
THEN {
WITH GetProp[node, $CharProps]
SELECT
FROM
rosary:
ROSARY => {
WITH Rosary.Fetch[rosary, index]
SELECT
FROM
p: PropList => RETURN [p];
ENDCASE => NULL;
};
ENDCASE => NULL;
};
};
PutCharPropList:
PUBLIC
PROC [node: RefTextNode, index:
INT, propList: PropList, nChars:
INT, event: Event, root: RefTextNode] ~ {
r: ROSARY ← GetPropsRosary[node, $CharProps, Rope.Size[node.rope]];
r ← Rosary.CatSegments[[r, 0, index], [Rosary.FromItem[propList, nChars]], [r, index + nChars]];
PutProp[node, $CharProps, CheckAllNIL[r], 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: RefTextNode, 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: RefTextNode, name:
ATOM, index:
INT, nChars:
INT, action: ModifyPropsAction, event: Event, root: RefTextNode]
RETURNS [quit:
BOOL] ~ {
prefixSize: INT ← INT.LAST;
r: ROSARY ~ GetPropsRosary[node, $CharProps, Rope.Size[node.rope]];
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 = 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, index, nChars], runAction];
};
s: ROSARY ~ Rosary.FromProcProc[p]; -- sets prefixSize and i as side effect
IF prefixSize #
INT.
LAST
THEN {
t: ROSARY ~ Rosary.CatSegments[[r, 0, prefixSize], [s], [r, i]];
IF prefixSize + Rosary.Size[s] # i THEN ERROR;
PutProp[node, $CharProps, CheckAllNIL[t], event, root];
};
};