MODULEThe routines in this module deal with the division of the buffer into screen lines.; IMPORT Point, Rd, Thread; IMPORT VTDef, VTRd; VTBase 
   ComputeLine, given the purported beginning of a screen line, computes
   its end and other of its characteristics. Up, given an arbitrary buffer
   position, computes the beginning of the line n lines above the line
   that the position lies on. (If n is 0, this is the beginning of the
   line that the position lies on.) Down, given the purported beginning of
   a line, computes the beginning of the line n lines below that line.
   (If n is 0, Down is the identity function.)
   All of these procedures check whether they can solve or simplify the
   problem quickly using the information in the view's virtual structure;
   this unfortunately makes them less readable. They also special-case on
   simple cases before performing expensive set-up operations necessary for
   more complicated cases (e.g., setting up a reader); this further
   complicates their logic.
Routine UnsafeLocateLine, given an index into the buffer, returns its line number in the view (or -1 if it is above the view, or -2 if it is below). UnsafeLocateLine assumes that the view's virtual structures are not dirty (hence its name).
TYPE VirtualLine = VTDef.VirtualLine;ComputeLine computes the characteristics of a screen line starting at
from; it returns the index after the end. (From is believed to be at
   the beginning of a screen line.) Max is set to the index after the
   last character examined to make the decision (the first was from).
   Turned is set to whether the end of the screen line is turned (if the
   screen line does not end in a new-line and is not at the end of the
   buffer). Width is set to the width in pixels needed to display the
   text. 
EXCEPTION Overflow; (* raised when the line gets too long *) (* local to ComputeLine *) PROCEDUREUp computes the beginning of the screen lineComputeLine ( view : View; avail : Pixels; from : I; VAR (* OUT*) max : I; VAR (* OUT*) turned: BOOLEAN; VAR (* OUT*) width : Pixels ): I RAISES {Rd.EndOfFile, Rd.Failure, Thread.Alerted} = VAR length: I := view.vt.length; BEGIN <* ASSERT 0 <= from AND from <= length + 1 *> (* try the very easy case first *) IF from >= length THEN max := MAX(from, length + 1); turned := FALSE; IF from = length THEN width := 1 ELSE width := 0 END; RETURN max END; (* try to solve the problem using the virtual structure *) IF avail = view.lineWidth AND NOT view.virtual.bodyDirty AND from >= view.virtual.line[0].virtualLine.from AND from < view.virtual.line[view.virtual.lines].virtualLine.from THEN WITH lineNo = UnsafeLocateLine(view, from), vl = view.virtual.line[lineNo].virtualLine DO IF vl.from = from THEN max := vl.max; turned := vl.turned; width := vl.width; RETURN vl.to END END END; (* solve the problem the hard way *) VTRd.InitReaderIx(view.vt, from); VAR i : I := from; (* the reader index *) w : Pixels := 0; (* the width so far *) ow : Pixels := 0; (* the previous value of w *) white := FALSE; (* true after a whitespace *) okI : I := 0; (* index of a possible break *) okw : Pixels := 0; (* the width up to okI *) c : CHAR; BEGIN WITH widthMap = view.vScreenFont.width, printable = view.vScreenFont.vFont.printable DO TRY LOOP (* can we read a character? *) IF i >= length THEN (* no; we're at eof *) <* ASSERT i <= length + 1 *> IF white THEN okI := i; okw := w END; turned := i # from; w := w + 1; (* width of the caret *) i := length + 1; IF w > avail THEN RAISE Overflow END; max := i; width := w; RETURN i END; (* read a character *) c := Rd.GetChar(view.vt.rd); i := i + 1; (* handle the cases *) IF c = ' ' THEN w := w + widthMap[' ']; IF w > avail THEN RAISE Overflow END; white := TRUE ELSIF c = '\n' THEN w := w + widthMap['\n']; IF white THEN okI := i - 1; okw := ow END; IF w > avail THEN RAISE Overflow END; max := i; turned := FALSE; width := w; RETURN i ELSIF c = '\t' AND '\t' IN printable THEN IF w + widthMap[' '] > avail THEN RAISE Overflow END; w := w + widthMap[' '] + widthMap['\t'] - 1; w := MIN(w - w MOD widthMap['\t'], avail); white := TRUE ELSE w := w + widthMap[c]; IF white THEN okI := i - 1; okw := ow END; IF w > avail THEN RAISE Overflow END; white := FALSE END; ow := w END EXCEPT Overflow => (* line got too long; break *) max := i; turned := TRUE; IF okI > 0 THEN width := okw; RETURN okI; ELSE width := ow; RETURN MAX(i - 1, from + 1); END; END; END; END; END ComputeLine;
n lines above the screen
   line that place is on. (Place is not believed to be at the beginning
   of a screen line.) Min and max are set to a half-open interval that
   includes a set of buffer positions that imply this result. Turned is
   set to whether the beginning of that screen line is turned (if the
   screen line is not preceded by a new-line and is not at the beginning of
   the buffer). If fewer than n screen lines exist, the beginning of the
   buffer is returned. We repeatedly reduce the problem by searching
   backwards for a position that is known to be the beginning of a line,
   then computing lines forward from it until we reach the point of
   interest. We remember the indices of the lines thus computed in a table;
   we index into this table to solve or to simplify the problem. 
PROCEDUREUp ( view : View; avail: Pixels; place: I; n : CARDINAL; VAR (* OUT*) start: VirtualStart) RAISES {Rd.EndOfFile, Rd.Failure, Thread.Alerted} = CONST BufSize = 512; ChunkSize = 100; VAR at, bI, rI, rrI, mI: I; l, nB, nn, lineNo : CARDINAL; rBuf : ARRAY [0 .. BufSize - 1] OF CHAR; ll : ARRAY [0 .. ChunkSize - 1] OF VirtualLine; BEGIN <* ASSERT ((place >= 0) AND (place <= view.vt.length)) *> start.turned := FALSE; (* default *) rrI := place; nB := 0; LOOP (* reduce problem *) (* try an easy solution *) IF place = 0 THEN start.at := 0; start.min := -1; start.max := 0; RETURN; END; (* try to reduce the problem using the virtual structure *) IF avail = view.lineWidth AND NOT view.virtual.dirty AND (place >= view.virtual.line[0].virtualLine.from) AND (place < view.virtual.line[view.virtual.lines].virtualLine.from) THEN lineNo := UnsafeLocateLine(view, place); l := MIN(lineNo, n); (* we can look "l" lines back *) (* reduce the problem *) place := view.virtual.line[lineNo - l].virtualLine.from; n := n - l; IF n = 0 THEN (* we've solved the problem *) start.at := place; start.min := view.virtual.start.min; (* conservative *) IF lineNo - l > 0 THEN start.turned := view.virtual.line[lineNo - l - 1].virtualLine.turned; start.max := view.virtual.line[lineNo - l - 1].virtualLine.max ELSE start.turned := view.virtual.start.turned; start.max := view.virtual.start.max END; RETURN; END; ELSE (* find some beginning of (buffer) line at or before place *) rI := place; (* next reverse read is at rI - 1 *) mI := place; (* after the last place we've looked *) IF rI - rrI > nB THEN rrI := rI END; LOOP rI := rI - 1; IF rI < rrI THEN IF rI < 0 THEN rI := -1; bI := 0; EXIT END; nn := rI MOD BufSize; rrI := rI - nn; VTRd.InitReaderIx(view.vt, rrI); nB := nn + 1; WITH n = Rd.GetSub(view.vt.rd, SUBARRAY(rBuf, 0, nB)) DO <* ASSERT n = nB *> END END; IF rBuf[rI - rrI] = '\n' THEN bI := rI + 1; (* beginning of a buffer line *) EXIT END END; at := bI; (* the latest beginning of buffer line not after place *) l := 0; (* index of lines computing forward *) LOOP (* find recent line breaks up to place *) WITH z_4 = ll[l MOD ChunkSize] DO z_4.from := at; IF at = place THEN EXIT END; z_4.to := ComputeLine(view, avail, at, z_4.max, z_4.turned, z_4.width); z_4.valid := TRUE; IF z_4.max > mI THEN mI := z_4.max END; IF z_4.to = at THEN (* roadblock in breaking; give arbitrary answer *) start.at := place; start.min := rI; start.max := mI; start.turned := TRUE; RETURN END; IF z_4.to > place THEN EXIT END; at := z_4.to END; l := l + 1 END; nn := MIN(n, MIN(l, ChunkSize - 1)); (* how far back we can look *) place := ll[(l - nn + ChunkSize) MOD ChunkSize].from; (* set place to bol *) n := n - nn; (* maybe reduce n *) IF n = 0 THEN start.at := place; start.min := rI; start.max := mI; start.turned := nn # l; RETURN END END; (* place is now at the beginning of a line; try an easy solution *) IF place = 0 THEN start.at := 0; start.min := -1; start.max := 0; RETURN END; (* iterate *) place := place - 1; n := n - 1; END; END Up; EXCEPTION Iterate; (* local to Down *) PROCEDUREDown (view: View; from: I; n: CARDINAL): I RAISES {Rd.EndOfFile, Rd.Failure, Thread.Alerted} = (* Down computes the beginning of the screen line "n" lines below the screen line that "from" is on. ("From" is believed to be at the beginning of a screen line.) If fewer than "n" screen lines exist, the end of the buffer is returned. *) VAR m : I; t : BOOLEAN; w : Pixels; l, lineNo: INTEGER; BEGIN WHILE n > 0 DO (* try to reduce problem using virtual structure *) TRY IF NOT view.virtual.bodyDirty AND (from >= view.virtual.line[0].virtualLine.from) AND (from < view.virtual.line[ view.virtual.lines].virtualLine.from) THEN lineNo := UnsafeLocateLine(view, from); IF view.virtual.line[lineNo].virtualLine.from = from THEN l := MIN(view.virtual.lines - lineNo, n); IF l > 0 THEN n := n - l; from := view.virtual.line[lineNo + l].virtualLine.from; RAISE Iterate END END END; (* do it the hard way *) from := ComputeLine(view, view.lineWidth, from, m, t, w); n := n - 1 EXCEPT | Iterate => END END; RETURN from; END Down; PROCEDUREUnsafeLocateLine (view: View; place: I): INTEGER RAISES {} = VAR i, j, k: INTEGER; BEGIN IF place < view.virtual.line[0].virtualLine.from THEN RETURN -1; ELSIF place >= view.virtual.line[view.virtual.lines].virtualLine.from THEN RETURN -2; ELSE i := 0; j := view.virtual.lines - 1; WHILE i < j DO k := (i + j + 1) DIV 2; IF view.virtual.line[k].virtualLine.from <= place THEN i := k ELSE j := k - 1 END END; RETURN i; END; END UnsafeLocateLine; PROCEDUREUnsafeLocatePoint ( view : View; place: I; VAR (* OUT*) p : Point.T; off : CARDINAL := 1) RAISES {Rd.EndOfFile, Rd.Failure, Thread.Alerted} = VAR i : INTEGER; w : Pixels; c : CHAR; BEGIN i := UnsafeLocateLine (view, place); IF i < 0 THEN p.v := i ELSE WITH vl = view.virtual.line [i] DO p.v := view.rect.text.north + i * view.lineSpacing; VTRd.InitReaderIx (view.vt, vl.virtualLine.from); WITH sf = view.vScreenFont DO WITH vFont = sf.vFont DO w := 0; FOR index := vl.virtualLine.from TO place - off DO c := Rd.GetChar (view.vt.rd); IF c = '\n' THEN w := view.lineWidth ELSE w := w + sf.width [c]; IF (c = '\t') AND ('\t' IN vFont.printable) THEN w := w - 1 + sf.width [' ']; w := w - w MOD sf.width ['\t'] END END END END END END; p.h := MIN (view.rect.text.west + w, view.rect.text.east) END END UnsafeLocatePoint; BEGIN END VTBase.