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 { 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; 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] = { 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 Rope.Fetch[text.rope, where-1] # 15C AND NOT LineContainsPreviousBlank[n, lines] THEN { 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; InvalidateSpan[viewer, [[text, start], [text, start+oldlen]], FALSE]; 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: BOOL _ TRUE, movingOut, unnesting: BOOL _ FALSE] = { 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 { start _ 0; end _ LAST[INTEGER]; startClipped _ endClipped _ FALSE } ELSE [start, end, startClipped, endClipped] _ TEditSelection.ComputeSpanLines[v, span ! TEditSelection.CannotFindIt => { 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] = { 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] = { span: TextNode.Span _ [[node, 0], TextNode.LastLocWithin[node]]; InvalidateSpan[viewer, span] }; InvalidateBranchAndNext: PROC [viewer: ViewerClasses.Viewer, node: TextNode.Ref] = { 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] = { 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: BOOL _ FALSE; 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 { 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]]; 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 { 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; 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 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. Β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 RRA: be careful here, just in case there is a race (one has been observed) Don't keep searching too long; give up and make a new event instead. - mfp invalidate lines containing old text and 1 line before update line offsets for other lines in same node that start beyond the replacement AND where+lines[n].nChars>start -- do "NOT lines[n].valid" test instead - mfp AND (oldlen > 0 OR InsertContainsBlank[]) -- omit this to get hyphenation refresh - mfp invalidate 1 line before start to take care of back ripple checkedInsertForBlank: BOOL _ FALSE; 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] }; can safely pass errorFlag=FALSE since if fail to find text node in viewer, then nothing needs invalidation invalidate lines containing text from nodes in span cannot trust the line table anymore so invalidate everything we must have previously deleted either first/last node in viewer and span start/end node not in the viewer line table invalidate lines for children of node invalidate lines for children of node invalidate lines for children of node and one node beyond this takes care of case in which something has been inserted in front of the first node try adjusting for level clipping 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 This is a property that can affect the appearance DiffCommentProps: PROC [n1, n2: TextNode.Ref] RETURNS [BOOL] = { RETURN [n1 = NIL OR n2 = NIL OR n1.comment # n2.comment] }; 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]) Κ4˜codešœ™KšœB™BKšœ*™*Kšœ(™(K™0—K™šΟk ˜ Kšœ œ%˜5Kšœœ˜Kšœœ œ˜Kšœ œ˜Kšœ œ9˜IKšœ œ ˜Kšœ œ˜Kšœ œ˜Kšœœ<˜OKšœ œ˜!Kšœœ3˜GKšœ œ˜'Kšœœ ˜šœ œ˜ K˜——lead2šΠblœœ˜ Kšœ˜”Kšœ ˜Kšœ˜šœœ˜!K˜—š Οn œœœœNœ ˜~K˜/šœ œœ˜KšœJ™JK˜Kšœœ˜K˜—K˜K˜—šŸœœ œ)˜Mšœ œ˜šœœ˜Kšœ#˜#šœœ˜šœœ˜šœ)˜)šœœœ˜K˜/šœ œ˜Kšœ'˜'—K˜—K˜K˜—Kšœ˜——K˜—Kšœ˜—K˜K˜—Kšœœ˜šŸ œœœœ˜?Kšœœ˜Kš œœœœœ˜4šœCœœ˜Tšœ œ˜Kš œœœœœœ˜@Kšœ˜—šœœœ˜K™J—Kšœ˜—Kšœœ˜K˜K˜—šŸ œœ?˜RKšœœœ˜;K˜*K˜BK˜K˜—šŸœœrœ˜”Kšœ6™6KšœR™RK˜šŸœœ˜*Kšœ'œ ˜6K˜/Kšœœœœ˜Kšœ œœ˜2Kšœœœ!Οc˜Vš œœ œœ˜3K˜Kšœœœ˜&šœ˜&šœ ˜ Kšœ˜ Kšœ˜Kšœ˜šœœ˜Kšœ#œ#™M—šœ&˜)Kšœ œΟs-™W—šœœ%œ˜2Kšœ:™:Kšœœ˜Kšœ˜—Kšœ˜—K˜+šœ˜ šœœ˜ Kšœœ˜.Kšœœ˜K˜—Kšœ˜——Kšœ˜—K˜K˜—š Ÿœœœ"œœ˜dK˜K˜0Kšœœ#˜CKšœœœœ˜(K˜!Kšœœ˜ K˜2šœœœ ˜0Kšœ)œ œœ˜EKšœ˜—K˜K˜K˜—K˜'˜$K™—Kšœœœ™$Kšœœ™šŸœœœœ™.K™Kšœ œœœ™"Kšœœœ™;K™!Kšœœ™K™.šœœ œ ™$Kšœ)œœœ™UKšœ™—Kšœœ™K™Kšœ™K™K™—Kšœ>œ˜EKšœK™KKšœ™K˜š œ/œœœ ˜NK˜ Kšœ˜—K˜K˜—šŸœœœ@œœœœ˜Kšœ3™3šŸœœ˜9Kšœ œ˜Kšœœ˜Kšœ'œ ˜6K˜/šœœ œ˜&Kšœ<™˜RKšœW™WK˜0šŸœœ˜8Kšœ'œ ˜6K˜Kšœœœœ˜K˜Kšœœœ˜(Kšœœœ! ˜VK˜Kšœœ˜K˜—K˜š œ/œœœ ˜NK˜Kšœ˜—K˜K˜—šŸœœIœ˜fšŸœœ7˜OKšœœ˜Kšœœœ˜šœœ˜Kšœ)˜)Kšœ œœ /˜T˜;Kšœ*œœ˜=—šœœ'œ˜:Kšœ ™ K˜Kšœœ$˜2šœ œ˜Kš œœœ œœ˜@K˜1šœœœ˜K˜Kšœœ˜˜8Kšœ,œœ˜?—K˜—K˜—K˜—Kš œœ œœ ˜1Kš œœœœ ˜;šœ'œ˜KKšœœ˜ —Kš œ œœ œœ˜RKšœœ˜"K˜—Kšœ˜K˜—˜ Kšœ œœ˜.—K˜ š œ/œœœ ˜NKšœ˜Kšœ˜ —K˜K˜—šŸœœœ˜FK˜šœœ˜K˜K˜%Kšœ˜—Kšœ ˜K˜K˜—šŸœœœœ˜CK˜.K˜Kš œœœœœ˜4š˜šœ˜$Kšœ œœ˜Kšœœ˜ Kšœ˜—Kšœœœœ˜DKšœ˜—Kšœœ˜K˜K˜—šŸ œœœ˜KKšœœ˜=K˜K˜—šŸœœ œœ˜CK˜K˜šœœ˜šœœœ˜'Kšœ3œœœ˜EK˜&Kšœœ!˜+K˜