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
Doug Wyatt, January 25, 1984 10:55:32 am PST
DIRECTORY
Atom USING [MakeAtom],
CheckNode USING [CheckRope, CheckRuns],
EditNotify USING [Change, Notify],
NodeAddrs USING [MapTextAddrs, MoveTextAddr, PinTextAddr, PutTextAddr, Replace, UnpinAll],
NodeProps USING [GetProp, PrefixAtom, PrefixName, PutProp],
Rope USING [Fetch, ROPE],
RopeEdit USING [AlphaNumericChar, Concat, Fetch, Flatten, LowerCase, Replace, ReplaceByChar, ReplaceByString, RopeStats, SemiFlatten, Size, Substr, UpperCase],
RopeFrom USING [String],
RopeReader USING [FreeRopeReader, Get, GetRope, GetRopeReader, Ref, SetPosition],
TextEdit USING [CapChange, DestSpanProc, Event, Looks, MaxLen, MaxNat, MaxOffset, noLooks, Offset, OneSpanProc, Ref, RefTextNode],
TextLooks USING [ChangeLooks, Concat, CreateRun, FetchLooks, Flatten, LooksStats, Replace, ReplaceByRun, Runs, Substr],
TextNode USING [countMax, NewTextNode, pZone, Ref, RefTextNode, Root, TypeName],
UndoEvent USING [Note, SubEvent];
TextEditImpl: CEDAR MONITOR
IMPORTS npI:NodeProps, RopeEdit, TextLooks, EditNotify, UndoEvent, NodeAddrs, TextNode, RopeFrom, RopeReader, Rope, Atom, CheckNode
EXPORTS TextEdit =
BEGIN OPEN TextEdit;
ROPE: TYPE = Rope.ROPE;
Change: TYPE = EditNotify.Change;
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]];
EditNotify.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];
EditNotify.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: BOOLEAN ← FALSE;
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: BOOLEAN ← TRUE, 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]];
EditNotify.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];
EditNotify.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: BOOLEAN ← TRUE, 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]];
EditNotify.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];
EditNotify.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: BOOLEAN ← TRUE, 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]];
EditNotify.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];
EditNotify.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]];
EditNotify.Notify[notify,before];
text.runs ← newRuns;
BumpCount[text];
EditNotify.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]]];
EditNotify.Notify[notify,before];
node.typename ← typeName;
EditNotify.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]]];
EditNotify.Notify[notify,before];
npI.PutProp[node,key,value];
EditNotify.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: BOOLEAN ← TRUE;
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]];
EditNotify.Notify[notify,before];
rdr ← RopeReader.GetRopeReader[];
UNTIL len=0
DO
c: CHAR;
maxText: NAT = 1000;
num: NAT ← MIN[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: BOOLEAN ← TRUE;
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];
EditNotify.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: BOOL ← FALSE;
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.