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