3 Rectangle scrollr; /* scroll bar rectangle */
4 Rectangle lastsr; /* used for scroll bar */
5 int holdon; /* hold mode */
6 int rawon; /* raw mode */
7 int scrolling; /* window scrolls */
8 int clickmsec; /* time of last click */
9 uint clickq0; /* point of last click */
20 int label(Rune*, int);
23 void hangupnote(void*, char*);
49 {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC,
50 0xFF, 0xF0, 0xFF, 0xF0, 0xFF, 0xF8, 0xFF, 0xFC,
51 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC,
52 0xF3, 0xF8, 0xF1, 0xF0, 0xE0, 0xE0, 0xC0, 0x40, },
53 {0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x06, 0xC0, 0x1C,
54 0xC0, 0x30, 0xC0, 0x30, 0xC0, 0x38, 0xC0, 0x1C,
55 0xC0, 0x0E, 0xC0, 0x07, 0xCE, 0x0E, 0xDF, 0x1C,
56 0xD3, 0xB8, 0xF1, 0xF0, 0xE0, 0xE0, 0xC0, 0x40, }
62 fprint(2, "usage: 9term [-ars] [cmd ...]\n");
63 threadexitsall("usage");
67 threadmain(int argc, char *argv[])
76 case 'a': /* acme mode */
80 /* not clear this is useful */
88 p = getenv("tabstop");
90 p = getenv("TABSTOP");
91 if(p != 0 && maxtab <= 0)
92 maxtab = strtoul(p, 0, 0);
96 initdraw(nil, nil, "9term");
99 mc = initmouse(nil, screen);
100 kc = initkeyboard(nil);
101 rcstart(rcfd, argc, argv);
105 t.f = mallocz(sizeof(Frame), 1);
107 cols[BACK] = allocimagemix(display, DPaleyellow, DWhite);
108 cols[HIGH] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DDarkyellow);
109 cols[BORD] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DYellowgreen);
110 cols[TEXT] = display->black;
111 cols[HTEXT] = display->black;
113 hcols[BACK] = cols[BACK];
114 hcols[HIGH] = cols[HIGH];
115 hcols[BORD] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 0x006600FF);
116 hcols[TEXT] = hcols[BORD];
117 hcols[HTEXT] = hcols[TEXT];
119 plumbcolor = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x006600FF);
120 execcolor = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xAA0000FF);
122 draw(screen, screen->r, cols[BACK], nil, ZP);
128 hangupnote(void *a, char *msg)
130 if(getpid() != mainpid)
132 if(strcmp(msg, "hangup") == 0 && rcpid != 0){
133 postnote(PNGROUP, rcpid, "hangup");
149 i = 1-i; /* toggle */
150 n = read(rcfd[0], rcbuf[i].data, sizeof rcbuf[i].data);
153 fprint(2, "9term: host read error: %r\n");
154 threadexitsall("host");
165 hostc = chancreate(sizeof(int), 0);
166 proccreate(hostproc, hostc, 32*1024);
175 {mc->c, &mc->m, CHANRCV},
176 {kc->c, &r, CHANRCV},
177 {hostc, &i, CHANRCV},
178 {mc->resizec, nil, CHANRCV},
186 flushimage(display, 1);
188 if(!scrolling && t.qh > t.org+t.f->nchars)
201 conswrite(rcbuf[i].data, rcbuf[i].n);
213 if(getwindow(display, Refnone) < 0)
214 fatal("can't reattach to window");
215 draw(screen, screen->r, cols[BACK], nil, ZP);
231 scrollr.max.x = r.min.x+Scrollwid;
232 lastsr = Rect(0,0,0,0);
234 r.min.x += Scrollwid+Scrollgap;
237 frinit(t.f, r, font, screen, holdon ? hcols : cols);
238 t.f->maxtab = maxtab*stringwidth(font, "0");
242 p = stringsize(font, "0");
243 if(p.x == 0 || p.y == 0)
246 ws.ws_row = Dy(r)/p.y;
247 ws.ws_col = Dx(r)/p.x;
248 ws.ws_xpixel = Dx(r);
249 ws.ws_ypixel = Dy(r);
250 if(ws.ws_row != ows.ws_row || ws.ws_col != ows.ws_col)
251 if(ioctl(rcfd[0], TIOCSWINSZ, &ws) < 0)
252 fprint(2, "ioctl: %r\n");
259 setcursor(mc, &whitearrow);
263 draw(screen, screen->r, cols[BACK], nil, ZP);
269 wordclick(uint *q0, uint *q1)
271 while(*q1<t.nr && !isspace(t.r[*q1]))
273 while(*q0>0 && !isspace(t.r[*q0-1]))
278 aselect(uint *q0, uint *q1, Image *color)
281 uint oldq0, oldq1, newq0, newq1;
283 /* save old selection */
287 /* sweep out area and record it */
288 t.f->cols[HIGH] = color;
289 t.f->cols[HTEXT] = display->white;
295 if(t.m.buttons != 0){
303 /* restore old selection */
304 t.f->cols[HIGH] = cols[HIGH];
305 t.f->cols[HTEXT] = cols[HTEXT];
313 /* selected a region */
320 /* clicked inside previous selection */
321 /* the "<=" in newq0 <= oldq1 allows us to click the right edge */
322 if(oldq0 <= newq0 && newq0 <= oldq1){
334 static Rune Lnl[1] = { '\n' };
344 if(but != 1 && but != 2 && but != 4)
347 if (ptinrect(t.m.xy, scrollr)) {
349 if(t.qh<=t.org+t.f->nchars)
360 if(aselect(&q0, &q1, execcolor) >= 0){
367 paste(t.r+q0, q1-q0, 1);
368 if(t.r[q1-1] != '\n')
376 if(aselect(&q0, &q1, plumbcolor) >= 0)
389 q0 = frcharofpt(t.f, t.m.xy) + t.org;
390 if(t.m.msec-clickmsec<500 && clickq0==q0 && t.q0==t.q1 && b==1){
391 doubleclick(&t.q0, &t.q1);
393 /* t.t.i->flush(); */
396 /* stay here until something interesting happens */
400 } while(t.m.buttons==b && abs(t.m.xy.x-x)<4 && abs(t.m.xy.y-y)<4);
401 t.m.xy.x = x; /* in case we're calling frselect */
406 if(t.m.buttons == b) {
409 t.q0 = t.f->p0 + t.org;
410 t.q1 = t.f->p1 + t.org;
411 clickmsec = t.m.msec;
414 if((t.m.buttons != b) &&(b&1)){
415 enum{Cancut = 1, Canpaste = 2} state = Cancut | Canpaste;
423 }else if(t.m.buttons&4){
427 paste(t.snarf, t.nsnarf, 0);
429 state = Cancut|Canpaste;
438 Rune newline[] = { '\n', 0 };
444 menu2str[Scroll] = "noscroll";
446 menu2str[Scroll] = "scroll";
448 switch(menuhit(but, mc, &menu2, nil)){
459 paste(t.snarf, t.nsnarf, 0);
473 paste(t.snarf, t.nsnarf, 1);
474 if(t.nsnarf == 0 || t.snarf[t.nsnarf-1] != '\n')
475 paste(newline, 1, 1);
480 scrolling = !scrolling;
490 fatal("bad menu item");
501 if(r==SCROLLKEY){ /* scroll key */
502 setorigin(line2q(t.f->maxlines*2/3), 1);
503 if(t.qh<=t.org+t.f->nchars)
506 }else if(r == BACKSCROLLKEY){
507 setorigin(backnl(t.org, t.f->maxlines*2/3), 1);
520 }else if(r == PASTE){
522 paste(t.snarf, t.nsnarf, 0);
528 if(rawon && t.q0==t.nr){
534 if(r==ESC || (holdon && r==0x7F)){ /* toggle hold */
546 case 0x7F: /* DEL: send interrupt */
547 t.qh = t.q0 = t.q1 = t.nr;
549 sig = 2; /* SIGINT */
550 if(ioctl(rcfd[0], TIOCSIG, &sig) < 0)
551 fprint(2, "sending interrupt: %r\n");
553 case 0x08: /* ^H: erase character */
554 case 0x15: /* ^U: erase line */
555 case 0x17: /* ^W: erase word */
556 if (t.q0 != 0 && t.q0 != t.qh)
575 /* there is known to be at least one character to erase */
576 if(c == 0x08) /* ^H: erase character */
585 if(r == '\n'){ /* eat at most one more character */
586 if(q == t.q0) /* eat the newline */
592 if(eq && skipping) /* found one; stop skipping */
594 else if(!eq && !skipping)
613 /* look to see if there is a complete line */
614 for(i=t.qh; i<t.nr; i++){
616 if(c=='\n' || c=='\004')
636 while(n >= UTFmax && (t.qh<t.nr || t.nraw > 0)) {
638 width = runetochar(p, &t.raw[0]);
640 runemove(t.raw, t.raw+1, t.nraw);
642 width = runetochar(p, &t.r[t.qh++]);
646 if(!rawon && (c == '\n' || c == '\004'))
649 /* take out control-d when not doing a zero length write */
651 if(write(rcfd[1], buf, n) < 0)
658 conswrite(char *p, int n)
663 /* convert to runes */
666 /* handle partial runes */
667 while(i < UTFmax && n>0) {
672 if(fullrune(t.part, i)) {
674 chartorune(buf2, t.part);
679 /* there is a little extra room in a message buf */
682 while(n >= UTFmax || fullrune(p, n)) {
687 if(n < UTFmax && !fullrune(p, n))
689 i = chartorune(q, p);
695 runewrite(buf2, q-buf2);
699 assert(n+t.npart < UTFmax);
700 memcpy(t.part+t.npart, p, n);
709 runewrite(Rune *r, int n)
722 /* get rid of backspaces */
738 /* write turned into a delete */
745 runemove(t.r+q0, t.r+q1, t.nr-q1);
758 else if(q0 < t.org+t.f->nchars){
768 frdelete(t.f, p0, p1);
774 if(t.nr>HiWater && t.qh>=t.org){
789 runemove(t.r, t.r+m, t.nr);
791 t.r = runerealloc(t.r, t.nr+n);
792 runemove(t.r+t.qh+n, t.r+t.qh, t.nr-t.qh);
793 runemove(t.r+t.qh, r, n);
797 else if(t.qh <= t.f->nchars+t.org)
798 frinsert(t.f, r, r+n, t.qh-t.org);
817 if (q0 < t.org && q1 >= t.org)
823 runemove(t.r+q0, t.r+q1, t.nr-q1);
832 else if(q0 < t.org+t.f->nchars){
838 frdelete(t.f, p0, p1);
859 t.snarf = runerealloc(t.snarf, n);
860 for(i=0,p=t.snarf; i<n; p++)
861 i += chartorune(p, pp+i);
862 t.nsnarf = p-t.snarf;
866 char sbuf[SnarfSize];
877 t.snarf = runerealloc(t.snarf, n);
878 for(i=0,p=sbuf,rp=t.snarf; i<n && p < sbuf+SnarfSize-UTFmax; i++){
879 *rp++ = *(t.r+t.q0+i);
880 p += runetochar(p, t.r+t.q0+i);
882 t.nsnarf = rp-t.snarf;
888 paste(Rune *r, int n, int advance)
894 if(rawon && t.q0==t.nr){
902 if(t.nr>HiWater && t.q0>=t.org && t.q0>=t.qh){
913 runemove(t.r, t.r+m, t.nr);
917 * if this is a button2 execute then we might have been passed
918 * runes inside the buffer. must save them before realloc.
921 if(t.r <= r && r < t.r+n){
922 rbuf = runemalloc(n);
923 runemove(rbuf, r, n);
926 t.r = runerealloc(t.r, t.nr+n);
928 runemove(t.r+q0+n, t.r+q0, t.nr-q0);
929 runemove(t.r+q0, r, n);
937 else if(q0 <= t.f->nchars+t.org)
938 frinsert(t.f, r, r+n, q0-t.org);
949 if (t.f->nlines >= t.f->maxlines)
951 frinsert(t.f, t.r + t.org + t.f->nchars, t.r + t.nr, t.f->nchars);
961 if(t.org+f->p0 == t.q0 && t.org+f->p1 == t.q1)
966 frdrawsel(f, frptofchar(f, f->p0), f->p0, f->p1, 0);
979 frdrawsel(f, frptofchar(f, f->p0), f->p0, f->p1, 1);
982 if(t.qh<=t.org+t.f.nchars && t.cwqueue != 0)
983 t.cwqueue->wakeup <-= 0;
999 nl = t.f->maxlines/5;
1001 nl = 4*t.f->maxlines/5;
1003 /* avoid going in the wrong direction */
1004 if (q0>t.org && q<t.org)
1007 /* keep trying until q0 is on the screen */
1008 while(!cansee(q0)) {
1009 assert(q0 >= t.org);
1011 q = line2q(t.f->maxlines-nl);
1022 qe = t.org+t.f->nchars;
1024 if(q0>=t.org && q0 < qe)
1028 if (t.f->nlines < t.f->maxlines)
1030 if (q0 > 0 && t.r[t.nr-1] == '\n')
1037 setorigin(uint org, int exact)
1042 if(org>0 && !exact){
1043 /* try and start after a newline */
1044 /* don't try harder than 256 chars */
1045 for(i=0; i<256 && org<t.nr; i++){
1046 if(t.r[org-1] == '\n')
1053 if(a>=0 && a<t.f->nchars)
1054 frdelete(t.f, 0, a);
1055 else if(a<0 && -a<100*t.f->maxlines){
1057 frinsert(t.f, t.r+org, t.r+org+n, 0);
1059 frdelete(t.f, 0, t.f->nchars);
1072 return frcharofpt(f, Pt(f->r.min.x, f->r.min.y + n*font->height))+t.org;
1076 backnl(uint p, uint n)
1081 /* at 256 chars, call it a line anyway */
1082 for(j=256; --j>0 && p>0; p--)
1085 if (p == 0 || i == 0)
1089 return 0; /* alef bug */
1093 addraw(Rune *r, int nr)
1095 t.raw = runerealloc(t.raw, t.nraw+nr);
1096 runemove(t.raw+t.nraw, r, nr);
1099 if(t.crqueue != nil)
1100 t.crqueue->wakeup <-= 0;
1105 Rune left1[] = { '{', '[', '(', '<', 0xab, 0 };
1106 Rune right1[] = { '}', ']', ')', '>', 0xbb, 0 };
1107 Rune left2[] = { '\n', 0 };
1108 Rune left3[] = { '\'', '"', '`', 0 };
1125 doubleclick(uint *q0, uint *q1)
1131 for(i=0; left[i]!=0; i++){
1135 /* try matching character to left, looking right */
1142 if(clickmatch(c, r[p-l], 1, &q))
1146 /* try matching character to right, looking left */
1153 if(clickmatch(c, l[p-r], -1, &q)){
1154 *q1 = *q0+(*q0<t.nr && c=='\n');
1156 if(c!='\n' || q!=0 || t.r[0]=='\n')
1162 /* try filling out word to right */
1163 while(*q1<t.nr && isalnum(t.r[*q1]))
1165 /* try filling out word to left */
1166 while(*q0>0 && isalnum(t.r[*q0-1]))
1171 clickmatch(int cl, int cr, int dir, uint *q)
1195 return cl=='\n' && nest==1;
1199 rcstart(int fd[2], int argc, char **argv)
1209 argv[0] = getenv("SHELL");
1216 * fd0 is slave (tty), fd1 is master (pty)
1219 if(getpts(fd, slave) < 0)
1220 fprint(2, "getpts: %r\n");
1222 switch(pid = fork()) {
1224 putenv("TERM", "9term");
1227 // tcsetpgrp(0, pid);
1228 sfd = open(slave, ORDWR);
1230 fprint(2, "open %s: %r\n", slave);
1231 if(ioctl(sfd, TIOCSCTTY, 0) < 0)
1232 fprint(2, "ioctl TIOCSCTTY: %r\n");
1233 // ioctl(sfd, I_PUSH, "ptem");
1234 // ioctl(sfd, I_PUSH, "ldterm");
1238 system("stty tabs -onlcr -echo");
1239 execvp(argv[0], argv);
1240 fprint(2, "exec %s failed: %r\n", argv[0]);
1244 fatal("proc failed: %r");
1260 assert(t.q0 <= t.q1 && t.q1 <= t.nr);
1261 assert(t.org <= t.nr && t.qh <= t.nr);
1262 assert(f->p0 <= f->p1 && f->p1 <= f->nchars);
1263 assert(t.org + f->nchars <= t.nr);
1264 assert(t.org+f->nchars==t.nr || (f->nlines >= f->maxlines));
1268 strrune(Rune *s, Rune c)
1287 Rectangle r, r1, r2;
1291 r.min.x += 1; /* border between margin and bar */
1293 if(scrx==0 || scrx->r.max.y < r.max.y){
1296 scrx = allocimage(display, Rect(0, 0, 32, r.max.y), screen->chan, 1, DPaleyellow);
1298 fatal("scroll balloc");
1302 r2 = scrpos(r1, t.org, t.org+t.f->nchars, t.nr);
1303 if(!eqrect(r2, lastsr)){
1305 draw(scrx, r1, cols[BORD], nil, ZP);
1306 draw(scrx, r2, cols[BACK], nil, r2.min);
1308 // r2.min.x = r2.max.x-1;
1309 // draw(scrx, r2, cols[BORD], nil, ZP);
1310 draw(screen, r, scrx, nil, r1.min);
1315 scrpos(Rectangle r, ulong p0, ulong p1, ulong tot)
1320 q = insetrect(r, 1);
1321 h = q.max.y-q.min.y;
1324 if(tot > 1024L*1024L)
1325 tot >>= 10, p0 >>= 10, p1 >>= 10;
1327 q.min.y += h*p0/tot;
1329 q.max.y -= h*(tot-p1)/tot;
1330 if(q.max.y < q.min.y+2){
1331 if(q.min.y+2 <= r.max.y)
1332 q.max.y = q.min.y+2;
1334 q.min.y = q.max.y-2;
1344 int x, y, my, h, first, exact;
1346 s = insetrect(scrollr, 1);
1347 h = s.max.y-s.min.y;
1348 x = (s.min.x+s.max.x)/2;
1352 if(t.m.xy.x<s.min.x || s.max.x<=t.m.xy.x){
1361 // if(!eqpt(t.m.xy, Pt(x, my)))
1362 // cursorset(Pt(x, my));
1368 if(t.nr > 1024*1024)
1369 p0 = ((t.nr>>10)*(y-s.min.y)/h)<<10;
1371 p0 = t.nr*(y-s.min.y)/h;
1374 p0 = backnl(t.org, (my-s.min.y)/font->height);
1376 p0 = t.org+frcharofpt(t.f, Pt(s.max.x, my));
1379 setorigin(p0, exact);
1385 }while(t.m.buttons & (1<<(but-1)));
1391 if((plumbfd = plumbopen("send", OWRITE)) < 0)
1396 plumb(uint q0, uint q1)
1404 if(getchildwd(rcpid, childwdir, sizeof childwdir) == 0)
1408 pm = malloc(sizeof(Plumbmsg));
1409 pm->src = strdup("9term");
1411 pm->wdir = strdup(w);
1412 pm->type = strdup("text");
1417 wordclick(&q0, &q1);
1418 sprint(cbuf, "click=%d", p0-q0);
1419 pm->attr = plumbunpackattr(cbuf);
1425 pm->data = malloc(SnarfSize);
1427 for(i=0,p=pm->data; i<n && p < pm->data + SnarfSize-UTFmax; i++)
1428 p += runetochar(p, t.r+q0+i);
1430 pm->ndata = strlen(pm->data);
1431 plumbsend(plumbfd, pm);
1436 * Process in-band messages about window title changes.
1437 * The messages are of the form:
1441 * where xxx is the new directory. This format was chosen
1442 * because it changes the label on xterm windows.
1445 label(Rune *sr, int n)
1447 Rune *sl, *el, *er, *r;
1450 for(r=er-1; r>=sr; r--)
1457 if(el-sr > sizeof wdir)
1458 sr = el - sizeof wdir;
1459 for(sl=el-3; sl>=sr; sl--)
1460 if(sl[0]=='\033' && sl[1]==']' && sl[2]==';')
1465 snprint(wdir, sizeof wdir, "%.*S", (el-1)-(sl+3), sl+3);
1466 drawsetlabel(display, wdir);
1468 runemove(sl, el, er-el);