<<-- TiogaDisplay2Impl.mesa, last edit by Paxton on June 20, 1983 1:22 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], TiogaNode USING [Location, Offset, Ref, RefTextNode, Span], TiogaNodeOps USING [BranchChild, Forward, ForwardClipped, LastLocWithin, LastWithin, Level, NarrowToTextNode, Next, Parent, StepForward], TiogaDisplay, TiogaDocument USING [GetViewerForRoot, LineTable, maxClip, TiogaDocumentData], TiogaInput USING [currentEvent], TiogaSelection USING [ComputePosLine, ComputeSpanLines, CannotFindIt], UndoEvent USING [Note, Ref, SubEvent], ViewerClasses USING [Viewer], ViewerOps USING [SetNewVersion]; TiogaDisplay2Impl: CEDAR MONITOR IMPORTS Rope, EditNotify, RopeEdit, RopeReader, TextEdit, TiogaNodeOps, TiogaDocument, TiogaInput, TiogaSelection, UndoEvent, ViewerOps EXPORTS TiogaDisplay = BEGIN OPEN TiogaSelection, TiogaDocument; Change: TYPE = EditNotify.Change; EstablishLine: PUBLIC ENTRY PROC [tdd: TiogaDocumentData, newLine: TiogaNode.Location, position: INTEGER _ 0] = BEGIN lines: TiogaDocument.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: TiogaDocumentData = 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 TiogaInput.currentEvent = NIL THEN RETURN [TRUE]; FOR l: UndoEvent.SubEvent _ TiogaInput.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: TiogaNode.Location] = { notify: REF ChangingView Change _ NEW[ChangingView Change]; notify^ _ [ChangingView[viewer,oldLine]]; UndoEvent.Note[TiogaInput.currentEvent,UndoChangeViewer,notify] }; UpdateAfterTextEdit: PROC [viewer: ViewerClasses.Viewer, text: TiogaNode.RefTextNode, start, newlen, oldlen: TiogaNode.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: TiogaDocumentData = 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: TiogaNode.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: TiogaNode.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: TiogaNode.Offset = newlen-oldlen; end: TiogaNode.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: TiogaNode.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: TiogaDocumentData = 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] _ TiogaSelection.ComputeSpanLines[v, span ! TiogaSelection.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: TiogaNode.Ref] = { <<-- invalidate lines for children of node>> child: TiogaNode.Ref; span: TiogaNode.Span; <> span _ [[child,0],TiogaNodeOps.LastLocWithin[node]]; InvalidateSpan[viewer,span] }; InvalidateBranch: PUBLIC PROC [viewer: ViewerClasses.Viewer, node: TiogaNode.Ref] = { <<-- invalidate lines for children of node>> span: TiogaNode.Span _ [[node,0],TiogaNodeOps.LastLocWithin[node]]; InvalidateSpan[viewer,span] }; InvalidateBranchAndNext: PROC [viewer: ViewerClasses.Viewer, node: TiogaNode.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: TiogaNode.Ref] = { <<-- this takes care of case in which something has been inserted in front of the first node>> next: TiogaNode.Ref _ TiogaNodeOps.StepForwardNode[last]; UpdateViewerStartPos: PROC [v: ViewerClasses.Viewer] = { tdd: TiogaDocumentData = 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: TiogaNode.Ref, movingIn, afterDest: BOOL] = { InvalidateDestLine: PROC [v: ViewerClasses.Viewer, dest: TiogaNode.Location] = { line: INTEGER; clipped, failed: BOOLEAN _ FALSE; tdd: TiogaDocumentData = NARROW[v.data]; IF tdd = NIL THEN RETURN; IF movingIn THEN tdd.movedIn _ TRUE; -- remember that have moved nodes into this doc [line, clipped] _ TiogaSelection.ComputePosLine[v, dest ! TiogaSelection.CannotFindIt => { failed _ TRUE; CONTINUE } ]; IF failed AND tdd.clipLevel < maxClip THEN { -- try adjusting for level clipping n: TiogaNode.Ref _ dest.node; delta: INTEGER _ TiogaNodeOps.Level[n]-tdd.clipLevel; IF delta > 0 THEN { FOR i:INTEGER IN [0..delta) DO n _ TiogaNodeOps.Parent[n]; ENDLOOP; n _ TiogaNodeOps.ForwardClipped[n,tdd.clipLevel].nx; IF n # NIL THEN { dest _ [n,0]; afterDest _ failed _ FALSE; [line, clipped] _ TiogaSelection.ComputePosLine[v, dest ! TiogaSelection.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: TiogaNode.Location = [node, IF afterDest THEN TextEdit.Size[TiogaNodeOps.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: TiogaNode.Ref] RETURNS [TiogaNode.Ref] = { prev1, prev2: TiogaNode.Ref; UNTIL node=NIL DO prev2 _ prev1; node _ TiogaNodeOps.Parent[prev1 _ node]; ENDLOOP; RETURN [prev2] }; SpanIsBranches: PROC [first, last: TiogaNode.Ref] RETURNS [BOOL] = { parent: TiogaNode.Ref = TiogaNodeOps.Parent[first]; n: TiogaNode.Ref _ last; IF TiogaNodeOps.FirstChild[n] # NIL THEN RETURN [FALSE]; DO SELECT (n _ TiogaNodeOps.Parent[n]) FROM parent => RETURN [TRUE]; NIL => EXIT; ENDCASE; IF TiogaNodeOps.Next[n] # NIL THEN RETURN [TiogaNodeOps.Parent[n] = parent]; ENDLOOP; RETURN [FALSE] }; SpanFromNodes: PROC [first,last: TiogaNode.Ref] RETURNS [TiogaNode.Span] = { RETURN [[[first,0],[last,MAX[TextEdit.Size[TiogaNodeOps.NarrowToTextNode[last]],1]-1]]] }; NotifyBeforeEdit: PROC [change: REF READONLY EditNotify.Change] = { viewer: ViewerClasses.Viewer; span: TiogaNode.Span; WITH change SELECT FROM x: REF READONLY Change.NodeNesting => { IF (viewer _ TiogaDocument.GetViewerForRoot[x.root])=NIL THEN RETURN; span _ SpanFromNodes[x.first,x.last]; IF ~SpanIsBranches[x.first,x.last] THEN span.end _ TiogaNodeOps.LastLocWithin[FirstLevelParent[x.last]]; InvalidateSpan[viewer,span,TRUE,FALSE,(x.change<0)] }; x: REF READONLY Change.MovingNodes => { IF (viewer _ TiogaDocument.GetViewerForRoot[x.sourceRoot]) # NIL THEN { branches: BOOL _ SpanIsBranches[x.first,x.last]; span _ SpanFromNodes[x.first,x.last]; IF ~branches THEN span.end _ TiogaNodeOps.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 _ TiogaDocument.GetViewerForRoot[x.destRoot])=NIL THEN RETURN; InvalidateDest[viewer,x.dest,TRUE,x.afterDest] }; x: REF READONLY Change.ChangingType => { x.node.dirty _ TRUE; IF (viewer _ TiogaDocument.GetViewerForRoot[x.root])=NIL THEN RETURN; InvalidateBranchAndNext[viewer,x.node] }; x: REF READONLY Change.ChangingProp => { x.node.dirty _ TRUE; IF (viewer _ TiogaDocument.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: TiogaNode.Span; WITH change SELECT FROM x: REF READONLY Change.ChangingText => { x.text.dirty _ TRUE; IF (viewer _ TiogaDocument.GetViewerForRoot[x.root])=NIL THEN RETURN; UpdateAfterTextEdit[viewer,x.text,x.start,x.newlen,x.oldlen,x.oldRope] }; x: REF READONLY Change.InsertingNode => { span: TiogaNode.Span; child: TiogaNode.Ref; DiffCommentProps: PROC [n1, n2: TiogaNode.Ref] RETURNS [BOOL] = { node1, node2: TiogaNode.RefTextNode; IF (node1 _ TiogaNodeOps.NarrowToTextNode[n1]) = NIL THEN RETURN [TRUE]; IF (node2 _ TiogaNodeOps.NarrowToTextNode[n2]) = NIL THEN RETURN [TRUE]; RETURN [node1.comment # node2.comment] }; x.new.new _ x.dest.dirty _ TRUE; IF (viewer _ TiogaDocument.GetViewerForRoot[x.root])=NIL THEN RETURN; ViewerOps.SetNewVersion[viewer]; InvalidateDest[viewer,x.dest,FALSE,TRUE]; IF (child _ TiogaNodeOps.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],TiogaNodeOps.LastLocWithin[x.new]]; InvalidateSpan[viewer,span]; UpdateStartPos[viewer,x.new,x.new] }}; x: REF READONLY Change.MovingNodes => { IF (viewer _ TiogaDocument.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 _ TiogaNodeOps.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.