plan9port/src/cmd/acme/exec.c
Russ Cox ba31ab3044 9term, acme: autoscroll
Ignore scroll/noscroll window setting.
Instead, scroll when the write begins in
or immediately after the displayed window content.

In the new scrolling discipline, executing
"Noscroll" is replaced by typing Page Up or
using the mouse to scroll higher in the buffer,
and executing "Scroll" is replaced by typing End
or using the mouse to scroll to the bottom of
the buffer.

R=r, r2
http://codereview.appspot.com/4433060
2011-04-27 13:18:07 -04:00

1663 lines
34 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 <9pclient.h>
#include "dat.h"
#include "fns.h"
Buffer snarfbuf;
/*
* These functions get called as:
*
* fn(et, t, argt, flag1, flag1, flag2, s, n);
*
* Where the arguments are:
*
* et: the Text* in which the executing event (click) occurred
* t: the Text* containing the current selection (Edit, Cut, Snarf, Paste)
* argt: the Text* containing the argument for a 2-1 click.
* e->flag1: from Exectab entry
* e->flag2: from Exectab entry
* s: the command line remainder (e.g., "x" if executing "Dump x")
* n: length of s (s is *not* NUL-terminated)
*/
void doabort(Text*, Text*, Text*, int, int, Rune*, int);
void del(Text*, Text*, Text*, int, int, Rune*, int);
void delcol(Text*, Text*, Text*, int, int, Rune*, int);
void dotfiles(Text*, Text*, Text*, int, int, Rune*, int);
void dump(Text*, Text*, Text*, int, int, Rune*, int);
void edit(Text*, Text*, Text*, int, int, Rune*, int);
void xexit(Text*, Text*, Text*, int, int, Rune*, int);
void fontx(Text*, Text*, Text*, int, int, Rune*, int);
void get(Text*, Text*, Text*, int, int, Rune*, int);
void id(Text*, Text*, Text*, int, int, Rune*, int);
void incl(Text*, Text*, Text*, int, int, Rune*, int);
void indent(Text*, Text*, Text*, int, int, Rune*, int);
void xkill(Text*, Text*, Text*, int, int, Rune*, int);
void local(Text*, Text*, Text*, int, int, Rune*, int);
void look(Text*, Text*, Text*, int, int, Rune*, int);
void newcol(Text*, Text*, Text*, int, int, Rune*, int);
void paste(Text*, Text*, Text*, int, int, Rune*, int);
void put(Text*, Text*, Text*, int, int, Rune*, int);
void putall(Text*, Text*, Text*, int, int, Rune*, int);
void sendx(Text*, Text*, Text*, int, int, Rune*, int);
void sort(Text*, Text*, Text*, int, int, Rune*, int);
void tab(Text*, Text*, Text*, int, int, Rune*, int);
void zeroxx(Text*, Text*, Text*, int, int, Rune*, int);
typedef struct Exectab Exectab;
struct Exectab
{
Rune *name;
void (*fn)(Text*, Text*, Text*, int, int, Rune*, int);
int mark;
int flag1;
int flag2;
};
static Rune LAbort[] = { 'A', 'b', 'o', 'r', 't', 0 };
static Rune LCut[] = { 'C', 'u', 't', 0 };
static Rune LDel[] = { 'D', 'e', 'l', 0 };
static Rune LDelcol[] = { 'D', 'e', 'l', 'c', 'o', 'l', 0 };
static Rune LDelete[] = { 'D', 'e', 'l', 'e', 't', 'e', 0 };
static Rune LDump[] = { 'D', 'u', 'm', 'p', 0 };
static Rune LEdit[] = { 'E', 'd', 'i', 't', 0 };
static Rune LExit[] = { 'E', 'x', 'i', 't', 0 };
static Rune LFont[] = { 'F', 'o', 'n', 't', 0 };
static Rune LGet[] = { 'G', 'e', 't', 0 };
static Rune LID[] = { 'I', 'D', 0 };
static Rune LIncl[] = { 'I', 'n', 'c', 'l', 0 };
static Rune LIndent[] = { 'I', 'n', 'd', 'e', 'n', 't', 0 };
static Rune LKill[] = { 'K', 'i', 'l', 'l', 0 };
static Rune LLoad[] = { 'L', 'o', 'a', 'd', 0 };
static Rune LLocal[] = { 'L', 'o', 'c', 'a', 'l', 0 };
static Rune LLook[] = { 'L', 'o', 'o', 'k', 0 };
static Rune LNew[] = { 'N', 'e', 'w', 0 };
static Rune LNewcol[] = { 'N', 'e', 'w', 'c', 'o', 'l', 0 };
static Rune LPaste[] = { 'P', 'a', 's', 't', 'e', 0 };
static Rune LPut[] = { 'P', 'u', 't', 0 };
static Rune LPutall[] = { 'P', 'u', 't', 'a', 'l', 'l', 0 };
static Rune LRedo[] = { 'R', 'e', 'd', 'o', 0 };
static Rune LSend[] = { 'S', 'e', 'n', 'd', 0 };
static Rune LSnarf[] = { 'S', 'n', 'a', 'r', 'f', 0 };
static Rune LSort[] = { 'S', 'o', 'r', 't', 0 };
static Rune LTab[] = { 'T', 'a', 'b', 0 };
static Rune LUndo[] = { 'U', 'n', 'd', 'o', 0 };
static Rune LZerox[] = { 'Z', 'e', 'r', 'o', 'x', 0 };
Exectab exectab[] = {
{ LAbort, doabort, FALSE, XXX, XXX, },
{ LCut, cut, TRUE, TRUE, TRUE },
{ LDel, del, FALSE, FALSE, XXX },
{ LDelcol, delcol, FALSE, XXX, XXX },
{ LDelete, del, FALSE, TRUE, XXX },
{ LDump, dump, FALSE, TRUE, XXX },
{ LEdit, edit, FALSE, XXX, XXX },
{ LExit, xexit, FALSE, XXX, XXX },
{ LFont, fontx, FALSE, XXX, XXX },
{ LGet, get, FALSE, TRUE, XXX },
{ LID, id, FALSE, XXX, XXX },
{ LIncl, incl, FALSE, XXX, XXX },
{ LIndent, indent, FALSE, XXX, XXX },
{ LKill, xkill, FALSE, XXX, XXX },
{ LLoad, dump, FALSE, FALSE, XXX },
{ LLocal, local, FALSE, XXX, XXX },
{ LLook, look, FALSE, XXX, XXX },
{ LNew, new, FALSE, XXX, XXX },
{ LNewcol, newcol, FALSE, XXX, XXX },
{ LPaste, paste, TRUE, TRUE, XXX },
{ LPut, put, FALSE, XXX, XXX },
{ LPutall, putall, FALSE, XXX, XXX },
{ LRedo, undo, FALSE, FALSE, XXX },
{ LSend, sendx, TRUE, XXX, XXX },
{ LSnarf, cut, FALSE, TRUE, FALSE },
{ LSort, sort, FALSE, XXX, XXX },
{ LTab, tab, FALSE, XXX, XXX },
{ LUndo, undo, FALSE, TRUE, XXX },
{ LZerox, zeroxx, FALSE, XXX, XXX },
{ nil, 0, 0, 0, 0 }
};
Exectab*
lookup(Rune *r, int n)
{
Exectab *e;
int nr;
r = skipbl(r, n, &n);
if(n == 0)
return nil;
findbl(r, n, &nr);
nr = n-nr;
for(e=exectab; e->name; e++)
if(runeeq(r, nr, e->name, runestrlen(e->name)) == TRUE)
return e;
return nil;
}
int
isexecc(int c)
{
if(isfilec(c))
return 1;
return c=='<' || c=='|' || c=='>';
}
void
execute(Text *t, uint aq0, uint aq1, int external, Text *argt)
{
uint q0, q1;
Rune *r, *s;
char *b, *a, *aa;
Exectab *e;
int c, n, f;
Runestr dir;
q0 = aq0;
q1 = aq1;
if(q1 == q0){ /* expand to find word (actually file name) */
/* if in selection, choose selection */
if(t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){
q0 = t->q0;
q1 = t->q1;
}else{
while(q1<t->file->b.nc && isexecc(c=textreadc(t, q1)) && c!=':')
q1++;
while(q0>0 && isexecc(c=textreadc(t, q0-1)) && c!=':')
q0--;
if(q1 == q0)
return;
}
}
r = runemalloc(q1-q0);
bufread(&t->file->b, q0, r, q1-q0);
e = lookup(r, q1-q0);
if(!external && t->w!=nil && t->w->nopen[QWevent]>0){
f = 0;
if(e)
f |= 1;
if(q0!=aq0 || q1!=aq1){
bufread(&t->file->b, aq0, r, aq1-aq0);
f |= 2;
}
aa = getbytearg(argt, TRUE, TRUE, &a);
if(a){
if(strlen(a) > EVENTSIZE){ /* too big; too bad */
free(aa);
free(a);
warning(nil, "argument string too long\n");
return;
}
f |= 8;
}
c = 'x';
if(t->what == Body)
c = 'X';
n = aq1-aq0;
if(n <= EVENTSIZE)
winevent(t->w, "%c%d %d %d %d %.*S\n", c, aq0, aq1, f, n, n, r);
else
winevent(t->w, "%c%d %d %d 0 \n", c, aq0, aq1, f, n);
if(q0!=aq0 || q1!=aq1){
n = q1-q0;
bufread(&t->file->b, q0, r, n);
if(n <= EVENTSIZE)
winevent(t->w, "%c%d %d 0 %d %.*S\n", c, q0, q1, n, n, r);
else
winevent(t->w, "%c%d %d 0 0 \n", c, q0, q1, n);
}
if(a){
winevent(t->w, "%c0 0 0 %d %s\n", c, utflen(a), a);
if(aa)
winevent(t->w, "%c0 0 0 %d %s\n", c, utflen(aa), aa);
else
winevent(t->w, "%c0 0 0 0 \n", c);
}
free(r);
free(aa);
free(a);
return;
}
if(e){
if(e->mark && seltext!=nil)
if(seltext->what == Body){
seq++;
filemark(seltext->w->body.file);
}
s = skipbl(r, q1-q0, &n);
s = findbl(s, n, &n);
s = skipbl(s, n, &n);
(*e->fn)(t, seltext, argt, e->flag1, e->flag2, s, n);
free(r);
return;
}
b = runetobyte(r, q1-q0);
free(r);
dir = dirname(t, nil, 0);
if(dir.nr==1 && dir.r[0]=='.'){ /* sigh */
free(dir.r);
dir.r = nil;
dir.nr = 0;
}
aa = getbytearg(argt, TRUE, TRUE, &a);
if(t->w)
incref(&t->w->ref);
run(t->w, b, dir.r, dir.nr, TRUE, aa, a, FALSE);
}
char*
printarg(Text *argt, uint q0, uint q1)
{
char *buf;
if(argt->what!=Body || argt->file->name==nil)
return nil;
buf = emalloc(argt->file->nname+32);
if(q0 == q1)
sprint(buf, "%.*S:#%d", argt->file->nname, argt->file->name, q0);
else
sprint(buf, "%.*S:#%d,#%d", argt->file->nname, argt->file->name, q0, q1);
return buf;
}
char*
getarg(Text *argt, int doaddr, int dofile, Rune **rp, int *nrp)
{
int n;
Expand e;
char *a;
*rp = nil;
*nrp = 0;
if(argt == nil)
return nil;
a = nil;
textcommit(argt, TRUE);
if(expand(argt, argt->q0, argt->q1, &e)){
free(e.bname);
if(e.nname && dofile){
e.name = runerealloc(e.name, e.nname+1);
if(doaddr)
a = printarg(argt, e.q0, e.q1);
*rp = e.name;
*nrp = e.nname;
return a;
}
free(e.name);
}else{
e.q0 = argt->q0;
e.q1 = argt->q1;
}
n = e.q1 - e.q0;
*rp = runemalloc(n+1);
bufread(&argt->file->b, e.q0, *rp, n);
if(doaddr)
a = printarg(argt, e.q0, e.q1);
*nrp = n;
return a;
}
char*
getbytearg(Text *argt, int doaddr, int dofile, char **bp)
{
Rune *r;
int n;
char *aa;
*bp = nil;
aa = getarg(argt, doaddr, dofile, &r, &n);
if(r == nil)
return nil;
*bp = runetobyte(r, n);
free(r);
return aa;
}
void
doabort(Text *__0, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5)
{
static int n;
USED(__0);
USED(_0);
USED(_1);
USED(_2);
USED(_3);
USED(_4);
USED(_5);
if(n++ == 0)
warning(nil, "executing Abort again will call abort()\n");
else
abort();
}
void
newcol(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5)
{
Column *c;
USED(_0);
USED(_1);
USED(_2);
USED(_3);
USED(_4);
USED(_5);
c = rowadd(et->row, nil, -1);
if(c)
winsettag(coladd(c, nil, nil, -1));
}
void
delcol(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5)
{
int i;
Column *c;
Window *w;
USED(_0);
USED(_1);
USED(_2);
USED(_3);
USED(_4);
USED(_5);
c = et->col;
if(c==nil || colclean(c)==0)
return;
for(i=0; i<c->nw; i++){
w = c->w[i];
if(w->nopen[QWevent]+w->nopen[QWaddr]+w->nopen[QWdata]+w->nopen[QWxdata] > 0){
warning(nil, "can't delete column; %.*S is running an external command\n", w->body.file->nname, w->body.file->name);
return;
}
}
rowclose(et->col->row, et->col, TRUE);
}
void
del(Text *et, Text *_0, Text *_1, int flag1, int _2, Rune *_3, int _4)
{
USED(_0);
USED(_1);
USED(_2);
USED(_3);
USED(_4);
if(et->col==nil || et->w == nil)
return;
if(flag1 || et->w->body.file->ntext>1 || winclean(et->w, FALSE))
colclose(et->col, et->w, TRUE);
}
void
sort(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5)
{
USED(_0);
USED(_1);
USED(_2);
USED(_3);
USED(_4);
USED(_5);
if(et->col)
colsort(et->col);
}
uint
seqof(Window *w, int isundo)
{
/* if it's undo, see who changed with us */
if(isundo)
return w->body.file->seq;
/* if it's redo, see who we'll be sync'ed up with */
return fileredoseq(w->body.file);
}
void
undo(Text *et, Text *_0, Text *_1, int flag1, int _2, Rune *_3, int _4)
{
int i, j;
Column *c;
Window *w;
uint seq;
USED(_0);
USED(_1);
USED(_2);
USED(_3);
USED(_4);
if(et==nil || et->w== nil)
return;
seq = seqof(et->w, flag1);
if(seq == 0){
/* nothing to undo */
return;
}
/*
* Undo the executing window first. Its display will update. other windows
* in the same file will not call show() and jump to a different location in the file.
* Simultaneous changes to other files will be chaotic, however.
*/
winundo(et->w, flag1);
for(i=0; i<row.ncol; i++){
c = row.col[i];
for(j=0; j<c->nw; j++){
w = c->w[j];
if(w == et->w)
continue;
if(seqof(w, flag1) == seq)
winundo(w, flag1);
}
}
}
char*
getname(Text *t, Text *argt, Rune *arg, int narg, int isput)
{
char *s;
Rune *r;
int i, n, promote;
Runestr dir;
getarg(argt, FALSE, TRUE, &r, &n);
promote = FALSE;
if(r == nil)
promote = TRUE;
else if(isput){
/* if are doing a Put, want to synthesize name even for non-existent file */
/* best guess is that file name doesn't contain a slash */
promote = TRUE;
for(i=0; i<n; i++)
if(r[i] == '/'){
promote = FALSE;
break;
}
if(promote){
t = argt;
arg = r;
narg = n;
}
}
if(promote){
n = narg;
if(n <= 0){
s = runetobyte(t->file->name, t->file->nname);
return s;
}
/* prefix with directory name if necessary */
dir.r = nil;
dir.nr = 0;
if(n>0 && arg[0]!='/'){
dir = dirname(t, nil, 0);
if(dir.nr==1 && dir.r[0]=='.'){ /* sigh */
free(dir.r);
dir.r = nil;
dir.nr = 0;
}
}
if(dir.r){
r = runemalloc(dir.nr+n+1);
runemove(r, dir.r, dir.nr);
free(dir.r);
if(dir.nr>0 && r[dir.nr]!='/' && n>0 && arg[0]!='/')
r[dir.nr++] = '/';
runemove(r+dir.nr, arg, n);
n += dir.nr;
}else{
r = runemalloc(n+1);
runemove(r, arg, n);
}
}
s = runetobyte(r, n);
free(r);
if(strlen(s) == 0){
free(s);
s = nil;
}
return s;
}
void
zeroxx(Text *et, Text *t, Text *_1, int _2, int _3, Rune *_4, int _5)
{
Window *nw;
int c, locked;
USED(_1);
USED(_2);
USED(_3);
USED(_4);
USED(_5);
locked = FALSE;
if(t!=nil && t->w!=nil && t->w!=et->w){
locked = TRUE;
c = 'M';
if(et->w)
c = et->w->owner;
winlock(t->w, c);
}
if(t == nil)
t = et;
if(t==nil || t->w==nil)
return;
t = &t->w->body;
if(t->w->isdir)
warning(nil, "%.*S is a directory; Zerox illegal\n", t->file->nname, t->file->name);
else{
nw = coladd(t->w->col, nil, t->w, -1);
/* ugly: fix locks so w->unlock works */
winlock1(nw, t->w->owner);
}
if(locked)
winunlock(t->w);
}
void
get(Text *et, Text *t, Text *argt, int flag1, int _0, Rune *arg, int narg)
{
char *name;
Rune *r;
int i, n, dirty, samename, isdir;
Window *w;
Text *u;
Dir *d;
USED(_0);
if(flag1)
if(et==nil || et->w==nil)
return;
if(!et->w->isdir && (et->w->body.file->b.nc>0 && !winclean(et->w, TRUE)))
return;
w = et->w;
t = &w->body;
name = getname(t, argt, arg, narg, FALSE);
if(name == nil){
warning(nil, "no file name\n");
return;
}
if(t->file->ntext>1){
d = dirstat(name);
isdir = (d!=nil && (d->qid.type & QTDIR));
free(d);
if(isdir){
warning(nil, "%s is a directory; can't read with multiple windows on it\n", name);
return;
}
}
r = bytetorune(name, &n);
for(i=0; i<t->file->ntext; i++){
u = t->file->text[i];
/* second and subsequent calls with zero an already empty buffer, but OK */
textreset(u);
windirfree(u->w);
}
samename = runeeq(r, n, t->file->name, t->file->nname);
textload(t, 0, name, samename);
if(samename){
t->file->mod = FALSE;
dirty = FALSE;
}else{
t->file->mod = TRUE;
dirty = TRUE;
}
for(i=0; i<t->file->ntext; i++)
t->file->text[i]->w->dirty = dirty;
free(name);
free(r);
winsettag(w);
t->file->unread = FALSE;
for(i=0; i<t->file->ntext; i++){
u = t->file->text[i];
textsetselect(&u->w->tag, u->w->tag.file->b.nc, u->w->tag.file->b.nc);
textscrdraw(u);
}
}
void
putfile(File *f, int q0, int q1, Rune *namer, int nname)
{
uint n, m;
Rune *r;
char *s, *name;
int i, fd, q;
Dir *d, *d1;
Window *w;
int isapp;
w = f->curtext->w;
name = runetobyte(namer, nname);
d = dirstat(name);
if(d!=nil && runeeq(namer, nname, f->name, f->nname)){
/* f->mtime+1 because when talking over NFS it's often off by a second */
if(f->dev!=d->dev || f->qidpath!=d->qid.path || abs(f->mtime-d->mtime) > 1){
if(f->unread)
warning(nil, "%s not written; file already exists\n", name);
else
warning(nil, "%s modified%s%s since last read\n\twas %t; now %t\n", name, d->muid[0]?" by ":"", d->muid, f->mtime, d->mtime);
f->dev = d->dev;
f->qidpath = d->qid.path;
f->mtime = d->mtime;
goto Rescue1;
}
}
fd = create(name, OWRITE, 0666);
if(fd < 0){
warning(nil, "can't create file %s: %r\n", name);
goto Rescue1;
}
r = fbufalloc();
s = fbufalloc();
free(d);
d = dirfstat(fd);
isapp = (d!=nil && d->length>0 && (d->qid.type&QTAPPEND));
if(isapp){
warning(nil, "%s not written; file is append only\n", name);
goto Rescue2;
}
for(q=q0; q<q1; q+=n){
n = q1 - q;
if(n > BUFSIZE/UTFmax)
n = BUFSIZE/UTFmax;
bufread(&f->b, q, r, n);
m = snprint(s, BUFSIZE+1, "%.*S", n, r);
if(write(fd, s, m) != m){
warning(nil, "can't write file %s: %r\n", name);
goto Rescue2;
}
}
if(runeeq(namer, nname, f->name, f->nname)){
if(q0!=0 || q1!=f->b.nc){
f->mod = TRUE;
w->dirty = TRUE;
f->unread = TRUE;
}else{
// In case the file is on NFS, reopen the fd
// before dirfstat to cause the attribute cache
// to be updated (otherwise the mtime in the
// dirfstat below will be stale and not match
// what NFS sees). The file is already written,
// so this should be a no-op when not on NFS.
// Opening for OWRITE (but no truncation)
// in case we don't have read permission.
// (The create above worked, so we probably
// still have write permission.)
close(fd);
fd = open(name, OWRITE);
d1 = dirfstat(fd);
if(d1 != nil){
free(d);
d = d1;
}
f->qidpath = d->qid.path;
f->dev = d->dev;
f->mtime = d->mtime;
f->mod = FALSE;
w->dirty = FALSE;
f->unread = FALSE;
}
for(i=0; i<f->ntext; i++){
f->text[i]->w->putseq = f->seq;
f->text[i]->w->dirty = w->dirty;
}
}
fbuffree(s);
fbuffree(r);
free(d);
free(namer);
free(name);
close(fd);
winsettag(w);
return;
Rescue2:
fbuffree(s);
fbuffree(r);
close(fd);
/* fall through */
Rescue1:
free(d);
free(namer);
free(name);
}
void
put(Text *et, Text *_0, Text *argt, int _1, int _2, Rune *arg, int narg)
{
int nname;
Rune *namer;
Window *w;
File *f;
char *name;
USED(_0);
USED(_1);
USED(_2);
if(et==nil || et->w==nil || et->w->isdir)
return;
w = et->w;
f = w->body.file;
name = getname(&w->body, argt, arg, narg, TRUE);
if(name == nil){
warning(nil, "no file name\n");
return;
}
namer = bytetorune(name, &nname);
putfile(f, 0, f->b.nc, namer, nname);
free(name);
}
void
dump(Text *_0, Text *_1, Text *argt, int isdump, int _2, Rune *arg, int narg)
{
char *name;
USED(_0);
USED(_1);
USED(_2);
if(narg)
name = runetobyte(arg, narg);
else
getbytearg(argt, FALSE, TRUE, &name);
if(isdump)
rowdump(&row, name);
else
rowload(&row, name, FALSE);
free(name);
}
void
cut(Text *et, Text *t, Text *_0, int dosnarf, int docut, Rune *_2, int _3)
{
uint q0, q1, n, locked, c;
Rune *r;
USED(_0);
USED(_2);
USED(_3);
/*
* if not executing a mouse chord (et != t) and snarfing (dosnarf)
* and executed Cut or Snarf in window tag (et->w != nil),
* then use the window body selection or the tag selection
* or do nothing at all.
*/
if(et!=t && dosnarf && et->w!=nil){
if(et->w->body.q1>et->w->body.q0){
t = &et->w->body;
if(docut)
filemark(t->file); /* seq has been incremented by execute */
}else if(et->w->tag.q1>et->w->tag.q0)
t = &et->w->tag;
else
t = nil;
}
if(t == nil) /* no selection */
return;
locked = FALSE;
if(t->w!=nil && et->w!=t->w){
locked = TRUE;
c = 'M';
if(et->w)
c = et->w->owner;
winlock(t->w, c);
}
if(t->q0 == t->q1){
if(locked)
winunlock(t->w);
return;
}
if(dosnarf){
q0 = t->q0;
q1 = t->q1;
bufdelete(&snarfbuf, 0, snarfbuf.nc);
r = fbufalloc();
while(q0 < q1){
n = q1 - q0;
if(n > RBUFSIZE)
n = RBUFSIZE;
bufread(&t->file->b, q0, r, n);
bufinsert(&snarfbuf, snarfbuf.nc, r, n);
q0 += n;
}
fbuffree(r);
acmeputsnarf();
}
if(docut){
textdelete(t, t->q0, t->q1, TRUE);
textsetselect(t, t->q0, t->q0);
if(t->w){
textscrdraw(t);
winsettag(t->w);
}
}else if(dosnarf) /* Snarf command */
argtext = t;
if(locked)
winunlock(t->w);
}
void
paste(Text *et, Text *t, Text *_0, int selectall, int tobody, Rune *_1, int _2)
{
int c;
uint q, q0, q1, n;
Rune *r;
USED(_0);
USED(_1);
USED(_2);
/* if(tobody), use body of executing window (Paste or Send command) */
if(tobody && et!=nil && et->w!=nil){
t = &et->w->body;
filemark(t->file); /* seq has been incremented by execute */
}
if(t == nil)
return;
acmegetsnarf();
if(t==nil || snarfbuf.nc==0)
return;
if(t->w!=nil && et->w!=t->w){
c = 'M';
if(et->w)
c = et->w->owner;
winlock(t->w, c);
}
cut(t, t, nil, FALSE, TRUE, nil, 0);
q = 0;
q0 = t->q0;
q1 = t->q0+snarfbuf.nc;
r = fbufalloc();
while(q0 < q1){
n = q1 - q0;
if(n > RBUFSIZE)
n = RBUFSIZE;
if(r == nil)
r = runemalloc(n);
bufread(&snarfbuf, q, r, n);
textinsert(t, q0, r, n, TRUE);
q += n;
q0 += n;
}
fbuffree(r);
if(selectall)
textsetselect(t, t->q0, q1);
else
textsetselect(t, q1, q1);
if(t->w){
textscrdraw(t);
winsettag(t->w);
}
if(t->w!=nil && et->w!=t->w)
winunlock(t->w);
}
void
look(Text *et, Text *t, Text *argt, int _0, int _1, Rune *arg, int narg)
{
Rune *r;
int n;
USED(_0);
USED(_1);
if(et && et->w){
t = &et->w->body;
if(narg > 0){
search(t, arg, narg);
return;
}
getarg(argt, FALSE, FALSE, &r, &n);
if(r == nil){
n = t->q1-t->q0;
r = runemalloc(n);
bufread(&t->file->b, t->q0, r, n);
}
search(t, r, n);
free(r);
}
}
static Rune Lnl[] = { '\n', 0 };
void
sendx(Text *et, Text *t, Text *_0, int _1, int _2, Rune *_3, int _4)
{
USED(_0);
USED(_1);
USED(_2);
USED(_3);
USED(_4);
if(et->w==nil)
return;
t = &et->w->body;
if(t->q0 != t->q1)
cut(t, t, nil, TRUE, FALSE, nil, 0);
textsetselect(t, t->file->b.nc, t->file->b.nc);
paste(t, t, nil, TRUE, TRUE, nil, 0);
if(textreadc(t, t->file->b.nc-1) != '\n'){
textinsert(t, t->file->b.nc, Lnl, 1, TRUE);
textsetselect(t, t->file->b.nc, t->file->b.nc);
textshow(t, t->q1, t->q1, 1);
}
}
void
edit(Text *et, Text *_0, Text *argt, int _1, int _2, Rune *arg, int narg)
{
Rune *r;
int len;
USED(_0);
USED(_1);
USED(_2);
if(et == nil)
return;
getarg(argt, FALSE, TRUE, &r, &len);
seq++;
if(r != nil){
editcmd(et, r, len);
free(r);
}else
editcmd(et, arg, narg);
}
void
xexit(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5)
{
USED(et);
USED(_0);
USED(_1);
USED(_2);
USED(_3);
USED(_4);
USED(_5);
if(rowclean(&row)){
sendul(cexit, 0);
threadexits(nil);
}
}
void
putall(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5)
{
int i, j, e;
Window *w;
Column *c;
char *a;
USED(et);
USED(_0);
USED(_1);
USED(_2);
USED(_3);
USED(_4);
USED(_5);
for(i=0; i<row.ncol; i++){
c = row.col[i];
for(j=0; j<c->nw; j++){
w = c->w[j];
if(w->isscratch || w->isdir || w->body.file->nname==0)
continue;
if(w->nopen[QWevent] > 0)
continue;
a = runetobyte(w->body.file->name, w->body.file->nname);
e = access(a, 0);
if(w->body.file->mod || w->body.ncache)
if(e < 0)
warning(nil, "no auto-Put of %s: %r\n", a);
else{
wincommit(w, &w->body);
put(&w->body, nil, nil, XXX, XXX, nil, 0);
}
free(a);
}
}
}
void
id(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5)
{
USED(_0);
USED(_1);
USED(_2);
USED(_3);
USED(_4);
USED(_5);
if(et && et->w)
warning(nil, "/mnt/acme/%d/\n", et->w->id);
}
void
local(Text *et, Text *_0, Text *argt, int _1, int _2, Rune *arg, int narg)
{
char *a, *aa;
Runestr dir;
USED(_0);
USED(_1);
USED(_2);
aa = getbytearg(argt, TRUE, TRUE, &a);
dir = dirname(et, nil, 0);
if(dir.nr==1 && dir.r[0]=='.'){ /* sigh */
free(dir.r);
dir.r = nil;
dir.nr = 0;
}
run(nil, runetobyte(arg, narg), dir.r, dir.nr, FALSE, aa, a, FALSE);
}
void
xkill(Text *_0, Text *_1, Text *argt, int _2, int _3, Rune *arg, int narg)
{
Rune *a, *cmd, *r;
int na;
USED(_0);
USED(_1);
USED(_2);
USED(_3);
getarg(argt, FALSE, FALSE, &r, &na);
if(r)
xkill(nil, nil, nil, 0, 0, r, na);
/* loop condition: *arg is not a blank */
for(;;){
a = findbl(arg, narg, &na);
if(a == arg)
break;
cmd = runemalloc(narg-na+1);
runemove(cmd, arg, narg-na);
sendp(ckill, cmd);
arg = skipbl(a, na, &narg);
}
}
static Rune Lfix[] = { 'f', 'i', 'x', 0 };
static Rune Lvar[] = { 'v', 'a', 'r', 0 };
void
fontx(Text *et, Text *t, Text *argt, int _0, int _1, Rune *arg, int narg)
{
Rune *a, *r, *flag, *file;
int na, nf;
char *aa;
Reffont *newfont;
Dirlist *dp;
int i, fix;
USED(_0);
USED(_1);
if(et==nil || et->w==nil)
return;
t = &et->w->body;
flag = nil;
file = nil;
/* loop condition: *arg is not a blank */
nf = 0;
for(;;){
a = findbl(arg, narg, &na);
if(a == arg)
break;
r = runemalloc(narg-na+1);
runemove(r, arg, narg-na);
if(runeeq(r, narg-na, Lfix, 3) || runeeq(r, narg-na, Lvar, 3)){
free(flag);
flag = r;
}else{
free(file);
file = r;
nf = narg-na;
}
arg = skipbl(a, na, &narg);
}
getarg(argt, FALSE, TRUE, &r, &na);
if(r)
if(runeeq(r, na, Lfix, 3) || runeeq(r, na, Lvar, 3)){
free(flag);
flag = r;
}else{
free(file);
file = r;
nf = na;
}
fix = 1;
if(flag)
fix = runeeq(flag, runestrlen(flag), Lfix, 3);
else if(file == nil){
newfont = rfget(FALSE, FALSE, FALSE, nil);
if(newfont)
fix = strcmp(newfont->f->name, t->fr.font->name)==0;
}
if(file){
aa = runetobyte(file, nf);
newfont = rfget(fix, flag!=nil, FALSE, aa);
free(aa);
}else
newfont = rfget(fix, FALSE, FALSE, nil);
if(newfont){
draw(screen, t->w->r, textcols[BACK], nil, ZP);
rfclose(t->reffont);
t->reffont = newfont;
t->fr.font = newfont->f;
frinittick(&t->fr);
if(t->w->isdir){
t->all.min.x++; /* force recolumnation; disgusting! */
for(i=0; i<t->w->ndl; i++){
dp = t->w->dlp[i];
aa = runetobyte(dp->r, dp->nr);
dp->wid = stringwidth(newfont->f, aa);
free(aa);
}
}
/* avoid shrinking of window due to quantization */
colgrow(t->w->col, t->w, -1);
}
free(file);
free(flag);
}
void
incl(Text *et, Text *_0, Text *argt, int _1, int _2, Rune *arg, int narg)
{
Rune *a, *r;
Window *w;
int na, n, len;
USED(_0);
USED(_1);
USED(_2);
if(et==nil || et->w==nil)
return;
w = et->w;
n = 0;
getarg(argt, FALSE, TRUE, &r, &len);
if(r){
n++;
winaddincl(w, r, len);
}
/* loop condition: *arg is not a blank */
for(;;){
a = findbl(arg, narg, &na);
if(a == arg)
break;
r = runemalloc(narg-na+1);
runemove(r, arg, narg-na);
n++;
winaddincl(w, r, narg-na);
arg = skipbl(a, na, &narg);
}
if(n==0 && w->nincl){
for(n=w->nincl; --n>=0; )
warning(nil, "%S ", w->incl[n]);
warning(nil, "\n");
}
}
static Rune LON[] = { 'O', 'N', 0 };
static Rune LOFF[] = { 'O', 'F', 'F', 0 };
static Rune Lon[] = { 'o', 'n', 0 };
enum {
IGlobal = -2,
IError = -1,
Ion = 0,
Ioff = 1
};
static int
indentval(Rune *s, int n)
{
if(n < 2)
return IError;
if(runestrncmp(s, LON, n) == 0){
globalautoindent = TRUE;
warning(nil, "Indent ON\n");
return IGlobal;
}
if(runestrncmp(s, LOFF, n) == 0){
globalautoindent = FALSE;
warning(nil, "Indent OFF\n");
return IGlobal;
}
return runestrncmp(s, Lon, n) == 0;
}
static void
fixindent(Window *w, void *arg)
{
USED(arg);
w->autoindent = globalautoindent;
}
void
indent(Text *et, Text *_0, Text *argt, int _1, int _2, Rune *arg, int narg)
{
Rune *a, *r;
Window *w;
int na, len, autoindent;
USED(_0);
USED(_1);
USED(_2);
w = nil;
if(et!=nil && et->w!=nil)
w = et->w;
autoindent = IError;
getarg(argt, FALSE, TRUE, &r, &len);
if(r!=nil && len>0)
autoindent = indentval(r, len);
else{
a = findbl(arg, narg, &na);
if(a != arg)
autoindent = indentval(arg, narg-na);
}
if(autoindent == IGlobal)
allwindows(fixindent, nil);
else if(w != nil && autoindent >= 0)
w->autoindent = autoindent;
}
void
tab(Text *et, Text *_0, Text *argt, int _1, int _2, Rune *arg, int narg)
{
Rune *a, *r;
Window *w;
int na, len, tab;
char *p;
USED(_0);
USED(_1);
USED(_2);
if(et==nil || et->w==nil)
return;
w = et->w;
getarg(argt, FALSE, TRUE, &r, &len);
tab = 0;
if(r!=nil && len>0){
p = runetobyte(r, len);
if('0'<=p[0] && p[0]<='9')
tab = atoi(p);
free(p);
}else{
a = findbl(arg, narg, &na);
if(a != arg){
p = runetobyte(arg, narg-na);
if('0'<=p[0] && p[0]<='9')
tab = atoi(p);
free(p);
}
}
if(tab > 0){
if(w->body.tabstop != tab){
w->body.tabstop = tab;
winresize(w, w->r, FALSE, TRUE);
}
}else
warning(nil, "%.*S: Tab %d\n", w->body.file->nname, w->body.file->name, w->body.tabstop);
}
void
runproc(void *argvp)
{
/* args: */
Window *win;
char *s;
Rune *rdir;
int ndir;
int newns;
char *argaddr;
char *arg;
Command *c;
Channel *cpid;
int iseditcmd;
/* end of args */
char *e, *t, *name, *filename, *dir, **av, *news;
Rune r, **incl;
int ac, w, inarg, i, n, fd, nincl, winid;
int sfd[3];
int pipechar;
char buf[512];
int olddir;
int ret;
/*static void *parg[2]; */
char *rcarg[4];
void **argv;
CFsys *fs;
threadsetname("runproc");
argv = argvp;
win = argv[0];
s = argv[1];
rdir = argv[2];
ndir = (uintptr)argv[3];
newns = (uintptr)argv[4];
argaddr = argv[5];
arg = argv[6];
c = argv[7];
cpid = argv[8];
iseditcmd = (uintptr)argv[9];
free(argv);
t = s;
while(*t==' ' || *t=='\n' || *t=='\t')
t++;
for(e=t; *e; e++)
if(*e==' ' || *e=='\n' || *e=='\t' )
break;
name = emalloc((e-t)+2);
memmove(name, t, e-t);
name[e-t] = 0;
e = utfrrune(name, '/');
if(e)
memmove(name, e+1, strlen(e+1)+1); /* strcpy but overlaps */
strcat(name, " "); /* add blank here for ease in waittask */
c->name = bytetorune(name, &c->nname);
free(name);
pipechar = 0;
if(*t=='<' || *t=='|' || *t=='>')
pipechar = *t++;
c->iseditcmd = iseditcmd;
c->text = s;
if(newns){
nincl = 0;
incl = nil;
if(win){
filename = smprint("%.*S", win->body.file->nname, win->body.file->name);
nincl = win->nincl;
if(nincl > 0){
incl = emalloc(nincl*sizeof(Rune*));
for(i=0; i<nincl; i++){
n = runestrlen(win->incl[i]);
incl[i] = runemalloc(n+1);
runemove(incl[i], win->incl[i], n);
}
}
winid = win->id;
}else{
filename = nil;
winid = 0;
if(activewin)
winid = activewin->id;
}
rfork(RFNAMEG|RFENVG|RFFDG|RFNOTEG);
sprint(buf, "%d", winid);
putenv("winid", buf);
if(filename){
putenv("%", filename);
free(filename);
}
c->md = fsysmount(rdir, ndir, incl, nincl);
if(c->md == nil){
fprint(2, "child: can't allocate mntdir: %r\n");
threadexits("fsysmount");
}
sprint(buf, "%d", c->md->id);
if((fs = nsmount("acme", buf)) == nil){
fprint(2, "child: can't mount acme: %r\n");
fsysdelid(c->md);
c->md = nil;
threadexits("nsmount");
}
if(winid>0 && (pipechar=='|' || pipechar=='>')){
sprint(buf, "%d/rdsel", winid);
sfd[0] = fsopenfd(fs, buf, OREAD);
}else
sfd[0] = open("/dev/null", OREAD);
if((winid>0 || iseditcmd) && (pipechar=='|' || pipechar=='<')){
if(iseditcmd){
if(winid > 0)
sprint(buf, "%d/editout", winid);
else
sprint(buf, "editout");
}else
sprint(buf, "%d/wrsel", winid);
sfd[1] = fsopenfd(fs, buf, OWRITE);
sfd[2] = fsopenfd(fs, "cons", OWRITE);
}else{
sfd[1] = fsopenfd(fs, "cons", OWRITE);
sfd[2] = sfd[1];
}
fsunmount(fs);
}else{
rfork(RFFDG|RFNOTEG);
fsysclose();
sfd[0] = open("/dev/null", OREAD);
sfd[1] = open("/dev/null", OWRITE);
sfd[2] = dup(erroutfd, -1);
}
if(win)
winclose(win);
if(argaddr)
putenv("acmeaddr", argaddr);
if(strlen(t) > sizeof buf-10) /* may need to print into stack */
goto Hard;
inarg = FALSE;
for(e=t; *e; e+=w){
w = chartorune(&r, e);
if(r==' ' || r=='\t')
continue;
if(r < ' ')
goto Hard;
if(utfrune("#;&|^$=`'{}()<>[]*?^~`", r))
goto Hard;
inarg = TRUE;
}
if(!inarg)
goto Fail;
ac = 0;
av = nil;
inarg = FALSE;
for(e=t; *e; e+=w){
w = chartorune(&r, e);
if(r==' ' || r=='\t'){
inarg = FALSE;
*e = 0;
continue;
}
if(!inarg){
inarg = TRUE;
av = realloc(av, (ac+1)*sizeof(char**));
av[ac++] = e;
}
}
av = realloc(av, (ac+2)*sizeof(char**));
av[ac++] = arg;
av[ac] = nil;
c->av = av;
/*
* clumsy -- we're not running in a separate thread
* so we have to save the current directory and put
* it back when we're done. if this gets to be a regular
* thing we could change threadexec to take a directory too.
*/
olddir = -1;
if(rdir != nil){
olddir = open(".", OREAD);
dir = runetobyte(rdir, ndir);
chdir(dir); /* ignore error: probably app. window */
free(dir);
}
ret = threadspawn(sfd, av[0], av);
if(olddir >= 0){
fchdir(olddir);
close(olddir);
}
if(ret >= 0){
if(cpid)
sendul(cpid, ret);
threadexits("");
}
/* libthread uses execvp so no need to do this */
#if 0
e = av[0];
if(e[0]=='/' || (e[0]=='.' && e[1]=='/'))
goto Fail;
if(cputype){
sprint(buf, "%s/%s", cputype, av[0]);
procexec(cpid, sfd, buf, av);
}
sprint(buf, "/bin/%s", av[0]);
procexec(cpid, sfd, buf, av);
#endif
goto Fail;
Hard:
/*
* ugly: set path = (. $cputype /bin)
* should honor $path if unusual.
*/
if(cputype){
n = 0;
memmove(buf+n, ".", 2);
n += 2;
i = strlen(cputype)+1;
memmove(buf+n, cputype, i);
n += i;
memmove(buf+n, "/bin", 5);
n += 5;
fd = create("/env/path", OWRITE, 0666);
write(fd, buf, n);
close(fd);
}
if(arg){
news = emalloc(strlen(t) + 1 + 1 + strlen(arg) + 1 + 1);
if(news){
sprint(news, "%s '%s'", t, arg); /* BUG: what if quote in arg? */
free(s);
t = news;
c->text = news;
}
}
olddir = -1;
if(rdir != nil){
olddir = open(".", OREAD);
dir = runetobyte(rdir, ndir);
chdir(dir); /* ignore error: probably app. window */
free(dir);
}
rcarg[0] = "rc";
rcarg[1] = "-c";
rcarg[2] = t;
rcarg[3] = nil;
ret = threadspawn(sfd, rcarg[0], rcarg);
if(olddir >= 0){
fchdir(olddir);
close(olddir);
}
if(ret >= 0){
if(cpid)
sendul(cpid, ret);
threadexits(nil);
}
warning(nil, "exec rc: %r\n");
Fail:
/* threadexec hasn't happened, so send a zero */
close(sfd[0]);
close(sfd[1]);
if(sfd[2] != sfd[1])
close(sfd[2]);
sendul(cpid, 0);
threadexits(nil);
}
void
runwaittask(void *v)
{
Command *c;
Channel *cpid;
void **a;
threadsetname("runwaittask");
a = v;
c = a[0];
cpid = a[1];
free(a);
do
c->pid = recvul(cpid);
while(c->pid == ~0);
free(c->av);
if(c->pid != 0) /* successful exec */
sendp(ccommand, c);
else{
if(c->iseditcmd)
sendul(cedit, 0);
free(c->name);
free(c->text);
free(c);
}
chanfree(cpid);
}
void
run(Window *win, char *s, Rune *rdir, int ndir, int newns, char *argaddr, char *xarg, int iseditcmd)
{
void **arg;
Command *c;
Channel *cpid;
if(s == nil)
return;
arg = emalloc(10*sizeof(void*));
c = emalloc(sizeof *c);
cpid = chancreate(sizeof(ulong), 0);
chansetname(cpid, "cpid %s", s);
arg[0] = win;
arg[1] = s;
arg[2] = rdir;
arg[3] = (void*)(uintptr)ndir;
arg[4] = (void*)(uintptr)newns;
arg[5] = argaddr;
arg[6] = xarg;
arg[7] = c;
arg[8] = cpid;
arg[9] = (void*)(uintptr)iseditcmd;
threadcreate(runproc, arg, STACK);
/* mustn't block here because must be ready to answer mount() call in run() */
arg = emalloc(2*sizeof(void*));
arg[0] = c;
arg[1] = cpid;
threadcreate(runwaittask, arg, STACK);
}