Before, executing Get in a file rewound the window offset and selection to the start of the file. After this CL, Get preserves the window offset and selection, where preserve is defined as "the same line number and rune offset within the line". So if the window started at line 10 before and the selection was line 13 chars 5-7, then that will still be true after Get, provided the new content is large enough. This should help the common situation of plumbing a compiler error, realizing the window is out of date, clicking Get, and then losing the positioning from the plumb operation.
1664 lines
33 KiB
C
1664 lines
33 KiB
C
#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"
|
|
|
|
Image *tagcols[NCOL];
|
|
Image *textcols[NCOL];
|
|
static Rune Ldot[] = { '.', 0 };
|
|
|
|
enum{
|
|
TABDIR = 3 /* width of tabs in directory windows */
|
|
};
|
|
|
|
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);
|
|
textredraw(t, r, rf->f, screen, -1);
|
|
}
|
|
|
|
void
|
|
textredraw(Text *t, Rectangle r, Font *f, Image *b, int odx)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
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);
|
|
textredraw(t, r, t->fr.font, t->fr.b, odx);
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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++;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
void
|
|
typecommit(Text *t)
|
|
{
|
|
if(t->w != nil)
|
|
wincommit(t->w, t);
|
|
else
|
|
textcommit(t, TRUE);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
Rune
|
|
textreadc(Text *t, uint q)
|
|
{
|
|
Rune r;
|
|
|
|
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;
|
|
}
|
|
|
|
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 */
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
return;
|
|
case Kdown:
|
|
if(t->what == Tag)
|
|
goto Tagdown;
|
|
n = t->fr.maxlines/3;
|
|
goto case_Down;
|
|
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:
|
|
if(t->what == Tag)
|
|
goto Tagup;
|
|
n = t->fr.maxlines/3;
|
|
goto case_Up;
|
|
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:
|
|
typecommit(t);
|
|
if(t->org > t->iq1) {
|
|
q0 = textbacknl(t, t->iq1, 1);
|
|
textsetorigin(t, q0, TRUE);
|
|
} else
|
|
textshow(t, 0, 0, FALSE);
|
|
return;
|
|
case Kend:
|
|
typecommit(t);
|
|
if(t->iq1 > t->org+t->fr.nchars) {
|
|
if(t->iq1 > t->file->b.nc) {
|
|
// should not happen, but does. and it will crash textbacknl.
|
|
t->iq1 = t->file->b.nc;
|
|
}
|
|
q0 = textbacknl(t, t->iq1, 1);
|
|
textsetorigin(t, q0, TRUE);
|
|
} else
|
|
textshow(t, t->file->b.nc, t->file->b.nc, FALSE);
|
|
return;
|
|
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 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 Kcmd+'c': /* %C: copy */
|
|
typecommit(t);
|
|
cut(t, t, nil, TRUE, FALSE, nil, 0);
|
|
return;
|
|
case Kcmd+'z': /* %Z: undo */
|
|
typecommit(t);
|
|
undo(t, nil, nil, TRUE, 0, nil, 0);
|
|
return;
|
|
case Kcmd+'Z': /* %-shift-Z: redo */
|
|
typecommit(t);
|
|
undo(t, nil, nil, FALSE, 0, 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 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 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 '\n':
|
|
if(t->w->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);
|
|
}
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static Text *clicktext;
|
|
static uint clickmsec;
|
|
static Text *selecttext;
|
|
static uint selectq;
|
|
|
|
/*
|
|
* called from frame library
|
|
*/
|
|
void
|
|
framescroll(Frame *f, int dl)
|
|
{
|
|
if(f != &selecttext->fr)
|
|
error("frameselect not right frame");
|
|
textframescroll(selecttext, dl);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
static
|
|
int
|
|
region(int a, int b)
|
|
{
|
|
if(a < b)
|
|
return -1;
|
|
if(a == b)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
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]);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
enum {
|
|
DELAY = 2,
|
|
MINMOVE = 4
|
|
};
|
|
|
|
uint
|
|
xselect(Frame *f, Mousectl *mc, Image *col, uint *p1p) /* when called, button is down */
|
|
{
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
int
|
|
textselect3(Text *t, uint *q0, uint *q1)
|
|
{
|
|
int h;
|
|
|
|
h = (textselect23(t, q0, q1, but3col, 1|2) == 0);
|
|
return h;
|
|
}
|
|
|
|
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)--;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// 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.
|
|
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;
|
|
}
|
|
|
|
// 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.
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|