<> <> <> <> <> <<>> 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 <start -- do "NOT lines[n].valid" test instead - mfp>> AND Rope.Fetch[text.rope, where-1] # 15C < 0 OR InsertContainsBlank[]) -- omit this to get hyphenation refresh - mfp>> 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.