plan9port/src/cmd/acme/util.c
Russ Cox 37f8ed2410 acme: mouse movement for Del
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
2012-09-23 22:01:56 -04:00

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;
}