plan9fox/acme/bin/source/win/main.c
kvik 8918bd5981 acme: implement 'scratch' ctl command (thanks Drew DeVault)
The new command marks the target window as a scratch window -- a window
whose state cannot be "dirtied" by changes made to its body, therefore
avoiding warnings about unsaved changes when deleting the window or
exiting acme.

Existing examples of scratch windows are error, directory, and guide
windows, whose scratchness is set internally.

With the new command users and programs alike can create their own
scratch windows.  This is put to use in acme's own win(1).
2020-05-31 22:39:46 +02:00

567 lines
11 KiB
C

#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <fcall.h>
#include <9p.h>
#include <ctype.h>
#include "dat.h"
void mainctl(void*);
int debug;
int eraseinput;
int notepg = -1;
int dirty = 0;
char *wdir;
char *wname;
Window *win; /* the main window */
void
usage(void)
{
fprint(2, "usage: %s [-Dde] [windowname]\n", argv0);
threadexitsall("usage");
}
void
threadmain(int argc, char *argv[])
{
char buf[1024], *s;
quotefmtinstall();
ARGBEGIN{
case 'd':
debug = 1;
chatty9p++;
break;
case 'e':
eraseinput = 1;
break;
case 'D':
{extern int _threaddebuglevel;
_threaddebuglevel = 1<<20;
}
}ARGEND
if(argc == 0)
wname = argv0;
else
wname = argv[0];
s = utfrrune(wname, '/');
if(s != nil)
wname = s+1;
if(getwd(buf, sizeof buf) == 0)
wdir = "/";
else
wdir = buf;
wdir = estrdup(wdir);
win = newwindow();
winsetdir(win, wdir, wname);
wintagwrite(win, "Send Noscroll", 5+8);
ctlprint(win->ctl, "scroll");
ctlprint(win->ctl, "scratch");
snprint(buf, sizeof(buf), "/proc/%d/notepg", getpid());
notepg = open(buf, OWRITE);
mountcons(win);
snprint(buf, sizeof buf, "%d", win->id);
putenv("winid", buf);
snprint(buf, sizeof(buf), "/mnt/wsys/%d", win->id);
bind(buf, "/dev/acme", MREPL);
bind("/dev/acme/body", "/dev/text", MREPL);
threadexits(nil);
}
int
EQUAL(char *s, char *t)
{
while(tolower(*s) == tolower(*t++))
if(*s++ == '\0')
return 1;
return 0;
}
int
command(Window *w, char *s)
{
while(*s==' ' || *s=='\t' || *s=='\n')
s++;
if(strcmp(s, "Delete")==0 || strcmp(s, "Del")==0){
windel(w, 1);
threadexitsall(nil);
return 1;
}
if(EQUAL(s, "scroll")){
ctlprint(w->ctl, "scroll\nshow");
return 1;
}
if(EQUAL(s, "noscroll")){
ctlprint(w->ctl, "noscroll");
return 1;
}
return 0;
}
static long
utfncpy(char *to, char *from, int n)
{
char *end, *e;
e = to+n;
if(to >= e)
return 0;
end = memccpy(to, from, '\0', e - to);
if(end == nil){
end = e;
if(end[-1]&0x80){
if(end-2>=to && (end[-2]&0xE0)==0xC0)
return end-to;
if(end-3>=to && (end[-3]&0xF0)==0xE0)
return end-to;
while(end>to && (*--end&0xC0)==0x80)
;
}
}else
end--;
return end - to;
}
/* sendinput and fsloop run in the same proc (can't interrupt each other). */
static Req *q;
static Req **eq;
static int
__sendinput(Window *w, ulong q0, ulong q1)
{
char *s, *t;
int n, nb, eofchar;
static int partial;
static char tmp[UTFmax];
Req *r;
Rune rune;
if(!q)
return 0;
r = q;
n = 0;
if(partial){
Partial:
nb = partial;
if(nb > r->ifcall.count)
nb = r->ifcall.count;
memmove(r->ofcall.data, tmp, nb);
if(nb!=partial)
memmove(tmp, tmp+nb, partial-nb);
partial -= nb;
q = r->aux;
if(q == nil)
eq = &q;
r->aux = nil;
r->ofcall.count = nb;
if(debug)
fprint(2, "satisfy read with partial\n");
respond(r, nil);
return n;
}
if(q0==q1)
return 0;
s = emalloc((q1-q0)*UTFmax+1);
n = winread(w, q0, q1, s);
s[n] = '\0';
t = strpbrk(s, "\n\004");
if(t == nil){
free(s);
return 0;
}
r = q;
eofchar = 0;
if(*t == '\004'){
eofchar = 1;
*t = '\0';
}else
*++t = '\0';
nb = utfncpy((char*)r->ofcall.data, s, r->ifcall.count);
if(nb==0 && s<t && r->ifcall.count > 0){
partial = utfncpy(tmp, s, UTFmax);
assert(partial > 0);
chartorune(&rune, tmp);
partial = runelen(rune);
free(s);
n = 1;
goto Partial;
}
n = utfnlen(r->ofcall.data, nb);
if(nb==strlen(s) && eofchar)
n++;
r->ofcall.count = nb;
q = r->aux;
if(q == nil)
eq = &q;
r->aux = nil;
if(debug)
fprint(2, "read returns %lud-%lud: %.*q\n", q0, q0+n, n, r->ofcall.data);
respond(r, nil);
return n;
}
static int
_sendinput(Window *w, ulong q0, ulong *q1)
{
char buf[32];
int n;
n = __sendinput(w, q0, *q1);
if(!n || !eraseinput)
return n;
/* erase q0 to q0+n */
snprint(buf, sizeof(buf), "#%lud,#%lud", q0, q0+n);
winsetaddr(w, buf, 0);
write(w->data, buf, 0);
*q1 -= n;
return 0;
}
int
sendinput(Window *w, ulong q0, ulong *q1)
{
ulong n;
Req *oq;
n = 0;
do {
oq = q;
n += _sendinput(w, q0+n, q1);
} while(q != oq);
return n;
}
Event esendinput;
void
fsloop(void *arg)
{
Fsevent e;
Req **l, *r;
Window *w = arg; /* the main window */
threadcreate(mainctl, w, STACK);
startpipe();
eq = &q;
memset(&esendinput, 0, sizeof esendinput);
esendinput.c1 = 'C';
for(;;){
while(recv(fschan, &e) == -1)
;
r = e.r;
switch(e.type){
case 'r':
*eq = r;
r->aux = nil;
eq = &r->aux;
/* call sendinput with hostpt and endpt */
sendp(win->cevent, &esendinput);
break;
case 'f':
for(l=&q; *l; l=&(*l)->aux){
if(*l == r->oldreq){
*l = (*l)->aux;
if(*l == nil)
eq = l;
respond(r->oldreq, "interrupted");
break;
}
}
respond(r, nil);
break;
}
}
}
void
sendit(char *s)
{
// char tmp[32];
write(win->body, s, strlen(s));
/*
* RSC: The problem here is that other procs can call sendit,
* so we lose our single-threadedness if we call sendinput.
* In fact, we don't even have the right queue memory,
* I think that we'll get a write event from the body write above,
* and we can do the sendinput then, from our single thread.
*
* I still need to figure out how to test this assertion for
* programs that use /srv/win*
*
winselect(win, "$", 0);
seek(win->addr, 0UL, 0);
if(read(win->addr, tmp, 2*12) == 2*12)
hostpt += sendinput(win, hostpt, atol(tmp), );
*/
}
void
execevent(Window *w, Event *e, int (*command)(Window*, char*))
{
Event *ea, *e2;
int n, na, len, needfree;
char *s, *t;
ea = nil;
e2 = nil;
if(e->flag & 2)
e2 = recvp(w->cevent);
if(e->flag & 8){
ea = recvp(w->cevent);
na = ea->nb;
recvp(w->cevent);
}else
na = 0;
needfree = 0;
s = e->b;
if(e->nb==0 && (e->flag&2)){
s = e2->b;
e->q0 = e2->q0;
e->q1 = e2->q1;
e->nb = e2->nb;
}
if(e->nb==0 && e->q0<e->q1){
/* fetch data from window */
s = emalloc((e->q1-e->q0)*UTFmax+2);
n = winread(w, e->q0, e->q1, s);
s[n] = '\0';
needfree = 1;
}else
if(na){
t = emalloc(strlen(s)+1+na+2);
sprint(t, "%s %s", s, ea->b);
if(needfree)
free(s);
s = t;
needfree = 1;
}
/* if it's a known command, do it */
/* if it's a long message, it can't be for us anyway */
if(!command(w, s) && s[0]!='\0'){ /* send it as typed text */
/* if it's a built-in from the tag, send it back */
if(e->flag & 1)
fprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
else{ /* send text to main window */
len = strlen(s);
if(len>0 && s[len-1]!='\n' && s[len-1]!='\004'){
if(!needfree){
/* if(needfree), we left room for a newline before */
t = emalloc(len+2);
strcpy(t, s);
s = t;
needfree = 1;
}
s[len++] = '\n';
s[len] = '\0';
}
sendit(s);
}
}
if(needfree)
free(s);
}
int
hasboundary(Rune *r, int nr)
{
int i;
for(i=0; i<nr; i++)
if(r[i]=='\n' || r[i]=='\004')
return 1;
return 0;
}
void
mainctl(void *v)
{
Window *w;
Event *e;
int delta, pendingS, pendingK;
ulong hostpt, endpt;
char tmp[32];
w = v;
proccreate(wineventproc, w, STACK);
hostpt = 0;
endpt = 0;
winsetaddr(w, "0", 0);
pendingS = 0;
pendingK = 0;
for(;;){
if(debug)
fprint(2, "input range %lud-%lud\n", hostpt, endpt);
e = recvp(w->cevent);
if(debug)
fprint(2, "msg: %C %C %d %d %d %d %q\n",
e->c1 ? e->c1 : ' ', e->c2 ? e->c2 : ' ', e->q0, e->q1, e->flag, e->nb, e->b);
switch(e->c1){
default:
Unknown:
fprint(2, "unknown message %c%c\n", e->c1, e->c2);
break;
case 'C': /* input needed for /dev/cons */
if(pendingS)
pendingK = 1;
else
hostpt += sendinput(w, hostpt, &endpt);
break;
case 'S': /* output to stdout */
sprint(tmp, "#%lud", hostpt);
winsetaddr(w, tmp, 0);
write(w->data, e->b, e->nb);
pendingS += e->nr;
break;
case 'E': /* write to tag or body; body happens due to sendit */
delta = e->q1-e->q0;
if(e->c2=='I'){
endpt += delta;
if(e->q0 < hostpt)
hostpt += delta;
else
hostpt += sendinput(w, hostpt, &endpt);
break;
}
if(!islower(e->c2))
fprint(2, "win msg: %C %C %d %d %d %d %q\n",
e->c1, e->c2, e->q0, e->q1, e->flag, e->nb, e->b);
break;
case 'F': /* generated by our actions (specifically case 'S' above) */
delta = e->q1-e->q0;
if(e->c2=='D'){
/* we know about the delete by _sendinput */
break;
}
if(e->c2=='I'){
pendingS -= e->q1 - e->q0;
if(pendingS < 0)
fprint(2, "win: pendingS = %d\n", pendingS);
if(e->q0 != hostpt)
fprint(2, "win: insert at %d expected %lud\n", e->q0, hostpt);
endpt += delta;
hostpt += delta;
sendp(writechan, nil);
if(pendingS == 0 && pendingK){
pendingK = 0;
hostpt += sendinput(w, hostpt, &endpt);
}
break;
}
if(!islower(e->c2))
fprint(2, "win msg: %C %C %d %d %d %d %q\n",
e->c1, e->c2, e->q0, e->q1, e->flag, e->nb, e->b);
break;
case 'K':
delta = e->q1-e->q0;
switch(e->c2){
case 'D':
endpt -= delta;
if(e->q1 < hostpt)
hostpt -= delta;
else if(e->q0 < hostpt)
hostpt = e->q0;
break;
case 'I':
delta = e->q1 - e->q0;
endpt += delta;
if(endpt < e->q1) /* just in case */
endpt = e->q1;
if(e->q0 < hostpt)
hostpt += delta;
if(e->nr>0 && e->r[e->nr-1]==0x7F){
write(notepg, "interrupt", 9);
hostpt = endpt;
break;
}
if(e->q0 >= hostpt
&& hasboundary(e->r, e->nr)){
/*
* If we are between the S message (which
* we processed by inserting text in the
* window) and the F message notifying us
* that the text has been inserted, then our
* impression of the hostpt and acme's
* may be different. This could be seen if you
* hit enter a bunch of times in a con
* session. To work around the unreliability,
* only send input if we don't have an S pending.
* The same race occurs between when a character
* is typed and when we get notice of it, but
* since characters tend to be typed at the end
* of the buffer, we don't run into it. There's
* no workaround possible for this typing race,
* since we can't tell when the user has typed
* something but we just haven't been notified.
*/
if(pendingS)
pendingK = 1;
else
hostpt += sendinput(w, hostpt, &endpt);
}
break;
}
break;
case 'M': /* mouse */
delta = e->q1-e->q0;
switch(e->c2){
case 'x':
case 'X':
execevent(w, e, command);
break;
case 'l': /* reflect all searches back to acme */
case 'L':
if(e->flag & 2)
recvp(w->cevent);
winwriteevent(w, e);
break;
case 'I':
endpt += delta;
if(e->q0 < hostpt)
hostpt += delta;
else
hostpt += sendinput(w, hostpt, &endpt);
break;
case 'D':
endpt -= delta;
if(e->q1 < hostpt)
hostpt -= delta;
else if(e->q0 < hostpt)
hostpt = e->q0;
break;
case 'd': /* modify away; we don't care */
case 'i':
break;
default:
goto Unknown;
}
}
}
}