| #include <u.h> |
| #include <libc.h> |
| #include <draw.h> |
| #include <thread.h> |
| #include <mouse.h> |
| #include <cursor.h> |
| #include <keyboard.h> |
| #include <frame.h> |
| #include "flayer.h" |
| #include "samterm.h" |
| |
| Text cmd; |
| Rune *scratch; |
| long nscralloc; |
| Cursor *cursor; |
| Flayer *which = 0; |
| Flayer *work = 0; |
| long snarflen; |
| long typestart = -1; |
| long typeend = -1; |
| long typeesc = -1; |
| long modified = 0; /* strange lookahead for menus */ |
| char hostlock = 1; |
| char hasunlocked = 0; |
| int maxtab = 8; |
| int chord; |
| int autoindent; |
| |
| #define chording 0 /* code here for reference but it causes deadlocks */ |
| |
| void |
| notifyf(void *a, char *msg) |
| { |
| if(strcmp(msg, "interrupt") == 0) |
| noted(NCONT); |
| noted(NDFLT); |
| } |
| |
| void |
| threadmain(int argc, char *argv[]) |
| { |
| int i, got, scr, w; |
| Text *t; |
| Rectangle r; |
| Flayer *nwhich; |
| |
| /* |
| * sam is talking to us on fd 0 and 1. |
| * move these elsewhere so that if we accidentally |
| * use 0 and 1 in other code, nothing bad happens. |
| */ |
| dup(0, 3); |
| dup(1, 4); |
| hostfd[0] = 3; |
| hostfd[1] = 4; |
| close(0); |
| close(1); |
| open("/dev/null", OREAD); |
| if(open("/dev/tty", OWRITE) < 0) |
| open("/dev/null", OWRITE); |
| |
| notify(notifyf); |
| |
| if(protodebug) print("getscreen\n"); |
| getscreen(argc, argv); |
| if(protodebug) print("iconinit\n"); |
| iconinit(); |
| if(protodebug) print("initio\n"); |
| initio(); |
| if(protodebug) print("scratch\n"); |
| scratch = alloc(100*RUNESIZE); |
| nscralloc = 100; |
| r = screen->r; |
| r.max.y = r.min.y+Dy(r)/5; |
| if(protodebug) print("flstart\n"); |
| flstart(screen->clipr); |
| rinit(&cmd.rasp); |
| flnew(&cmd.l[0], gettext, 1, &cmd); |
| flinit(&cmd.l[0], r, font, cmdcols); |
| cmd.nwin = 1; |
| which = &cmd.l[0]; |
| cmd.tag = Untagged; |
| outTs(Tversion, VERSION); |
| startnewfile(Tstartcmdfile, &cmd); |
| |
| got = 0; |
| if(protodebug) print("loop\n"); |
| for(;;got = waitforio()){ |
| if(hasunlocked && RESIZED()) |
| resize(); |
| if(got&(1<<RHost)) |
| rcv(); |
| if(got&(1<<RPlumb)){ |
| for(i=0; cmd.l[i].textfn==0; i++) |
| ; |
| current(&cmd.l[i]); |
| flsetselect(which, cmd.rasp.nrunes, cmd.rasp.nrunes); |
| type(which, RPlumb); |
| } |
| if(got&(1<<RKeyboard)) |
| if(which) |
| type(which, RKeyboard); |
| else |
| kbdblock(); |
| if(got&(1<<RMouse)){ |
| if(hostlock==2 || !ptinrect(mousep->xy, screen->r)){ |
| mouseunblock(); |
| continue; |
| } |
| nwhich = flwhich(mousep->xy); |
| scr = which && ptinrect(mousep->xy, which->scroll); |
| if(mousep->buttons) |
| flushtyping(1); |
| if(chording && chord==1 && !mousep->buttons) |
| chord = 0; |
| if(chording && chord) |
| chord |= mousep->buttons; |
| else if(mousep->buttons&1){ |
| if(nwhich){ |
| if(nwhich!=which) |
| current(nwhich); |
| else if(scr) |
| scroll(which, 1); |
| else{ |
| t=(Text *)which->user1; |
| if(flselect(which)){ |
| outTsl(Tdclick, t->tag, which->p0); |
| t->lock++; |
| }else if(t!=&cmd) |
| outcmd(); |
| if(mousep->buttons&1) |
| chord = mousep->buttons; |
| } |
| } |
| }else if((mousep->buttons&2) && which){ |
| if(scr) |
| scroll(which, 2); |
| else |
| menu2hit(); |
| }else if((mousep->buttons&4)){ |
| if(scr) |
| scroll(which, 3); |
| else |
| menu3hit(); |
| } |
| mouseunblock(); |
| } |
| if(chording && chord){ |
| t = (Text*)which->user1; |
| if(!t->lock && !hostlock){ |
| w = which-t->l; |
| if(chord&2){ |
| cut(t, w, 1, 1); |
| chord &= ~2; |
| }else if(chord&4){ |
| paste(t, w); |
| chord &= ~4; |
| } |
| } |
| } |
| } |
| } |
| |
| void |
| resize(void) |
| { |
| int i; |
| |
| flresize(screen->clipr); |
| for(i = 0; i<nname; i++) |
| if(text[i]) |
| hcheck(text[i]->tag); |
| } |
| |
| void |
| current(Flayer *nw) |
| { |
| Text *t; |
| |
| if(which) |
| flborder(which, 0); |
| if(nw){ |
| flushtyping(1); |
| flupfront(nw); |
| flborder(nw, 1); |
| buttons(Up); |
| t = (Text *)nw->user1; |
| t->front = nw-&t->l[0]; |
| if(t != &cmd) |
| work = nw; |
| } |
| which = nw; |
| } |
| |
| void |
| closeup(Flayer *l) |
| { |
| Text *t=(Text *)l->user1; |
| int m; |
| |
| m = whichmenu(t->tag); |
| if(m < 0) |
| return; |
| flclose(l); |
| if(l == which){ |
| which = 0; |
| current(flwhich(Pt(0, 0))); |
| } |
| if(l == work) |
| work = 0; |
| if(--t->nwin == 0){ |
| rclear(&t->rasp); |
| free((uchar *)t); |
| text[m] = 0; |
| }else if(l == &t->l[t->front]){ |
| for(m=0; m<NL; m++) /* find one; any one will do */ |
| if(t->l[m].textfn){ |
| t->front = m; |
| return; |
| } |
| panic("close"); |
| } |
| } |
| |
| Flayer * |
| findl(Text *t) |
| { |
| int i; |
| for(i = 0; i<NL; i++) |
| if(t->l[i].textfn==0) |
| return &t->l[i]; |
| return 0; |
| } |
| |
| void |
| duplicate(Flayer *l, Rectangle r, Font *f, int close) |
| { |
| Text *t=(Text *)l->user1; |
| Flayer *nl = findl(t); |
| Rune *rp; |
| ulong n; |
| |
| if(nl){ |
| flnew(nl, gettext, l->user0, (char *)t); |
| flinit(nl, r, f, l->f.cols); |
| nl->origin = l->origin; |
| rp = (*l->textfn)(l, l->f.nchars, &n); |
| flinsert(nl, rp, rp+n, l->origin); |
| flsetselect(nl, l->p0, l->p1); |
| if(close){ |
| flclose(l); |
| if(l==which) |
| which = 0; |
| }else |
| t->nwin++; |
| current(nl); |
| hcheck(t->tag); |
| } |
| setcursor(mousectl, cursor); |
| } |
| |
| void |
| buttons(int updown) |
| { |
| while(((mousep->buttons&7)!=0) != updown) |
| getmouse(); |
| } |
| |
| int |
| getr(Rectangle *rp) |
| { |
| Point p; |
| Rectangle r; |
| |
| *rp = getrect(3, mousectl); |
| if(rp->max.x && rp->max.x-rp->min.x<=5 && rp->max.y-rp->min.y<=5){ |
| p = rp->min; |
| r = cmd.l[cmd.front].entire; |
| *rp = screen->r; |
| if(cmd.nwin==1){ |
| if (p.y <= r.min.y) |
| rp->max.y = r.min.y; |
| else if (p.y >= r.max.y) |
| rp->min.y = r.max.y; |
| if (p.x <= r.min.x) |
| rp->max.x = r.min.x; |
| else if (p.x >= r.max.x) |
| rp->min.x = r.max.x; |
| } |
| } |
| return rectclip(rp, screen->r) && |
| rp->max.x-rp->min.x>100 && rp->max.y-rp->min.y>40; |
| } |
| |
| void |
| snarf(Text *t, int w) |
| { |
| Flayer *l = &t->l[w]; |
| |
| if(l->p1>l->p0){ |
| snarflen = l->p1-l->p0; |
| outTsll(Tsnarf, t->tag, l->p0, l->p1); |
| } |
| } |
| |
| void |
| cut(Text *t, int w, int save, int check) |
| { |
| long p0, p1; |
| Flayer *l; |
| |
| l = &t->l[w]; |
| p0 = l->p0; |
| p1 = l->p1; |
| if(p0 == p1) |
| return; |
| if(p0 < 0) |
| panic("cut"); |
| if(save) |
| snarf(t, w); |
| outTsll(Tcut, t->tag, p0, p1); |
| flsetselect(l, p0, p0); |
| t->lock++; |
| hcut(t->tag, p0, p1-p0); |
| if(check) |
| hcheck(t->tag); |
| } |
| |
| void |
| paste(Text *t, int w) |
| { |
| if(snarflen){ |
| cut(t, w, 0, 0); |
| t->lock++; |
| outTsl(Tpaste, t->tag, t->l[w].p0); |
| } |
| } |
| |
| void |
| scrorigin(Flayer *l, int but, long p0) |
| { |
| Text *t=(Text *)l->user1; |
| |
| switch(but){ |
| case 1: |
| outTsll(Torigin, t->tag, l->origin, p0); |
| break; |
| case 2: |
| outTsll(Torigin, t->tag, p0, 1L); |
| break; |
| case 3: |
| horigin(t->tag,p0); |
| } |
| } |
| |
| int |
| alnum(int 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 0; |
| if(0x7F<=c && c<=0xA0) |
| return 0; |
| if(utfrune("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", c)) |
| return 0; |
| return 1; |
| } |
| |
| int |
| raspc(Rasp *r, long p) |
| { |
| ulong n; |
| rload(r, p, p+1, &n); |
| if(n) |
| return scratch[0]; |
| return 0; |
| } |
| |
| long |
| ctlw(Rasp *r, long o, long p) |
| { |
| int c; |
| |
| if(--p < o) |
| return o; |
| if(raspc(r, p)=='\n') |
| return p; |
| for(; p>=o && !alnum(c=raspc(r, p)); --p) |
| if(c=='\n') |
| return p+1; |
| for(; p>o && alnum(raspc(r, p-1)); --p) |
| ; |
| return p>=o? p : o; |
| } |
| |
| long |
| ctlu(Rasp *r, long o, long p) |
| { |
| if(--p < o) |
| return o; |
| if(raspc(r, p)=='\n') |
| return p; |
| for(; p-1>=o && raspc(r, p-1)!='\n'; --p) |
| ; |
| return p>=o? p : o; |
| } |
| |
| int |
| center(Flayer *l, long a) |
| { |
| Text *t; |
| |
| t = l->user1; |
| if(!t->lock && (a<l->origin || l->origin+l->f.nchars<a)){ |
| if(a > t->rasp.nrunes) |
| a = t->rasp.nrunes; |
| outTsll(Torigin, t->tag, a, 2L); |
| return 1; |
| } |
| return 0; |
| } |
| |
| int |
| thirds(Flayer *l, long a, int n) |
| { |
| Text *t; |
| Rectangle s; |
| long lines; |
| |
| t = l->user1; |
| if(!t->lock && (a<l->origin || l->origin+l->f.nchars<a)){ |
| if(a > t->rasp.nrunes) |
| a = t->rasp.nrunes; |
| s = insetrect(l->scroll, 1); |
| lines = (n*(s.max.y-s.min.y)/l->f.font->height+1)/3; |
| if (lines < 2) |
| lines = 2; |
| outTsll(Torigin, t->tag, a, lines); |
| return 1; |
| } |
| return 0; |
| } |
| |
| int |
| onethird(Flayer *l, long a) |
| { |
| return thirds(l, a, 1); |
| } |
| |
| int |
| twothirds(Flayer *l, long a) |
| { |
| return thirds(l, a, 2); |
| } |
| |
| void |
| flushtyping(int clearesc) |
| { |
| Text *t; |
| ulong n; |
| |
| if(clearesc) |
| typeesc = -1; |
| if(typestart == typeend) { |
| modified = 0; |
| return; |
| } |
| t = which->user1; |
| if(t != &cmd) |
| modified = 1; |
| rload(&t->rasp, typestart, typeend, &n); |
| scratch[n] = 0; |
| if(t==&cmd && typeend==t->rasp.nrunes && scratch[typeend-typestart-1]=='\n'){ |
| setlock(); |
| outcmd(); |
| } |
| outTslS(Ttype, t->tag, typestart, scratch); |
| typestart = -1; |
| typeend = -1; |
| } |
| |
| #define BACKSCROLLKEY Kup |
| #define ENDKEY Kend |
| #define ESC 0x1B |
| #define HOMEKEY Khome |
| #define LEFTARROW Kleft |
| #define LINEEND 0x05 |
| #define LINESTART 0x01 |
| #define PAGEDOWN Kpgdown |
| #define PAGEUP Kpgup |
| #define RIGHTARROW Kright |
| #define SCROLLKEY Kdown |
| #define CUT (Kcmd+'x') |
| #define COPY (Kcmd+'c') |
| #define PASTE (Kcmd+'v') |
| |
| int |
| nontypingkey(int c) |
| { |
| switch(c){ |
| case BACKSCROLLKEY: |
| case ENDKEY: |
| case HOMEKEY: |
| case LEFTARROW: |
| case LINEEND: |
| case LINESTART: |
| case PAGEDOWN: |
| case PAGEUP: |
| case RIGHTARROW: |
| case SCROLLKEY: |
| return 1; |
| } |
| if(c >= Kcmd) |
| return 1; |
| return 0; |
| } |
| |
| void |
| type(Flayer *l, int res) /* what a bloody mess this is */ |
| { |
| Text *t = (Text *)l->user1; |
| Rune buf[100]; |
| Rune *p = buf; |
| int c, backspacing; |
| long a, a0; |
| int scrollkey; |
| |
| scrollkey = 0; |
| if(res == RKeyboard) |
| scrollkey = nontypingkey(qpeekc()); /* ICK */ |
| |
| if(hostlock || t->lock){ |
| kbdblock(); |
| return; |
| } |
| a = l->p0; |
| if(a!=l->p1 && !scrollkey){ |
| flushtyping(1); |
| cut(t, t->front, 1, 1); |
| return; /* it may now be locked */ |
| } |
| backspacing = 0; |
| while((c = kbdchar())>0){ |
| if(res == RKeyboard){ |
| if(nontypingkey(c) || c==ESC) |
| break; |
| /* backspace, ctrl-u, ctrl-w, del */ |
| if(c=='\b' || c==0x15 || c==0x17 || c==0x7F){ |
| backspacing = 1; |
| break; |
| } |
| } |
| *p++ = c; |
| if(autoindent) |
| if(c == '\n'){ |
| /* autoindent */ |
| int cursor, ch; |
| cursor = ctlu(&t->rasp, 0, a+(p-buf)-1); |
| while(p < buf+nelem(buf)){ |
| ch = raspc(&t->rasp, cursor++); |
| if(ch == ' ' || ch == '\t') |
| *p++ = ch; |
| else |
| break; |
| } |
| } |
| if(c == '\n' || p >= buf+nelem(buf)) |
| break; |
| } |
| if(p > buf){ |
| if(typestart < 0) |
| typestart = a; |
| if(typeesc < 0) |
| typeesc = a; |
| hgrow(t->tag, a, p-buf, 0); |
| t->lock++; /* pretend we Trequest'ed for hdatarune*/ |
| hdatarune(t->tag, a, buf, p-buf); |
| a += p-buf; |
| l->p0 = a; |
| l->p1 = a; |
| typeend = a; |
| if(c=='\n' || typeend-typestart>100) |
| flushtyping(0); |
| onethird(l, a); |
| } |
| if(c==SCROLLKEY || c==PAGEDOWN){ |
| flushtyping(0); |
| center(l, l->origin+l->f.nchars+1); |
| }else if(c==BACKSCROLLKEY || c==PAGEUP){ |
| flushtyping(0); |
| a0 = l->origin-l->f.nchars; |
| if(a0 < 0) |
| a0 = 0; |
| center(l, a0); |
| }else if(c == RIGHTARROW){ |
| flushtyping(0); |
| a0 = l->p0; |
| if(a0 < t->rasp.nrunes) |
| a0++; |
| flsetselect(l, a0, a0); |
| center(l, a0); |
| }else if(c == LEFTARROW){ |
| flushtyping(0); |
| a0 = l->p0; |
| if(a0 > 0) |
| a0--; |
| flsetselect(l, a0, a0); |
| center(l, a0); |
| }else if(c == HOMEKEY){ |
| flushtyping(0); |
| center(l, 0); |
| }else if(c == ENDKEY){ |
| flushtyping(0); |
| center(l, t->rasp.nrunes); |
| }else if(c == LINESTART || c == LINEEND){ |
| flushtyping(1); |
| if(c == LINESTART) |
| while(a > 0 && raspc(&t->rasp, a-1)!='\n') |
| a--; |
| else |
| while(a < t->rasp.nrunes && raspc(&t->rasp, a)!='\n') |
| a++; |
| l->p0 = l->p1 = a; |
| for(l=t->l; l<&t->l[NL]; l++) |
| if(l->textfn) |
| flsetselect(l, l->p0, l->p1); |
| }else if(backspacing && !hostlock){ |
| /* backspacing immediately after outcmd(): sorry */ |
| if(l->f.p0>0 && a>0){ |
| switch(c){ |
| case '\b': |
| case 0x7F: /* del */ |
| l->p0 = a-1; |
| break; |
| case 0x15: /* ctrl-u */ |
| l->p0 = ctlu(&t->rasp, l->origin, a); |
| break; |
| case 0x17: /* ctrl-w */ |
| l->p0 = ctlw(&t->rasp, l->origin, a); |
| break; |
| } |
| l->p1 = a; |
| if(l->p1 != l->p0){ |
| /* cut locally if possible */ |
| if(typestart<=l->p0 && l->p1<=typeend){ |
| t->lock++; /* to call hcut */ |
| hcut(t->tag, l->p0, l->p1-l->p0); |
| /* hcheck is local because we know rasp is contiguous */ |
| hcheck(t->tag); |
| }else{ |
| flushtyping(0); |
| cut(t, t->front, 0, 1); |
| } |
| } |
| if(typeesc >= l->p0) |
| typeesc = l->p0; |
| if(typestart >= 0){ |
| if(typestart >= l->p0) |
| typestart = l->p0; |
| typeend = l->p0; |
| if(typestart == typeend){ |
| typestart = -1; |
| typeend = -1; |
| modified = 0; |
| } |
| } |
| } |
| }else{ |
| if(c==ESC && typeesc>=0){ |
| l->p0 = typeesc; |
| l->p1 = a; |
| flushtyping(1); |
| } |
| for(l=t->l; l<&t->l[NL]; l++) |
| if(l->textfn) |
| flsetselect(l, l->p0, l->p1); |
| switch(c) { |
| case CUT: |
| flushtyping(0); |
| cut(t, t->front, 1, 1); |
| break; |
| case COPY: |
| flushtyping(0); |
| snarf(t, t->front); |
| break; |
| case PASTE: |
| flushtyping(0); |
| paste(t, t->front); |
| break; |
| } |
| } |
| } |
| |
| |
| void |
| outcmd(void){ |
| if(work) |
| outTsll(Tworkfile, ((Text *)work->user1)->tag, work->p0, work->p1); |
| } |
| |
| void |
| panic(char *s) |
| { |
| panic1(display, s); |
| } |
| |
| void |
| panic1(Display *d, char *s) |
| { |
| fprint(2, "samterm:panic: "); |
| perror(s); |
| abort(); |
| } |
| |
| Rune* |
| gettext(Flayer *l, long n, ulong *np) |
| { |
| Text *t; |
| |
| t = l->user1; |
| rload(&t->rasp, l->origin, l->origin+n, np); |
| return scratch; |
| } |
| |
| long |
| scrtotal(Flayer *l) |
| { |
| return ((Text *)l->user1)->rasp.nrunes; |
| } |
| |
| void* |
| alloc(ulong n) |
| { |
| void *p; |
| |
| p = malloc(n); |
| if(p == 0) |
| panic("alloc"); |
| memset(p, 0, n); |
| return p; |
| } |