<<>> <> <> <> <> <> <> <<>> 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.