DIRECTORY
Atom USING [GetPName],
Rope USING [Balance, Cat, Concat, Fetch, Flatten, FromChar, FromProc, 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],
TextLooks USING [ChangeLooks, Concat, CreateRun, FetchLooks, Flatten, Looks, LooksStats, Replace, ReplaceByRun, Runs, Substr],
Tioga,
TiogaPrivate;
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];
};
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];
};
GetRosary:
PROC [node: Node, name:
ATOM]
RETURNS [
ROSARY] ~ {
WITH GetProp[node, name]
SELECT
FROM
rosary: ROSARY => RETURN[rosary];
ENDCASE => RETURN[NIL];
};
GetCharSets:
PROC [node: Node]
RETURNS [
ROSARY] ~
INLINE {
RETURN[IF node.hascharsets THEN GetRosary[node, $CharSets] ELSE NIL];
};
GetCharProps:
PROC [node: Node]
RETURNS [
ROSARY] ~
INLINE {
RETURN[IF node.hascharprops THEN GetRosary[node, $CharProps] ELSE NIL];
};
DoReplace:
PROC [world: World, root: Node, dest: Text, sourceRope:
ROPE, sourceRuns: Runs, sourceCharProps:
ROSARY, sourceCharSets:
ROSARY, sourceStart, sourceLen:
INT]
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[world, dest.node];
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[world, notify, before];
IF sourceLen > 0
THEN {
replaceRope ← Rope.Substr[sourceRope, sourceStart, sourceLen];
replaceRuns ← TextLooks.Substr[sourceRuns, sourceStart, sourceLen];
};
IF dest.len=0
THEN {
-- doing an insert
IF dest.start=0
THEN {
-- insert at start
newRope ← Rope.Concat[replaceRope, destRope];
newRuns ← TextLooks.Concat[replaceRuns, destRuns, sourceLen, destSize];
special ← TRUE;
}
ELSE
IF dest.start=destSize
THEN {
-- insert at end
newRope ← Rope.Concat[destRope, replaceRope];
newRuns ← TextLooks.Concat[destRuns, replaceRuns, destSize, sourceLen];
special ← TRUE;
};
}
ELSE
IF sourceLen=0
THEN {
-- doing a delete
IF dest.start=0
THEN {
-- delete from start
newRope ← Rope.Substr[destRope, dest.len, size];
newRuns ← TextLooks.Substr[destRuns, dest.len, size];
special ← TRUE;
}
ELSE
IF dest.end=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: 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 dest.node.hascharsets
OR sourceCharSets#
NIL
THEN {
newCharSets: ROSARY ~ RosaryReplace[dest: destCharSets, destSize: destSize, destStart: dest.start, destLen: dest.len, source: sourceCharSets, sourceStart: sourceStart, sourceLen: sourceLen];
PutProp[dest.node, $CharSets, newCharSets];
};
IF dest.node.hascharprops
OR sourceCharProps#
NIL
THEN {
newCharProps: ROSARY ~ RosaryReplace[dest: destCharProps, destSize: destSize, destStart: dest.start, destLen: dest.len, source: sourceCharProps, sourceStart: sourceStart, sourceLen: sourceLen];
PutProp[dest.node, $CharProps, newCharProps];
};
AddrReplace[dest.node, dest.start, dest.len, sourceLen];
BumpCount[dest.node];
EditNotify[world, notify, after];
IF alreadysaved THEN Free[notify]
ELSE NoteEvent[world, UndoChangeText, notify];
RETURN [[dest.node, dest.start, sourceLen]];
};
UndoChangeText:
PROC [world: World, undoRef:
REF Change] = {
x: REF Change.ChangingText ~ NARROW[undoRef];
must replace all of text since only save first change per event
[] ← DoReplace[
world: world,
root: x.root,
dest: [x.text, 0, Size[x.text]],
sourceRope: x.oldRope,
sourceRuns: x.oldRuns,
sourceCharProps: x.oldCharProps,
sourceCharSets: x.oldCharSets,
sourceStart: 0,
sourceLen: Rope.Size[x.oldRope]
];
};
ReplaceText:
PUBLIC
PROC [world: World, destRoot, sourceRoot: Node, dest: Text, source: Text]
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 {
IF dest.len#0 THEN DeleteText[world, destRoot, dest];
RETURN [[dest.node, dest.start, 0]];
}
ELSE
RETURN DoReplace[
world: world,
root: destRoot,
dest: dest,
sourceRope: source.node.rope,
sourceRuns: source.node.runs,
sourceCharProps: GetCharProps[source.node],
sourceCharSets: GetCharSets[source.node],
sourceStart: source.start,
sourceLen: source.len
];
DeleteText:
PUBLIC
PROC [world: World, root: Node, text: Text] = {
[] ← ReplaceByRope[world: world, root: root, dest: text, rope: NIL, inherit: FALSE, looks: ALL[FALSE], charSet: 0];
};
CopyText:
PUBLIC
PROC [world: World, destRoot, sourceRoot: Node, dest: Location, source: Text]
RETURNS [result: Text] = {
RETURN ReplaceText[world: world, destRoot: destRoot, sourceRoot: sourceRoot, dest: [dest.node, dest.where, 0], source: source];
MoveText:
PUBLIC
PROC [world: World, destRoot, sourceRoot: Node, dest: Location, source: Text]
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
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.
[] ← MapTextAddrs[dest.node, MoveAndPinAddrs];
result ← CopyText[world, destRoot, sourceRoot, dest, source];
IF dest.where<source.start
THEN source.start ← source.start+source.len
Move toward start of node: CopyText has shifted the source to the right.
ELSE result.start ← dest.where-source.len;
Move toward end of node: DeleteText will shift the result to the left.
DeleteText[world, sourceRoot, source];
UnpinAllAddrs[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;
MoveTextAddr[from: source.node, to: dest.node, addr: addr, location: new];
};
};
[] ← MapTextAddrs[source.node, MoveAddrs];
result ← CopyText[world, destRoot, sourceRoot, dest, source];
DeleteText[world, sourceRoot, source];
};
};
TransposeText:
PUBLIC
PROC [world: World, alphaRoot, betaRoot: Node, alpha: Text, beta: Text]
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 beta.start<alpha.start
OR (beta.start=alpha.start
AND beta.end<alpha.end)
THEN {
{ temp: Text ~ alpha; alpha ← beta; beta ← temp; };
switched ← TRUE;
};
IF alpha.end=beta.start
THEN {
-- turn into a move instead
betaResult ← MoveText[world, root, root, [node, alpha.start], beta];
alphaResult ← [node, alpha.start+beta.len, alpha.len];
}
ELSE
IF beta.start<alpha.end
THEN {
-- overlapping
IF beta.end<alpha.end THEN { alphaResult ← alpha; betaResult ← 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[world, root, root, alphaHead, betaTail];
alphaResult ← [node, alpha.start+betaTail.len, alpha.len];
betaResult ← [node, alpha.start, beta.len];
};
}
ELSE {
-- disjoint, alpha before beta
alphaResult ← MoveText[world, root, root, [node, beta.end], alpha];
beta.start ← beta.start-alpha.len; -- moving alpha behind beta shifts beta left
betaResult ← MoveText[world, root, root, [node, alpha.start], beta];
};
}
ELSE {
-- transpose between two different nodes
alphaResult ← MoveText[world, betaRoot, alphaRoot, [beta.node, beta.start], alpha];
beta.start ← beta.start+alpha.len; -- moving alpha in front of beta shifts beta right
betaResult ← MoveText[world, alphaRoot, betaRoot, [alpha.node, alpha.start], beta];
};
IF switched THEN RETURN[betaResult, alphaResult];
};
MoveTextOnto:
PUBLIC
PROC [world: World, destRoot, sourceRoot: Node, dest, source: Text]
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[world, root, [node, afterStart, afterLen]];
};
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[world, root, [node, beforeStart, beforeLen]];
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[world, root, root, [node, dest.start], source];
}
ELSE {
-- different nodes
DeleteText[world, destRoot, dest];
result ← MoveText[world, destRoot, sourceRoot, [dest.node, dest.start], source];
};
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 [world: World, root: Node, dest: Text, rope:
ROPE, inherit:
BOOL, looks: Looks, charSet: CharSet]
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[world, dest.node];
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[world, 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];
AddrReplace[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];
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];
PutProp[dest.node, $CharProps, newCharProps];
};
EditNotify[world, notify, after];
IF alreadysaved THEN Free[notify]
ELSE NoteEvent[world, UndoChangeText, notify];
};
RETURN [[dest.node, dest.start, ropeSize]];
};
ReplaceByChar:
PUBLIC
PROC [world: World, root: Node, dest: Text, char:
CHAR,
inherit:
BOOL ←
TRUE, looks: Looks ← noLooks, charSet: CharSet ← 0
]
RETURNS [result: Text] = {
RETURN ReplaceByRope[world: world, root: root, dest: dest, rope: Rope.FromChar[char], inherit: inherit, looks: looks, charSet: charSet];
};
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 PropList. Note that we do not use Atom.PropList, 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.
PropListGet:
PUBLIC
PROC [propList: PropList, name:
ATOM]
RETURNS [value:
REF] ~ {
FOR list: PropList ← propList, list.rest
UNTIL list=
NIL
DO
prop: Prop ~ list.first;
IF prop.name=name THEN RETURN[prop.value];
ENDLOOP;
RETURN[NIL];
};
PropListPut:
PUBLIC
PROC [propList: PropList, name:
ATOM, value:
REF]
RETURNS [PropList] ~ {
IF PropListGet[propList, name]=value THEN RETURN [propList]
ELSE {
rem: PropList ~ PropListRem[propList, name];
IF value=NIL THEN RETURN [rem]
ELSE RETURN [CONS[NEW[PropRep ← [name, value]], rem]];
};
};
PropListRem:
PROC [propList: PropList, name:
ATOM]
RETURNS [PropList] ~ {
IF propList=NIL THEN RETURN [NIL]
ELSE IF propList.first.name=name THEN RETURN [propList.rest]
ELSE {
remRest: PropList ~ PropListRem[propList.rest, name];
IF propList.rest=remRest THEN RETURN [propList]
ELSE RETURN [CONS[propList.first, remRest]];
};
};
PropListMap:
PUBLIC
PROC [propList: PropList, action: MapPropsAction]
RETURNS [quit:
BOOL] ~ {
FOR list: PropList ← propList, list.rest
UNTIL list=
NIL
DO
prop: Prop ~ list.first;
IF action[prop.name, prop.value] THEN RETURN[TRUE];
ENDLOOP;
RETURN [FALSE];
};
GetCharProp:
PUBLIC
PROC [node: Node, index:
INT, name:
ATOM]
RETURNS [value:
REF] ~ {
RETURN [PropListGet[GetCharPropList[node, index], name]];
};
SetCharProp:
PUBLIC
PROC [world: World, text: Text, name:
ATOM, value:
REF, root: Node] ~ {
size: INT ~ Size[text.node];
text ← ClipText[text, size];
IF text.len#0
THEN {
oldCharProps: ROSARY ~ GetCharProps[text.node];
p:
PROC[q:
PROC[
REF,
INT]] ~ {
action: Rosary.RunActionType ~ {
oldList: PropList ~ NARROW[item];
q[PropListPut[oldList, name, value], repeat];
};
RosaryMapRuns[oldCharProps, text.start, text.len, action];
};
replaceCharProps: ROSARY ~ Rosary.FromProcProc[p];
newCharProps: ROSARY ~ RosaryReplace[dest: oldCharProps, destSize: size, destStart: text.start, destLen: text.len, source: replaceCharProps, sourceStart: 0, sourceLen: text.len];
SetProp[world, text.node, $CharProps, newCharProps, 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]]];
};
SetCharPropList:
PUBLIC
PROC [world: World, text: Text, propList: PropList, root: Node] ~ {
size: INT ~ Size[text.node];
text ← ClipText[text, size];
IF text.len#0
THEN {
oldCharProps: ROSARY ~ GetCharProps[text.node];
replaceCharProps: ROSARY ~ Rosary.FromItem[propList, text.len];
newCharProps: ROSARY ~ RosaryReplace[dest: oldCharProps, destSize: size, destStart: text.start, destLen: text.len, source: replaceCharProps, sourceStart: 0, sourceLen: text.len];
SetProp[world, text.node, $CharProps, newCharProps, root];
};
};
MapCharProps:
PUBLIC
PROC [node: Node, index:
INT, action: MapPropsAction]
RETURNS [quit:
BOOL] ~ {
RETURN PropListMap[GetCharPropList[node, index], action];
};
ModifyCharProps:
PUBLIC
PROC [world: World, text: Text, name:
ATOM, action: ModifyPropsAction, root: Node ←
NIL]
RETURNS [quit:
BOOL] ~ {
size: INT ~ Size[text.node];
text ← ClipText[text, size];
IF text.len#0
THEN {
prefixNIL: INT ~ INT.LAST;
prefixSize: INT ← prefixNIL;
oldCharProps: ROSARY ~ GetCharProps[text.node];
i: INT ← text.start;
p:
PROC[q:
PROC[
REF,
INT]] ~ {
runAction: Rosary.RunActionType ~ {
oldList: PropList ~ NARROW[item];
oldValue: REF ~ PropListGet[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 PropListPut[oldList, name, newValue];
q[newList, repeat];
};
i ← i + repeat;
IF stop THEN RETURN [TRUE];
};
RosaryMapRuns[oldCharProps, text.start, text.len, 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, destStart: prefixSize, destLen: replaceSize, source: replaceCharProps, sourceStart: 0, sourceLen: replaceSize];
IF Rosary.Size[replaceCharProps] # replaceSize THEN ERROR;
SetProp[world, text.node, $CharProps, newCharProps, root];
};
};
};
Changing Style / Format of node
SetFormat:
PUBLIC
PROC [world: World, node: Node, formatName:
ATOM, root: Node ←
NIL] = {
oldFormatName: ATOM ~ node.formatName;
IF root=NIL THEN root ← Root[node];
IF oldFormatName#formatName
THEN {
notify:
REF Change.ChangingFormat ~
NEW[Change.ChangingFormat ←
[ChangingFormat[root, node, formatName, oldFormatName]]];
EditNotify[world, notify, before];
node.formatName ← formatName;
EditNotify[world, notify, after];
NoteEvent[world, UndoSetFormat, notify];
};
};
UndoSetFormat:
PROC [world: World, undoRef:
REF Change] = {
x: REF Change.ChangingFormat ~ NARROW[undoRef];
SetFormat[world, x.node, x.oldFormatName, x.root];
};
ChangeStyle:
PUBLIC
PROC [world: World, node: Node, name:
ROPE, root: Node ←
NIL] = {
newprefix: ROPE ← NIL;
IF node=NIL THEN RETURN;
IF Rope.Size[name] > 0 THEN newprefix ← Rope.Cat[quote, name, quote, " style"];
SetProp[world, node, $Prefix, newprefix, root];
};
SetProp:
PUBLIC
PROC [world: World, node: Node, name:
ATOM, value:
REF, root: Node ←
NIL] = {
IF node=NIL THEN NULL
ELSE {
notify: REF Change.ChangingProp;
oldval: REF ~ GetProp[node, name];
IF oldval=value THEN RETURN;
IF root=NIL THEN root ← Root[node];
notify ← NEW[Change.ChangingProp ← [ChangingProp[root, node, Atom.GetPName[name], name, value, oldval]]];
EditNotify[world, notify, before];
PutProp[node, name, value];
EditNotify[world, notify, after];
NoteEvent[world, UndoSetProp, notify];
};
};
UndoSetProp:
PROC [world: World, undoRef:
REF Change] = {
x: REF Change.ChangingProp ~ NARROW[undoRef];
SetProp[world: world, node: x.node, name: x.propAtom, value: x.oldval, root: x.root];
};