If the mouse was in the tag of the old window, it was most likely pointing at Del. If bringing up a new window from below and not moving the mouse somewhere else, adjust it so that it ends up pointing at Del in the replacement window's tag too. This makes it easy to Del a sequence of windows in a column, from top to bottom. http://www.youtube.com/watch?v=ET8w6RT6u5M R=r http://codereview.appspot.com/6558047
493 lines
8.1 KiB
C
493 lines
8.1 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 "dat.h"
|
|
#include "fns.h"
|
|
|
|
static Point prevmouse;
|
|
static Window *mousew;
|
|
|
|
Range
|
|
range(int q0, int q1)
|
|
{
|
|
Range r;
|
|
|
|
r.q0 = q0;
|
|
r.q1 = q1;
|
|
return r;
|
|
}
|
|
|
|
Runestr
|
|
runestr(Rune *r, uint n)
|
|
{
|
|
Runestr rs;
|
|
|
|
rs.r = r;
|
|
rs.nr = n;
|
|
return rs;
|
|
}
|
|
|
|
void
|
|
cvttorunes(char *p, int n, Rune *r, int *nb, int *nr, int *nulls)
|
|
{
|
|
uchar *q;
|
|
Rune *s;
|
|
int j, w;
|
|
|
|
/*
|
|
* Always guaranteed that n bytes may be interpreted
|
|
* without worrying about partial runes. This may mean
|
|
* reading up to UTFmax-1 more bytes than n; the caller
|
|
* knows this. If n is a firm limit, the caller should
|
|
* set p[n] = 0.
|
|
*/
|
|
q = (uchar*)p;
|
|
s = r;
|
|
for(j=0; j<n; j+=w){
|
|
if(*q < Runeself){
|
|
w = 1;
|
|
*s = *q++;
|
|
}else{
|
|
w = chartorune(s, (char*)q);
|
|
q += w;
|
|
}
|
|
if(*s)
|
|
s++;
|
|
else if(nulls)
|
|
*nulls = TRUE;
|
|
}
|
|
*nb = (char*)q-p;
|
|
*nr = s-r;
|
|
}
|
|
|
|
void
|
|
error(char *s)
|
|
{
|
|
fprint(2, "acme: %s: %r\n", s);
|
|
threadexitsall(nil);
|
|
}
|
|
|
|
Window*
|
|
errorwin1(Rune *dir, int ndir, Rune **incl, int nincl)
|
|
{
|
|
Window *w;
|
|
Rune *r;
|
|
int i, n;
|
|
static Rune Lpluserrors[] = { '+', 'E', 'r', 'r', 'o', 'r', 's', 0 };
|
|
|
|
r = runemalloc(ndir+8);
|
|
if((n = ndir) != 0){
|
|
runemove(r, dir, ndir);
|
|
r[n++] = L'/';
|
|
}
|
|
runemove(r+n, Lpluserrors, 7);
|
|
n += 7;
|
|
w = lookfile(r, n);
|
|
if(w == nil){
|
|
if(row.ncol == 0)
|
|
if(rowadd(&row, nil, -1) == nil)
|
|
error("can't create column to make error window");
|
|
w = coladd(row.col[row.ncol-1], nil, nil, -1);
|
|
w->filemenu = FALSE;
|
|
winsetname(w, r, n);
|
|
}
|
|
free(r);
|
|
for(i=nincl; --i>=0; ){
|
|
n = runestrlen(incl[i]);
|
|
r = runemalloc(n);
|
|
runemove(r, incl[i], n);
|
|
winaddincl(w, r, n);
|
|
}
|
|
w->autoindent = globalautoindent;
|
|
return w;
|
|
}
|
|
|
|
/* make new window, if necessary; return with it locked */
|
|
Window*
|
|
errorwin(Mntdir *md, int owner)
|
|
{
|
|
Window *w;
|
|
|
|
for(;;){
|
|
if(md == nil)
|
|
w = errorwin1(nil, 0, nil, 0);
|
|
else
|
|
w = errorwin1(md->dir, md->ndir, md->incl, md->nincl);
|
|
winlock(w, owner);
|
|
if(w->col != nil)
|
|
break;
|
|
/* window was deleted too fast */
|
|
winunlock(w);
|
|
}
|
|
return w;
|
|
}
|
|
|
|
/*
|
|
* Incoming window should be locked.
|
|
* It will be unlocked and returned window
|
|
* will be locked in its place.
|
|
*/
|
|
Window*
|
|
errorwinforwin(Window *w)
|
|
{
|
|
int i, n, nincl, owner;
|
|
Rune **incl;
|
|
Runestr dir;
|
|
Text *t;
|
|
|
|
t = &w->body;
|
|
dir = dirname(t, nil, 0);
|
|
if(dir.nr==1 && dir.r[0]=='.'){ /* sigh */
|
|
free(dir.r);
|
|
dir.r = nil;
|
|
dir.nr = 0;
|
|
}
|
|
incl = nil;
|
|
nincl = w->nincl;
|
|
if(nincl > 0){
|
|
incl = emalloc(nincl*sizeof(Rune*));
|
|
for(i=0; i<nincl; i++){
|
|
n = runestrlen(w->incl[i]);
|
|
incl[i] = runemalloc(n+1);
|
|
runemove(incl[i], w->incl[i], n);
|
|
}
|
|
}
|
|
owner = w->owner;
|
|
winunlock(w);
|
|
for(;;){
|
|
w = errorwin1(dir.r, dir.nr, incl, nincl);
|
|
winlock(w, owner);
|
|
if(w->col != nil)
|
|
break;
|
|
/* window deleted too fast */
|
|
winunlock(w);
|
|
}
|
|
return w;
|
|
}
|
|
|
|
typedef struct Warning Warning;
|
|
|
|
struct Warning{
|
|
Mntdir *md;
|
|
Buffer buf;
|
|
Warning *next;
|
|
};
|
|
|
|
static Warning *warnings;
|
|
|
|
static
|
|
void
|
|
addwarningtext(Mntdir *md, Rune *r, int nr)
|
|
{
|
|
Warning *warn;
|
|
|
|
for(warn = warnings; warn; warn=warn->next){
|
|
if(warn->md == md){
|
|
bufinsert(&warn->buf, warn->buf.nc, r, nr);
|
|
return;
|
|
}
|
|
}
|
|
warn = emalloc(sizeof(Warning));
|
|
warn->next = warnings;
|
|
warn->md = md;
|
|
if(md)
|
|
fsysincid(md);
|
|
warnings = warn;
|
|
bufinsert(&warn->buf, 0, r, nr);
|
|
nbsendp(cwarn, 0);
|
|
}
|
|
|
|
/* called while row is locked */
|
|
void
|
|
flushwarnings(void)
|
|
{
|
|
Warning *warn, *next;
|
|
Window *w;
|
|
Text *t;
|
|
int owner, nr, q0, n;
|
|
Rune *r;
|
|
|
|
for(warn=warnings; warn; warn=next) {
|
|
w = errorwin(warn->md, 'E');
|
|
t = &w->body;
|
|
owner = w->owner;
|
|
if(owner == 0)
|
|
w->owner = 'E';
|
|
wincommit(w, t);
|
|
/*
|
|
* Most commands don't generate much output. For instance,
|
|
* Edit ,>cat goes through /dev/cons and is already in blocks
|
|
* because of the i/o system, but a few can. Edit ,p will
|
|
* put the entire result into a single hunk. So it's worth doing
|
|
* this in blocks (and putting the text in a buffer in the first
|
|
* place), to avoid a big memory footprint.
|
|
*/
|
|
r = fbufalloc();
|
|
q0 = t->file->b.nc;
|
|
for(n = 0; n < warn->buf.nc; n += nr){
|
|
nr = warn->buf.nc - n;
|
|
if(nr > RBUFSIZE)
|
|
nr = RBUFSIZE;
|
|
bufread(&warn->buf, n, r, nr);
|
|
textbsinsert(t, t->file->b.nc, r, nr, TRUE, &nr);
|
|
}
|
|
textshow(t, q0, t->file->b.nc, 1);
|
|
free(r);
|
|
winsettag(t->w);
|
|
textscrdraw(t);
|
|
w->owner = owner;
|
|
w->dirty = FALSE;
|
|
winunlock(w);
|
|
bufclose(&warn->buf);
|
|
next = warn->next;
|
|
if(warn->md)
|
|
fsysdelid(warn->md);
|
|
free(warn);
|
|
}
|
|
warnings = nil;
|
|
}
|
|
|
|
void
|
|
warning(Mntdir *md, char *s, ...)
|
|
{
|
|
Rune *r;
|
|
va_list arg;
|
|
|
|
va_start(arg, s);
|
|
r = runevsmprint(s, arg);
|
|
va_end(arg);
|
|
if(r == nil)
|
|
error("runevsmprint failed");
|
|
addwarningtext(md, r, runestrlen(r));
|
|
free(r);
|
|
}
|
|
|
|
int
|
|
runeeq(Rune *s1, uint n1, Rune *s2, uint n2)
|
|
{
|
|
if(n1 != n2)
|
|
return FALSE;
|
|
return memcmp(s1, s2, n1*sizeof(Rune)) == 0;
|
|
}
|
|
|
|
uint
|
|
min(uint a, uint b)
|
|
{
|
|
if(a < b)
|
|
return a;
|
|
return b;
|
|
}
|
|
|
|
uint
|
|
max(uint a, uint b)
|
|
{
|
|
if(a > b)
|
|
return a;
|
|
return b;
|
|
}
|
|
|
|
char*
|
|
runetobyte(Rune *r, int n)
|
|
{
|
|
char *s;
|
|
|
|
if(r == nil)
|
|
return nil;
|
|
s = emalloc(n*UTFmax+1);
|
|
setmalloctag(s, getcallerpc(&r));
|
|
snprint(s, n*UTFmax+1, "%.*S", n, r);
|
|
return s;
|
|
}
|
|
|
|
Rune*
|
|
bytetorune(char *s, int *ip)
|
|
{
|
|
Rune *r;
|
|
int nb, nr;
|
|
|
|
nb = strlen(s);
|
|
r = runemalloc(nb+1);
|
|
cvttorunes(s, nb, r, &nb, &nr, nil);
|
|
r[nr] = '\0';
|
|
*ip = nr;
|
|
return r;
|
|
}
|
|
|
|
int
|
|
isalnum(Rune c)
|
|
{
|
|
/*
|
|
* Hard to get absolutely right. Use what we know about ASCII
|
|
* and assume anything above the Latin control characters is
|
|
* potentially an alphanumeric.
|
|
*/
|
|
if(c <= ' ')
|
|
return FALSE;
|
|
if(0x7F<=c && c<=0xA0)
|
|
return FALSE;
|
|
if(utfrune("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", c))
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
int
|
|
rgetc(void *v, uint n)
|
|
{
|
|
return ((Rune*)v)[n];
|
|
}
|
|
|
|
int
|
|
tgetc(void *a, uint n)
|
|
{
|
|
Text *t;
|
|
|
|
t = a;
|
|
if(n >= t->file->b.nc)
|
|
return 0;
|
|
return textreadc(t, n);
|
|
}
|
|
|
|
Rune*
|
|
skipbl(Rune *r, int n, int *np)
|
|
{
|
|
while(n>0 && (*r==' ' || *r=='\t' || *r=='\n')){
|
|
--n;
|
|
r++;
|
|
}
|
|
*np = n;
|
|
return r;
|
|
}
|
|
|
|
Rune*
|
|
findbl(Rune *r, int n, int *np)
|
|
{
|
|
while(n>0 && *r!=' ' && *r!='\t' && *r!='\n'){
|
|
--n;
|
|
r++;
|
|
}
|
|
*np = n;
|
|
return r;
|
|
}
|
|
|
|
void
|
|
savemouse(Window *w)
|
|
{
|
|
prevmouse = mouse->xy;
|
|
mousew = w;
|
|
}
|
|
|
|
int
|
|
restoremouse(Window *w)
|
|
{
|
|
int did;
|
|
|
|
did = 0;
|
|
if(mousew!=nil && mousew==w) {
|
|
moveto(mousectl, prevmouse);
|
|
did = 1;
|
|
}
|
|
mousew = nil;
|
|
return did;
|
|
}
|
|
|
|
void
|
|
clearmouse()
|
|
{
|
|
mousew = nil;
|
|
}
|
|
|
|
char*
|
|
estrdup(char *s)
|
|
{
|
|
char *t;
|
|
|
|
t = strdup(s);
|
|
if(t == nil)
|
|
error("strdup failed");
|
|
setmalloctag(t, getcallerpc(&s));
|
|
return t;
|
|
}
|
|
|
|
void*
|
|
emalloc(uint n)
|
|
{
|
|
void *p;
|
|
|
|
p = malloc(n);
|
|
if(p == nil)
|
|
error("malloc failed");
|
|
setmalloctag(p, getcallerpc(&n));
|
|
memset(p, 0, n);
|
|
return p;
|
|
}
|
|
|
|
void*
|
|
erealloc(void *p, uint n)
|
|
{
|
|
p = realloc(p, n);
|
|
if(p == nil)
|
|
error("realloc failed");
|
|
setmalloctag(p, getcallerpc(&n));
|
|
return p;
|
|
}
|
|
|
|
/*
|
|
* Heuristic city.
|
|
*/
|
|
Window*
|
|
makenewwindow(Text *t)
|
|
{
|
|
Column *c;
|
|
Window *w, *bigw, *emptyw;
|
|
Text *emptyb;
|
|
int i, y, el;
|
|
|
|
if(activecol)
|
|
c = activecol;
|
|
else if(seltext && seltext->col)
|
|
c = seltext->col;
|
|
else if(t && t->col)
|
|
c = t->col;
|
|
else{
|
|
if(row.ncol==0 && rowadd(&row, nil, -1)==nil)
|
|
error("can't make column");
|
|
c = row.col[row.ncol-1];
|
|
}
|
|
activecol = c;
|
|
if(t==nil || t->w==nil || c->nw==0)
|
|
return coladd(c, nil, nil, -1);
|
|
|
|
/* find biggest window and biggest blank spot */
|
|
emptyw = c->w[0];
|
|
bigw = emptyw;
|
|
for(i=1; i<c->nw; i++){
|
|
w = c->w[i];
|
|
/* use >= to choose one near bottom of screen */
|
|
if(w->body.fr.maxlines >= bigw->body.fr.maxlines)
|
|
bigw = w;
|
|
if(w->body.fr.maxlines-w->body.fr.nlines >= emptyw->body.fr.maxlines-emptyw->body.fr.nlines)
|
|
emptyw = w;
|
|
}
|
|
emptyb = &emptyw->body;
|
|
el = emptyb->fr.maxlines-emptyb->fr.nlines;
|
|
/* if empty space is big, use it */
|
|
if(el>15 || (el>3 && el>(bigw->body.fr.maxlines-1)/2))
|
|
y = emptyb->fr.r.min.y+emptyb->fr.nlines*font->height;
|
|
else{
|
|
/* if this window is in column and isn't much smaller, split it */
|
|
if(t->col==c && Dy(t->w->r)>2*Dy(bigw->r)/3)
|
|
bigw = t->w;
|
|
y = (bigw->r.min.y + bigw->r.max.y)/2;
|
|
}
|
|
w = coladd(c, nil, nil, y);
|
|
if(w->body.fr.maxlines < 2)
|
|
colgrow(w->col, w, 1);
|
|
return w;
|
|
}
|