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}