2019-11-14 23:15:48 +00:00
|
|
|
#include <u.h>
|
|
|
|
#include <libc.h>
|
|
|
|
#include <draw.h>
|
|
|
|
#include <thread.h>
|
|
|
|
#include <cursor.h>
|
|
|
|
#include <mouse.h>
|
|
|
|
#include <keyboard.h>
|
|
|
|
#include <frame.h>
|
|
|
|
#include <fcall.h>
|
|
|
|
#include <plumb.h>
|
|
|
|
#include <libsec.h>
|
|
|
|
#include <complete.h>
|
|
|
|
#include "dat.h"
|
|
|
|
#include "fns.h"
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
Image* tagcols[NCOL];
|
|
|
|
Image* textcols[NCOL];
|
|
|
|
static Rune Ldot[] = {'.', 0};
|
2019-11-14 23:15:48 +00:00
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
enum {
|
|
|
|
TABDIR = 3 /* width of tabs in directory windows */
|
2019-11-14 23:15:48 +00:00
|
|
|
};
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
void textinit(Text* t, File* f, Rectangle r, Reffont* rf, Image* cols[NCOL]) {
|
|
|
|
t->file = f;
|
|
|
|
t->all = r;
|
|
|
|
t->scrollr = r;
|
|
|
|
t->scrollr.max.x = r.min.x + Scrollwid;
|
|
|
|
t->lastsr = nullrect;
|
|
|
|
r.min.x += Scrollwid + Scrollgap;
|
|
|
|
t->eq0 = ~0;
|
|
|
|
t->ncache = 0;
|
|
|
|
t->reffont = rf;
|
|
|
|
t->tabstop = maxtab;
|
|
|
|
memmove(t->fr.cols, cols, sizeof t->fr.cols);
|
2022-02-20 20:43:24 +00:00
|
|
|
textredraw(t, r, rf->f, screen, -1, 0);
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2022-02-20 20:43:24 +00:00
|
|
|
void textdrawactive(Text* t, int active) {
|
|
|
|
Image* b;
|
|
|
|
Rectangle br;
|
|
|
|
|
|
|
|
b = t->fr.cols[BACK];
|
|
|
|
if (active)
|
|
|
|
b = t->fr.cols[HIGH];
|
|
|
|
br.min = t->scrollr.max;
|
|
|
|
br.max.x = br.min.x + 1;
|
|
|
|
br.max.y = br.min.y + Dy(t->all);
|
|
|
|
draw(screen, br, b, nil, b->r.min);
|
|
|
|
}
|
|
|
|
|
|
|
|
void textredraw(Text* t, Rectangle r, Font* f, Image* b, int odx, int active) {
|
2021-06-13 05:54:54 +00:00
|
|
|
int maxt;
|
|
|
|
Rectangle rr;
|
|
|
|
|
|
|
|
frinit(&t->fr, r, f, b, t->fr.cols);
|
|
|
|
rr = t->fr.r;
|
|
|
|
rr.min.x -= Scrollwid + Scrollgap; /* back fill to scroll bar */
|
|
|
|
if (!t->fr.noredraw)
|
|
|
|
draw(t->fr.b, rr, t->fr.cols[BACK], nil, ZP);
|
|
|
|
/* use no wider than 3-space tabs in a directory */
|
|
|
|
maxt = maxtab;
|
|
|
|
if (t->what == Body) {
|
|
|
|
if (t->w->isdir)
|
|
|
|
maxt = min(TABDIR, maxtab);
|
|
|
|
else
|
|
|
|
maxt = t->tabstop;
|
|
|
|
}
|
|
|
|
t->fr.maxtab = maxt * stringwidth(f, "0");
|
|
|
|
if (t->what == Body && t->w->isdir && odx != Dx(t->all)) {
|
|
|
|
if (t->fr.maxlines > 0) {
|
|
|
|
textreset(t);
|
|
|
|
textcolumnate(t, t->w->dlp, t->w->ndl);
|
|
|
|
textshow(t, 0, 0, 1);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
textfill(t);
|
|
|
|
textsetselect(t, t->q0, t->q1);
|
|
|
|
}
|
2022-02-20 20:43:24 +00:00
|
|
|
/*textdrawactive(t, active);*/
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
int textresize(Text* t, Rectangle r, int keepextra) {
|
|
|
|
int odx;
|
|
|
|
|
|
|
|
if (Dy(r) <= 0)
|
|
|
|
r.max.y = r.min.y;
|
|
|
|
else if (!keepextra)
|
|
|
|
r.max.y -= Dy(r) % t->fr.font->height;
|
|
|
|
odx = Dx(t->all);
|
|
|
|
t->all = r;
|
|
|
|
t->scrollr = r;
|
|
|
|
t->scrollr.max.x = r.min.x + Scrollwid;
|
|
|
|
t->lastsr = nullrect;
|
|
|
|
r.min.x += Scrollwid + Scrollgap;
|
|
|
|
frclear(&t->fr, 0);
|
2022-02-20 20:43:24 +00:00
|
|
|
textredraw(t, r, t->fr.font, t->fr.b, odx, 0);
|
2021-06-13 05:54:54 +00:00
|
|
|
if (keepextra && t->fr.r.max.y < t->all.max.y && !t->fr.noredraw) {
|
|
|
|
/* draw background in bottom fringe of window */
|
|
|
|
r.min.x -= Scrollgap;
|
|
|
|
r.min.y = t->fr.r.max.y;
|
|
|
|
r.max.y = t->all.max.y;
|
|
|
|
draw(screen, r, t->fr.cols[BACK], nil, ZP);
|
|
|
|
}
|
|
|
|
return t->all.max.y;
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
void textclose(Text* t) {
|
|
|
|
free(t->cache);
|
|
|
|
frclear(&t->fr, 1);
|
|
|
|
filedeltext(t->file, t);
|
|
|
|
t->file = nil;
|
|
|
|
rfclose(t->reffont);
|
|
|
|
if (argtext == t)
|
|
|
|
argtext = nil;
|
|
|
|
if (typetext == t)
|
|
|
|
typetext = nil;
|
|
|
|
if (seltext == t)
|
|
|
|
seltext = nil;
|
|
|
|
if (mousetext == t)
|
|
|
|
mousetext = nil;
|
|
|
|
if (barttext == t)
|
|
|
|
barttext = nil;
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
int dircmp(const void* a, const void* b) {
|
|
|
|
Dirlist *da, *db;
|
|
|
|
int i, n;
|
|
|
|
|
|
|
|
da = *(Dirlist**)a;
|
|
|
|
db = *(Dirlist**)b;
|
|
|
|
n = min(da->nr, db->nr);
|
|
|
|
i = memcmp(da->r, db->r, n * sizeof(Rune));
|
|
|
|
if (i)
|
|
|
|
return i;
|
|
|
|
return da->nr - db->nr;
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
void textcolumnate(Text* t, Dirlist** dlp, int ndl) {
|
|
|
|
int i, j, w, colw, mint, maxt, ncol, nrow;
|
|
|
|
Dirlist* dl;
|
|
|
|
uint q1;
|
|
|
|
static Rune Lnl[] = {'\n', 0};
|
|
|
|
static Rune Ltab[] = {'\t', 0};
|
|
|
|
|
|
|
|
if (t->file->ntext > 1)
|
|
|
|
return;
|
|
|
|
mint = stringwidth(t->fr.font, "0");
|
|
|
|
/* go for narrower tabs if set more than 3 wide */
|
|
|
|
t->fr.maxtab = min(maxtab, TABDIR) * mint;
|
|
|
|
maxt = t->fr.maxtab;
|
|
|
|
colw = 0;
|
|
|
|
for (i = 0; i < ndl; i++) {
|
|
|
|
dl = dlp[i];
|
|
|
|
w = dl->wid;
|
|
|
|
if (maxt - w % maxt < mint || w % maxt == 0)
|
|
|
|
w += mint;
|
|
|
|
if (w % maxt)
|
|
|
|
w += maxt - (w % maxt);
|
|
|
|
if (w > colw)
|
|
|
|
colw = w;
|
|
|
|
}
|
|
|
|
if (colw == 0)
|
|
|
|
ncol = 1;
|
|
|
|
else
|
|
|
|
ncol = max(1, Dx(t->fr.r) / colw);
|
|
|
|
nrow = (ndl + ncol - 1) / ncol;
|
|
|
|
|
|
|
|
q1 = 0;
|
|
|
|
for (i = 0; i < nrow; i++) {
|
|
|
|
for (j = i; j < ndl; j += nrow) {
|
|
|
|
dl = dlp[j];
|
|
|
|
fileinsert(t->file, q1, dl->r, dl->nr);
|
|
|
|
q1 += dl->nr;
|
|
|
|
if (j + nrow >= ndl)
|
|
|
|
break;
|
|
|
|
w = dl->wid;
|
|
|
|
if (maxt - w % maxt < mint) {
|
|
|
|
fileinsert(t->file, q1, Ltab, 1);
|
|
|
|
q1++;
|
|
|
|
w += mint;
|
|
|
|
}
|
|
|
|
do {
|
|
|
|
fileinsert(t->file, q1, Ltab, 1);
|
|
|
|
q1++;
|
|
|
|
w += maxt - (w % maxt);
|
|
|
|
} while (w < colw);
|
|
|
|
}
|
|
|
|
fileinsert(t->file, q1, Lnl, 1);
|
|
|
|
q1++;
|
|
|
|
}
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
int textload(Text* t, uint q0, char* file, int setqid) {
|
|
|
|
Rune* rp;
|
|
|
|
Dirlist *dl, **dlp;
|
|
|
|
int fd, i, j, n, ndl, nulls;
|
|
|
|
uint q, q1;
|
|
|
|
Dir *d, *dbuf;
|
|
|
|
char* tmp;
|
|
|
|
Text* u;
|
|
|
|
DigestState* h;
|
|
|
|
|
|
|
|
if (t->ncache != 0 || t->file->b.nc || t->w == nil || t != &t->w->body)
|
|
|
|
error("text.load");
|
|
|
|
if (t->w->isdir && t->file->nname == 0) {
|
|
|
|
warning(nil, "empty directory name");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (ismtpt(file)) {
|
|
|
|
warning(nil, "will not open self mount point %s\n", file);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
fd = open(file, OREAD);
|
|
|
|
if (fd < 0) {
|
|
|
|
warning(nil, "can't open %s: %r\n", file);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
d = dirfstat(fd);
|
|
|
|
if (d == nil) {
|
|
|
|
warning(nil, "can't fstat %s: %r\n", file);
|
|
|
|
goto Rescue;
|
|
|
|
}
|
|
|
|
nulls = FALSE;
|
|
|
|
h = nil;
|
|
|
|
if (d->qid.type & QTDIR) {
|
|
|
|
/* this is checked in get() but it's possible the file changed underfoot */
|
|
|
|
if (t->file->ntext > 1) {
|
|
|
|
warning(
|
|
|
|
nil,
|
|
|
|
"%s is a directory; can't read with multiple windows on it\n",
|
|
|
|
file);
|
|
|
|
goto Rescue;
|
|
|
|
}
|
|
|
|
t->w->isdir = TRUE;
|
|
|
|
t->w->filemenu = FALSE;
|
|
|
|
if (t->file->nname > 0 && t->file->name[t->file->nname - 1] != '/') {
|
|
|
|
rp = runemalloc(t->file->nname + 1);
|
|
|
|
runemove(rp, t->file->name, t->file->nname);
|
|
|
|
rp[t->file->nname] = '/';
|
|
|
|
winsetname(t->w, rp, t->file->nname + 1);
|
|
|
|
free(rp);
|
|
|
|
}
|
|
|
|
dlp = nil;
|
|
|
|
ndl = 0;
|
|
|
|
dbuf = nil;
|
|
|
|
while ((n = dirread(fd, &dbuf)) > 0) {
|
|
|
|
for (i = 0; i < n; i++) {
|
|
|
|
dl = emalloc(sizeof(Dirlist));
|
|
|
|
j = strlen(dbuf[i].name);
|
|
|
|
tmp = emalloc(j + 1 + 1);
|
|
|
|
memmove(tmp, dbuf[i].name, j);
|
|
|
|
if (dbuf[i].qid.type & QTDIR)
|
|
|
|
tmp[j++] = '/';
|
|
|
|
tmp[j] = '\0';
|
|
|
|
dl->r = bytetorune(tmp, &dl->nr);
|
|
|
|
dl->wid = stringwidth(t->fr.font, tmp);
|
|
|
|
free(tmp);
|
|
|
|
ndl++;
|
|
|
|
dlp = realloc(dlp, ndl * sizeof(Dirlist*));
|
|
|
|
dlp[ndl - 1] = dl;
|
|
|
|
}
|
|
|
|
free(dbuf);
|
|
|
|
}
|
|
|
|
qsort(dlp, ndl, sizeof(Dirlist*), dircmp);
|
|
|
|
t->w->dlp = dlp;
|
|
|
|
t->w->ndl = ndl;
|
|
|
|
textcolumnate(t, dlp, ndl);
|
|
|
|
q1 = t->file->b.nc;
|
|
|
|
} else {
|
|
|
|
t->w->isdir = FALSE;
|
|
|
|
t->w->filemenu = TRUE;
|
|
|
|
if (q0 == 0)
|
|
|
|
h = sha1(nil, 0, nil, nil);
|
|
|
|
q1 = q0 + fileload(t->file, q0, fd, &nulls, h);
|
|
|
|
}
|
|
|
|
if (setqid) {
|
|
|
|
if (h != nil) {
|
|
|
|
sha1(nil, 0, t->file->sha1, h);
|
|
|
|
h = nil;
|
|
|
|
} else {
|
|
|
|
memset(t->file->sha1, 0, sizeof t->file->sha1);
|
|
|
|
}
|
|
|
|
t->file->dev = d->dev;
|
|
|
|
t->file->mtime = d->mtime;
|
|
|
|
t->file->qidpath = d->qid.path;
|
|
|
|
}
|
|
|
|
close(fd);
|
|
|
|
rp = fbufalloc();
|
|
|
|
for (q = q0; q < q1; q += n) {
|
|
|
|
n = q1 - q;
|
|
|
|
if (n > RBUFSIZE)
|
|
|
|
n = RBUFSIZE;
|
|
|
|
bufread(&t->file->b, q, rp, n);
|
|
|
|
if (q < t->org)
|
|
|
|
t->org += n;
|
|
|
|
else if (q <= t->org + t->fr.nchars)
|
|
|
|
frinsert(&t->fr, rp, rp + n, q - t->org);
|
|
|
|
if (t->fr.lastlinefull)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
fbuffree(rp);
|
|
|
|
for (i = 0; i < t->file->ntext; i++) {
|
|
|
|
u = t->file->text[i];
|
|
|
|
if (u != t) {
|
|
|
|
if (u->org > u->file->b.nc) /* will be 0 because of reset(), but safety
|
|
|
|
first */
|
|
|
|
u->org = 0;
|
|
|
|
textresize(u, u->all, TRUE);
|
|
|
|
textbacknl(u, u->org, 0); /* go to beginning of line */
|
|
|
|
}
|
|
|
|
textsetselect(u, q0, q0);
|
|
|
|
}
|
|
|
|
if (nulls)
|
|
|
|
warning(nil, "%s: NUL bytes elided\n", file);
|
|
|
|
free(d);
|
|
|
|
return q1 - q0;
|
|
|
|
|
|
|
|
Rescue:
|
|
|
|
close(fd);
|
|
|
|
return -1;
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
uint textbsinsert(Text* t, uint q0, Rune* r, uint n, int tofile, int* nrp) {
|
|
|
|
Rune *bp, *tp, *up;
|
|
|
|
int i, initial;
|
|
|
|
|
|
|
|
if (t->what == Tag) { /* can't happen but safety first: mustn't backspace over
|
|
|
|
file name */
|
|
|
|
Err:
|
|
|
|
textinsert(t, q0, r, n, tofile);
|
|
|
|
*nrp = n;
|
|
|
|
return q0;
|
|
|
|
}
|
|
|
|
bp = r;
|
|
|
|
for (i = 0; i < n; i++)
|
|
|
|
if (*bp++ == '\b') {
|
|
|
|
--bp;
|
|
|
|
initial = 0;
|
|
|
|
tp = runemalloc(n);
|
|
|
|
runemove(tp, r, i);
|
|
|
|
up = tp + i;
|
|
|
|
for (; i < n; i++) {
|
|
|
|
*up = *bp++;
|
|
|
|
if (*up == '\b')
|
|
|
|
if (up == tp)
|
|
|
|
initial++;
|
|
|
|
else
|
|
|
|
--up;
|
|
|
|
else
|
|
|
|
up++;
|
|
|
|
}
|
|
|
|
if (initial) {
|
|
|
|
if (initial > q0)
|
|
|
|
initial = q0;
|
|
|
|
q0 -= initial;
|
|
|
|
textdelete(t, q0, q0 + initial, tofile);
|
|
|
|
}
|
|
|
|
n = up - tp;
|
|
|
|
textinsert(t, q0, tp, n, tofile);
|
|
|
|
free(tp);
|
|
|
|
*nrp = n;
|
|
|
|
return q0;
|
|
|
|
}
|
|
|
|
goto Err;
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
void textinsert(Text* t, uint q0, Rune* r, uint n, int tofile) {
|
|
|
|
int c, i;
|
|
|
|
Text* u;
|
|
|
|
|
|
|
|
if (tofile && t->ncache != 0)
|
|
|
|
error("text.insert");
|
|
|
|
if (n == 0)
|
|
|
|
return;
|
|
|
|
if (tofile) {
|
|
|
|
fileinsert(t->file, q0, r, n);
|
|
|
|
if (t->what == Body) {
|
|
|
|
t->w->dirty = TRUE;
|
|
|
|
t->w->utflastqid = -1;
|
|
|
|
}
|
|
|
|
if (t->file->ntext > 1)
|
|
|
|
for (i = 0; i < t->file->ntext; i++) {
|
|
|
|
u = t->file->text[i];
|
|
|
|
if (u != t) {
|
|
|
|
u->w->dirty = TRUE; /* always a body */
|
|
|
|
textinsert(u, q0, r, n, FALSE);
|
|
|
|
textsetselect(u, u->q0, u->q1);
|
|
|
|
textscrdraw(u);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (q0 < t->iq1)
|
|
|
|
t->iq1 += n;
|
|
|
|
if (q0 < t->q1)
|
|
|
|
t->q1 += n;
|
|
|
|
if (q0 < t->q0)
|
|
|
|
t->q0 += n;
|
|
|
|
if (q0 < t->org)
|
|
|
|
t->org += n;
|
|
|
|
else if (q0 <= t->org + t->fr.nchars)
|
|
|
|
frinsert(&t->fr, r, r + n, q0 - t->org);
|
|
|
|
if (t->w) {
|
|
|
|
c = 'i';
|
|
|
|
if (t->what == Body)
|
|
|
|
c = 'I';
|
|
|
|
if (n <= EVENTSIZE)
|
|
|
|
winevent(t->w, "%c%d %d 0 %d %.*S\n", c, q0, q0 + n, n, n, r);
|
|
|
|
else
|
|
|
|
winevent(t->w, "%c%d %d 0 0 \n", c, q0, q0 + n, n);
|
|
|
|
}
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
void typecommit(Text* t) {
|
|
|
|
if (t->w != nil)
|
|
|
|
wincommit(t->w, t);
|
|
|
|
else
|
|
|
|
textcommit(t, TRUE);
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
void textfill(Text* t) {
|
|
|
|
Rune* rp;
|
|
|
|
int i, n, m, nl;
|
|
|
|
|
|
|
|
if (t->fr.lastlinefull || t->nofill)
|
|
|
|
return;
|
|
|
|
if (t->ncache > 0)
|
|
|
|
typecommit(t);
|
|
|
|
rp = fbufalloc();
|
|
|
|
do {
|
|
|
|
n = t->file->b.nc - (t->org + t->fr.nchars);
|
|
|
|
if (n == 0)
|
|
|
|
break;
|
|
|
|
if (n > 2000) /* educated guess at reasonable amount */
|
|
|
|
n = 2000;
|
|
|
|
bufread(&t->file->b, t->org + t->fr.nchars, rp, n);
|
|
|
|
/*
|
|
|
|
* it's expensive to frinsert more than we need, so
|
|
|
|
* count newlines.
|
|
|
|
*/
|
|
|
|
nl = t->fr.maxlines - t->fr.nlines;
|
|
|
|
m = 0;
|
|
|
|
for (i = 0; i < n;) {
|
|
|
|
if (rp[i++] == '\n') {
|
|
|
|
m++;
|
|
|
|
if (m >= nl)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
frinsert(&t->fr, rp, rp + i, t->fr.nchars);
|
|
|
|
} while (t->fr.lastlinefull == FALSE);
|
|
|
|
fbuffree(rp);
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
void textdelete(Text* t, uint q0, uint q1, int tofile) {
|
|
|
|
uint n, p0, p1;
|
|
|
|
int i, c;
|
|
|
|
Text* u;
|
|
|
|
|
|
|
|
if (tofile && t->ncache != 0)
|
|
|
|
error("text.delete");
|
|
|
|
n = q1 - q0;
|
|
|
|
if (n == 0)
|
|
|
|
return;
|
|
|
|
if (tofile) {
|
|
|
|
filedelete(t->file, q0, q1);
|
|
|
|
if (t->what == Body) {
|
|
|
|
t->w->dirty = TRUE;
|
|
|
|
t->w->utflastqid = -1;
|
|
|
|
}
|
|
|
|
if (t->file->ntext > 1)
|
|
|
|
for (i = 0; i < t->file->ntext; i++) {
|
|
|
|
u = t->file->text[i];
|
|
|
|
if (u != t) {
|
|
|
|
u->w->dirty = TRUE; /* always a body */
|
|
|
|
textdelete(u, q0, q1, FALSE);
|
|
|
|
textsetselect(u, u->q0, u->q1);
|
|
|
|
textscrdraw(u);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (q0 < t->iq1)
|
|
|
|
t->iq1 -= min(n, t->iq1 - q0);
|
|
|
|
if (q0 < t->q0)
|
|
|
|
t->q0 -= min(n, t->q0 - q0);
|
|
|
|
if (q0 < t->q1)
|
|
|
|
t->q1 -= min(n, t->q1 - q0);
|
|
|
|
if (q1 <= t->org)
|
|
|
|
t->org -= n;
|
|
|
|
else if (q0 < t->org + t->fr.nchars) {
|
|
|
|
p1 = q1 - t->org;
|
|
|
|
if (p1 > t->fr.nchars)
|
|
|
|
p1 = t->fr.nchars;
|
|
|
|
if (q0 < t->org) {
|
|
|
|
t->org = q0;
|
|
|
|
p0 = 0;
|
|
|
|
} else
|
|
|
|
p0 = q0 - t->org;
|
|
|
|
frdelete(&t->fr, p0, p1);
|
|
|
|
textfill(t);
|
|
|
|
}
|
|
|
|
if (t->w) {
|
|
|
|
c = 'd';
|
|
|
|
if (t->what == Body)
|
|
|
|
c = 'D';
|
|
|
|
winevent(t->w, "%c%d %d 0 0 \n", c, q0, q1);
|
|
|
|
}
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
void textconstrain(Text* t, uint q0, uint q1, uint* p0, uint* p1) {
|
|
|
|
*p0 = min(q0, t->file->b.nc);
|
|
|
|
*p1 = min(q1, t->file->b.nc);
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
Rune textreadc(Text* t, uint q) {
|
|
|
|
Rune r;
|
2019-11-14 23:15:48 +00:00
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
if (t->cq0 <= q && q < t->cq0 + t->ncache)
|
|
|
|
r = t->cache[q - t->cq0];
|
|
|
|
else
|
|
|
|
bufread(&t->file->b, q, &r, 1);
|
|
|
|
return r;
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
static int spacesindentbswidth(Text* t) {
|
|
|
|
uint q, col;
|
|
|
|
Rune r;
|
|
|
|
|
|
|
|
col = textbswidth(t, 0x15);
|
|
|
|
q = t->q0;
|
|
|
|
while (q > 0) {
|
|
|
|
r = textreadc(t, q - 1);
|
|
|
|
if (r != ' ')
|
|
|
|
break;
|
|
|
|
q--;
|
|
|
|
if (--col % t->tabstop == 0)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (t->q0 == q)
|
|
|
|
return 1;
|
|
|
|
return t->q0 - q;
|
2020-11-17 19:26:25 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
int textbswidth(Text* t, Rune c) {
|
|
|
|
uint q, eq;
|
|
|
|
Rune r;
|
|
|
|
int skipping;
|
|
|
|
|
|
|
|
/* there is known to be at least one character to erase */
|
|
|
|
if (c == 0x08) { /* ^H: erase character */
|
|
|
|
if (t->what == Body && t->w->indent[SPACESINDENT])
|
|
|
|
return spacesindentbswidth(t);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
q = t->q0;
|
|
|
|
skipping = TRUE;
|
|
|
|
while (q > 0) {
|
|
|
|
r = textreadc(t, q - 1);
|
|
|
|
if (r == '\n') { /* eat at most one more character */
|
|
|
|
if (q == t->q0) /* eat the newline */
|
|
|
|
--q;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (c == 0x17) {
|
|
|
|
eq = isalnum(r);
|
|
|
|
if (eq && skipping) /* found one; stop skipping */
|
|
|
|
skipping = FALSE;
|
|
|
|
else if (!eq && !skipping)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
--q;
|
|
|
|
}
|
|
|
|
return t->q0 - q;
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
int textfilewidth(Text* t, uint q0, int oneelement) {
|
|
|
|
uint q;
|
|
|
|
Rune r;
|
|
|
|
|
|
|
|
q = q0;
|
|
|
|
while (q > 0) {
|
|
|
|
r = textreadc(t, q - 1);
|
|
|
|
if (r <= ' ')
|
|
|
|
break;
|
|
|
|
if (oneelement && r == '/')
|
|
|
|
break;
|
|
|
|
--q;
|
|
|
|
}
|
|
|
|
return q0 - q;
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
Rune* textcomplete(Text* t) {
|
|
|
|
int i, nstr, npath;
|
|
|
|
uint q;
|
|
|
|
Rune tmp[200];
|
|
|
|
Rune *str, *path;
|
|
|
|
Rune* rp;
|
|
|
|
Completion* c;
|
|
|
|
char *s, *dirs;
|
|
|
|
Runestr dir;
|
|
|
|
|
|
|
|
/* control-f: filename completion; works back to white space or / */
|
|
|
|
if (t->q0 < t->file->b.nc && textreadc(t, t->q0) > ' ') /* must be at end of
|
|
|
|
word */
|
|
|
|
return nil;
|
|
|
|
nstr = textfilewidth(t, t->q0, TRUE);
|
|
|
|
str = runemalloc(nstr);
|
|
|
|
npath = textfilewidth(t, t->q0 - nstr, FALSE);
|
|
|
|
path = runemalloc(npath);
|
|
|
|
|
|
|
|
c = nil;
|
|
|
|
rp = nil;
|
|
|
|
dirs = nil;
|
|
|
|
|
|
|
|
q = t->q0 - nstr;
|
|
|
|
for (i = 0; i < nstr; i++)
|
|
|
|
str[i] = textreadc(t, q++);
|
|
|
|
q = t->q0 - nstr - npath;
|
|
|
|
for (i = 0; i < npath; i++)
|
|
|
|
path[i] = textreadc(t, q++);
|
|
|
|
/* is path rooted? if not, we need to make it relative to window path */
|
|
|
|
if (npath > 0 && path[0] == '/')
|
|
|
|
dir = runestr(path, npath);
|
|
|
|
else {
|
|
|
|
dir = dirname(t, nil, 0);
|
|
|
|
if (dir.nr + 1 + npath > nelem(tmp)) {
|
|
|
|
free(dir.r);
|
|
|
|
goto Return;
|
|
|
|
}
|
|
|
|
if (dir.nr == 0) {
|
|
|
|
dir.nr = 1;
|
|
|
|
dir.r = runestrdup(Ldot);
|
|
|
|
}
|
|
|
|
runemove(tmp, dir.r, dir.nr);
|
|
|
|
tmp[dir.nr] = '/';
|
|
|
|
runemove(tmp + dir.nr + 1, path, npath);
|
|
|
|
free(dir.r);
|
|
|
|
dir.r = tmp;
|
|
|
|
dir.nr += 1 + npath;
|
|
|
|
dir = cleanrname(dir);
|
|
|
|
}
|
|
|
|
|
|
|
|
s = smprint("%.*S", nstr, str);
|
|
|
|
dirs = smprint("%.*S", dir.nr, dir.r);
|
|
|
|
c = complete(dirs, s);
|
|
|
|
free(s);
|
|
|
|
if (c == nil) {
|
|
|
|
warning(nil, "error attempting completion: %r\n");
|
|
|
|
goto Return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!c->advance) {
|
|
|
|
warning(
|
|
|
|
nil,
|
|
|
|
"%.*S%s%.*S*%s\n",
|
|
|
|
dir.nr,
|
|
|
|
dir.r,
|
|
|
|
dir.nr > 0 && dir.r[dir.nr - 1] != '/' ? "/" : "",
|
|
|
|
nstr,
|
|
|
|
str,
|
|
|
|
c->nmatch ? "" : ": no matches in:");
|
|
|
|
for (i = 0; i < c->nfile; i++)
|
|
|
|
warning(nil, " %s\n", c->filename[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (c->advance)
|
|
|
|
rp = runesmprint("%s", c->string);
|
|
|
|
else
|
|
|
|
rp = nil;
|
|
|
|
Return:
|
|
|
|
freecompletion(c);
|
|
|
|
free(dirs);
|
|
|
|
free(str);
|
|
|
|
free(path);
|
|
|
|
return rp;
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
2021-06-13 05:54:54 +00:00
|
|
|
void texttype(Text* t, Rune r) {
|
|
|
|
uint q0, q1;
|
|
|
|
int nnb, nb, n, i;
|
|
|
|
int nr;
|
|
|
|
Rune* rp;
|
|
|
|
Text* u;
|
|
|
|
|
|
|
|
if (t->what != Body && t->what != Tag && r == '\n')
|
|
|
|
return;
|
|
|
|
if (t->what == Tag)
|
|
|
|
t->w->tagsafe = FALSE;
|
|
|
|
|
|
|
|
nr = 1;
|
|
|
|
rp = &r;
|
|
|
|
switch (r) {
|
|
|
|
case Kleft:
|
|
|
|
typecommit(t);
|
|
|
|
if (t->q0 > 0)
|
|
|
|
textshow(t, t->q0 - 1, t->q0 - 1, TRUE);
|
|
|
|
return;
|
|
|
|
case Kright:
|
|
|
|
typecommit(t);
|
|
|
|
if (t->q1 < t->file->b.nc)
|
|
|
|
textshow(t, t->q1 + 1, t->q1 + 1, TRUE);
|
2019-11-21 19:43:04 +00:00
|
|
|
return;
|
2021-06-13 05:54:54 +00:00
|
|
|
case Kdown:
|
|
|
|
typecommit(t);
|
|
|
|
q0 = t->q0;
|
|
|
|
nnb = 0;
|
|
|
|
if (t->q0 > 0 && textreadc(t, t->q0 - 1) != '\n')
|
|
|
|
nnb = textbswidth(t, 0x15);
|
|
|
|
while (q0 < t->file->b.nc && textreadc(t, q0) != '\n')
|
|
|
|
q0++;
|
|
|
|
if (q0 == t->file->b.nc) {
|
|
|
|
textshow(t, q0, q0, TRUE);
|
|
|
|
return;
|
|
|
|
}
|
2019-11-21 19:43:04 +00:00
|
|
|
q0++;
|
2021-06-13 05:54:54 +00:00
|
|
|
while (nnb >= 0 && q0 < t->file->b.nc) {
|
|
|
|
if (textreadc(t, q0) == '\n')
|
|
|
|
break;
|
|
|
|
nnb--;
|
|
|
|
if (nnb >= 0)
|
|
|
|
q0++;
|
|
|
|
}
|
|
|
|
textshow(t, q0, q0, TRUE);
|
|
|
|
return;
|
|
|
|
case Kscrollonedown:
|
|
|
|
if (t->what == Tag)
|
|
|
|
goto Tagdown;
|
|
|
|
n = mousescrollsize(t->fr.maxlines);
|
|
|
|
if (n <= 0)
|
|
|
|
n = 1;
|
|
|
|
goto case_Down;
|
|
|
|
case Kpgdown:
|
|
|
|
n = 2 * t->fr.maxlines / 3;
|
|
|
|
case_Down:
|
|
|
|
q0 =
|
|
|
|
t->org + frcharofpt(
|
|
|
|
&t->fr,
|
|
|
|
Pt(t->fr.r.min.x, t->fr.r.min.y + n * t->fr.font->height));
|
|
|
|
textsetorigin(t, q0, TRUE);
|
|
|
|
return;
|
|
|
|
case Kup:
|
|
|
|
typecommit(t);
|
|
|
|
nnb = 0;
|
|
|
|
if (t->q0 > 0 && textreadc(t, t->q0 - 1) != '\n')
|
|
|
|
nnb = textbswidth(t, 0x15);
|
|
|
|
q1 = nnb;
|
|
|
|
if (t->q0 - nnb > 1 && textreadc(t, t->q0 - nnb - 1) == '\n')
|
|
|
|
nnb++;
|
|
|
|
q0 = t->q0 - nnb;
|
|
|
|
textshow(t, q0, q0, TRUE);
|
|
|
|
|
|
|
|
nnb = textbswidth(t, 0x15);
|
|
|
|
if (nnb <= 1)
|
|
|
|
return;
|
|
|
|
q0 = q0 - nnb;
|
|
|
|
while (q1 > 0 && textreadc(t, q0) != '\n') {
|
|
|
|
q1--;
|
|
|
|
q0++;
|
|
|
|
}
|
|
|
|
textshow(t, q0, q0, TRUE);
|
|
|
|
return;
|
|
|
|
case Kscrolloneup:
|
|
|
|
if (t->what == Tag)
|
|
|
|
goto Tagup;
|
|
|
|
n = mousescrollsize(t->fr.maxlines);
|
|
|
|
goto case_Up;
|
|
|
|
case Kpgup:
|
|
|
|
n = 2 * t->fr.maxlines / 3;
|
|
|
|
case_Up:
|
|
|
|
q0 = textbacknl(t, t->org, n);
|
|
|
|
textsetorigin(t, q0, TRUE);
|
|
|
|
return;
|
|
|
|
case Khome:
|
|
|
|
case 0x01: /* ^A: beginning of line */
|
|
|
|
typecommit(t);
|
|
|
|
/* go to where ^U would erase, if not already at BOL */
|
|
|
|
nnb = 0;
|
|
|
|
if (t->q0 > 0 && textreadc(t, t->q0 - 1) != '\n')
|
|
|
|
nnb = textbswidth(t, 0x15);
|
|
|
|
textshow(t, t->q0 - nnb, t->q0 - nnb, TRUE);
|
|
|
|
return;
|
|
|
|
case Kend:
|
|
|
|
case 0x05: /* ^E: end of line */
|
|
|
|
typecommit(t);
|
|
|
|
q0 = t->q0;
|
|
|
|
while (q0 < t->file->b.nc && textreadc(t, q0) != '\n')
|
|
|
|
q0++;
|
|
|
|
textshow(t, q0, q0, TRUE);
|
|
|
|
return;
|
|
|
|
case 0x03: /* Ctrl-c: copy */
|
|
|
|
case Kcmd + 'c': /* %C: copy */
|
|
|
|
typecommit(t);
|
|
|
|
cut(t, t, nil, TRUE, FALSE, nil, 0);
|
|
|
|
return;
|
|
|
|
case 0x1a: /* Ctrl-z: undo */
|
|
|
|
case Kcmd + 'z': /* %Z: undo */
|
|
|
|
typecommit(t);
|
|
|
|
undo(t, nil, nil, TRUE, 0, nil, 0);
|
|
|
|
return;
|
|
|
|
case 0x19: /* Ctrl-y: redo */
|
|
|
|
case Kcmd + 'Z': /* %-shift-Z: redo */
|
|
|
|
typecommit(t);
|
|
|
|
undo(t, nil, nil, FALSE, 0, nil, 0);
|
|
|
|
return;
|
|
|
|
case 0x13: /* Ctrl-S: put file */
|
|
|
|
case Kcmd + 's':
|
|
|
|
typecommit(t);
|
|
|
|
put(&(t->w)->body, nil, nil, XXX, XXX, nil, 0);
|
|
|
|
return;
|
|
|
|
Tagdown:
|
|
|
|
/* expand tag to show all text */
|
|
|
|
if (!t->w->tagexpand) {
|
|
|
|
t->w->tagexpand = TRUE;
|
|
|
|
winresize(t->w, t->w->r, FALSE, TRUE);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
|
|
|
|
Tagup:
|
|
|
|
/* shrink tag to single line */
|
|
|
|
if (t->w->tagexpand) {
|
|
|
|
t->w->tagexpand = FALSE;
|
|
|
|
t->w->taglines = 1;
|
|
|
|
winresize(t->w, t->w->r, FALSE, TRUE);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (t->what == Body) {
|
|
|
|
seq++;
|
|
|
|
filemark(t->file);
|
|
|
|
}
|
|
|
|
/* cut/paste must be done after the seq++/filemark */
|
|
|
|
switch (r) {
|
|
|
|
case 0x18: /* Ctrl-X: cut */
|
|
|
|
case Kcmd + 'x': /* %X: cut */
|
|
|
|
typecommit(t);
|
|
|
|
if (t->what == Body) {
|
|
|
|
seq++;
|
|
|
|
filemark(t->file);
|
|
|
|
}
|
|
|
|
cut(t, t, nil, TRUE, TRUE, nil, 0);
|
|
|
|
textshow(t, t->q0, t->q0, 1);
|
|
|
|
t->iq1 = t->q0;
|
|
|
|
return;
|
|
|
|
case 0x16: /* Ctrl-V: paste */
|
|
|
|
case Kcmd + 'v': /* %V: paste */
|
|
|
|
typecommit(t);
|
|
|
|
if (t->what == Body) {
|
|
|
|
seq++;
|
|
|
|
filemark(t->file);
|
|
|
|
}
|
|
|
|
paste(t, t, nil, TRUE, FALSE, nil, 0);
|
|
|
|
textshow(t, t->q0, t->q1, 1);
|
|
|
|
t->iq1 = t->q1;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (t->q1 > t->q0) {
|
|
|
|
if (t->ncache != 0)
|
|
|
|
error("text.type");
|
|
|
|
cut(t, t, nil, TRUE, TRUE, nil, 0);
|
|
|
|
t->eq0 = ~0;
|
|
|
|
}
|
|
|
|
textshow(t, t->q0, t->q0, 1);
|
|
|
|
switch (r) {
|
|
|
|
case 0x06: /* ^F: complete */
|
|
|
|
case Kins:
|
|
|
|
typecommit(t);
|
|
|
|
rp = textcomplete(t);
|
|
|
|
if (rp == nil)
|
|
|
|
return;
|
|
|
|
nr = runestrlen(rp);
|
|
|
|
break; /* fall through to normal insertion case */
|
|
|
|
case 0x1B:
|
|
|
|
if (t->eq0 != ~0) {
|
|
|
|
if (t->eq0 <= t->q0)
|
|
|
|
textsetselect(t, t->eq0, t->q0);
|
|
|
|
else
|
|
|
|
textsetselect(t, t->q0, t->eq0);
|
|
|
|
}
|
|
|
|
if (t->ncache > 0)
|
|
|
|
typecommit(t);
|
|
|
|
t->iq1 = t->q0;
|
|
|
|
return;
|
|
|
|
case 0x08: /* ^H: erase character */
|
|
|
|
case 0x15: /* ^U: erase line */
|
|
|
|
case 0x17: /* ^W: erase word */
|
|
|
|
if (t->q0 == 0) /* nothing to erase */
|
|
|
|
return;
|
|
|
|
nnb = textbswidth(t, r);
|
|
|
|
q1 = t->q0;
|
|
|
|
q0 = q1 - nnb;
|
|
|
|
/* if selection is at beginning of window, avoid deleting invisible text
|
|
|
|
*/
|
|
|
|
if (q0 < t->org) {
|
|
|
|
q0 = t->org;
|
|
|
|
nnb = q1 - q0;
|
|
|
|
}
|
|
|
|
if (nnb <= 0)
|
|
|
|
return;
|
|
|
|
for (i = 0; i < t->file->ntext; i++) {
|
|
|
|
u = t->file->text[i];
|
|
|
|
u->nofill = TRUE;
|
|
|
|
nb = nnb;
|
|
|
|
n = u->ncache;
|
|
|
|
if (n > 0) {
|
|
|
|
if (q1 != u->cq0 + n)
|
|
|
|
error("text.type backspace");
|
|
|
|
if (n > nb)
|
|
|
|
n = nb;
|
|
|
|
u->ncache -= n;
|
|
|
|
textdelete(u, q1 - n, q1, FALSE);
|
|
|
|
nb -= n;
|
|
|
|
}
|
|
|
|
if (u->eq0 == q1 || u->eq0 == ~0)
|
|
|
|
u->eq0 = q0;
|
|
|
|
if (nb && u == t)
|
|
|
|
textdelete(u, q0, q0 + nb, TRUE);
|
|
|
|
if (u != t)
|
|
|
|
textsetselect(u, u->q0, u->q1);
|
|
|
|
else
|
|
|
|
textsetselect(t, q0, q0);
|
|
|
|
u->nofill = FALSE;
|
|
|
|
}
|
|
|
|
for (i = 0; i < t->file->ntext; i++)
|
|
|
|
textfill(t->file->text[i]);
|
|
|
|
t->iq1 = t->q0;
|
|
|
|
return;
|
|
|
|
case '\t':
|
|
|
|
if (t->what == Body && t->w->indent[SPACESINDENT]) {
|
|
|
|
nnb = textbswidth(t, 0x15);
|
|
|
|
if (nnb == 1 && textreadc(t, t->q0 - 1) == '\n')
|
|
|
|
nnb = 0;
|
|
|
|
nnb = t->tabstop - nnb % t->tabstop;
|
|
|
|
rp = runemalloc(nnb);
|
|
|
|
for (nr = 0; nr < nnb; nr++)
|
|
|
|
rp[nr] = ' ';
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case '\n':
|
|
|
|
if (t->what == Body && t->w->indent[AUTOINDENT]) {
|
|
|
|
/* find beginning of previous line using backspace code */
|
|
|
|
nnb = textbswidth(t, 0x15); /* ^U case */
|
|
|
|
rp = runemalloc(nnb + 1);
|
|
|
|
nr = 0;
|
|
|
|
rp[nr++] = r;
|
|
|
|
for (i = 0; i < nnb; i++) {
|
|
|
|
r = textreadc(t, t->q0 - nnb + i);
|
|
|
|
if (r != ' ' && r != '\t')
|
|
|
|
break;
|
|
|
|
rp[nr++] = r;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break; /* fall through to normal code */
|
|
|
|
}
|
|
|
|
/* otherwise ordinary character; just insert, typically in caches of all texts
|
|
|
|
*/
|
|
|
|
for (i = 0; i < t->file->ntext; i++) {
|
|
|
|
u = t->file->text[i];
|
|
|
|
if (u->eq0 == ~0)
|
|
|
|
u->eq0 = t->q0;
|
|
|
|
if (u->ncache == 0)
|
|
|
|
u->cq0 = t->q0;
|
|
|
|
else if (t->q0 != u->cq0 + u->ncache)
|
|
|
|
error("text.type cq1");
|
|
|
|
/*
|
|
|
|
* Change the tag before we add to ncache,
|
|
|
|
* so that if the window body is resized the
|
|
|
|
* commit will not find anything in ncache.
|
|
|
|
*/
|
|
|
|
if (u->what == Body && u->ncache == 0) {
|
|
|
|
u->needundo = TRUE;
|
|
|
|
winsettag(t->w);
|
|
|
|
u->needundo = FALSE;
|
|
|
|
}
|
|
|
|
textinsert(u, t->q0, rp, nr, FALSE);
|
|
|
|
if (u != t)
|
|
|
|
textsetselect(u, u->q0, u->q1);
|
|
|
|
if (u->ncache + nr > u->ncachealloc) {
|
|
|
|
u->ncachealloc += 10 + nr;
|
|
|
|
u->cache = runerealloc(u->cache, u->ncachealloc);
|
2019-11-21 19:43:04 +00:00
|
|
|
}
|
2021-06-13 05:54:54 +00:00
|
|
|
runemove(u->cache + u->ncache, rp, nr);
|
|
|
|
u->ncache += nr;
|
|
|
|
}
|
|
|
|
if (rp != &r)
|
|
|
|
free(rp);
|
|
|
|
textsetselect(t, t->q0 + nr, t->q0 + nr);
|
|
|
|
if (r == '\n' && t->w != nil)
|
|
|
|
wincommit(t->w, t);
|
|
|
|
t->iq1 = t->q0;
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
void textcommit(Text* t, int tofile) {
|
|
|
|
if (t->ncache == 0)
|
|
|
|
return;
|
|
|
|
if (tofile)
|
|
|
|
fileinsert(t->file, t->cq0, t->cache, t->ncache);
|
|
|
|
if (t->what == Body) {
|
|
|
|
t->w->dirty = TRUE;
|
|
|
|
t->w->utflastqid = -1;
|
|
|
|
}
|
|
|
|
t->ncache = 0;
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
static Text* clicktext;
|
|
|
|
static uint clickmsec;
|
|
|
|
static Text* selecttext;
|
|
|
|
static uint selectq;
|
2019-11-14 23:15:48 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* called from frame library
|
|
|
|
*/
|
2021-06-13 05:54:54 +00:00
|
|
|
void framescroll(Frame* f, int dl) {
|
|
|
|
if (f != &selecttext->fr)
|
|
|
|
error("frameselect not right frame");
|
|
|
|
textframescroll(selecttext, dl);
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
void textframescroll(Text* t, int dl) {
|
|
|
|
uint q0;
|
|
|
|
|
|
|
|
if (dl == 0) {
|
|
|
|
scrsleep(100);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (dl < 0) {
|
|
|
|
q0 = textbacknl(t, t->org, -dl);
|
|
|
|
if (selectq > t->org + t->fr.p0)
|
|
|
|
textsetselect(t, t->org + t->fr.p0, selectq);
|
|
|
|
else
|
|
|
|
textsetselect(t, selectq, t->org + t->fr.p0);
|
|
|
|
} else {
|
|
|
|
if (t->org + t->fr.nchars == t->file->b.nc)
|
|
|
|
return;
|
|
|
|
q0 = t->org + frcharofpt(
|
|
|
|
&t->fr,
|
|
|
|
Pt(t->fr.r.min.x, t->fr.r.min.y + dl * t->fr.font->height));
|
|
|
|
if (selectq > t->org + t->fr.p1)
|
|
|
|
textsetselect(t, t->org + t->fr.p1, selectq);
|
|
|
|
else
|
|
|
|
textsetselect(t, selectq, t->org + t->fr.p1);
|
|
|
|
}
|
|
|
|
textsetorigin(t, q0, TRUE);
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
void textselect(Text* t) {
|
|
|
|
uint q0, q1;
|
|
|
|
int b, x, y;
|
|
|
|
int state;
|
|
|
|
enum { None, Cut, Paste };
|
|
|
|
|
|
|
|
selecttext = t;
|
|
|
|
/*
|
|
|
|
* To have double-clicking and chording, we double-click
|
|
|
|
* immediately if it might make sense.
|
|
|
|
*/
|
|
|
|
b = mouse->buttons;
|
|
|
|
q0 = t->q0;
|
|
|
|
q1 = t->q1;
|
|
|
|
selectq = t->org + frcharofpt(&t->fr, mouse->xy);
|
|
|
|
if (clicktext == t && mouse->msec - clickmsec < 500)
|
|
|
|
if (q0 == q1 && selectq == q0) {
|
|
|
|
textdoubleclick(t, &q0, &q1);
|
|
|
|
textsetselect(t, q0, q1);
|
|
|
|
flushimage(display, 1);
|
|
|
|
x = mouse->xy.x;
|
|
|
|
y = mouse->xy.y;
|
|
|
|
/* stay here until something interesting happens */
|
|
|
|
do
|
|
|
|
readmouse(mousectl);
|
|
|
|
while (mouse->buttons == b && abs(mouse->xy.x - x) < 3 &&
|
|
|
|
abs(mouse->xy.y - y) < 3);
|
|
|
|
mouse->xy.x = x; /* in case we're calling frselect */
|
|
|
|
mouse->xy.y = y;
|
|
|
|
q0 = t->q0; /* may have changed */
|
|
|
|
q1 = t->q1;
|
|
|
|
selectq = q0;
|
|
|
|
}
|
|
|
|
if (mouse->buttons == b) {
|
|
|
|
t->fr.scroll = framescroll;
|
|
|
|
frselect(&t->fr, mousectl);
|
|
|
|
/* horrible botch: while asleep, may have lost selection altogether */
|
|
|
|
if (selectq > t->file->b.nc)
|
|
|
|
selectq = t->org + t->fr.p0;
|
|
|
|
t->fr.scroll = nil;
|
|
|
|
if (selectq < t->org)
|
|
|
|
q0 = selectq;
|
|
|
|
else
|
|
|
|
q0 = t->org + t->fr.p0;
|
|
|
|
if (selectq > t->org + t->fr.nchars)
|
|
|
|
q1 = selectq;
|
|
|
|
else
|
|
|
|
q1 = t->org + t->fr.p1;
|
|
|
|
}
|
|
|
|
if (q0 == q1) {
|
|
|
|
if (q0 == t->q0 && clicktext == t && mouse->msec - clickmsec < 500) {
|
|
|
|
textdoubleclick(t, &q0, &q1);
|
|
|
|
clicktext = nil;
|
|
|
|
} else {
|
|
|
|
clicktext = t;
|
|
|
|
clickmsec = mouse->msec;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
clicktext = nil;
|
|
|
|
textsetselect(t, q0, q1);
|
|
|
|
flushimage(display, 1);
|
|
|
|
state = None; /* what we've done; undo when possible */
|
|
|
|
while (mouse->buttons) {
|
|
|
|
mouse->msec = 0;
|
|
|
|
b = mouse->buttons;
|
|
|
|
if ((b & 1) && (b & 6)) {
|
|
|
|
if (state == None && t->what == Body) {
|
|
|
|
seq++;
|
|
|
|
filemark(t->w->body.file);
|
|
|
|
}
|
|
|
|
if (b & 2) {
|
|
|
|
if (state == Paste && t->what == Body) {
|
|
|
|
winundo(t->w, TRUE);
|
|
|
|
textsetselect(t, q0, t->q1);
|
|
|
|
state = None;
|
|
|
|
} else if (state != Cut) {
|
|
|
|
cut(t, t, nil, TRUE, TRUE, nil, 0);
|
|
|
|
state = Cut;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (state == Cut && t->what == Body) {
|
|
|
|
winundo(t->w, TRUE);
|
|
|
|
textsetselect(t, q0, t->q1);
|
|
|
|
state = None;
|
|
|
|
} else if (state != Paste) {
|
|
|
|
paste(t, t, nil, TRUE, FALSE, nil, 0);
|
|
|
|
state = Paste;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
textscrdraw(t);
|
|
|
|
clearmouse();
|
|
|
|
}
|
|
|
|
flushimage(display, 1);
|
|
|
|
while (mouse->buttons == b)
|
|
|
|
readmouse(mousectl);
|
|
|
|
clicktext = nil;
|
|
|
|
}
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
void textshow(Text* t, uint q0, uint q1, int doselect) {
|
|
|
|
int qe;
|
|
|
|
int nl;
|
|
|
|
int tsd;
|
|
|
|
int nc;
|
|
|
|
uint q;
|
|
|
|
|
|
|
|
if (t->what != Body) {
|
|
|
|
if (doselect)
|
|
|
|
textsetselect(t, q0, q1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (t->w != nil && t->fr.maxlines == 0)
|
|
|
|
colgrow(t->col, t->w, 1);
|
|
|
|
if (doselect)
|
|
|
|
textsetselect(t, q0, q1);
|
|
|
|
qe = t->org + t->fr.nchars;
|
|
|
|
tsd = FALSE; /* do we call textscrdraw? */
|
|
|
|
nc = t->file->b.nc + t->ncache;
|
|
|
|
if (t->org <= q0) {
|
|
|
|
if (nc == 0 || q0 < qe)
|
|
|
|
tsd = TRUE;
|
|
|
|
else if (q0 == qe && qe == nc) {
|
|
|
|
if (textreadc(t, nc - 1) == '\n') {
|
|
|
|
if (t->fr.nlines < t->fr.maxlines)
|
|
|
|
tsd = TRUE;
|
|
|
|
} else
|
|
|
|
tsd = TRUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (tsd)
|
|
|
|
textscrdraw(t);
|
|
|
|
else {
|
|
|
|
if (t->w->nopen[QWevent] > 0)
|
|
|
|
nl = 3 * t->fr.maxlines / 4;
|
|
|
|
else
|
|
|
|
nl = t->fr.maxlines / 4;
|
|
|
|
q = textbacknl(t, q0, nl);
|
|
|
|
/* avoid going backwards if trying to go forwards - long lines! */
|
|
|
|
if (!(q0 > t->org && q < t->org))
|
|
|
|
textsetorigin(t, q, TRUE);
|
|
|
|
while (q0 > t->org + t->fr.nchars)
|
|
|
|
textsetorigin(t, t->org + 1, FALSE);
|
|
|
|
}
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
static int region(int a, int b) {
|
|
|
|
if (a < b)
|
|
|
|
return -1;
|
|
|
|
if (a == b)
|
|
|
|
return 0;
|
|
|
|
return 1;
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
void selrestore(Frame* f, Point pt0, uint p0, uint p1) {
|
|
|
|
if (p1 <= f->p0 || p0 >= f->p1) {
|
|
|
|
/* no overlap */
|
|
|
|
frdrawsel0(f, pt0, p0, p1, f->cols[BACK], f->cols[TEXT]);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (p0 >= f->p0 && p1 <= f->p1) {
|
|
|
|
/* entirely inside */
|
|
|
|
frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* they now are known to overlap */
|
|
|
|
|
|
|
|
/* before selection */
|
|
|
|
if (p0 < f->p0) {
|
|
|
|
frdrawsel0(f, pt0, p0, f->p0, f->cols[BACK], f->cols[TEXT]);
|
|
|
|
p0 = f->p0;
|
|
|
|
pt0 = frptofchar(f, p0);
|
|
|
|
}
|
|
|
|
/* after selection */
|
|
|
|
if (p1 > f->p1) {
|
|
|
|
frdrawsel0(
|
|
|
|
f,
|
|
|
|
frptofchar(f, f->p1),
|
|
|
|
f->p1,
|
|
|
|
p1,
|
|
|
|
f->cols[BACK],
|
|
|
|
f->cols[TEXT]);
|
|
|
|
p1 = f->p1;
|
|
|
|
}
|
|
|
|
/* inside selection */
|
|
|
|
frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
void textsetselect(Text* t, uint q0, uint q1) {
|
|
|
|
int p0, p1, ticked;
|
|
|
|
|
|
|
|
/* t->fr.p0 and t->fr.p1 are always right; t->q0 and t->q1 may be off */
|
|
|
|
t->q0 = q0;
|
|
|
|
t->q1 = q1;
|
|
|
|
/* compute desired p0,p1 from q0,q1 */
|
|
|
|
p0 = q0 - t->org;
|
|
|
|
p1 = q1 - t->org;
|
|
|
|
ticked = 1;
|
|
|
|
if (p0 < 0) {
|
|
|
|
ticked = 0;
|
|
|
|
p0 = 0;
|
|
|
|
}
|
|
|
|
if (p1 < 0)
|
|
|
|
p1 = 0;
|
|
|
|
if (p0 > t->fr.nchars)
|
|
|
|
p0 = t->fr.nchars;
|
|
|
|
if (p1 > t->fr.nchars) {
|
|
|
|
ticked = 0;
|
|
|
|
p1 = t->fr.nchars;
|
|
|
|
}
|
|
|
|
if (p0 == t->fr.p0 && p1 == t->fr.p1) {
|
|
|
|
if (p0 == p1 && ticked != t->fr.ticked)
|
|
|
|
frtick(&t->fr, frptofchar(&t->fr, p0), ticked);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (p0 > p1)
|
|
|
|
sysfatal(
|
|
|
|
"acme: textsetselect p0=%d p1=%d q0=%ud q1=%ud t->org=%d nchars=%d",
|
|
|
|
p0,
|
|
|
|
p1,
|
|
|
|
q0,
|
|
|
|
q1,
|
|
|
|
(int)t->org,
|
|
|
|
(int)t->fr.nchars);
|
|
|
|
/* screen disagrees with desired selection */
|
|
|
|
if (t->fr.p1 <= p0 || p1 <= t->fr.p0 || p0 == p1 || t->fr.p1 == t->fr.p0) {
|
|
|
|
/* no overlap or too easy to bother trying */
|
|
|
|
frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p0), t->fr.p0, t->fr.p1, 0);
|
|
|
|
if (p0 != p1 || ticked)
|
|
|
|
frdrawsel(&t->fr, frptofchar(&t->fr, p0), p0, p1, 1);
|
|
|
|
goto Return;
|
|
|
|
}
|
|
|
|
/* overlap; avoid unnecessary painting */
|
|
|
|
if (p0 < t->fr.p0) {
|
|
|
|
/* extend selection backwards */
|
|
|
|
frdrawsel(&t->fr, frptofchar(&t->fr, p0), p0, t->fr.p0, 1);
|
|
|
|
} else if (p0 > t->fr.p0) {
|
|
|
|
/* trim first part of selection */
|
|
|
|
frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p0), t->fr.p0, p0, 0);
|
|
|
|
}
|
|
|
|
if (p1 > t->fr.p1) {
|
|
|
|
/* extend selection forwards */
|
|
|
|
frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p1), t->fr.p1, p1, 1);
|
|
|
|
} else if (p1 < t->fr.p1) {
|
|
|
|
/* trim last part of selection */
|
|
|
|
frdrawsel(&t->fr, frptofchar(&t->fr, p1), p1, t->fr.p1, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
Return:
|
|
|
|
t->fr.p0 = p0;
|
|
|
|
t->fr.p1 = p1;
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Release the button in less than DELAY ms and it's considered a null selection
|
|
|
|
* if the mouse hardly moved, regardless of whether it crossed a char boundary.
|
|
|
|
*/
|
2021-06-13 05:54:54 +00:00
|
|
|
enum { DELAY = 2, MINMOVE = 4 };
|
2019-11-14 23:15:48 +00:00
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
uint xselect(
|
|
|
|
Frame* f, Mousectl* mc, Image* col,
|
|
|
|
uint* p1p) /* when called, button is down */
|
2019-11-14 23:15:48 +00:00
|
|
|
{
|
2021-06-13 05:54:54 +00:00
|
|
|
uint p0, p1, q, tmp;
|
|
|
|
ulong msec;
|
|
|
|
Point mp, pt0, pt1, qt;
|
|
|
|
int reg, b;
|
|
|
|
|
|
|
|
mp = mc->m.xy;
|
|
|
|
b = mc->m.buttons;
|
|
|
|
msec = mc->m.msec;
|
|
|
|
|
|
|
|
/* remove tick */
|
|
|
|
if (f->p0 == f->p1)
|
|
|
|
frtick(f, frptofchar(f, f->p0), 0);
|
|
|
|
p0 = p1 = frcharofpt(f, mp);
|
|
|
|
pt0 = frptofchar(f, p0);
|
|
|
|
pt1 = frptofchar(f, p1);
|
|
|
|
reg = 0;
|
|
|
|
frtick(f, pt0, 1);
|
|
|
|
do {
|
|
|
|
q = frcharofpt(f, mc->m.xy);
|
|
|
|
if (p1 != q) {
|
|
|
|
if (p0 == p1)
|
|
|
|
frtick(f, pt0, 0);
|
|
|
|
if (reg != region(q, p0)) { /* crossed starting point; reset */
|
|
|
|
if (reg > 0)
|
|
|
|
selrestore(f, pt0, p0, p1);
|
|
|
|
else if (reg < 0)
|
|
|
|
selrestore(f, pt1, p1, p0);
|
|
|
|
p1 = p0;
|
|
|
|
pt1 = pt0;
|
|
|
|
reg = region(q, p0);
|
|
|
|
if (reg == 0)
|
|
|
|
frdrawsel0(f, pt0, p0, p1, col, display->white);
|
|
|
|
}
|
|
|
|
qt = frptofchar(f, q);
|
|
|
|
if (reg > 0) {
|
|
|
|
if (q > p1)
|
|
|
|
frdrawsel0(f, pt1, p1, q, col, display->white);
|
|
|
|
|
|
|
|
else if (q < p1)
|
|
|
|
selrestore(f, qt, q, p1);
|
|
|
|
} else if (reg < 0) {
|
|
|
|
if (q > p1)
|
|
|
|
selrestore(f, pt1, p1, q);
|
|
|
|
else
|
|
|
|
frdrawsel0(f, qt, q, p1, col, display->white);
|
|
|
|
}
|
|
|
|
p1 = q;
|
|
|
|
pt1 = qt;
|
|
|
|
}
|
|
|
|
if (p0 == p1)
|
|
|
|
frtick(f, pt0, 1);
|
|
|
|
flushimage(f->display, 1);
|
|
|
|
readmouse(mc);
|
|
|
|
} while (mc->m.buttons == b);
|
|
|
|
if (
|
|
|
|
mc->m.msec - msec < DELAY && p0 != p1 && abs(mp.x - mc->m.xy.x) < MINMOVE &&
|
|
|
|
abs(mp.y - mc->m.xy.y) < MINMOVE) {
|
|
|
|
if (reg > 0)
|
|
|
|
selrestore(f, pt0, p0, p1);
|
|
|
|
else if (reg < 0)
|
|
|
|
selrestore(f, pt1, p1, p0);
|
|
|
|
p1 = p0;
|
|
|
|
}
|
|
|
|
if (p1 < p0) {
|
|
|
|
tmp = p0;
|
|
|
|
p0 = p1;
|
|
|
|
p1 = tmp;
|
|
|
|
}
|
|
|
|
pt0 = frptofchar(f, p0);
|
|
|
|
if (p0 == p1)
|
|
|
|
frtick(f, pt0, 0);
|
|
|
|
selrestore(f, pt0, p0, p1);
|
|
|
|
/* restore tick */
|
|
|
|
if (f->p0 == f->p1)
|
|
|
|
frtick(f, frptofchar(f, f->p0), 1);
|
|
|
|
flushimage(f->display, 1);
|
|
|
|
*p1p = p1;
|
|
|
|
return p0;
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
int textselect23(Text* t, uint* q0, uint* q1, Image* high, int mask) {
|
|
|
|
uint p0, p1;
|
|
|
|
int buts;
|
|
|
|
|
|
|
|
p0 = xselect(&t->fr, mousectl, high, &p1);
|
|
|
|
buts = mousectl->m.buttons;
|
|
|
|
if ((buts & mask) == 0) {
|
|
|
|
*q0 = p0 + t->org;
|
|
|
|
*q1 = p1 + t->org;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (mousectl->m.buttons)
|
|
|
|
readmouse(mousectl);
|
|
|
|
return buts;
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
int textselect2(Text* t, uint* q0, uint* q1, Text** tp) {
|
|
|
|
int buts;
|
|
|
|
|
|
|
|
*tp = nil;
|
|
|
|
buts = textselect23(t, q0, q1, but2col, 4);
|
|
|
|
if (buts & 4)
|
|
|
|
return 0;
|
|
|
|
if (buts & 1) { /* pick up argument */
|
|
|
|
*tp = argtext;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 1;
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
int textselect3(Text* t, uint* q0, uint* q1) {
|
|
|
|
int h;
|
2019-11-14 23:15:48 +00:00
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
h = (textselect23(t, q0, q1, but3col, 1 | 2) == 0);
|
|
|
|
return h;
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
static Rune left1[] = {'{', '[', '(', '<', 0xab, 0};
|
|
|
|
static Rune right1[] = {'}', ']', ')', '>', 0xbb, 0};
|
|
|
|
static Rune left2[] = {'\n', 0};
|
|
|
|
static Rune left3[] = {'\'', '"', '`', 0};
|
|
|
|
|
|
|
|
static Rune* left[] = {left1, left2, left3, nil};
|
|
|
|
static Rune* right[] = {right1, left2, left3, nil};
|
|
|
|
|
|
|
|
void textdoubleclick(Text* t, uint* q0, uint* q1) {
|
|
|
|
int c, i;
|
|
|
|
Rune *r, *l, *p;
|
|
|
|
uint q;
|
|
|
|
|
|
|
|
if (textclickhtmlmatch(t, q0, q1))
|
|
|
|
return;
|
|
|
|
|
|
|
|
for (i = 0; left[i] != nil; i++) {
|
|
|
|
q = *q0;
|
|
|
|
l = left[i];
|
|
|
|
r = right[i];
|
|
|
|
/* try matching character to left, looking right */
|
|
|
|
if (q == 0)
|
|
|
|
c = '\n';
|
|
|
|
else
|
|
|
|
c = textreadc(t, q - 1);
|
|
|
|
p = runestrchr(l, c);
|
|
|
|
if (p != nil) {
|
|
|
|
if (textclickmatch(t, c, r[p - l], 1, &q))
|
|
|
|
*q1 = q - (c != '\n');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
/* try matching character to right, looking left */
|
|
|
|
if (q == t->file->b.nc)
|
|
|
|
c = '\n';
|
|
|
|
else
|
|
|
|
c = textreadc(t, q);
|
|
|
|
p = runestrchr(r, c);
|
|
|
|
if (p != nil) {
|
|
|
|
if (textclickmatch(t, c, l[p - r], -1, &q)) {
|
|
|
|
*q1 = *q0 + (*q0 < t->file->b.nc && c == '\n');
|
|
|
|
*q0 = q;
|
|
|
|
if (c != '\n' || q != 0 || textreadc(t, 0) == '\n')
|
|
|
|
(*q0)++;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* try filling out word to right */
|
|
|
|
while (*q1 < t->file->b.nc && isalnum(textreadc(t, *q1)))
|
|
|
|
(*q1)++;
|
|
|
|
/* try filling out word to left */
|
|
|
|
while (*q0 > 0 && isalnum(textreadc(t, *q0 - 1)))
|
|
|
|
(*q0)--;
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
int textclickmatch(Text* t, int cl, int cr, int dir, uint* q) {
|
|
|
|
Rune c;
|
|
|
|
int nest;
|
|
|
|
|
|
|
|
nest = 1;
|
|
|
|
for (;;) {
|
|
|
|
if (dir > 0) {
|
|
|
|
if (*q == t->file->b.nc)
|
|
|
|
break;
|
|
|
|
c = textreadc(t, *q);
|
|
|
|
(*q)++;
|
|
|
|
} else {
|
|
|
|
if (*q == 0)
|
|
|
|
break;
|
|
|
|
(*q)--;
|
|
|
|
c = textreadc(t, *q);
|
|
|
|
}
|
|
|
|
if (c == cr) {
|
|
|
|
if (--nest == 0)
|
|
|
|
return 1;
|
|
|
|
} else if (c == cl)
|
|
|
|
nest++;
|
|
|
|
}
|
|
|
|
return cl == '\n' && nest == 1;
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Is the text starting at location q an html tag?
|
|
|
|
// Return 1 for <a>, -1 for </a>, 0 for no tag or <a />.
|
|
|
|
// Set *q1, if non-nil, to the location after the tag.
|
2021-06-13 05:54:54 +00:00
|
|
|
static int ishtmlstart(Text* t, uint q, uint* q1) {
|
|
|
|
int c, c1, c2;
|
|
|
|
|
|
|
|
if (q + 2 > t->file->b.nc)
|
|
|
|
return 0;
|
|
|
|
if (textreadc(t, q++) != '<')
|
|
|
|
return 0;
|
|
|
|
c = textreadc(t, q++);
|
|
|
|
c1 = c;
|
|
|
|
c2 = c;
|
|
|
|
while (c != '>') {
|
|
|
|
if (q >= t->file->b.nc)
|
|
|
|
return 0;
|
|
|
|
c2 = c;
|
|
|
|
c = textreadc(t, q++);
|
|
|
|
}
|
|
|
|
if (q1)
|
|
|
|
*q1 = q;
|
|
|
|
if (c1 == '/') // closing tag
|
|
|
|
return -1;
|
|
|
|
if (c2 == '/' || c2 == '!') // open + close tag or comment
|
|
|
|
return 0;
|
|
|
|
return 1;
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Is the text ending at location q an html tag?
|
|
|
|
// Return 1 for <a>, -1 for </a>, 0 for no tag or <a />.
|
|
|
|
// Set *q0, if non-nil, to the start of the tag.
|
2021-06-13 05:54:54 +00:00
|
|
|
static int ishtmlend(Text* t, uint q, uint* q0) {
|
|
|
|
int c, c1, c2;
|
|
|
|
|
|
|
|
if (q < 2)
|
|
|
|
return 0;
|
|
|
|
if (textreadc(t, --q) != '>')
|
|
|
|
return 0;
|
|
|
|
c = textreadc(t, --q);
|
|
|
|
c1 = c;
|
|
|
|
c2 = c;
|
|
|
|
while (c != '<') {
|
|
|
|
if (q == 0)
|
|
|
|
return 0;
|
|
|
|
c1 = c;
|
|
|
|
c = textreadc(t, --q);
|
|
|
|
}
|
|
|
|
if (q0)
|
|
|
|
*q0 = q;
|
|
|
|
if (c1 == '/') // closing tag
|
|
|
|
return -1;
|
|
|
|
if (c2 == '/' || c2 == '!') // open + close tag or comment
|
|
|
|
return 0;
|
|
|
|
return 1;
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
int textclickhtmlmatch(Text* t, uint* q0, uint* q1) {
|
|
|
|
int depth, n;
|
|
|
|
uint q, nq;
|
|
|
|
|
|
|
|
q = *q0;
|
|
|
|
// after opening tag? scan forward for closing tag
|
|
|
|
if (ishtmlend(t, q, nil) == 1) {
|
|
|
|
depth = 1;
|
|
|
|
while (q < t->file->b.nc) {
|
|
|
|
n = ishtmlstart(t, q, &nq);
|
|
|
|
if (n != 0) {
|
|
|
|
depth += n;
|
|
|
|
if (depth == 0) {
|
|
|
|
*q1 = q;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
q = nq;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
q++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// before closing tag? scan backward for opening tag
|
|
|
|
if (ishtmlstart(t, q, nil) == -1) {
|
|
|
|
depth = -1;
|
|
|
|
while (q > 0) {
|
|
|
|
n = ishtmlend(t, q, &nq);
|
|
|
|
if (n != 0) {
|
|
|
|
depth += n;
|
|
|
|
if (depth == 0) {
|
|
|
|
*q0 = q;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
q = nq;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
q--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
uint textbacknl(Text* t, uint p, uint n) {
|
|
|
|
int i, j;
|
|
|
|
|
|
|
|
/* look for start of this line if n==0 */
|
|
|
|
if (n == 0 && p > 0 && textreadc(t, p - 1) != '\n')
|
|
|
|
n = 1;
|
|
|
|
i = n;
|
|
|
|
while (i-- > 0 && p > 0) {
|
|
|
|
--p; /* it's at a newline now; back over it */
|
|
|
|
if (p == 0)
|
|
|
|
break;
|
|
|
|
/* at 128 chars, call it a line anyway */
|
|
|
|
for (j = 128; --j > 0 && p > 0; p--)
|
|
|
|
if (textreadc(t, p - 1) == '\n')
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return p;
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
void textsetorigin(Text* t, uint org, int exact) {
|
|
|
|
int i, a, fixup;
|
|
|
|
Rune* r;
|
|
|
|
uint n;
|
|
|
|
|
|
|
|
if (org > 0 && !exact && textreadc(t, org - 1) != '\n') {
|
|
|
|
/* org is an estimate of the char posn; find a newline */
|
|
|
|
/* don't try harder than 256 chars */
|
|
|
|
for (i = 0; i < 256 && org < t->file->b.nc; i++) {
|
|
|
|
if (textreadc(t, org) == '\n') {
|
|
|
|
org++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
org++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
a = org - t->org;
|
|
|
|
fixup = 0;
|
|
|
|
if (a >= 0 && a < t->fr.nchars) {
|
|
|
|
frdelete(&t->fr, 0, a);
|
|
|
|
fixup = 1; /* frdelete can leave end of last line in wrong selection mode;
|
|
|
|
it doesn't know what follows */
|
|
|
|
} else if (a < 0 && -a < t->fr.nchars) {
|
|
|
|
n = t->org - org;
|
|
|
|
r = runemalloc(n);
|
|
|
|
bufread(&t->file->b, org, r, n);
|
|
|
|
frinsert(&t->fr, r, r + n, 0);
|
|
|
|
free(r);
|
|
|
|
} else
|
|
|
|
frdelete(&t->fr, 0, t->fr.nchars);
|
|
|
|
t->org = org;
|
|
|
|
textfill(t);
|
|
|
|
textscrdraw(t);
|
|
|
|
textsetselect(t, t->q0, t->q1);
|
|
|
|
if (fixup && t->fr.p1 > t->fr.p0)
|
|
|
|
frdrawsel(
|
|
|
|
&t->fr,
|
|
|
|
frptofchar(&t->fr, t->fr.p1 - 1),
|
|
|
|
t->fr.p1 - 1,
|
|
|
|
t->fr.p1,
|
|
|
|
1);
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 05:54:54 +00:00
|
|
|
void textreset(Text* t) {
|
|
|
|
t->file->seq = 0;
|
|
|
|
t->eq0 = ~0;
|
|
|
|
/* do t->delete(0, t->nc, TRUE) without building backup stuff */
|
|
|
|
textsetselect(t, t->org, t->org);
|
|
|
|
frdelete(&t->fr, 0, t->fr.nchars);
|
|
|
|
t->org = 0;
|
|
|
|
t->q0 = 0;
|
|
|
|
t->q1 = 0;
|
|
|
|
filereset(t->file);
|
|
|
|
bufreset(&t->file->b);
|
2019-11-14 23:15:48 +00:00
|
|
|
}
|