TextEditImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
written by Bill Paxton, March 1981
last edit by Bill Paxton, December 28, 1982 11:43 am
Last Edited by: Maxwell, January 5, 1983 3:31 pm
Michael Plass, April 1, 1985 4:14:29 pm PST
Doug Wyatt, March 4, 1985 1:16:05 pm PST
DIRECTORY
Atom,
CheckNode USING [CheckRope, CheckRuns],
EditNotify USING [Change, Notify],
NodeAddrs USING [MapTextAddrs, MoveTextAddr, PinTextAddr, PutTextAddr, Replace, UnpinAll],
NodeProps USING [GetProp, PutProp],
Rope,
RopeEdit,
RopeReader USING [FreeRopeReader, Get, GetRope, GetRopeReader, Ref, SetPosition],
Rosary,
TextEdit,
TextLooks,
TextNode USING [countMax, NewTextNode, Ref, Root],
UndoEvent USING [Note, Ref, SubEvent];
TextEditImpl: CEDAR MONITOR
IMPORTS Atom, CheckNode, EditNotify, NodeAddrs, NodeProps, Rope, RopeEdit, RopeReader, Rosary, TextLooks, TextNode, UndoEvent
EXPORTS TextEdit =
BEGIN
RefTextNode: TYPE = TextNode.Ref;
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];
MaxNat: NAT = LAST[NAT];
Event: TYPE = UndoEvent.Ref;
CapChange: TYPE ~ TextEdit.CapChange;
DestSpanProc: TYPE ~ TextEdit.DestSpanProc;
OneSpanProc: TYPE ~ TextEdit.OneSpanProc;
MapPropsAction: TYPE ~ TextEdit.MapPropsAction;
ModifyPropsAction: TYPE ~ TextEdit.ModifyPropsAction;
Change: TYPE = EditNotify.Change;
Runs: TYPE = TextLooks.Runs;
CharSet: TYPE ~ [0..256);
-- **** Cache of notify records ****
scratch1, scratch2, scratch3: REF Change.ChangingText;
Create: PROC RETURNS [REF Change.ChangingText] = {
RETURN [NEW[Change.ChangingText]];
};
Alloc: ENTRY PROC RETURNS [scratch: REF Change.ChangingText] = {
ENABLE UNWIND => NULL;
IF scratch3 # NIL THEN { scratch ← scratch3; scratch3 ← NIL }
ELSE IF scratch2 # NIL THEN { scratch ← scratch2; scratch2 ← NIL }
ELSE IF scratch1 # NIL THEN { scratch ← scratch1; scratch1 ← NIL }
ELSE scratch ← Create[];
};
Free: ENTRY PROC [scratch: REF Change.ChangingText] = {
ENABLE UNWIND => NULL;
IF scratch3 = scratch OR scratch2 = scratch OR scratch1 = scratch THEN ERROR;
IF scratch3 = NIL THEN scratch3 ← scratch
ELSE IF scratch2 = NIL THEN scratch2 ← scratch
ELSE IF scratch1 = NIL THEN scratch1 ← scratch;
};
-- **** 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: 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: 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: INTINT.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: 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, 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];
};
};
-- **** Changing Style / Format of node ****
ChangeFormat: PUBLIC PROC [node: RefTextNode, formatName: ATOM, event: Event ← NIL, root: RefTextNode ← 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: RefTextNode, name: ROPE, event: Event ← NIL, root: RefTextNode ← 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: RefTextNode, name: ATOM, value: REF, event: Event ← NIL, root: RefTextNode ← 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: RefTextNode, name: ATOM] RETURNS [value: REF] = {
IF node=NIL THEN RETURN[NIL];
RETURN [NodeProps.GetProp[node, name]];
};
GetPropsRosary: PROC [node: RefTextNode, propName: ATOM, wantedSize: INT] RETURNS [ROSARY] ~ {
WITH GetProp[node, propName] SELECT FROM
rosary: ROSARY => {
rosarySize: INT ~ Rosary.Size[rosary];
zero: [0..0] ~ wantedSize-rosarySize;
RETURN [rosary];
};
ENDCASE => NULL;
RETURN [Rosary.FromItem[NIL, wantedSize]];
};
-- **** Fetch info operations ****
Fetch: PUBLIC PROC [text: RefTextNode, index: INT] RETURNS [charSet: CharSet, char: CHAR, looks: Looks] = {
[charSet, char] ← FetchChar[text, index];
looks ← FetchLooks[text, index];
};
FetchChar: PUBLIC PROC [text: RefTextNode, index: INT] RETURNS [charSet: CharSet ← 0, char: CHAR] = {
char ← Rope.Fetch[text.rope, index];
IF text.hascharsets THEN {
WITH GetProp[text, $CharSets] SELECT FROM
rosary: ROSARY => {
WITH Rosary.Fetch[rosary, index] SELECT FROM
cs: REF CharSet => charSet ← cs^;
ENDCASE => NULL;
};
ENDCASE => NULL;
};
};
FetchLooks: PUBLIC PROC
[text: RefTextNode, index: INT] RETURNS [Looks] = {
-- returns the looks for the character at the given location
-- use reader's if getting looks for sequence of locations
RETURN [TextLooks.FetchLooks[text.runs, index]];
};
GetRope: PUBLIC PROC [text: RefTextNode] RETURNS [ROPE] =
{ RETURN [IF text=NIL THEN NIL ELSE text.rope] };
GetRuns: PUBLIC PROC [text: RefTextNode] RETURNS [Runs] =
{ RETURN [IF text=NIL THEN NIL ELSE text.runs] };
Size: PUBLIC PROC [text: RefTextNode] RETURNS [INT] =
{ RETURN [IF text=NIL THEN 0 ELSE Rope.Size[text.rope]] };
-- **** Operations to create a text node ****
FromRope: PUBLIC PROC [rope: ROPE] RETURNS [new: RefTextNode] = {
-- create a text node with looks from a normal rope
new ← TextNode.NewTextNode[];
new.rope ← rope; new.last ← TRUE;
new.runs ← TextLooks.CreateRun[Rope.Size[rope]];
};
FromString: PUBLIC PROC [string: REF READONLY TEXT]
RETURNS [new: RefTextNode] = {
-- copies the contents of the string
new ← TextNode.NewTextNode[]; new.last ← TRUE;
new.rope ← Rope.FromRefText[string];
new.runs ← TextLooks.CreateRun[Rope.Size[new.rope]];
};
DocFromNode: PUBLIC PROC [child: RefTextNode] RETURNS [new: RefTextNode] = {
new ← TextNode.NewTextNode[];
new.child ← child; new.last ← TRUE;
child.next ← new; child.last ← TRUE;
};
-- ***** Caps and Lowercase
ChangeCaps: PUBLIC PROC [
root: RefTextNode,
dest: RefTextNode, start: INT ← 0, len: INT ← MaxLen,
how: CapChange ← allCaps, event: Event ← NIL] = {
notify: REF Change.ChangingText;
oldRope, destRope: ROPE;
rdr: RopeReader.Ref;
initCap: BOOLTRUE;
destSize: INT;
alreadysaved: BOOL;
IF dest=NIL THEN RETURN;
destSize ← Rope.Size[oldRope ← destRope ← dest.rope];
start ← MIN[MAX[0, start], destSize];
IF (len ← MIN[MAX[0, len], destSize-start])=0 THEN RETURN;
notify ← IF (alreadysaved ← AlreadySaved[dest, event]) THEN Alloc[]
ELSE NEW[Change.ChangingText];
notify^ ← [ChangingText[root, dest, start, len, len, destRope, dest.runs]];
EditNotify.Notify[notify, before];
rdr ← RopeReader.GetRopeReader[];
UNTIL len=0 DO
c: CHAR;
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;
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];
EditNotify.Notify[notify, after];
IF alreadysaved THEN Free[notify]
ELSE UndoEvent.Note[event, UndoChangeText, notify];
};
AllCaps: PUBLIC PROC [root: RefTextNode, dest: RefTextNode, start: INT, len: INT, event: Event]
= { ChangeCaps[root, dest, start, len, allCaps, event] };
AllLower: PUBLIC PROC [root: RefTextNode, dest: RefTextNode, start: INT, len: INT, event: Event]
= { ChangeCaps[root, dest, start, len, allLower, event] };
InitialCaps: PUBLIC PROC [root: RefTextNode, dest: RefTextNode, start: INT, len: INT, event: Event]
= { ChangeCaps[root, dest, start, len, initCaps, event] };
-- ***** Miscellaneous
BumpCount: PROC [text: RefTextNode] = {
countLimit: INT ← 20;
count: [0..TextNode.countMax];
IF text=NIL THEN RETURN;
IF (count←text.count) = TextNode.countMax OR (text.count ← count+1) >= countLimit THEN
-- see if need to flatten
IF Flatten[text] THEN text.count ← 0
ELSE text.count ← text.count/2;
};
debug: BOOLFALSE;
Flatten: PUBLIC PROC [text: RefTextNode] RETURNS [BOOL] = {
looksSize, looksPieces, looksDepth: INT ← 0;
ropeFlag, looksFlag: BOOLFALSE;
rope: ROPE ← text.rope;
runs: Runs ← text.runs;
ropeSize: INT ~ Rope.Size[rope];
[looksSize, looksPieces, looksDepth] ← TextLooks.LooksStats[runs];
-- size = # of characters
-- pieces = # terminal nodes in tree
-- depth = max depth of tree
IF ropeSize=0 AND looksSize=0 THEN RETURN [FALSE];
looksFlag ← (looksPieces > 20 AND looksSize < 5*looksPieces) OR looksDepth > 100 OR
(SELECT looksPieces/8 FROM
< 2 => looksDepth/4 > looksPieces,
< 6 => looksDepth/2 > looksPieces,
< 12 => looksDepth > looksPieces,
ENDCASE => looksDepth > looksPieces/2);
IF ropeFlag OR looksFlag THEN {
IF debug THEN { CheckNode.CheckRope[rope]; CheckNode.CheckRuns[runs] };
text.rope ← IF ropeSize>256 THEN Rope.Balance[rope] ELSE Rope.Flatten[rope];
text.runs ← TextLooks.Flatten[runs];
IF debug THEN { CheckNode.CheckRope[text.rope]; CheckNode.CheckRuns[text.runs] };
RETURN [TRUE];
};
RETURN [FALSE];
};
maxSearchDepth: NAT ← 20;
AlreadySaved: PUBLIC PROC [text: RefTextNode, event: Event] RETURNS [BOOL] = {
-- 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];
};
END.