mirror of
https://hacklab.nilfm.cc/acme
synced 2024-10-22 14:31:48 +00:00
Derek Stevens
2908f1ff9b
window tag/body with keyboard focus will render tick in hilighted text color; doesn't work yet for column/row tags
441 lines
8.9 KiB
C
441 lines
8.9 KiB
C
#include <u.h>
|
|
#include <libc.h>
|
|
#include <draw.h>
|
|
#include <thread.h>
|
|
#include <cursor.h>
|
|
#include <mouse.h>
|
|
#include <keyboard.h>
|
|
#include "libframe/frame.h"
|
|
#include <fcall.h>
|
|
#include <plumb.h>
|
|
#include <libsec.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);
|
|
xfidlog(w, "new");
|
|
}
|
|
free(r);
|
|
for (i = nincl; --i >= 0;) {
|
|
n = runestrlen(incl[i]);
|
|
r = runemalloc(n);
|
|
runemove(r, incl[i], n);
|
|
winaddincl(w, r, n);
|
|
}
|
|
for (i = 0; i < NINDENT; i++)
|
|
w->indent[i] = globalindent[i];
|
|
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;
|
|
if (n1 == 0)
|
|
return TRUE;
|
|
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;
|
|
}
|