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] = { 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 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]; 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] = { 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 { 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 < 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] = { 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] = { 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] = { 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]]; 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. <-- TEditDisplay2Impl.mesa, last edit by Paxton on December 22, 1982 3:58 pm Last Edited by: Maxwell, January 6, 1983 2:02 pm -- 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 -- 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 -- 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 Ê ˜JšÏcK™KJ™0šÏk ˜ Jšœ žœ$˜4Jšœžœžœ˜ Jšœ žœ ˜Jšœ žœ8˜HJšœ žœ˜Jšœ žœ§˜µJ˜ Jšœžœ;˜NJšœ žœ˜ Jšœžœ2˜FJšœ žœ˜&Jšœžœ ˜Jšœ žœ˜ J˜—Jšœž ˜ J˜Jšžœ|˜ƒJšžœ˜J˜Jšžœžœ˜)J˜Jšœžœ˜!J˜šÏn œžœžœžœ5˜UJšœ žœž˜J˜/J˜Jšœžœ˜Jšžœ˜J˜—šŸœžœ žœ)˜MJšœžœžœ ˜-Jšœžœ˜/Jšžœžœžœžœ˜Jšžœžœ/˜NJ˜!J˜—šŸ œžœžœžœ˜?Jš žœžœžœžœžœ˜4šžœCžœžœž˜Tšžœ žœž˜šžœ žœž˜Jš žœžœžœžœžœ˜@Jšžœ˜——Jšžœ˜—Jšžœžœ˜J˜—šŸ œžœ?˜RJšœžœ&žœ˜JJ˜)J˜BJ˜—šŸœžœ˜8J˜J˜'Jšœžœ˜Jš9™9JšU™UJ˜šŸœžœ˜*Jšœžœ ˜(J˜!Jšžœžœžœžœ˜Jšžœ žœžœ˜2Jšžœžœ!˜Sšžœžœžœž˜(J˜Jšžœžœžœ˜&šžœž˜&šœ žœ˜Jšžœžœ˜1Jšžœ˜Jšžœ%˜(Jšžœ žœ˜*šžœ%ž˜,Jš=™=Jšœžœ˜——J˜+šžœžœžœ˜Jšœžœ˜.Jšœžœ˜——Jšžœ˜—J˜J˜—š Ÿœžœžœžœžœ˜VJšžœ ˜J˜ J˜0Jšžœžœžœžœ˜(J˜Jšœžœ˜ J˜%šžœžœžœ ž˜4Jšžœžœ žœžœ˜:Jšžœ˜—J˜J˜—J˜'J˜$Jšœžœžœ˜$Jšœžœ˜J˜š Ÿœžœžœžœžœ ˜=J˜ Jšžœ žœžœžœ˜"Jšžœžœžœ˜;J˜Jšœžœ˜J˜!šžœžœ žœ ž˜$Jšžœžœžœžœ˜JJšžœ˜—Jšœžœ˜J˜Jšžœ˜J˜—šœ9žœ˜@šM™MJš"™"——J˜šžœžœž˜šžœ/žœ ž˜CJ˜ Jšžœ˜——J˜J˜—šŸœžœžœ4˜OJš œ žœžœžœžœ˜?Jš6™6šŸœžœ˜9Jšœ žœ˜Jšœžœ˜"Jšœžœ ˜(J˜!šžœžœ žœ˜&Jš?™?Jšœžœžœ˜Jšœžœ˜#—šžœT˜X˜ JšC™CJš7™7Jšœžœžœ˜Jšœžœ˜"Jšœžœ žœœžœžœžœœ˜VJšžœ˜ ——Jšžœ žœžœ ˜%šžœ žœžœž˜šžœ žœž˜-šœ2ž˜5J˜PJšžœ˜1——Jšžœžœ˜ —Jšžœ žœ ˜3Jš žœžœžœžœ˜MJš žœžœ žœžœ žœ ˜Dšžœžœžœž˜!Jšœžœ˜%Jšžœ˜—Jšžœ žœžœ˜(—J˜šžœžœž˜šžœ/žœ ž˜CJšœžœ˜$J˜———šŸœžœžœ7˜VJš(™(J˜J˜Jšžœ%žœžœžœ˜7J˜0J˜J˜—šŸœžœžœ7˜TJš(™(J˜>J˜J˜—šŸœžœ7˜TJš<™