-- TEditDisplay2Impl.mesa, last edit by Paxton on December 22, 1982 3:58 pm
Last Edited by: Maxwell, January 6, 1983 2:02 pm
DIRECTORY
EditNotify USING [AddNotifyProc, Change, ChangeSet],
Rope USING [Equal, Fetch, ROPE],
RopeEdit USING [BlankChar],
RopeReader USING [GetRopeReader, FreeRopeReader, Get, Ref, SetPosition],
TextEdit USING [Offset, Size],
TextNode USING [FirstChild, Forward, ForwardClipped, LastLocWithin, LastWithin, Level, Location, NarrowToTextNode, Next, Offset, Parent, pZone, Ref, RefTextNode, Span, StepForward],
TEditDisplay,
TEditDocument USING [GetViewerForRoot, LineTable, maxClip, TEditDocumentData],
TEditInput USING [currentEvent],
TEditSelection USING [ComputePosLine, ComputeSpanLines, CannotFindIt],
UndoEvent USING [Note, Ref, SubEvent],
ViewerClasses USING [Viewer],
ViewerOps USING [SetNewVersion];
TEditDisplay2Impl: CEDAR MONITOR
IMPORTS Rope, EditNotify, RopeEdit, RopeReader, TextEdit, TextNode, TEditDocument, TEditInput, TEditSelection, UndoEvent, ViewerOps
EXPORTS TEditDisplay =
BEGIN OPEN TEditSelection, TEditDocument;
Change: TYPE = EditNotify.Change;
EstablishLine:
PUBLIC
ENTRY
PROC [tdd: TEditDocumentData, newLine: TextNode.Location,
position: INTEGER ← 0] = BEGIN
lines: TEditDocument.LineTable = tdd.lineTable;
lines[position].pos ← newLine;
lines[position].valid ← FALSE;
END;
UndoChangeViewer:
PROC [undoRef:
REF Change, currentEvent: UndoEvent.Ref] = {
x: REF Change.ChangingView = NARROW[undoRef];
tdd: TEditDocumentData = NARROW[x.viewer.data];
IF tdd = NIL THEN RETURN;
IF ~AlreadySaved[x.viewer] THEN SaveViewerPos[x.viewer, tdd.lineTable[0].pos];
[] ← EstablishLine[tdd, x.old] };
AlreadySaved:
PROC [v: ViewerClasses.Viewer]
RETURNS [
BOOL] = {
IF TEditInput.currentEvent = NIL THEN RETURN [TRUE];
FOR l: UndoEvent.SubEvent ← TEditInput.currentEvent.subevents, l.next
UNTIL l=
NIL
DO
IF l.undoRef #
NIL
THEN
WITH l.undoRef
SELECT
FROM
x: REF Change.ChangingView => IF x.viewer = v THEN RETURN[TRUE];
ENDCASE;
ENDLOOP;
RETURN [FALSE] };
SaveViewerPos:
PROC [viewer: ViewerClasses.Viewer, oldLine: TextNode.Location] = {
notify: REF ChangingView Change ← TextNode.pZone.NEW[ChangingView Change];
notify^ ← [ChangingView[viewer,oldLine]];
UndoEvent.Note[TEditInput.currentEvent,UndoChangeViewer,notify] };
UpdateAfterTextEdit:
PROC [viewer: ViewerClasses.Viewer,
text: TextNode.RefTextNode,
start, newlen, oldlen: TextNode.Offset,
oldrope: Rope.ROPE] = {
-- invalidate lines containing old text and 1 line before
-- update line offsets for other lines in same node that start beyond the replacement
Update:
PROC [v: ViewerClasses.Viewer] = {
tdd: TEditDocumentData = NARROW[v.data];
lines: LineTable = tdd.lineTable;
IF tdd = NIL THEN RETURN;
IF tdd.tsInfo=NIL THEN ViewerOps.SetNewVersion[v];
IF ~AlreadySaved[v] THEN SaveViewerPos[v, lines[0].pos]; -- record viewer start pos
FOR n:
INTEGER
IN [0..lines.lastLine]
DO
where: TextNode.Offset;
IF lines[n].pos.node # text THEN LOOP;
SELECT where ← lines[n].pos.where
FROM
<= start =>
IF n > 0
AND lines[n-1].pos.node=text AND lines[n-1].valid
AND where+lines[n].nChars>start
AND Rope.Fetch[text.rope,where-1] # 15C
AND (oldlen > 0 OR InsertContainsBlank[])
AND ~LineContainsPreviousBlank[n,lines]
THEN
-- invalidate 1 line before start to take care of back ripple
lines[n-1].valid ← FALSE;
>= end => lines[n].pos.where ← where+delta;
ENDCASE =>
IF n=0
THEN {
lines[0].pos.where ← MIN[where, start+newlen];
tdd.fixStart ← TRUE };
ENDLOOP;
};
LineContainsPreviousBlank:
PROC [n:
INTEGER, lines: LineTable]
RETURNS [yes:
BOOL] = {
OPEN RopeReader;
rdr: Ref;
lineStart: TextNode.Offset = lines[n].pos.where;
IF lineStart>=start THEN RETURN [FALSE];
rdr ← GetRopeReader[];
yes ← FALSE;
SetPosition[rdr,text.rope,lineStart];
FOR i:
INT ← lines[n].pos.where, i+1
UNTIL i=start
DO
IF RopeEdit.BlankChar[Get[rdr]] THEN { yes ← TRUE; EXIT };
ENDLOOP;
FreeRopeReader[rdr] };
delta: TextNode.Offset = newlen-oldlen;
end: TextNode.Offset = start+oldlen;
checkedInsertForBlank: BOOL ← FALSE;
insertContainsBlank: BOOL;
InsertContainsBlank:
PROC
RETURNS [
BOOL] = {
OPEN RopeReader;
rdr: Ref;
IF newlen = 0 THEN RETURN [FALSE];
IF checkedInsertForBlank THEN RETURN [insertContainsBlank];
rdr ← GetRopeReader[];
insertContainsBlank ← FALSE;
SetPosition[rdr,text.rope,start];
FOR i:
INT ← 0, i+1
UNTIL i=newlen
DO
IF RopeEdit.BlankChar[Get[rdr]] THEN { insertContainsBlank ← TRUE; EXIT };
ENDLOOP;
checkedInsertForBlank ← TRUE;
FreeRopeReader[rdr];
RETURN [insertContainsBlank] };
InvalidateSpan[viewer,[[text,start],[text,start+oldlen]],
FALSE];
-- can safely pass errorFlag=FALSE since if fail to find text node in viewer,
-- then nothing needs invalidation
Update[viewer];
IF viewer.link #
NIL
THEN
FOR v: ViewerClasses.Viewer ← viewer.link, v.link
UNTIL v=viewer
DO
Update[v];
ENDLOOP;
};
InvalidateSpan:
PUBLIC
PROC [viewer: ViewerClasses.Viewer, span: TextNode.Span,
errorFlag: BOOL ← TRUE, movingOut, unnesting: BOOL ← FALSE] = {
-- invalidate lines containing text from nodes in span
InvalidateLinesInSpan:
PROC [v: ViewerClasses.Viewer] = {
start, end: INTEGER;
startClipped, endClipped: BOOLEAN;
tdd: TEditDocumentData = NARROW[v.data];
lines: LineTable = tdd.lineTable;
IF tdd.movedOut
AND tdd.movedIn
THEN {
-- cannot trust the line table anymore so invalidate everything
start ← 0; end ← LAST[INTEGER];
startClipped ← endClipped ← FALSE }
ELSE [start, end, startClipped, endClipped] ← TEditSelection.ComputeSpanLines[v, span !
TEditSelection.CannotFindIt => {
-- we must have previously deleted either first/last node in viewer
-- and span start/end node not in the viewer line table
end ← LAST[INTEGER];
startClipped ← endClipped ← FALSE;
start ← IF errorFlag THEN 0 --invalidate all-- ELSE LAST[INTEGER] --invalidate none--;
CONTINUE } ];
IF end < 0 THEN RETURN; -- off screen
IF start =
LAST[
INTEGER]
THEN
IF (unnesting
AND tdd.clipLevel < maxClip)
OR
(lines[lines.lastLine].pos.node = span.start.node
AND
lines[lines.lastLine].pos.where+lines[lines.lastLine].nChars = span.start.where)
THEN start ← lines.lastLine -- may be visible now
ELSE RETURN;
IF start < 0 THEN start ← 0; -- begins above screen
IF end = LAST[INTEGER] THEN end ← tdd.lineTable.lastLine -- ends below screen
ELSE IF endClipped AND (end > start OR ~unnesting) THEN end ← end-1;
FOR n:
INTEGER
IN [start..end]
DO
tdd.lineTable.lines[n].valid ← FALSE;
ENDLOOP;
IF movingOut THEN tdd.movedOut ← TRUE };
InvalidateLinesInSpan[viewer];
IF viewer.link #
NIL
THEN
FOR v: ViewerClasses.Viewer ← viewer.link, v.link
UNTIL v=viewer
DO
InvalidateLinesInSpan[v]; ENDLOOP };
InvalidateChildren:
PUBLIC
PROC [viewer: ViewerClasses.Viewer, node: TextNode.Ref] = {
-- invalidate lines for children of node
child: TextNode.Ref;
span: TextNode.Span;
IF (child ← TextNode.FirstChild[node])=NIL THEN RETURN;
span ← [[child,0],TextNode.LastLocWithin[node]];
InvalidateSpan[viewer,span] };
InvalidateBranch:
PUBLIC
PROC [viewer: ViewerClasses.Viewer, node: TextNode.Ref] = {
-- invalidate lines for children of node
span: TextNode.Span ← [[node,0],TextNode.LastLocWithin[node]];
InvalidateSpan[viewer,span] };
InvalidateBranchAndNext:
PROC [viewer: ViewerClasses.Viewer, node: TextNode.Ref] = {
-- invalidate lines for children of node and one node beyond
OPEN TextNode;
last: Ref = LastWithin[node];
next: Ref = Forward[last].nx;
end: Ref = IF next=NIL THEN last ELSE next;
span: Span ← [[node,0], [end,0]];
InvalidateSpan[viewer,span] };
UpdateStartPos:
PROC
[viewer: ViewerClasses.Viewer, first, last: TextNode.Ref] = {
-- this takes care of case in which something has been inserted in front of the first node
next: TextNode.Ref ← TextNode.StepForward[last];
UpdateViewerStartPos:
PROC [v: ViewerClasses.Viewer] = {
tdd: TEditDocumentData = NARROW[v.data];
lines: LineTable;
IF tdd = NIL THEN RETURN;
lines ← tdd.lineTable;
IF lines[0].pos.node # next THEN RETURN;
IF ~AlreadySaved[v] THEN SaveViewerPos[v, lines[0].pos]; -- record viewer start pos
lines[0].pos ← [first, 0];
lines[0].valid ← FALSE };
UpdateViewerStartPos[viewer];
IF viewer.link #
NIL
THEN
FOR v: ViewerClasses.Viewer ← viewer.link, v.link
UNTIL v=viewer
DO
UpdateViewerStartPos[v];
ENDLOOP;
};
InvalidateDest:
PROC
[viewer: ViewerClasses.Viewer, node: TextNode.Ref, movingIn, afterDest: BOOL] = {
InvalidateDestLine:
PROC [v: ViewerClasses.Viewer, dest: TextNode.Location] = {
line: INTEGER;
clipped, failed: BOOLEAN ← FALSE;
tdd: TEditDocumentData = NARROW[v.data];
IF tdd = NIL THEN RETURN;
IF movingIn THEN tdd.movedIn ← TRUE; -- remember that have moved nodes into this doc
[line, clipped] ← TEditSelection.ComputePosLine[v, dest !
TEditSelection.CannotFindIt => { failed ← TRUE; CONTINUE } ];
IF failed
AND tdd.clipLevel < maxClip
THEN {
-- try adjusting for level clipping
n: TextNode.Ref ← dest.node;
delta: INTEGER ← TextNode.Level[n]-tdd.clipLevel;
IF delta > 0
THEN {
FOR i:
INTEGER
IN [0..delta)
DO n ← TextNode.Parent[n];
ENDLOOP;
n ← TextNode.ForwardClipped[n,tdd.clipLevel].nx;
IF n #
NIL
THEN {
dest ← [n,0];
afterDest ← failed ← FALSE;
[line, clipped] ← TEditSelection.ComputePosLine[v, dest !
TEditSelection.CannotFindIt => { failed ← TRUE; CONTINUE } ] }}};
IF failed OR line < 0 THEN RETURN; -- not visible
IF line =
LAST[
INTEGER]
THEN
-- beyond last thing on viewer
IF tdd.clipLevel < maxClip THEN line ← tdd.lineTable.lastLine
ELSE RETURN;
IF afterDest AND ~clipped AND line < tdd.lineTable.lastLine THEN line ← line+1;
tdd.lineTable[line].valid ← FALSE;
};
pos: TextNode.Location = [node,
IF afterDest THEN TextEdit.Size[TextNode.NarrowToTextNode[node]] ELSE 0];
InvalidateDestLine[viewer,pos];
IF viewer.link #
NIL
THEN
FOR v: ViewerClasses.Viewer ← viewer.link, v.link
UNTIL v=viewer
DO
InvalidateDestLine[v,pos]; ENDLOOP };
FirstLevelParent:
PROC [node: TextNode.Ref]
RETURNS [TextNode.Ref] = {
prev1, prev2: TextNode.Ref;
UNTIL node=
NIL
DO
prev2 ← prev1;
node ← TextNode.Parent[prev1 ← node];
ENDLOOP;
RETURN [prev2] };
SpanIsBranches:
PROC [first, last: TextNode.Ref]
RETURNS [
BOOL] = {
parent: TextNode.Ref = TextNode.Parent[first];
n: TextNode.Ref ← last;
IF TextNode.FirstChild[n] # NIL THEN RETURN [FALSE];
DO
SELECT (n ← TextNode.Parent[n])
FROM
parent => RETURN [TRUE];
NIL => EXIT;
ENDCASE;
IF TextNode.Next[n] # NIL THEN RETURN [TextNode.Parent[n] = parent];
ENDLOOP;
RETURN [FALSE] };
SpanFromNodes:
PROC [first,last: TextNode.Ref]
RETURNS [TextNode.Span] = {
RETURN [[[first,0],[last,MAX[TextEdit.Size[TextNode.NarrowToTextNode[last]],1]-1]]] };
NotifyBeforeEdit:
PROC [change:
REF
READONLY EditNotify.Change] = {
viewer: ViewerClasses.Viewer;
span: TextNode.Span;
WITH change
SELECT
FROM
x:
REF READONLY Change.NodeNesting => {
IF (viewer ← TEditDocument.GetViewerForRoot[x.root])=NIL THEN RETURN;
span ← SpanFromNodes[x.first,x.last];
IF ~SpanIsBranches[x.first,x.last]
THEN
span.end ← TextNode.LastLocWithin[FirstLevelParent[x.last]];
InvalidateSpan[viewer,span,TRUE,FALSE,(x.change<0)] };
x:
REF READONLY Change.MovingNodes => {
IF (viewer ← TEditDocument.GetViewerForRoot[x.sourceRoot]) #
NIL
THEN {
branches: BOOL ← SpanIsBranches[x.first,x.last];
span ← SpanFromNodes[x.first,x.last];
IF ~branches
THEN span.end ← TextNode.LastLocWithin[FirstLevelParent[x.last]];
-- want span.last to be the last thing that can be influenced by the move
-- this is a conservative but easy to compute span that is sure to get everything
InvalidateSpan[viewer,span,TRUE,TRUE];
ViewerOps.SetNewVersion[viewer] };
x.dest.dirty ← x.pred.dirty ← TRUE;
IF (viewer ← TEditDocument.GetViewerForRoot[x.destRoot])=NIL THEN RETURN;
InvalidateDest[viewer,x.dest,TRUE,x.afterDest] };
x:
REF READONLY Change.ChangingType => {
x.node.dirty ← TRUE;
IF (viewer ← TEditDocument.GetViewerForRoot[x.root])=NIL THEN RETURN;
InvalidateBranchAndNext[viewer,x.node] };
x:
REF READONLY Change.ChangingProp => {
x.node.dirty ← TRUE;
IF (viewer ← TEditDocument.GetViewerForRoot[x.root])=NIL THEN RETURN;
IF ~Rope.Equal[x.propName,"Prefix"]
AND ~Rope.Equal[x.propName,"Postfix"]
AND ~Rope.Equal[x.propName,"Comment"]
AND ~Rope.Equal[x.propName,"StyleDef"] THEN NULL
ELSE
-- this is a property that can influence appearance
InvalidateBranchAndNext[viewer,x.node] };
ENDCASE;
IF viewer # NIL THEN ViewerOps.SetNewVersion[viewer];
};
NotifyAfterEdit:
PROC [change:
REF
READONLY EditNotify.Change] = {
viewer: ViewerClasses.Viewer;
span: TextNode.Span;
WITH change
SELECT
FROM
x:
REF READONLY Change.ChangingText => {
x.text.dirty ← TRUE;
IF (viewer ← TEditDocument.GetViewerForRoot[x.root])=NIL THEN RETURN;
UpdateAfterTextEdit[viewer,x.text,x.start,x.newlen,x.oldlen,x.oldRope] };
x:
REF READONLY Change.InsertingNode => {
span: TextNode.Span;
child: TextNode.Ref;
DiffCommentProps:
PROC [n1, n2: TextNode.Ref]
RETURNS [
BOOL] = {
node1, node2: TextNode.RefTextNode;
IF (node1 ← TextNode.NarrowToTextNode[n1]) = NIL THEN RETURN [TRUE];
IF (node2 ← TextNode.NarrowToTextNode[n2]) = NIL THEN RETURN [TRUE];
RETURN [node1.comment # node2.comment] };
x.new.new ← x.dest.dirty ← TRUE;
IF (viewer ← TEditDocument.GetViewerForRoot[x.root])=NIL THEN RETURN;
ViewerOps.SetNewVersion[viewer];
InvalidateDest[viewer,x.dest,FALSE,TRUE];
IF (child ← TextNode.FirstChild[x.new]) #
NIL
-- new inherited children of old
AND (x.dest.next # x.new
--different levels--
OR
x.dest.hasstyledef OR x.new.hasstyledef OR
x.dest.hasprefix OR x.new.hasprefix OR
x.dest.haspostfix OR x.new.haspostfix OR
x.dest.typename # x.new.typename OR
DiffCommentProps[x.dest, x.new]) THEN { -- must invalidate children too
span ← [[child,0],TextNode.LastLocWithin[x.new]];
InvalidateSpan[viewer,span];
UpdateStartPos[viewer,x.new,x.new] }};
x:
REF
READONLY Change.MovingNodes => {
IF (viewer ← TEditDocument.GetViewerForRoot[x.destRoot])=NIL THEN RETURN;
UpdateStartPos[viewer,x.first,x.last];
IF SpanIsBranches[x.first,x.last] THEN RETURN;
span.start ← [x.dest,0];
span.end ← TextNode.LastLocWithin[FirstLevelParent[x.last]];
InvalidateSpan[viewer,span] };
ENDCASE;
};
Init:
PROC = {
beforeEditChanges: EditNotify.ChangeSet ← ALL[TRUE];
afterEditChanges: EditNotify.ChangeSet ← ALL[FALSE];
beforeEditChanges[ChangingText] ← FALSE;
beforeEditChanges[InsertingNode] ← FALSE;
EditNotify.AddNotifyProc[proc: NotifyBeforeEdit, time: before, changeSet: beforeEditChanges];
afterEditChanges[MovingNodes] ← TRUE;
afterEditChanges[InsertingNode] ← TRUE;
afterEditChanges[ChangingText] ← TRUE;
EditNotify.AddNotifyProc[proc: NotifyAfterEdit, time: after, changeSet: afterEditChanges];
};
Init;
END.