8918bd5981
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).
567 lines
11 KiB
C
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;
|
|
}
|
|
}
|
|
}
|
|
}
|