master
util.c
ohmu
·
2026-09-13
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 <libsec.h>
12#include "dat.h"
13#include "fns.h"
14#include "parser.h"
15
16static Point prevmouse;
17static Window *mousew;
18
19Range
20range(int q0, int q1)
21{
22 Range r;
23
24 r.q0 = q0;
25 r.q1 = q1;
26 return r;
27}
28
29Runestr
30runestr(Rune *r, uint n)
31{
32 Runestr rs;
33
34 rs.r = r;
35 rs.nr = n;
36 return rs;
37}
38
39void
40cvttorunes(char *p, int n, Rune *r, int *nb, int *nr, int *nulls)
41{
42 uchar *q;
43 Rune *s;
44 int j, w;
45
46 /*
47 * Always guaranteed that n bytes may be interpreted
48 * without worrying about partial runes. This may mean
49 * reading up to UTFmax-1 more bytes than n; the caller
50 * knows this. If n is a firm limit, the caller should
51 * set p[n] = 0.
52 */
53 q = (uchar*)p;
54 s = r;
55 for(j=0; j<n; j+=w){
56 if(*q < Runeself){
57 w = 1;
58 *s = *q++;
59 }else{
60 w = chartorune(s, (char*)q);
61 q += w;
62 }
63 if(*s)
64 s++;
65 else if(nulls)
66 *nulls = TRUE;
67 }
68 *nb = (char*)q-p;
69 *nr = s-r;
70}
71
72void
73error(char *s)
74{
75 fprint(2, "acme: %s: %r\n", s);
76 threadexitsall(nil);
77}
78
79Window*
80errorwin1(Rune *dir, int ndir, Rune **incl, int nincl)
81{
82 Window *w;
83 Rune *r;
84 int i, n;
85 static Rune Lpluserrors[] = { '+', 'E', 'r', 'r', 'o', 'r', 's', 0 };
86
87 r = runemalloc(ndir+8);
88 if((n = ndir) != 0){
89 runemove(r, dir, ndir);
90 r[n++] = L'/';
91 }
92 runemove(r+n, Lpluserrors, 7);
93 n += 7;
94 w = lookfile(r, n);
95 if(w == nil){
96 if(row.ncol == 0)
97 if(rowadd(&row, nil, -1) == nil)
98 error("can't create column to make error window");
99 w = coladd(row.col[row.ncol-1], nil, nil, -1);
100 w->filemenu = FALSE;
101 winsetname(w, r, n);
102 xfidlog(w, "new");
103 }
104 free(r);
105 for(i=nincl; --i>=0; ){
106 n = runestrlen(incl[i]);
107 r = runemalloc(n);
108 runemove(r, incl[i], n);
109 winaddincl(w, r, n);
110 }
111 w->autoindent = globalautoindent;
112 return w;
113}
114
115/* make new window, if necessary; return with it locked */
116Window*
117errorwin(Mntdir *md, int owner)
118{
119 Window *w;
120
121 for(;;){
122 if(md == nil)
123 w = errorwin1(nil, 0, nil, 0);
124 else
125 w = errorwin1(md->dir, md->ndir, md->incl, md->nincl);
126 winlock(w, owner);
127 if(w->col != nil)
128 break;
129 /* window was deleted too fast */
130 winunlock(w);
131 }
132 return w;
133}
134
135/*
136 * Incoming window should be locked.
137 * It will be unlocked and returned window
138 * will be locked in its place.
139 */
140Window*
141errorwinforwin(Window *w)
142{
143 int i, n, nincl, owner;
144 Rune **incl;
145 Runestr dir;
146 Text *t;
147
148 t = &w->body;
149 dir = dirname(t, nil, 0);
150 if(dir.nr==1 && dir.r[0]=='.'){ /* sigh */
151 free(dir.r);
152 dir.r = nil;
153 dir.nr = 0;
154 }
155 incl = nil;
156 nincl = w->nincl;
157 if(nincl > 0){
158 incl = emalloc(nincl*sizeof(Rune*));
159 for(i=0; i<nincl; i++){
160 n = runestrlen(w->incl[i]);
161 incl[i] = runemalloc(n+1);
162 runemove(incl[i], w->incl[i], n);
163 }
164 }
165 owner = w->owner;
166 winunlock(w);
167 for(;;){
168 w = errorwin1(dir.r, dir.nr, incl, nincl);
169 winlock(w, owner);
170 if(w->col != nil)
171 break;
172 /* window deleted too fast */
173 winunlock(w);
174 }
175 return w;
176}
177
178typedef struct Warning Warning;
179
180struct Warning{
181 Mntdir *md;
182 Buffer buf;
183 Warning *next;
184};
185
186static Warning *warnings;
187
188static
189void
190addwarningtext(Mntdir *md, Rune *r, int nr)
191{
192 Warning *warn;
193
194 for(warn = warnings; warn; warn=warn->next){
195 if(warn->md == md){
196 bufinsert(&warn->buf, warn->buf.nc, r, nr);
197 return;
198 }
199 }
200 warn = emalloc(sizeof(Warning));
201 warn->next = warnings;
202 warn->md = md;
203 if(md)
204 fsysincid(md);
205 warnings = warn;
206 bufinsert(&warn->buf, 0, r, nr);
207 nbsendp(cwarn, 0);
208}
209
210/* called while row is locked */
211void
212flushwarnings(void)
213{
214 Warning *warn, *next;
215 Window *w;
216 Text *t;
217 int owner, nr, q0, n;
218 Rune *r;
219
220 for(warn=warnings; warn; warn=next) {
221 w = errorwin(warn->md, 'E');
222 t = &w->body;
223 owner = w->owner;
224 if(owner == 0)
225 w->owner = 'E';
226 wincommit(w, t);
227 /*
228 * Most commands don't generate much output. For instance,
229 * Edit ,>cat goes through /dev/cons and is already in blocks
230 * because of the i/o system, but a few can. Edit ,p will
231 * put the entire result into a single hunk. So it's worth doing
232 * this in blocks (and putting the text in a buffer in the first
233 * place), to avoid a big memory footprint.
234 */
235 r = fbufalloc();
236 q0 = t->file->b.nc;
237 for(n = 0; n < warn->buf.nc; n += nr){
238 nr = warn->buf.nc - n;
239 if(nr > RBUFSIZE)
240 nr = RBUFSIZE;
241 bufread(&warn->buf, n, r, nr);
242 textbsinsert(t, t->file->b.nc, r, nr, TRUE, &nr);
243 }
244 textshow(t, q0, t->file->b.nc, 1);
245 free(r);
246 winsettag(t->w);
247 textscrdraw(t);
248 w->owner = owner;
249 w->dirty = FALSE;
250 winunlock(w);
251 bufclose(&warn->buf);
252 next = warn->next;
253 if(warn->md)
254 fsysdelid(warn->md);
255 free(warn);
256 }
257 warnings = nil;
258}
259
260void
261warning(Mntdir *md, char *s, ...)
262{
263 Rune *r;
264 va_list arg;
265
266 va_start(arg, s);
267 r = runevsmprint(s, arg);
268 va_end(arg);
269 if(r == nil)
270 error("runevsmprint failed");
271 addwarningtext(md, r, runestrlen(r));
272 free(r);
273}
274
275int
276runeeq(Rune *s1, uint n1, Rune *s2, uint n2)
277{
278 if(n1 != n2)
279 return FALSE;
280 return memcmp(s1, s2, n1*sizeof(Rune)) == 0;
281}
282
283uint
284min(uint a, uint b)
285{
286 if(a < b)
287 return a;
288 return b;
289}
290
291uint
292max(uint a, uint b)
293{
294 if(a > b)
295 return a;
296 return b;
297}
298
299char*
300runetobyte(Rune *r, int n)
301{
302 char *s;
303
304 if(r == nil)
305 return nil;
306 s = emalloc(n*UTFmax+1);
307 setmalloctag(s, getcallerpc(&r));
308 snprint(s, n*UTFmax+1, "%.*S", n, r);
309 return s;
310}
311
312Rune*
313bytetorune(char *s, int *ip)
314{
315 Rune *r;
316 int nb, nr;
317
318 nb = strlen(s);
319 r = runemalloc(nb+1);
320 cvttorunes(s, nb, r, &nb, &nr, nil);
321 r[nr] = '\0';
322 *ip = nr;
323 return r;
324}
325
326int
327isalnum(Rune c)
328{
329 /*
330 * Hard to get absolutely right. Use what we know about ASCII
331 * and assume anything above the Latin control characters is
332 * potentially an alphanumeric.
333 */
334 if(c <= ' ')
335 return FALSE;
336 if(0x7F<=c && c<=0xA0)
337 return FALSE;
338 if(utfrune("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", c))
339 return FALSE;
340 return TRUE;
341}
342
343int
344rgetc(void *v, uint n)
345{
346 return ((Rune*)v)[n];
347}
348
349int
350tgetc(void *a, uint n)
351{
352 Text *t;
353
354 t = a;
355 if(n >= t->file->b.nc)
356 return 0;
357 return textreadc(t, n);
358}
359
360Rune*
361skipbl(Rune *r, int n, int *np)
362{
363 while(n>0 && (*r==' ' || *r=='\t' || *r=='\n')){
364 --n;
365 r++;
366 }
367 *np = n;
368 return r;
369}
370
371Rune*
372findbl(Rune *r, int n, int *np)
373{
374 while(n>0 && *r!=' ' && *r!='\t' && *r!='\n'){
375 --n;
376 r++;
377 }
378 *np = n;
379 return r;
380}
381
382void
383savemouse(Window *w)
384{
385 prevmouse = mouse->xy;
386 mousew = w;
387}
388
389int
390restoremouse(Window *w)
391{
392 int did;
393
394 did = 0;
395 if(mousew!=nil && mousew==w) {
396 moveto(mousectl, prevmouse);
397 did = 1;
398 }
399 mousew = nil;
400 return did;
401}
402
403void
404clearmouse()
405{
406 mousew = nil;
407}
408
409char*
410estrdup(char *s)
411{
412 char *t;
413
414 t = strdup(s);
415 if(t == nil)
416 error("strdup failed");
417 setmalloctag(t, getcallerpc(&s));
418 return t;
419}
420
421void*
422emalloc(uint n)
423{
424 void *p;
425
426 p = malloc(n);
427 if(p == nil)
428 error("malloc failed");
429 setmalloctag(p, getcallerpc(&n));
430 memset(p, 0, n);
431 return p;
432}
433
434void*
435erealloc(void *p, uint n)
436{
437 p = realloc(p, n);
438 if(p == nil)
439 error("realloc failed");
440 setmalloctag(p, getcallerpc(&n));
441 return p;
442}
443
444/*
445 * Heuristic city.
446 */
447Window*
448makenewwindow(Text *t)
449{
450 Column *c;
451 Window *w, *bigw, *emptyw;
452 Text *emptyb;
453 int i, y, el;
454
455 if(activecol)
456 c = activecol;
457 else if(seltext && seltext->col)
458 c = seltext->col;
459 else if(t && t->col)
460 c = t->col;
461 else{
462 if(row.ncol==0 && rowadd(&row, nil, -1)==nil)
463 error("can't make column");
464 c = row.col[row.ncol-1];
465 }
466 activecol = c;
467 if(t==nil || t->w==nil || c->nw==0)
468 return coladd(c, nil, nil, -1);
469
470 /* find biggest window and biggest blank spot */
471 emptyw = c->w[0];
472 bigw = emptyw;
473 for(i=1; i<c->nw; i++){
474 w = c->w[i];
475 /* use >= to choose one near bottom of screen */
476 if(w->body.fr.maxlines >= bigw->body.fr.maxlines)
477 bigw = w;
478 if(w->body.fr.maxlines-w->body.fr.nlines >= emptyw->body.fr.maxlines-emptyw->body.fr.nlines)
479 emptyw = w;
480 }
481 emptyb = &emptyw->body;
482 el = emptyb->fr.maxlines-emptyb->fr.nlines;
483 /* if empty space is big, use it */
484 if(el>15 || (el>3 && el>(bigw->body.fr.maxlines-1)/2))
485 y = emptyb->fr.r.min.y+emptyb->fr.nlines*font->height;
486 else{
487 /* if this window is in column and isn't much smaller, split it */
488 if(t->col==c && Dy(t->w->r)>2*Dy(bigw->r)/3)
489 bigw = t->w;
490 y = (bigw->r.min.y + bigw->r.max.y)/2;
491 }
492 w = coladd(c, nil, nil, y);
493 if(w->body.fr.maxlines < 2)
494 colgrow(w->col, w, 1);
495 return w;
496}