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