TEditDisplay2Impl.mesa
Copyright Ó 1985, 1986 by Xerox Corporation. All rights reserved.
Michael Plass, May 9, 1986 12:23:06 pm PDT
Doug Wyatt, March 3, 1985 4:08:29 pm PST
Russ Atkinson (RRA) June 13, 1985 5:32:13 pm PDT
DIRECTORY
EditNotify USING [AddNotifyProc, Change, ChangeSet],
NodePropsExtras USING [Is],
Rope USING [Fetch, ROPE],
RopeEdit USING [BlankChar],
RopeReader USING [GetRopeReader, FreeRopeReader, Get, Ref, SetPosition],
TextEdit USING [Size],
TextNode USING [FirstChild, Forward, ForwardClipped, LastLocWithin, LastWithin, Level, Location, Next, Offset, Parent, Ref, RefTextNode, Span, StepForward],
TEditDisplay USING [],
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 EditNotify, NodePropsExtras, Rope, RopeEdit, RopeReader, TextEdit, TextNode, TEditDocument, TEditInput, TEditSelection, UndoEvent, ViewerOps
EXPORTS TEditDisplay
= BEGIN
Change: TYPE = EditNotify.Change;
EstablishLine: PUBLIC ENTRY PROC [tdd: TEditDocument.TEditDocumentData, newLine: TextNode.Location, position: INTEGER ← 0] = {
lines: TEditDocument.LineTable = tdd.lineTable;
IF lines # NIL THEN {
RRA: be careful here, just in case there is a race (one has been observed)
lines[position].pos ← newLine;
lines[position].valid ← FALSE;
};
};
UndoChangeViewer: PROC [undoRef: REF Change, currentEvent: UndoEvent.Ref] = {
WITH undoRef SELECT FROM
x: REF Change.ChangingView => {
v: ViewerClasses.Viewer ← x.viewer;
IF v # NIL THEN
WITH v.data SELECT FROM
tdd: TEditDocument.TEditDocumentData => {
IF NOT AlreadySaved[v] THEN {
lines: TEditDocument.LineTable = tdd.lineTable;
IF lines # NIL THEN
SaveViewerPos[v, tdd.lineTable[0].pos];
};
[] ← EstablishLine[tdd, x.old]
};
ENDCASE;
};
ENDCASE;
};
maxSearchDepth: NAT ← 20;
AlreadySaved: PROC [v: ViewerClasses.Viewer] RETURNS [BOOL] = {
k: NAT ← maxSearchDepth;
IF TEditInput.currentEvent = NIL THEN RETURN [TRUE];
FOR l: UndoEvent.SubEvent ← TEditInput.currentEvent.subevents, l.next UNTIL l=NIL DO
WITH l.undoRef SELECT FROM
x: REF Change.ChangingView => IF x.viewer = v 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]
};
SaveViewerPos: PROC [viewer: ViewerClasses.Viewer, oldLine: TextNode.Location] = {
notify: REF Change.ChangingView ← NEW[Change.ChangingView];
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: TEditDocument.TEditDocumentData = NARROW[v.data];
lines: TEditDocument.LineTable = tdd.lineTable;
IF tdd = NIL THEN RETURN;
IF tdd.tsInfo=NIL THEN ViewerOps.SetNewVersion[v];
IF NOT AlreadySaved[v] THEN SaveViewerPos[v, lines[0].pos]; -- record viewer start pos
FOR n: INTEGER DECREASING 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 NOT lines[n].valid
AND where+lines[n].nChars>start -- do "NOT lines[n].valid" test instead - mfp
AND Rope.Fetch[text.rope, where-1] # 15C
AND (oldlen > 0 OR InsertContainsBlank[]) -- omit this to get hyphenation refresh - mfp
AND NOT 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: TEditDocument.LineTable] RETURNS [yes: BOOL] = {
rdr: RopeReader.Ref;
lineStart: TextNode.Offset = lines[n].pos.where;
searchEnd: TextNode.Offset = MIN[lineStart+lines[n].nChars, start];
IF lineStart>=start THEN RETURN [FALSE];
rdr ← RopeReader.GetRopeReader[];
yes ← FALSE;
RopeReader.SetPosition[rdr, text.rope, lineStart];
FOR i: INT ← lineStart, i+1 UNTIL i=searchEnd DO
IF RopeEdit.BlankChar[RopeReader.Get[rdr]] THEN { yes ← TRUE; EXIT };
ENDLOOP;
RopeReader.FreeRopeReader[rdr]
};
delta: TextNode.Offset = newlen-oldlen;
end: TextNode.Offset = start+oldlen;
checkedInsertForBlank: BOOLFALSE;
insertContainsBlank: BOOL;
InsertContainsBlank: PROC RETURNS [BOOL] = {
rdr: RopeReader.Ref;
IF newlen = 0 THEN RETURN [FALSE];
IF checkedInsertForBlank THEN RETURN [insertContainsBlank];
rdr ← RopeReader.GetRopeReader[];
insertContainsBlank ← FALSE;
RopeReader.SetPosition[rdr, text.rope, start];
FOR i:INT ← 0, i+1 UNTIL i=newlen DO
IF RopeEdit.BlankChar[RopeReader.Get[rdr]] THEN { insertContainsBlank ← TRUE; EXIT };
ENDLOOP;
checkedInsertForBlank ← TRUE;
RopeReader.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];
FOR v: ViewerClasses.Viewer ← viewer.link, v.link UNTIL v = NIL OR v=viewer DO
Update[v];
ENDLOOP;
};
InvalidateSpan: PUBLIC PROC [viewer: ViewerClasses.Viewer, span: TextNode.Span, errorFlag: BOOLTRUE, movingOut, unnesting: BOOLFALSE] = {
invalidate lines containing text from nodes in span
InvalidateLinesInSpan: PROC [v: ViewerClasses.Viewer] = {
start, end: INTEGER;
startClipped, endClipped: BOOL;
tdd: TEditDocument.TEditDocumentData = NARROW[v.data];
lines: TEditDocument.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 < TEditDocument.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 NOT 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];
FOR v: ViewerClasses.Viewer ← viewer.link, v.link UNTIL v = NIL OR 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
last: TextNode.Ref = TextNode.LastWithin[node];
next: TextNode.Ref = TextNode.Forward[last].nx;
end: TextNode.Ref = IF next=NIL THEN last ELSE next;
span: TextNode.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: TEditDocument.TEditDocumentData = NARROW[v.data];
lines: TEditDocument.LineTable;
IF tdd = NIL THEN RETURN;
lines ← tdd.lineTable;
IF lines[0].pos.node # next THEN RETURN;
IF NOT AlreadySaved[v] THEN SaveViewerPos[v, lines[0].pos]; -- record viewer start pos
lines[0].pos ← [first, 0];
lines[0].valid ← FALSE
};
UpdateViewerStartPos[viewer];
FOR v: ViewerClasses.Viewer ← viewer.link, v.link UNTIL v = NIL OR 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: BOOLFALSE;
WITH v.data SELECT FROM
tdd: TEditDocument.TEditDocumentData => {
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 < TEditDocument.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 < TEditDocument.maxClip THEN line ← tdd.lineTable.lastLine
ELSE RETURN;
IF afterDest AND NOT clipped AND line < tdd.lineTable.lastLine THEN line ← line+1;
tdd.lineTable[line].valid ← FALSE;
};
ENDCASE;
};
pos: TextNode.Location = [node,
IF afterDest THEN TextEdit.Size[node] ELSE 0];
InvalidateDestLine[viewer, pos];
FOR v: ViewerClasses.Viewer ← viewer.link, v.link UNTIL v = NIL OR 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[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 NOT 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 NOT 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.ChangingFormat => {
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 NodePropsExtras.Is[x.propAtom, $Visible] THEN {
This is a property that can affect the 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] = {
RETURN [n1 = NIL OR n2 = NIL OR n1.comment # n2.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
The following tests used to be done, but they can't be all inclusive since new Visible properties may be added. Therefore, invalidate children unconditionally. If this gets too expensive, we could perhaps conjure up a correct test.
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.hascharprops OR x.new.hascharprops OR
x.dest.formatName # x.new.formatName 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.