/* Copyright 1996 Acorn Computers Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Title: c.txt1 * Purpose: text display object, system-independent implementation part. * Author: WRS * Status: under development * History: * 18 July 87 -- started * 11-Jan-88: conversion to C started. * 13-Dec-89: WRS: msgs literal text put back in. * 1-Jun-90: NDR: reset txt->last_ref if txt->charoptionset & txt_UPDATED set * 1-Jun-90: NDR: removed definition of txt1__maxbi(), since it's not used * 8-May-91: ECN: #ifndefed out unused ROM functions */ #define BOOL int #define TRUE 1 #define FALSE 0 #define NULL 0 #include <limits.h> #include <string.h> #include <stdlib.h> #include <limits.h> #include <stdarg.h> #include "trace.h" #include "werr.h" #include "flex.h" #include "msgs.h" #include "txt.h" #include "EditIntern/txtundo.h" #include "EditIntern/txt1.h" #include "EditIntern/txt3.h" #include "VerIntern/messages.h" #define GUARDBYTE 23 #define MINEXTENDEDTOP 20 /* 1024 in real life I think. */ /* -------- Forward References. -------- */ void txt1__assert(BOOL b, char *msg); void txt1__assert2(BOOL b, char *msg1, char *msg2); void txt1__assertcharbufindex(txt t, txt1_bufindex i, char *msg); void txt1__yell(char *s1, char *s2); BOOL txt1__lessoreqbi(txt t, txt1_bufindex a, txt1_bufindex b); txt_index txt1__bufindextoindex(txt t, txt1_bufindex b); txt1_bufindex txt1__indextobufindex(txt t, txt_index i); txt1_bufindex txt1__firstchbi(txt t); void txt1__incbi(txt t, txt1_bufindex *i /*inout*/, int n); void txt1__incrawbi(txt t, txt1_bufindex *bi /*inout*/, int n); void txt1__updatescrollbar(txt t); txt1_bufindex txt1__termchbi(txt t); void txt1__ensuredotvisible(txt t, int dotmove); void txt1__showcaret(txt t); void txt1__hidecaret(txt t); txt1_zits txt1__spacewidth(txt t); void txt1__displayreplace1(txt t, int n); void txt1__displayreplace2(txt t, int n, int ndeleted); void txt1_scrollbarmoveby(txt t, int by); void txt1__measure( txt t, txt1_zits *x /*inout*/, txt1_zits *y /*inout*/, txt1_zits ylim, /* y>=ylim -> stop straight away. */ txt1_bufindex *at /*inout*/, txt1_bufindex lim, /* at==lim -> stop straight away. */ BOOL generate); BOOL txt1__measureback( txt t, txt1_zits *x /*inout*/, txt1_zits *y /*inout*/, txt1_zits ylim, /* y<=ylim -> stop straight away */ txt1_bufindex *at /*inout*/, txt1_bufindex lim, /* at==lim -> stop straight away */ BOOL generate); void txt1__cancelallcalls(txt t); void txt1__horizmeasure(txt t, txt1_zits x, txt1_zits xlim, txt1_bufindex *at /*inout*/); void txt1__redisplaytext(txt t, BOOL fresh); BOOL txt1__dotatlinebreak(txt t); void txt1__deletemarker(txt t, txt1_imarker *m); void txt1__movemarker(txt t, txt1_imarker *m, txt1_bufindex newpos); void txt1__cleartoendofline(txt t, txt1_zits *x /*inout*/, txt1_zits *y /*inout*/, BOOL generate); void txt1__displayfrom(txt t, txt1_zits x, txt1_zits y, txt1_bufindex at); void txt1__italicadjust(txt t); /* -------- The text buffer structure. -------- */ /* The traditional representation of the body of a text buffer (a la emacs) consists of a contiguous area of memory, with the characters of the file placed contiguously within it. at the) {t (= cursor) there is a gap, so that insertion and deletion at) {t is quick. moving the) {t involves a (literal) block move of characters. markers are implemented by a similar buffer of pointers into the text buffer, they are kept in order within the buffer so that movement/deletion only involve work if some markers are actually involved. if the text gets too big to fit in the buffer then allocate a new buffer and copy. The buffer here is similar, with the additional complication that i wish tty-style use to be efficient, i.e. adding things at the bottom and removing corresponding things at the top. For this reason the whole thing can actually circulate within the buffer. The pointers into the buffer are as follows. in order to distinguish the full and empty cases there's always a single extra 0c character (called termch) in the buffer, after the last legal character. "the array" refers to the abstract array the text object represents. 0: base of the buffer. top: max possible legal value. t->buf[top] is a character in the buffer. txtstart: points to the first character in the array, or to termch if the array is empty. gapstart: points to the first character in the gap. never > top. can be equal to gapend, if no gap (i.e. array full). can be equal to t->txtstart, if) {t is at start of array. gapend: points to the first character after the gap. can point to termch, if) {t is at the end of the array. Based on this, here are various predicates and invariants. invariant: top >= 1 txtstart <= top gapstart <= top gapend <= top if gapstart <= gapend then (gap contiguous) txtstart <= gapstart or txtstart > gapend else txtstart >= gapstart or txtstart < gapend end buf[(txtstart-1) % top] == 0c (termch) buffer full: gapstart == gapend char "at") {t: buf[gapend] dot at start of array: txtstart == gapstart dot at end of array: txtstart == (gapend + 1) % top size of array: (top - 1 - (gapend - gapstart)) % top array is empty: ) {t at start of array ) {t at end of array size of array == 0 Markers are held as two chains of markerrep, one before the dot in the array and one at or beyond it. */ #if TRACE void txt1__checkbufinvariant(txt t, char *msg) { txt1_imarker *imarkerptr; txt1__assert2(t->top >= 1, msg, ":1"); txt1__assert2(t->txtstart <= t->top, msg, ":2"); txt1__assert2(t->gapstart <= t->top, msg, ":3"); txt1__assert2(t->gapend <= t->top, msg, ":4"); txt1__assert2(t->buf[t->top+1] == GUARDBYTE, msg, ":11"); if (t->gapstart <= t->gapend) { /* gap contiguous */ txt1__assert2(t->txtstart<=t->gapstart || t->txtstart>t->gapend, msg, ":5"); } else { txt1__assert2(t->txtstart<=t->gapstart && t->txtstart>t->gapend, msg, ":6"); }; if (t->txtstart == 0) { txt1__assert2(t->buf[t->top] == 0, msg, ":7"); } else { txt1__assert2(t->buf[t->txtstart-1] == 0, msg, ":8"); }; imarkerptr = t->marksbefore; while (imarkerptr != NULL) { txt1__assertcharbufindex(t, imarkerptr->pos, ":9"); /* msg lost */ imarkerptr = imarkerptr->next; }; imarkerptr = t->marksafter; while (imarkerptr != NULL) { txt1__assertcharbufindex(t, imarkerptr->pos, ":10"); /* msg lost */ imarkerptr = imarkerptr->next; }; } #else #define txt1__checkbufinvariant(t,msg) ; #endif /* A "charbufindex" is a bufindex that does not point into the gap, i.e. it points at a real char in the array, or at termch. */ #if TRACE void txt1__assertcharbufindex(txt t, txt1_bufindex i, char *msg) { txt1__assert2(i <= t->top, msg, ":1"); if (t->gapend >= t->gapstart) { /* gap contiguous */ txt1__assert2(i < t->gapstart || i >= t->gapend, msg, ":2"); } else { txt1__assert2(i < t->gapstart && i >= t->gapend, msg, ":3"); }; } #else #define txt1__assertcharbufindex(t,i,msg) ; #endif #if TRACE void txt1__checkscreeninvariant(txt t, char *msg) /* called after display update. */ { if (0 == (txt_DISPLAY & t->charoptionset)) return; if (! txt1__lessoreqbi(t, t->w->firstvis.pos, t->gapend)) { tracef("**firstvis problem: fv=%i dot=%i.\n", t->w->firstvis.pos, t->gapend); txt1__yell(msg, ":1"); }; if (! txt1__lessoreqbi(t, t->gapend, t->w->lastvis.pos)) { tracef("**lastvis problem: lv=%i dot=%i.\n", t->w->lastvis.pos, t->gapend); txt1__yell(msg, ":2"); }; txt1__assert2(txt1__lessoreqbi(t, t->w->firstvis.pos, t->gapend), msg, ":1"); txt1__assert2(txt1__lessoreqbi(t, t->gapend, t->w->lastvis.pos), msg, ":2"); if (t->w->lastvisy > t->w->limy + 2 * t->w->linesep) { tracef("**lastvisy problem, =%i (limy=%i).\n", t->w->lastvisy, t->w->limy); }; if (t->w->firstvisy <= 0 || t->w->firstvisy > t->w->linesep) { tracef("**firstvisy problem, =%i.\n", t->w->firstvisy); }; txt1__assert2(t->w->firstvisy > 0, msg, ":7"); txt1__assert2(t->w->firstvisy <= t->w->linesep, msg, ":8"); txt1__assert2(t->w->lastvisy <= t->w->limy + 2 * t->w->linesep, msg, ":9"); /* the 2* is because a newlinech at the end of a partially visible line takes lastvisy to refer to the next char. */ txt1__assert2(t->w->carety > 0, msg, ":3"); txt1__assert2(t->w->carety < (t->w->linesep * t->w->limy) + t->w->linesep, msg, ":4"); txt1__assert2(t->w->caretx >= 0, msg, ":5"); if (TRUE /* >>>> (wrap in charoptionset) */) { txt1__assert2(t->w->caretx <= t->w->limx, msg, ":6"); /* inadequate test! */ /* note that we are accepting the equal case, for when a line exactly fits. see the comment at the end of procedure measure. */ }; } #else #define txt1__checkscreeninvariant(t,msg) ; #endif static int txt1__min(int a, int b) {return(a < b ? a : b);} static int txt1__max(int a, int b) {return(a > b ? a : b);} /* -------- checking and debugging. -------- */ #if TRACE void txt1__writes(char *s) { tracef(s); } /*>>>> this treatment of error cases in the "release" form is pretty inadequate. some centralised "panic" formula should exist. */ void txt1__yell2(char *s1, char *s2, char *s3) { txt1__writes("wrs internal error: "); txt1__writes(s1); txt1__writes(s2); txt1__writes(s3); txt1__writes("\n"); } void txt1__yell(char *s1, char *s2) { txt1__yell2(s1, s2, ""); } void txt1__assert(int b, char *msg) { if (! b) txt1__yell("assert fail: ", msg); } void txt1__assert2(int b, char *msg1, char *msg2) { if (! b) txt1__yell2("assert fail: ", msg1, msg2); } void txt1__newline(void) { tracef("\n"); } #else #define txt1__assert(t,msg) ; #endif /* -------- printing the text buffer. -------- */ #if TRACE void txt1__safewrch(char c) { if (c >= 32 && c < 127) { tracef("%c", c); } else if (c == 0) { tracef("."); } else if (c == '\n') { tracef("~"); } else { tracef("?"); }; } void txt1__tab(int by) { while (by-- > 0) tracef(" "); } void txt1__prmarkerchain(txt t, txt1_imarker *m) { if (m == NULL) {tracef(" -> NULL\n");} else { tracef(" -> "); if (m == &(t->selstart)) { tracef("ss:"); } else if (m == &t->selend) { tracef("se:"); } else if (m == &t->w->firstvis) { tracef("fv:"); } else if (m == &t->w->lastvis) { tracef("lv:"); }; tracef("%i", m->pos); txt1__prmarkerchain(t, m->next); }; } void txt1__pr(txt t) { int i; if (t->top > 300) { tracef(">>>>top=%i start=%i gstart=%i gend=%i\n", t->top, t->txtstart, t->gapstart, t->gapend); } else { /* zap the gap characters to make things clearer */ if (t->gapend > t->gapstart) { for (i = t->gapstart; i <= t->gapend-1; i++) t->buf[i] = '*'; } else if (t->gapend < t->gapstart) { for (i = t->gapstart; i <= t->top; i++) t->buf[i] = '*'; for (i = 1; i <= t->gapend; i++) t->buf[i-1] = '*'; }; tracef(" ["); for (i = 0; i <= t->top; i++) txt1__safewrch(t->buf[i]); tracef("]\n"); txt1__tab(t->txtstart+1); tracef("start^ (%i)\n", t->txtstart); txt1__tab(t->gapstart); tracef("gstart^ (%i)\n", t->gapstart); txt1__tab(t->gapend+2); tracef("gend^ (%i)\n", t->gapend); }; /* prettyprint markers. */ tracef("marks before:"); txt1__prmarkerchain(t, t->marksbefore); tracef("marks after:"); txt1__prmarkerchain(t, t->marksafter); tracef("fvy=%i lvy=%i.\n", t->w->firstvisy, t->w->lastvisy); } #endif /* -------- Creation and deletion. -------- */ BOOL txt1_inittextbuffer(txt t) /* The buffer is initially set up as existing, but containing only termch. */ /* >>>> initial kludge: set up small buffer to prevent requirement for extension. */ { t->top = 50; (void) flex_alloc((void**) &(t->buf), t->top+2); if (t->buf == 0) { tracef0("txt1_initt failed, no store.\n"); return FALSE; }; t->buf[t->top+1] = GUARDBYTE; t->gapstart = 0; t->gapend = t->top; t->txtstart = 0; t->buf[t->gapend] = 0; /* termch */ /* use of donewmarker no good here because of invariant checks. */ t->selstart.pos = t->gapend; t->selstart.next = NULL; t->selend.pos = t->gapend; t->selend.next = &t->selstart; /* t->w->firstvis.pos = t->gapend; t->w->firstvis.next = &t->selend; t->w->lastvis.pos = t->gapend; t->w->lastvis.next = &t->w->firstvis; t->marksbefore = NULL; t->marksafter = &t->w->lastvis; t->w->firstvisy = 1; t->w->lastvisy = 1; */ t->marksbefore = NULL; t->marksafter = &t->selend; t->calls = NULL; t->last_ref = 0; txt1__checkbufinvariant(t, "itb"); /*>*/ return TRUE; } /* >>>> change things so that this routine cannot fail, e.g. doesn't call flex. First buffer should be non-existent, or tiny at end of text record. */ void txt1_disposetextbuffer(txt t) { flex_free((void**) &t->buf); } static BOOL txt1__extendtextbuffer(txt t, txt1_bufindex newtop) /* Based on a model whereby you don't have both available at the same time, in case the underlying storage mechanisms improve... */ { txt1_imarker *m; if (newtop <= t->top) return TRUE; /* extendstorearea(t->buf, (t->top+2)*8, (newtop+2)*8); */ if (! flex_extend((void**) &t->buf, newtop + 2)) { tracef0("txt1_extendtextbuffer: flex_extend fails.\n"); return FALSE; }; /* >>>> running-out-of-store case ignored. */ if (t->gapstart <= t->gapend) {/* gap contiguous */ memmove( /*to*/ &(t->buf[newtop - (t->top - t->gapend)]), /*from*/ &(t->buf[t->gapend]), 1 + t->top - t->gapend); m = t->marksafter; while (m != NULL) { m->pos += newtop - t->top; m = m->next; }; if (t->txtstart > t->gapend) t->txtstart += newtop - t->top; t->gapend += newtop - t->top; } else { /* gap discontiguous */ /* no need to move things */ }; t->top = newtop; t->buf[t->top+1] = GUARDBYTE; txt1__checkbufinvariant(t, "etb"); tracef0("buffer extended."); #if TRACE txt1__pr(t); #endif return TRUE; } /* >>>> if (we run out of store spuriously, what happens? this could happen in any "insert" operation. hum. */ static int txt1__spaceavailable(txt t) /* size of gap */ { if (t->gapend >= t->gapstart) { return t->gapend - t->gapstart; } else { return 1 + t->top - (t->gapstart - t->gapend); }; } #if FALSE /* Never tried, and not useful. */ void txt1__deletefromfront(txt t, int n) /* Delete characters from the front of the array, adding the resulting space to the gap. The most important case is where the gap is right at the end, in tty usage. */ { txt1_bufindex base; txt1_bufindex segtop; int segsize; txt_index place; txt1_imarker *m; if (0 != (txt_DISPLAY & t->charoptionset)) { /* if (any deleted characters are visible then we take a special route. */ base = txt1__firstchbi(t); txt1__incbi(t, &base, n); if (txt1__lessoreqbi(t, t->w->firstvis.pos, base)) { place = txt1__bufindextoindex(t, t->gapend); if (place < n) {place = 0;} else {place -= n;}; /* these routines will update firstvis.pos as appropriate */ txt1_dosetdot(t, 0); txt1_dodelete(t, n); txt1_dosetdot(t, place); return; }; }; /* normal case, chars to be deleted are not visible. */ while (n > 0) { base = 0; if (t->gapend < t->txtstart) base = t->gapend; /* you won't meet t->gapstart below t->txtstart and above t->gapend, because t->txtstart is not in the gap. */ /* using segsize from t->txtstart is tricky 'cos it might equal t->gapstart. */ segtop = 1 + t->top; if (t->gapstart >= t->txtstart) segtop = t->gapstart; segsize = segtop - t->txtstart; if (segsize > n) segsize = n; if (segsize > 0) {/* i.e. gap not at t->txtstart */ memmove( /*to*/ &(t->buf[base+segsize]), /*from*/ &(t->buf[base]), t->txtstart-base); /* markers in the first segsize places must move up */ m = t->marksbefore; while (m != NULL) { if ((m->pos >= t->txtstart) && (m->pos < t->txtstart+segsize)) { m->pos += segsize; if (m->pos == t->top+1) m->pos = 0; }; m = m->next; }; txt1__incrawbi(t, &t->txtstart, segsize); /* could reach top+1 */ n -= segsize; if (base == t->gapend) { t->gapend += segsize; } else { /* the block move wraps round at the base of the buffer. */ if (t->gapend < (1+t->top-segsize)) { /* t->gapend is not going to wrap round */ memmove( /*to*/ &(t->buf[0]), /*from*/ &(t->buf[1+t->top-segsize]), segsize); memmove( /*to*/ &(t->buf[t->gapend+segsize]), /*from*/ &(t->buf[t->gapend]), (1+t->top-t->gapend)-segsize); t->gapend += segsize; } else { /* t->gapend is going to wrap from top to bottom */ memmove( /*to*/ &(t->buf[segsize - (1+t->top-t->gapend)]), /*from*/ &(t->buf[t->gapend]), 1+t->top-t->gapend); t->gapend = segsize - (1+t->top-t->gapend); }; }; if (t->txtstart == t->top+1) t->txtstart = 0; /* markers after the gap must move up */ m = t->marksafter; while (m != NULL) { txt1__incrawbi(t, &m->pos, segsize); m = m->next; }; txt1__updatescrollbar(t); txt1__checkbufinvariant(t, "dff"); } else { /* the gap is at the start of the array. */ txt1_dodelete(t, n); n = 0; }; }; } #endif static int txt1__ensurespace(txt t, int space) /* Ensure that the gap leaves at least the specified amount of space, extending if necessary. This may in fact not be possible, e.g. extension not allowed and space required physically bigger than buffer. So, the return value is equal to "space" if there's enough, else the amount of space there actually is. */ { int gapsize; txt1_bufindex newtop; gapsize = txt1__spaceavailable(t); if (gapsize >= space) { return space; } else { if (t->oaction == txt_REFUSE) { /* take no action */ #if FALSE } else if (t->oaction == txt_CYCLE) { werr(TRUE, "Fatal: t1 cycle"); txt1__deletefromfront(t, space-gapsize); #endif } else { /* oaction == extend */ if (t->top > 40000) /* increase by 20 percent, but cap at 8000 */ newtop = t->top + 8000; else newtop = (t->top * 5) / 4; if (newtop < MINEXTENDEDTOP) newtop = MINEXTENDEDTOP; if (space > gapsize + (newtop - t->top)) { newtop = t->top + space - gapsize; /* >>>>? space - (t->top - gapsize); */ if (newtop > 40000) /* increase by 20 percent, but cap at 8000 */ newtop += 8000; else newtop = (newtop * 5) / 4; }; txt1__extendtextbuffer(t, newtop); }; return txt1__spaceavailable(t); }; } /* -------- Option setting. -------- */ #ifndef UROM int txt1_dobufsize(txt t) { if (t->buf == 0) {return 0;} else {return t->top + 1;}; } #endif #ifndef UROM BOOL txt1_dosetbufsize(txt t, int b) /* returns FALSE if can't. */ { return txt1__extendtextbuffer(t, b); } #endif /* >>>> not extensively used or tested! */ /* >>>> do error handling right! */ #ifndef UROM txt_overflowaction txt1_dooaction(txt t) { return t->oaction; } #endif #ifndef UROM void txt1_dosetoaction(txt t, txt_overflowaction o) { t->oaction = o; } #endif void txt1_dosetcharoptions(txt t, txt_charoption affect, txt_charoption values) { txt_charoption prev; prev = t->charoptionset; t->charoptionset = (t->charoptionset & ~affect) | (affect & values); if ((affect & values) & txt_UPDATED) t->last_ref = 0; /* buffer modified, so forget Message_DataSaved */ if (0 != (txt_CARET & (prev ^ t->charoptionset))) { if (0 != (txt_CARET & t->charoptionset)) { t->w->rawshowcaret(t); } else { t->w->rawhidecaret(t); }; }; while (txt3_foreachwindow(t)) { t->w->donewcharoptions(t, prev); if (0 != (txt_DISPLAY & t->charoptionset) && 0 == (txt_DISPLAY & prev)) { txt1_redisplay(t, 1); }; }; } void txt1_dosetdisplayok(txt t) { /* We assume that the display is up to date. This is useful for swapping windows, for temporarily moving to the top/bot of the file to ensure that it's all one segment, etc. */ t->charoptionset |= txt_DISPLAY; } /* -------- Iterators over segments of the array. -------- */ static BOOL txt1__segback( txt t, txt1_bufindex *start /*inout*/, int *n /*out*/) /* "*start" is a charbufindex. If it's at the first char in the array then leave *start,*n unchanged and return FALSE. otherwise, return TRUE and set "*start" and "*n" with the biggest n such that segsize(*start)==*n, and incbi(start, n) would get you back to the initial start. */ { txt1_bufindex newstart; txt1__assertcharbufindex(t, *start, "sb-1"); if (*start==t->txtstart || (*start==t->gapend && t->txtstart==t->gapstart)) { /* at start of array */ return FALSE; } else { if (*start == t->gapend) *start = t->gapstart; if (*start == 0) *start = t->top+1; /* below start we could meet t->gapend or t->txtstart or 0 */ newstart = 0; if (t->gapend < *start) newstart = t->gapend; if ((t->txtstart < *start) && (t->txtstart > newstart)) { newstart = t->txtstart; }; /* newstart < start */ *n = *start - newstart; *start = newstart; txt1__assertcharbufindex(t, *start, "sb-2"); return TRUE; }; } static BOOL txt1__segsize(txt t, txt1_bufindex start, int *n /*out*/) /* "start" is a charbufindex. segsize returns the number of characters at and beyond "start" in the buffer that are bona fide characters from the array. Returns FALSE if "start" is at the end of the array. if it returns TRUE then n>0. the resulting segment does not include termch. */ { txt1_bufindex seglim; txt1__assertcharbufindex(t, start, "sss"); /* we could meet t->gapstart, or t->txtstart, or t->top. */ if (start == txt1__termchbi(t)) { txt1__assert(t->buf[start] == 0, "sss-1"); *n = 0; return FALSE; } else { seglim = t->top + 1; /* one beyond segment */ if (t->txtstart == 0) {seglim = t->top;}; /* t->buf[t->top]==termch */ if (t->gapstart > start) seglim = t->gapstart; if (t->txtstart > start && t->txtstart <= seglim) { seglim = t->txtstart-1; /* point at termch */ }; *n = seglim - start; return TRUE; }; } void txt1_doarraysegment( /* public */ txt t, txt_index at, char **a /*out*/, int *n /*out*/) { txt1_bufindex b; b = txt1__indextobufindex(t, at); if (! txt1__segsize(t, b, n)) *n = 0; *a = &t->buf[b]; } /* -------- Index and Bufindex arithmetic. -------- */ txt_index txt1__bufindextoindex(txt t, txt1_bufindex b) /* the bufindex must be a charbufindex. */ { txt_index i; int segsize; /* last thing before b could be t->gapend, 0, t->txtstart */ txt1__assertcharbufindex(t, b, "biti-1"); i = 0; while (txt1__segback(t, &b, &segsize)) i += segsize; return i; } txt1_bufindex txt1__indextobufindex(txt t, txt_index i) { txt1_bufindex at; int n; /* huge index possible (e.g. 0x7fffffff), seems ok. */ /* start at bufstart, you could meet t->gapstart or t->top or i */ at = txt1__firstchbi(t); while (txt1__segsize(t, at, &n)) { if (n > i) {/* got there */ return at + i; } else { /* still going */ i -= n; txt1__incbi(t, &at, n); }; }; /* got to the end of the array, didn't reach i. thus, return the maximum possible bufindex. */ return at; txt1__checkbufinvariant(t, "itbi"); } void txt1__incrawbi(txt t, txt1_bufindex *bi /*inout*/, int n) /* Increment a bufindex, wrapping round at top but ignoring the gap. */ { *bi += n; if (*bi > t->top) *bi -= t->top+1; /* The bufinvariants to not necessarily hold at this point. e.g. consider use within domovedot, dodelete. */ } static void txt1__decrawbi(txt t, txt1_bufindex *bi /*inout*/, int n) /* decrement bufindex, wrapping round at the bottom but ignoring the gap. */ { if (*bi < n) bi += t->top+1; *bi -= n; /* the bufinvariants to not necessarily hold at this point. e.g. consider use within domovedot, dodelete. */ } void txt1__incbi(txt t, txt1_bufindex *i /*inout*/, int n) /* i is a charbufindex. increment it, keeping things that way. If you hit the end of the array, stick there. */ { int segsize; txt1__assertcharbufindex(t, *i, "inbi-1"); while ((n!=0) && txt1__segsize(t, *i, &segsize)) { if (n < segsize) { *i += n; txt1__assertcharbufindex(t, *i, "inbi-2"); return; } else { /* more complex */ *i += segsize; /* no longer bona fide */ n -= segsize; if (*i > t->top) *i = 0; if (*i == t->txtstart) {/* we just overflowed! */ *i = txt1__termchbi(t); n = 0; }; if (*i == t->gapstart) *i = t->gapend; }; }; txt1__assertcharbufindex(t, *i, "inbi-3"); } static void txt1__decbi(txt t, txt1_bufindex *i /*inout*/, int n) { txt1_bufindex segstart; int segsize; while (1) { if (n == 0) break; segstart = *i; if (! txt1__segback(t, &segstart, &segsize)) { /* i is correct now */ break; }; if (segsize >= n) { *i = segstart + segsize - n; break; } else { n -= segsize; *i = segstart; /* and loop */ }; }; } txt1_bufindex txt1__termchbi(txt t) /* returns the bufindex of termch. */ { if (t->txtstart == 0) {return t->top;} else {return t->txtstart-1;}; } txt1_bufindex txt1__firstchbi(txt t) /* returns the bufindex of the first char in the array. */ { if (t->gapstart == t->txtstart) { return t->gapend; } else { return t->txtstart; }; } static BOOL txt1__lessthanbi(txt t, txt1_bufindex a, txt1_bufindex b) { if ((a < t->txtstart) == (b < t->txtstart)) { return a < b; } else { return b < a; }; } BOOL txt1__lessoreqbi(txt t, txt1_bufindex a, txt1_bufindex b) { if (a == b) { return TRUE; } else if ((a < t->txtstart) == (b < t->txtstart)) { return a < b; } else { return b < a; }; } #if FALSE /* not used! */ static txt1_bufindex txt1__maxbi(txt t, txt1_bufindex a, txt1_bufindex b) { if (txt1__lessthanbi(t, a, b)) {return b;} else {return a;}; } #endif /* -------- Public operations on the array of characters. -------- */ txt_index txt1_dodot(txt t) { int result; result = t->gapstart - t->txtstart; if (result < 0) result += t->top; return result; } txt_index txt1_dosize(txt t) { if (t->gapend >= t->gapstart) {/* gap contiguous or null */ return t->top - (t->gapend - t->gapstart); } else { return (t->gapstart - t->gapend) - 1; }; } void txt1_dosetdot(txt t, txt_index to) { if (to > 100000000) {/* problem with range conversion! */ /* e.g. use of 0x7fffffff to get to end */ to = 100000000; }; txt1_domovedot(t, to - txt1__bufindextoindex(t, t->gapend)); } void txt1_domovedot(txt t, int by) /* movedot involves (potentially) moving the gap in the buffer. It will not affect the display, except (a) the caret could move, and (b) if we're displaying and the result is off the display area then we must redisplay/scroll. */ { int saveby; int n; int tomove; txt1_bufindex from; txt1_imarker *marker; tracef1("domovedot %i.\n", by); txt1__assert(t->w == t->windows[1], "md-3"); /* If by==0, or other values which still cause nothing to happen (because of begin/end of array) we must still wiggle the caret, because of caretoffset. */ /* save undo information */ txtundo_putnumber(t->undostate, txt1__bufindextoindex(t, t->gapend)); txtundo_putcode(t->undostate, 'm'); /* update the text buffer, by moving the gap. */ saveby = 0; /* dist actually moved, for display */ if (by < 0) { /* we must move backwards to get to "to" */ /* this means copying chars up, so that the gap moves down */ from = t->gapend; tomove = -by; while ((tomove>0) && txt1__segback(t, &from, &n)) { if (n > tomove) { from += n-tomove; n = tomove; }; if (t->gapend == 0) {t->gapend = t->top+1;}; if (t->gapend < t->gapstart) {/* gap is discontiguous */ if (n > t->gapend) { /* we can't move all that at once */ from += n - t->gapend; n = t->gapend; }; }; if ((t->gapstart == 0) && (t->gapend > 0) && (n > t->gapend)) { /* the move jumps over the end, so gapsize is the maximum that we can move */ /* >>>> rather a kludge, think through this. */ /* compare with deletefromfront, for instance. */ n = t->gapend; }; txt1__decrawbi(t, &t->gapstart, n); txt1__decrawbi(t, &t->gapend, n); /* will ensure t->gapend is normalised */ memmove( /*to*/ &(t->buf[t->gapend]), /*from*/ &(t->buf[t->gapstart]), n); tomove -= n; saveby -= n; from = t->gapend; while (/* move markers */ (t->marksbefore != NULL) && (t->marksbefore->pos >= t->gapstart) && (t->marksbefore->pos < t->gapstart+n) ) { /* move the marker from the before list to the after list */ marker = t->marksbefore; t->marksbefore = marker->next; marker->next = t->marksafter; t->marksafter = marker; marker->pos -= t->gapstart; /* still +ve */ marker->pos += t->gapend; }; txt1__checkbufinvariant(t, "md-1"); }; } else { /* by >= 0 */ /* we must move forwards to get to "to" */ /* this means copying chars) {wn, so that the gap moves up */ tomove = by; while (tomove>0 && txt1__segsize(t, t->gapend, &n)) { /* contiguous chunk at t->gapend, of n bytes */ if (tomove < n) n = tomove; if (t->gapend < t->gapstart) { /* gap is discontiguous */ /* >>>> && t->gapend!=0 extra exclusion, surely spurious? */ if (n > (1+t->top-t->gapstart)) {/* can't fit that many */ n = 1+t->top-t->gapstart; }; }; while (/* move markers */ (t->marksafter != NULL) && (t->marksafter->pos >= t->gapend) && (t->marksafter->pos < t->gapend + n) ) { /* move the marker from the after list to the before list */ marker = t->marksafter; t->marksafter = marker->next; marker->next = t->marksbefore; t->marksbefore = marker; marker->pos -= t->gapend; /* still +ve */ marker->pos += t->gapstart; }; memmove( /*to*/ &(t->buf[t->gapstart]), /*from*/ &(t->buf[t->gapend]), n); txt1__incrawbi(t, &t->gapstart, n); txt1__incrawbi(t, &t->gapend, n); tomove -= n; saveby += n; txt1__checkbufinvariant(t, "md-2"); }; }; txt1_domovemarker(t, &(t->w->caret), txt1_dodot(t)); txt1__ensuredotvisible(t, saveby); txt1__checkbufinvariant(t, "dmd"); } void txt1_doinsertchar(txt t, char c) { txt1_doreplacechars(t, 0, &c, 1); } void txt1_doinsertstring(txt t, char *s) { txt1_doreplacechars(t, 0, s, strlen(s)); } static void txt1__dodeletebufferstuff(txt t, int n) /* Deletion causes the gap to get bigger. */ { txt1_imarker *m; txt1_bufindex oldgapend; int delsize; int segsize; /* For undoing the selection: if either endpoint of the selection is within the deleted portion, then we need to save the exact position of the selection in the undo buffer. */ { txt_index selstart = txt1__bufindextoindex(t, t->selstart.pos); txt_index selend = txt1__bufindextoindex(t, t->selend.pos); txt_index dot = txt1_dodot(t); if (selstart != selend) { /* only important if selection set. */ if ((selstart >= dot && selstart <= dot+n) || (selend >= dot && selend <= dot+n)) { txtundo_putnumber(t->undostate, selstart); txtundo_putnumber(t->undostate, selend); txtundo_putcode(t->undostate, 'l'); }; }; }; /* updates to the text buffer */ while ((n>0) && txt1__segsize(t, t->gapend, &segsize)) { delsize = txt1__min(n, segsize); oldgapend = t->gapend; txtundo_putbytes(t->undostate, &(t->buf[t->gapend]), delsize); txtundo_putnumber(t->undostate, delsize); txtundo_putcode(t->undostate, 'i'); txt1__incrawbi(t, &t->gapend, delsize); n -= delsize; m = t->marksafter; while (/* deal with markers */ (m != NULL) && (m->pos >= oldgapend) && (m->pos < oldgapend + delsize) ) { m->pos = t->gapend; m = m->next; }; }; txt1__checkbufinvariant(t, "ddbs"); } void txt1_doreplacechars(txt t, int ntodelete, char *a, int n) /* Insertion of characters in the buffer causes the gap to reduce in size, and can lead to extension being necessary. Display changes are generated directly, to reduce redraw and flicker. */ /* KJB Oct 2002: Can now pass a null pointer to fill with spaces */ { txt1_bufindex gapsegbase; int gapsegsize; int n1; int saven; #if TRACE BOOL dump; #endif char *s; int nspaces; char *spaces; txt1_bufindex b; txt1_imarker *m; txt1__assert(t->w == t->windows[1], "drc-1"); if ((ntodelete == 0) && (n == 0)) { if ((t->w->caretoffsetx != 0) || (t->w->caretoffsety != 0)) { txt1__hidecaret(t); t->w->caretoffsetx = 0; t->w->caretoffsety = 0; txt1__showcaret(t); }; return; }; if (t->w->caretoffsetx > 0 && (t->gapend == txt1__termchbi(t) || t->buf[t->gapend] == '\n') && 0 != (txt_DISPLAY & t->charoptionset) ) { /* he is sticking out at the end of a line. */ ntodelete = 0; /* deletion is ignored in this case */ if (n == 0) return; /* teeny visual bug: should show caret */ nspaces = t->w->caretoffsetx / txt1__spacewidth(t); tracef1("pad insertion with %i spaces.\n", nspaces); t->w->caretoffsetx = 0; t->w->caretoffsety = 0; spaces = " "; /* 40 of them */ #if FALSE for (n1 = 1; nspaces > 0 /* n1 <= (nspaces + 39) / 40 */; n1++) { #else while (nspaces > 0) { #endif txt1_doreplacechars(t, 0, spaces, txt1__min(40, nspaces)); txt1_domovedot(t, txt1__min(40, nspaces)); nspaces -= 40; }; } else { t->w->caretoffsetx = 0; /* t->w->caretoffsety is left as it is for now. This is picked up by displayreplace2, in order to catch the case where you insert/delete at the front of a split line. */ }; #if TRACE /* debugging printout if inserting a lone ^q */ s = a; dump = (n==1) && (s!=NULL) && (*s==17); if (dump) {txt1__pr(t); txtundo_pr(t->undostate); return;}; #endif /* the changes to the text buffer */ t->charoptionset |= txt_UPDATED; t->last_ref = 0; /* ignore subsequent DataSaved message */ /* Strip identical chars from end of insertion and deletion. Some of the displayreplace stuff assumes that both do not end in newlines. */ if (ntodelete > 0) { b = t->gapend; txt1__incbi(t, &b, ntodelete-1); /* points at last char to be deleted */ s = a; while (1) { if (ntodelete == 0) break; if (n == 0) break; if (t->buf[b] != (s==NULL) ? ' ' : s[n-1]) break; /* last inserted == last deleted: so, don't touch either. */ txt1__decbi(t, &b, 1); n--; ntodelete--; }; }; /* measure the stuff to be deleted */ while (txt3_foreachwindow(t)) { txt1__displayreplace1(t, ntodelete); }; /* and delete it */ txt1__dodeletebufferstuff(t, ntodelete); n1 = txt1__ensurespace(t, n); if (n1 < n) { /* there's not enough space. drop the first few chars, rather than the last few. */ werr(FALSE, msgs_lookup(MSGS_txt48)); if (a) a += n-n1; n = n1; }; saven = n; /* insert the new chars into the text buffer. */ while (n > 0) { if (t->gapend == 0) { t->gapend = t->top+1; m = t->marksafter; while ((m != NULL) && (m->pos == 0)) { m->pos = t->top+1; m = m->next; }; }; gapsegbase = 0; if (t->gapstart < t->gapend) gapsegbase = t->gapstart; gapsegsize = t->gapend - gapsegbase; if (gapsegsize > n) { gapsegsize = n; }; m = t->marksafter; while ((m != NULL) && (m->pos == t->gapend)) { m->pos -= gapsegsize; m = m->next; }; t->gapend -= gapsegsize; n -= gapsegsize; if (a) memmove(&(t->buf[t->gapend]), a, gapsegsize); else memset(&(t->buf[t->gapend]), ' ', gapsegsize); /* KJB - surely a should be incremented here? */ txtundo_putnumber(t->undostate, gapsegsize); txtundo_putcode(t->undostate, 'd'); }; /* the changes to the display */ while (txt3_foreachwindow(t)) { if (t->w->lastvis.pos == t->gapend) { txt1_domovemarker( t, &(t->w->lastvis), txt1__bufindextoindex(t, t->gapend) + saven); }; txt1__displayreplace2(t, saven, ntodelete); }; /* The lastvis adjustment above is definitely what is required for insertion at the end of the file. but, it's not correct in other curcumstances where the replace intersects lastvis. clearly, this could result in lastvis being anywhere within the replaced section. in such a case displayreplace2 ends up calling displayfrom, and all is well. */ txt1__checkbufinvariant(t, "drc"); #if TRACE if (dump) txt1__pr(t); #endif } void txt1_dodelete(txt t, int n) { txt1_doreplacechars(t, n, NULL, 0); } char txt1_docharatdot(txt t) { return t->buf[t->gapend]; } char txt1_docharat(txt t, txt_index i) { return t->buf[txt1__indextobufindex(t, i)]; } #ifndef UROM void txt1_docharsatdot( txt t, char *a, int *n /* inout */) { txt1_bufindex from; int at; int todo; int segsize; int copysize; from = t->gapend; at = 0; /* index into a */ todo = *n; while ((todo>0) && txt1__segsize(t, from, &segsize)) { copysize = txt1__min(segsize, todo); memmove(/*to*/ a, /*from*/ &(t->buf[from]), copysize); todo -= copysize; a += copysize; txt1__incbi(t, &from, segsize); }; n -= todo; txt1__checkbufinvariant(t, "dcsa"); } #endif void txt1_doreplaceatend(txt t, int ntodelete, char *buffer, int n) { BOOL simple =(t->charoptionset && txt_DISPLAY) == 0; /* Not even displayed is the simplest case of all. */ txt_index dot = txt1_dodot(t); if (ntodelete > txt1_dosize(t)) { ntodelete = txt1_dosize(t); }; if (! simple) { while (txt3_foreachwindow(t)) { if (t->w->lastvis.pos == txt1__termchbi(t)) { simple = TRUE; }; }; }; /* We take the !simple route only if displaying, but no window is displaying the tail of the file. */ if (! simple) t->charoptionset &= ~txt_DISPLAY; txt1_dosetdot(t, txt1_dosize(t) - ntodelete); txt1_doreplacechars(t, ntodelete, buffer, n); txt1_dosetdot(t, dot); if (! simple) { t->charoptionset |= txt_DISPLAY; /* We assert that the display is already correct. */ while (txt3_foreachwindow(t)) { txt1__updatescrollbar(t); }; }; } void txt1_domovevertical(txt t, int by, BOOL caretstill) /* >>>> this is pretty rhunic, the stuff about caretoffset is a little out of control. t->w->caretoffsety can only be 0 or t->w->linesep, the latter case for when you're at a line break, and wish to display the cursor at the start of the succuessor line rather than the end of the previous one. */ { txt1_zits x; txt1_zits y; txt1_zits y1; txt1_zits keepcaretx; txt1_bufindex at; BOOL caret; tracef2("domovevertical %i %i.\n", by, caretstill); if (caretstill) {txt1_scrollbarmoveby(t, by); return;}; x = t->w->caretx; y = t->w->carety; if (t->w->caretoffsety != 0) { x += t->w->caretoffsetx; y += t->w->caretoffsety; }; keepcaretx = t->w->caretx + t->w->caretoffsetx; at = t->gapend; y1 = y + by * t->w->linesep; if (by == 0) return; caret = 0 != (txt_CARET & t->charoptionset); txt1__hidecaret(t); t->charoptionset &= ~txt_CARET; if (by > 0) { txt1__measure(t, &x, &y, y1, &at, txt1__termchbi(t), FALSE); } else { if (! txt1__measureback(t, &x, &y, y1, &at, txt1__firstchbi(t), FALSE)) { tracef0("first measureback failed.\n"); y -= t->w->caretoffsety; (void) txt1__measureback(t, &x, &y, y1, &at, txt1__firstchbi(t), FALSE); }; }; txt1__cancelallcalls(t); /* should be superfluous! */ txt1__horizmeasure(t, x, t->w->caretx + t->w->caretoffsetx, &at); if ( 0 == (txt_DISPLAY & t->charoptionset) || (abs(by) < (3 * t->w->limy) / (4 * t->w->linesep))) { /* could end up doing on-screen block copies */ tracef0("movevertical is near.\n"); txt1_dosetdot(t, txt1__bufindextoindex(t, at)); } else { /* Moving by almost a page, rather force a redraw and keep cursor in the same place. */ tracef0("movevertical is far.\n"); t->charoptionset &= ~txt_DISPLAY; txt1_dosetdot(t, txt1__bufindextoindex(t, at)); t->charoptionset |= txt_DISPLAY; txt1__redisplaytext(t, TRUE); }; if ((keepcaretx == 0) && txt1__dotatlinebreak(t) ) { /* He has landed at a linebreak, and placed himself at the end of the previous line. x will stay right (at the front of a line) but we must advance y. */ t->w->caretoffsety = t->w->linesep; } else { t->w->caretoffsety = 0; }; t->w->caretoffsetx = keepcaretx - t->w->caretx; tracef4("caretx/y=%i,%i caretoffsetx=%i,y=%i.\n", t->w->caretx, t->w->carety, t->w->caretoffsetx, t->w->caretoffsety); if (caret) t->charoptionset |= txt_CARET; txt1__showcaret(t); txt1__checkbufinvariant(t, "dmv"); } static txt1_zits txt1__measurechar(txt t, char c) { char ar[4]; char *a; int n; txt1_zits width; ar[0] = c; a = &(ar[0]); n = 1; width = INT_MAX; t->w->rawmeasure(t, &a, &n, &width); tracef1("charwidth = %i.\n", INT_MAX - width); return INT_MAX - width; } BOOL txt1__dotatlinebreak(txt t) /* TRUE if caret is at the break of two display lines in the same text line. */ { return (t->buf[t->gapend] != '\n') && (t->gapend != txt1__termchbi(t)) && (txt1__measurechar(t, t->buf[t->gapend]) > t->w->limx - t->w->caretx); } txt1_zits txt1__spacewidth(txt t) { return txt1__measurechar(t, ' '); } void txt1_domovehorizontal(txt t, int by) { int i; BOOL caret; txt1_zits spacewidth; txt1__hidecaret(t); caret = 0 != (txt_CARET & t->charoptionset); t->charoptionset &= ~txt_CARET; spacewidth = txt1__spacewidth(t); for (i = 1; i <= abs(by); i++) { if (((t->w->caretoffsetx > 0) || ((t->w->caretoffsetx == 0) && (by > 0))) && ((t->gapend == txt1__termchbi(t)) || (t->buf[t->gapend] == '\n')) && (t->w->caretoffsetx + t->w->caretx + by*spacewidth <= t->w->limx) ) { /* he is sticking out at the end of a line. */ tracef0("horiz movement.\n"); #if TRUE t->w->caretoffsetx = txt1__max(0, t->w->caretoffsetx + by*spacewidth); by = 0; /* prevent further looping. */ #else /* bug found 20-Jan-89, prevented by>1 from working. */ t->w->caretoffsetx += (by > 0 ? spacewidth : -spacewidth); if (t->w->caretoffsetx < 0) t->w->caretoffsetx = 0; #endif } else { tracef0("no horiz movement.\n"); /* domovedot, with one funny case. */ if (by > 0) { txt1_domovedot(t, 1); } else { txt1_domovedot(t, -1); if (txt1__dotatlinebreak(t)) { /* retreat to line break: be at start of following line */ t->w->caretoffsetx = - t->w->caretx; t->w->caretoffsety = t->w->linesep; }; }; }; }; if (caret) { t->charoptionset |= txt_CARET; }; txt1__showcaret(t); } /* >>>> Slightly funny on the last line of the file. Some way of making nothing happen if you're beyond the end of the file? or insert '\n's? Hum, seems a lot of work to do it all properly. */ /* -------- Line operations. -------- */ static txt1_bufindex txt1__startofline(txt t, txt1_bufindex containing) { txt1_bufindex from; txt1_bufindex i; int n; while (1) { from = containing; if (txt1__segback(t, &from, &n)) { if (t->buf[from+n-1] == '\n') {return containing;} else { for (i=n; i>=2; i--) { if (t->buf[from+i-2] == '\n') {return from+i-1;}; }; containing = from; }; } else { return from; }; }; } static txt1_bufindex txt1__startofreasonableline(txt t, txt1_bufindex containing) /* An attempt to prevent hassles about very long lines causing long delays. after maxreasonableline we give up, and stop at the next multiple of maxreasonableline within the file. this will give a constant stopping point, e.g. if we move around a lot. */ /* Truely correct implementation is the same as startofline. */ { txt1_bufindex from; txt1_bufindex i; txt1_bufindex cutoff; int n; int count; txt_index index; #define MAXREASONABLELINE 1024 count = 0; cutoff = txt1__firstchbi(t); while (1) { from = containing; if (txt1__segback(t, &from, &n)) { if (t->buf[from+n-1] == '\n') {return containing;} else { for (i=n; i>=2; i--) { if (t->buf[from+i-2] == '\n') {return from+i-1;}; if (from+i-1 == cutoff) { tracef0("startofreasonableline gave up.\n"); return from+i-1; }; count++; if (count > MAXREASONABLELINE) { /* get impatient */ index = txt1__bufindextoindex(t, from+i-2); /* where we are now */ index -= index % MAXREASONABLELINE; cutoff = txt1__indextobufindex(t, index); }; }; containing = from; }; } else { return from; }; }; } static txt1_bufindex txt1__nextlineend(txt t, txt1_bufindex containing) /* if pointing at a '\n', returns with the same value. Has to be quite fast, so the critical part of the loop has no procedure calls. */ { int n; txt1__assertcharbufindex(t, containing, "nle-1"); if (! txt1__segsize(t, containing, &n)) return containing; while (1) { if (t->buf[containing] == '\n') return containing; if (n != 1) { containing++; n--; } else { txt1__incbi(t, &containing, 1); if (! txt1__segsize(t, containing, &n)) return containing; }; }; } /* -------- Operations on markers. -------- */ void txt1_donewmarker(txt t, txt1_imarker *addr) { addr->next = t->marksafter; addr->pos = t->gapend; t->marksafter = addr; txt1__checkbufinvariant(t, "dnm"); } #if TRACE void txt1__badmarker(char *s) { txt1__yell("bad marker: ", s); } #endif void txt1_domovemarker(txt t, txt1_imarker *addr, txt_index to) { txt1__movemarker(t, addr, txt1__indextobufindex(t, to)); } void txt1_domovedottomarker(txt t, txt1_imarker *addr) { #if TRACE if (addr->pos > t->top) {/* simple consistency check */ txt1__badmarker("movedottomarker"); }; #endif txt1_dosetdot(t, txt1__bufindextoindex(t, addr->pos)); txt1__checkbufinvariant(t, "ddtm"); } txt_index txt1_doindexofmarker(txt t, txt1_imarker *addr) { #if TRACE if (addr->pos > t->top) txt1__badmarker("indexofmarker"); #endif return txt1__bufindextoindex(t, addr->pos); } void txt1_dodisposemarker(txt t, txt1_imarker *addr) /* need to search down the marker chains to find this guy. */ { txt1__deletemarker(t, addr); addr->next = NULL; addr->pos = INT_MAX; txt1__checkbufinvariant(t, "ddm"); } static txt1_imarker **txt1__findmarker(txt t, txt1_imarker *m, txt1_imarker **p) { t=t; while (1) { #if TRACE if (*p == NULL) { /* the marker isn't there: probably a client marker, moving/deleting a marker that isn't there. */ txt1__badmarker("move/deletemarker"); }; #endif if (*p == m) break; p = &((*p)->next); }; return p; } void txt1__deletemarker(txt t, txt1_imarker *m) { txt1_imarker **deleteat; /* decide where to delete */ if (txt1__lessoreqbi(t, t->gapend, m->pos)) { deleteat = txt1__findmarker(t, m, &(t->marksafter)); } else { deleteat = txt1__findmarker(t, m, &(t->marksbefore)); }; txt1__assert(*deleteat == m, "dm-1"); *deleteat = m->next; /* perform the deletion */ txt1__checkbufinvariant(t, "dm-2"); } static txt1_imarker **txt1__findplacebefore(txt t, txt1_bufindex b, txt1_imarker **p) /* find the right place to insert a marker that wishes to be at i. */ { while (1) { if (*p == NULL) break; if (txt1__lessoreqbi(t, (*p)->pos, b)) break; p = &((*p)->next); }; return p; } static txt1_imarker **txt1__findplaceafter(txt t, txt1_bufindex b, txt1_imarker **p) /* find the right place to insert a marker that wishes to be at b. */ { while (1) { if (*p == NULL) break; if (txt1__lessoreqbi(t, b, (*p)->pos)) break; p = &((*p)->next); }; return p; } static void txt1__insertmarker(txt t, txt1_imarker *m) { txt1_imarker **insertat; /* decide where to insert */ if (txt1__lessoreqbi(t, t->gapend, m->pos)) { insertat = txt1__findplaceafter(t, m->pos, &(t->marksafter)); } else { insertat = txt1__findplacebefore(t, m->pos, &(t->marksbefore)); }; m->next = *insertat; /* perform the insertion */ *insertat = m; txt1__checkbufinvariant(t, "im-1"); } void txt1__movemarker(txt t, txt1_imarker *m, txt1_bufindex newpos) /* Move the marker from its current position, to the indicated new one. This may involve moving it around in the chains of markers, as appropriate. This is quite slow, but is necessary for keeping track of selection and scroll bar stuff. */ { if (m->pos == newpos) return; /* shortcut */ tracef2("movemarker from=%i to=%i\n", m->pos, newpos); txt1__deletemarker(t, m); /* cut out from the lists */ m->pos = newpos; /* update the position */ txt1__insertmarker(t, m); /* put back into the lists */ txt1__checkbufinvariant(t, "mm-1"); } /* -------- Operations on the selection. -------- */ BOOL txt1_doselectset(txt t) { return t->selstart.pos != t->selend.pos; } txt_index txt1_doselectstart(txt t) { return txt1__bufindextoindex(t, t->selstart.pos); } txt_index txt1_doselectend(txt t) { return txt1__bufindextoindex(t, t->selend.pos); } void txt1__redisplayselectrange(txt t, txt1_bufindex from, txt1_bufindex to); /* forward reference */ static void txt1__order(txt_index *a, txt_index *b) { txt_index temp; if (*a > *b) { temp = *a; *a = *b; *b = temp; }; } void txt1_dosetselect(txt t, txt_index start, txt_index end) /* It turns out that we have four markers, giving the old and new positions of the selection. Regardless of their identity we should redisplay the first and last section defined by these. */ { txt_index a[5]; /* 1..4 used */ txt1__order(&start, &end); a[1] = start; a[2] = end; a[3] = txt1__bufindextoindex(t, t->selstart.pos); a[4] = txt1__bufindextoindex(t, t->selend.pos); /* Spot obvious null calls, so as not to clog up the undo buffer. */ { BOOL wasnull = a[3] == a[4]; BOOL isnull = start == end; if ((wasnull && isnull) || (start == a[3] && end == a[4])) { tracef0("null setting of selection.\n"); return; }; }; /* record the change in the undo buffer. */ txtundo_putnumber(t->undostate, a[3]); txtundo_putnumber(t->undostate, a[4]); txtundo_putcode(t->undostate, 'l'); txt1_domovemarker(t, &(t->selstart), start); /* no redisplay happens */ txt1_domovemarker(t, &(t->selend), end); /* sort the pointers */ txt1__order(&a[3], &a[4]); txt1__order(&a[2], &a[3]); txt1__order(&a[1], &a[2]); txt1__order(&a[3], &a[4]); txt1__order(&a[2], &a[3]); txt1__order(&a[3], &a[4]); /* and redisplay 1..2, 3..4 */ while (txt3_foreachwindow(t)) { txt1__redisplayselectrange(t, txt1__indextobufindex(t, a[1]), txt1__indextobufindex(t, a[2])); txt1__redisplayselectrange(t, txt1__indextobufindex(t, a[3]), txt1__indextobufindex(t, a[4])); }; txt1__checkbufinvariant(t, "dses"); } static BOOL txt1__withinselect(txt t, txt1_bufindex at, int *n /*out*/) /* Returns TRUE if the char at bufindex is within the selection, else FALSE. In addition to this, tells you (at least) how many subsequent characters (>= 1 unless we are at termch) are the same as this one. */ { if (! txt1__segsize(t, at, n)) return FALSE; /* end of array */ if (txt1__lessthanbi(t, at, t->selstart.pos)) { /* before select */ if ((t->selstart.pos >= at) && (t->selstart.pos < at + *n)) { *n = t->selstart.pos - at; }; return FALSE; } else if (txt1__lessthanbi(t, at, t->selend.pos)) {/* within select */ if (t->selend.pos >= at && t->selend.pos < at + *n) { *n = t->selend.pos - at; }; return TRUE; } else { /* after select */ return FALSE; }; } /* ======== Display. ======== */ /* the editing operations are written to update the display as they go along, if (the display bit is set. a more general model providing an arbitrary redisplay after arbitrary edits is (sadly) much more difficult to arrange. */ /* -------- Basic operations. -------- */ static void txt1__paintcall( txt t, txt1_zits x, txt1_zits y, char *ad, int n, txt1_zits width, BOOL highlight, txt1_callendopt callendopt) /* Well don't actually do it, but add it to the list of pending calls. */ /* >>>> Limit the number of calls? to how many? 3*no of lines perhaps? */ { txt1_call *c = malloc(sizeof(txt1_call)); #if FALSE if (c == 0) { tracef0("txt1/malloc store problem.\n"); c = malloc(sizeof(txt1_call)); }; #endif if (c == 0) { werr(TRUE, msgs_lookup(MSGS_txt49)); }; c->x = x; c->y = y; c->ad = ad; c->n = n; c->width = width; c->highlight = highlight; c->callendopt = callendopt; c->next = t->calls; t->calls = c; } #if FALSE /* caller has now been removed */ static BOOL txt1__callsequaloryshifted(txt1_call *c1, txt1_call *c2, txt1_zits byy) /* This is used to chop off the tail of the display of a large paragraph, see displayreplace2. A difference in y coordinate is permissable, by byy. */ { if (c1==NULL || c2==NULL) return c1==c2; return (c1->x == c2->x) && (c1->y + byy == c2->y) && (c1->ad == c2->ad) && (c1->n == c2->n) && (c1->width == c2->width) && (c1->highlight == c2->highlight) && (c1->callendopt == c2->callendopt); } #endif static void txt1__reverseorderofcalls(txt t) /* standard reverse-in-place of a singly linked list. */ { txt1_call *forw; txt1_call *back; txt1_call *temp; forw = t->calls; back = NULL; while (forw != NULL) { temp = forw; forw = temp->next; temp->next = back; back = temp; }; t->calls = back; } static void txt1__sortandreversecalls(txt t) /* An in-place bubble-sort, on the assumption that things are already pretty well sorted. */ { txt1_call *forw; txt1_call *back; txt1_call *temp; txt1_call *temp1; txt1_call *temp2; forw = t->calls; back = NULL; if (forw == NULL) return; while (1) { txt1__assert(forw != NULL, "sarc-1"); /* assert: all on back and one on forw are sorted */ /* move forward one call */ temp = forw; forw = forw->next; temp->next = back; back = temp; txt1__assert(back != NULL, "sarc-2"); /* assert: all calls on "back" are sorted */ if (forw == NULL) break; while ((back != NULL) && /* not in good order */ ((back->y < forw->y) || ((back->y == forw->y) && (back->x < forw->x))) ) { /* swap these two over */ tracef0("sarc-swap.\n"); temp1 = forw->next; temp2 = back->next; temp = forw; forw = back; back = temp; forw->next = temp1; back->next = temp2; /* move back one call */ temp = back; back = back->next; temp->next = forw; forw = temp; }; }; t->calls = back; } static void txt1__paintallcalls(txt t) /* Actually perform the stored-up paint calls. */ { txt1_call *c; txt1__sortandreversecalls(t); /* see comments below */ t->w->rawpaintseveral(t, t->calls, FALSE); /* do the painting */ /* and dispose of the call records */ while (t->calls != NULL) { c = t->calls; t->calls = c->next; free(c); }; } /* Calls tend to be built up from the cursor backwards, then from the cursor forwards. If the former set of calls is reversed before the latter operation, this brings the calls for the cursor line into one place. Reversing them again means that the calls are encountered left->right on the cursor line. This is a considerable help to sys-dep paint systems which wish to combine such calls into a single one, for performance and to improve handling of italic fonts. */ /* The sortandreversecalls handles any funny cases that mess this up, e.g. a selection just before the cursor and multi-line text lines before the cursor. this means we are absolutely sure that calls are generated in the right order. The system-dependent stuff can use this in worrying about italics etc. */ static void txt1__invertallcalls(txt t) /* Similar to the above, with alreadycorrect TRUE. */ /* >>>> Common them up instead of this duplication! */ { txt1_call *c; txt1__sortandreversecalls(t); t->w->rawpaintseveral(t, t->calls, TRUE); /* and dispose of the call records */ while (t->calls != NULL) { c = t->calls; t->calls = c->next; free(c); }; } static void txt1__movecalls(txt t, txt1_zits by, txt1_call *to) /* Move the most recently planned calls in the y direction. */ { txt1_call *c = t->calls; while (1) { if (c == to) break; if (c == NULL) break; tracef2("moving paint call at y=%i to y=%i\n", c->y, c->y+by); c->y += by; c = c->next; }; } static void txt1__cancelcalls(txt t, txt1_call *to) /* cancel the n most recently planned calls. */ { txt1_call *c; while (1) { if (t->calls == to) break; if (t->calls == NULL) break; c = t->calls; t->calls = t->calls->next; free(c); }; } void txt1__cancelallcalls(txt t) { txt1__cancelcalls(t, NULL); } static void txt1__cancelcallswithylessthan(txt t, txt1_zits y, txt1_call *to) /* >>>> Used in measureback when a long text line is partially visible. */ { txt1_call **p; txt1_call *c; p = &(t->calls); while (1) { c = *p; if (c == NULL) break; if (c == to) break; if (c->y < y) {/* delete */ tracef2("cancelcallswithylessthan strikes, %i<%i.\n", c->y, y); *p = c->next; free(c); } else { p = &(c->next); }; }; } /* -------- Line measuring. -------- */ void txt1__measure( txt t, txt1_zits *x /*inout*/, txt1_zits *y /*inout*/, txt1_zits ylim, /* y>=ylim -> stop straight away. */ txt1_bufindex *at /*inout*/, txt1_bufindex lim, /* at==lim -> stop straight away. */ BOOL generate) /* This procedure adds to the list of pending calls the calls necessary to display the specified segment of the array. It advances x and y as if the calls were made. It generates calls to clear the ends of lines when '\n's are found, and advances x and y to the start of the next line. If it finds itself generating calls at or beyond ylim it stops. */ /* There is a subtlety concerning what happens if the result of a measure lands you at the screen split of a long text line. If we stop because of ylim then (x,y) is at the start of the new screen line. if we stop because of lim then it's at the end of the previous one. */ /* >>>> If measure finds itself generating more calls than can possibly fit in the window it should delete the early ones as it goes, this behaviour is necessary in the case where the top thing in the window is the tail of an incredibly long line (if we are not to bust store by saving up zillions of useless calls). */ /* >>>> xlim form built in? */ /* >>>> no-calls flag built in? or static flag in state... */ { int segsize; int msize; txt1_zits spacewidth; char *addr; BOOL highlight = FALSE; int highlightno; txt1_bufindex nextlineend; tracef3("measure scr (%i,%i) ylim=%i", *x, *y, ylim); tracef2(" at=%i lim=%i\n", *at, lim); txt1__assertcharbufindex(t, *at, "m-1"); nextlineend = txt1__nextlineend(t, *at); highlightno = 0; if (txt1__lessoreqbi(t, lim, *at)) { tracef3(" -- immediate exit at (%i,%i) at=%i\n", *x, *y, *at); return; }; while (1) { txt1__assertcharbufindex(t, *at, "m-2"); if (*y >= ylim) break; if (*at == lim) break; if (highlightno == 0) { highlight = txt1__withinselect(t, *at, &highlightno); }; if (*at == nextlineend) { /* we're at a newlinech. */ /* can't be termch because not (at=lim). */ txt1__cleartoendofline(t, x, y, generate); /* advances x and y */ txt1__incbi(t, at, 1); highlightno--; nextlineend = txt1__nextlineend(t, *at); } else { /* not a line end, normal characters. */ if (! txt1__segsize(t, *at, &segsize)) break; /* >>>> needn't recalculate this every time? */ if (lim > *at && lim < (*at + segsize)) { segsize = lim - *at; }; if (nextlineend > *at && nextlineend < (*at + segsize)) { segsize = nextlineend - *at; }; if (segsize > highlightno) { segsize = highlightno; }; msize = segsize; addr = &(t->buf[*at]); spacewidth = t->w->limx - *x; t->w->rawmeasure(t, &addr, &msize, &spacewidth); if (*x == 0 && segsize == msize) { /* this window's so small, not even one char will fit. in order to prevent an infinite loop, we inist on at least one char per line. */ tracef0("huge char case.\n"); msize--; }; addr = &(t->buf[*at]); txt1__incbi(t, at, segsize - msize); highlightno -= segsize - msize; if (msize == 0) { /* we finished all the characters, good. */ if ((t->buf[*at] == '\n') && (*at != lim)) { /* optimise filling in of the rest of the rest of the line */ if (generate) txt1__paintcall( t, *x, *y, addr, segsize, (t->w->limx - spacewidth) - *x, highlight, txt1_CETEXTLINE); *x = 0; *y += t->w->linesep; txt1__incbi(t, at, 1); if (highlightno != 0) highlightno--; /* don't care if we highlight a '\n' */ nextlineend = txt1__nextlineend(t, *at); } else { /* t->buf[at] is probably termch. Careful to leave x, y in the right place. */ if (t->buf[*at] == '\n') { if (generate) txt1__paintcall( t, *x, *y, addr, segsize, (t->w->limx - spacewidth) - *x, highlight, txt1_CETEXTLINE); } else { if (generate) txt1__paintcall( t, *x, *y, addr, segsize, (t->w->limx - spacewidth) - *x, highlight, txt1_CECONTINUE); }; /* >>>> a bit bulky, use a separate variable. */ *x = t->w->limx - spacewidth; }; } else { /* we ran out of width on the line, with msize chars to spare */ if (generate) txt1__paintcall( t, *x, *y, addr, segsize-msize, (t->w->limx - spacewidth) - *x, highlight, txt1_CEDISPLINE); *x = 0; *y += t->w->linesep; }; }; }; tracef3(" -- exit at (%i,%i) at=%i\n", *x, *y, *at); } /* >>>> What happens in the case where the line exactly fits on the screen line? At the moment, no line-wrap occurs in this case and the cursor will not actually be visible. It would be possible to fix this around the "if (msize == 0)" above, but I won't unless people complain. in an imprecise window world, this case is less common than with VDUs. */ /* What is the position of the first character of a screen line that is the continuation of a text line? It's at the right hand edge. this is not intuitive for doing cursor-up from the line below! but, if you try to fudge it here (based on deciding that the next char won't fit, even though you didn't ask me to measure it, so I will advance to the next line) then you have a situation where inserting a new (thinner) character at the cursor will affect the line above. So, leave this horror until we tackle wordwrap. this is also a problem for caching the positions of firstvis and lastvis, which are assumed to have x=0. If they are at the continuation of a wrapped text line domeasureback gets this wrong and returns FALSE. note from this slightly devious code in decfirst/last. */ BOOL txt1__measureback( txt t, txt1_zits *x /*inout*/, txt1_zits *y /*inout*/, txt1_zits ylim, /* y<=ylim -> stop straight away */ txt1_bufindex *at /*inout*/, txt1_bufindex lim, /* at==lim -> stop straight away */ BOOL generate) /* Measure backwards through the array from "at" at (x,y), generating calls as you go. Stop before generating calls with y<ylim, or for any characters before lim. note y==ylim not an excuse to stop, keep going for all such chars. */ /* This works by skipping backwards a whole text line, and measuring forwards. If the line was more than one screen line long, the calls are moved up a little after the event. */ /* The routine returns FALSE if the x coordinate given just doesn't match what we find, in which case x returns with a more suitable value. This is used in two cases. The first is when the initial x is a total guess, for total redisplay of text. */ /* The second case involves the murky issue of the exact position of a split between two screen lines forming a single text line. If measure stops at such a point due to ylim then the start of the continuation line is returned. If this position is given to this routine then it returns FALSE, it can't know for sure that this is a split point except by measuring forwards too. This is important with partial redisplays involving firstvis and lastvis, which are kept as a y coordinate only with an implicit x==0. see incfirst/last etc. for this. */ /* >>>> This whole area is unsatisfactory. deeper insight required! Is this duality of the coordinates of a split point inevitable? */ { txt1_zits x1; txt1_zits y1; txt1_call *callsalready; txt1_bufindex lstart; txt1_bufindex at1; txt1_zits initialy; txt1_bufindex initialat; tracef3("measureback from (%i,%i) ylim=%i", *x, *y, ylim); tracef2(" at=%i lim=%i\n", *at, lim); lstart = txt1__startofreasonableline(t, *at); initialy = *y; initialat = *at; callsalready = t->calls; if (*at != lstart || *x != 0) { /* left hand part of line containing cursor. */ if (*y < ylim /* >>>> y<=ylim */) { tracef3(" -- exit TRUE at (%i,%i) at=%i\n", *x, *y, *at); return TRUE; }; if (txt1__lessoreqbi(t, *at, lim)) { tracef3(" -- exit TRUE at (%i,%i) at=%i\n", *x, *y, *at); return TRUE; }; x1 = 0; y1 = *y; at1 = lstart; callsalready = t->calls; txt1__measure(t, &x1, &y1, INT_MAX, &at1, *at, generate); if (x1 != *x) { /* the x he gave us just doesn't work: tell him a better one, and return. the calls will not get used, it's up to him to cancel them. */ #if FALSE *y = y1; /* for inc/decfirst/last. has no effect for the case used by redisplaytext, as we never get t->w->caretx/y wrong unless y=y1. */ #endif *x = x1; tracef3(" -- exit FALSE at (%i,%i) at=%i\n", *x, *y, *at); return FALSE; }; if (y1 == *y) { /* it's a single line, all is well */ } else { /* this line is longer than one screen line. so, shift the calls you've just generated up a little. */ txt1__movecalls(t, *y - y1, callsalready); *y += (*y - y1); /* *y-y1 is -ve */ }; *at = lstart; *x = 0; }; while (1) { /* for each text line */ if (txt1__lessoreqbi(t, *at, lim) || *y < ylim) { /* we have finished or gone too far back, we were only supposed to go to lim. so, track forwards again until we reach both this and ylim, and) {return. */ /* >>>> could y<=ylim be better? better to cover the whole ground,) {go forwards. want min "at" with same y. */ txt1__cancelcallswithylessthan(t, ylim, callsalready); callsalready = t->calls; txt1__measure(t, x, y, initialy+1, at, lim, FALSE); txt1__measure(t, x, y, ylim, at, initialat, FALSE); txt1__cancelcalls(t, callsalready); /* >>>> superfluous */ break; }; /* x==0. we're not at the start of the array. try the next line. */ at1 = *at; txt1__decbi(t, &at1, 1); lstart = txt1__startofreasonableline(t, at1); at1 = lstart; x1 = 0; y1 = *y - t->w->linesep; /* guess at single line */ callsalready = t->calls; txt1__measure(t, &x1, &y1, INT_MAX, &at1, *at, generate); /* we know the last character in there is a newline */ txt1__assert(x1 == 0, "mb"); if (y1 == *y) { /* it's a single line, usual case. */ *y -= t->w->linesep; } else { /* it's a long line, so move it up a little. */ txt1__movecalls(t, *y - y1, callsalready); *y = (*y - t->w->linesep) + (*y - y1); /* y-y1 is -ve */ }; *at = lstart; }; tracef3(" -- exit TRUE at (%i,%i) at=%i\n", *x, *y, *at); return TRUE; } /* If the exit from measureback is at the screen split of a long text line, there is an unpleasant mix of cases concerning where (x,y) end up pointing. You can return with y<ylim in this case, this is delicate for decfirst/last. >>>> This is far from satisfactory! a clearer model is sought. */ void txt1__horizmeasure(txt t, txt1_zits x, txt1_zits xlim, txt1_bufindex *at /*inout*/) /* Advance at until we are pointing at something in the xlim column. Stop at end of line. */ { txt1_bufindex lineend; char *addr; int segsize; txt1_zits spacewidth; tracef3("horizmeasure x=%i xlim=%i at=%i.\n", x, xlim, *at); lineend = txt1__nextlineend(t, *at); /* or end of array */ while (1) { if (*at == lineend) break; if (! txt1__segsize(t, *at, &segsize)) break; /* end of file */ if (*at + segsize > lineend) { segsize = lineend - *at; }; addr = &(t->buf[*at]); spacewidth = xlim - x; t->w->rawmeasure(t, &addr, &segsize, &spacewidth); x = xlim - spacewidth; txt1__incbi(t, at, addr - &(t->buf[*at])); if (segsize != 0) break; /* no room for any more */ }; if (*at != txt1__termchbi(t) && t->buf[*at] != '\n' && txt1__measurechar(t, t->buf[*at]) / 2 < xlim - x ) { tracef0("round up by 1.\n"); txt1__incbi(t, at, 1); }; tracef2("horizmeasure exit x=%i at=%i.\n", x, *at); } #if FALSE >>>> not needed, until fonts get better. void txt1__measurepreviouschar(txt t, txt1_zits x, txt1_zits y, txt1_bufindex at) /* Used for italic adjustment to things. if (x!=0) then measure (and generate a call for) just one character before at. */ { txt1_bufindex prev; txt1_zits initialx; tracef0("measurepreviouschar.\n"); if (x == 0) return; /* at start of line */ initialx = x; prev = at; txt1__decbi(t, &prev, 1); assert(prev != at, "mpc-1"); /* x would have been 0 */ assert(t->buf[prev] != '\n', "mpc-2"); /* ditto */ measure(t, &x, &y, INT_MAX, &prev, at); /* will surely generate exactly one call. */ assert(calls != NULL, "mpc-3"); calls->x -= x - initialx; } #endif void txt1__cleartoendofline(txt t, txt1_zits *x /*inout*/, txt1_zits *y /*inout*/, BOOL generate) /* Generate the calls that perform blanking of the rest of the window beyond the specified point. */ { tracef2("clear to end of line %i %i.\n", *x, *y); if (generate) txt1__paintcall(t, *x, *y, NULL, 0, 0, FALSE, txt1_CETEXTLINE); *x = 0; *y += t->w->linesep; } void txt1__updatescrollbar(txt t) { int max; int first; int last; txt1_percent size; txt1_percent offset; max = txt1__bufindextoindex(t, txt1__termchbi(t)); /* >= 1 */ first = txt1__bufindextoindex(t, t->w->firstvis.pos); last = txt1__bufindextoindex(t, t->w->lastvis.pos); tracef3("scrollbar: max=%i first=%i last=%i\n", max, first, last); if (last < first) last = first; if (max == 0) { size = 100; offset = 0; } else { size = ((last - first) * 100) / max; if (size == 0) size = 1; /* very big files. */ offset = (first * 100) / max; }; /* try not to keep fiddling the size by small amounts. */ if (t->w->isize != 0 && abs(size - t->w->isize) < 3) { size = t->w->isize; }; if (size != t->w->isize || offset != t->w->ioffset) { t->w->isize = size; t->w->ioffset = offset; t->w->rawsetsize(t); }; } /* -------- Total redisplay. -------- */ void txt1__redisplaytext(txt t, BOOL fresh) /* Redisplay the window, resetting t->w->caretx/y if necessary in order to keep dot on the screen. Assume the caret is not painted, and do not paint it. */ { txt1_zits x; txt1_zits y; txt1_bufindex at; if (t->w->carety < t->w->linesep) t->w->carety = t->w->linesep; if (t->w->carety > t->w->limy) t->w->carety = t->w->limy; /* This is quite common on a change of font. */ if (fresh && t->w->lastvisy < t->w->limy) { /* try to move things down a little. */ t->w->carety += t->w->limy - t->w->lastvisy; }; while (1) { x = t->w->caretx; y = t->w->carety; at = t->w->caret.pos; if (at == txt1__firstchbi(t)) {x = 0; t->w->caretx = 0;}; /* 17-Nov-88: If this is true, the measureback will succeed vacuously and we won't get told about the wrong x. */ if (txt1__measureback(t, &x, &y, 1, &at, txt1__firstchbi(t), TRUE)) { txt1__assert( txt1__lessoreqbi(t, at, t->w->caret.pos), "rt-1"); txt1__assert(y <= t->w->carety, "rt-2"); /* >>>> bug hunt: should always be TRUE after measureback... */ /* ylim=1 means, any character which is at all visible on the top line must be taken into account. */ if (y > t->w->linesep) { /* we didn't get to the beginning of the screen, so t->w->caretx/y must be wrong. must move t->w->carety up the screen. */ txt1__cancelallcalls(t); t->w->carety = t->w->carety + t->w->linesep - y; /* and loop */ } else { /* all is well, and calls before cursor planned. */ txt1__movemarker(t, &(t->w->firstvis), at); t->w->firstvisy = y; tracef1("setting firstvis to %i.\n", at); txt1__reverseorderofcalls(t); txt1__displayfrom(t, t->w->caretx, t->w->carety, t->w->caret.pos); /* calls updatescrollbar */ txt1__paintallcalls(t); break; }; } else { /* t->w->caretx is wrong. we must adjust it, and go round again. */ txt1__cancelallcalls(t); t->w->caretx = x; /* and loop */ }; }; } /* The reverseorderofcalls above is somewhat tweaky, but it means that the calls for the construction of the line with the cursor on it actually end up very close to each other. The sorting in paintallcalls is optimised to assume that things are almost in the right order already. */ /* Just before the loop, I tried going: if (t->w->lastvisy < t->w->limy) { t->w->carety += t->w->limy - t->w->lastvisy; }; in order to improve screen usage near the end of the file. Unfortunately I cannot distinguish times when he's trying to redraw the whole thing, rather than just repaint something. In the latter case, the above is disastrous if I got to the non-normalised state somehow, e.g. by incremental edits. >>>> must change this so that it's only within this module that this improvement is made. */ void txt1_checklastvis(txt t) /* This is used by txtar when shinking/growing the size of the window and avoiding a total repaint. */ { txt1__displayfrom(t, t->w->caretx, t->w->carety, t->w->caret.pos); /* calls updatescrollbar */ txt1__cancelcalls(t, NULL); } #if FALSE /* not used */ void txt1__redisplaybetween(txt t, txt1_zits ymin, txt1_zits ymax) /* Redisplay any lines of the window between the stated limits. You are assured that t->w->caretx/y etc. are correct and reasonable, e.g. so you don't have to measure things up beyond ymin. */ { txt1_zits x; txt1_zits y; x = t->w->caretx; y = t->w->carety; if (! txt1__measureback(t, &x, &y, ymin, &t->w->caret.pos, txt1__firstchbi(t), TRUE)) { txt1__assert(FALSE, "rb"); }; txt1__displayfrom(t, t->w->caretx, t->w->carety, t->w->caret.pos); txt1__paintallcalls(t); } #endif void txt1__displayfrom(txt t, txt1_zits x, txt1_zits y, txt1_bufindex at) /* Display the characters from at onwards in the window, starting at the value of (x,y). Do not assume that at==bufend. If you reach the end of the array, blank the rest of the window. */ { txt1_bufindex term; term = txt1__termchbi(t); txt1__measure(t, &x, &y, t->w->limy+t->w->linesep, &at, term, TRUE); txt1__movemarker(t, &(t->w->lastvis), at); t->w->lastvisy = y; if (x != 0) { /* if t->w->lastvis is at the split of a long line then we remember the y coord of the start of the continuation, because implicitly t->w->lastvisx==0. Here we have hit the end of file, without a '\n', and we act in a simialar way. */ t->w->lastvisy += t->w->linesep; }; if (at == term) {/* blank the rest of the screen */ while (y < t->w->limy+t->w->linesep) txt1__cleartoendofline(t, &x, &y, TRUE); }; tracef2("setting t->w->lastvis to %i, lastvisy to %i.\n", at, t->w->lastvisy); txt1__updatescrollbar(t); } void txt1__showcaret(txt t) { if (0 != (txt_CARET & t->charoptionset) && (t->w == t->windows[1])) { t->w->rawshowcaret(t); }; /* Regardless of whether the caret is visible, force its position to be so in the current window. Without this the display invariants will not be maintained. This code is already executed by rawshowcaret in the case where the caret is visible. */ if (t->w->carety + t->w->caretoffsety < txt1__min(t->w->linesep, t->w->limy)) { tracef0("auto-shifting, carety<=0.\n"); txt1_domovevertical( t, - (1 + (t->w->linesep-1 - (t->w->carety + t->w->caretoffsety)) / t->w->linesep), TRUE); } else if (t->w->carety + t->w->caretoffsety > t->w->limy) { tracef0("auto-shifting, carety > limy.\n"); txt1_domovevertical( t, ((t->w->linesep-1 + t->w->carety + t->w->caretoffsety - t->w->limy) / t->w->linesep), TRUE); }; } void txt1__hidecaret(txt t) { if (0 != (txt_CARET & t->charoptionset) && (t->w == t->windows[1])) { t->w->rawhidecaret(t); }; } /* -------- Incremental display update. -------- */ void txt1_redisplay(txt t, BOOL fresh) /* Redisplay all, including the caret. */ { if (0 == (txt_DISPLAY & t->charoptionset)) return; txt1__hidecaret(t); txt1__redisplaytext(t, fresh); txt1__showcaret(t); txt1__checkscreeninvariant(t, "rd-1"); } void txt1__redisplayselectrange(txt t, txt1_bufindex from, txt1_bufindex to) /* The selection attribute of the given range has changed. Invert the painting of the characters of that array. If inversion is not available, repaint them. Copes with from/to being the wrong way round, if necessary. */ { txt1_bufindex temp; txt1_zits x; txt1_zits y; txt1_bufindex at; if (0 == (txt_DISPLAY & t->charoptionset)) return; if (from == to) return; if (txt1__lessthanbi(t, to, from)) {/* swap */ temp = to; to = from; from = temp; }; if (t->w->italic && ! t->w->highlight_reversable) { /* redisplay whole lines. */ tracef0("redisplay whole lines.\n"); from = txt1__startofline(t, from); to = txt1__nextlineend(t, to); }; /* txt1__hidecaret(t); */ x = t->w->caretx; y = t->w->carety; at = t->w->caret.pos; if (txt1__lessthanbi(t, from, t->w->caret.pos)) { (void) txt1__measureback(t, &x, &y, 0 /* INT_MIN */, &at, from, FALSE); } else { txt1__measure(t, &x, &y, t->w->limy+t->w->linesep /* INT_MAX */, &at, from, FALSE); }; txt1__cancelallcalls(t); txt1__measure(t, &x, &y, t->w->limy+t->w->linesep, &at, to, TRUE); txt1__invertallcalls(t); /* txt1__showcaret(t); */ } void txt1__ensuredotvisible(txt t, int dotmove) /* The text has not changed and display is up to date and caret visible, but the dot has moved. Thus, this only happens at the end of domovedot. If dotmove is positive then the dot has just moved forward by that amount compared to what is displayed on the screen and held by t->t->w->caretx/y. */ /* >>>> At the moment, only called on the primary window. */ { txt1_zits prevcarety; txt1_bufindex prevdot; if (0 == (txt_DISPLAY & t->charoptionset)) return; txt1__hidecaret(t); t->w->caretoffsetx = 0; t->w->caretoffsety = 0; prevcarety = t->w->carety; if (dotmove > 0) {/* We have moved forwards in the file. */ /* We must measure what there is between prevdot and t->gapend. */ prevdot = t->gapend; txt1__decbi(t, &prevdot, dotmove); /* set up prevdot */ txt1__measure(t, &t->w->caretx, &t->w->carety, (3*t->w->limy) / 2, &prevdot, t->gapend, FALSE); txt1__cancelallcalls(t); if (prevdot != t->gapend) { /* We did not reach the new dot, so it must be several lines beyond the end of the window thus, a total repaint is required. */ t->w->caretx = 0; if (t->gapend == txt1__termchbi(t)) { t->w->carety = t->w->limy; } else { t->w->carety = t->w->limy / 2; t->w->carety = (t->w->carety / t->w->linesep) * t->w->linesep; }; txt1__redisplaytext(t, TRUE); } else { /* t->w->caretx/y have been updated ok. */ /* The caret is either within the window, or just outside. If the latter dorawshowcaret will call domovevertical in order to correct things, so there's nothing more to be done here. */ }; } else if (dotmove < 0) {/* have moved backwards in the file. */ prevdot = t->gapend; txt1__incbi(t, &prevdot, -dotmove); if (! txt1__measureback(t, &t->w->caretx, &t->w->carety, -(t->w->limy / 2), &prevdot, t->gapend, FALSE) ) { txt1__assert(FALSE, "edv-1"); }; txt1__cancelallcalls(t); if (prevdot != t->gapend) { /* we did not reach the new dot, so it must be several lines beyond the start of the window. Thus, a total repaint is required. */ t->w->caretx = 0; if (t->gapend == txt1__termchbi(t)) { t->w->carety = t->w->limy; } else { t->w->carety = t->w->limy / 2; t->w->carety = (t->w->carety / t->w->linesep) * t->w->linesep; }; txt1__redisplaytext(t, TRUE); } else { /* it's still visible. */ txt1__assert( txt1__lessoreqbi(t, t->w->firstvis.pos, t->gapend), "edv-3"); /* t->w->caretx/y have been updated ok. */ /* The caret is either within the window, or just outside. The latter dorawshowcaret will call domovevertical in order to correct things, so there's nothing more to be done here. */ }; } else { /* moveby == 0. still important to wiggle the caret if display, because of t->w->caretoffset. */ }; txt1__showcaret(t); txt1__checkscreeninvariant(t, "edv-2"); } /* I have experimented with making the "limy / 2" in the total redisplays into just "limy". This helps if moving to the end of the file, but otherwise keeps ending you up (after a long hop, e.g. a find operation) on the bottom line of the display. */ /* the problem with suddenly setting carety to limy / 2 is that it suddenly assumes that any value of zits is a bona fide line separation value. Surprisingly, this assumption is made nowhere else. Thus the div mul by t->w->linesep. (it's all a cheat: zits are os units in TextArthur in the y direction, and I really think in pixels! Oh well.) */ static void txt1__incfirst(txt t, txt1_zits byy) /* This procedure will generate calls and increment firstvis, for use when a copylines has been successful. It is used in ensuredotvisible above, and also when a scroll bar is clicked. It ignores the caret, so invariants etc. do not hold during this procedure. */ { txt1_zits x; txt1_bufindex at; tracef0("increment firstvis.\n"); x = 0; at = t->w->firstvis.pos; txt1__measure(t, &x, &t->w->firstvisy, byy + 1, &at, txt1__termchbi(t), FALSE); txt1__cancelallcalls(t); t->w->firstvisy -= byy; txt1__movemarker(t, &(t->w->firstvis), at); tracef3("firstvis moved to %i, ch=%i, t->w->firstvisy=%i.\n", t->w->firstvis.pos, t->buf[t->w->firstvis.pos], t->w->firstvisy); } static void txt1__inclast(txt t, txt1_zits byy) /* This procedure will generate calls and increment lastvis, for use when a copylines has been successful. It is used in ensuredotvisible above, and also when a scroll bar is clicked. It ignores the caret, so invariants etc. do not hold during this procedure. */ { txt1_zits x, savex; txt1_zits y; txt1_bufindex at; txt1_call *callssofar; tracef0("increment lastvis.\n"); tracef4("byy=%i limy=%i lastvisy=%i linesep=%i\n", byy, t->w->limy, t->w->lastvisy, t->w->linesep); /* There may some characters partially visible, which are before lastvis. These must be repainted. spotting this is quite tricky, e.g. the case where lastvis == termch and there's no '\n' at the end of the file. We rely on measureback to do no work in such cases. */ tracef0("repaint partial at bottom.\n"); x = 0; savex = 0; y = t->w->lastvisy - byy; at = t->w->lastvis.pos; callssofar = t->calls; if (! txt1__measureback(t, &x, &y, y - t->w->linesep, &at, txt1__firstchbi(t), TRUE) ) { /* lastvis must be at the continuation of a long, wrapped line. */ tracef0("lastvisy is on a continuation line.\n"); y -= t->w->linesep; t->w->lastvisy -= t->w->linesep; savex = x; txt1__cancelcalls(t, callssofar); /* x and y now point to the end of the previous screen line, which is wrapped onto the one we started at. */ if (! txt1__measureback( t, &x, &y, y - t->w->linesep, &at, txt1__firstchbi(t), TRUE) ) { tracef0("**lastvisy foul-up.\n"); }; }; txt1__cancelcallswithylessthan(t, 1 + t->w->limy - byy, callssofar); /* done purely for the calls. */ x = savex; /* x = 0; */ y = t->w->lastvisy - byy; at = t->w->lastvis.pos; txt1__measure(t, &x, &y, t->w->limy+t->w->linesep, &at, txt1__termchbi(t), TRUE); txt1__movemarker(t, &(t->w->lastvis), at); tracef1("lastvis moved to %i.\n", t->w->lastvis.pos); t->w->lastvisy = y; if (x != 0) { /* we're at the end of array, no '\n' */ t->w->lastvisy += t->w->linesep; }; tracef1("lastvisy set to %i.\n", t->w->lastvisy); if (at == txt1__termchbi(t)) {/* blank the rest of the screen */ while (y < t->w->limy+t->w->linesep) txt1__cleartoendofline(t, &x, &y, TRUE); }; } /* The cancelcallswithylessthan handles tricky cases where characters are repainted when in fact they were fully visible. it seems quite hard not to generate these in the first place, maybe it's a bodge and should be done neater... */ static BOOL txt1__decfirst(txt t, txt1_zits byy) /* Similar to incfirst, but for moving towards the start of the array. */ /* Returns FALSE if you hit the start of the array without being able to move back as far as you'd hoped. */ { txt1_zits x; txt1_zits y; txt1_bufindex at; txt1_call *callssofar; tracef0("decrement firstvis.\n"); if (t->w->firstvisy < t->w->linesep) { /* the first line of the display was not totally visible, and must be repainted. */ tracef0("repaint partial at top.\n"); x = 0; y = t->w->firstvisy + byy; at = t->w->firstvis.pos; txt1__measure(t, &x, &y, y+t->w->linesep, &at, txt1__termchbi(t), TRUE); /* done purely for the calls. */ }; x = 0; y = t->w->firstvisy + byy; at = t->w->firstvis.pos; callssofar = t->calls; if (! txt1__measureback(t, &x, &y, 1 /* >>>> t->w->firstvisy */, &at, txt1__firstchbi(t), TRUE)) { /* firstvis must be at the continuation of a long text line. */ tracef0("firstvisy is on a continuation line.\n"); txt1__cancelcalls(t, callssofar); y -= t->w->linesep; if (! txt1__measureback(t, &x, &y, 1, &at, txt1__firstchbi(t), TRUE)) { tracef0("**firstvisy foul-up.\n"); }; }; if (y < 1) { /* Unpleasant case: we have come to rest at the split of a long text line, and measureback puts (x,y) at the end of the prev line. firstvis/lastvis always represent this case by assuming start of continuation line, so adjust things here. */ y += t->w->linesep; }; if (y > t->w->linesep) { /* Another unpleasant case: we are at the top of file. */ /* He's going to have to redraw. */ tracef0("start of file, need to redraw.\n"); txt1__cancelallcalls(t); return FALSE; }; txt1__movemarker(t, &(t->w->firstvis), at); t->w->firstvisy = y; tracef3("firstvis moved to %i, ch=%i, t->w->firstvisy=%i.\n", t->w->firstvis.pos, t->buf[t->w->firstvis.pos], t->w->firstvisy); return TRUE; } static void txt1__declast(txt t, txt1_zits byy) /* similar to inclast, but for moving towards the start of the array. */ { txt1_zits x; txt1_zits y; txt1_bufindex at; txt1_call *callssofar; tracef0("decrement lastvis.\n"); x = 0; y = t->w->lastvisy + byy; at = t->w->lastvis.pos; callssofar = t->calls; if (! txt1__measureback(t, &x, &y, t->w->limy + t->w->linesep, &at, txt1__firstchbi(t), TRUE) ) { /* lastvis must be at the continuation of a long, wrapped line. */ tracef0("lastvisy is on a continuation line.\n"); y -= t->w->linesep; /* x and y now point to the end of the previous screen line, which is wrapped onto the one we started at. */ if (! txt1__measureback(t, &x, &y, t->w->limy + t->w->linesep, &at, txt1__firstchbi(t), TRUE)) { tracef0("**lastvisy foul-up.\n"); }; }; t->w->lastvisy = y; txt1__movemarker(t, &(t->w->lastvis), at); if (at == txt1__termchbi(t) && x != 0) { t->w->lastvisy += t->w->linesep; }; tracef1("lastvis moved to %i.\n", t->w->lastvis.pos); txt1__cancelcalls(t, callssofar); } void txt1__displayreplace1(txt t, int n) /* We're about to replace the next n characters. Measure them up for this. Return the end position of the characters you're about to delete. If this is way beyond the end of the screen, don't bother. */ /* In the case of multiple windows, the deletion/insertion could be miles away from this window. If t->gapend is after lastvis, or before the '\n' before firstvis then we needn't do anything, except perhaps update the scroll bar. This is the first thing to check. */ /* Otherwise, all we know (within this window) is that t->w->caret.pos is accurately displayed at t->w->caretx and t->w->carety. We must calculate the location of the gap, and then the location of the end of the deleted passage. in the one-window case the former of these will be very trivial indeed. */ { txt1_bufindex newdot; txt1_bufindex at; if (0 == (txt_DISPLAY & t->charoptionset)) { t->w->delendy = INT_MAX; return; }; if (txt1__lessthanbi(t, t->w->lastvis.pos, t->gapend)) { tracef0("displayreplace1, update is beyond this window.\n"); t->w->dotx = 0; t->w->doty = 0; t->w->delendx = 0; t->w->delendy = 0; return; }; if (txt1__lessthanbi(t, t->gapend, txt1__startofline(t, t->w->firstvis.pos))) { tracef0("displayreplace1, update is before this window.\n"); t->w->dotx = 0; t->w->doty = 0; t->w->delendx = 0; t->w->delendy = 0; return; }; /* >>>> a little inefficient, common these two up. */ t->w->dotx = t->w->caretx; t->w->doty = t->w->carety; at = t->w->caret.pos; if (txt1__lessthanbi(t, t->gapend, t->w->caret.pos)) { if (! txt1__measureback(t, &t->w->dotx, &t->w->doty, INT_MIN, &at, t->gapend, TRUE)) { tracef0("fail in measure1.\n"); }; } else { txt1__measure(t, &t->w->dotx, &t->w->doty, INT_MAX, &at, t->gapend, TRUE); }; txt1__assert(t->gapend == at, "dr-1"); /* t->w->dotx, t->w->doty now set up. */ newdot = at; txt1__incbi(t, &newdot, n); t->w->delendx = t->w->dotx; t->w->delendy = t->w->doty; txt1__measure(t, &t->w->delendx, &t->w->delendy, (3*t->w->limy) / 2, &at, newdot, FALSE); txt1__cancelallcalls(t); tracef2("displayreplace1 x=%i y=%i\n", t->w->delendx, t->w->delendy); } void txt1__displayreplace2(txt t, int n, int ndeleted) /* n characters have just been inserted, replacing ndeleted characters that used to go from t->w->dotx/y to t->w->delendx/y. Do the necessary replacement. move caretx/y if they are after this. */ /* A fairly general scheme is used that makes use of copylines when it can. We measure the insertion, and if it is the same size as the deletion (and font not italic) we just repaint that. We measure things as they are now, discovering the next '\n' after/at end of the insertion. We then measure things as they used to be, e.g. as they are on the screen, moving (delendx,delendy) to the next '\n'. If there are any identical calls at insline and delline then common them up, setting xs to 0. */ { txt1_zits insendx; txt1_zits insendy; txt1_bufindex at; txt1_bufindex lineend; txt1_bufindex atinsend; txt1_bufindex atdelend; #if FALSE txt1_call *c; #endif txt1_call *callsinsend; BOOL giveup; ndeleted=ndeleted; if (0 == (txt_DISPLAY & t->charoptionset) || (n==0 && t->w->delendx == t->w->dotx && t->w->delendy == t->w->doty) ) { return; }; if (txt1__lessthanbi(t, t->w->lastvis.pos, t->gapend)) { tracef0("displayreplace2, update is beyond this window.\n"); return; }; if (txt1__lessthanbi(t, t->gapend, txt1__startofline(t, t->w->firstvis.pos))) { tracef0("displayreplace2, update is before this window.\n"); return; }; if (t->w == t->windows[1]) txt1__hidecaret(t); txt1__italicadjust(t); insendx = t->w->dotx; insendy = t->w->doty; atinsend = t->gapend; at = atinsend; txt1__incbi(t, &at, n); txt1__measure(t, &insendx, &insendy, (3*t->w->limy) / 2, &atinsend, at, TRUE); if ((! t->w->italic) && (insendx == t->w->delendx) && (insendy == t->w->delendy) ) { /* exact replace: that's all we need to) { */ tracef0("exact replace.\n"); } else { giveup = FALSE; /* choose a common point up to which to do old/new measuring. */ lineend = atinsend; if ((insendx != 0) || (t->w->delendx != 0)) { lineend = txt1__nextlineend(t, lineend); txt1__incbi(t, &lineend, 1); /* actually, start of following line. */ }; /* measure new case */ at = lineend; txt1__measure(t, &insendx, &insendy, (3*t->w->limy) / 2, &atinsend, at, TRUE); if (insendx != 0) { /* end of array with no '\n' case. */ txt1__cleartoendofline(t, &insendx, &insendy, TRUE); }; callsinsend = t->calls; /* measure old case */ atdelend = t->gapend; txt1__incbi(t, &atdelend, n); at = lineend; txt1__measure(t, &t->w->delendx, &t->w->delendy, (3*t->w->limy) / 2, &atdelend, at, TRUE); if (t->w->delendx != 0) { txt1__cleartoendofline(t, &t->w->delendx, &t->w->delendy, TRUE); }; #if FALSE /* Seems to cause a bug if deleting a block which starts and ends at exactly the same x-offset on different lines... See the note at the bottom about only doing whole lines. */ /* Should work without this. And does, I believe! */ /* Cancel calls that are identical above insend and delend. The ordering of calls created above is quite important here. Everything created since callsinsend is going to be cancelled. */ if (t->calls != callsinsend) { c = t->calls; while (txt1__callsequaloryshifted( c, callsinsend, insendy - t->w->delendy)) { tracef0("cancel equal calls.\n"); if (callsinsend->y <= insendy) { tracef0("dec ins/delendy in step.\n"); /* we can decrement ins/delendy, in step. */ insendy -= t->w->linesep; t->w->delendy -= t->w->linesep; insendx = 0; t->w->delendx = 0; }; c = c->next; callsinsend = callsinsend->next; }; }; /* >>>> This should only delete whole lines of display, or italics will go wrong. I should check this, there may be some very unlikely cases around? With the symptoms being slight italic imperfections. */ #endif /* cancel superfluous calls. */ txt1__cancelcalls(t, callsinsend); if (insendy == t->w->delendy) { /* we paint these calls: done. */ tracef0("insendy == delendy.\n"); /* >>>> could cancel the cleartoendofline if (insendx was < delendx */ /* >>>> more stringent test needed if (italic, e.g. was "f" at end... */ /* One last case to care about, where we're near the bottom of the window */ if (insendy >= t->w->lastvisy) { /* must worry about updating lastvis. just give up, no great loss since we're near the bottom of the display anyway. */ tracef0("near bottom.\n"); giveup = TRUE; }; } else { insendy -= t->w->linesep; t->w->delendy -= t->w->linesep; /* The y values now indicate the top of the subsequent line. This is so that the copylines calls get the indicated line itself too. It also removes confusion over cases where the inserted/deleted segment does, or does not, end in a '\n'. */ if (insendy > t->w->delendy) { /* we are inserting some lines. */ tracef2("insendy=%i > delendy=%i.\n", insendy, t->w->delendy); if (insendy > t->w->limy - 2 * t->w->linesep) { /* not worth any copy */ giveup = TRUE; } else if ( t->w->rawcopylines(t, t->w->delendy, insendy, t->w->limy - insendy) ) { txt1__declast(t, insendy - t->w->delendy); } else { /* copy didn't work */ giveup = TRUE; }; } else if (insendy < t->w->delendy) { /* we are deleting some lines. */ tracef2("insendy=%i < delendy=%i.\n", insendy, t->w->delendy); if (t->w->delendy > t->w->limy - 2 * t->w->linesep) { /* not worth any copy */ giveup = TRUE; } else if ( t->w->rawcopylines( t, t->w->delendy, insendy, t->w->limy - t->w->delendy) ) { txt1__inclast(t, t->w->delendy - insendy); } else { /* copy didn't work */ giveup = TRUE; }; }; }; if (giveup) { txt1__cancelallcalls(t); /* slightly clumsy, not important. */ txt1__italicadjust(t); txt1__displayfrom(t, t->w->dotx, t->w->doty, t->gapend); }; }; txt1__paintallcalls(t); /* finally, if we aren't the primary window then our caret could get moved by all of this. */ if (txt1__lessthanbi(t, t->gapend, t->w->caret.pos)) { tracef0("repositioning subsidiary window caret.\n"); t->w->caretx = t->w->dotx; t->w->carety = t->w->doty; at = t->gapend; txt1__measure(t, &t->w->caretx, &t->w->carety, INT_MAX, &at, t->w->caret.pos, FALSE); txt1__cancelallcalls(t); tracef3("new caret at %i = %i,%i.\n", t->w->caret.pos, t->w->caretx, t->w->carety); }; t->w->caretoffsetx = 0; if (t->w == t->windows[1]) { /* this only applies to the primary window. */ if (t->w->caretoffsety != 0) { /* we are sitting at a line break, at the start of the following line. in such a case it seems appropriate to continue the display there. note that in all of the above, this issue can safely be ignored. */ t->w->caretoffsetx = - t->w->caretx; }; txt1__showcaret(t); } else { t->w->caretoffsety = 0; }; txt1__checkscreeninvariant(t, "dr2-1"); } /* >>>> Note that the statement about this not affecting t->w->caretx/y precludes breaking at word boundaries. What if you are close to the end of a line, and you delete a space? This could cause you to advance t->w->caretx/y to the start of the previous line. Could t->w->caretx/y move up the screen? Yes, by inserting a space while close to the front of a wrapped line you could find yourself shoot to the end of the previous one. This means that word-broken layout needs constant remeasuring from the start of the current line. Let's do the simpler form first! */ /* Some thought has gone into the wordwrap thing. the point that stays unmoved is the next start of line before the cursor. what you do is: if new/old strings both end with '\n', decrement counts. measure old paragraph make change measure new paragraph note change in endpoint, copylines etc. knock out common call lines in the lists above perform whatever is left. In addition we need changes to measure to actually do the wordwrap decisions. */ void txt1__italicadjust(txt t) /* It would be quite safe for this procedure to have no effect. The purpose is that, when inserting things before an italic "f" or something, the tail of the "f" can get left behind. So, if using an italic font (signalled by sys-dep stuff) this procedure ensures that the entire line is repainted every time. */ { txt1_bufindex at; txt1_zits x; txt1_zits y; if (t->w->italic) { at = t->w->caret.pos; x = t->w->caretx; y = t->w->carety; if (txt1__measureback(t, &x, &y, y - 1 /*t->w->linesep*/ /*INT_MIN*/, &at, txt1__startofline(t, t->w->caret.pos), TRUE)) { }; /* the calls have been created. */ }; } /* >>>> At this point I could remove from the calls generated any with y!=t->w->carety: if inserting several (screen) lines on in a long (text) line, this will currently repaint the whole (text) line. */ /* >>>> tried setting ylim... */ /* >>>> It can be done much neater: just paint one character before in the italic case, but do not rub-out underneath that char. Hooray for specifying the rub-out box separately! But, how is this to be presented in the interface between texts1/2? perhaps this is a universal problem? how sure can I be that the answer is universal, e.g. look at x-windows... */ /* -------- Public scroll bar operations. -------- */ void txt1_thumbforw(txt t, txt1_percent by) /* Ideally, lastvis advances by "by" percent of the distance from it to the end of the array. */ /* It's difficult to know exactly what he means by this. for the moment a simple move is performed. This is slightly unsatisfactory for small distances in that only the cursor will move: but, a better solution is not really clear. */ { txt_index ci = txt1__bufindextoindex(t, t->gapend); txt_index end = txt1__bufindextoindex(t, txt1__termchbi(t)); int moveamount = (by * (end - ci)) / 100; if (by > t->w->isize && moveamount * 20 > txt1_dosize(t)) { /* quite a long drag */ /* Try counting backwards a little so that we tend to hit newlines. This will reduce sideways scrolling when thumbing with a narrow window. */ int i = 0; while (i < 120 && i < moveamount) { if (txt_charat(t, txt_dot(t) + moveamount - i) == '\n') { moveamount -= i; moveamount++; break; }; i++; }; txt1_domovedot(t, moveamount); } else { /* short drag */ #if FALSE int nlines = t->w->limy / t->w->linesep; txt1_scrollbarmoveby(t, (nlines * by) / t->w->isize); #else txt1_scrollbarmoveby(t, 1); #endif }; } void txt1_thumbback(txt t, txt1_percent by) /* Ideally, firstvis retreats by "by" percent of the distance from it to the beginning of the array. */ { txt_index ci = txt1__bufindextoindex(t, t->gapend); int amount = (by * ci) / 100; if (by > t->w->isize && by > 5) { /* quite a long drag, or within 1% of touching top of file */ /* if (by < 100) by++; */ /* Try counting backwards a little so that we tend to hit newlines. This will reduce sideways scrolling when thumbing with a narrow window. */ int i = 0; while (i < 120 && i < amount) { if (txt_charat(t, txt_dot(t) - amount - i) == '\n') { amount += i; amount++; break; }; i++; }; txt1_domovedot(t, - amount); } else { txt1_scrollbarmoveby(t, -1); ci = txt1__bufindextoindex(t, t->w->firstvis.pos); if (ci < txt1_dosize(t) / 50) { /* fix problems with scrolling close to top of buffer. */ txt1_dosetdot(t, 0); }; }; } /* The fudge to "by" is because rounding seems to cause 99-percent moves to get to the front of the array. */ void txt1_scrollbarmoveby(txt t, int by) /* A click in the "move one line" arrow box of a scroll bar causes this to be called. */ /* If by==1 then advance the minimum amount reasonable, e.g. one display line. If by==-1 go backwards in a similar way. this is because the window can't know what value of offset to suggest for a small movement using thumbforw/back. For larger values, act in the obvious way: e.g. for movement by a whole page. */ /* If the caret is not quite visible at the moment then this call is taken as a good time to take corrective action. The window application can call this routine, for instance, if the caret goes (just) out of bounds due to a move or replace. */ { txt1_zits byy; BOOL copyfirst; BOOL caret; BOOL caretinbounds; tracef1("scrollbarmoveby %i.\n", by); tracef3("limy=%i lastvisy=%i linesep=%i\n", t->w->limy, t->w->lastvisy, t->w->linesep); if (by == 0) return; byy = abs(by) * t->w->linesep; /* >>>> replaced by stuff below if ((by > 0) && (t->w->lastvis.pos == txt1__termchbi(t))) { if (t->w->lastvisy < t->w->limy) { return; }; }; if ((by < 0) && (t->w->firstvis.pos == txt1__firstchbi(t))) { if (t->w->firstvisy < t->w->linesep) { byy = t->w->linesep - t->w->firstvisy; by = -1; } else { return; }; }; */ /* Try to align things so that you are showing a whole line in the direction in which you're moving. This speeds up repeated scrolling by reducing the amount of repainting of partial lines. */ if (by < 0) { if (t->w->firstvisy < txt1__min(t->w->linesep, t->w->limy)) { /* there's a partial line at the top. */ byy -= t->w->firstvisy; } else if (t->w->firstvis.pos == txt1__firstchbi(t)) { return; /* nothing to be) {ne. */ }; }; if (by > 0) { if (t->w->lastvisy > t->w->limy + t->w->linesep) { /* there's a partial line at the bottom. */ byy += t->w->lastvisy - (t->w->limy + t->w->linesep + t->w->linesep); } else if (t->w->lastvisy < t->w->limy && t->w->lastvis.pos == txt1__termchbi(t)) { tracef0("exit sbmb 0.\n"); return; /* nothing to be done. */ }; }; caret = 0 != (txt_CARET & t->charoptionset); txt1__hidecaret(t); t->charoptionset &= ~txt_CARET; if (byy > (3*t->w->limy) / 4) { txt1_domovevertical(t, by, FALSE); } else { tracef2("scroll by %i %i.\n", by, byy); if (by > 0) {/* advance in the array */ caretinbounds = t->w->carety + t->w->caretoffsety <= t->w->limy; copyfirst = t->w->carety + t->w->caretoffsety > byy + t->w->linesep; if (caretinbounds && (! copyfirst)) { txt1_domovevertical(t, by, FALSE); }; if (t->w->rawcopylines(t, byy, 0, t->w->limy - byy)) { txt1__incfirst(t, byy); txt1__inclast(t, byy); /* position the caret */ t->w->carety -= byy; txt1__paintallcalls(t); } else { t->w->carety -= byy; txt1__redisplaytext(t, TRUE); }; } else { /* retreat in the array */ caretinbounds = t->w->carety + t->w->caretoffsety >= txt1__min(t->w->linesep, t->w->limy); copyfirst = t->w->carety + t->w->caretoffsety < t->w->limy - byy; if (caretinbounds && (! copyfirst)) { txt1_domovevertical(t, by, FALSE); }; if (t->w->rawcopylines(t, 0, byy, t->w->limy - byy)) { if (! txt1__decfirst(t, byy)) { t->w->carety += byy; txt1__redisplaytext(t, TRUE); } else { txt1__declast(t, byy); /* adjust caret */ t->w->carety += byy; txt1__paintallcalls(t); }; } else { t->w->carety += byy; txt1__redisplaytext(t, TRUE); }; }; if (caretinbounds && copyfirst) {txt1_domovevertical(t, by, FALSE);}; txt1__updatescrollbar(t); }; if (caret) t->charoptionset |= txt_CARET; txt1__showcaret(t); } txt_index txt1_windowcoordstoindex(txt t, txt1_zits x, txt1_zits y) /* >>>> need an xlim arg on measure to do this! */ /* need it for vertical movement, too. */ { txt1_zits x1; txt1_zits y1; txt1_bufindex at; txt_index i; x1 = t->w->caretx; y1 = t->w->carety; at = t->gapend; if (y > t->w->carety || (y > t->w->carety-t->w->linesep && x >= t->w->caretx) ) {/* forwards of cursor */ txt1__measure(t, &x1, &y1, y, &at, txt1__termchbi(t), FALSE); } else { /* backwards from cursor */ (void) txt1__measureback(t, &x1, &y1, y, &at, txt1__firstchbi(t), FALSE); }; txt1__cancelallcalls(t); txt1__horizmeasure(t, x1, x, &at); i = txt1__bufindextoindex(t, at); tracef3("windowcoordstoindex (%i,%i) = %i.\n", x, y, i); return i; } /* end */