DIRECTORY Ascii USING [CR, LF], CharOps USING [Blank], EditNotify USING [AddNotifyProc, Change, ChangeSet], NodeProps USING [Is], Rope USING [Fetch, ROPE], RopeReader USING [GetRopeReader, FreeRopeReader, Get, Ref, SetPosition], TEditDisplay USING [], TEditDocument USING [GetViewerForRoot, LineTable, maxClip, TEditDocumentData], TEditInput USING [currentEvent], TEditSelection USING [ComputePosLine, ComputeSpanLines, CannotFindIt], TextEdit USING [Size], TextNode USING [FirstChild, Forward, ForwardClipped, LastLocWithin, LastWithin, Level, Next, Parent, Root, StepForward], Tioga USING [Node, Location, Span], UndoEvent USING [Note, Event, EventRep, SubEvent], ViewerClasses USING [Viewer], ViewerOps USING [SetNewVersion]; TEditDisplay2Impl: CEDAR MONITOR IMPORTS CharOps, EditNotify, NodeProps, Rope, RopeReader, TEditDocument, TEditInput, TEditSelection, TextEdit, TextNode, UndoEvent, ViewerOps EXPORTS TEditDisplay, Tioga = BEGIN Node: TYPE ~ Tioga.Node; Viewer: TYPE ~ ViewerClasses.Viewer; Change: TYPE ~ EditNotify.Change; Event: TYPE ~ UndoEvent.Event; EventRep: PUBLIC TYPE ~ UndoEvent.EventRep; SetNewVersion: PROC [viewer: Viewer] = { IF viewer # NIL THEN WITH viewer.data SELECT FROM tdd: TEditDocument.TEditDocumentData => { IF tdd.tsInfo = NIL THEN ViewerOps.SetNewVersion[viewer]; }; ENDCASE => NULL; }; EstablishLine: PUBLIC ENTRY PROC [tdd: TEditDocument.TEditDocumentData, newLine: Tioga.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: Event] = { WITH undoRef SELECT FROM x: REF Change.ChangingView => { v: Viewer ¬ NARROW[x.view]; 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: 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.view = v THEN RETURN[TRUE]; ENDCASE; IF (k ¬ k-1) = 0 THEN EXIT; ENDLOOP; RETURN [FALSE] }; SaveViewerPos: PROC [viewer: Viewer, oldLine: Tioga.Location] = { notify: REF Change.ChangingView ¬ NEW[Change.ChangingView]; notify­ ¬ [ChangingView[viewer, oldLine]]; UndoEvent.Note[TEditInput.currentEvent, UndoChangeViewer, notify] }; UpdateAfterTextEdit: PROC [viewer: Viewer, text: Node, start, newlen, oldlen: INT, oldrope: Rope.ROPE] = { delta: INT = newlen-oldlen; end: INT = start+oldlen; Update: PROC [v: Viewer] = { tdd: TEditDocument.TEditDocumentData = NARROW[v.data]; lines: TEditDocument.LineTable = tdd.lineTable; IF tdd = NIL THEN RETURN; 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 IF lines[n].pos.node = text THEN { where: INT ~ lines[n].pos.where; IF lines[n].valid AND MAX[where, start] <= MIN[where+lines[n].nChars, end] THEN lines[n].valid ¬ FALSE; SELECT where FROM <= start => { IF n > 0 AND lines[n-1].pos.node=text AND lines[n-1].valid AND NOT lines[n].valid AND NOT (SELECT Rope.Fetch[text.rope, where-1] FROM Ascii.CR, Ascii.LF => TRUE, ENDCASE => FALSE) 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: INT = lines[n].pos.where; searchEnd: INT = 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 CharOps.Blank[RopeReader.Get[rdr]] THEN { yes ¬ TRUE; EXIT }; ENDLOOP; RopeReader.FreeRopeReader[rdr] }; Update[viewer]; FOR v: Viewer ¬ viewer.link, v.link UNTIL v = NIL OR v=viewer DO Update[v]; ENDLOOP; }; InvalidateSpan: PUBLIC PROC [viewer: Viewer, span: Tioga.Span, errorFlag: BOOL ¬ TRUE, movingOut, unnesting: BOOL ¬ FALSE] = { InvalidateLinesInSpan: PROC [v: 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: Viewer ¬ viewer.link, v.link UNTIL v = NIL OR v=viewer DO InvalidateLinesInSpan[v]; ENDLOOP }; InvalidateChildren: PUBLIC PROC [viewer: Viewer, node: Node] = { child: Node; span: Tioga.Span; IF (child ¬ TextNode.FirstChild[node])=NIL THEN RETURN; span ¬ [[child, 0], TextNode.LastLocWithin[node]]; InvalidateSpan[viewer, span] }; InvalidateBranch: PUBLIC PROC [viewer: Viewer, node: Node] = { span: Tioga.Span ¬ [[node, 0], TextNode.LastLocWithin[node]]; InvalidateSpan[viewer, span] }; InvalidateBranchAndNext: PROC [viewer: Viewer, node: Node] = { last: Node = TextNode.LastWithin[node]; next: Node = TextNode.Forward[last].nx; end: Node = IF next=NIL THEN last ELSE next; span: Tioga.Span ¬ [[node, 0], [end, TextEdit.Size[end]]]; InvalidateSpan[viewer, span] }; UpdateStartPos: PROC [viewer: Viewer, first, last: Node] = { next: Node ¬ TextNode.StepForward[last]; UpdateViewerStartPos: PROC [v: 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: Viewer ¬ viewer.link, v.link UNTIL v = NIL OR v=viewer DO UpdateViewerStartPos[v]; ENDLOOP; }; InvalidateDest: PROC [viewer: Viewer, node: Node, movingIn, afterDest: BOOL] = { InvalidateDestLine: PROC [v: Viewer, dest: Tioga.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: Node ¬ 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: Tioga.Location = [node, IF afterDest THEN TextEdit.Size[node] ELSE 0]; InvalidateDestLine[viewer, pos]; FOR v: Viewer ¬ viewer.link, v.link UNTIL v = NIL OR v=viewer DO InvalidateDestLine[v, pos]; ENDLOOP ; }; FirstLevelParent: PROC [node: Node] RETURNS [Node] = { prev1, prev2: Node; UNTIL node=NIL DO prev2 ¬ prev1; node ¬ TextNode.Parent[prev1 ¬ node]; ENDLOOP; RETURN [prev2] }; SpanIsBranches: PROC [first, last: Node] RETURNS [BOOL] = { parent: Node = TextNode.Parent[first]; n: Node ¬ 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: Node] RETURNS [Tioga.Span] = { RETURN [[[first, 0], [last, MAX[TextEdit.Size[last], 1]-1]]] }; ViewerFromNode: PROC [node: Node] RETURNS [Viewer] ~ { IF node=NIL THEN RETURN[NIL]; RETURN[TEditDocument.GetViewerForRoot[TextNode.Root[node]]]; }; NotifyBeforeEdit: PROC [change: REF READONLY Change] = { viewer: Viewer; span: Tioga.Span; WITH change SELECT FROM x: REF READONLY Change.NodeNesting => { IF (viewer ¬ ViewerFromNode[x.first])=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 ¬ ViewerFromNode[x.first]) # 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]; SetNewVersion[viewer] }; x.dest.dirty ¬ x.pred.dirty ¬ TRUE; IF (viewer ¬ ViewerFromNode[x.dest])=NIL THEN RETURN; InvalidateDest[viewer, x.dest, TRUE, x.afterDest] }; x: REF READONLY Change.ChangingProp => { x.node.dirty ¬ TRUE; IF (viewer ¬ ViewerFromNode[x.node])=NIL THEN RETURN; IF NodeProps.Is[x.name, $Visible] THEN { InvalidateBranchAndNext[viewer, x.node]; }; }; ENDCASE; SetNewVersion[viewer]; }; NotifyAfterEdit: PROC [change: REF READONLY Change] = { viewer: Viewer; span: Tioga.Span; WITH change SELECT FROM x: REF READONLY Change.ChangingText => { x.text.dirty ¬ TRUE; IF (viewer ¬ ViewerFromNode[x.text])=NIL THEN RETURN; UpdateAfterTextEdit[viewer, x.text, x.start, x.newlen, x.oldlen, x.oldRope] }; x: REF READONLY Change.InsertingNode => { span: Tioga.Span; child: Node; x.new.new ¬ x.dest.dirty ¬ TRUE; IF (viewer ¬ ViewerFromNode[x.dest])=NIL THEN RETURN; 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 ¬ ViewerFromNode[x.dest])=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, 1987, 1988, 1991, 1992 by Xerox Corporation. All rights reserved. Russ Atkinson (RRA) June 13, 1985 5:32:13 pm PDT Michael Plass, March 2, 1987 10:39:29 pm PST Willie-s, February 13, 1991 6:02 pm PST Doug Wyatt, February 27, 1992 6:01 pm PST 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 invalidate 1 line before start to take care of back ripple 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: Node] 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]) Κ|•NewlineDelimiter –(cedarcode) style™codešœ™Kšœ ΟeœO™ZK™0Kšœ,™,K™'Kšœ)™)K™—šΟk ˜ Kšœžœžœžœ˜Kšœžœ ˜Kšœ žœ%˜5Kšœ žœ˜Kšœžœ žœ˜Kšœ žœ9˜IKšœ žœ˜Kšœžœ<˜OKšœ žœ˜!Kšœžœ3˜GKšœ žœ ˜Kšœ žœk˜yKšœžœ˜$Kšœ žœ$˜3Kšœžœ ˜Kšœ žœ˜ —lead2šΟnœžœž˜ Kšžœ†˜Kšžœ˜Kšœž˜Kšœžœ˜Kšœžœ˜$Kšœžœ˜!Kšœžœ˜šœ žœžœ˜+K˜—šŸ œžœ˜(š žœ žœžœžœ žœž˜1šœ)˜)Kšžœžœžœ!˜9Kšœ˜—Kšžœžœ˜—Kšœ˜K˜—š Ÿ œžœžœžœKžœ ˜{K˜/šžœ žœžœ˜KšœJ™JK˜Kšœžœ˜K˜—K˜K˜—šŸœžœ žœ!˜Ešžœ žœž˜šœžœ˜Kšœ žœ ˜šžœžœž˜šžœžœž˜šœ)˜)šžœžœžœ˜K˜/šžœ žœž˜Kšœ'˜'—K˜—K˜K˜—Kšžœ˜——K˜—Kšžœ˜—K˜K˜—Kšœžœ˜šŸ œžœ žœžœ˜1Kšœžœ˜Kš žœžœžœžœžœ˜4šžœCžœžœž˜Tšžœ žœž˜Kš œžœžœ žœžœžœ˜>Kšžœ˜—šžœžœžœ˜K™J—Kšžœ˜—Kšžœžœ˜K˜K˜—šŸ œžœ.˜AKšœžœžœ˜;K˜*K˜BK˜K˜—šŸœžœ5žœžœ˜jKšœ6™6KšœR™RK˜Kšœžœ˜Kšœžœ˜šŸœžœ˜Kšœ'žœ ˜6K˜/Kšžœžœžœžœ˜Kšœ˜Kšžœžœžœ!Οc˜Vš žœžœž œžœž˜3šžœžœ˜"Kšœžœ˜ Kš žœžœžœžœžœžœ˜gšžœž˜šœ ˜ šžœ˜Kšžœ˜Kšžœ˜Kšžœžœ˜šžœžœžœ ž˜3Kš œžœžœžœžœžœ˜-—Kšžœžœ%žœ˜2Kšœ:™:Kšœžœ˜Kšœ˜—Kšœ˜—K˜+šžœ˜ šžœžœ˜ Kšœžœ˜.Kšœžœ˜K˜—Kšœ˜——Kšœ˜—Kšžœ˜—K˜—š Ÿœžœžœ"žœžœ˜dK˜Kšœ žœ˜$Kšœ žœžœ#˜7Kšžœžœžœžœ˜(K˜!Kšœžœ˜ K˜2šžœžœžœ ž˜0Kšžœ$žœ žœžœ˜@Kšžœ˜—K˜K˜—K˜š žœ!žœžœžœ ž˜@K˜ Kšžœ˜—K˜K˜—šŸœžœžœ/žœžœžœžœ˜~Kšœ3™3šŸœžœ˜+Kšœ žœ˜Kšœžœ˜Kšœ'žœ ˜6K˜/šžœžœ žœ˜&Kšœ<™Kšœ%™%K˜=K˜K˜K˜—šŸœžœ!˜>Kšœ9™9K˜'K˜'Kš œ žœžœžœžœ˜,K˜:K˜K˜K˜—šŸœžœ(˜Kšœžœ˜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˜ š žœ!žœžœžœ ž˜@Kšœ˜Kšžœ˜ —K˜K˜—šŸœžœžœ ˜6K˜šžœžœž˜K˜K˜%Kšžœ˜—Kšžœ ˜K˜K˜—šŸœžœžœžœ˜;K˜&K˜Kš žœžœžœžœžœ˜4šž˜šžœž˜$Kšœ žœžœ˜Kšžœžœ˜ Kšžœ˜—Kšžœžœžœžœ˜DKšžœ˜—Kšžœžœ˜K˜K˜—šŸ œžœžœ˜@Kšžœžœ˜=K˜K˜—šŸœžœžœ ˜6Kš žœžœžœžœžœ˜Kšžœ6˜