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}