-- TextEditImpl.mesa
-- 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
DIRECTORY
TextEdit,
EditNotify,
UndoEvent,
NodeProps,
RopeEdit,
RopeFrom,
RopeReader,
TextLooks,
TextNode,
NodeAddrs,
Atom,
Rope,
CheckNode;
TextEditImpl: CEDAR MONITOR
IMPORTS npI:NodeProps, RopeEdit, TextLooks, EditNotify, UndoEvent, NodeAddrs,
TextNode, RopeFrom, RopeReader, Rope, Atom, CheckNode
EXPORTS TextEdit =
BEGIN
OPEN TextEdit, EditNotify;
Runs: TYPE = TextLooks.Runs;
-- **** Cache of notify records ****
scratch1, scratch2, scratch3: REF ChangingText Change;
Create: PROC RETURNS [REF ChangingText Change] = {
RETURN [TextNode.pZone.NEW[ChangingText Change]] };
Alloc: ENTRY PROC RETURNS [scratch: REF ChangingText Change] = {
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 ChangingText Change] = {
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 ****
DoReplace: PROC [root: Ref,
dest: RefTextNode, destStart, destLen, destSize: Offset,
sourceRope: ROPE, sourceRuns: Runs,
sourceStart, sourceLen: Offset, event: Event]
RETURNS [resultStart, resultLen: Offset] = {
destRope, replaceRope, newRope: ROPE;
size: Offset;
notify: REF ChangingText Change;
alreadysaved: BOOLEAN;
destRuns, replaceRuns, newRuns: Runs;
special: BOOLEAN;
size ← destSize-destLen+sourceLen;
destRuns ← dest.runs; destRope ← dest.rope;
notify ← IF (alreadysaved ← AlreadySaved[dest,event]) THEN Alloc[]
ELSE TextNode.pZone.NEW[ChangingText Change];
notify^ ← [ChangingText[root,dest,destStart,sourceLen,destLen,destRope,destRuns]];
Notify[notify,before];
IF sourceLen > 0 THEN {
replaceRope ← RopeEdit.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 ← RopeEdit.Substr[destRope,destLen,size];
newRuns ← TextLooks.Substr[destRuns,destLen,size];
special ← TRUE }
ELSE IF (destStart+destLen)=destSize THEN { -- delete from end
newRope ← RopeEdit.Substr[destRope,0,size];
newRuns ← TextLooks.Substr[destRuns,0,size];
special ← TRUE }};
IF ~special THEN {
newRope ← RopeEdit.Replace[destRope,destStart,destLen,
replaceRope,destSize,sourceLen];
newRuns ← TextLooks.Replace[destRuns,destStart,destLen,
replaceRuns,destSize,sourceLen] };
dest.rope ← newRope; dest.runs ← newRuns;
NodeAddrs.Replace[dest,destStart,destLen,sourceLen];
BumpCount[dest];
Notify[notify,after];
IF alreadysaved THEN Free[notify]
ELSE UndoEvent.Note[event,UndoChangeText,notify];
RETURN [destStart,sourceLen] };
MoveText: PUBLIC PROC [
destRoot, sourceRoot: Ref,
dest: RefTextNode, destLoc: Offset ← 0,
source: RefTextNode, start: Offset ← 0, len: Offset ← MaxLen,
event: Event ← NIL]
RETURNS [resultStart, resultLen: Offset] = {
MoveAddrs: PROC [addr: REF, location: Offset] RETURNS [quit: BOOLEAN] = {
IF location NOT IN [start..start+len) THEN RETURN [FALSE];
IF dest=source THEN { -- pin addr at new location
new: Offset ← 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] };
destSize, sourceSize: Offset;
sourceSize ← RopeEdit.Size[source.rope];
start ← MIN[MAX[0,start],sourceSize];
len ← MIN[MAX[0,len],sourceSize-start];
destSize ← IF dest=source THEN sourceSize ELSE RopeEdit.Size[dest.rope];
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: Ref,
alpha: RefTextNode, alphaStart: Offset ← 0, alphaLen: Offset ← MaxLen,
beta: RefTextNode, betaStart: Offset ← 0, betaLen: Offset ← MaxLen, event: Event ← NIL]
RETURNS [alphaResultStart, alphaResultLen, betaResultStart, betaResultLen: Offset] = {
SwitchResults: PROC = {
start, len: Offset;
start ← betaResultStart; len ← betaResultLen;
betaResultStart ← alphaResultStart; betaResultLen ← alphaResultLen;
alphaResultStart ← start; alphaResultLen ← len };
alphaSize, betaSize: Offset;
switched: BOOLEANFALSE;
alphaSize ← RopeEdit.Size[alpha.rope];
alphaStart ← MIN[MAX[0,alphaStart],alphaSize];
alphaResultLen ← alphaLen ← MIN[MAX[0,alphaLen], alphaSize-alphaStart];
betaSize ← IF beta=alpha THEN alphaSize ELSE RopeEdit.Size[beta.rope];
betaStart ← MIN[MAX[0,betaStart],betaSize];
betaResultLen ← betaLen ← MIN[MAX[0,betaLen],betaSize-betaStart];
IF alpha=beta THEN {
alphaEnd: Offset;
IF alphaStart > betaStart THEN { -- switch them
start: Offset ← alphaStart;
len: Offset ← alphaLen;
root: Ref ← 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: Offset;
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: Ref, dest: RefTextNode, char: CHAR,
start: Offset ← 0, len: Offset ← MaxLen,
inherit: BOOLEANTRUE, looks: Looks ← noLooks, event: Event ← NIL]
RETURNS [resultStart, resultLen: Offset] = {
-- if inherit is false, char gets specifed looks  
-- if inherit is true, char gets looks in following manner:
-- if dest length is 0, then gets looks from argument list, else
-- if start > 0, then looks and list of previous char,
-- else looks of char following replacement
notify: REF ChangingText Change;
destRope, newRope: ROPE;
destSize: Offset;
alreadysaved: BOOLEAN;
IF dest=NIL THEN RETURN [0,0];
destSize ← RopeEdit.Size[destRope ← dest.rope];
start ← MIN[MAX[0,start],destSize];
len ← MIN[MAX[0,len],destSize-start];
notify ← IF (alreadysaved ← AlreadySaved[dest,event]) THEN Alloc[]
ELSE TextNode.pZone.NEW[ChangingText Change];
notify^ ← [ChangingText[root,dest,start,1,len,destRope,dest.runs]];
Notify[notify,before];
newRope ← RopeEdit.ReplaceByChar[destRope,char,start,len,destSize];
dest.runs ← TextLooks.ReplaceByRun[dest.runs,start,len,1,destSize,inherit,looks];
dest.rope ← newRope;
NodeAddrs.Replace[dest,start,len,1];
BumpCount[dest];
Notify[notify,after];
IF alreadysaved THEN Free[notify]
ELSE UndoEvent.Note[event,UndoChangeText,notify];
RETURN [start,1] };
ReplaceByString: PUBLIC PROC [
root: Ref, dest: RefTextNode, string: REF READONLY TEXT,
stringStart: NAT ← 0, stringNum: NAT ← MaxNat,
start: Offset ← 0, len: Offset ← MaxLen,
inherit: BOOLEANTRUE, looks: Looks ← noLooks, event: Event ← NIL]
RETURNS [resultStart, resultLen: Offset] = {
notify: REF ChangingText Change;
destRope, newRope: ROPE;
alreadysaved: BOOLEAN;
destSize: Offset;
IF dest=NIL THEN RETURN [0,0];
stringNum ← IF string=NIL OR stringStart >= string.length THEN 0 ELSE
MIN[stringNum, string.length-stringStart];
destSize ← RopeEdit.Size[destRope ← dest.rope];
start ← MIN[MAX[0,start],destSize];
len ← MIN[MAX[0,len],destSize-start];
notify ← IF (alreadysaved ← AlreadySaved[dest,event]) THEN Alloc[]
ELSE TextNode.pZone.NEW[ChangingText Change];
notify^ ← [ChangingText[root,dest,start,stringNum,len,destRope,dest.runs]];
Notify[notify,before];
newRope ← RopeEdit.ReplaceByString[destRope,string,stringStart,stringNum,
start,len,destSize];
dest.runs ← TextLooks.ReplaceByRun[dest.runs,start,len,stringNum,
destSize,inherit,looks];
dest.rope ← newRope;
NodeAddrs.Replace[dest,start,len,stringNum];
BumpCount[dest];
Notify[notify,after];
IF alreadysaved THEN Free[notify]
ELSE UndoEvent.Note[event,UndoChangeText,notify];
RETURN [start,stringNum] };
UndoChangeText: PROC [undoRef: REF, currentEvent: Event] = TRUSTED {
saved: REF Change ← NARROW[undoRef];
WITH x:saved SELECT FROM
ChangingText => [] ← ReplaceByText[x.root,x.text,0,MaxLen,
x.oldRope,x.oldRuns,0,MaxLen,currentEvent];
-- must replace all of text since only save first change per event
ENDCASE => ERROR };
ReplaceByText: PUBLIC PROC [
root: Ref, dest: RefTextNode, destStart: Offset ← 0, destLen: Offset ← MaxLen,
sourceRope: ROPE, sourceRuns: Runs,
sourceStart: Offset ← 0, sourceLen: Offset ← MaxLen, event: Event ← NIL]
RETURNS [resultStart, resultLen: Offset] = {
sourceSize, destSize: Offset;
IF dest=NIL THEN RETURN [0,0];
sourceSize ← RopeEdit.Size[sourceRope];
sourceStart ← MIN[MAX[0,sourceStart],sourceSize];
IF sourceLen # 0 THEN {
IF sourceStart >= sourceSize THEN sourceStart ← sourceSize;
sourceLen ← MIN[MAX[0,sourceLen], sourceSize-sourceStart]};
destSize ← RopeEdit.Size[dest.rope];
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,dest,destStart,destLen,destSize,
sourceRope,sourceRuns,sourceStart,sourceLen,event] };
ReplaceText: PUBLIC PROC [
destRoot, sourceRoot: Ref,
dest: RefTextNode, destStart: Offset ← 0, destLen: Offset ← MaxLen,
source: RefTextNode, sourceStart: Offset ← 0, sourceLen: Offset ← MaxLen,
event: Event ← NIL]
RETURNS [resultStart, resultLen: Offset] = {
-- 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
sourceRope: ROPE;
sourceSize, destSize: Offset;
sourceRuns: Runs;
IF dest=NIL THEN RETURN [0,0];
IF source # NIL THEN { sourceRope ← source.rope; sourceRuns ← source.runs };
sourceSize ← RopeEdit.Size[sourceRope];
sourceStart ← MIN[MAX[0,sourceStart],sourceSize];
IF sourceLen # 0 THEN {
IF sourceStart >= sourceSize THEN sourceStart ← sourceSize;
sourceLen ← MIN[MAX[0,sourceLen], sourceSize-sourceStart]};
destSize ← RopeEdit.Size[dest.rope];
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[destRoot,dest,destStart,destLen,destSize,
sourceRope,sourceRuns,sourceStart,sourceLen,event] };
DeleteText: PUBLIC OneSpanProc = {
[] ← ReplaceText[root, NIL, text, start, len, NIL, 0, MaxLen, event] };
CopyText: PUBLIC DestSpanProc = { -- copy the specified text
[resultStart,resultLen] ← ReplaceText[destRoot, sourceRoot, dest, destLoc, 0,
source, start, len, event] };
MoveTextOnto: PUBLIC PROC [
destRoot, sourceRoot: Ref,
dest: RefTextNode, destStart: Offset ← 0, destLen: Offset ← MaxLen,
source: RefTextNode, sourceStart: Offset ← 0, sourceLen: Offset ← MaxLen,
event: Event ← NIL]
RETURNS [resultStart, resultLen: Offset] = {
sourceSize, destSize, start, len, end, destEnd: Offset;
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: Ref, dest: RefTextNode, rope: ROPE,
start: Offset ← 0, len: Offset ← MaxLen,
inherit: BOOLEANTRUE, looks: Looks ← noLooks, event: Event]
RETURNS [resultStart, resultLen: Offset] = {
notify: REF ChangingText Change;
destRope, newRope: ROPE;
alreadysaved: BOOLEAN;
destSize, ropeSize: Offset;
IF dest=NIL THEN RETURN [0,0];
ropeSize ← RopeEdit.Size[rope];
destSize ← RopeEdit.Size[destRope ← dest.rope];
start ← MIN[MAX[0,start],destSize];
len ← MIN[MAX[0,len],destSize-start];
notify ← IF (alreadysaved ← AlreadySaved[dest,event]) THEN Alloc[]
ELSE TextNode.pZone.NEW[ChangingText Change];
notify^ ← [ChangingText[root,dest,start,ropeSize,len,destRope,dest.runs]];
Notify[notify,before];
newRope ← RopeEdit.Replace[destRope,start,len,rope,destSize,ropeSize];
dest.runs ← TextLooks.ReplaceByRun[dest.runs,start,len,ropeSize,destSize,inherit,looks];
dest.rope ← newRope;
NodeAddrs.Replace[dest,start,len,ropeSize];
BumpCount[dest];
Notify[notify,after];
IF alreadysaved THEN Free[notify]
ELSE UndoEvent.Note[event,UndoChangeText,notify];
RETURN [start,ropeSize] };
-- **** Operation to add or delete looks ****
ChangeLooks: PUBLIC PROC [
root: Ref, text: RefTextNode, remove, add: Looks,
start: Offset ← 0, len: Offset ← MaxOffset, event: Event ← NIL] = {
-- first remove then add in the given range
size: Offset;
newRuns: Runs;
IF text=NIL THEN RETURN;
size ← RopeEdit.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: Ref, text: RefTextNode,
newRuns: Runs, start, len: Offset, event: Event] = {
oldRuns: Runs ← text.runs;
notify: REF ChangingText Change;
alreadysaved: BOOLEAN;
notify ← IF (alreadysaved ← AlreadySaved[text,event]) THEN Alloc[]
ELSE TextNode.pZone.NEW[ChangingText Change];
notify^ ← [ChangingText[root,text,start,len,len,text.rope,oldRuns]];
Notify[notify,before];
text.runs ← newRuns;
BumpCount[text];
Notify[notify,after];
IF alreadysaved THEN Free[notify]
ELSE UndoEvent.Note[event,UndoChangeText,notify] };
-- **** Changing Style / Type of node ****
ChangeType: PUBLIC PROC [node: Ref, typeName: TextNode.TypeName, event: Event ← NIL, root: Ref ← NIL] = {
notify: REF ChangingType Change;
oldTypeName: TextNode.TypeName ← node.typename;
IF node=NIL THEN RETURN;
IF root=NIL THEN root ← TextNode.Root[node];
IF oldTypeName = typeName THEN RETURN; -- no change
notify ← TextNode.pZone.NEW[ChangingType Change ←
[ChangingType[root,node,typeName,oldTypeName]]];
Notify[notify,before];
node.typename ← typeName;
Notify[notify,after];
IF event#NIL THEN UndoEvent.Note[event,UndoChangeType,notify] };
UndoChangeType: PROC [undoRef: REF, currentEvent: Event] = TRUSTED {
saved: REF Change ← NARROW[undoRef];
WITH x:saved SELECT FROM
ChangingType => ChangeType[x.node,x.oldTypeName,currentEvent,x.root];
ENDCASE => ERROR };
prefixAtom: ATOM = npI.PrefixAtom[];
prefixName: ROPE = npI.PrefixName[];
ChangeStyle: PUBLIC PROC [node: Ref, name: ROPE, event: Event ← NIL, root: Ref ← NIL] = {
newprefix: ROPE;
IF node=NIL THEN RETURN;
IF RopeEdit.Size[name] > 0 THEN
newprefix ← RopeEdit.Concat["\"",RopeEdit.Concat[name,"\" style"]];
PutProp[node,prefixName,newprefix,event,root] };
PutProp: PUBLIC PROC [node: Ref, name: ROPE, value: REF, event: Event ← NIL, root: Ref ← NIL] = {
notify: REF ChangingProp Change;
key: ATOM;
oldval: REF;
IF node=NIL THEN RETURN;
IF root=NIL THEN root ← TextNode.Root[node];
key ← Atom.MakeAtom[name];
IF (oldval ← npI.GetProp[node,key]) = value THEN RETURN;
notify ← TextNode.pZone.NEW[ChangingProp Change ←
[ChangingProp[root,node,name,key,value,oldval]]];
Notify[notify,before];
npI.PutProp[node,key,value];
Notify[notify,after];
IF event#NIL THEN UndoEvent.Note[event,UndoPutProp,notify] };
UndoPutProp: PROC [undoRef: REF, currentEvent: Event] = TRUSTED {
saved: REF Change ← NARROW[undoRef];
WITH x:saved SELECT FROM
ChangingProp => PutProp[x.node,x.propName,x.oldval,currentEvent,x.root];
ENDCASE => ERROR };
GetProp: PUBLIC PROC [node: Ref, name: ROPE] RETURNS [value: REF] = {
key: ATOM;
IF node=NIL THEN RETURN;
key ← Atom.MakeAtom[name];
RETURN [npI.GetProp[node,key]] };
-- **** Fetch info operations ****
Fetch: PUBLIC PROC [text: RefTextNode, index: Offset] RETURNS [CHAR, Looks] = {
-- fetches the indexed information
-- use readers if want info from contiquous locations
RETURN [RopeEdit.Fetch[text.rope,index],
TextLooks.FetchLooks[text.runs,index]] };
FetchChar: PUBLIC PROC [text: RefTextNode, index: Offset] RETURNS [CHAR] = {
-- fetches the indexed information
-- use readers if want info from contiquous locations
RETURN [RopeEdit.Fetch[text.rope,index]] };
FetchLooks: PUBLIC PROC
[text: RefTextNode, index: Offset] 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 [Offset] =
{ RETURN [IF text=NIL THEN 0 ELSE RopeEdit.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[RopeEdit.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 ← RopeFrom.String[string];
new.runs ← TextLooks.CreateRun[RopeEdit.Size[new.rope]] };
DocFromNode: PUBLIC PROC [child: Ref] RETURNS [new: Ref] = {
new ← TextNode.NewTextNode[];
new.child ← child; new.last ← TRUE;
child.next ← new; child.last ← TRUE };
-- ***** Cap's and Lowercase
ChangeCaps: PUBLIC PROC [
root: Ref,
dest: RefTextNode, start: Offset ← 0, len: Offset ← MaxLen,
how: CapChange ← allCaps, event: Event ← NIL] = {
notify: REF ChangingText Change;
oldRope, destRope: ROPE;
rdr: RopeReader.Ref;
initCap: BOOLEANTRUE;
destSize: Offset;
alreadysaved: BOOLEAN;
IF dest=NIL THEN RETURN;
destSize ← RopeEdit.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 TextNode.pZone.NEW[ChangingText Change];
notify^ ← [ChangingText[root,dest,start,len,len,destRope,dest.runs]];
Notify[notify,before];
rdr ← RopeReader.GetRopeReader[];
UNTIL len=0 DO
c: CHAR;
maxText: NAT = 1000;
num: NATMIN[len, maxText];
new: REF TEXT ← TextNode.pZone.NEW[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: BOOLEANTRUE;
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.ReplaceByString[destRope,new,0,num,start,num,destSize];
IF RopeEdit.Size[destRope] # destSize THEN ERROR;
len ← len-num; start ← start+num;
ENDLOOP;
dest.rope ← destRope;
RopeReader.FreeRopeReader[rdr];
Notify[notify,after];
IF alreadysaved THEN Free[notify]
ELSE UndoEvent.Note[event,UndoChangeText,notify] };
-- ***** Miscellaneous
BumpCount: PROC [text: RefTextNode] = {
countLimit: Offset ← 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 };
maxRopeDepth: Offset;
debug: BOOLFALSE;
Flatten: PUBLIC PROC [text: RefTextNode] RETURNS [BOOLEAN] = {
ropeSize, ropePieces, ropeDepth: Offset;
looksSize, looksPieces, looksDepth: Offset;
ropeFlag, looksFlag: BOOLEAN;
rope: ROPE ← text.rope;
runs: Runs ← text.runs;
[ropeSize,ropePieces,ropeDepth] ← RopeEdit.RopeStats[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];
IF ropeDepth > maxRopeDepth THEN maxRopeDepth ← ropeDepth; -- for debugging
ropeFlag ← (ropePieces > 20 AND ropeSize < 5*ropePieces) OR ropeDepth > 100 OR
(SELECT ropePieces/8 FROM
< 2 => ropeDepth/4 > ropePieces,
< 6 => ropeDepth/2 > ropePieces,
< 12 => ropeDepth > ropePieces,
ENDCASE => ropeDepth > ropePieces/2);
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 RopeEdit.SemiFlatten[rope]
ELSE RopeEdit.Flatten[rope];
text.runs ← TextLooks.Flatten[runs];
IF debug THEN {
CheckNode.CheckRope[text.rope];
CheckNode.CheckRuns[text.runs] };
RETURN [TRUE] };
RETURN [FALSE] };
AlreadySaved: PUBLIC PROC [text: RefTextNode, event: Event] RETURNS [BOOLEAN] = TRUSTED {
-- returns TRUE if there is already a ChangingText record for given node in the event
IF event = NIL THEN RETURN [TRUE];
FOR l: UndoEvent.SubEvent ← event.subevents, l.next UNTIL l=NIL DO
IF l.undoRef # NIL THEN
WITH x: l.undoRef SELECT FROM
ChangingText => IF x.text = text THEN RETURN[TRUE];
ENDCASE;
ENDLOOP;
RETURN [FALSE] };
-- ***** Initialization
Start: PUBLIC PROC = {
};
END.