TextEditImpl.mesa
Copyright Ó 1985, 1986, 1987, 1988 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, October 19, 1987 9:46:08 am PDT
Doug Wyatt, February 17, 1988 6:20:54 pm PST
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];
TextEditImpl: CEDAR MONITOR
IMPORTS Atom, Basics, CardTab, EditNotify, NodeAddrs, NodeProps, Rope, RopeEdit, RopeReader, Rosary, TextLooks, TextNode, UndoEvent
EXPORTS TextEdit
= BEGIN OPEN TextEdit;
Node: TYPE = TextNode.Node;
ROPE: TYPE = Rope.ROPE;
ROSARY: TYPE = Rosary.ROSARY;
Looks: TYPE = TextLooks.Looks;
noLooks: Looks = TextLooks.noLooks;
allLooks: Looks = TextLooks.allLooks;
MaxLen, MaxOffset: INT = LAST[INT];
Event: TYPE = UndoEvent.Event;
Change: TYPE = EditNotify.Change;
Runs: TYPE = TextLooks.Runs;
CharSet: TYPE ~ [0..256);
Cache of notify records
scratch1, scratch2, scratch3: REF Change.ChangingText;
Alloc: ENTRY PROC RETURNS [scratch: REF Change.ChangingText] = INLINE --gfi saver-- {
ENABLE UNWIND => NULL;
SELECT TRUE FROM
(scratch3 # NIL) => { scratch ← scratch3; scratch3 ← NIL };
(scratch2 # NIL) => { scratch ← scratch2; scratch2 ← NIL };
(scratch1 # NIL) => { scratch ← scratch1; scratch1 ← NIL };
ENDCASE => { scratch ← NEW[Change.ChangingText] };
};
Free: ENTRY PROC [scratch: REF Change.ChangingText] = INLINE --gfi saver-- {
ENABLE UNWIND => NULL;
IF scratch3 = scratch OR scratch2 = scratch OR scratch1 = scratch THEN ERROR;
SELECT TRUE FROM
(scratch3 = NIL) => scratch3 ← scratch;
(scratch2 = NIL) => scratch2 ← scratch;
(scratch1 = NIL) => scratch1 ← scratch;
ENDCASE => NULL;
};
Text Editing Support
QuitIfNotNIL: PROC [item: REF, repeat: INT] RETURNS [quit: BOOL] ~ { quit ← item#NIL };
CheckAllNIL: PROC [rosary: ROSARY] RETURNS [ROSARY] = INLINE --gfi saver-- {
IF Rosary.MapRuns[[rosary], QuitIfNotNIL].quit THEN RETURN [rosary] ELSE RETURN [NIL];
};
maxSearchDepth: NAT ← 20;
AlreadySaved: PROC [text: Node, event: Event] RETURNS [BOOL] = INLINE --gfi saver-- {
-- returns TRUE if there is already a ChangingText record for given node in the event
k: NAT ← maxSearchDepth;
IF event = NIL THEN RETURN [TRUE];
FOR l: UndoEvent.SubEvent ← event.subevents, l.next UNTIL l=NIL DO
IF l.undoRef # NIL THEN
WITH l.undoRef SELECT FROM
x: REF Change.ChangingText => IF x.text = text THEN RETURN[TRUE];
ENDCASE;
IF (k ← k-1) = 0 THEN EXIT;
Don't keep searching too long; give up and make a new event instead. - mfp
ENDLOOP;
RETURN [FALSE];
};
DoWithNotify: PROC [action: PROC, event: Event, root: Node, text: Node, start: INT, newlen: INT, oldlen: INT] = INLINE --gfi saver-- {
alreadysaved: BOOL ~ AlreadySaved[text, event];
notify: REF Change.ChangingText = IF alreadysaved THEN Alloc[] ELSE NEW[Change.ChangingText];
notify^ ← [ChangingText[
root: root, text: text, start: start, newlen: newlen, oldlen: oldlen,
oldRope: text.rope, oldRuns: text.charLooks,
oldCharSets: text.charSets,
oldCharProps: text.charProps
]];
EditNotify.Notify[notify, before];
action[];
EditNotify.Notify[notify, after];
IF alreadysaved THEN Free[notify] ELSE UndoEvent.Note[event, UndoChangeText, notify];
};
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: BOOLFALSE;
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: INTINT.LAST;
i: INT ← start;
p: PROC[q: PROC[REF, INT]] ~ {
runAction: Rosary.RunActionType ~ {
oldList: PropList ~ NARROW[item];
oldValue: REF ~ GetPropFromList[oldList, name];
stop: BOOLFALSE; newValue: REFNIL;
[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];
};
};
Changing Style / Format of node
ChangeFormat: PUBLIC PROC [node: Node, formatName: ATOM, event: Event ← NIL, root: Node ← NIL] = {
notify: REF Change.ChangingFormat;
oldFormatName: ATOM ← node.formatName;
IF node=NIL THEN RETURN;
IF root=NIL THEN root ← TextNode.Root[node];
IF oldFormatName = formatName THEN RETURN; -- no change
notify ← NEW[Change.ChangingFormat ←
[ChangingFormat[root, node, formatName, oldFormatName]]];
EditNotify.Notify[notify, before];
node.formatName ← formatName;
EditNotify.Notify[notify, after];
IF event#NIL THEN UndoEvent.Note[event, UndoChangeFormat, notify];
};
UndoChangeFormat: PROC [undoRef: REF, currentEvent: Event] = {
WITH undoRef SELECT FROM
x: REF Change.ChangingFormat => {
ChangeFormat[x.node, x.oldFormatName, currentEvent, x.root];
};
ENDCASE => ERROR;
};
quote: ROPE ~ "\"";
ChangeStyle: PUBLIC PROC [node: Node, name: ROPE, event: Event ← NIL, root: Node ← NIL] = {
newprefix: ROPENIL;
IF node=NIL THEN RETURN;
IF Rope.Size[name] > 0 THEN newprefix ← Rope.Cat[quote, name, quote, " style"];
PutProp[node, $Prefix, newprefix, event, root];
};
PutProp: PUBLIC PROC [node: Node, name: ATOM, value: REF, event: Event ← NIL, root: Node ← NIL] = {
notify: REF Change.ChangingProp;
oldval: REF;
IF node=NIL THEN RETURN;
IF root=NIL THEN root ← TextNode.Root[node];
IF (oldval ← NodeProps.GetProp[node, name]) = value THEN RETURN;
notify ← NEW[Change.ChangingProp←[ChangingProp[root, node, Atom.GetPName[name], name, value, oldval]]];
EditNotify.Notify[notify, before];
NodeProps.PutProp[node, name, value];
EditNotify.Notify[notify, after];
IF event#NIL THEN UndoEvent.Note[event, UndoPutProp, notify];
};
UndoPutProp: PROC [undoRef: REF, currentEvent: Event] = {
WITH undoRef SELECT FROM
x: REF Change.ChangingProp => {
PutProp[x.node, x.propAtom, x.oldval, currentEvent, x.root];
};
ENDCASE => ERROR;
};
GetProp: PUBLIC PROC [node: Node, name: ATOM] RETURNS [value: REF] = {
IF node=NIL THEN RETURN[NIL];
RETURN [NodeProps.GetProp[node, name]];
};
Fetch info operations
Size: PUBLIC PROC [node: Node] RETURNS [INT] ~ {
RETURN [IF node=NIL THEN 0 ELSE Rope.InlineSize[node.rope]];
};
FetchChar: PUBLIC PROC [node: Node, index: INT] RETURNS [char: XChar ← [0, 0]] ~ {
char.code ← ORD[Rope.Fetch[node.rope, index]];
IF node.charSets#NIL THEN {
refSet: REF CharSet ~ NARROW[Rosary.Fetch[node.charSets, index]];
IF refSet#NIL THEN char.set ← refSet^;
};
};
FetchLooks: PUBLIC PROC [node: Node, index: INT] RETURNS [Looks] ~ {
charLooks: ROSARY ~ IF node=NIL THEN NIL ELSE node.charLooks;
IF charLooks=NIL
THEN {
[] ← Basics.NonNegative[Size[node]-Basics.NonNegative[index]-1];
RETURN[noLooks];
}
ELSE {
refLooks: REF Looks ~ NARROW[Rosary.Fetch[charLooks, index]];
RETURN[IF refLooks=NIL THEN noLooks ELSE refLooks^];
};
};
FetchCharPropList: PUBLIC PROC [node: Node, index: INT] RETURNS [PropList] ~ {
charProps: ROSARY ~ IF node=NIL THEN NIL ELSE node.charProps;
IF charProps=NIL
THEN {
[] ← Basics.NonNegative[Size[node]-Basics.NonNegative[index]-1];
RETURN[NIL];
}
ELSE {
propList: PropList ~ NARROW[Rosary.Fetch[charProps, index]];
RETURN[propList];
};
};
ItemRun: TYPE ~ RECORD [item: REF, start, end: INT];
FetchItemRun: PROC [base: ROSARY, index: INT] RETURNS [ItemRun] ~ {
origin: INT ~ Basics.NonNegative[index];
[] ← Basics.NonNegative[base.size-index-1]; -- bounds check
DO
WITH base SELECT FROM
f: REF Rosary.RosaryRep.leaf => {
start: INT ~ origin-index;
RETURN [[f.item, start, start+f.size]];
};
c: REF Rosary.RosaryRep.concat => {
pos: INT ~ c.base.size;
IF index < pos THEN base ← c.base
ELSE {base ← c.rest; index ← index-pos};
};
o: REF Rosary.RosaryRep.object => ERROR; -- not implemented
ENDCASE => ERROR; -- malformed rosary
ENDLOOP;
};
FetchCharSetRun: PUBLIC PROC [node: Node, index: INT]
RETURNS [CharSetRun] ~ {
IF node=NIL OR node.charSets=NIL
THEN RETURN[[charSet: 0, start: 0, end: Size[node]]]
ELSE {
run: ItemRun ~ FetchItemRun[node.charSets, index];
ref: REF CharSet ~ NARROW[run.item];
RETURN[[charSet: (IF ref=NIL THEN 0 ELSE ref^), start: run.start, end: run.end]]
};
};
FetchLooksRun: PUBLIC PROC [node: Node, index: INT]
RETURNS [LooksRun] ~ {
IF node=NIL OR node.charLooks=NIL
THEN RETURN[[looks: noLooks, start: 0, end: Size[node]]]
ELSE {
run: ItemRun ~ FetchItemRun[node.charSets, index];
ref: REF Looks ~ NARROW[run.item];
RETURN[[looks: (IF ref=NIL THEN noLooks ELSE ref^), start: run.start, end: run.end]]
};
};
FetchCharPropListRun: PUBLIC PROC [node: Node, index: INT]
RETURNS [PropListRun] ~ {
IF node=NIL OR node.charProps=NIL
THEN RETURN[[propList: NIL, start: 0, end: Size[node]]]
ELSE {
run: ItemRun ~ FetchItemRun[node.charSets, index];
RETURN[[propList: NARROW[run.item], start: run.start, end: run.end]]
};
};
Operations to create a text node
FromRope: PUBLIC PROC [rope: ROPE] RETURNS [new: Node] = {
-- create a text node with looks from a normal rope
new ← TextNode.NewTextNode[];
new.rope ← rope; new.last ← TRUE;
new.charLooks ← TextLooks.CreateRun[Rope.Size[rope]];
};
FromString: PUBLIC PROC [string: REF READONLY TEXT]
RETURNS [new: Node] = {
-- copies the contents of the string
new ← TextNode.NewTextNode[]; new.last ← TRUE;
new.rope ← Rope.FromRefText[string];
new.charLooks ← TextLooks.CreateRun[Rope.Size[new.rope]];
};
DocFromNode: PUBLIC PROC [child: Node] RETURNS [new: Node] = {
new ← TextNode.NewTextNode[];
new.child ← child; new.last ← TRUE;
child.next ← new; child.last ← TRUE;
};
Caps and Lowercase
ChangeCaps: PUBLIC PROC [root: Node, dest: Node, start: INT, len: INT, how: CapChange, event: Event] = {
destSize: INT = (IF dest = NIL THEN 0 ELSE Rope.Size[dest.rope]);
Inner: PROC = {
destRope: ROPE ← dest.rope;
rdr: RopeReader.Ref = RopeReader.GetRopeReader[];
UNTIL len=0 DO
maxText: NAT = 1000;
num: NATMIN[len, maxText];
new: REF TEXTNEW[TEXT[num]];
RopeReader.SetPosition[rdr, destRope, start];
SELECT how FROM
firstCap => {
IF num > 0 THEN {
new[0] ← RopeEdit.UpperCase[RopeReader.Get[rdr]];
FOR i: NAT IN [1..num) DO
new[i] ← RopeEdit.LowerCase[RopeReader.Get[rdr]];
ENDLOOP
};
};
allCaps => {
FOR i: NAT IN [0..num) DO
new[i] ← RopeEdit.UpperCase[RopeReader.Get[rdr]];
ENDLOOP;
};
allLower => {
FOR i: NAT IN [0..num) DO
new[i] ← RopeEdit.LowerCase[RopeReader.Get[rdr]];
ENDLOOP;
};
initCaps => {
init: BOOLTRUE;
c: CHAR;
FOR i: NAT IN [0..num) DO
SELECT c ← RopeReader.Get[rdr] FROM
IN ['a..'z] => {
new[i] ← IF init THEN c-40B ELSE c; -- force first upper
init ← FALSE };
IN ['A..'Z] => {
new[i] ← IF init THEN c ELSE c+40B; -- force others lower
init ← FALSE };
ENDCASE => {
new[i] ← c;
init ← c # '\' AND ~RopeEdit.AlphaNumericChar[c] };
ENDLOOP
};
ENDCASE => ERROR;
new.length ← num;
destRope ← RopeEdit.ReplaceByTEXT[destRope, new, 0, num, start, num, destSize];
IF Rope.Size[destRope] # destSize THEN ERROR;
len ← len-num; start ← start+num;
ENDLOOP;
dest.rope ← destRope;
RopeReader.FreeRopeReader[rdr];
};
start ← MIN[MAX[0, start], destSize];
len ← MIN[len, destSize-start];
IF len <= 0 THEN RETURN;
DoWithNotify[action: Inner, event: event, root: root, text: dest, start: start, newlen: len, oldlen: len];
};
END.