1 #include <u.h>
2 #include <libc.h>
3 #include <draw.h>
4 #include <thread.h>
5 #include <cursor.h>
6 #include <mouse.h>
7 #include <keyboard.h>
8 #include <frame.h>
9 #include <fcall.h>
10 #include <regexp.h>
11 #include <plumb.h>
12 #include "dat.h"
13 #include "fns.h"
14
15 Window* openfile(Text*, Expand*);
16
17 int nuntitled;
18
19 void
look3(Text * t,uint q0,uint q1,int external)20 look3(Text *t, uint q0, uint q1, int external)
21 {
22 int n, c, f, expanded;
23 Text *ct;
24 Expand e;
25 Rune *r;
26 uint p;
27 Plumbmsg *m;
28 Runestr dir;
29 char buf[32];
30
31 ct = seltext;
32 if(ct == nil)
33 seltext = t;
34 expanded = expand(t, q0, q1, &e);
35 if(!external && t->w!=nil && t->w->nopen[QWevent]>0){
36 /* send alphanumeric expansion to external client */
37 if(expanded == FALSE)
38 return;
39 f = 0;
40 if((e.at!=nil && t->w!=nil) || (e.nname>0 && lookfile(e.name, e.nname)!=nil))
41 f = 1; /* acme can do it without loading a file */
42 if(q0!=e.q0 || q1!=e.q1)
43 f |= 2; /* second (post-expand) message follows */
44 if(e.nname)
45 f |= 4; /* it's a file name */
46 c = 'l';
47 if(t->what == Body)
48 c = 'L';
49 n = q1-q0;
50 if(n <= EVENTSIZE){
51 r = runemalloc(n);
52 bufread(t->file, q0, r, n);
53 winevent(t->w, "%c%d %d %d %d %.*S\n", c, q0, q1, f, n, n, r);
54 free(r);
55 }else
56 winevent(t->w, "%c%d %d %d 0 \n", c, q0, q1, f, n);
57 if(q0==e.q0 && q1==e.q1)
58 return;
59 if(e.nname){
60 n = e.nname;
61 if(e.a1 > e.a0)
62 n += 1+(e.a1-e.a0);
63 r = runemalloc(n);
64 runemove(r, e.name, e.nname);
65 if(e.a1 > e.a0){
66 r[e.nname] = ':';
67 bufread(e.at->file, e.a0, r+e.nname+1, e.a1-e.a0);
68 }
69 }else{
70 n = e.q1 - e.q0;
71 r = runemalloc(n);
72 bufread(t->file, e.q0, r, n);
73 }
74 f &= ~2;
75 if(n <= EVENTSIZE)
76 winevent(t->w, "%c%d %d %d %d %.*S\n", c, e.q0, e.q1, f, n, n, r);
77 else
78 winevent(t->w, "%c%d %d %d 0 \n", c, e.q0, e.q1, f, n);
79 free(r);
80 goto Return;
81 }
82 if(plumbsendfd >= 0){
83 /* send whitespace-delimited word to plumber */
84 m = emalloc(sizeof(Plumbmsg));
85 m->src = estrdup("acme");
86 m->dst = nil;
87 dir = dirname(t, nil, 0);
88 if(dir.nr==1 && dir.r[0]=='.'){ /* sigh */
89 free(dir.r);
90 dir.r = nil;
91 dir.nr = 0;
92 }
93 if(dir.nr == 0)
94 m->wdir = estrdup(wdir);
95 else
96 m->wdir = runetobyte(dir.r, dir.nr);
97 free(dir.r);
98 m->type = estrdup("text");
99 m->attr = nil;
100 buf[0] = '\0';
101 if(q1 == q0){
102 if(t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){
103 q0 = t->q0;
104 q1 = t->q1;
105 }else{
106 p = q0;
107 while(q0>0 && (c=tgetc(t, q0-1))!=' ' && c!='\t' && c!='\n')
108 q0--;
109 while(q1<t->file->nc && (c=tgetc(t, q1))!=' ' && c!='\t' && c!='\n')
110 q1++;
111 if(q1 == q0){
112 plumbfree(m);
113 goto Return;
114 }
115 sprint(buf, "click=%d", p-q0);
116 m->attr = plumbunpackattr(buf);
117 }
118 }
119 r = runemalloc(q1-q0);
120 bufread(t->file, q0, r, q1-q0);
121 m->data = runetobyte(r, q1-q0);
122 m->ndata = strlen(m->data);
123 free(r);
124 if(m->ndata<messagesize-1024 && plumbsend(plumbsendfd, m) >= 0){
125 plumbfree(m);
126 goto Return;
127 }
128 plumbfree(m);
129 /* plumber failed to match; fall through */
130 }
131
132 /* interpret alphanumeric string ourselves */
133 if(expanded == FALSE)
134 return;
135 if(e.name || e.at)
136 openfile(t, &e);
137 else{
138 if(t->w == nil)
139 return;
140 ct = &t->w->body;
141 if(t->w != ct->w)
142 winlock(ct->w, 'M');
143 if(t == ct)
144 textsetselect(ct, e.q1, e.q1);
145 n = e.q1 - e.q0;
146 r = runemalloc(n);
147 bufread(t->file, e.q0, r, n);
148 if(search(ct, r, n) && e.jump)
149 moveto(mousectl, addpt(frptofchar(ct, ct->p0), Pt(4, ct->font->height-4)));
150 if(t->w != ct->w)
151 winunlock(ct->w);
152 free(r);
153 }
154
155 Return:
156 free(e.name);
157 free(e.bname);
158 }
159
160 int
plumbgetc(void * a,uint n)161 plumbgetc(void *a, uint n)
162 {
163 Rune *r;
164
165 r = a;
166 if(n>runestrlen(r))
167 return 0;
168 return r[n];
169 }
170
171 void
plumblook(Plumbmsg * m)172 plumblook(Plumbmsg *m)
173 {
174 Expand e;
175 char *addr;
176
177 if(m->ndata >= BUFSIZE){
178 warning(nil, "insanely long file name (%d bytes) in plumb message (%.32s...)\n", m->ndata, m->data);
179 return;
180 }
181 e.q0 = 0;
182 e.q1 = 0;
183 if(m->data[0] == '\0')
184 return;
185 e.ar = nil;
186 e.bname = m->data;
187 e.name = bytetorune(e.bname, &e.nname);
188 e.jump = TRUE;
189 e.a0 = 0;
190 e.a1 = 0;
191 addr = plumblookup(m->attr, "addr");
192 if(addr != nil){
193 e.ar = bytetorune(addr, &e.a1);
194 e.agetc = plumbgetc;
195 }
196 openfile(nil, &e);
197 free(e.name);
198 free(e.at);
199 }
200
201 void
plumbshow(Plumbmsg * m)202 plumbshow(Plumbmsg *m)
203 {
204 Window *w;
205 Rune rb[256], *r;
206 int nb, nr;
207 Runestr rs;
208 char *name, *p, namebuf[16];
209
210 w = makenewwindow(nil);
211 name = plumblookup(m->attr, "filename");
212 if(name == nil){
213 name = namebuf;
214 nuntitled++;
215 snprint(namebuf, sizeof namebuf, "Untitled-%d", nuntitled);
216 }
217 p = nil;
218 if(name[0]!='/' && m->wdir!=nil && m->wdir[0]!='\0'){
219 nb = strlen(m->wdir) + 1 + strlen(name) + 1;
220 p = emalloc(nb);
221 snprint(p, nb, "%s/%s", m->wdir, name);
222 name = p;
223 }
224 cvttorunes(name, strlen(name), rb, &nb, &nr, nil);
225 free(p);
226 rs = cleanrname((Runestr){rb, nr});
227 winsetname(w, rs.r, rs.nr);
228 r = runemalloc(m->ndata);
229 cvttorunes(m->data, m->ndata, r, &nb, &nr, nil);
230 textinsert(&w->body, 0, r, nr, TRUE);
231 free(r);
232 w->body.file->mod = FALSE;
233 w->dirty = FALSE;
234 winsettag(w);
235 textscrdraw(&w->body);
236 textsetselect(&w->tag, w->tag.file->nc, w->tag.file->nc);
237 }
238
239 int
search(Text * ct,Rune * r,uint n)240 search(Text *ct, Rune *r, uint n)
241 {
242 uint q, nb, maxn;
243 int around;
244 Rune *s, *b, *c;
245
246 if(n==0 || n>ct->file->nc)
247 return FALSE;
248 if(2*n > RBUFSIZE){
249 warning(nil, "string too long\n");
250 return FALSE;
251 }
252 maxn = max(2*n, RBUFSIZE);
253 s = fbufalloc();
254 b = s;
255 nb = 0;
256 b[nb] = 0;
257 around = 0;
258 q = ct->q1;
259 for(;;){
260 if(q >= ct->file->nc){
261 q = 0;
262 around = 1;
263 nb = 0;
264 b[nb] = 0;
265 }
266 if(nb > 0){
267 c = runestrchr(b, r[0]);
268 if(c == nil){
269 q += nb;
270 nb = 0;
271 b[nb] = 0;
272 if(around && q>=ct->q1)
273 break;
274 continue;
275 }
276 q += (c-b);
277 nb -= (c-b);
278 b = c;
279 }
280 /* reload if buffer covers neither string nor rest of file */
281 if(nb<n && nb!=ct->file->nc-q){
282 nb = ct->file->nc-q;
283 if(nb >= maxn)
284 nb = maxn-1;
285 bufread(ct->file, q, s, nb);
286 b = s;
287 b[nb] = '\0';
288 }
289 /* this runeeq is fishy but the null at b[nb] makes it safe */
290 if(runeeq(b, n, r, n)==TRUE){
291 if(ct->w){
292 textshow(ct, q, q+n, 1);
293 winsettag(ct->w);
294 }else{
295 ct->q0 = q;
296 ct->q1 = q+n;
297 }
298 seltext = ct;
299 fbuffree(s);
300 return TRUE;
301 }
302 --nb;
303 b++;
304 q++;
305 if(around && q>=ct->q1)
306 break;
307 }
308 fbuffree(s);
309 return FALSE;
310 }
311
312 int
isfilec(Rune r)313 isfilec(Rune r)
314 {
315 if(isalnum(r))
316 return TRUE;
317 if(runestrchr(L".-+/:", r))
318 return TRUE;
319 return FALSE;
320 }
321
322 /* Runestr wrapper for cleanname */
323 Runestr
cleanrname(Runestr rs)324 cleanrname(Runestr rs)
325 {
326 char *s;
327 int nb, nulls;
328
329 s = runetobyte(rs.r, rs.nr);
330 cleanname(s);
331 cvttorunes(s, strlen(s), rs.r, &nb, &rs.nr, &nulls);
332 free(s);
333 return rs;
334 }
335
336 Runestr
includefile(Rune * dir,Rune * file,int nfile)337 includefile(Rune *dir, Rune *file, int nfile)
338 {
339 int m, n;
340 char *a;
341 Rune *r;
342
343 m = runestrlen(dir);
344 a = emalloc((m+1+nfile)*UTFmax+1);
345 sprint(a, "%S/%.*S", dir, nfile, file);
346 n = access(a, 0);
347 free(a);
348 if(n < 0)
349 return (Runestr){nil, 0};
350 r = runemalloc(m+1+nfile);
351 runemove(r, dir, m);
352 runemove(r+m, L"/", 1);
353 runemove(r+m+1, file, nfile);
354 free(file);
355 return cleanrname((Runestr){r, m+1+nfile});
356 }
357
358 static Rune *objdir;
359
360 Runestr
includename(Text * t,Rune * r,int n)361 includename(Text *t, Rune *r, int n)
362 {
363 Window *w;
364 char buf[128];
365 Runestr file;
366 int i;
367
368 if(objdir==nil && objtype!=nil){
369 sprint(buf, "/%s/include", objtype);
370 objdir = bytetorune(buf, &i);
371 objdir = runerealloc(objdir, i+1);
372 objdir[i] = '\0';
373 }
374
375 w = t->w;
376 if(n==0 || r[0]=='/' || w==nil)
377 goto Rescue;
378 if(n>2 && r[0]=='.' && r[1]=='/')
379 goto Rescue;
380 file.r = nil;
381 file.nr = 0;
382 for(i=0; i<w->nincl && file.r==nil; i++)
383 file = includefile(w->incl[i], r, n);
384
385 if(file.r == nil)
386 file = includefile(L"/sys/include", r, n);
387 if(file.r==nil && objdir!=nil)
388 file = includefile(objdir, r, n);
389 if(file.r == nil)
390 goto Rescue;
391 return file;
392
393 Rescue:
394 return (Runestr){r, n};
395 }
396
397 Runestr
dirname(Text * t,Rune * r,int n)398 dirname(Text *t, Rune *r, int n)
399 {
400 Rune *b, c;
401 uint m, nt;
402 int slash;
403 Runestr tmp;
404
405 b = nil;
406 if(t==nil || t->w==nil)
407 goto Rescue;
408 nt = t->w->tag.file->nc;
409 if(nt == 0)
410 goto Rescue;
411 if(n>=1 && r[0]=='/')
412 goto Rescue;
413 b = runemalloc(nt+n+1);
414 bufread(t->w->tag.file, 0, b, nt);
415 slash = -1;
416 for(m=0; m<nt; m++){
417 c = b[m];
418 if(c == '/')
419 slash = m;
420 if(c==' ' || c=='\t')
421 break;
422 }
423 if(slash < 0)
424 goto Rescue;
425 runemove(b+slash+1, r, n);
426 free(r);
427 return cleanrname((Runestr){b, slash+1+n});
428
429 Rescue:
430 free(b);
431 tmp = (Runestr){r, n};
432 if(r)
433 return cleanrname(tmp);
434 return tmp;
435 }
436
437 int
expandfile(Text * t,uint q0,uint q1,Expand * e)438 expandfile(Text *t, uint q0, uint q1, Expand *e)
439 {
440 int i, n, nname, colon, eval;
441 uint amin, amax;
442 Rune *r, c;
443 Window *w;
444 Runestr rs;
445
446 amax = q1;
447 if(q1 == q0){
448 colon = -1;
449 while(q1<t->file->nc && isfilec(c=textreadc(t, q1))){
450 if(c == ':'){
451 colon = q1;
452 break;
453 }
454 q1++;
455 }
456 while(q0>0 && (isfilec(c=textreadc(t, q0-1)) || isaddrc(c) || isregexc(c))){
457 q0--;
458 if(colon<0 && c==':')
459 colon = q0;
460 }
461 /*
462 * if it looks like it might begin file: , consume address chars after :
463 * otherwise terminate expansion at :
464 */
465 if(colon >= 0){
466 q1 = colon;
467 if(colon<t->file->nc-1 && isaddrc(textreadc(t, colon+1))){
468 q1 = colon+1;
469 while(q1<t->file->nc && isaddrc(textreadc(t, q1)))
470 q1++;
471 }
472 }
473 if(q1 > q0)
474 if(colon >= 0){ /* stop at white space */
475 for(amax=colon+1; amax<t->file->nc; amax++)
476 if((c=textreadc(t, amax))==' ' || c=='\t' || c=='\n')
477 break;
478 }else
479 amax = t->file->nc;
480 }
481 amin = amax;
482 e->q0 = q0;
483 e->q1 = q1;
484 n = q1-q0;
485 if(n == 0)
486 return FALSE;
487 /* see if it's a file name */
488 r = runemalloc(n);
489 bufread(t->file, q0, r, n);
490 /* first, does it have bad chars? */
491 nname = -1;
492 for(i=0; i<n; i++){
493 c = r[i];
494 if(c==':' && nname<0){
495 if(q0+i+1<t->file->nc && (i==n-1 || isaddrc(textreadc(t, q0+i+1))))
496 amin = q0+i;
497 else
498 goto Isntfile;
499 nname = i;
500 }
501 }
502 if(nname == -1)
503 nname = n;
504 for(i=0; i<nname; i++)
505 if(!isfilec(r[i]))
506 goto Isntfile;
507 /*
508 * See if it's a file name in <>, and turn that into an include
509 * file name if so. Should probably do it for "" too, but that's not
510 * restrictive enough syntax and checking for a #include earlier on the
511 * line would be silly.
512 */
513 if(q0>0 && textreadc(t, q0-1)=='<' && q1<t->file->nc && textreadc(t, q1)=='>'){
514 rs = includename(t, r, nname);
515 r = rs.r;
516 nname = rs.nr;
517 }
518 else if(amin == q0)
519 goto Isfile;
520 else{
521 rs = dirname(t, r, nname);
522 r = rs.r;
523 nname = rs.nr;
524 }
525 e->bname = runetobyte(r, nname);
526 /* if it's already a window name, it's a file */
527 w = lookfile(r, nname);
528 if(w != nil)
529 goto Isfile;
530 /* if it's the name of a file, it's a file */
531 if(access(e->bname, 0) < 0){
532 free(e->bname);
533 e->bname = nil;
534 goto Isntfile;
535 }
536
537 Isfile:
538 e->name = r;
539 e->nname = nname;
540 e->at = t;
541 e->a0 = amin+1;
542 eval = FALSE;
543 address(nil, nil, (Range){-1,-1}, (Range){0, 0}, t, e->a0, amax, tgetc, &eval, (uint*)&e->a1);
544 return TRUE;
545
546 Isntfile:
547 free(r);
548 return FALSE;
549 }
550
551 int
expand(Text * t,uint q0,uint q1,Expand * e)552 expand(Text *t, uint q0, uint q1, Expand *e)
553 {
554 memset(e, 0, sizeof *e);
555 e->agetc = tgetc;
556 /* if in selection, choose selection */
557 e->jump = TRUE;
558 if(q1==q0 && t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){
559 q0 = t->q0;
560 q1 = t->q1;
561 if(t->what == Tag)
562 e->jump = FALSE;
563 }
564
565 if(expandfile(t, q0, q1, e))
566 return TRUE;
567
568 if(q0 == q1){
569 while(q1<t->file->nc && isalnum(textreadc(t, q1)))
570 q1++;
571 while(q0>0 && isalnum(textreadc(t, q0-1)))
572 q0--;
573 }
574 e->q0 = q0;
575 e->q1 = q1;
576 return q1 > q0;
577 }
578
579 Window*
lookfile(Rune * s,int n)580 lookfile(Rune *s, int n)
581 {
582 int i, j, k;
583 Window *w;
584 Column *c;
585 Text *t;
586
587 /* avoid terminal slash on directories */
588 if(n>1 && s[n-1] == '/')
589 --n;
590 for(j=0; j<row.ncol; j++){
591 c = row.col[j];
592 for(i=0; i<c->nw; i++){
593 w = c->w[i];
594 t = &w->body;
595 k = t->file->nname;
596 if(k>1 && t->file->name[k-1] == '/')
597 k--;
598 if(runeeq(t->file->name, k, s, n)){
599 w = w->body.file->curtext->w;
600 if(w->col != nil) /* protect against race deleting w */
601 return w;
602 }
603 }
604 }
605 return nil;
606 }
607
608 Window*
lookid(int id,int dump)609 lookid(int id, int dump)
610 {
611 int i, j;
612 Window *w;
613 Column *c;
614
615 for(j=0; j<row.ncol; j++){
616 c = row.col[j];
617 for(i=0; i<c->nw; i++){
618 w = c->w[i];
619 if(dump && w->dumpid == id)
620 return w;
621 if(!dump && w->id == id)
622 return w;
623 }
624 }
625 return nil;
626 }
627
628
629 Window*
openfile(Text * t,Expand * e)630 openfile(Text *t, Expand *e)
631 {
632 Range r;
633 Window *w, *ow;
634 int eval, i, n;
635 Rune *rp;
636 uint dummy;
637
638 if(e->nname == 0){
639 w = t->w;
640 if(w == nil)
641 return nil;
642 }else
643 w = lookfile(e->name, e->nname);
644 if(w){
645 t = &w->body;
646 if(!t->col->safe && t->maxlines==0) /* window is obscured by full-column window */
647 colgrow(t->col, t->col->w[0], 1);
648 }else{
649 ow = nil;
650 if(t)
651 ow = t->w;
652 w = makenewwindow(t);
653 t = &w->body;
654 winsetname(w, e->name, e->nname);
655 textload(t, 0, e->bname, 1);
656 t->file->mod = FALSE;
657 t->w->dirty = FALSE;
658 winsettag(t->w);
659 textsetselect(&t->w->tag, t->w->tag.file->nc, t->w->tag.file->nc);
660 if(ow != nil){
661 for(i=ow->nincl; --i>=0; ){
662 n = runestrlen(ow->incl[i]);
663 rp = runemalloc(n);
664 runemove(rp, ow->incl[i], n);
665 winaddincl(w, rp, n);
666 }
667 w->autoindent = ow->autoindent;
668 }else
669 w->autoindent = globalautoindent;
670 }
671 if(e->a1 == e->a0)
672 eval = FALSE;
673 else{
674 eval = TRUE;
675 r = address(nil, t, (Range){-1, -1}, (Range){t->q0, t->q1}, e->at, e->a0, e->a1, e->agetc, &eval, &dummy);
676 if(eval == FALSE)
677 e->jump = FALSE; /* don't jump if invalid address */
678 }
679 if(eval == FALSE){
680 r.q0 = t->q0;
681 r.q1 = t->q1;
682 }
683 textshow(t, r.q0, r.q1, 1);
684 winsettag(t->w);
685 seltext = t;
686 if(e->jump)
687 moveto(mousectl, addpt(frptofchar(t, t->p0), Pt(4, font->height-4)));
688 return w;
689 }
690
691 void
new(Text * et,Text * t,Text * argt,int flag1,int flag2,Rune * arg,int narg)692 new(Text *et, Text *t, Text *argt, int flag1, int flag2, Rune *arg, int narg)
693 {
694 int ndone;
695 Rune *a, *f;
696 int na, nf;
697 Expand e;
698 Runestr rs;
699
700 getarg(argt, FALSE, TRUE, &a, &na);
701 if(a){
702 new(et, t, nil, flag1, flag2, a, na);
703 if(narg == 0)
704 return;
705 }
706 /* loop condition: *arg is not a blank */
707 for(ndone=0; ; ndone++){
708 a = findbl(arg, narg, &na);
709 if(a == arg){
710 if(ndone==0 && et->col!=nil)
711 winsettag(coladd(et->col, nil, nil, -1));
712 break;
713 }
714 nf = narg-na;
715 f = runemalloc(nf);
716 runemove(f, arg, nf);
717 rs = dirname(et, f, nf);
718 f = rs.r;
719 nf = rs.nr;
720 memset(&e, 0, sizeof e);
721 e.name = f;
722 e.nname = nf;
723 e.bname = runetobyte(f, nf);
724 e.jump = TRUE;
725 openfile(et, &e);
726 free(f);
727 free(e.bname);
728 arg = skipbl(a, na, &narg);
729 }
730 }
731