├── README.md ├── acme ├── acme.c ├── addr.c ├── buff.c ├── cols.c ├── dat.h ├── disk.c ├── ecmd.c ├── edit.c ├── edit.h ├── elog.c ├── exec.c ├── file.c ├── fns.h ├── fsys.c ├── logf.c ├── look.c ├── mail │ ├── dat.h │ ├── guide │ ├── html.c │ ├── html.o │ ├── mail.c │ ├── mail.o │ ├── mesg.c │ ├── mesg.o │ ├── mkbox │ ├── mkfile │ ├── o.Mail │ ├── readme │ ├── reply.c │ ├── reply.o │ ├── util.c │ ├── util.o │ ├── win.c │ └── win.o ├── mkfile ├── regx.c ├── rows.c ├── scrl.c ├── text.c ├── time.c ├── util.c ├── wind.c └── xfid.c └── screenshot.png /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | __acmePimp__ 3 | ============= 4 | --- 5 | 6 | acmePimp is my personal pimp of Rob Pike's great acme editor (or to be more concrete: Russ Cox's plan9port of acme). 7 | 8 | NOTE: acmePimp is tested for OSX only ! 9 | 10 | what is this? 11 | 12 | ![screenshot of acmePimp: screenshot.png](./screenshot.png?raw=true "acmePimp screenshot") 13 | 14 | when first tried out acme didn't really liked it for 15 | 16 | - use with missing 3 button mouse on MBP 17 | - contra intuitive use of the arrow UP- & DOWN- keys 18 | - it's color layout (there is a narrative about the pastel colors - i know) 19 | -> i got the principal color changes from the web, but i can't find the link again? 20 | - no go autocompletion 21 | -> solved using "agoc" https://github.com/s-urbaniak/agoc which shows me possible completions (and the definitions!) of https://github.com/nsf/gocode in a separate window 22 | 23 | on the first i adapted and actually don't need chording. 24 | 25 | on the second i found that i have a problem primary with the UP key as i use to open-close brackets left-arrow-enter-enter-up-arrow when starting a function or the like. Later found the i ofetn use the DOWN key involuntary - or for it's "natural purpose". 26 | 27 | with this commit all arrows move the cursor: 28 | 29 | * UP - end of the pevious line 30 | * DOWN - next line same vertical position or end-of-line if the line is shorter than it's predecessor. 31 | 32 | last commits include the usual CMD+'s' for saving and auto parenthesis closing addition for opening '(', '{' and "[". 33 | 34 | that is edited within _text.c_ in the acme folder (search for /*me*/ for the changes) and up-arrow will now jump up to the line end of the previous line. 35 | 36 | for acme (_acme.c_) the tab indention is default now and (proportional) default font is set to /mnt/font/Menlo-Regular/14a/font (search "/*me*/" in acme.c for the changes in the source code) 37 | 38 | call acme -o ... to use acme's original color mode (acme -a .. does set off auto indention) 39 | 40 | make sure you compiled plan9/src/cmd/fontsrv to access OSX fonts; list of fonts: 41 | 42 | $ cd /usr/local/plan9/src/cmd/fontsrv 43 | $ fontsrv -p . 44 | 45 | 46 | 47 | --- 48 | 49 | installation: 50 | 51 | clone or download this repo 52 | 53 | cd 54 | // backup the original acme folder 55 | $ mv /usr/local/plan9/src/cmd/acme /usr/local/plan9/src/cmd/_acme.bup 56 | $ mv acme /usr/local/plan9/src/cmd/acme 57 | $ cd /usr/local/plan9/src/cmd/acme 58 | $ 9 mk install 59 | -------------------------------------------------------------------------------- /acme/addr.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "dat.h" 12 | #include "fns.h" 13 | 14 | enum 15 | { 16 | None = 0, 17 | Fore = '+', 18 | Back = '-' 19 | }; 20 | 21 | enum 22 | { 23 | Char, 24 | Line 25 | }; 26 | 27 | int 28 | isaddrc(int r) 29 | { 30 | if(r && utfrune("0123456789+-/$.#,;?", r)!=nil) 31 | return TRUE; 32 | return FALSE; 33 | } 34 | 35 | /* 36 | * quite hard: could be almost anything but white space, but we are a little conservative, 37 | * aiming for regular expressions of alphanumerics and no white space 38 | */ 39 | int 40 | isregexc(int r) 41 | { 42 | if(r == 0) 43 | return FALSE; 44 | if(isalnum(r)) 45 | return TRUE; 46 | if(utfrune("^+-.*?#,;[]()$", r)!=nil) 47 | return TRUE; 48 | return FALSE; 49 | } 50 | 51 | Range 52 | number(uint showerr, Text *t, Range r, int line, int dir, int size, int *evalp) 53 | { 54 | uint q0, q1; 55 | 56 | if(size == Char){ 57 | if(dir == Fore) 58 | line = r.q1+line; 59 | else if(dir == Back){ 60 | if(r.q0==0 && line>0) 61 | r.q0 = t->file->b.nc; 62 | line = r.q0 - line; 63 | } 64 | if(line<0 || line>t->file->b.nc) 65 | goto Rescue; 66 | *evalp = TRUE; 67 | return range(line, line); 68 | } 69 | q0 = r.q0; 70 | q1 = r.q1; 71 | switch(dir){ 72 | case None: 73 | q0 = 0; 74 | q1 = 0; 75 | Forward: 76 | while(line>0 && q1file->b.nc) 77 | if(textreadc(t, q1++) == '\n' || q1==t->file->b.nc) 78 | if(--line > 0) 79 | q0 = q1; 80 | if(line==1 && q1==t->file->b.nc) // 6 goes to end of 5-line file 81 | break; 82 | if(line > 0) 83 | goto Rescue; 84 | break; 85 | case Fore: 86 | if(q1 > 0) 87 | while(q1file->b.nc && textreadc(t, q1-1) != '\n') 88 | q1++; 89 | q0 = q1; 90 | goto Forward; 91 | case Back: 92 | if(q0 < t->file->b.nc) 93 | while(q0>0 && textreadc(t, q0-1)!='\n') 94 | q0--; 95 | q1 = q0; 96 | while(line>0 && q0>0){ 97 | if(textreadc(t, q0-1) == '\n'){ 98 | if(--line >= 0) 99 | q1 = q0; 100 | } 101 | --q0; 102 | } 103 | /* :1-1 is :0 = #0, but :1-2 is an error */ 104 | if(line > 1) 105 | goto Rescue; 106 | while(q0>0 && textreadc(t, q0-1)!='\n') 107 | --q0; 108 | } 109 | *evalp = TRUE; 110 | return range(q0, q1); 111 | 112 | Rescue: 113 | if(showerr) 114 | warning(nil, "address out of range\n"); 115 | *evalp = FALSE; 116 | return r; 117 | } 118 | 119 | 120 | Range 121 | regexp(uint showerr, Text *t, Range lim, Range r, Rune *pat, int dir, int *foundp) 122 | { 123 | int found; 124 | Rangeset sel; 125 | int q; 126 | 127 | if(pat[0] == '\0' && rxnull()){ 128 | if(showerr) 129 | warning(nil, "no previous regular expression\n"); 130 | *foundp = FALSE; 131 | return r; 132 | } 133 | if(pat[0] && rxcompile(pat) == FALSE){ 134 | *foundp = FALSE; 135 | return r; 136 | } 137 | if(dir == Back) 138 | found = rxbexecute(t, r.q0, &sel); 139 | else{ 140 | if(lim.q0 < 0) 141 | q = Infinity; 142 | else 143 | q = lim.q1; 144 | found = rxexecute(t, nil, r.q1, q, &sel); 145 | } 146 | if(!found && showerr) 147 | warning(nil, "no match for regexp\n"); 148 | *foundp = found; 149 | return sel.r[0]; 150 | } 151 | 152 | Range 153 | address(uint showerr, Text *t, Range lim, Range ar, void *a, uint q0, uint q1, int (*getc)(void*, uint), int *evalp, uint *qp) 154 | { 155 | int dir, size, npat; 156 | int prevc, c, nc, n; 157 | uint q; 158 | Rune *pat; 159 | Range r, nr; 160 | 161 | r = ar; 162 | q = q0; 163 | dir = None; 164 | size = Line; 165 | c = 0; 166 | while(q < q1){ 167 | prevc = c; 168 | c = (*getc)(a, q++); 169 | switch(c){ 170 | default: 171 | *qp = q-1; 172 | return r; 173 | case ';': 174 | ar = r; 175 | /* fall through */ 176 | case ',': 177 | if(prevc == 0) /* lhs defaults to 0 */ 178 | r.q0 = 0; 179 | if(q>=q1 && t!=nil && t->file!=nil) /* rhs defaults to $ */ 180 | r.q1 = t->file->b.nc; 181 | else{ 182 | nr = address(showerr, t, lim, ar, a, q, q1, getc, evalp, &q); 183 | r.q1 = nr.q1; 184 | } 185 | *qp = q; 186 | return r; 187 | case '+': 188 | case '-': 189 | if(*evalp && (prevc=='+' || prevc=='-')) 190 | if((nc=(*getc)(a, q))!='#' && nc!='/' && nc!='?') 191 | r = number(showerr, t, r, 1, prevc, Line, evalp); /* do previous one */ 192 | dir = c; 193 | break; 194 | case '.': 195 | case '$': 196 | if(q != q0+1){ 197 | *qp = q-1; 198 | return r; 199 | } 200 | if(*evalp) 201 | if(c == '.') 202 | r = ar; 203 | else 204 | r = range(t->file->b.nc, t->file->b.nc); 205 | if(q < q1) 206 | dir = Fore; 207 | else 208 | dir = None; 209 | break; 210 | case '#': 211 | if(q==q1 || (c=(*getc)(a, q++))<'0' || '9' 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "dat.h" 12 | #include "fns.h" 13 | 14 | enum 15 | { 16 | Slop = 100 /* room to grow with reallocation */ 17 | }; 18 | 19 | static 20 | void 21 | sizecache(Buffer *b, uint n) 22 | { 23 | if(n <= b->cmax) 24 | return; 25 | b->cmax = n+Slop; 26 | b->c = runerealloc(b->c, b->cmax); 27 | } 28 | 29 | static 30 | void 31 | addblock(Buffer *b, uint i, uint n) 32 | { 33 | if(i > b->nbl) 34 | error("internal error: addblock"); 35 | 36 | b->bl = realloc(b->bl, (b->nbl+1)*sizeof b->bl[0]); 37 | if(i < b->nbl) 38 | memmove(b->bl+i+1, b->bl+i, (b->nbl-i)*sizeof(Block*)); 39 | b->bl[i] = disknewblock(disk, n); 40 | b->nbl++; 41 | } 42 | 43 | static 44 | void 45 | delblock(Buffer *b, uint i) 46 | { 47 | if(i >= b->nbl) 48 | error("internal error: delblock"); 49 | 50 | diskrelease(disk, b->bl[i]); 51 | b->nbl--; 52 | if(i < b->nbl) 53 | memmove(b->bl+i, b->bl+i+1, (b->nbl-i)*sizeof(Block*)); 54 | b->bl = realloc(b->bl, b->nbl*sizeof b->bl[0]); 55 | } 56 | 57 | /* 58 | * Move cache so b->cq <= q0 < b->cq+b->cnc. 59 | * If at very end, q0 will fall on end of cache block. 60 | */ 61 | 62 | static 63 | void 64 | flush(Buffer *b) 65 | { 66 | if(b->cdirty || b->cnc==0){ 67 | if(b->cnc == 0) 68 | delblock(b, b->cbi); 69 | else 70 | diskwrite(disk, &b->bl[b->cbi], b->c, b->cnc); 71 | b->cdirty = FALSE; 72 | } 73 | } 74 | 75 | static 76 | void 77 | setcache(Buffer *b, uint q0) 78 | { 79 | Block **blp, *bl; 80 | uint i, q; 81 | 82 | if(q0 > b->nc) 83 | error("internal error: setcache"); 84 | /* 85 | * flush and reload if q0 is not in cache. 86 | */ 87 | if(b->nc == 0 || (b->cq<=q0 && q0cq+b->cnc)) 88 | return; 89 | /* 90 | * if q0 is at end of file and end of cache, continue to grow this block 91 | */ 92 | if(q0==b->nc && q0==b->cq+b->cnc && b->cnccq){ 97 | q = 0; 98 | i = 0; 99 | }else{ 100 | q = b->cq; 101 | i = b->cbi; 102 | } 103 | blp = &b->bl[i]; 104 | while(q+(*blp)->u.n <= q0 && q+(*blp)->u.n < b->nc){ 105 | q += (*blp)->u.n; 106 | i++; 107 | blp++; 108 | if(i >= b->nbl) 109 | error("block not found"); 110 | } 111 | bl = *blp; 112 | /* remember position */ 113 | b->cbi = i; 114 | b->cq = q; 115 | sizecache(b, bl->u.n); 116 | b->cnc = bl->u.n; 117 | /*read block*/ 118 | diskread(disk, bl, b->c, b->cnc); 119 | } 120 | 121 | void 122 | bufinsert(Buffer *b, uint q0, Rune *s, uint n) 123 | { 124 | uint i, m, t, off; 125 | 126 | if(q0 > b->nc) 127 | error("internal error: bufinsert"); 128 | 129 | while(n > 0){ 130 | setcache(b, q0); 131 | off = q0-b->cq; 132 | if(b->cnc+n <= Maxblock){ 133 | /* Everything fits in one block. */ 134 | t = b->cnc+n; 135 | m = n; 136 | if(b->bl == nil){ /* allocate */ 137 | if(b->cnc != 0) 138 | error("internal error: bufinsert1 cnc!=0"); 139 | addblock(b, 0, t); 140 | b->cbi = 0; 141 | } 142 | sizecache(b, t); 143 | runemove(b->c+off+m, b->c+off, b->cnc-off); 144 | runemove(b->c+off, s, m); 145 | b->cnc = t; 146 | goto Tail; 147 | } 148 | /* 149 | * We must make a new block. If q0 is at 150 | * the very beginning or end of this block, 151 | * just make a new block and fill it. 152 | */ 153 | if(q0==b->cq || q0==b->cq+b->cnc){ 154 | if(b->cdirty) 155 | flush(b); 156 | m = min(n, Maxblock); 157 | if(b->bl == nil){ /* allocate */ 158 | if(b->cnc != 0) 159 | error("internal error: bufinsert2 cnc!=0"); 160 | i = 0; 161 | }else{ 162 | i = b->cbi; 163 | if(q0 > b->cq) 164 | i++; 165 | } 166 | addblock(b, i, m); 167 | sizecache(b, m); 168 | runemove(b->c, s, m); 169 | b->cq = q0; 170 | b->cbi = i; 171 | b->cnc = m; 172 | goto Tail; 173 | } 174 | /* 175 | * Split the block; cut off the right side and 176 | * let go of it. 177 | */ 178 | m = b->cnc-off; 179 | if(m > 0){ 180 | i = b->cbi+1; 181 | addblock(b, i, m); 182 | diskwrite(disk, &b->bl[i], b->c+off, m); 183 | b->cnc -= m; 184 | } 185 | /* 186 | * Now at end of block. Take as much input 187 | * as possible and tack it on end of block. 188 | */ 189 | m = min(n, Maxblock-b->cnc); 190 | sizecache(b, b->cnc+m); 191 | runemove(b->c+b->cnc, s, m); 192 | b->cnc += m; 193 | Tail: 194 | b->nc += m; 195 | q0 += m; 196 | s += m; 197 | n -= m; 198 | b->cdirty = TRUE; 199 | } 200 | } 201 | 202 | void 203 | bufdelete(Buffer *b, uint q0, uint q1) 204 | { 205 | uint m, n, off; 206 | 207 | if(!(q0<=q1 && q0<=b->nc && q1<=b->nc)) 208 | error("internal error: bufdelete"); 209 | while(q1 > q0){ 210 | setcache(b, q0); 211 | off = q0-b->cq; 212 | if(q1 > b->cq+b->cnc) 213 | n = b->cnc - off; 214 | else 215 | n = q1-q0; 216 | m = b->cnc - (off+n); 217 | if(m > 0) 218 | runemove(b->c+off, b->c+off+n, m); 219 | b->cnc -= n; 220 | b->cdirty = TRUE; 221 | q1 -= n; 222 | b->nc -= n; 223 | } 224 | } 225 | 226 | static int 227 | bufloader(void *v, uint q0, Rune *r, int nr) 228 | { 229 | bufinsert(v, q0, r, nr); 230 | return nr; 231 | } 232 | 233 | uint 234 | loadfile(int fd, uint q0, int *nulls, int(*f)(void*, uint, Rune*, int), void *arg) 235 | { 236 | char *p; 237 | Rune *r; 238 | int l, m, n, nb, nr; 239 | uint q1; 240 | 241 | p = emalloc((Maxblock+UTFmax+1)*sizeof p[0]); 242 | r = runemalloc(Maxblock); 243 | m = 0; 244 | n = 1; 245 | q1 = q0; 246 | /* 247 | * At top of loop, may have m bytes left over from 248 | * last pass, possibly representing a partial rune. 249 | */ 250 | while(n > 0){ 251 | n = read(fd, p+m, Maxblock); 252 | if(n < 0){ 253 | warning(nil, "read error in Buffer.load"); 254 | break; 255 | } 256 | m += n; 257 | p[m] = 0; 258 | l = m; 259 | if(n > 0) 260 | l -= UTFmax; 261 | cvttorunes(p, l, r, &nb, &nr, nulls); 262 | memmove(p, p+nb, m-nb); 263 | m -= nb; 264 | q1 += (*f)(arg, q1, r, nr); 265 | } 266 | free(p); 267 | free(r); 268 | return q1-q0; 269 | } 270 | 271 | uint 272 | bufload(Buffer *b, uint q0, int fd, int *nulls) 273 | { 274 | if(q0 > b->nc) 275 | error("internal error: bufload"); 276 | return loadfile(fd, q0, nulls, bufloader, b); 277 | } 278 | 279 | void 280 | bufread(Buffer *b, uint q0, Rune *s, uint n) 281 | { 282 | uint m; 283 | 284 | if(!(q0<=b->nc && q0+n<=b->nc)) 285 | error("bufread: internal error"); 286 | 287 | while(n > 0){ 288 | setcache(b, q0); 289 | m = min(n, b->cnc-(q0-b->cq)); 290 | runemove(s, b->c+(q0-b->cq), m); 291 | q0 += m; 292 | s += m; 293 | n -= m; 294 | } 295 | } 296 | 297 | void 298 | bufreset(Buffer *b) 299 | { 300 | int i; 301 | 302 | b->nc = 0; 303 | b->cnc = 0; 304 | b->cq = 0; 305 | b->cdirty = 0; 306 | b->cbi = 0; 307 | /* delete backwards to avoid n² behavior */ 308 | for(i=b->nbl-1; --i>=0; ) 309 | delblock(b, i); 310 | } 311 | 312 | void 313 | bufclose(Buffer *b) 314 | { 315 | bufreset(b); 316 | free(b->c); 317 | b->c = nil; 318 | b->cnc = 0; 319 | free(b->bl); 320 | b->bl = nil; 321 | b->nbl = 0; 322 | } 323 | -------------------------------------------------------------------------------- /acme/cols.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "dat.h" 12 | #include "fns.h" 13 | 14 | static Rune Lheader[] = { 15 | 'N', 'e', 'w', ' ', 16 | 'C', 'u', 't', ' ', 17 | 'P', 'a', 's', 't', 'e', ' ', 18 | 'S', 'n', 'a', 'r', 'f', ' ', 19 | 'S', 'o', 'r', 't', ' ', 20 | 'Z', 'e', 'r', 'o', 'x', ' ', 21 | 'D', 'e', 'l', 'c', 'o', 'l', ' ', 22 | 0 23 | }; 24 | 25 | void 26 | colinit(Column *c, Rectangle r) 27 | { 28 | Rectangle r1; 29 | Text *t; 30 | 31 | draw(screen, r, display->white, nil, ZP); 32 | c->r = r; 33 | c->w = nil; 34 | c->nw = 0; 35 | t = &c->tag; 36 | t->w = nil; 37 | t->col = c; 38 | r1 = r; 39 | r1.max.y = r1.min.y + font->height; 40 | textinit(t, fileaddtext(nil, t), r1, &reffont, tagcols); 41 | t->what = Columntag; 42 | r1.min.y = r1.max.y; 43 | r1.max.y += Border; 44 | draw(screen, r1, display->black, nil, ZP); 45 | textinsert(t, 0, Lheader, 38, TRUE); 46 | textsetselect(t, t->file->b.nc, t->file->b.nc); 47 | draw(screen, t->scrollr, colbutton, nil, colbutton->r.min); 48 | c->safe = TRUE; 49 | } 50 | 51 | Window* 52 | coladd(Column *c, Window *w, Window *clone, int y) 53 | { 54 | Rectangle r, r1; 55 | Window *v; 56 | int i, j, minht, ymax, buggered; 57 | 58 | v = nil; 59 | r = c->r; 60 | r.min.y = c->tag.fr.r.max.y+Border; 61 | if(ynw>0){ /* steal half of last window by default */ 62 | v = c->w[c->nw-1]; 63 | y = v->body.fr.r.min.y+Dy(v->body.fr.r)/2; 64 | } 65 | /* look for window we'll land on */ 66 | for(i=0; inw; i++){ 67 | v = c->w[i]; 68 | if(y < v->r.max.y) 69 | break; 70 | } 71 | buggered = 0; 72 | if(c->nw > 0){ 73 | if(i < c->nw) 74 | i++; /* new window will go after v */ 75 | /* 76 | * if landing window (v) is too small, grow it first. 77 | */ 78 | minht = v->tag.fr.font->height+Border+1; 79 | j = 0; 80 | while(!c->safe || v->body.fr.maxlines<=3 || Dy(v->body.all) <= minht){ 81 | if(++j > 10){ 82 | buggered = 1; /* too many windows in column */ 83 | break; 84 | } 85 | colgrow(c, v, 1); 86 | } 87 | 88 | /* 89 | * figure out where to split v to make room for w 90 | */ 91 | 92 | /* new window stops where next window begins */ 93 | if(i < c->nw) 94 | ymax = c->w[i]->r.min.y-Border; 95 | else 96 | ymax = c->r.max.y; 97 | 98 | /* new window must start after v's tag ends */ 99 | y = max(y, v->tagtop.max.y+Border); 100 | 101 | /* new window must start early enough to end before ymax */ 102 | y = min(y, ymax - minht); 103 | 104 | /* if y is too small, too many windows in column */ 105 | if(y < v->tagtop.max.y+Border) 106 | buggered = 1; 107 | 108 | /* 109 | * resize & redraw v 110 | */ 111 | r = v->r; 112 | r.max.y = ymax; 113 | draw(screen, r, textcols[BACK], nil, ZP); 114 | r1 = r; 115 | y = min(y, ymax-(v->tag.fr.font->height*v->taglines+v->body.fr.font->height+Border+1)); 116 | r1.max.y = min(y, v->body.fr.r.min.y+v->body.fr.nlines*v->body.fr.font->height); 117 | r1.min.y = winresize(v, r1, FALSE, FALSE); 118 | r1.max.y = r1.min.y+Border; 119 | draw(screen, r1, display->black, nil, ZP); 120 | 121 | /* 122 | * leave r with w's coordinates 123 | */ 124 | r.min.y = r1.max.y; 125 | } 126 | if(w == nil){ 127 | w = emalloc(sizeof(Window)); 128 | w->col = c; 129 | draw(screen, r, textcols[BACK], nil, ZP); 130 | wininit(w, clone, r); 131 | }else{ 132 | w->col = c; 133 | winresize(w, r, FALSE, TRUE); 134 | } 135 | w->tag.col = c; 136 | w->tag.row = c->row; 137 | w->body.col = c; 138 | w->body.row = c->row; 139 | c->w = realloc(c->w, (c->nw+1)*sizeof(Window*)); 140 | memmove(c->w+i+1, c->w+i, (c->nw-i)*sizeof(Window*)); 141 | c->nw++; 142 | c->w[i] = w; 143 | c->safe = TRUE; 144 | 145 | /* if there were too many windows, redraw the whole column */ 146 | if(buggered) 147 | colresize(c, c->r); 148 | 149 | savemouse(w); 150 | /* near the button, but in the body */ 151 | moveto(mousectl, addpt(w->tag.scrollr.max, Pt(3, 3))); 152 | barttext = &w->body; 153 | return w; 154 | } 155 | 156 | void 157 | colclose(Column *c, Window *w, int dofree) 158 | { 159 | Rectangle r; 160 | int i, didmouse, up; 161 | 162 | /* w is locked */ 163 | if(!c->safe) 164 | colgrow(c, w, 1); 165 | for(i=0; inw; i++) 166 | if(c->w[i] == w) 167 | goto Found; 168 | error("can't find window"); 169 | Found: 170 | r = w->r; 171 | w->tag.col = nil; 172 | w->body.col = nil; 173 | w->col = nil; 174 | didmouse = restoremouse(w); 175 | if(dofree){ 176 | windelete(w); 177 | winclose(w); 178 | } 179 | c->nw--; 180 | memmove(c->w+i, c->w+i+1, (c->nw-i)*sizeof(Window*)); 181 | c->w = realloc(c->w, c->nw*sizeof(Window*)); 182 | if(c->nw == 0){ 183 | draw(screen, r, display->white, nil, ZP); 184 | return; 185 | } 186 | up = 0; 187 | if(i == c->nw){ /* extend last window down */ 188 | w = c->w[i-1]; 189 | r.min.y = w->r.min.y; 190 | r.max.y = c->r.max.y; 191 | }else{ /* extend next window up */ 192 | up = 1; 193 | w = c->w[i]; 194 | r.max.y = w->r.max.y; 195 | } 196 | draw(screen, r, textcols[BACK], nil, ZP); 197 | if(c->safe) { 198 | if(!didmouse && up) 199 | w->showdel = TRUE; 200 | winresize(w, r, FALSE, TRUE); 201 | if(!didmouse && up) 202 | movetodel(w); 203 | } 204 | } 205 | 206 | void 207 | colcloseall(Column *c) 208 | { 209 | int i; 210 | Window *w; 211 | 212 | if(c == activecol) 213 | activecol = nil; 214 | textclose(&c->tag); 215 | for(i=0; inw; i++){ 216 | w = c->w[i]; 217 | winclose(w); 218 | } 219 | c->nw = 0; 220 | free(c->w); 221 | free(c); 222 | clearmouse(); 223 | } 224 | 225 | void 226 | colmousebut(Column *c) 227 | { 228 | moveto(mousectl, divpt(addpt(c->tag.scrollr.min, c->tag.scrollr.max), 2)); 229 | } 230 | 231 | void 232 | colresize(Column *c, Rectangle r) 233 | { 234 | int i; 235 | Rectangle r1, r2; 236 | Window *w; 237 | 238 | clearmouse(); 239 | r1 = r; 240 | r1.max.y = r1.min.y + c->tag.fr.font->height; 241 | textresize(&c->tag, r1, TRUE); 242 | draw(screen, c->tag.scrollr, colbutton, nil, colbutton->r.min); 243 | r1.min.y = r1.max.y; 244 | r1.max.y += Border; 245 | draw(screen, r1, display->black, nil, ZP); 246 | r1.max.y = r.max.y; 247 | for(i=0; inw; i++){ 248 | w = c->w[i]; 249 | w->maxlines = 0; 250 | if(i == c->nw-1) 251 | r1.max.y = r.max.y; 252 | else 253 | r1.max.y = r1.min.y+(Dy(w->r)+Border)*Dy(r)/Dy(c->r); 254 | r1.max.y = max(r1.max.y, r1.min.y + Border+font->height); 255 | r2 = r1; 256 | r2.max.y = r2.min.y+Border; 257 | draw(screen, r2, display->black, nil, ZP); 258 | r1.min.y = r2.max.y; 259 | r1.min.y = winresize(w, r1, FALSE, i==c->nw-1); 260 | } 261 | c->r = r; 262 | } 263 | 264 | static 265 | int 266 | colcmp(const void *a, const void *b) 267 | { 268 | Rune *r1, *r2; 269 | int i, nr1, nr2; 270 | 271 | r1 = (*(Window**)a)->body.file->name; 272 | nr1 = (*(Window**)a)->body.file->nname; 273 | r2 = (*(Window**)b)->body.file->name; 274 | nr2 = (*(Window**)b)->body.file->nname; 275 | for(i=0; inw == 0) 292 | return; 293 | clearmouse(); 294 | rp = emalloc(c->nw*sizeof(Rectangle)); 295 | wp = emalloc(c->nw*sizeof(Window*)); 296 | memmove(wp, c->w, c->nw*sizeof(Window*)); 297 | qsort(wp, c->nw, sizeof(Window*), colcmp); 298 | for(i=0; inw; i++) 299 | rp[i] = wp[i]->r; 300 | r = c->r; 301 | r.min.y = c->tag.fr.r.max.y; 302 | draw(screen, r, textcols[BACK], nil, ZP); 303 | y = r.min.y; 304 | for(i=0; inw; i++){ 305 | w = wp[i]; 306 | r.min.y = y; 307 | if(i == c->nw-1) 308 | r.max.y = c->r.max.y; 309 | else 310 | r.max.y = r.min.y+Dy(w->r)+Border; 311 | r1 = r; 312 | r1.max.y = r1.min.y+Border; 313 | draw(screen, r1, display->black, nil, ZP); 314 | r.min.y = r1.max.y; 315 | y = winresize(w, r, FALSE, i==c->nw-1); 316 | } 317 | free(rp); 318 | free(c->w); 319 | c->w = wp; 320 | } 321 | 322 | void 323 | colgrow(Column *c, Window *w, int but) 324 | { 325 | Rectangle r, cr; 326 | int i, j, k, l, y1, y2, *nl, *ny, tot, nnl, onl, dnl, h; 327 | Window *v; 328 | 329 | for(i=0; inw; i++) 330 | if(c->w[i] == w) 331 | goto Found; 332 | error("can't find window"); 333 | 334 | Found: 335 | cr = c->r; 336 | if(but < 0){ /* make sure window fills its own space properly */ 337 | r = w->r; 338 | if(i==c->nw-1 || c->safe==FALSE) 339 | r.max.y = cr.max.y; 340 | else 341 | r.max.y = c->w[i+1]->r.min.y - Border; 342 | winresize(w, r, FALSE, TRUE); 343 | return; 344 | } 345 | cr.min.y = c->w[0]->r.min.y; 346 | if(but == 3){ /* full size */ 347 | if(i != 0){ 348 | v = c->w[0]; 349 | c->w[0] = w; 350 | c->w[i] = v; 351 | } 352 | draw(screen, cr, textcols[BACK], nil, ZP); 353 | winresize(w, cr, FALSE, TRUE); 354 | for(i=1; inw; i++) 355 | c->w[i]->body.fr.maxlines = 0; 356 | c->safe = FALSE; 357 | return; 358 | } 359 | /* store old #lines for each window */ 360 | onl = w->body.fr.maxlines; 361 | nl = emalloc(c->nw * sizeof(int)); 362 | ny = emalloc(c->nw * sizeof(int)); 363 | tot = 0; 364 | for(j=0; jnw; j++){ 365 | l = c->w[j]->taglines-1 + c->w[j]->body.fr.maxlines; 366 | nl[j] = l; 367 | tot += l; 368 | } 369 | /* approximate new #lines for this window */ 370 | if(but == 2){ /* as big as can be */ 371 | memset(nl, 0, c->nw * sizeof(int)); 372 | goto Pack; 373 | } 374 | nnl = min(onl + max(min(5, w->taglines-1+w->maxlines), onl/2), tot); 375 | if(nnl < w->taglines-1+w->maxlines) 376 | nnl = (w->taglines-1+w->maxlines + nnl)/2; 377 | if(nnl == 0) 378 | nnl = 2; 379 | dnl = nnl - onl; 380 | /* compute new #lines for each window */ 381 | for(k=1; knw; k++){ 382 | /* prune from later window */ 383 | j = i+k; 384 | if(jnw && nl[j]){ 385 | l = min(dnl, max(1, nl[j]/2)); 386 | nl[j] -= l; 387 | nl[i] += l; 388 | dnl -= l; 389 | } 390 | /* prune from earlier window */ 391 | j = i-k; 392 | if(j>=0 && nl[j]){ 393 | l = min(dnl, max(1, nl[j]/2)); 394 | nl[j] -= l; 395 | nl[i] += l; 396 | dnl -= l; 397 | } 398 | } 399 | Pack: 400 | /* pack everyone above */ 401 | y1 = cr.min.y; 402 | for(j=0; jw[j]; 404 | r = v->r; 405 | r.min.y = y1; 406 | r.max.y = y1+Dy(v->tagtop); 407 | if(nl[j]) 408 | r.max.y += 1 + nl[j]*v->body.fr.font->height; 409 | r.min.y = winresize(v, r, c->safe, FALSE); 410 | r.max.y += Border; 411 | draw(screen, r, display->black, nil, ZP); 412 | y1 = r.max.y; 413 | } 414 | /* scan to see new size of everyone below */ 415 | y2 = c->r.max.y; 416 | for(j=c->nw-1; j>i; j--){ 417 | v = c->w[j]; 418 | r = v->r; 419 | r.min.y = y2-Dy(v->tagtop); 420 | if(nl[j]) 421 | r.min.y -= 1 + nl[j]*v->body.fr.font->height; 422 | r.min.y -= Border; 423 | ny[j] = r.min.y; 424 | y2 = r.min.y; 425 | } 426 | /* compute new size of window */ 427 | r = w->r; 428 | r.min.y = y1; 429 | r.max.y = y2; 430 | h = w->body.fr.font->height; 431 | if(Dy(r) < Dy(w->tagtop)+1+h+Border) 432 | r.max.y = r.min.y + Dy(w->tagtop)+1+h+Border; 433 | /* draw window */ 434 | r.max.y = winresize(w, r, c->safe, TRUE); 435 | if(i < c->nw-1){ 436 | r.min.y = r.max.y; 437 | r.max.y += Border; 438 | draw(screen, r, display->black, nil, ZP); 439 | for(j=i+1; jnw; j++) 440 | ny[j] -= (y2-r.max.y); 441 | } 442 | /* pack everyone below */ 443 | y1 = r.max.y; 444 | for(j=i+1; jnw; j++){ 445 | v = c->w[j]; 446 | r = v->r; 447 | r.min.y = y1; 448 | r.max.y = y1+Dy(v->tagtop); 449 | if(nl[j]) 450 | r.max.y += 1 + nl[j]*v->body.fr.font->height; 451 | y1 = winresize(v, r, c->safe, j==c->nw-1); 452 | if(j < c->nw-1){ /* no border on last window */ 453 | r.min.y = y1; 454 | r.max.y += Border; 455 | draw(screen, r, display->black, nil, ZP); 456 | y1 = r.max.y; 457 | } 458 | } 459 | free(nl); 460 | free(ny); 461 | c->safe = TRUE; 462 | winmousebut(w); 463 | } 464 | 465 | void 466 | coldragwin(Column *c, Window *w, int but) 467 | { 468 | Rectangle r; 469 | int i, b; 470 | Point p, op; 471 | Window *v; 472 | Column *nc; 473 | 474 | clearmouse(); 475 | setcursor(mousectl, &boxcursor); 476 | b = mouse->buttons; 477 | op = mouse->xy; 478 | while(mouse->buttons == b) 479 | readmouse(mousectl); 480 | setcursor(mousectl, nil); 481 | if(mouse->buttons){ 482 | while(mouse->buttons) 483 | readmouse(mousectl); 484 | return; 485 | } 486 | 487 | for(i=0; inw; i++) 488 | if(c->w[i] == w) 489 | goto Found; 490 | error("can't find window"); 491 | 492 | Found: 493 | if(w->tagexpand) /* force recomputation of window tag size */ 494 | w->taglines = 1; 495 | p = mouse->xy; 496 | if(abs(p.x-op.x)<5 && abs(p.y-op.y)<5){ 497 | colgrow(c, w, but); 498 | winmousebut(w); 499 | return; 500 | } 501 | /* is it a flick to the right? */ 502 | if(abs(p.y-op.y)<10 && p.x>op.x+30 && rowwhichcol(c->row, p)==c) 503 | p.x = op.x+Dx(w->r); /* yes: toss to next column */ 504 | nc = rowwhichcol(c->row, p); 505 | if(nc!=nil && nc!=c){ 506 | colclose(c, w, FALSE); 507 | coladd(nc, w, nil, p.y); 508 | winmousebut(w); 509 | return; 510 | } 511 | if(i==0 && c->nw==1) 512 | return; /* can't do it */ 513 | if((i>0 && p.yw[i-1]->r.min.y) || (inw-1 && p.y>w->r.max.y) 514 | || (i==0 && p.y>w->r.max.y)){ 515 | /* shuffle */ 516 | colclose(c, w, FALSE); 517 | coladd(c, w, nil, p.y); 518 | winmousebut(w); 519 | return; 520 | } 521 | if(i == 0) 522 | return; 523 | v = c->w[i-1]; 524 | if(p.y < v->tagtop.max.y) 525 | p.y = v->tagtop.max.y; 526 | if(p.y > w->r.max.y-Dy(w->tagtop)-Border) 527 | p.y = w->r.max.y-Dy(w->tagtop)-Border; 528 | r = v->r; 529 | r.max.y = p.y; 530 | if(r.max.y > v->body.fr.r.min.y){ 531 | r.max.y -= (r.max.y-v->body.fr.r.min.y)%v->body.fr.font->height; 532 | if(v->body.fr.r.min.y == v->body.fr.r.max.y) 533 | r.max.y++; 534 | } 535 | r.min.y = winresize(v, r, c->safe, FALSE); 536 | r.max.y = r.min.y+Border; 537 | draw(screen, r, display->black, nil, ZP); 538 | r.min.y = r.max.y; 539 | if(i == c->nw-1) 540 | r.max.y = c->r.max.y; 541 | else 542 | r.max.y = c->w[i+1]->r.min.y-Border; 543 | winresize(w, r, c->safe, TRUE); 544 | c->safe = TRUE; 545 | winmousebut(w); 546 | } 547 | 548 | Text* 549 | colwhich(Column *c, Point p) 550 | { 551 | int i; 552 | Window *w; 553 | 554 | if(!ptinrect(p, c->r)) 555 | return nil; 556 | if(ptinrect(p, c->tag.all)) 557 | return &c->tag; 558 | for(i=0; inw; i++){ 559 | w = c->w[i]; 560 | if(ptinrect(p, w->r)){ 561 | if(ptinrect(p, w->tagtop) || ptinrect(p, w->tag.all)) 562 | return &w->tag; 563 | /* exclude partial line at bottom */ 564 | if(p.x >= w->body.scrollr.max.x && p.y >= w->body.fr.r.max.y) 565 | return nil; 566 | return &w->body; 567 | } 568 | } 569 | return nil; 570 | } 571 | 572 | int 573 | colclean(Column *c) 574 | { 575 | int i, clean; 576 | 577 | clean = TRUE; 578 | for(i=0; inw; i++) 579 | clean &= winclean(c->w[i], TRUE); 580 | return clean; 581 | } 582 | -------------------------------------------------------------------------------- /acme/dat.h: -------------------------------------------------------------------------------- 1 | enum 2 | { 3 | Qdir, 4 | Qacme, 5 | Qcons, 6 | Qconsctl, 7 | Qdraw, 8 | Qeditout, 9 | Qindex, 10 | Qlabel, 11 | Qlog, 12 | Qnew, 13 | 14 | QWaddr, 15 | QWbody, 16 | QWctl, 17 | QWdata, 18 | QWeditout, 19 | QWerrors, 20 | QWevent, 21 | QWrdsel, 22 | QWwrsel, 23 | QWtag, 24 | QWxdata, 25 | QMAX 26 | }; 27 | 28 | enum 29 | { 30 | Blockincr = 256, 31 | Maxblock = 8*1024, 32 | NRange = 10, 33 | Infinity = 0x7FFFFFFF /* huge value for regexp address */ 34 | }; 35 | 36 | #define Buffer AcmeBuffer 37 | typedef struct Block Block; 38 | typedef struct Buffer Buffer; 39 | typedef struct Command Command; 40 | typedef struct Column Column; 41 | typedef struct Dirlist Dirlist; 42 | typedef struct Dirtab Dirtab; 43 | typedef struct Disk Disk; 44 | typedef struct Expand Expand; 45 | typedef struct Fid Fid; 46 | typedef struct File File; 47 | typedef struct Elog Elog; 48 | typedef struct Mntdir Mntdir; 49 | typedef struct Range Range; 50 | typedef struct Rangeset Rangeset; 51 | typedef struct Reffont Reffont; 52 | typedef struct Row Row; 53 | typedef struct Runestr Runestr; 54 | typedef struct Text Text; 55 | typedef struct Timer Timer; 56 | typedef struct Window Window; 57 | typedef struct Xfid Xfid; 58 | 59 | struct Runestr 60 | { 61 | Rune *r; 62 | int nr; 63 | }; 64 | 65 | struct Range 66 | { 67 | int q0; 68 | int q1; 69 | }; 70 | 71 | struct Block 72 | { 73 | vlong addr; /* disk address in bytes */ 74 | union 75 | { 76 | uint n; /* number of used runes in block */ 77 | Block *next; /* pointer to next in free list */ 78 | } u; 79 | }; 80 | 81 | struct Disk 82 | { 83 | int fd; 84 | vlong addr; /* length of temp file */ 85 | Block *free[Maxblock/Blockincr+1]; 86 | }; 87 | 88 | Disk* diskinit(void); 89 | Block* disknewblock(Disk*, uint); 90 | void diskrelease(Disk*, Block*); 91 | void diskread(Disk*, Block*, Rune*, uint); 92 | void diskwrite(Disk*, Block**, Rune*, uint); 93 | 94 | struct Buffer 95 | { 96 | uint nc; 97 | Rune *c; /* cache */ 98 | uint cnc; /* bytes in cache */ 99 | uint cmax; /* size of allocated cache */ 100 | uint cq; /* position of cache */ 101 | int cdirty; /* cache needs to be written */ 102 | uint cbi; /* index of cache Block */ 103 | Block **bl; /* array of blocks */ 104 | uint nbl; /* number of blocks */ 105 | }; 106 | void bufinsert(Buffer*, uint, Rune*, uint); 107 | void bufdelete(Buffer*, uint, uint); 108 | uint bufload(Buffer*, uint, int, int*); 109 | void bufread(Buffer*, uint, Rune*, uint); 110 | void bufclose(Buffer*); 111 | void bufreset(Buffer*); 112 | 113 | struct Elog 114 | { 115 | short type; /* Delete, Insert, Filename */ 116 | uint q0; /* location of change (unused in f) */ 117 | uint nd; /* number of deleted characters */ 118 | uint nr; /* # runes in string or file name */ 119 | Rune *r; 120 | }; 121 | void elogterm(File*); 122 | void elogclose(File*); 123 | void eloginsert(File*, int, Rune*, int); 124 | void elogdelete(File*, int, int); 125 | void elogreplace(File*, int, int, Rune*, int); 126 | void elogapply(File*); 127 | 128 | struct File 129 | { 130 | Buffer b; /* the data */ 131 | Buffer delta; /* transcript of changes */ 132 | Buffer epsilon; /* inversion of delta for redo */ 133 | Buffer *elogbuf; /* log of pending editor changes */ 134 | Elog elog; /* current pending change */ 135 | Rune *name; /* name of associated file */ 136 | int nname; /* size of name */ 137 | uvlong qidpath; /* of file when read */ 138 | ulong mtime; /* of file when read */ 139 | int dev; /* of file when read */ 140 | int unread; /* file has not been read from disk */ 141 | int editclean; /* mark clean after edit command */ 142 | 143 | int seq; /* if seq==0, File acts like Buffer */ 144 | int mod; 145 | Text *curtext; /* most recently used associated text */ 146 | Text **text; /* list of associated texts */ 147 | int ntext; 148 | int dumpid; /* used in dumping zeroxed windows */ 149 | }; 150 | File* fileaddtext(File*, Text*); 151 | void fileclose(File*); 152 | void filedelete(File*, uint, uint); 153 | void filedeltext(File*, Text*); 154 | void fileinsert(File*, uint, Rune*, uint); 155 | uint fileload(File*, uint, int, int*); 156 | void filemark(File*); 157 | void filereset(File*); 158 | void filesetname(File*, Rune*, int); 159 | void fileundelete(File*, Buffer*, uint, uint); 160 | void fileuninsert(File*, Buffer*, uint, uint); 161 | void fileunsetname(File*, Buffer*); 162 | void fileundo(File*, int, uint*, uint*); 163 | uint fileredoseq(File*); 164 | 165 | enum /* Text.what */ 166 | { 167 | Columntag, 168 | Rowtag, 169 | Tag, 170 | Body 171 | }; 172 | 173 | struct Text 174 | { 175 | File *file; 176 | Frame fr; 177 | Reffont *reffont; 178 | uint org; 179 | uint q0; 180 | uint q1; 181 | int what; 182 | int tabstop; 183 | Window *w; 184 | Rectangle scrollr; 185 | Rectangle lastsr; 186 | Rectangle all; 187 | Row *row; 188 | Column *col; 189 | 190 | uint iq1; /* last input position */ 191 | uint eq0; /* start of typing for ESC */ 192 | uint cq0; /* cache position */ 193 | int ncache; /* storage for insert */ 194 | int ncachealloc; 195 | Rune *cache; 196 | int nofill; 197 | int needundo; 198 | }; 199 | 200 | uint textbacknl(Text*, uint, uint); 201 | uint textbsinsert(Text*, uint, Rune*, uint, int, int*); 202 | int textbswidth(Text*, Rune); 203 | int textclickhtmlmatch(Text*, uint*, uint*); 204 | int textclickmatch(Text*, int, int, int, uint*); 205 | void textclose(Text*); 206 | void textcolumnate(Text*, Dirlist**, int); 207 | void textcommit(Text*, int); 208 | void textconstrain(Text*, uint, uint, uint*, uint*); 209 | void textdelete(Text*, uint, uint, int); 210 | void textdoubleclick(Text*, uint*, uint*); 211 | void textfill(Text*); 212 | void textframescroll(Text*, int); 213 | void textinit(Text*, File*, Rectangle, Reffont*, Image**); 214 | void textinsert(Text*, uint, Rune*, uint, int); 215 | int textload(Text*, uint, char*, int); 216 | Rune textreadc(Text*, uint); 217 | void textredraw(Text*, Rectangle, Font*, Image*, int); 218 | void textreset(Text*); 219 | int textresize(Text*, Rectangle, int); 220 | void textscrdraw(Text*); 221 | void textscroll(Text*, int); 222 | void textselect(Text*); 223 | int textselect2(Text*, uint*, uint*, Text**); 224 | int textselect23(Text*, uint*, uint*, Image*, int); 225 | int textselect3(Text*, uint*, uint*); 226 | void textsetorigin(Text*, uint, int); 227 | void textsetselect(Text*, uint, uint); 228 | void textshow(Text*, uint, uint, int); 229 | void texttype(Text*, Rune); 230 | 231 | struct Window 232 | { 233 | QLock lk; 234 | Ref ref; 235 | Text tag; 236 | Text body; 237 | Rectangle r; 238 | uchar isdir; 239 | uchar isscratch; 240 | uchar filemenu; 241 | uchar dirty; 242 | uchar autoindent; 243 | uchar showdel; 244 | int id; 245 | Range addr; 246 | Range limit; 247 | uchar nopen[QMAX]; 248 | uchar nomark; 249 | Range wrselrange; 250 | int rdselfd; 251 | Column *col; 252 | Xfid *eventx; 253 | char *events; 254 | int nevents; 255 | int owner; 256 | int maxlines; 257 | Dirlist **dlp; 258 | int ndl; 259 | int putseq; 260 | int nincl; 261 | Rune **incl; 262 | Reffont *reffont; 263 | QLock ctllock; 264 | uint ctlfid; 265 | char *dumpstr; 266 | char *dumpdir; 267 | int dumpid; 268 | int utflastqid; 269 | int utflastboff; 270 | int utflastq; 271 | int tagsafe; /* taglines is correct */ 272 | int tagexpand; 273 | int taglines; 274 | Rectangle tagtop; 275 | QLock editoutlk; 276 | }; 277 | 278 | void wininit(Window*, Window*, Rectangle); 279 | void winlock(Window*, int); 280 | void winlock1(Window*, int); 281 | void winunlock(Window*); 282 | void wintype(Window*, Text*, Rune); 283 | void winundo(Window*, int); 284 | void winsetname(Window*, Rune*, int); 285 | void winsettag(Window*); 286 | void winsettag1(Window*); 287 | void wincommit(Window*, Text*); 288 | int winresize(Window*, Rectangle, int, int); 289 | void winclose(Window*); 290 | void windelete(Window*); 291 | int winclean(Window*, int); 292 | void windirfree(Window*); 293 | void winevent(Window*, char*, ...); 294 | void winmousebut(Window*); 295 | void winaddincl(Window*, Rune*, int); 296 | void wincleartag(Window*); 297 | char *winctlprint(Window*, char*, int); 298 | 299 | struct Column 300 | { 301 | Rectangle r; 302 | Text tag; 303 | Row *row; 304 | Window **w; 305 | int nw; 306 | int safe; 307 | }; 308 | 309 | void colinit(Column*, Rectangle); 310 | Window* coladd(Column*, Window*, Window*, int); 311 | void colclose(Column*, Window*, int); 312 | void colcloseall(Column*); 313 | void colresize(Column*, Rectangle); 314 | Text* colwhich(Column*, Point); 315 | void coldragwin(Column*, Window*, int); 316 | void colgrow(Column*, Window*, int); 317 | int colclean(Column*); 318 | void colsort(Column*); 319 | void colmousebut(Column*); 320 | 321 | struct Row 322 | { 323 | QLock lk; 324 | Rectangle r; 325 | Text tag; 326 | Column **col; 327 | int ncol; 328 | 329 | }; 330 | 331 | void rowinit(Row*, Rectangle); 332 | Column* rowadd(Row*, Column *c, int); 333 | void rowclose(Row*, Column*, int); 334 | Text* rowwhich(Row*, Point); 335 | Column* rowwhichcol(Row*, Point); 336 | void rowresize(Row*, Rectangle); 337 | Text* rowtype(Row*, Rune, Point); 338 | void rowdragcol(Row*, Column*, int but); 339 | int rowclean(Row*); 340 | void rowdump(Row*, char*); 341 | int rowload(Row*, char*, int); 342 | void rowloadfonts(char*); 343 | 344 | struct Timer 345 | { 346 | int dt; 347 | int cancel; 348 | Channel *c; /* chan(int) */ 349 | Timer *next; 350 | }; 351 | 352 | struct Command 353 | { 354 | int pid; 355 | Rune *name; 356 | int nname; 357 | char *text; 358 | char **av; 359 | int iseditcmd; 360 | Mntdir *md; 361 | Command *next; 362 | }; 363 | 364 | struct Dirtab 365 | { 366 | char *name; 367 | uchar type; 368 | uint qid; 369 | uint perm; 370 | }; 371 | 372 | struct Mntdir 373 | { 374 | int id; 375 | int ref; 376 | Rune *dir; 377 | int ndir; 378 | Mntdir *next; 379 | int nincl; 380 | Rune **incl; 381 | }; 382 | 383 | struct Fid 384 | { 385 | int fid; 386 | int busy; 387 | int open; 388 | Qid qid; 389 | Window *w; 390 | Dirtab *dir; 391 | Fid *next; 392 | Mntdir *mntdir; 393 | int nrpart; 394 | uchar rpart[UTFmax]; 395 | vlong logoff; // for putlog 396 | }; 397 | 398 | 399 | struct Xfid 400 | { 401 | void *arg; /* args to xfidinit */ 402 | Fcall fcall; 403 | Xfid *next; 404 | Channel *c; /* chan(void(*)(Xfid*)) */ 405 | Fid *f; 406 | uchar *buf; 407 | int flushed; 408 | }; 409 | 410 | void xfidctl(void *); 411 | void xfidflush(Xfid*); 412 | void xfidopen(Xfid*); 413 | void xfidclose(Xfid*); 414 | void xfidread(Xfid*); 415 | void xfidwrite(Xfid*); 416 | void xfidctlwrite(Xfid*, Window*); 417 | void xfideventread(Xfid*, Window*); 418 | void xfideventwrite(Xfid*, Window*); 419 | void xfidindexread(Xfid*); 420 | void xfidutfread(Xfid*, Text*, uint, int); 421 | int xfidruneread(Xfid*, Text*, uint, uint); 422 | void xfidlogopen(Xfid*); 423 | void xfidlogread(Xfid*); 424 | void xfidlogflush(Xfid*); 425 | void xfidlog(Window*, char*); 426 | 427 | struct Reffont 428 | { 429 | Ref ref; 430 | Font *f; 431 | 432 | }; 433 | Reffont *rfget(int, int, int, char*); 434 | void rfclose(Reffont*); 435 | 436 | struct Rangeset 437 | { 438 | Range r[NRange]; 439 | }; 440 | 441 | struct Dirlist 442 | { 443 | Rune *r; 444 | int nr; 445 | int wid; 446 | }; 447 | 448 | struct Expand 449 | { 450 | uint q0; 451 | uint q1; 452 | Rune *name; 453 | int nname; 454 | char *bname; 455 | int jump; 456 | union{ 457 | Text *at; 458 | Rune *ar; 459 | } u; 460 | int (*agetc)(void*, uint); 461 | int a0; 462 | int a1; 463 | }; 464 | 465 | enum 466 | { 467 | /* fbufalloc() guarantees room off end of BUFSIZE */ 468 | BUFSIZE = Maxblock+IOHDRSZ, /* size from fbufalloc() */ 469 | RBUFSIZE = BUFSIZE/sizeof(Rune), 470 | EVENTSIZE = 256, 471 | }; 472 | 473 | #define Scrollwid scalesize(display, 12) 474 | #define Scrollgap scalesize(display, 4) 475 | #define Margin scalesize(display, 4) 476 | #define Border scalesize(display, 2) 477 | #define ButtonBorder scalesize(display, 2) 478 | 479 | #define QID(w,q) ((w<<8)|(q)) 480 | #define WIN(q) ((((ulong)(q).path)>>8) & 0xFFFFFF) 481 | #define FILE(q) ((q).path & 0xFF) 482 | 483 | #undef FALSE 484 | #undef TRUE 485 | 486 | enum 487 | { 488 | FALSE, 489 | TRUE, 490 | XXX 491 | }; 492 | 493 | enum 494 | { 495 | Empty = 0, 496 | Null = '-', 497 | Delete = 'd', 498 | Insert = 'i', 499 | Replace = 'r', 500 | Filename = 'f' 501 | }; 502 | 503 | enum /* editing */ 504 | { 505 | Inactive = 0, 506 | Inserting, 507 | Collecting 508 | }; 509 | 510 | uint globalincref; 511 | uint seq; 512 | uint maxtab; /* size of a tab, in units of the '0' character */ 513 | 514 | Display *display; 515 | Image *screen; 516 | Font *font; 517 | Mouse *mouse; 518 | Mousectl *mousectl; 519 | Keyboardctl *keyboardctl; 520 | Reffont reffont; 521 | Image *modbutton; 522 | Image *colbutton; 523 | Image *button; 524 | Image *but2col; 525 | Image *but3col; 526 | Cursor boxcursor; 527 | Row row; 528 | int timerpid; 529 | Disk *disk; 530 | Text *seltext; 531 | Text *argtext; 532 | Text *mousetext; /* global because Text.close needs to clear it */ 533 | Text *typetext; /* global because Text.close needs to clear it */ 534 | Text *barttext; /* shared between mousetask and keyboardthread */ 535 | int bartflag; 536 | int swapscrollbuttons; 537 | Window *activewin; 538 | Column *activecol; 539 | Buffer snarfbuf; 540 | Rectangle nullrect; 541 | int fsyspid; 542 | char *cputype; 543 | char *objtype; 544 | char *home; 545 | char *acmeshell; 546 | char *fontnames[2]; 547 | Image *tagcols[NCOL]; 548 | Image *textcols[NCOL]; 549 | extern char wdir[]; /* must use extern because no dimension given */ 550 | int editing; 551 | int erroutfd; 552 | int messagesize; /* negotiated in 9P version setup */ 553 | int globalautoindent; 554 | int oriniginalcolors; /*me*/ 555 | int dodollarsigns; 556 | char* mtpt; 557 | 558 | enum 559 | { 560 | Kscrolloneup = KF|0x20, 561 | Kscrollonedown = KF|0x21 562 | }; 563 | 564 | Channel *cplumb; /* chan(Plumbmsg*) */ 565 | Channel *cwait; /* chan(Waitmsg) */ 566 | Channel *ccommand; /* chan(Command*) */ 567 | Channel *ckill; /* chan(Rune*) */ 568 | Channel *cxfidalloc; /* chan(Xfid*) */ 569 | Channel *cxfidfree; /* chan(Xfid*) */ 570 | Channel *cnewwindow; /* chan(Channel*) */ 571 | Channel *mouseexit0; /* chan(int) */ 572 | Channel *mouseexit1; /* chan(int) */ 573 | Channel *cexit; /* chan(int) */ 574 | Channel *cerr; /* chan(char*) */ 575 | Channel *cedit; /* chan(int) */ 576 | Channel *cwarn; /* chan(void*)[1] (really chan(unit)[1]) */ 577 | 578 | QLock editoutlk; 579 | 580 | #define STACK 65536 581 | -------------------------------------------------------------------------------- /acme/disk.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "dat.h" 12 | #include "fns.h" 13 | 14 | static Block *blist; 15 | 16 | int 17 | tempfile(void) 18 | { 19 | char buf[128]; 20 | int i, fd; 21 | 22 | snprint(buf, sizeof buf, "/tmp/X%d.%.4sacme", getpid(), getuser()); 23 | for(i='A'; i<='Z'; i++){ 24 | buf[5] = i; 25 | if(access(buf, AEXIST) == 0) 26 | continue; 27 | fd = create(buf, ORDWR|ORCLOSE|OCEXEC, 0600); 28 | if(fd >= 0) 29 | return fd; 30 | } 31 | return -1; 32 | } 33 | 34 | Disk* 35 | diskinit() 36 | { 37 | Disk *d; 38 | 39 | d = emalloc(sizeof(Disk)); 40 | d->fd = tempfile(); 41 | if(d->fd < 0){ 42 | fprint(2, "acme: can't create temp file: %r\n"); 43 | threadexitsall("diskinit"); 44 | } 45 | return d; 46 | } 47 | 48 | static 49 | uint 50 | ntosize(uint n, uint *ip) 51 | { 52 | uint size; 53 | 54 | if(n > Maxblock) 55 | error("internal error: ntosize"); 56 | size = n; 57 | if(size & (Blockincr-1)) 58 | size += Blockincr - (size & (Blockincr-1)); 59 | /* last bucket holds blocks of exactly Maxblock */ 60 | if(ip) 61 | *ip = size/Blockincr; 62 | return size * sizeof(Rune); 63 | } 64 | 65 | Block* 66 | disknewblock(Disk *d, uint n) 67 | { 68 | uint i, j, size; 69 | Block *b; 70 | 71 | size = ntosize(n, &i); 72 | b = d->free[i]; 73 | if(b) 74 | d->free[i] = b->u.next; 75 | else{ 76 | /* allocate in chunks to reduce malloc overhead */ 77 | if(blist == nil){ 78 | blist = emalloc(100*sizeof(Block)); 79 | for(j=0; j<100-1; j++) 80 | blist[j].u.next = &blist[j+1]; 81 | } 82 | b = blist; 83 | blist = b->u.next; 84 | b->addr = d->addr; 85 | if(d->addr+size < d->addr){ 86 | error("temp file overflow"); 87 | } 88 | d->addr += size; 89 | } 90 | b->u.n = n; 91 | return b; 92 | } 93 | 94 | void 95 | diskrelease(Disk *d, Block *b) 96 | { 97 | uint i; 98 | 99 | ntosize(b->u.n, &i); 100 | b->u.next = d->free[i]; 101 | d->free[i] = b; 102 | } 103 | 104 | void 105 | diskwrite(Disk *d, Block **bp, Rune *r, uint n) 106 | { 107 | int size, nsize; 108 | Block *b; 109 | 110 | b = *bp; 111 | size = ntosize(b->u.n, nil); 112 | nsize = ntosize(n, nil); 113 | if(size != nsize){ 114 | diskrelease(d, b); 115 | b = disknewblock(d, n); 116 | *bp = b; 117 | } 118 | if(pwrite(d->fd, r, n*sizeof(Rune), b->addr) != n*sizeof(Rune)) 119 | error("write error to temp file"); 120 | b->u.n = n; 121 | } 122 | 123 | void 124 | diskread(Disk *d, Block *b, Rune *r, uint n) 125 | { 126 | if(n > b->u.n) 127 | error("internal error: diskread"); 128 | 129 | ntosize(b->u.n, nil); 130 | if(pread(d->fd, r, n*sizeof(Rune), b->addr) != n*sizeof(Rune)) 131 | error("read error from temp file"); 132 | } 133 | -------------------------------------------------------------------------------- /acme/edit.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "dat.h" 12 | #include "edit.h" 13 | #include "fns.h" 14 | 15 | static char linex[]="\n"; 16 | static char wordx[]=" \t\n"; 17 | struct cmdtab cmdtab[]={ 18 | /* cmdc text regexp addr defcmd defaddr count token fn */ 19 | '\n', 0, 0, 0, 0, aDot, 0, 0, nl_cmd, 20 | 'a', 1, 0, 0, 0, aDot, 0, 0, a_cmd, 21 | 'b', 0, 0, 0, 0, aNo, 0, linex, b_cmd, 22 | 'c', 1, 0, 0, 0, aDot, 0, 0, c_cmd, 23 | 'd', 0, 0, 0, 0, aDot, 0, 0, d_cmd, 24 | 'e', 0, 0, 0, 0, aNo, 0, wordx, e_cmd, 25 | 'f', 0, 0, 0, 0, aNo, 0, wordx, f_cmd, 26 | 'g', 0, 1, 0, 'p', aDot, 0, 0, g_cmd, 27 | 'i', 1, 0, 0, 0, aDot, 0, 0, i_cmd, 28 | 'm', 0, 0, 1, 0, aDot, 0, 0, m_cmd, 29 | 'p', 0, 0, 0, 0, aDot, 0, 0, p_cmd, 30 | 'r', 0, 0, 0, 0, aDot, 0, wordx, e_cmd, 31 | 's', 0, 1, 0, 0, aDot, 1, 0, s_cmd, 32 | 't', 0, 0, 1, 0, aDot, 0, 0, m_cmd, 33 | 'u', 0, 0, 0, 0, aNo, 2, 0, u_cmd, 34 | 'v', 0, 1, 0, 'p', aDot, 0, 0, g_cmd, 35 | 'w', 0, 0, 0, 0, aAll, 0, wordx, w_cmd, 36 | 'x', 0, 1, 0, 'p', aDot, 0, 0, x_cmd, 37 | 'y', 0, 1, 0, 'p', aDot, 0, 0, x_cmd, 38 | '=', 0, 0, 0, 0, aDot, 0, linex, eq_cmd, 39 | 'B', 0, 0, 0, 0, aNo, 0, linex, B_cmd, 40 | 'D', 0, 0, 0, 0, aNo, 0, linex, D_cmd, 41 | 'X', 0, 1, 0, 'f', aNo, 0, 0, X_cmd, 42 | 'Y', 0, 1, 0, 'f', aNo, 0, 0, X_cmd, 43 | '<', 0, 0, 0, 0, aDot, 0, linex, pipe_cmd, 44 | '|', 0, 0, 0, 0, aDot, 0, linex, pipe_cmd, 45 | '>', 0, 0, 0, 0, aDot, 0, linex, pipe_cmd, 46 | /* deliberately unimplemented: 47 | 'k', 0, 0, 0, 0, aDot, 0, 0, k_cmd, 48 | 'n', 0, 0, 0, 0, aNo, 0, 0, n_cmd, 49 | 'q', 0, 0, 0, 0, aNo, 0, 0, q_cmd, 50 | '!', 0, 0, 0, 0, aNo, 0, linex, plan9_cmd, 51 | */ 52 | 0, 0, 0, 0, 0, 0, 0, 0 53 | }; 54 | 55 | Cmd *parsecmd(int); 56 | Addr *compoundaddr(void); 57 | Addr *simpleaddr(void); 58 | void freecmd(void); 59 | void okdelim(int); 60 | 61 | Rune *cmdstartp; 62 | Rune *cmdendp; 63 | Rune *cmdp; 64 | Channel *editerrc; 65 | 66 | String *lastpat; 67 | int patset; 68 | 69 | List cmdlist; 70 | List addrlist; 71 | List stringlist; 72 | Text *curtext; 73 | int editing = Inactive; 74 | 75 | String* newstring(int); 76 | 77 | void 78 | editthread(void *v) 79 | { 80 | Cmd *cmdp; 81 | 82 | USED(v); 83 | threadsetname("editthread"); 84 | while((cmdp=parsecmd(0)) != 0){ 85 | if(cmdexec(curtext, cmdp) == 0) 86 | break; 87 | freecmd(); 88 | } 89 | sendp(editerrc, nil); 90 | } 91 | 92 | void 93 | allelogterm(Window *w, void *x) 94 | { 95 | USED(x); 96 | elogterm(w->body.file); 97 | } 98 | 99 | void 100 | alleditinit(Window *w, void *x) 101 | { 102 | USED(x); 103 | textcommit(&w->tag, TRUE); 104 | textcommit(&w->body, TRUE); 105 | w->body.file->editclean = FALSE; 106 | } 107 | 108 | void 109 | allupdate(Window *w, void *x) 110 | { 111 | Text *t; 112 | int i; 113 | File *f; 114 | 115 | USED(x); 116 | t = &w->body; 117 | f = t->file; 118 | if(f->curtext != t) /* do curtext only */ 119 | return; 120 | if(f->elog.type == Null) 121 | elogterm(f); 122 | else if(f->elog.type != Empty){ 123 | elogapply(f); 124 | if(f->editclean){ 125 | f->mod = FALSE; 126 | for(i=0; intext; i++) 127 | f->text[i]->w->dirty = FALSE; 128 | } 129 | } 130 | textsetselect(t, t->q0, t->q1); 131 | textscrdraw(t); 132 | winsettag(w); 133 | } 134 | 135 | void 136 | editerror(char *fmt, ...) 137 | { 138 | va_list arg; 139 | char *s; 140 | 141 | va_start(arg, fmt); 142 | s = vsmprint(fmt, arg); 143 | va_end(arg); 144 | freecmd(); 145 | allwindows(allelogterm, nil); /* truncate the edit logs */ 146 | sendp(editerrc, s); 147 | threadexits(nil); 148 | } 149 | 150 | void 151 | editcmd(Text *ct, Rune *r, uint n) 152 | { 153 | char *err; 154 | 155 | if(n == 0) 156 | return; 157 | if(2*n > RBUFSIZE){ 158 | warning(nil, "string too long\n"); 159 | return; 160 | } 161 | 162 | allwindows(alleditinit, nil); 163 | if(cmdstartp) 164 | free(cmdstartp); 165 | cmdstartp = runemalloc(n+2); 166 | runemove(cmdstartp, r, n); 167 | if(r[n-1] != '\n') 168 | cmdstartp[n++] = '\n'; 169 | cmdstartp[n] = '\0'; 170 | cmdendp = cmdstartp+n; 171 | cmdp = cmdstartp; 172 | if(ct->w == nil) 173 | curtext = nil; 174 | else 175 | curtext = &ct->w->body; 176 | resetxec(); 177 | if(editerrc == nil){ 178 | editerrc = chancreate(sizeof(char*), 0); 179 | chansetname(editerrc, "editerrc"); 180 | lastpat = allocstring(0); 181 | } 182 | threadcreate(editthread, nil, STACK); 183 | err = recvp(editerrc); 184 | editing = Inactive; 185 | if(err != nil){ 186 | if(err[0] != '\0') 187 | warning(nil, "Edit: %s\n", err); 188 | free(err); 189 | } 190 | 191 | /* update everyone whose edit log has data */ 192 | allwindows(allupdate, nil); 193 | } 194 | 195 | int 196 | getch(void) 197 | { 198 | if(cmdp == cmdendp) 199 | return -1; 200 | return *cmdp++; 201 | } 202 | 203 | int 204 | nextc(void) 205 | { 206 | if(cmdp == cmdendp) 207 | return -1; 208 | return *cmdp; 209 | } 210 | 211 | void 212 | ungetch(void) 213 | { 214 | if(--cmdp < cmdstartp) 215 | error("ungetch"); 216 | } 217 | 218 | long 219 | getnum(int signok) 220 | { 221 | long n; 222 | int c, sign; 223 | 224 | n = 0; 225 | sign = 1; 226 | if(signok>1 && nextc()=='-'){ 227 | sign = -1; 228 | getch(); 229 | } 230 | if((c=nextc())<'0' || '9'= 0) 246 | ungetch(); 247 | return c; 248 | } 249 | 250 | /* 251 | * Check that list has room for one more element. 252 | */ 253 | void 254 | growlist(List *l) 255 | { 256 | if(l->u.listptr==0 || l->nalloc==0){ 257 | l->nalloc = INCR; 258 | l->u.listptr = emalloc(INCR*sizeof(void*)); 259 | l->nused = 0; 260 | }else if(l->nused == l->nalloc){ 261 | l->u.listptr = erealloc(l->u.listptr, (l->nalloc+INCR)*sizeof(void*)); 262 | memset(l->u.ptr+l->nalloc, 0, INCR*sizeof(void*)); 263 | l->nalloc += INCR; 264 | } 265 | } 266 | 267 | /* 268 | * Remove the ith element from the list 269 | */ 270 | void 271 | dellist(List *l, int i) 272 | { 273 | memmove(&l->u.ptr[i], &l->u.ptr[i+1], (l->nused-(i+1))*sizeof(void*)); 274 | l->nused--; 275 | } 276 | 277 | /* 278 | * Add a new element, whose position is i, to the list 279 | */ 280 | void 281 | inslist(List *l, int i, void *v) 282 | { 283 | growlist(l); 284 | memmove(&l->u.ptr[i+1], &l->u.ptr[i], (l->nused-i)*sizeof(void*)); 285 | l->u.ptr[i] = v; 286 | l->nused++; 287 | } 288 | 289 | void 290 | listfree(List *l) 291 | { 292 | free(l->u.listptr); 293 | free(l); 294 | } 295 | 296 | String* 297 | allocstring(int n) 298 | { 299 | String *s; 300 | 301 | s = emalloc(sizeof(String)); 302 | s->n = n; 303 | s->nalloc = n+10; 304 | s->r = emalloc(s->nalloc*sizeof(Rune)); 305 | s->r[n] = '\0'; 306 | return s; 307 | } 308 | 309 | void 310 | freestring(String *s) 311 | { 312 | free(s->r); 313 | free(s); 314 | } 315 | 316 | Cmd* 317 | newcmd(void){ 318 | Cmd *p; 319 | 320 | p = emalloc(sizeof(Cmd)); 321 | inslist(&cmdlist, cmdlist.nused, p); 322 | return p; 323 | } 324 | 325 | String* 326 | newstring(int n) 327 | { 328 | String *p; 329 | 330 | p = allocstring(n); 331 | inslist(&stringlist, stringlist.nused, p); 332 | return p; 333 | } 334 | 335 | Addr* 336 | newaddr(void) 337 | { 338 | Addr *p; 339 | 340 | p = emalloc(sizeof(Addr)); 341 | inslist(&addrlist, addrlist.nused, p); 342 | return p; 343 | } 344 | 345 | void 346 | freecmd(void) 347 | { 348 | int i; 349 | 350 | while(cmdlist.nused > 0) 351 | free(cmdlist.u.ucharptr[--cmdlist.nused]); 352 | while(addrlist.nused > 0) 353 | free(addrlist.u.ucharptr[--addrlist.nused]); 354 | while(stringlist.nused>0){ 355 | i = --stringlist.nused; 356 | freestring(stringlist.u.stringptr[i]); 357 | } 358 | } 359 | 360 | void 361 | okdelim(int c) 362 | { 363 | if(c=='\\' || ('a'<=c && c<='z') 364 | || ('A'<=c && c<='Z') || ('0'<=c && c<='9')) 365 | editerror("bad delimiter %c\n", c); 366 | } 367 | 368 | void 369 | atnl(void) 370 | { 371 | int c; 372 | 373 | cmdskipbl(); 374 | c = getch(); 375 | if(c != '\n') 376 | editerror("newline expected (saw %C)", c); 377 | } 378 | 379 | void 380 | Straddc(String *s, int c) 381 | { 382 | if(s->n+1 >= s->nalloc){ 383 | s->nalloc += 10; 384 | s->r = erealloc(s->r, s->nalloc*sizeof(Rune)); 385 | } 386 | s->r[s->n++] = c; 387 | s->r[s->n] = '\0'; 388 | } 389 | 390 | void 391 | getrhs(String *s, int delim, int cmd) 392 | { 393 | int c; 394 | 395 | while((c = getch())>0 && c!=delim && c!='\n'){ 396 | if(c == '\\'){ 397 | if((c=getch()) <= 0) 398 | error("bad right hand side"); 399 | if(c == '\n'){ 400 | ungetch(); 401 | c='\\'; 402 | }else if(c == 'n') 403 | c='\n'; 404 | else if(c!=delim && (cmd=='s' || c!='\\')) /* s does its own */ 405 | Straddc(s, '\\'); 406 | } 407 | Straddc(s, c); 408 | } 409 | ungetch(); /* let client read whether delimiter, '\n' or whatever */ 410 | } 411 | 412 | String * 413 | collecttoken(char *end) 414 | { 415 | String *s = newstring(0); 416 | int c; 417 | 418 | while((c=nextc())==' ' || c=='\t') 419 | Straddc(s, getch()); /* blanks significant for getname() */ 420 | while((c=getch())>0 && utfrune(end, c)==0) 421 | Straddc(s, c); 422 | if(c != '\n') 423 | atnl(); 424 | return s; 425 | } 426 | 427 | String * 428 | collecttext(void) 429 | { 430 | String *s; 431 | int begline, i, c, delim; 432 | 433 | s = newstring(0); 434 | if(cmdskipbl()=='\n'){ 435 | getch(); 436 | i = 0; 437 | do{ 438 | begline = i; 439 | while((c = getch())>0 && c!='\n') 440 | i++, Straddc(s, c); 441 | i++, Straddc(s, '\n'); 442 | if(c < 0) 443 | goto Return; 444 | }while(s->r[begline]!='.' || s->r[begline+1]!='\n'); 445 | s->r[s->n-2] = '\0'; 446 | s->n -= 2; 447 | }else{ 448 | okdelim(delim = getch()); 449 | getrhs(s, delim, 'a'); 450 | if(nextc()==delim) 451 | getch(); 452 | atnl(); 453 | } 454 | Return: 455 | return s; 456 | } 457 | 458 | int 459 | cmdlookup(int c) 460 | { 461 | int i; 462 | 463 | for(i=0; cmdtab[i].cmdc; i++) 464 | if(cmdtab[i].cmdc == c) 465 | return i; 466 | return -1; 467 | } 468 | 469 | Cmd* 470 | parsecmd(int nest) 471 | { 472 | int i, c; 473 | struct cmdtab *ct; 474 | Cmd *cp, *ncp; 475 | Cmd cmd; 476 | 477 | cmd.next = cmd.u.cmd = 0; 478 | cmd.re = 0; 479 | cmd.flag = cmd.num = 0; 480 | cmd.addr = compoundaddr(); 481 | if(cmdskipbl() == -1) 482 | return 0; 483 | if((c=getch())==-1) 484 | return 0; 485 | cmd.cmdc = c; 486 | if(cmd.cmdc=='c' && nextc()=='d'){ /* sleazy two-character case */ 487 | getch(); /* the 'd' */ 488 | cmd.cmdc='c'|0x100; 489 | } 490 | i = cmdlookup(cmd.cmdc); 491 | if(i >= 0){ 492 | if(cmd.cmdc == '\n') 493 | goto Return; /* let nl_cmd work it all out */ 494 | ct = &cmdtab[i]; 495 | if(ct->defaddr==aNo && cmd.addr) 496 | editerror("command takes no address"); 497 | if(ct->count) 498 | cmd.num = getnum(ct->count); 499 | if(ct->regexp){ 500 | /* x without pattern -> .*\n, indicated by cmd.re==0 */ 501 | /* X without pattern is all files */ 502 | if((ct->cmdc!='x' && ct->cmdc!='X') || 503 | ((c = nextc())!=' ' && c!='\t' && c!='\n')){ 504 | cmdskipbl(); 505 | if((c = getch())=='\n' || c<0) 506 | editerror("no address"); 507 | okdelim(c); 508 | cmd.re = getregexp(c); 509 | if(ct->cmdc == 's'){ 510 | cmd.u.text = newstring(0); 511 | getrhs(cmd.u.text, c, 's'); 512 | if(nextc() == c){ 513 | getch(); 514 | if(nextc() == 'g') 515 | cmd.flag = getch(); 516 | } 517 | 518 | } 519 | } 520 | } 521 | if(ct->addr && (cmd.u.mtaddr=simpleaddr())==0) 522 | editerror("bad address"); 523 | if(ct->defcmd){ 524 | if(cmdskipbl() == '\n'){ 525 | getch(); 526 | cmd.u.cmd = newcmd(); 527 | cmd.u.cmd->cmdc = ct->defcmd; 528 | }else if((cmd.u.cmd = parsecmd(nest))==0) 529 | error("defcmd"); 530 | }else if(ct->text) 531 | cmd.u.text = collecttext(); 532 | else if(ct->token) 533 | cmd.u.text = collecttoken(ct->token); 534 | else 535 | atnl(); 536 | }else 537 | switch(cmd.cmdc){ 538 | case '{': 539 | cp = 0; 540 | do{ 541 | if(cmdskipbl()=='\n') 542 | getch(); 543 | ncp = parsecmd(nest+1); 544 | if(cp) 545 | cp->next = ncp; 546 | else 547 | cmd.u.cmd = ncp; 548 | }while(cp = ncp); 549 | break; 550 | case '}': 551 | atnl(); 552 | if(nest==0) 553 | editerror("right brace with no left brace"); 554 | return 0; 555 | default: 556 | editerror("unknown command %c", cmd.cmdc); 557 | } 558 | Return: 559 | cp = newcmd(); 560 | *cp = cmd; 561 | return cp; 562 | } 563 | 564 | String* 565 | getregexp(int delim) 566 | { 567 | String *buf, *r; 568 | int i, c; 569 | 570 | buf = allocstring(0); 571 | for(i=0; ; i++){ 572 | if((c = getch())=='\\'){ 573 | if(nextc()==delim) 574 | c = getch(); 575 | else if(nextc()=='\\'){ 576 | Straddc(buf, c); 577 | c = getch(); 578 | } 579 | }else if(c==delim || c=='\n') 580 | break; 581 | if(i >= RBUFSIZE) 582 | editerror("regular expression too long"); 583 | Straddc(buf, c); 584 | } 585 | if(c!=delim && c) 586 | ungetch(); 587 | if(buf->n > 0){ 588 | patset = TRUE; 589 | freestring(lastpat); 590 | lastpat = buf; 591 | }else 592 | freestring(buf); 593 | if(lastpat->n == 0) 594 | editerror("no regular expression defined"); 595 | r = newstring(lastpat->n); 596 | runemove(r->r, lastpat->r, lastpat->n); /* newstring put \0 at end */ 597 | return r; 598 | } 599 | 600 | Addr * 601 | simpleaddr(void) 602 | { 603 | Addr addr; 604 | Addr *ap, *nap; 605 | 606 | addr.num = 0; 607 | addr.next = 0; 608 | addr.u.left = 0; 609 | switch(cmdskipbl()){ 610 | case '#': 611 | addr.type = getch(); 612 | addr.num = getnum(1); 613 | break; 614 | case '0': case '1': case '2': case '3': case '4': 615 | case '5': case '6': case '7': case '8': case '9': 616 | addr.num = getnum(1); 617 | addr.type='l'; 618 | break; 619 | case '/': case '?': case '"': 620 | addr.u.re = getregexp(addr.type = getch()); 621 | break; 622 | case '.': 623 | case '$': 624 | case '+': 625 | case '-': 626 | case '\'': 627 | addr.type = getch(); 628 | break; 629 | default: 630 | return 0; 631 | } 632 | if(addr.next = simpleaddr()) 633 | switch(addr.next->type){ 634 | case '.': 635 | case '$': 636 | case '\'': 637 | if(addr.type!='"') 638 | case '"': 639 | editerror("bad address syntax"); 640 | break; 641 | case 'l': 642 | case '#': 643 | if(addr.type=='"') 644 | break; 645 | /* fall through */ 646 | case '/': 647 | case '?': 648 | if(addr.type!='+' && addr.type!='-'){ 649 | /* insert the missing '+' */ 650 | nap = newaddr(); 651 | nap->type='+'; 652 | nap->next = addr.next; 653 | addr.next = nap; 654 | } 655 | break; 656 | case '+': 657 | case '-': 658 | break; 659 | default: 660 | error("simpleaddr"); 661 | } 662 | ap = newaddr(); 663 | *ap = addr; 664 | return ap; 665 | } 666 | 667 | Addr * 668 | compoundaddr(void) 669 | { 670 | Addr addr; 671 | Addr *ap, *next; 672 | 673 | addr.u.left = simpleaddr(); 674 | if((addr.type = cmdskipbl())!=',' && addr.type!=';') 675 | return addr.u.left; 676 | getch(); 677 | next = addr.next = compoundaddr(); 678 | if(next && (next->type==',' || next->type==';') && next->u.left==0) 679 | editerror("bad address syntax"); 680 | ap = newaddr(); 681 | *ap = addr; 682 | return ap; 683 | } 684 | -------------------------------------------------------------------------------- /acme/edit.h: -------------------------------------------------------------------------------- 1 | /*#pragma varargck argpos editerror 1*/ 2 | 3 | typedef struct Addr Addr; 4 | typedef struct Address Address; 5 | typedef struct Cmd Cmd; 6 | typedef struct List List; 7 | typedef struct String String; 8 | 9 | struct String 10 | { 11 | int n; /* excludes NUL */ 12 | Rune *r; /* includes NUL */ 13 | int nalloc; 14 | }; 15 | 16 | struct Addr 17 | { 18 | char type; /* # (char addr), l (line addr), / ? . $ + - , ; */ 19 | union{ 20 | String *re; 21 | Addr *left; /* left side of , and ; */ 22 | } u; 23 | ulong num; 24 | Addr *next; /* or right side of , and ; */ 25 | }; 26 | 27 | struct Address 28 | { 29 | Range r; 30 | File *f; 31 | }; 32 | 33 | struct Cmd 34 | { 35 | Addr *addr; /* address (range of text) */ 36 | String *re; /* regular expression for e.g. 'x' */ 37 | union{ 38 | Cmd *cmd; /* target of x, g, {, etc. */ 39 | String *text; /* text of a, c, i; rhs of s */ 40 | Addr *mtaddr; /* address for m, t */ 41 | } u; 42 | Cmd *next; /* pointer to next element in {} */ 43 | short num; 44 | ushort flag; /* whatever */ 45 | ushort cmdc; /* command character; 'x' etc. */ 46 | }; 47 | 48 | extern struct cmdtab{ 49 | ushort cmdc; /* command character */ 50 | uchar text; /* takes a textual argument? */ 51 | uchar regexp; /* takes a regular expression? */ 52 | uchar addr; /* takes an address (m or t)? */ 53 | uchar defcmd; /* default command; 0==>none */ 54 | uchar defaddr; /* default address */ 55 | uchar count; /* takes a count e.g. s2/// */ 56 | char *token; /* takes text terminated by one of these */ 57 | int (*fn)(Text*, Cmd*); /* function to call with parse tree */ 58 | }cmdtab[]; 59 | 60 | #define INCR 25 /* delta when growing list */ 61 | 62 | struct List /* code depends on a long being able to hold a pointer */ 63 | { 64 | int nalloc; 65 | int nused; 66 | union{ 67 | void *listptr; 68 | void* *ptr; 69 | uchar* *ucharptr; 70 | String* *stringptr; 71 | } u; 72 | }; 73 | 74 | enum Defaddr{ /* default addresses */ 75 | aNo, 76 | aDot, 77 | aAll 78 | }; 79 | 80 | int nl_cmd(Text*, Cmd*), a_cmd(Text*, Cmd*), b_cmd(Text*, Cmd*); 81 | int c_cmd(Text*, Cmd*), d_cmd(Text*, Cmd*); 82 | int B_cmd(Text*, Cmd*), D_cmd(Text*, Cmd*), e_cmd(Text*, Cmd*); 83 | int f_cmd(Text*, Cmd*), g_cmd(Text*, Cmd*), i_cmd(Text*, Cmd*); 84 | int k_cmd(Text*, Cmd*), m_cmd(Text*, Cmd*), n_cmd(Text*, Cmd*); 85 | int p_cmd(Text*, Cmd*); 86 | int s_cmd(Text*, Cmd*), u_cmd(Text*, Cmd*), w_cmd(Text*, Cmd*); 87 | int x_cmd(Text*, Cmd*), X_cmd(Text*, Cmd*), pipe_cmd(Text*, Cmd*); 88 | int eq_cmd(Text*, Cmd*); 89 | 90 | String *allocstring(int); 91 | void freestring(String*); 92 | String *getregexp(int); 93 | Addr *newaddr(void); 94 | Address cmdaddress(Addr*, Address, int); 95 | int cmdexec(Text*, Cmd*); 96 | void editerror(char*, ...); 97 | int cmdlookup(int); 98 | void resetxec(void); 99 | void Straddc(String*, int); 100 | -------------------------------------------------------------------------------- /acme/elog.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "dat.h" 12 | #include "fns.h" 13 | #include "edit.h" 14 | 15 | static char Wsequence[] = "warning: changes out of sequence\n"; 16 | static int warned = FALSE; 17 | 18 | /* 19 | * Log of changes made by editing commands. Three reasons for this: 20 | * 1) We want addresses in commands to apply to old file, not file-in-change. 21 | * 2) It's difficult to track changes correctly as things move, e.g. ,x m$ 22 | * 3) This gives an opportunity to optimize by merging adjacent changes. 23 | * It's a little bit like the Undo/Redo log in Files, but Point 3) argues for a 24 | * separate implementation. To do this well, we use Replace as well as 25 | * Insert and Delete 26 | */ 27 | 28 | typedef struct Buflog Buflog; 29 | struct Buflog 30 | { 31 | short type; /* Replace, Filename */ 32 | uint q0; /* location of change (unused in f) */ 33 | uint nd; /* # runes to delete */ 34 | uint nr; /* # runes in string or file name */ 35 | }; 36 | 37 | enum 38 | { 39 | Buflogsize = sizeof(Buflog)/sizeof(Rune) 40 | }; 41 | 42 | /* 43 | * Minstring shouldn't be very big or we will do lots of I/O for small changes. 44 | * Maxstring is RBUFSIZE so we can fbufalloc() once and not realloc elog.r. 45 | */ 46 | enum 47 | { 48 | Minstring = 16, /* distance beneath which we merge changes */ 49 | Maxstring = RBUFSIZE /* maximum length of change we will merge into one */ 50 | }; 51 | 52 | void 53 | eloginit(File *f) 54 | { 55 | if(f->elog.type != Empty) 56 | return; 57 | f->elog.type = Null; 58 | if(f->elogbuf == nil) 59 | f->elogbuf = emalloc(sizeof(Buffer)); 60 | if(f->elog.r == nil) 61 | f->elog.r = fbufalloc(); 62 | bufreset(f->elogbuf); 63 | } 64 | 65 | void 66 | elogclose(File *f) 67 | { 68 | if(f->elogbuf){ 69 | bufclose(f->elogbuf); 70 | free(f->elogbuf); 71 | f->elogbuf = nil; 72 | } 73 | } 74 | 75 | void 76 | elogreset(File *f) 77 | { 78 | f->elog.type = Null; 79 | f->elog.nd = 0; 80 | f->elog.nr = 0; 81 | } 82 | 83 | void 84 | elogterm(File *f) 85 | { 86 | elogreset(f); 87 | if(f->elogbuf) 88 | bufreset(f->elogbuf); 89 | f->elog.type = Empty; 90 | fbuffree(f->elog.r); 91 | f->elog.r = nil; 92 | warned = FALSE; 93 | } 94 | 95 | void 96 | elogflush(File *f) 97 | { 98 | Buflog b; 99 | 100 | b.type = f->elog.type; 101 | b.q0 = f->elog.q0; 102 | b.nd = f->elog.nd; 103 | b.nr = f->elog.nr; 104 | switch(f->elog.type){ 105 | default: 106 | warning(nil, "unknown elog type 0x%ux\n", f->elog.type); 107 | break; 108 | case Null: 109 | break; 110 | case Insert: 111 | case Replace: 112 | if(f->elog.nr > 0) 113 | bufinsert(f->elogbuf, f->elogbuf->nc, f->elog.r, f->elog.nr); 114 | /* fall through */ 115 | case Delete: 116 | bufinsert(f->elogbuf, f->elogbuf->nc, (Rune*)&b, Buflogsize); 117 | break; 118 | } 119 | elogreset(f); 120 | } 121 | 122 | void 123 | elogreplace(File *f, int q0, int q1, Rune *r, int nr) 124 | { 125 | uint gap; 126 | 127 | if(q0==q1 && nr==0) 128 | return; 129 | eloginit(f); 130 | if(f->elog.type!=Null && q0elog.q0){ 131 | if(warned++ == 0) 132 | warning(nil, Wsequence); 133 | elogflush(f); 134 | } 135 | /* try to merge with previous */ 136 | gap = q0 - (f->elog.q0+f->elog.nd); /* gap between previous and this */ 137 | if(f->elog.type==Replace && f->elog.nr+gap+nr 0){ 140 | bufread(&f->b, f->elog.q0+f->elog.nd, f->elog.r+f->elog.nr, gap); 141 | f->elog.nr += gap; 142 | } 143 | f->elog.nd += gap + q1-q0; 144 | runemove(f->elog.r+f->elog.nr, r, nr); 145 | f->elog.nr += nr; 146 | return; 147 | } 148 | } 149 | elogflush(f); 150 | f->elog.type = Replace; 151 | f->elog.q0 = q0; 152 | f->elog.nd = q1-q0; 153 | f->elog.nr = nr; 154 | if(nr > RBUFSIZE) 155 | editerror("internal error: replacement string too large(%d)", nr); 156 | runemove(f->elog.r, r, nr); 157 | } 158 | 159 | void 160 | eloginsert(File *f, int q0, Rune *r, int nr) 161 | { 162 | int n; 163 | 164 | if(nr == 0) 165 | return; 166 | eloginit(f); 167 | if(f->elog.type!=Null && q0elog.q0){ 168 | if(warned++ == 0) 169 | warning(nil, Wsequence); 170 | elogflush(f); 171 | } 172 | /* try to merge with previous */ 173 | if(f->elog.type==Insert && q0==f->elog.q0 && f->elog.nr+nrelog.r+f->elog.nr, r, nr); 175 | f->elog.nr += nr; 176 | return; 177 | } 178 | while(nr > 0){ 179 | elogflush(f); 180 | f->elog.type = Insert; 181 | f->elog.q0 = q0; 182 | n = nr; 183 | if(n > RBUFSIZE) 184 | n = RBUFSIZE; 185 | f->elog.nr = n; 186 | runemove(f->elog.r, r, n); 187 | r += n; 188 | nr -= n; 189 | } 190 | } 191 | 192 | void 193 | elogdelete(File *f, int q0, int q1) 194 | { 195 | if(q0 == q1) 196 | return; 197 | eloginit(f); 198 | if(f->elog.type!=Null && q0elog.q0+f->elog.nd){ 199 | if(warned++ == 0) 200 | warning(nil, Wsequence); 201 | elogflush(f); 202 | } 203 | /* try to merge with previous */ 204 | if(f->elog.type==Delete && f->elog.q0+f->elog.nd==q0){ 205 | f->elog.nd += q1-q0; 206 | return; 207 | } 208 | elogflush(f); 209 | f->elog.type = Delete; 210 | f->elog.q0 = q0; 211 | f->elog.nd = q1-q0; 212 | } 213 | 214 | #define tracelog 0 215 | void 216 | elogapply(File *f) 217 | { 218 | Buflog b; 219 | Rune *buf; 220 | uint i, n, up, mod; 221 | uint tq0, tq1; 222 | Buffer *log; 223 | Text *t; 224 | int owner; 225 | 226 | elogflush(f); 227 | log = f->elogbuf; 228 | t = f->curtext; 229 | 230 | buf = fbufalloc(); 231 | mod = FALSE; 232 | 233 | owner = 0; 234 | if(t->w){ 235 | owner = t->w->owner; 236 | if(owner == 0) 237 | t->w->owner = 'E'; 238 | } 239 | 240 | /* 241 | * The edit commands have already updated the selection in t->q0, t->q1, 242 | * but using coordinates relative to the unmodified buffer. As we apply the log, 243 | * we have to update the coordinates to be relative to the modified buffer. 244 | * Textinsert and textdelete will do this for us; our only work is to apply the 245 | * convention that an insertion at t->q0==t->q1 is intended to select the 246 | * inserted text. 247 | */ 248 | 249 | /* 250 | * We constrain the addresses in here (with textconstrain()) because 251 | * overlapping changes will generate bogus addresses. We will warn 252 | * about changes out of sequence but proceed anyway; here we must 253 | * keep things in range. 254 | */ 255 | 256 | while(log->nc > 0){ 257 | up = log->nc-Buflogsize; 258 | bufread(log, up, (Rune*)&b, Buflogsize); 259 | switch(b.type){ 260 | default: 261 | fprint(2, "elogapply: 0x%ux\n", b.type); 262 | abort(); 263 | break; 264 | 265 | case Replace: 266 | if(tracelog) 267 | warning(nil, "elog replace %d %d (%d %d)\n", 268 | b.q0, b.q0+b.nd, t->q0, t->q1); 269 | if(!mod){ 270 | mod = TRUE; 271 | filemark(f); 272 | } 273 | textconstrain(t, b.q0, b.q0+b.nd, &tq0, &tq1); 274 | textdelete(t, tq0, tq1, TRUE); 275 | up -= b.nr; 276 | for(i=0; i RBUFSIZE) 279 | n = RBUFSIZE; 280 | bufread(log, up+i, buf, n); 281 | textinsert(t, tq0+i, buf, n, TRUE); 282 | } 283 | if(t->q0 == b.q0 && t->q1 == b.q0) 284 | t->q1 += b.nr; 285 | break; 286 | 287 | case Delete: 288 | if(tracelog) 289 | warning(nil, "elog delete %d %d (%d %d)\n", 290 | b.q0, b.q0+b.nd, t->q0, t->q1); 291 | if(!mod){ 292 | mod = TRUE; 293 | filemark(f); 294 | } 295 | textconstrain(t, b.q0, b.q0+b.nd, &tq0, &tq1); 296 | textdelete(t, tq0, tq1, TRUE); 297 | break; 298 | 299 | case Insert: 300 | if(tracelog) 301 | warning(nil, "elog insert %d %d (%d %d)\n", 302 | b.q0, b.q0+b.nr, t->q0, t->q1); 303 | if(!mod){ 304 | mod = TRUE; 305 | filemark(f); 306 | } 307 | textconstrain(t, b.q0, b.q0, &tq0, &tq1); 308 | up -= b.nr; 309 | for(i=0; i RBUFSIZE) 312 | n = RBUFSIZE; 313 | bufread(log, up+i, buf, n); 314 | textinsert(t, tq0+i, buf, n, TRUE); 315 | } 316 | if(t->q0 == b.q0 && t->q1 == b.q0) 317 | t->q1 += b.nr; 318 | break; 319 | 320 | /* case Filename: 321 | f->seq = u.seq; 322 | fileunsetname(f, epsilon); 323 | f->mod = u.mod; 324 | up -= u.n; 325 | free(f->name); 326 | if(u.n == 0) 327 | f->name = nil; 328 | else 329 | f->name = runemalloc(u.n); 330 | bufread(delta, up, f->name, u.n); 331 | f->nname = u.n; 332 | break; 333 | */ 334 | } 335 | bufdelete(log, up, log->nc); 336 | } 337 | fbuffree(buf); 338 | elogterm(f); 339 | 340 | /* 341 | * Bad addresses will cause bufload to crash, so double check. 342 | * If changes were out of order, we expect problems so don't complain further. 343 | */ 344 | if(t->q0 > f->b.nc || t->q1 > f->b.nc || t->q0 > t->q1){ 345 | if(!warned) 346 | warning(nil, "elogapply: can't happen %d %d %d\n", t->q0, t->q1, f->b.nc); 347 | t->q1 = min(t->q1, f->b.nc); 348 | t->q0 = min(t->q0, t->q1); 349 | } 350 | 351 | if(t->w) 352 | t->w->owner = owner; 353 | } 354 | -------------------------------------------------------------------------------- /acme/file.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "dat.h" 12 | #include "fns.h" 13 | 14 | /* 15 | * Structure of Undo list: 16 | * The Undo structure follows any associated data, so the list 17 | * can be read backwards: read the structure, then read whatever 18 | * data is associated (insert string, file name) and precedes it. 19 | * The structure includes the previous value of the modify bit 20 | * and a sequence number; successive Undo structures with the 21 | * same sequence number represent simultaneous changes. 22 | */ 23 | 24 | typedef struct Undo Undo; 25 | struct Undo 26 | { 27 | short type; /* Delete, Insert, Filename */ 28 | short mod; /* modify bit */ 29 | uint seq; /* sequence number */ 30 | uint p0; /* location of change (unused in f) */ 31 | uint n; /* # runes in string or file name */ 32 | }; 33 | 34 | enum 35 | { 36 | Undosize = sizeof(Undo)/sizeof(Rune) 37 | }; 38 | 39 | File* 40 | fileaddtext(File *f, Text *t) 41 | { 42 | if(f == nil){ 43 | f = emalloc(sizeof(File)); 44 | f->unread = TRUE; 45 | } 46 | f->text = realloc(f->text, (f->ntext+1)*sizeof(Text*)); 47 | f->text[f->ntext++] = t; 48 | f->curtext = t; 49 | return f; 50 | } 51 | 52 | void 53 | filedeltext(File *f, Text *t) 54 | { 55 | int i; 56 | 57 | for(i=0; intext; i++) 58 | if(f->text[i] == t) 59 | goto Found; 60 | error("can't find text in filedeltext"); 61 | 62 | Found: 63 | f->ntext--; 64 | if(f->ntext == 0){ 65 | fileclose(f); 66 | return; 67 | } 68 | memmove(f->text+i, f->text+i+1, (f->ntext-i)*sizeof(Text*)); 69 | if(f->curtext == t) 70 | f->curtext = f->text[0]; 71 | } 72 | 73 | void 74 | fileinsert(File *f, uint p0, Rune *s, uint ns) 75 | { 76 | if(p0 > f->b.nc) 77 | error("internal error: fileinsert"); 78 | if(f->seq > 0) 79 | fileuninsert(f, &f->delta, p0, ns); 80 | bufinsert(&f->b, p0, s, ns); 81 | if(ns) 82 | f->mod = TRUE; 83 | } 84 | 85 | void 86 | fileuninsert(File *f, Buffer *delta, uint p0, uint ns) 87 | { 88 | Undo u; 89 | 90 | /* undo an insertion by deleting */ 91 | u.type = Delete; 92 | u.mod = f->mod; 93 | u.seq = f->seq; 94 | u.p0 = p0; 95 | u.n = ns; 96 | bufinsert(delta, delta->nc, (Rune*)&u, Undosize); 97 | } 98 | 99 | void 100 | filedelete(File *f, uint p0, uint p1) 101 | { 102 | if(!(p0<=p1 && p0<=f->b.nc && p1<=f->b.nc)) 103 | error("internal error: filedelete"); 104 | if(f->seq > 0) 105 | fileundelete(f, &f->delta, p0, p1); 106 | bufdelete(&f->b, p0, p1); 107 | if(p1 > p0) 108 | f->mod = TRUE; 109 | } 110 | 111 | void 112 | fileundelete(File *f, Buffer *delta, uint p0, uint p1) 113 | { 114 | Undo u; 115 | Rune *buf; 116 | uint i, n; 117 | 118 | /* undo a deletion by inserting */ 119 | u.type = Insert; 120 | u.mod = f->mod; 121 | u.seq = f->seq; 122 | u.p0 = p0; 123 | u.n = p1-p0; 124 | buf = fbufalloc(); 125 | for(i=p0; i RBUFSIZE) 128 | n = RBUFSIZE; 129 | bufread(&f->b, i, buf, n); 130 | bufinsert(delta, delta->nc, buf, n); 131 | } 132 | fbuffree(buf); 133 | bufinsert(delta, delta->nc, (Rune*)&u, Undosize); 134 | 135 | } 136 | 137 | void 138 | filesetname(File *f, Rune *name, int n) 139 | { 140 | if(f->seq > 0) 141 | fileunsetname(f, &f->delta); 142 | free(f->name); 143 | f->name = runemalloc(n); 144 | runemove(f->name, name, n); 145 | f->nname = n; 146 | f->unread = TRUE; 147 | } 148 | 149 | void 150 | fileunsetname(File *f, Buffer *delta) 151 | { 152 | Undo u; 153 | 154 | /* undo a file name change by restoring old name */ 155 | u.type = Filename; 156 | u.mod = f->mod; 157 | u.seq = f->seq; 158 | u.p0 = 0; /* unused */ 159 | u.n = f->nname; 160 | if(f->nname) 161 | bufinsert(delta, delta->nc, f->name, f->nname); 162 | bufinsert(delta, delta->nc, (Rune*)&u, Undosize); 163 | } 164 | 165 | uint 166 | fileload(File *f, uint p0, int fd, int *nulls) 167 | { 168 | if(f->seq > 0) 169 | error("undo in file.load unimplemented"); 170 | return bufload(&f->b, p0, fd, nulls); 171 | } 172 | 173 | /* return sequence number of pending redo */ 174 | uint 175 | fileredoseq(File *f) 176 | { 177 | Undo u; 178 | Buffer *delta; 179 | 180 | delta = &f->epsilon; 181 | if(delta->nc == 0) 182 | return 0; 183 | bufread(delta, delta->nc-Undosize, (Rune*)&u, Undosize); 184 | return u.seq; 185 | } 186 | 187 | void 188 | fileundo(File *f, int isundo, uint *q0p, uint *q1p) 189 | { 190 | Undo u; 191 | Rune *buf; 192 | uint i, j, n, up; 193 | uint stop; 194 | Buffer *delta, *epsilon; 195 | 196 | if(isundo){ 197 | /* undo; reverse delta onto epsilon, seq decreases */ 198 | delta = &f->delta; 199 | epsilon = &f->epsilon; 200 | stop = f->seq; 201 | }else{ 202 | /* redo; reverse epsilon onto delta, seq increases */ 203 | delta = &f->epsilon; 204 | epsilon = &f->delta; 205 | stop = 0; /* don't know yet */ 206 | } 207 | 208 | buf = fbufalloc(); 209 | while(delta->nc > 0){ 210 | up = delta->nc-Undosize; 211 | bufread(delta, up, (Rune*)&u, Undosize); 212 | if(isundo){ 213 | if(u.seq < stop){ 214 | f->seq = u.seq; 215 | goto Return; 216 | } 217 | }else{ 218 | if(stop == 0) 219 | stop = u.seq; 220 | if(u.seq > stop) 221 | goto Return; 222 | } 223 | switch(u.type){ 224 | default: 225 | fprint(2, "undo: 0x%ux\n", u.type); 226 | abort(); 227 | break; 228 | 229 | case Delete: 230 | f->seq = u.seq; 231 | fileundelete(f, epsilon, u.p0, u.p0+u.n); 232 | f->mod = u.mod; 233 | bufdelete(&f->b, u.p0, u.p0+u.n); 234 | for(j=0; jntext; j++) 235 | textdelete(f->text[j], u.p0, u.p0+u.n, FALSE); 236 | *q0p = u.p0; 237 | *q1p = u.p0; 238 | break; 239 | 240 | case Insert: 241 | f->seq = u.seq; 242 | fileuninsert(f, epsilon, u.p0, u.n); 243 | f->mod = u.mod; 244 | up -= u.n; 245 | for(i=0; i RBUFSIZE) 248 | n = RBUFSIZE; 249 | bufread(delta, up+i, buf, n); 250 | bufinsert(&f->b, u.p0+i, buf, n); 251 | for(j=0; jntext; j++) 252 | textinsert(f->text[j], u.p0+i, buf, n, FALSE); 253 | } 254 | *q0p = u.p0; 255 | *q1p = u.p0+u.n; 256 | break; 257 | 258 | case Filename: 259 | f->seq = u.seq; 260 | fileunsetname(f, epsilon); 261 | f->mod = u.mod; 262 | up -= u.n; 263 | free(f->name); 264 | if(u.n == 0) 265 | f->name = nil; 266 | else 267 | f->name = runemalloc(u.n); 268 | bufread(delta, up, f->name, u.n); 269 | f->nname = u.n; 270 | break; 271 | } 272 | bufdelete(delta, up, delta->nc); 273 | } 274 | if(isundo) 275 | f->seq = 0; 276 | Return: 277 | fbuffree(buf); 278 | } 279 | 280 | void 281 | filereset(File *f) 282 | { 283 | bufreset(&f->delta); 284 | bufreset(&f->epsilon); 285 | f->seq = 0; 286 | } 287 | 288 | void 289 | fileclose(File *f) 290 | { 291 | free(f->name); 292 | f->nname = 0; 293 | f->name = nil; 294 | free(f->text); 295 | f->ntext = 0; 296 | f->text = nil; 297 | bufclose(&f->b); 298 | bufclose(&f->delta); 299 | bufclose(&f->epsilon); 300 | elogclose(f); 301 | free(f); 302 | } 303 | 304 | void 305 | filemark(File *f) 306 | { 307 | if(f->epsilon.nc) 308 | bufdelete(&f->epsilon, 0, f->epsilon.nc); 309 | f->seq = seq; 310 | } 311 | -------------------------------------------------------------------------------- /acme/fns.h: -------------------------------------------------------------------------------- 1 | /* 2 | #pragma varargck argpos warning 2 3 | #pragma varargck argpos warningew 2 4 | */ 5 | 6 | void warning(Mntdir*, char*, ...); 7 | void warningew(Window*, Mntdir*, char*, ...); 8 | 9 | #define fbufalloc() emalloc(BUFSIZE) 10 | #define fbuffree(x) free(x) 11 | 12 | void plumblook(Plumbmsg *m); 13 | void plumbshow(Plumbmsg*m); 14 | void acmeputsnarf(void); 15 | void acmegetsnarf(void); 16 | int tempfile(void); 17 | void scrlresize(void); 18 | Font* getfont(int, int, char*); 19 | char* getarg(Text*, int, int, Rune**, int*); 20 | char* getbytearg(Text*, int, int, char**); 21 | void new(Text*, Text*, Text*, int, int, Rune*, int); 22 | void undo(Text*, Text*, Text*, int, int, Rune*, int); 23 | void scrsleep(uint); 24 | void savemouse(Window*); 25 | int restoremouse(Window*); 26 | void clearmouse(void); 27 | void allwindows(void(*)(Window*, void*), void*); 28 | uint loadfile(int, uint, int*, int(*)(void*, uint, Rune*, int), void*); 29 | void movetodel(Window*); 30 | 31 | Window* errorwin(Mntdir*, int); 32 | Window* errorwinforwin(Window*); 33 | Runestr cleanrname(Runestr); 34 | void run(Window*, char*, Rune*, int, int, char*, char*, int); 35 | void fsysclose(void); 36 | void setcurtext(Text*, int); 37 | int isfilec(Rune); 38 | void rxinit(void); 39 | int rxnull(void); 40 | Runestr dirname(Text*, Rune*, int); 41 | void error(char*); 42 | void cvttorunes(char*, int, Rune*, int*, int*, int*); 43 | void* tmalloc(uint); 44 | void tfree(void); 45 | void killprocs(void); 46 | void killtasks(void); 47 | int runeeq(Rune*, uint, Rune*, uint); 48 | int ALEF_tid(void); 49 | void iconinit(void); 50 | Timer* timerstart(int); 51 | void timerstop(Timer*); 52 | void timercancel(Timer*); 53 | void timerinit(void); 54 | void cut(Text*, Text*, Text*, int, int, Rune*, int); 55 | void paste(Text*, Text*, Text*, int, int, Rune*, int); 56 | void get(Text*, Text*, Text*, int, int, Rune*, int); 57 | void put(Text*, Text*, Text*, int, int, Rune*, int); 58 | void putfile(File*, int, int, Rune*, int); 59 | void fontx(Text*, Text*, Text*, int, int, Rune*, int); 60 | #undef isalnum 61 | #define isalnum acmeisalnum 62 | int isalnum(Rune); 63 | void execute(Text*, uint, uint, int, Text*); 64 | int search(Text*, Rune*, uint); 65 | void look3(Text*, uint, uint, int); 66 | void editcmd(Text*, Rune*, uint); 67 | uint min(uint, uint); 68 | uint max(uint, uint); 69 | Window* lookfile(Rune*, int); 70 | Window* lookid(int, int); 71 | char* runetobyte(Rune*, int); 72 | Rune* bytetorune(char*, int*); 73 | void fsysinit(void); 74 | Mntdir* fsysmount(Rune*, int, Rune**, int); 75 | void fsysdelid(Mntdir*); 76 | void fsysincid(Mntdir*); 77 | Xfid* respond(Xfid*, Fcall*, char*); 78 | int rxcompile(Rune*); 79 | int rgetc(void*, uint); 80 | int tgetc(void*, uint); 81 | int isaddrc(int); 82 | int isregexc(int); 83 | void *emalloc(uint); 84 | void *erealloc(void*, uint); 85 | char *estrdup(char*); 86 | Range address(uint, Text*, Range, Range, void*, uint, uint, int (*)(void*, uint), int*, uint*); 87 | int rxexecute(Text*, Rune*, uint, uint, Rangeset*); 88 | int rxbexecute(Text*, uint, Rangeset*); 89 | Window* makenewwindow(Text *t); 90 | int expand(Text*, uint, uint, Expand*); 91 | Rune* skipbl(Rune*, int, int*); 92 | Rune* findbl(Rune*, int, int*); 93 | char* edittext(Window*, int, Rune*, int); 94 | void flushwarnings(void); 95 | void startplumbing(void); 96 | 97 | Runestr runestr(Rune*, uint); 98 | Range range(int, int); 99 | 100 | #define runemalloc(a) (Rune*)emalloc((a)*sizeof(Rune)) 101 | #define runerealloc(a, b) (Rune*)erealloc((a), (b)*sizeof(Rune)) 102 | #define runemove(a, b, c) memmove((a), (b), (c)*sizeof(Rune)) 103 | 104 | int ismtpt(char*); 105 | -------------------------------------------------------------------------------- /acme/fsys.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "dat.h" 12 | #include "fns.h" 13 | 14 | static int sfd; 15 | 16 | enum 17 | { 18 | Nhash = 16, 19 | DEBUG = 0 20 | }; 21 | 22 | static Fid *fids[Nhash]; 23 | 24 | Fid *newfid(int); 25 | 26 | static Xfid* fsysflush(Xfid*, Fid*); 27 | static Xfid* fsysauth(Xfid*, Fid*); 28 | static Xfid* fsysversion(Xfid*, Fid*); 29 | static Xfid* fsysattach(Xfid*, Fid*); 30 | static Xfid* fsyswalk(Xfid*, Fid*); 31 | static Xfid* fsysopen(Xfid*, Fid*); 32 | static Xfid* fsyscreate(Xfid*, Fid*); 33 | static Xfid* fsysread(Xfid*, Fid*); 34 | static Xfid* fsyswrite(Xfid*, Fid*); 35 | static Xfid* fsysclunk(Xfid*, Fid*); 36 | static Xfid* fsysremove(Xfid*, Fid*); 37 | static Xfid* fsysstat(Xfid*, Fid*); 38 | static Xfid* fsyswstat(Xfid*, Fid*); 39 | 40 | Xfid* (*fcall[Tmax])(Xfid*, Fid*); 41 | 42 | static void 43 | initfcall(void) 44 | { 45 | fcall[Tflush] = fsysflush; 46 | fcall[Tversion] = fsysversion; 47 | fcall[Tauth] = fsysauth; 48 | fcall[Tattach] = fsysattach; 49 | fcall[Twalk] = fsyswalk; 50 | fcall[Topen] = fsysopen; 51 | fcall[Tcreate] = fsyscreate; 52 | fcall[Tread] = fsysread; 53 | fcall[Twrite] = fsyswrite; 54 | fcall[Tclunk] = fsysclunk; 55 | fcall[Tremove]= fsysremove; 56 | fcall[Tstat] = fsysstat; 57 | fcall[Twstat] = fsyswstat; 58 | } 59 | 60 | char Eperm[] = "permission denied"; 61 | char Eexist[] = "file does not exist"; 62 | char Enotdir[] = "not a directory"; 63 | 64 | Dirtab dirtab[]= 65 | { 66 | { ".", QTDIR, Qdir, 0500|DMDIR }, 67 | { "acme", QTDIR, Qacme, 0500|DMDIR }, 68 | { "cons", QTFILE, Qcons, 0600 }, 69 | { "consctl", QTFILE, Qconsctl, 0000 }, 70 | { "draw", QTDIR, Qdraw, 0000|DMDIR }, /* to suppress graphics progs started in acme */ 71 | { "editout", QTFILE, Qeditout, 0200 }, 72 | { "index", QTFILE, Qindex, 0400 }, 73 | { "label", QTFILE, Qlabel, 0600 }, 74 | { "log", QTFILE, Qlog, 0400 }, 75 | { "new", QTDIR, Qnew, 0500|DMDIR }, 76 | { nil, } 77 | }; 78 | 79 | Dirtab dirtabw[]= 80 | { 81 | { ".", QTDIR, Qdir, 0500|DMDIR }, 82 | { "addr", QTFILE, QWaddr, 0600 }, 83 | { "body", QTAPPEND, QWbody, 0600|DMAPPEND }, 84 | { "ctl", QTFILE, QWctl, 0600 }, 85 | { "data", QTFILE, QWdata, 0600 }, 86 | { "editout", QTFILE, QWeditout, 0200 }, 87 | { "errors", QTFILE, QWerrors, 0200 }, 88 | { "event", QTFILE, QWevent, 0600 }, 89 | { "rdsel", QTFILE, QWrdsel, 0400 }, 90 | { "wrsel", QTFILE, QWwrsel, 0200 }, 91 | { "tag", QTAPPEND, QWtag, 0600|DMAPPEND }, 92 | { "xdata", QTFILE, QWxdata, 0600 }, 93 | { nil, } 94 | }; 95 | 96 | typedef struct Mnt Mnt; 97 | struct Mnt 98 | { 99 | QLock lk; 100 | int id; 101 | Mntdir *md; 102 | }; 103 | 104 | Mnt mnt; 105 | 106 | Xfid* respond(Xfid*, Fcall*, char*); 107 | int dostat(int, Dirtab*, uchar*, int, uint); 108 | uint getclock(void); 109 | 110 | char *user = "Wile E. Coyote"; 111 | static int closing = 0; 112 | int messagesize = Maxblock+IOHDRSZ; /* good start */ 113 | 114 | void fsysproc(void *); 115 | 116 | void 117 | fsysinit(void) 118 | { 119 | int p[2]; 120 | char *u; 121 | 122 | initfcall(); 123 | if(pipe(p) < 0) 124 | error("can't create pipe"); 125 | if(post9pservice(p[0], "acme", mtpt) < 0) 126 | error("can't post service"); 127 | sfd = p[1]; 128 | fmtinstall('F', fcallfmt); 129 | if((u = getuser()) != nil) 130 | user = estrdup(u); 131 | proccreate(fsysproc, nil, STACK); 132 | } 133 | 134 | void 135 | fsysproc(void *v) 136 | { 137 | int n; 138 | Xfid *x; 139 | Fid *f; 140 | Fcall t; 141 | uchar *buf; 142 | 143 | threadsetname("fsysproc"); 144 | 145 | USED(v); 146 | x = nil; 147 | for(;;){ 148 | buf = emalloc(messagesize+UTFmax); /* overflow for appending partial rune in xfidwrite */ 149 | n = read9pmsg(sfd, buf, messagesize); 150 | if(n <= 0){ 151 | if(closing) 152 | break; 153 | error("i/o error on server channel"); 154 | } 155 | if(x == nil){ 156 | sendp(cxfidalloc, nil); 157 | x = recvp(cxfidalloc); 158 | } 159 | x->buf = buf; 160 | if(convM2S(buf, n, &x->fcall) != n) 161 | error("convert error in convM2S"); 162 | if(DEBUG) 163 | fprint(2, "%F\n", &x->fcall); 164 | if(fcall[x->fcall.type] == nil) 165 | x = respond(x, &t, "bad fcall type"); 166 | else{ 167 | switch(x->fcall.type){ 168 | case Tversion: 169 | case Tauth: 170 | case Tflush: 171 | f = nil; 172 | break; 173 | case Tattach: 174 | f = newfid(x->fcall.fid); 175 | break; 176 | default: 177 | f = newfid(x->fcall.fid); 178 | if(!f->busy){ 179 | x->f = f; 180 | x = respond(x, &t, "fid not in use"); 181 | continue; 182 | } 183 | break; 184 | } 185 | x->f = f; 186 | x = (*fcall[x->fcall.type])(x, f); 187 | } 188 | } 189 | } 190 | 191 | Mntdir* 192 | fsysaddid(Rune *dir, int ndir, Rune **incl, int nincl) 193 | { 194 | Mntdir *m; 195 | int id; 196 | 197 | qlock(&mnt.lk); 198 | id = ++mnt.id; 199 | m = emalloc(sizeof *m); 200 | m->id = id; 201 | m->dir = dir; 202 | m->ref = 1; /* one for Command, one will be incremented in attach */ 203 | m->ndir = ndir; 204 | m->next = mnt.md; 205 | m->incl = incl; 206 | m->nincl = nincl; 207 | mnt.md = m; 208 | qunlock(&mnt.lk); 209 | return m; 210 | } 211 | 212 | void 213 | fsysincid(Mntdir *m) 214 | { 215 | qlock(&mnt.lk); 216 | m->ref++; 217 | qunlock(&mnt.lk); 218 | } 219 | 220 | void 221 | fsysdelid(Mntdir *idm) 222 | { 223 | Mntdir *m, *prev; 224 | int i; 225 | char buf[64]; 226 | 227 | if(idm == nil) 228 | return; 229 | qlock(&mnt.lk); 230 | if(--idm->ref > 0){ 231 | qunlock(&mnt.lk); 232 | return; 233 | } 234 | prev = nil; 235 | for(m=mnt.md; m; m=m->next){ 236 | if(m == idm){ 237 | if(prev) 238 | prev->next = m->next; 239 | else 240 | mnt.md = m->next; 241 | for(i=0; inincl; i++) 242 | free(m->incl[i]); 243 | free(m->incl); 244 | free(m->dir); 245 | free(m); 246 | qunlock(&mnt.lk); 247 | return; 248 | } 249 | prev = m; 250 | } 251 | qunlock(&mnt.lk); 252 | sprint(buf, "fsysdelid: can't find id %d\n", idm->id); 253 | sendp(cerr, estrdup(buf)); 254 | } 255 | 256 | /* 257 | * Called only in exec.c:/^run(), from a different FD group 258 | */ 259 | Mntdir* 260 | fsysmount(Rune *dir, int ndir, Rune **incl, int nincl) 261 | { 262 | return fsysaddid(dir, ndir, incl, nincl); 263 | } 264 | 265 | void 266 | fsysclose(void) 267 | { 268 | closing = 1; 269 | /* 270 | * apparently this is not kosher on openbsd. 271 | * perhaps because fsysproc is reading from sfd right now, 272 | * the close hangs indefinitely. 273 | close(sfd); 274 | */ 275 | } 276 | 277 | Xfid* 278 | respond(Xfid *x, Fcall *t, char *err) 279 | { 280 | int n; 281 | 282 | if(err){ 283 | t->type = Rerror; 284 | t->ename = err; 285 | }else 286 | t->type = x->fcall.type+1; 287 | t->fid = x->fcall.fid; 288 | t->tag = x->fcall.tag; 289 | if(x->buf == nil) 290 | x->buf = emalloc(messagesize); 291 | n = convS2M(t, x->buf, messagesize); 292 | if(n <= 0) 293 | error("convert error in convS2M"); 294 | if(write(sfd, x->buf, n) != n) 295 | error("write error in respond"); 296 | free(x->buf); 297 | x->buf = nil; 298 | if(DEBUG) 299 | fprint(2, "r: %F\n", t); 300 | return x; 301 | } 302 | 303 | static 304 | Xfid* 305 | fsysversion(Xfid *x, Fid *f) 306 | { 307 | Fcall t; 308 | 309 | USED(f); 310 | if(x->fcall.msize < 256) 311 | return respond(x, &t, "version: message size too small"); 312 | messagesize = x->fcall.msize; 313 | t.msize = messagesize; 314 | if(strncmp(x->fcall.version, "9P2000", 6) != 0) 315 | return respond(x, &t, "unrecognized 9P version"); 316 | t.version = "9P2000"; 317 | return respond(x, &t, nil); 318 | } 319 | 320 | static 321 | Xfid* 322 | fsysauth(Xfid *x, Fid *f) 323 | { 324 | Fcall t; 325 | 326 | USED(f); 327 | return respond(x, &t, "acme: authentication not required"); 328 | } 329 | 330 | static 331 | Xfid* 332 | fsysflush(Xfid *x, Fid *f) 333 | { 334 | USED(f); 335 | sendp(x->c, (void*)xfidflush); 336 | return nil; 337 | } 338 | 339 | static 340 | Xfid* 341 | fsysattach(Xfid *x, Fid *f) 342 | { 343 | Fcall t; 344 | int id; 345 | Mntdir *m; 346 | char buf[128]; 347 | 348 | if(strcmp(x->fcall.uname, user) != 0) 349 | return respond(x, &t, Eperm); 350 | f->busy = TRUE; 351 | f->open = FALSE; 352 | f->qid.path = Qdir; 353 | f->qid.type = QTDIR; 354 | f->qid.vers = 0; 355 | f->dir = dirtab; 356 | f->nrpart = 0; 357 | f->w = nil; 358 | t.qid = f->qid; 359 | f->mntdir = nil; 360 | id = atoi(x->fcall.aname); 361 | qlock(&mnt.lk); 362 | for(m=mnt.md; m; m=m->next) 363 | if(m->id == id){ 364 | f->mntdir = m; 365 | m->ref++; 366 | break; 367 | } 368 | if(m == nil && x->fcall.aname[0]){ 369 | snprint(buf, sizeof buf, "unknown id '%s' in attach", x->fcall.aname); 370 | sendp(cerr, estrdup(buf)); 371 | } 372 | qunlock(&mnt.lk); 373 | return respond(x, &t, nil); 374 | } 375 | 376 | static 377 | Xfid* 378 | fsyswalk(Xfid *x, Fid *f) 379 | { 380 | Fcall t; 381 | int c, i, j, id; 382 | Qid q; 383 | uchar type; 384 | ulong path; 385 | Fid *nf; 386 | Dirtab *d, *dir; 387 | Window *w; 388 | char *err; 389 | 390 | nf = nil; 391 | w = nil; 392 | if(f->open) 393 | return respond(x, &t, "walk of open file"); 394 | if(x->fcall.fid != x->fcall.newfid){ 395 | nf = newfid(x->fcall.newfid); 396 | if(nf->busy) 397 | return respond(x, &t, "newfid already in use"); 398 | nf->busy = TRUE; 399 | nf->open = FALSE; 400 | nf->mntdir = f->mntdir; 401 | if(f->mntdir) 402 | f->mntdir->ref++; 403 | nf->dir = f->dir; 404 | nf->qid = f->qid; 405 | nf->w = f->w; 406 | nf->nrpart = 0; /* not open, so must be zero */ 407 | if(nf->w) 408 | incref(&nf->w->ref); 409 | f = nf; /* walk f */ 410 | } 411 | 412 | t.nwqid = 0; 413 | err = nil; 414 | dir = nil; 415 | id = WIN(f->qid); 416 | q = f->qid; 417 | 418 | if(x->fcall.nwname > 0){ 419 | for(i=0; ifcall.nwname; i++){ 420 | if((q.type & QTDIR) == 0){ 421 | err = Enotdir; 422 | break; 423 | } 424 | 425 | if(strcmp(x->fcall.wname[i], "..") == 0){ 426 | type = QTDIR; 427 | path = Qdir; 428 | id = 0; 429 | if(w){ 430 | winclose(w); 431 | w = nil; 432 | } 433 | Accept: 434 | if(i == MAXWELEM){ 435 | err = "name too long"; 436 | break; 437 | } 438 | q.type = type; 439 | q.vers = 0; 440 | q.path = QID(id, path); 441 | t.wqid[t.nwqid++] = q; 442 | continue; 443 | } 444 | 445 | /* is it a numeric name? */ 446 | for(j=0; (c=x->fcall.wname[i][j]); j++) 447 | if(c<'0' || '9'fcall.wname[i]); 453 | qlock(&row.lk); 454 | w = lookid(id, FALSE); 455 | if(w == nil){ 456 | qunlock(&row.lk); 457 | break; 458 | } 459 | incref(&w->ref); /* we'll drop reference at end if there's an error */ 460 | path = Qdir; 461 | type = QTDIR; 462 | qunlock(&row.lk); 463 | dir = dirtabw; 464 | goto Accept; 465 | 466 | Regular: 467 | if(strcmp(x->fcall.wname[i], "new") == 0){ 468 | if(w) 469 | error("w set in walk to new"); 470 | sendp(cnewwindow, nil); /* signal newwindowthread */ 471 | w = recvp(cnewwindow); /* receive new window */ 472 | incref(&w->ref); 473 | type = QTDIR; 474 | path = QID(w->id, Qdir); 475 | id = w->id; 476 | dir = dirtabw; 477 | goto Accept; 478 | } 479 | 480 | if(id == 0) 481 | d = dirtab; 482 | else 483 | d = dirtabw; 484 | d++; /* skip '.' */ 485 | for(; d->name; d++) 486 | if(strcmp(x->fcall.wname[i], d->name) == 0){ 487 | path = d->qid; 488 | type = d->type; 489 | dir = d; 490 | goto Accept; 491 | } 492 | 493 | break; /* file not found */ 494 | } 495 | 496 | if(i==0 && err == nil) 497 | err = Eexist; 498 | } 499 | 500 | if(err!=nil || t.nwqidfcall.nwname){ 501 | if(nf){ 502 | nf->busy = FALSE; 503 | fsysdelid(nf->mntdir); 504 | } 505 | }else if(t.nwqid == x->fcall.nwname){ 506 | if(w){ 507 | f->w = w; 508 | w = nil; /* don't drop the reference */ 509 | } 510 | if(dir) 511 | f->dir = dir; 512 | f->qid = q; 513 | } 514 | 515 | if(w != nil) 516 | winclose(w); 517 | 518 | return respond(x, &t, err); 519 | } 520 | 521 | static 522 | Xfid* 523 | fsysopen(Xfid *x, Fid *f) 524 | { 525 | Fcall t; 526 | int m; 527 | 528 | /* can't truncate anything, so just disregard */ 529 | x->fcall.mode &= ~(OTRUNC|OCEXEC); 530 | /* can't execute or remove anything */ 531 | if(x->fcall.mode==OEXEC || (x->fcall.mode&ORCLOSE)) 532 | goto Deny; 533 | switch(x->fcall.mode){ 534 | default: 535 | goto Deny; 536 | case OREAD: 537 | m = 0400; 538 | break; 539 | case OWRITE: 540 | m = 0200; 541 | break; 542 | case ORDWR: 543 | m = 0600; 544 | break; 545 | } 546 | if(((f->dir->perm&~(DMDIR|DMAPPEND))&m) != m) 547 | goto Deny; 548 | 549 | sendp(x->c, (void*)xfidopen); 550 | return nil; 551 | 552 | Deny: 553 | return respond(x, &t, Eperm); 554 | } 555 | 556 | static 557 | Xfid* 558 | fsyscreate(Xfid *x, Fid *f) 559 | { 560 | Fcall t; 561 | 562 | USED(f); 563 | return respond(x, &t, Eperm); 564 | } 565 | 566 | static 567 | int 568 | idcmp(const void *a, const void *b) 569 | { 570 | return *(int*)a - *(int*)b; 571 | } 572 | 573 | static 574 | Xfid* 575 | fsysread(Xfid *x, Fid *f) 576 | { 577 | Fcall t; 578 | uchar *b; 579 | int i, id, n, o, e, j, k, *ids, nids; 580 | Dirtab *d, dt; 581 | Column *c; 582 | uint clock, len; 583 | char buf[16]; 584 | 585 | if(f->qid.type & QTDIR){ 586 | if(FILE(f->qid) == Qacme){ /* empty dir */ 587 | t.data = nil; 588 | t.count = 0; 589 | respond(x, &t, nil); 590 | return x; 591 | } 592 | o = x->fcall.offset; 593 | e = x->fcall.offset+x->fcall.count; 594 | clock = getclock(); 595 | b = emalloc(messagesize); 596 | id = WIN(f->qid); 597 | n = 0; 598 | if(id > 0) 599 | d = dirtabw; 600 | else 601 | d = dirtab; 602 | d++; /* first entry is '.' */ 603 | for(i=0; d->name!=nil && if->qid), d, b+n, x->fcall.count-n, clock); 605 | if(len <= BIT16SZ) 606 | break; 607 | if(i >= o) 608 | n += len; 609 | d++; 610 | } 611 | if(id == 0){ 612 | qlock(&row.lk); 613 | nids = 0; 614 | ids = nil; 615 | for(j=0; jnw; k++){ 618 | ids = realloc(ids, (nids+1)*sizeof(int)); 619 | ids[nids++] = c->w[k]->id; 620 | } 621 | } 622 | qunlock(&row.lk); 623 | qsort(ids, nids, sizeof ids[0], idcmp); 624 | j = 0; 625 | dt.name = buf; 626 | for(; jfcall.count-n, clock); 633 | if(len == 0) 634 | break; 635 | if(i >= o) 636 | n += len; 637 | j++; 638 | } 639 | free(ids); 640 | } 641 | t.data = (char*)b; 642 | t.count = n; 643 | respond(x, &t, nil); 644 | free(b); 645 | return x; 646 | } 647 | sendp(x->c, (void*)xfidread); 648 | return nil; 649 | } 650 | 651 | static 652 | Xfid* 653 | fsyswrite(Xfid *x, Fid *f) 654 | { 655 | USED(f); 656 | sendp(x->c, (void*)xfidwrite); 657 | return nil; 658 | } 659 | 660 | static 661 | Xfid* 662 | fsysclunk(Xfid *x, Fid *f) 663 | { 664 | fsysdelid(f->mntdir); 665 | sendp(x->c, (void*)xfidclose); 666 | return nil; 667 | } 668 | 669 | static 670 | Xfid* 671 | fsysremove(Xfid *x, Fid *f) 672 | { 673 | Fcall t; 674 | 675 | USED(f); 676 | return respond(x, &t, Eperm); 677 | } 678 | 679 | static 680 | Xfid* 681 | fsysstat(Xfid *x, Fid *f) 682 | { 683 | Fcall t; 684 | 685 | t.stat = emalloc(messagesize-IOHDRSZ); 686 | t.nstat = dostat(WIN(x->f->qid), f->dir, t.stat, messagesize-IOHDRSZ, getclock()); 687 | x = respond(x, &t, nil); 688 | free(t.stat); 689 | return x; 690 | } 691 | 692 | static 693 | Xfid* 694 | fsyswstat(Xfid *x, Fid *f) 695 | { 696 | Fcall t; 697 | 698 | USED(f); 699 | return respond(x, &t, Eperm); 700 | } 701 | 702 | Fid* 703 | newfid(int fid) 704 | { 705 | Fid *f, *ff, **fh; 706 | 707 | ff = nil; 708 | fh = &fids[fid&(Nhash-1)]; 709 | for(f=*fh; f; f=f->next) 710 | if(f->fid == fid) 711 | return f; 712 | else if(ff==nil && f->busy==FALSE) 713 | ff = f; 714 | if(ff){ 715 | ff->fid = fid; 716 | return ff; 717 | } 718 | f = emalloc(sizeof *f); 719 | f->fid = fid; 720 | f->next = *fh; 721 | *fh = f; 722 | return f; 723 | } 724 | 725 | uint 726 | getclock(void) 727 | { 728 | return time(0); 729 | } 730 | 731 | int 732 | dostat(int id, Dirtab *dir, uchar *buf, int nbuf, uint clock) 733 | { 734 | Dir d; 735 | 736 | d.qid.path = QID(id, dir->qid); 737 | d.qid.vers = 0; 738 | d.qid.type = dir->type; 739 | d.mode = dir->perm; 740 | d.length = 0; /* would be nice to do better */ 741 | d.name = dir->name; 742 | d.uid = user; 743 | d.gid = user; 744 | d.muid = user; 745 | d.atime = clock; 746 | d.mtime = clock; 747 | return convD2M(&d, buf, nbuf); 748 | } 749 | -------------------------------------------------------------------------------- /acme/logf.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "dat.h" 12 | #include "fns.h" 13 | 14 | // State for global log file. 15 | typedef struct Log Log; 16 | struct Log 17 | { 18 | QLock lk; 19 | Rendez r; 20 | 21 | vlong start; // msg[0] corresponds to 'start' in the global sequence of events 22 | 23 | // queued events (nev=entries in ev, mev=capacity of p) 24 | char **ev; 25 | int nev; 26 | int mev; 27 | 28 | // open acme/put files that need to read events 29 | Fid **f; 30 | int nf; 31 | int mf; 32 | 33 | // active (blocked) reads waiting for events 34 | Xfid **read; 35 | int nread; 36 | int mread; 37 | }; 38 | 39 | static Log eventlog; 40 | 41 | void 42 | xfidlogopen(Xfid *x) 43 | { 44 | qlock(&eventlog.lk); 45 | if(eventlog.nf >= eventlog.mf) { 46 | eventlog.mf = eventlog.mf*2; 47 | if(eventlog.mf == 0) 48 | eventlog.mf = 8; 49 | eventlog.f = erealloc(eventlog.f, eventlog.mf*sizeof eventlog.f[0]); 50 | } 51 | eventlog.f[eventlog.nf++] = x->f; 52 | x->f->logoff = eventlog.start + eventlog.nev; 53 | 54 | qunlock(&eventlog.lk); 55 | } 56 | 57 | void 58 | xfidlogclose(Xfid *x) 59 | { 60 | int i; 61 | 62 | qlock(&eventlog.lk); 63 | for(i=0; if) { 65 | eventlog.f[i] = eventlog.f[--eventlog.nf]; 66 | break; 67 | } 68 | } 69 | qunlock(&eventlog.lk); 70 | } 71 | 72 | void 73 | xfidlogread(Xfid *x) 74 | { 75 | char *p; 76 | int i; 77 | Fcall fc; 78 | 79 | qlock(&eventlog.lk); 80 | if(eventlog.nread >= eventlog.mread) { 81 | eventlog.mread = eventlog.mread*2; 82 | if(eventlog.mread == 0) 83 | eventlog.mread = 8; 84 | eventlog.read = erealloc(eventlog.read, eventlog.mread*sizeof eventlog.read[0]); 85 | } 86 | eventlog.read[eventlog.nread++] = x; 87 | 88 | if(eventlog.r.l == nil) 89 | eventlog.r.l = &eventlog.lk; 90 | x->flushed = FALSE; 91 | while(x->f->logoff >= eventlog.start+eventlog.nev && !x->flushed) 92 | rsleep(&eventlog.r); 93 | 94 | for(i=0; iflushed) { 102 | qunlock(&eventlog.lk); 103 | return; 104 | } 105 | 106 | i = x->f->logoff - eventlog.start; 107 | p = estrdup(eventlog.ev[i]); 108 | x->f->logoff++; 109 | qunlock(&eventlog.lk); 110 | 111 | fc.data = p; 112 | fc.count = strlen(p); 113 | respond(x, &fc, nil); 114 | free(p); 115 | } 116 | 117 | void 118 | xfidlogflush(Xfid *x) 119 | { 120 | int i; 121 | Xfid *rx; 122 | 123 | qlock(&eventlog.lk); 124 | for(i=0; ifcall.tag == x->fcall.oldtag) { 127 | rx->flushed = TRUE; 128 | rwakeupall(&eventlog.r); 129 | } 130 | } 131 | qunlock(&eventlog.lk); 132 | } 133 | 134 | /* 135 | * add a log entry for op on w. 136 | * expected calls: 137 | * 138 | * op == "new" for each new window 139 | * - caller of coladd or makenewwindow responsible for calling 140 | * xfidlog after setting window name 141 | * - exception: zerox 142 | * 143 | * op == "zerox" for new window created via zerox 144 | * - called from zeroxx 145 | * 146 | * op == "get" for Get executed on window 147 | * - called from get 148 | * 149 | * op == "put" for Put executed on window 150 | * - called from put 151 | * 152 | * op == "del" for deleted window 153 | * - called from winclose 154 | */ 155 | void 156 | xfidlog(Window *w, char *op) 157 | { 158 | int i, n; 159 | vlong min; 160 | File *f; 161 | char *name; 162 | 163 | qlock(&eventlog.lk); 164 | if(eventlog.nev >= eventlog.mev) { 165 | // Remove and free any entries that all readers have read. 166 | min = eventlog.start + eventlog.nev; 167 | for(i=0; i eventlog.f[i]->logoff) 169 | min = eventlog.f[i]->logoff; 170 | } 171 | if(min > eventlog.start) { 172 | n = min - eventlog.start; 173 | for(i=0; i= eventlog.mev) { 182 | eventlog.mev = eventlog.mev*2; 183 | if(eventlog.mev == 0) 184 | eventlog.mev = 8; 185 | eventlog.ev = erealloc(eventlog.ev, eventlog.mev*sizeof eventlog.ev[0]); 186 | } 187 | } 188 | f = w->body.file; 189 | name = runetobyte(f->name, f->nname); 190 | if(name == nil) 191 | name = estrdup(""); 192 | eventlog.ev[eventlog.nev++] = smprint("%d %s %s\n", w->id, op, name); 193 | free(name); 194 | if(eventlog.r.l == nil) 195 | eventlog.r.l = &eventlog.lk; 196 | rwakeupall(&eventlog.r); 197 | qunlock(&eventlog.lk); 198 | } 199 | -------------------------------------------------------------------------------- /acme/mail/dat.h: -------------------------------------------------------------------------------- 1 | typedef struct Event Event; 2 | typedef struct Exec Exec; 3 | typedef struct Message Message; 4 | typedef struct Window Window; 5 | 6 | enum 7 | { 8 | STACK = 8192, 9 | EVENTSIZE = 256, 10 | NEVENT = 5 11 | }; 12 | 13 | struct Event 14 | { 15 | int c1; 16 | int c2; 17 | int q0; 18 | int q1; 19 | int flag; 20 | int nb; 21 | int nr; 22 | char b[EVENTSIZE*UTFmax+1]; 23 | Rune r[EVENTSIZE+1]; 24 | }; 25 | 26 | struct Window 27 | { 28 | /* coordinate wineventproc and window thread */ 29 | QLock lk; 30 | int ref; 31 | 32 | /* file descriptors */ 33 | CFid* ctl; 34 | CFid* event; 35 | CFid* addr; 36 | CFid* data; 37 | CFid* body; 38 | 39 | /* event input */ 40 | char buf[512]; 41 | char *bufp; 42 | int nbuf; 43 | Event e[NEVENT]; 44 | 45 | int id; 46 | int open; 47 | Channel *cevent; 48 | }; 49 | 50 | struct Message 51 | { 52 | Window *w; 53 | CFid* ctlfd; 54 | char *name; 55 | char *replyname; 56 | uchar opened; 57 | uchar dirty; 58 | uchar isreply; 59 | uchar deleted; 60 | uchar writebackdel; 61 | uchar tagposted; 62 | uchar recursed; 63 | uchar level; 64 | uint replywinid; 65 | 66 | /* header info */ 67 | char *from; 68 | char *fromcolon; 69 | char *to; 70 | char *cc; 71 | char *replyto; 72 | char *sender; 73 | char *date; 74 | char *subject; 75 | char *type; 76 | char *disposition; 77 | char *filename; 78 | char *digest; 79 | 80 | Message *next; /* next in this mailbox */ 81 | Message *prev; /* prev in this mailbox */ 82 | Message *head; /* first subpart */ 83 | Message *tail; /* last subpart */ 84 | }; 85 | 86 | enum 87 | { 88 | NARGS = 100, 89 | NARGCHAR = 8*1024, 90 | EXECSTACK = STACK+(NARGS+1)*sizeof(char*)+NARGCHAR 91 | }; 92 | 93 | struct Exec 94 | { 95 | char *prog; 96 | char **argv; 97 | int p[2]; /* p[1] is write to program; p[0] set to prog fd 0*/ 98 | int q[2]; /* q[0] is read from program; q[1] set to prog fd 1 */ 99 | Channel *sync; 100 | }; 101 | 102 | extern Window* newwindow(void); 103 | extern CFid* winopenfile(Window*, char*); 104 | extern void winopenbody(Window*, int); 105 | extern void winclosebody(Window*); 106 | extern void wintagwrite(Window*, char*, int); 107 | extern void winname(Window*, char*); 108 | extern void winwriteevent(Window*, Event*); 109 | extern void winread(Window*, uint, uint, char*); 110 | extern int windel(Window*, int); 111 | extern void wingetevent(Window*, Event*); 112 | extern void wineventproc(void*); 113 | extern void winwritebody(Window*, char*, int); 114 | extern void winclean(Window*); 115 | extern int winselect(Window*, char*, int); 116 | extern char* winselection(Window*); 117 | extern int winsetaddr(Window*, char*, int); 118 | extern char* winreadbody(Window*, int*); 119 | extern void windormant(Window*); 120 | extern void winsetdump(Window*, char*, char*); 121 | extern void winincref(Window*); 122 | extern void windecref(Window*); 123 | 124 | extern void readmbox(Message*, char*, char*); 125 | extern void rewritembox(Window*, Message*); 126 | 127 | extern void mkreply(Message*, char*, char*, Plumbattr*, char*); 128 | extern void delreply(Message*); 129 | 130 | extern int mesgadd(Message*, char*, Dir*, char*); 131 | extern void mesgmenu(Window*, Message*); 132 | extern void mesgmenunew(Window*, Message*); 133 | extern int mesgopen(Message*, char*, char*, Message*, int, char*); 134 | extern void mesgctl(void*); 135 | extern void mesgsend(Message*); 136 | extern void mesgdel(Message*, Message*); 137 | extern void mesgmenudel(Window*, Message*, Message*); 138 | extern void mesgmenumark(Window*, char*, char*); 139 | extern void mesgmenumarkdel(Window*, Message*, Message*, int); 140 | extern Message* mesglookup(Message*, char*, char*); 141 | extern Message* mesglookupfile(Message*, char*, char*); 142 | extern void mesgfreeparts(Message*); 143 | extern int mesgcommand(Message*, char*); 144 | 145 | extern char* info(Message*, int, int); 146 | extern char* readfile(char*, char*, int*); 147 | extern char* readbody(char*, char*, int*); 148 | extern void ctlprint(CFid*, char*, ...); 149 | extern void* emalloc(uint); 150 | extern void* erealloc(void*, uint); 151 | extern char* estrdup(char*); 152 | extern char* estrstrdup(char*, char*); 153 | extern char* egrow(char*, char*, char*); 154 | extern char* eappend(char*, char*, char*); 155 | extern void error(char*, ...); 156 | extern int tokenizec(char*, char**, int, char*); 157 | extern void execproc(void*); 158 | extern int fsprint(CFid*, char*, ...); 159 | 160 | #pragma varargck argpos error 1 161 | #pragma varargck argpos ctlprint 2 162 | 163 | extern Window *wbox; 164 | extern Message mbox; 165 | extern Message replies; 166 | extern char *fsname; 167 | extern CFid *plumbsendfd; 168 | extern CFid *plumbseemailfd; 169 | extern char *home; 170 | extern char *outgoing; 171 | extern char *mailboxdir; 172 | extern char *mboxname; 173 | extern char *user; 174 | extern char *srvname; 175 | extern char deleted[]; 176 | extern int wctlfd; 177 | extern int shortmenu; 178 | 179 | extern CFsys *mailfs; 180 | extern CFsys *acmefs; 181 | 182 | -------------------------------------------------------------------------------- /acme/mail/guide: -------------------------------------------------------------------------------- 1 | Mail stored 2 | plumb /mail/box/$user/names 3 | mail -'x' someaddress 4 | mkbox /mail/box/$user/new_box 5 | -------------------------------------------------------------------------------- /acme/mail/html.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include <9pclient.h> 8 | #include "dat.h" 9 | 10 | char* 11 | formathtml(char *body, int *np) 12 | { 13 | int i, j, p[2], q[2]; 14 | Exec *e; 15 | char buf[1024]; 16 | Channel *sync; 17 | 18 | e = emalloc(sizeof(struct Exec)); 19 | if(pipe(p) < 0 || pipe(q) < 0) 20 | error("can't create pipe: %r"); 21 | 22 | e->p[0] = p[0]; 23 | e->p[1] = p[1]; 24 | e->q[0] = q[0]; 25 | e->q[1] = q[1]; 26 | e->argv = emalloc(3*sizeof(char*)); 27 | e->argv[0] = estrdup("htmlfmt"); 28 | e->argv[1] = estrdup("-cutf-8"); 29 | e->argv[2] = nil; 30 | e->prog = "htmlfmt"; 31 | sync = chancreate(sizeof(int), 0); 32 | e->sync = sync; 33 | proccreate(execproc, e, EXECSTACK); 34 | recvul(sync); 35 | close(p[0]); 36 | close(q[1]); 37 | 38 | if((i=write(p[1], body, *np)) != *np){ 39 | fprint(2, "Mail: warning: htmlfmt failed: wrote %d of %d: %r\n", i, *np); 40 | close(p[1]); 41 | close(q[0]); 42 | return body; 43 | } 44 | close(p[1]); 45 | 46 | free(body); 47 | body = nil; 48 | i = 0; 49 | for(;;){ 50 | j = read(q[0], buf, sizeof buf); 51 | if(j <= 0) 52 | break; 53 | body = realloc(body, i+j+1); 54 | if(body == nil) 55 | error("realloc failed: %r"); 56 | memmove(body+i, buf, j); 57 | i += j; 58 | body[i] = '\0'; 59 | } 60 | close(q[0]); 61 | 62 | *np = i; 63 | return body; 64 | } 65 | 66 | char* 67 | readbody(char *type, char *dir, int *np) 68 | { 69 | char *body; 70 | 71 | body = readfile(dir, "body", np); 72 | if(body != nil && strcmp(type, "text/html") == 0) 73 | return formathtml(body, np); 74 | return body; 75 | } 76 | -------------------------------------------------------------------------------- /acme/mail/html.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreasBriese/acmePimp/95305fa1f2ee0a59220d6ed97fbe1f534e417b3b/acme/mail/html.o -------------------------------------------------------------------------------- /acme/mail/mail.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include <9pclient.h> 6 | #include 7 | #include 8 | #include "dat.h" 9 | 10 | char *maildir = "Mail/"; /* mountpoint of mail file system */ 11 | char *mboxname = "mbox"; /* mailboxdir/mboxname is mail spool file */ 12 | char *mailboxdir = nil; /* nil == /mail/box/$user */ 13 | char *fsname; /* filesystem for mailboxdir/mboxname is at maildir/fsname */ 14 | char *user; 15 | char *outgoing; 16 | char *srvname; 17 | 18 | Window *wbox; 19 | Message mbox; 20 | Message replies; 21 | char *home; 22 | CFid *plumbsendfd; 23 | CFid *plumbseemailfd; 24 | CFid *plumbshowmailfd; 25 | CFid *plumbsendmailfd; 26 | Channel *cplumb; 27 | Channel *cplumbshow; 28 | Channel *cplumbsend; 29 | int wctlfd; 30 | void mainctl(void*); 31 | void plumbproc(void*); 32 | void plumbshowproc(void*); 33 | void plumbsendproc(void*); 34 | void plumbthread(void); 35 | void plumbshowthread(void*); 36 | void plumbsendthread(void*); 37 | 38 | int shortmenu; 39 | 40 | CFsys *mailfs; 41 | CFsys *acmefs; 42 | 43 | void 44 | usage(void) 45 | { 46 | fprint(2, "usage: Mail [-sS] [-n srvname] [-o outgoing] [mailboxname [directoryname]]\n"); 47 | threadexitsall("usage"); 48 | } 49 | 50 | void 51 | removeupasfs(void) 52 | { 53 | char buf[256]; 54 | 55 | if(strcmp(mboxname, "mbox") == 0) 56 | return; 57 | snprint(buf, sizeof buf, "close %s", mboxname); 58 | fswrite(mbox.ctlfd, buf, strlen(buf)); 59 | } 60 | 61 | int 62 | ismaildir(char *s) 63 | { 64 | Dir *d; 65 | int ret; 66 | 67 | d = fsdirstat(mailfs, s); 68 | if(d == nil) 69 | return 0; 70 | ret = d->qid.type & QTDIR; 71 | free(d); 72 | return ret; 73 | } 74 | 75 | void 76 | threadmain(int argc, char *argv[]) 77 | { 78 | char *s, *name; 79 | char err[ERRMAX], *cmd; 80 | int i, newdir; 81 | Fmt fmt; 82 | 83 | doquote = needsrcquote; 84 | quotefmtinstall(); 85 | 86 | /* open these early so we won't miss notification of new mail messages while we read mbox */ 87 | if((plumbsendfd = plumbopenfid("send", OWRITE|OCEXEC)) == nil) 88 | fprint(2, "warning: open plumb/send: %r\n"); 89 | if((plumbseemailfd = plumbopenfid("seemail", OREAD|OCEXEC)) == nil) 90 | fprint(2, "warning: open plumb/seemail: %r\n"); 91 | if((plumbshowmailfd = plumbopenfid("showmail", OREAD|OCEXEC)) == nil) 92 | fprint(2, "warning: open plumb/showmail: %r\n"); 93 | 94 | shortmenu = 0; 95 | srvname = "mail"; 96 | ARGBEGIN{ 97 | case 's': 98 | shortmenu = 1; 99 | break; 100 | case 'S': 101 | shortmenu = 2; 102 | break; 103 | case 'o': 104 | outgoing = EARGF(usage()); 105 | break; 106 | case 'm': 107 | smprint(maildir, "%s/", EARGF(usage())); 108 | break; 109 | case 'n': 110 | srvname = EARGF(usage()); 111 | break; 112 | default: 113 | usage(); 114 | }ARGEND 115 | 116 | acmefs = nsmount("acme",nil); 117 | if(acmefs == nil) 118 | error("cannot mount acme: %r"); 119 | mailfs = nsmount(srvname, nil); 120 | if(mailfs == nil) 121 | error("cannot mount %s: %r", srvname); 122 | 123 | name = "mbox"; 124 | 125 | newdir = 1; 126 | if(argc > 0){ 127 | i = strlen(argv[0]); 128 | if(argc>2 || i==0) 129 | usage(); 130 | /* see if the name is that of an existing /mail/fs directory */ 131 | if(argc==1 && argv[0][0] != '/' && ismaildir(argv[0])){ 132 | name = argv[0]; 133 | mboxname = estrdup(name); 134 | newdir = 0; 135 | }else{ 136 | if(argv[0][i-1] == '/') 137 | argv[0][i-1] = '\0'; 138 | s = strrchr(argv[0], '/'); 139 | if(s == nil) 140 | mboxname = estrdup(argv[0]); 141 | else{ 142 | *s++ = '\0'; 143 | if(*s == '\0') 144 | usage(); 145 | mailboxdir = argv[0]; 146 | mboxname = estrdup(s); 147 | } 148 | if(argc > 1) 149 | name = argv[1]; 150 | else 151 | name = mboxname; 152 | } 153 | } 154 | 155 | user = getenv("user"); 156 | if(user == nil) 157 | user = "none"; 158 | home = getenv("home"); 159 | if(home == nil) 160 | home = getenv("HOME"); 161 | if(home == nil) 162 | error("can't find $home"); 163 | if(mailboxdir == nil) 164 | mailboxdir = estrstrdup(home, "/mail"); 165 | if(outgoing == nil) 166 | outgoing = estrstrdup(mailboxdir, "/outgoing"); 167 | 168 | mbox.ctlfd = fsopen(mailfs, estrstrdup(mboxname, "/ctl"), OWRITE); 169 | if(mbox.ctlfd == nil) 170 | error("can't open %s: %r", estrstrdup(mboxname, "/ctl")); 171 | 172 | fsname = estrdup(name); 173 | if(newdir && argc > 0){ 174 | s = emalloc(5+strlen(mailboxdir)+strlen(mboxname)+strlen(name)+10+1); 175 | for(i=0; i<10; i++){ 176 | sprint(s, "open %s/%s %s", mailboxdir, mboxname, fsname); 177 | if(fswrite(mbox.ctlfd, s, strlen(s)) >= 0) 178 | break; 179 | err[0] = '\0'; 180 | errstr(err, sizeof err); 181 | if(strstr(err, "mbox name in use") == nil) 182 | error("can't create directory %s for mail: %s", name, err); 183 | free(fsname); 184 | fsname = emalloc(strlen(name)+10); 185 | sprint(fsname, "%s-%d", name, i); 186 | } 187 | if(i == 10) 188 | error("can't open %s/%s: %r", mailboxdir, mboxname); 189 | free(s); 190 | } 191 | 192 | s = estrstrdup(fsname, "/"); 193 | mbox.name = estrstrdup(maildir, s); 194 | mbox.level= 0; 195 | readmbox(&mbox, maildir, s); 196 | home = getenv("home"); 197 | if(home == nil) 198 | home = "/"; 199 | 200 | wbox = newwindow(); 201 | winname(wbox, mbox.name); 202 | wintagwrite(wbox, "Put Mail Delmesg ", 3+1+4+1+7+1); 203 | threadcreate(mainctl, wbox, STACK); 204 | 205 | fmtstrinit(&fmt); 206 | fmtprint(&fmt, "Mail"); 207 | if(shortmenu) 208 | fmtprint(&fmt, " -%c", "sS"[shortmenu-1]); 209 | if(outgoing) 210 | fmtprint(&fmt, " -o %s", outgoing); 211 | fmtprint(&fmt, " %s", name); 212 | cmd = fmtstrflush(&fmt); 213 | if(cmd == nil) 214 | sysfatal("out of memory"); 215 | winsetdump(wbox, "/acme/mail", cmd); 216 | mbox.w = wbox; 217 | 218 | mesgmenu(wbox, &mbox); 219 | winclean(wbox); 220 | 221 | /* wctlfd = open("/dev/wctl", OWRITE|OCEXEC); /* for acme window */ 222 | wctlfd = -1; 223 | cplumb = chancreate(sizeof(Plumbmsg*), 0); 224 | cplumbshow = chancreate(sizeof(Plumbmsg*), 0); 225 | if(strcmp(name, "mbox") == 0){ 226 | /* 227 | * Avoid creating multiple windows to send mail by only accepting 228 | * sendmail plumb messages if we're reading the main mailbox. 229 | */ 230 | plumbsendmailfd = plumbopenfid("sendmail", OREAD|OCEXEC); 231 | cplumbsend = chancreate(sizeof(Plumbmsg*), 0); 232 | proccreate(plumbsendproc, nil, STACK); 233 | threadcreate(plumbsendthread, nil, STACK); 234 | } 235 | /* start plumb reader as separate proc ... */ 236 | proccreate(plumbproc, nil, STACK); 237 | proccreate(plumbshowproc, nil, STACK); 238 | threadcreate(plumbshowthread, nil, STACK); 239 | fswrite(mbox.ctlfd, "refresh", 7); 240 | /* ... and use this thread to read the messages */ 241 | plumbthread(); 242 | } 243 | 244 | void 245 | plumbproc(void* v) 246 | { 247 | Plumbmsg *m; 248 | 249 | threadsetname("plumbproc"); 250 | for(;;){ 251 | m = plumbrecvfid(plumbseemailfd); 252 | sendp(cplumb, m); 253 | if(m == nil) 254 | threadexits(nil); 255 | } 256 | } 257 | 258 | void 259 | plumbshowproc(void* v) 260 | { 261 | Plumbmsg *m; 262 | 263 | threadsetname("plumbshowproc"); 264 | for(;;){ 265 | m = plumbrecvfid(plumbshowmailfd); 266 | sendp(cplumbshow, m); 267 | if(m == nil) 268 | threadexits(nil); 269 | } 270 | } 271 | 272 | void 273 | plumbsendproc(void* v) 274 | { 275 | Plumbmsg *m; 276 | 277 | threadsetname("plumbsendproc"); 278 | for(;;){ 279 | m = plumbrecvfid(plumbsendmailfd); 280 | sendp(cplumbsend, m); 281 | if(m == nil) 282 | threadexits(nil); 283 | } 284 | } 285 | 286 | void 287 | newmesg(char *name, char *digest) 288 | { 289 | Dir *d; 290 | 291 | if(strncmp(name, mbox.name, strlen(mbox.name)) != 0) 292 | return; /* message is about another mailbox */ 293 | if(mesglookupfile(&mbox, name, digest) != nil) 294 | return; 295 | if(strncmp(name, "Mail/", 5) == 0) 296 | name += 5; 297 | d = fsdirstat(mailfs, name); 298 | if(d == nil) 299 | return; 300 | if(mesgadd(&mbox, mbox.name, d, digest)) 301 | mesgmenunew(wbox, &mbox); 302 | free(d); 303 | } 304 | 305 | void 306 | showmesg(char *name, char *digest) 307 | { 308 | char *n; 309 | char *mb; 310 | 311 | mb = mbox.name; 312 | if(strncmp(name, mb, strlen(mb)) != 0) 313 | return; /* message is about another mailbox */ 314 | n = estrdup(name+strlen(mb)); 315 | if(n[strlen(n)-1] != '/') 316 | n = egrow(n, "/", nil); 317 | mesgopen(&mbox, mbox.name, name+strlen(mb), nil, 1, digest); 318 | free(n); 319 | } 320 | 321 | void 322 | delmesg(char *name, char *digest, int dodel, char *save) 323 | { 324 | Message *m; 325 | 326 | m = mesglookupfile(&mbox, name, digest); 327 | if(m != nil){ 328 | if(save) 329 | mesgcommand(m, estrstrdup("Save ", save)); 330 | if(dodel) 331 | mesgmenumarkdel(wbox, &mbox, m, 1); 332 | else{ 333 | /* notification came from plumber - message is gone */ 334 | mesgmenudel(wbox, &mbox, m); 335 | if(!m->opened) 336 | mesgdel(&mbox, m); 337 | } 338 | } 339 | } 340 | 341 | void 342 | plumbthread(void) 343 | { 344 | Plumbmsg *m; 345 | Plumbattr *a; 346 | char *type, *digest; 347 | 348 | threadsetname("plumbthread"); 349 | while((m = recvp(cplumb)) != nil){ 350 | a = m->attr; 351 | digest = plumblookup(a, "digest"); 352 | type = plumblookup(a, "mailtype"); 353 | if(type == nil) 354 | fprint(2, "Mail: plumb message with no mailtype attribute\n"); 355 | else if(strcmp(type, "new") == 0) 356 | newmesg(m->data, digest); 357 | else if(strcmp(type, "delete") == 0) 358 | delmesg(m->data, digest, 0, nil); 359 | else 360 | fprint(2, "Mail: unknown plumb attribute %s\n", type); 361 | plumbfree(m); 362 | } 363 | threadexits(nil); 364 | } 365 | 366 | void 367 | plumbshowthread(void *v) 368 | { 369 | Plumbmsg *m; 370 | 371 | USED(v); 372 | threadsetname("plumbshowthread"); 373 | while((m = recvp(cplumbshow)) != nil){ 374 | showmesg(m->data, plumblookup(m->attr, "digest")); 375 | plumbfree(m); 376 | } 377 | threadexits(nil); 378 | } 379 | 380 | void 381 | plumbsendthread(void *v) 382 | { 383 | Plumbmsg *m; 384 | 385 | USED(v); 386 | threadsetname("plumbsendthread"); 387 | while((m = recvp(cplumbsend)) != nil){ 388 | mkreply(nil, "Mail", m->data, m->attr, nil); 389 | plumbfree(m); 390 | } 391 | threadexits(nil); 392 | } 393 | 394 | int 395 | mboxcommand(Window *w, char *s) 396 | { 397 | char *args[10], **targs, *save; 398 | Window *sbox; 399 | Message *m, *next; 400 | int ok, nargs, i, j; 401 | CFid *searchfd; 402 | char buf[128], *res; 403 | 404 | nargs = tokenize(s, args, nelem(args)); 405 | if(nargs == 0) 406 | return 0; 407 | if(strcmp(args[0], "Mail") == 0){ 408 | if(nargs == 1) 409 | mkreply(nil, "Mail", "", nil, nil); 410 | else 411 | mkreply(nil, "Mail", args[1], nil, nil); 412 | return 1; 413 | } 414 | if(strcmp(s, "Del") == 0){ 415 | if(mbox.dirty){ 416 | mbox.dirty = 0; 417 | fprint(2, "mail: mailbox not written\n"); 418 | return 1; 419 | } 420 | if(w != mbox.w){ 421 | windel(w, 1); 422 | return 1; 423 | } 424 | ok = 1; 425 | for(m=mbox.head; m!=nil; m=next){ 426 | next = m->next; 427 | if(m->w){ 428 | if(windel(m->w, 0)) 429 | m->w = nil; 430 | else 431 | ok = 0; 432 | } 433 | } 434 | for(m=replies.head; m!=nil; m=next){ 435 | next = m->next; 436 | if(m->w){ 437 | if(windel(m->w, 0)) 438 | m->w = nil; 439 | else 440 | ok = 0; 441 | } 442 | } 443 | if(ok){ 444 | windel(w, 1); 445 | removeupasfs(); 446 | threadexitsall(nil); 447 | } 448 | return 1; 449 | } 450 | if(strcmp(s, "Put") == 0){ 451 | rewritembox(wbox, &mbox); 452 | return 1; 453 | } 454 | if(strcmp(s, "Get") == 0){ 455 | fswrite(mbox.ctlfd, "refresh", 7); 456 | return 1; 457 | } 458 | if(strcmp(s, "Delmesg") == 0){ 459 | save = nil; 460 | if(nargs > 1) 461 | save = args[1]; 462 | s = winselection(w); 463 | if(s == nil) 464 | return 1; 465 | nargs = 1; 466 | for(i=0; s[i]; i++) 467 | if(s[i] == '\n') 468 | nargs++; 469 | targs = emalloc(nargs*sizeof(char*)); /* could be too many for a local array */ 470 | nargs = getfields(s, targs, nargs, 1, "\n"); 471 | for(i=0; iname) != 0; m=m->prev); 528 | free(save); 529 | if(m == nil) 530 | break; 531 | fsprint(sbox->body, "%s%s\n", m->name, info(m, 0, 0)); 532 | m = m->prev; 533 | } 534 | free(res); 535 | winclean(sbox); 536 | winclosebody(sbox); 537 | return 1; 538 | } 539 | return 0; 540 | } 541 | 542 | void 543 | mainctl(void *v) 544 | { 545 | Window *w; 546 | Event *e, *e2, *eq, *ea; 547 | int na, nopen; 548 | char *s, *t, *buf; 549 | 550 | w = v; 551 | winincref(w); 552 | proccreate(wineventproc, w, STACK); 553 | 554 | for(;;){ 555 | e = recvp(w->cevent); 556 | switch(e->c1){ 557 | default: 558 | Unknown: 559 | print("unknown message %c%c\n", e->c1, e->c2); 560 | break; 561 | 562 | case 'E': /* write to body; can't affect us */ 563 | break; 564 | 565 | case 'F': /* generated by our actions; ignore */ 566 | break; 567 | 568 | case 'K': /* type away; we don't care */ 569 | break; 570 | 571 | case 'M': 572 | switch(e->c2){ 573 | case 'x': 574 | case 'X': 575 | ea = nil; 576 | e2 = nil; 577 | if(e->flag & 2) 578 | e2 = recvp(w->cevent); 579 | if(e->flag & 8){ 580 | ea = recvp(w->cevent); 581 | na = ea->nb; 582 | recvp(w->cevent); 583 | }else 584 | na = 0; 585 | s = e->b; 586 | /* if it's a known command, do it */ 587 | if((e->flag&2) && e->nb==0) 588 | s = e2->b; 589 | if(na){ 590 | t = emalloc(strlen(s)+1+na+1); 591 | sprint(t, "%s %s", s, ea->b); 592 | s = t; 593 | } 594 | /* if it's a long message, it can't be for us anyway */ 595 | if(!mboxcommand(w, s)) /* send it back */ 596 | winwriteevent(w, e); 597 | if(na) 598 | free(s); 599 | break; 600 | 601 | case 'l': 602 | case 'L': 603 | buf = nil; 604 | eq = e; 605 | if(e->flag & 2){ 606 | e2 = recvp(w->cevent); 607 | eq = e2; 608 | } 609 | s = eq->b; 610 | if(eq->q1>eq->q0 && eq->nb==0){ 611 | buf = emalloc((eq->q1-eq->q0)*UTFmax+1); 612 | winread(w, eq->q0, eq->q1, buf); 613 | s = buf; 614 | } 615 | nopen = 0; 616 | do{ 617 | /* skip 'deleted' string if present' */ 618 | if(strncmp(s, deleted, strlen(deleted)) == 0) 619 | s += strlen(deleted); 620 | /* skip mail box name if present */ 621 | if(strncmp(s, mbox.name, strlen(mbox.name)) == 0) 622 | s += strlen(mbox.name); 623 | nopen += mesgopen(&mbox, mbox.name, s, nil, 0, nil); 624 | while(*s!='\0' && *s++!='\n') 625 | ; 626 | }while(*s); 627 | if(nopen == 0) /* send it back */ 628 | winwriteevent(w, e); 629 | free(buf); 630 | break; 631 | 632 | case 'I': /* modify away; we don't care */ 633 | case 'D': 634 | case 'd': 635 | case 'i': 636 | break; 637 | 638 | default: 639 | goto Unknown; 640 | } 641 | } 642 | } 643 | } 644 | 645 | -------------------------------------------------------------------------------- /acme/mail/mail.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreasBriese/acmePimp/95305fa1f2ee0a59220d6ed97fbe1f534e417b3b/acme/mail/mail.o -------------------------------------------------------------------------------- /acme/mail/mesg.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreasBriese/acmePimp/95305fa1f2ee0a59220d6ed97fbe1f534e417b3b/acme/mail/mesg.o -------------------------------------------------------------------------------- /acme/mail/mkbox: -------------------------------------------------------------------------------- 1 | #!/bin/rc 2 | 3 | for(i){ 4 | if(! test -f $i){ 5 | if(cp /dev/null $i){ 6 | chmod 600 $i 7 | chmod +al $i 8 | } 9 | } 10 | if not echo $i already exists 11 | } 12 | -------------------------------------------------------------------------------- /acme/mail/mkfile: -------------------------------------------------------------------------------- 1 | <$PLAN9/src/mkhdr 2 | 3 | TARG=Mail 4 | OFILES=\ 5 | html.$O\ 6 | mail.$O\ 7 | mesg.$O\ 8 | reply.$O\ 9 | util.$O\ 10 | win.$O 11 | 12 | HFILES=dat.h 13 | 14 | <$PLAN9/src/mkone 15 | 16 | -------------------------------------------------------------------------------- /acme/mail/o.Mail: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreasBriese/acmePimp/95305fa1f2ee0a59220d6ed97fbe1f534e417b3b/acme/mail/o.Mail -------------------------------------------------------------------------------- /acme/mail/readme: -------------------------------------------------------------------------------- 1 | The Acme Mail program uses upas/fs to parse the mail box, and then 2 | presents a file-browser-like user interface to reading and sending 3 | messages. The Mail window presents each numbered message like the 4 | contents of a directory presented one per line. If a message has a 5 | Subject: line, that is shown indented on the following line. 6 | Multipart MIME-encoded messages are presented in the obvious 7 | hierarchical format. 8 | 9 | Mail uses upas/fs to access the mail box. By default it reads "mbox", 10 | the standard user mail box. If Mail is given an argument, it is 11 | passed to upas/fs as the name of the mail box (or upas/fs directory) 12 | to open. 13 | 14 | Although Mail works if the plumber is not running, it's designed to be 15 | run with plumbing enabled and many of its features work best if it is. 16 | 17 | The mailbox window has a few commands: Put writes back the mailbox; 18 | Mail creates a new window in which to compose a message; and Delmesg 19 | deletes messages by number. The number may be given as argument or 20 | indicated by selecting the header line in the mailbox window. 21 | (Delmesg does not expand null selections, in the interest of safety.) 22 | 23 | Clicking the right button on a message number opens it; clicking on 24 | any of the subparts of a message opens that (and also opens the 25 | message itself). Each message window has a few commands in the tag 26 | with obvious names: Reply, Delmsg, etc. "Reply" replies to the single 27 | sender of the message, "Reply all" or "Replyall" replies to everyone 28 | in the From:, To:, and CC: lines. 29 | 30 | Message parts with recognized MIME types such as image/jpeg are sent 31 | to the plumber for further dispatch. Acme Mail also listens to 32 | messages on the seemail and showmail plumbing ports, to report the 33 | arrival of new messages (highlighting the entry; right-click on the 34 | entry to open the message) and open them if you right-click on the 35 | face in the faces window. 36 | 37 | When composing a mail message or replying to a message, the first line 38 | of the text is a list of recipients of the message. To:, and CC:, and BCC: 39 | lines are interpreted in the usual way. Two other header lines are 40 | special to Acme Mail: 41 | Include: file places a copy of file in the message as an 42 | inline MIME attachment. 43 | Attach: file places a copy of file in the message as a regular 44 | MIME attachment. 45 | 46 | Acme Mail uses these conventions when replying to messages, 47 | constructing headers for the default behavior. You may edit these to 48 | change behavior. Most important, when replying to a message Mail will 49 | always Include: the original message; delete that line if you don't 50 | want to include it. 51 | 52 | If the mailbox 53 | /mail/box/$user/outgoing 54 | exists, Acme Mail will save your a copy of your outgoing messages 55 | there. Attachments are described in the copy but not included. 56 | 57 | The -m mntpoint flag specifies a different mount point for /upas/fs. 58 | -------------------------------------------------------------------------------- /acme/mail/reply.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include <9pclient.h> 8 | #include "dat.h" 9 | 10 | static int replyid; 11 | 12 | int 13 | quote(Message *m, CFid *fid, char *dir, char *quotetext) 14 | { 15 | char *body, *type; 16 | int i, n, nlines; 17 | char **lines; 18 | 19 | if(quotetext){ 20 | body = quotetext; 21 | n = strlen(body); 22 | type = nil; 23 | }else{ 24 | /* look for first textual component to quote */ 25 | type = readfile(dir, "type", &n); 26 | if(type == nil){ 27 | print("no type in %s\n", dir); 28 | return 0; 29 | } 30 | if(strncmp(type, "multipart/", 10)==0 || strncmp(type, "message/", 8)==0){ 31 | dir = estrstrdup(dir, "1/"); 32 | if(quote(m, fid, dir, nil)){ 33 | free(type); 34 | free(dir); 35 | return 1; 36 | } 37 | free(dir); 38 | } 39 | if(strncmp(type, "text", 4) != 0){ 40 | free(type); 41 | return 0; 42 | } 43 | body = readbody(m->type, dir, &n); 44 | if(body == nil) 45 | return 0; 46 | } 47 | nlines = 0; 48 | for(i=0; i%s%s\n", lines[i][0]=='>'? "" : " ", lines[i]); 62 | i++; 63 | } 64 | free(lines); 65 | free(body); /* will free quotetext if non-nil */ 66 | free(type); 67 | return 1; 68 | } 69 | 70 | void 71 | mkreply(Message *m, char *label, char *to, Plumbattr *attr, char *quotetext) 72 | { 73 | char buf[100]; 74 | CFid *fd; 75 | Message *r; 76 | char *dir, *t; 77 | int quotereply; 78 | Plumbattr *a; 79 | 80 | quotereply = (label[0] == 'Q'); 81 | 82 | if(quotereply && m && m->replywinid > 0){ 83 | snprint(buf, sizeof buf, "%d/body", m->replywinid); 84 | if((fd = fsopen(acmefs, buf, OWRITE)) != nil){ 85 | dir = estrstrdup(mbox.name, m->name); 86 | quote(m, fd, dir, quotetext); 87 | free(dir); 88 | return; 89 | } 90 | } 91 | 92 | r = emalloc(sizeof(Message)); 93 | r->isreply = 1; 94 | if(m != nil) 95 | r->replyname = estrdup(m->name); 96 | r->next = replies.head; 97 | r->prev = nil; 98 | if(replies.head != nil) 99 | replies.head->prev = r; 100 | replies.head = r; 101 | if(replies.tail == nil) 102 | replies.tail = r; 103 | r->name = emalloc(strlen(mbox.name)+strlen(label)+10); 104 | sprint(r->name, "%s%s%d", mbox.name, label, ++replyid); 105 | r->w = newwindow(); 106 | if(m) 107 | m->replywinid = r->w->id; 108 | winname(r->w, r->name); 109 | ctlprint(r->w->ctl, "cleartag"); 110 | wintagwrite(r->w, "fmt Look Post Undo", 4+5+5+4); 111 | r->tagposted = 1; 112 | threadcreate(mesgctl, r, STACK); 113 | winopenbody(r->w, OWRITE); 114 | if(to!=nil && to[0]!='\0') 115 | fsprint(r->w->body, "%s\n", to); 116 | for(a=attr; a; a=a->next) 117 | fsprint(r->w->body, "%s: %s\n", a->name, a->value); 118 | dir = nil; 119 | if(m != nil){ 120 | dir = estrstrdup(mbox.name, m->name); 121 | if(to == nil && attr == nil){ 122 | /* Reply goes to replyto; Reply all goes to From and To and CC */ 123 | if(strstr(label, "all") == nil) 124 | fsprint(r->w->body, "To: %s\n", m->replyto); 125 | else{ /* Replyall */ 126 | if(strlen(m->from) > 0) 127 | fsprint(r->w->body, "To: %s\n", m->from); 128 | if(strlen(m->to) > 0) 129 | fsprint(r->w->body, "To: %s\n", m->to); 130 | if(strlen(m->cc) > 0) 131 | fsprint(r->w->body, "CC: %s\n", m->cc); 132 | } 133 | } 134 | if(strlen(m->subject) > 0){ 135 | t = "Subject: Re: "; 136 | if(strlen(m->subject) >= 3) 137 | if(tolower(m->subject[0])=='r' && tolower(m->subject[1])=='e' && m->subject[2]==':') 138 | t = "Subject: "; 139 | fsprint(r->w->body, "%s%s\n", t, m->subject); 140 | } 141 | if(!quotereply){ 142 | fsprint(r->w->body, "Include: %sraw\n", dir); 143 | free(dir); 144 | } 145 | } 146 | fsprint(r->w->body, "\n"); 147 | if(m == nil) 148 | fsprint(r->w->body, "\n"); 149 | else if(quotereply){ 150 | quote(m, r->w->body, dir, quotetext); 151 | free(dir); 152 | } 153 | winclosebody(r->w); 154 | if(m==nil && (to==nil || to[0]=='\0')) 155 | winselect(r->w, "0", 0); 156 | else 157 | winselect(r->w, "$", 0); 158 | winclean(r->w); 159 | windormant(r->w); 160 | } 161 | 162 | void 163 | delreply(Message *m) 164 | { 165 | if(m->next == nil) 166 | replies.tail = m->prev; 167 | else 168 | m->next->prev = m->prev; 169 | if(m->prev == nil) 170 | replies.head = m->next; 171 | else 172 | m->prev->next = m->next; 173 | mesgfreeparts(m); 174 | free(m); 175 | } 176 | 177 | /* copy argv to stack and free the incoming strings, so we don't leak argument vectors */ 178 | void 179 | buildargv(char **inargv, char *argv[NARGS+1], char args[NARGCHAR]) 180 | { 181 | int i, n; 182 | char *s, *a; 183 | 184 | s = args; 185 | for(i=0; i= NARGCHAR) /* too many characters */ 191 | break; 192 | argv[i] = s; 193 | memmove(s, a, n); 194 | s += n; 195 | free(a); 196 | } 197 | argv[i] = nil; 198 | } 199 | 200 | void 201 | execproc(void *v) 202 | { 203 | struct Exec *e; 204 | int p[2], q[2]; 205 | char *prog; 206 | char *argv[NARGS+1], args[NARGCHAR]; 207 | int fd[3]; 208 | 209 | e = v; 210 | p[0] = e->p[0]; 211 | p[1] = e->p[1]; 212 | q[0] = e->q[0]; 213 | q[1] = e->q[1]; 214 | prog = e->prog; /* known not to be malloc'ed */ 215 | 216 | fd[0] = dup(p[0], -1); 217 | if(q[0]) 218 | fd[1] = dup(q[1], -1); 219 | else 220 | fd[1] = dup(1, -1); 221 | fd[2] = dup(2, -2); 222 | sendul(e->sync, 1); 223 | buildargv(e->argv, argv, args); 224 | free(e->argv); 225 | chanfree(e->sync); 226 | free(e); 227 | 228 | threadexec(nil, fd, prog, argv); 229 | close(fd[0]); 230 | close(fd[1]); 231 | close(fd[2]); 232 | 233 | fprint(2, "Mail: can't exec %s: %r\n", prog); 234 | threadexits("can't exec"); 235 | } 236 | 237 | enum{ 238 | ATTACH, 239 | BCC, 240 | CC, 241 | FROM, 242 | INCLUDE, 243 | TO 244 | }; 245 | 246 | char *headers[] = { 247 | "attach:", 248 | "bcc:", 249 | "cc:", 250 | "from:", 251 | "include:", 252 | "to:", 253 | nil 254 | }; 255 | 256 | int 257 | whichheader(char *h) 258 | { 259 | int i; 260 | 261 | for(i=0; headers[i]!=nil; i++) 262 | if(cistrcmp(h, headers[i]) == 0) 263 | return i; 264 | return -1; 265 | } 266 | 267 | char *tolist[200]; 268 | char *cclist[200]; 269 | char *bcclist[200]; 270 | int ncc, nbcc, nto; 271 | char *attlist[200]; 272 | char included[200]; 273 | 274 | int 275 | addressed(char *name) 276 | { 277 | int i; 278 | 279 | for(i=0; i 0) 352 | write(ofd, s, m); 353 | return n; 354 | } 355 | 356 | void 357 | write2(int fd, int ofd, char *buf, int n, int nofrom) 358 | { 359 | char *from, *p; 360 | int m; 361 | 362 | write(fd, buf, n); 363 | 364 | if(ofd <= 0) 365 | return; 366 | 367 | if(nofrom == 0){ 368 | write(ofd, buf, n); 369 | return; 370 | } 371 | 372 | /* need to escape leading From lines to avoid corrupting 'outgoing' mailbox */ 373 | for(p=buf; *p; p+=m){ 374 | from = cistrstr(p, "from"); 375 | if(from == nil) 376 | m = n; 377 | else 378 | m = from - p; 379 | if(m > 0) 380 | write(ofd, p, m); 381 | if(from){ 382 | if(p==buf || from[-1]=='\n') 383 | write(ofd, " ", 1); /* escape with space if From is at start of line */ 384 | write(ofd, from, 4); 385 | m += 4; 386 | } 387 | n -= m; 388 | } 389 | } 390 | 391 | void 392 | mesgsend(Message *m) 393 | { 394 | char *s, *body, *to; 395 | int i, j, h, n, natt, p[2]; 396 | struct Exec *e; 397 | Channel *sync; 398 | int first, nfld, delit, ofd; 399 | char *copy, *fld[100], *now; 400 | 401 | body = winreadbody(m->w, &n); 402 | /* assemble to: list from first line, to: line, and cc: line */ 403 | nto = 0; 404 | natt = 0; 405 | ncc = 0; 406 | nbcc = 0; 407 | first = 1; 408 | to = body; 409 | for(;;){ 410 | for(s=to; *s!='\n'; s++) 411 | if(*s == '\0'){ 412 | free(body); 413 | return; 414 | } 415 | if(s++ == to) /* blank line */ 416 | break; 417 | /* make copy of line to tokenize */ 418 | copy = emalloc(s-to); 419 | memmove(copy, to, s-to); 420 | copy[s-to-1] = '\0'; 421 | nfld = tokenizec(copy, fld, nelem(fld), ", \t"); 422 | if(nfld == 0){ 423 | free(copy); 424 | break; 425 | } 426 | n -= s-to; 427 | switch(h = whichheader(fld[0])){ 428 | case TO: 429 | case FROM: 430 | delit = 1; 431 | commas(to+strlen(fld[0]), s-1); 432 | for(i=1; i 0){ 478 | /* From dhog Fri Aug 24 22:13:00 EDT 2001 */ 479 | now = ctime(time(0)); 480 | seek(ofd, 0, 2); 481 | fprint(ofd, "From %s %s", user, now); 482 | fprint(ofd, "From: %s\n", user); 483 | fprint(ofd, "Date: %s", now); 484 | for(i=0; ip[0] = p[0]; 498 | e->p[1] = p[1]; 499 | e->prog = unsharp("#9/bin/upas/marshal"); 500 | e->argv = emalloc((1+1+2+4*natt+1)*sizeof(char*)); 501 | e->argv[0] = estrdup("marshal"); 502 | e->argv[1] = estrdup("-8"); 503 | j = 2; 504 | if(m->replyname){ 505 | e->argv[j++] = estrdup("-R"); 506 | e->argv[j++] = estrstrdup(mbox.name, m->replyname); 507 | } 508 | for(i=0; iargv[j++] = estrdup("-A"); 511 | else 512 | e->argv[j++] = estrdup("-a"); 513 | e->argv[j++] = estrdup(attlist[i]); 514 | } 515 | sync = chancreate(sizeof(int), 0); 516 | e->sync = sync; 517 | proccreate(execproc, e, EXECSTACK); 518 | recvul(sync); 519 | /* close(p[0]); */ 520 | 521 | /* using marshal -8, so generate rfc822 headers */ 522 | if(nto > 0){ 523 | print2(p[1], ofd, "To: "); 524 | for(i=0; i 0){ 529 | print2(p[1], ofd, "CC: "); 530 | for(i=0; i 0){ 535 | print2(p[1], ofd, "BCC: "); 536 | for(i=0; i 0) 543 | write2(p[1], ofd, body, i, 1); 544 | 545 | /* guarantee a blank line, to ensure attachments are separated from body */ 546 | if(i==0 || body[i-1]!='\n') 547 | write2(p[1], ofd, "\n\n", 2, 0); 548 | else if(i>1 && body[i-2]!='\n') 549 | write2(p[1], ofd, "\n", 1, 0); 550 | 551 | /* these look like pseudo-attachments in the "outgoing" box */ 552 | if(ofd>0 && natt>0){ 553 | for(i=0; i Include: %s\n", attlist[i]); 556 | else 557 | fprint(ofd, "=====> Attach: %s\n", attlist[i]); 558 | } 559 | if(ofd > 0) 560 | write(ofd, "\n", 1); 561 | 562 | for(i=0; ireplyname != nil) 569 | mesgmenumark(mbox.w, m->replyname, "\t[replied]"); 570 | if(m->name[0] == '/') 571 | s = estrdup(m->name); 572 | else 573 | s = estrstrdup(mbox.name, m->name); 574 | s = egrow(s, "-R", nil); 575 | winname(m->w, s); 576 | free(s); 577 | winclean(m->w); 578 | /* mark message unopened because it's no longer the original message */ 579 | m->opened = 0; 580 | } 581 | -------------------------------------------------------------------------------- /acme/mail/reply.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreasBriese/acmePimp/95305fa1f2ee0a59220d6ed97fbe1f534e417b3b/acme/mail/reply.o -------------------------------------------------------------------------------- /acme/mail/util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include <9pclient.h> 7 | #include "dat.h" 8 | 9 | void* 10 | emalloc(uint n) 11 | { 12 | void *p; 13 | 14 | p = malloc(n); 15 | if(p == nil) 16 | error("can't malloc: %r"); 17 | memset(p, 0, n); 18 | setmalloctag(p, getcallerpc(&n)); 19 | return p; 20 | } 21 | 22 | void* 23 | erealloc(void *p, uint n) 24 | { 25 | p = realloc(p, n); 26 | if(p == nil) 27 | error("can't realloc: %r"); 28 | setmalloctag(p, getcallerpc(&n)); 29 | return p; 30 | } 31 | 32 | char* 33 | estrdup(char *s) 34 | { 35 | char *t; 36 | 37 | t = emalloc(strlen(s)+1); 38 | strcpy(t, s); 39 | return t; 40 | } 41 | 42 | char* 43 | estrstrdup(char *s, char *t) 44 | { 45 | char *u; 46 | 47 | u = emalloc(strlen(s)+strlen(t)+1); 48 | strcpy(u, s); 49 | strcat(u, t); 50 | return u; 51 | } 52 | 53 | char* 54 | eappend(char *s, char *sep, char *t) 55 | { 56 | char *u; 57 | 58 | if(t == nil) 59 | u = estrstrdup(s, sep); 60 | else{ 61 | u = emalloc(strlen(s)+strlen(sep)+strlen(t)+1); 62 | strcpy(u, s); 63 | strcat(u, sep); 64 | strcat(u, t); 65 | } 66 | free(s); 67 | return u; 68 | } 69 | 70 | char* 71 | egrow(char *s, char *sep, char *t) 72 | { 73 | s = eappend(s, sep, t); 74 | free(t); 75 | return s; 76 | } 77 | 78 | void 79 | error(char *fmt, ...) 80 | { 81 | Fmt f; 82 | char buf[64]; 83 | va_list arg; 84 | 85 | fmtfdinit(&f, 2, buf, sizeof buf); 86 | fmtprint(&f, "Mail: "); 87 | va_start(arg, fmt); 88 | fmtvprint(&f, fmt, arg); 89 | va_end(arg); 90 | fmtprint(&f, "\n"); 91 | fmtfdflush(&f); 92 | threadexitsall(fmt); 93 | } 94 | 95 | void 96 | ctlprint(CFid *fd, char *fmt, ...) 97 | { 98 | int n; 99 | va_list arg; 100 | 101 | va_start(arg, fmt); 102 | n = fsvprint(fd, fmt, arg); 103 | va_end(arg); 104 | if(n <= 0) 105 | error("control file write error: %r"); 106 | } 107 | 108 | -------------------------------------------------------------------------------- /acme/mail/util.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreasBriese/acmePimp/95305fa1f2ee0a59220d6ed97fbe1f534e417b3b/acme/mail/util.o -------------------------------------------------------------------------------- /acme/mail/win.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include <9pclient.h> 7 | #include "dat.h" 8 | 9 | Window* 10 | newwindow(void) 11 | { 12 | char buf[12]; 13 | Window *w; 14 | 15 | w = emalloc(sizeof(Window)); 16 | w->ctl = fsopen(acmefs, "new/ctl", ORDWR|OCEXEC); 17 | if(w->ctl == nil || fsread(w->ctl, buf, 12)!=12) 18 | error("can't open window ctl file: %r"); 19 | 20 | w->id = atoi(buf); 21 | w->event = winopenfile(w, "event"); 22 | w->addr = nil; /* will be opened when needed */ 23 | w->body = nil; 24 | w->data = nil; 25 | w->cevent = chancreate(sizeof(Event*), 0); 26 | w->ref = 1; 27 | return w; 28 | } 29 | 30 | void 31 | winincref(Window *w) 32 | { 33 | qlock(&w->lk); 34 | ++w->ref; 35 | qunlock(&w->lk); 36 | } 37 | 38 | void 39 | windecref(Window *w) 40 | { 41 | qlock(&w->lk); 42 | if(--w->ref > 0){ 43 | qunlock(&w->lk); 44 | return; 45 | } 46 | fsclose(w->event); 47 | chanfree(w->cevent); 48 | free(w); 49 | } 50 | 51 | void 52 | winsetdump(Window *w, char *dir, char *cmd) 53 | { 54 | if(dir != nil) 55 | ctlprint(w->ctl, "dumpdir %s\n", dir); 56 | if(cmd != nil) 57 | ctlprint(w->ctl, "dump %s\n", cmd); 58 | } 59 | 60 | void 61 | wineventproc(void *v) 62 | { 63 | Window *w; 64 | int i; 65 | 66 | w = v; 67 | for(i=0; ; i++){ 68 | if(i >= NEVENT) 69 | i = 0; 70 | wingetevent(w, &w->e[i]); 71 | sendp(w->cevent, &w->e[i]); 72 | } 73 | } 74 | 75 | static CFid* 76 | winopenfile1(Window *w, char *f, int m) 77 | { 78 | char buf[64]; 79 | CFid* fd; 80 | 81 | sprint(buf, "%d/%s", w->id, f); 82 | fd = fsopen(acmefs, buf, m|OCEXEC); 83 | if(fd == nil) 84 | error("can't open window file %s: %r", f); 85 | return fd; 86 | } 87 | 88 | CFid* 89 | winopenfile(Window *w, char *f) 90 | { 91 | return winopenfile1(w, f, ORDWR); 92 | } 93 | 94 | void 95 | wintagwrite(Window *w, char *s, int n) 96 | { 97 | CFid* fid; 98 | 99 | fid = winopenfile(w, "tag"); 100 | if(fswrite(fid, s, n) != n) 101 | error("tag write: %r"); 102 | fsclose(fid); 103 | } 104 | 105 | void 106 | winname(Window *w, char *s) 107 | { 108 | int len; 109 | char *ns, *sp; 110 | Rune r = L'␣'; /* visible space */ 111 | 112 | len = 0; 113 | ns = emalloc(strlen(s)*runelen(r) + 1); 114 | for(sp = s; *sp != '\0'; sp++, len++){ 115 | if(isspace(*sp)){ 116 | len += runetochar(ns+len, &r)-1; 117 | continue; 118 | } 119 | *(ns+len) = *sp; 120 | } 121 | ctlprint(w->ctl, "name %s\n", ns); 122 | free(ns); 123 | return; 124 | } 125 | 126 | void 127 | winopenbody(Window *w, int mode) 128 | { 129 | char buf[256]; 130 | CFid* fid; 131 | 132 | sprint(buf, "%d/body", w->id); 133 | fid = fsopen(acmefs, buf, mode|OCEXEC); 134 | w->body = fid; 135 | if(w->body == nil) 136 | error("can't open window body file: %r"); 137 | } 138 | 139 | void 140 | winclosebody(Window *w) 141 | { 142 | if(w->body != nil){ 143 | fsclose(w->body); 144 | w->body = nil; 145 | } 146 | } 147 | 148 | void 149 | winwritebody(Window *w, char *s, int n) 150 | { 151 | if(w->body == nil) 152 | winopenbody(w, OWRITE); 153 | if(fswrite(w->body, s, n) != n) 154 | error("write error to window: %r"); 155 | } 156 | 157 | int 158 | wingetec(Window *w) 159 | { 160 | if(w->nbuf == 0){ 161 | w->nbuf = fsread(w->event, w->buf, sizeof w->buf); 162 | if(w->nbuf <= 0){ 163 | /* probably because window has exited, and only called by wineventproc, so just shut down */ 164 | windecref(w); 165 | threadexits(nil); 166 | } 167 | w->bufp = w->buf; 168 | } 169 | w->nbuf--; 170 | return *w->bufp++; 171 | } 172 | 173 | int 174 | wingeten(Window *w) 175 | { 176 | int n, c; 177 | 178 | n = 0; 179 | while('0'<=(c=wingetec(w)) && c<='9') 180 | n = n*10+(c-'0'); 181 | if(c != ' ') 182 | error("event number syntax"); 183 | return n; 184 | } 185 | 186 | int 187 | wingeter(Window *w, char *buf, int *nb) 188 | { 189 | Rune r; 190 | int n; 191 | 192 | r = wingetec(w); 193 | buf[0] = r; 194 | n = 1; 195 | if(r >= Runeself) { 196 | while(!fullrune(buf, n)) 197 | buf[n++] = wingetec(w); 198 | chartorune(&r, buf); 199 | } 200 | *nb = n; 201 | return r; 202 | } 203 | 204 | void 205 | wingetevent(Window *w, Event *e) 206 | { 207 | int i, nb; 208 | 209 | e->c1 = wingetec(w); 210 | e->c2 = wingetec(w); 211 | e->q0 = wingeten(w); 212 | e->q1 = wingeten(w); 213 | e->flag = wingeten(w); 214 | e->nr = wingeten(w); 215 | if(e->nr > EVENTSIZE) 216 | error("event string too long"); 217 | e->nb = 0; 218 | for(i=0; inr; i++){ 219 | e->r[i] = wingeter(w, e->b+e->nb, &nb); 220 | e->nb += nb; 221 | } 222 | e->r[e->nr] = 0; 223 | e->b[e->nb] = 0; 224 | if(wingetec(w) != '\n') 225 | error("event syntax error"); 226 | } 227 | 228 | void 229 | winwriteevent(Window *w, Event *e) 230 | { 231 | fsprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1); 232 | } 233 | 234 | void 235 | winread(Window *w, uint q0, uint q1, char *data) 236 | { 237 | int m, n, nr; 238 | char buf[256]; 239 | 240 | if(w->addr == nil) 241 | w->addr = winopenfile(w, "addr"); 242 | if(w->data == nil) 243 | w->data = winopenfile(w, "data"); 244 | m = q0; 245 | while(m < q1){ 246 | n = sprint(buf, "#%d", m); 247 | if(fswrite(w->addr, buf, n) != n) 248 | error("error writing addr: %r"); 249 | n = fsread(w->data, buf, sizeof buf); 250 | if(n <= 0) 251 | error("reading data: %r"); 252 | nr = utfnlen(buf, n); 253 | while(m+nr >q1){ 254 | do; while(n>0 && (buf[--n]&0xC0)==0x80); 255 | --nr; 256 | } 257 | if(n == 0) 258 | break; 259 | memmove(data, buf, n); 260 | data += n; 261 | *data = 0; 262 | m += nr; 263 | } 264 | } 265 | 266 | void 267 | windormant(Window *w) 268 | { 269 | if(w->addr != nil){ 270 | fsclose(w->addr); 271 | w->addr = nil; 272 | } 273 | if(w->body != nil){ 274 | fsclose(w->body); 275 | w->body = nil; 276 | } 277 | if(w->data != nil){ 278 | fsclose(w->data); 279 | w->data = nil; 280 | } 281 | } 282 | 283 | 284 | int 285 | windel(Window *w, int sure) 286 | { 287 | if(sure) 288 | fswrite(w->ctl, "delete\n", 7); 289 | else if(fswrite(w->ctl, "del\n", 4) != 4) 290 | return 0; 291 | /* event proc will die due to read error from event file */ 292 | windormant(w); 293 | fsclose(w->ctl); 294 | w->ctl = nil; 295 | return 1; 296 | } 297 | 298 | void 299 | winclean(Window *w) 300 | { 301 | ctlprint(w->ctl, "clean\n"); 302 | } 303 | 304 | int 305 | winsetaddr(Window *w, char *addr, int errok) 306 | { 307 | if(w->addr == nil) 308 | w->addr = winopenfile(w, "addr"); 309 | if(fswrite(w->addr, addr, strlen(addr)) < 0){ 310 | if(!errok) 311 | error("error writing addr(%s): %r", addr); 312 | return 0; 313 | } 314 | return 1; 315 | } 316 | 317 | int 318 | winselect(Window *w, char *addr, int errok) 319 | { 320 | if(winsetaddr(w, addr, errok)){ 321 | ctlprint(w->ctl, "dot=addr\n"); 322 | return 1; 323 | } 324 | return 0; 325 | } 326 | 327 | char* 328 | winreadbody(Window *w, int *np) /* can't use readfile because acme doesn't report the length */ 329 | { 330 | char *s; 331 | int m, na, n; 332 | 333 | if(w->body != nil) 334 | winclosebody(w); 335 | winopenbody(w, OREAD); 336 | s = nil; 337 | na = 0; 338 | n = 0; 339 | for(;;){ 340 | if(na < n+512){ 341 | na += 1024; 342 | s = realloc(s, na+1); 343 | } 344 | m = fsread(w->body, s+n, na-n); 345 | if(m <= 0) 346 | break; 347 | n += m; 348 | } 349 | s[n] = 0; 350 | winclosebody(w); 351 | *np = n; 352 | return s; 353 | } 354 | 355 | char* 356 | winselection(Window *w) 357 | { 358 | int m, n; 359 | char *buf; 360 | char tmp[256]; 361 | CFid* fid; 362 | 363 | fid = winopenfile1(w, "rdsel", OREAD); 364 | if(fid == nil) 365 | error("can't open rdsel: %r"); 366 | n = 0; 367 | buf = nil; 368 | for(;;){ 369 | m = fsread(fid, tmp, sizeof tmp); 370 | if(m <= 0) 371 | break; 372 | buf = erealloc(buf, n+m+1); 373 | memmove(buf+n, tmp, m); 374 | n += m; 375 | buf[n] = '\0'; 376 | } 377 | fsclose(fid); 378 | return buf; 379 | } 380 | -------------------------------------------------------------------------------- /acme/mail/win.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreasBriese/acmePimp/95305fa1f2ee0a59220d6ed97fbe1f534e417b3b/acme/mail/win.o -------------------------------------------------------------------------------- /acme/mkfile: -------------------------------------------------------------------------------- 1 | <$PLAN9/src/mkhdr 2 | 3 | TARG=acme 4 | DIRS=mail 5 | 6 | OFILES=\ 7 | acme.$O\ 8 | addr.$O\ 9 | buff.$O\ 10 | cols.$O\ 11 | disk.$O\ 12 | ecmd.$O\ 13 | edit.$O\ 14 | elog.$O\ 15 | exec.$O\ 16 | file.$O\ 17 | fsys.$O\ 18 | logf.$O\ 19 | look.$O\ 20 | regx.$O\ 21 | rows.$O\ 22 | scrl.$O\ 23 | text.$O\ 24 | time.$O\ 25 | util.$O\ 26 | wind.$O\ 27 | xfid.$O\ 28 | 29 | HFILES=dat.h\ 30 | edit.h\ 31 | fns.h\ 32 | 33 | <$PLAN9/src/mkone 34 | <$PLAN9/src/mkdirs 35 | 36 | edit.$O ecmd.$O elog.$O: edit.h 37 | 38 | likeplan9:V: 39 | mkdir -p likeplan9 40 | rm -f likeplan9/* 41 | for i in *.c 42 | do 43 | 9 sed 's/->(fcall|lk|b|fr|ref|m|u|u1)\./->/g; 44 | s/\.(fcall|lk|b|fr|ref|m|u|u1)([^a-zA-Z0-9_])/\2/g 45 | s/&(([a-zA-Z0-9_]|->|\.)*)->(fcall|lk|b|fr|ref|m|u|u1)([^a-zA-Z0-9_])/\1\4/g 46 | s/range\(([^,()]+), ([^,()]+)\)/(Range){\1, \2}/g 47 | ' $i >likeplan9/$i 48 | done 49 | 50 | diffplan9:V: 51 | mk likeplan9 52 | 9 diff -n plan9 likeplan9 | sed 's;likeplan9/;;' 53 | -------------------------------------------------------------------------------- /acme/rows.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "dat.h" 13 | #include "fns.h" 14 | 15 | static Rune Lcolhdr[] = { 16 | 'N', 'e', 'w', 'c', 'o', 'l', ' ', 17 | 'K', 'i', 'l', 'l', ' ', 18 | 'P', 'u', 't', 'a', 'l', 'l', ' ', 19 | 'D', 'u', 'm', 'p', ' ', 20 | 'E', 'x', 'i', 't', ' ', 21 | 0 22 | }; 23 | 24 | void 25 | rowinit(Row *row, Rectangle r) 26 | { 27 | Rectangle r1; 28 | Text *t; 29 | 30 | draw(screen, r, display->white, nil, ZP); 31 | row->r = r; 32 | row->col = nil; 33 | row->ncol = 0; 34 | r1 = r; 35 | r1.max.y = r1.min.y + font->height; 36 | t = &row->tag; 37 | textinit(t, fileaddtext(nil, t), r1, rfget(FALSE, FALSE, FALSE, nil), tagcols); 38 | t->what = Rowtag; 39 | t->row = row; 40 | t->w = nil; 41 | t->col = nil; 42 | r1.min.y = r1.max.y; 43 | r1.max.y += Border; 44 | draw(screen, r1, display->black, nil, ZP); 45 | textinsert(t, 0, Lcolhdr, 29, TRUE); 46 | textsetselect(t, t->file->b.nc, t->file->b.nc); 47 | } 48 | 49 | Column* 50 | rowadd(Row *row, Column *c, int x) 51 | { 52 | Rectangle r, r1; 53 | Column *d; 54 | int i; 55 | 56 | d = nil; 57 | r = row->r; 58 | r.min.y = row->tag.fr.r.max.y+Border; 59 | if(xncol>0){ /*steal 40% of last column by default */ 60 | d = row->col[row->ncol-1]; 61 | x = d->r.min.x + 3*Dx(d->r)/5; 62 | } 63 | /* look for column we'll land on */ 64 | for(i=0; incol; i++){ 65 | d = row->col[i]; 66 | if(x < d->r.max.x) 67 | break; 68 | } 69 | if(row->ncol > 0){ 70 | if(i < row->ncol) 71 | i++; /* new column will go after d */ 72 | r = d->r; 73 | if(Dx(r) < 100) 74 | return nil; 75 | draw(screen, r, display->white, nil, ZP); 76 | r1 = r; 77 | r1.max.x = min(x-Border, r.max.x-50); 78 | if(Dx(r1) < 50) 79 | r1.max.x = r1.min.x+50; 80 | colresize(d, r1); 81 | r1.min.x = r1.max.x; 82 | r1.max.x = r1.min.x+Border; 83 | draw(screen, r1, display->black, nil, ZP); 84 | r.min.x = r1.max.x; 85 | } 86 | if(c == nil){ 87 | c = emalloc(sizeof(Column)); 88 | colinit(c, r); 89 | incref(&reffont.ref); 90 | }else 91 | colresize(c, r); 92 | c->row = row; 93 | c->tag.row = row; 94 | row->col = realloc(row->col, (row->ncol+1)*sizeof(Column*)); 95 | memmove(row->col+i+1, row->col+i, (row->ncol-i)*sizeof(Column*)); 96 | row->col[i] = c; 97 | row->ncol++; 98 | clearmouse(); 99 | return c; 100 | } 101 | 102 | void 103 | rowresize(Row *row, Rectangle r) 104 | { 105 | int i, deltax; 106 | Rectangle or, r1, r2; 107 | Column *c; 108 | 109 | or = row->r; 110 | deltax = r.min.x - or.min.x; 111 | row->r = r; 112 | r1 = r; 113 | r1.max.y = r1.min.y + font->height; 114 | textresize(&row->tag, r1, TRUE); 115 | r1.min.y = r1.max.y; 116 | r1.max.y += Border; 117 | draw(screen, r1, display->black, nil, ZP); 118 | r.min.y = r1.max.y; 119 | r1 = r; 120 | r1.max.x = r1.min.x; 121 | for(i=0; incol; i++){ 122 | c = row->col[i]; 123 | r1.min.x = r1.max.x; 124 | /* the test should not be necessary, but guarantee we don't lose a pixel */ 125 | if(i == row->ncol-1) 126 | r1.max.x = r.max.x; 127 | else 128 | r1.max.x = (c->r.max.x-or.min.x)*Dx(r)/Dx(or) + deltax; 129 | if(i > 0){ 130 | r2 = r1; 131 | r2.max.x = r2.min.x+Border; 132 | draw(screen, r2, display->black, nil, ZP); 133 | r1.min.x = r2.max.x; 134 | } 135 | colresize(c, r1); 136 | } 137 | } 138 | 139 | void 140 | rowdragcol(Row *row, Column *c, int _0) 141 | { 142 | Rectangle r; 143 | int i, b, x; 144 | Point p, op; 145 | Column *d; 146 | 147 | USED(_0); 148 | 149 | clearmouse(); 150 | setcursor(mousectl, &boxcursor); 151 | b = mouse->buttons; 152 | op = mouse->xy; 153 | while(mouse->buttons == b) 154 | readmouse(mousectl); 155 | setcursor(mousectl, nil); 156 | if(mouse->buttons){ 157 | while(mouse->buttons) 158 | readmouse(mousectl); 159 | return; 160 | } 161 | 162 | for(i=0; incol; i++) 163 | if(row->col[i] == c) 164 | goto Found; 165 | error("can't find column"); 166 | 167 | Found: 168 | p = mouse->xy; 169 | if((abs(p.x-op.x)<5 && abs(p.y-op.y)<5)) 170 | return; 171 | if((i>0 && p.xcol[i-1]->r.min.x) || (incol-1 && p.x>c->r.max.x)){ 172 | /* shuffle */ 173 | x = c->r.min.x; 174 | rowclose(row, c, FALSE); 175 | if(rowadd(row, c, p.x) == nil) /* whoops! */ 176 | if(rowadd(row, c, x) == nil) /* WHOOPS! */ 177 | if(rowadd(row, c, -1)==nil){ /* shit! */ 178 | rowclose(row, c, TRUE); 179 | return; 180 | } 181 | colmousebut(c); 182 | return; 183 | } 184 | if(i == 0) 185 | return; 186 | d = row->col[i-1]; 187 | if(p.x < d->r.min.x+80+Scrollwid) 188 | p.x = d->r.min.x+80+Scrollwid; 189 | if(p.x > c->r.max.x-80-Scrollwid) 190 | p.x = c->r.max.x-80-Scrollwid; 191 | r = d->r; 192 | r.max.x = c->r.max.x; 193 | draw(screen, r, display->white, nil, ZP); 194 | r.max.x = p.x; 195 | colresize(d, r); 196 | r = c->r; 197 | r.min.x = p.x; 198 | r.max.x = r.min.x; 199 | r.max.x += Border; 200 | draw(screen, r, display->black, nil, ZP); 201 | r.min.x = r.max.x; 202 | r.max.x = c->r.max.x; 203 | colresize(c, r); 204 | colmousebut(c); 205 | } 206 | 207 | void 208 | rowclose(Row *row, Column *c, int dofree) 209 | { 210 | Rectangle r; 211 | int i; 212 | 213 | for(i=0; incol; i++) 214 | if(row->col[i] == c) 215 | goto Found; 216 | error("can't find column"); 217 | Found: 218 | r = c->r; 219 | if(dofree) 220 | colcloseall(c); 221 | row->ncol--; 222 | memmove(row->col+i, row->col+i+1, (row->ncol-i)*sizeof(Column*)); 223 | row->col = realloc(row->col, row->ncol*sizeof(Column*)); 224 | if(row->ncol == 0){ 225 | draw(screen, r, display->white, nil, ZP); 226 | return; 227 | } 228 | if(i == row->ncol){ /* extend last column right */ 229 | c = row->col[i-1]; 230 | r.min.x = c->r.min.x; 231 | r.max.x = row->r.max.x; 232 | }else{ /* extend next window left */ 233 | c = row->col[i]; 234 | r.max.x = c->r.max.x; 235 | } 236 | draw(screen, r, display->white, nil, ZP); 237 | colresize(c, r); 238 | } 239 | 240 | Column* 241 | rowwhichcol(Row *row, Point p) 242 | { 243 | int i; 244 | Column *c; 245 | 246 | for(i=0; incol; i++){ 247 | c = row->col[i]; 248 | if(ptinrect(p, c->r)) 249 | return c; 250 | } 251 | return nil; 252 | } 253 | 254 | Text* 255 | rowwhich(Row *row, Point p) 256 | { 257 | Column *c; 258 | 259 | if(ptinrect(p, row->tag.all)) 260 | return &row->tag; 261 | c = rowwhichcol(row, p); 262 | if(c) 263 | return colwhich(c, p); 264 | return nil; 265 | } 266 | 267 | Text* 268 | rowtype(Row *row, Rune r, Point p) 269 | { 270 | Window *w; 271 | Text *t; 272 | 273 | if(r == 0) 274 | r = Runeerror; 275 | 276 | clearmouse(); 277 | qlock(&row->lk); 278 | if(bartflag) 279 | t = barttext; 280 | else 281 | t = rowwhich(row, p); 282 | if(t!=nil && !(t->what==Tag && ptinrect(p, t->scrollr))){ 283 | w = t->w; 284 | if(w == nil) 285 | texttype(t, r); 286 | else{ 287 | winlock(w, 'K'); 288 | wintype(w, t, r); 289 | /* Expand tag if necessary */ 290 | if(t->what == Tag){ 291 | t->w->tagsafe = FALSE; 292 | if(r == '\n') 293 | t->w->tagexpand = TRUE; 294 | winresize(w, w->r, TRUE, TRUE); 295 | } 296 | winunlock(w); 297 | } 298 | } 299 | qunlock(&row->lk); 300 | return t; 301 | } 302 | 303 | int 304 | rowclean(Row *row) 305 | { 306 | int clean; 307 | int i; 308 | 309 | clean = TRUE; 310 | for(i=0; incol; i++) 311 | clean &= colclean(row->col[i]); 312 | return clean; 313 | } 314 | 315 | void 316 | rowdump(Row *row, char *file) 317 | { 318 | int i, j, fd, m, n, dumped; 319 | uint q0, q1; 320 | Biobuf *b; 321 | char *buf, *a, *fontname; 322 | Rune *r; 323 | Column *c; 324 | Window *w, *w1; 325 | Text *t; 326 | 327 | if(row->ncol == 0) 328 | return; 329 | buf = fbufalloc(); 330 | if(file == nil){ 331 | if(home == nil){ 332 | warning(nil, "can't find file for dump: $home not defined\n"); 333 | goto Rescue; 334 | } 335 | sprint(buf, "%s/acme.dump", home); 336 | file = buf; 337 | } 338 | fd = create(file, OWRITE, 0600); 339 | if(fd < 0){ 340 | warning(nil, "can't open %s: %r\n", file); 341 | goto Rescue; 342 | } 343 | b = emalloc(sizeof(Biobuf)); 344 | Binit(b, fd, OWRITE); 345 | r = fbufalloc(); 346 | Bprint(b, "%s\n", wdir); 347 | Bprint(b, "%s\n", fontnames[0]); 348 | Bprint(b, "%s\n", fontnames[1]); 349 | for(i=0; incol; i++){ 350 | c = row->col[i]; 351 | Bprint(b, "%11.7f", 100.0*(c->r.min.x-row->r.min.x)/Dx(row->r)); 352 | if(i == row->ncol-1) 353 | Bputc(b, '\n'); 354 | else 355 | Bputc(b, ' '); 356 | } 357 | for(i=0; incol; i++){ 358 | c = row->col[i]; 359 | for(j=0; jnw; j++) 360 | c->w[j]->body.file->dumpid = 0; 361 | } 362 | m = min(RBUFSIZE, row->tag.file->b.nc); 363 | bufread(&row->tag.file->b, 0, r, m); 364 | n = 0; 365 | while(nncol; i++){ 369 | c = row->col[i]; 370 | m = min(RBUFSIZE, c->tag.file->b.nc); 371 | bufread(&c->tag.file->b, 0, r, m); 372 | n = 0; 373 | while(nncol; i++){ 378 | c = row->col[i]; 379 | for(j=0; jnw; j++){ 380 | w = c->w[j]; 381 | wincommit(w, &w->tag); 382 | t = &w->body; 383 | /* windows owned by others get special treatment */ 384 | if(w->nopen[QWevent] > 0) 385 | if(w->dumpstr == nil) 386 | continue; 387 | /* zeroxes of external windows are tossed */ 388 | if(t->file->ntext > 1) 389 | for(n=0; nfile->ntext; n++){ 390 | w1 = t->file->text[n]->w; 391 | if(w == w1) 392 | continue; 393 | if(w1->nopen[QWevent]) 394 | goto Continue2; 395 | } 396 | fontname = ""; 397 | if(t->reffont->f != font) 398 | fontname = t->reffont->f->name; 399 | if(t->file->nname) 400 | a = runetobyte(t->file->name, t->file->nname); 401 | else 402 | a = emalloc(1); 403 | if(t->file->dumpid){ 404 | dumped = FALSE; 405 | Bprint(b, "x%11d %11d %11d %11d %11.7f %s\n", i, t->file->dumpid, 406 | w->body.q0, w->body.q1, 407 | 100.0*(w->r.min.y-c->r.min.y)/Dy(c->r), 408 | fontname); 409 | }else if(w->dumpstr){ 410 | dumped = FALSE; 411 | Bprint(b, "e%11d %11d %11d %11d %11.7f %s\n", i, t->file->dumpid, 412 | 0, 0, 413 | 100.0*(w->r.min.y-c->r.min.y)/Dy(c->r), 414 | fontname); 415 | }else if((w->dirty==FALSE && access(a, 0)==0) || w->isdir){ 416 | dumped = FALSE; 417 | t->file->dumpid = w->id; 418 | Bprint(b, "f%11d %11d %11d %11d %11.7f %s\n", i, w->id, 419 | w->body.q0, w->body.q1, 420 | 100.0*(w->r.min.y-c->r.min.y)/Dy(c->r), 421 | fontname); 422 | }else{ 423 | dumped = TRUE; 424 | t->file->dumpid = w->id; 425 | Bprint(b, "F%11d %11d %11d %11d %11.7f %11d %s\n", i, j, 426 | w->body.q0, w->body.q1, 427 | 100.0*(w->r.min.y-c->r.min.y)/Dy(c->r), 428 | w->body.file->b.nc, fontname); 429 | } 430 | free(a); 431 | winctlprint(w, buf, 0); 432 | Bwrite(b, buf, strlen(buf)); 433 | m = min(RBUFSIZE, w->tag.file->b.nc); 434 | bufread(&w->tag.file->b, 0, r, m); 435 | n = 0; 436 | while(nfile->b.nc; 442 | while(q0 < q1){ 443 | n = q1 - q0; 444 | if(n > BUFSIZE/UTFmax) 445 | n = BUFSIZE/UTFmax; 446 | bufread(&t->file->b, q0, r, n); 447 | Bprint(b, "%.*S", n, r); 448 | q0 += n; 449 | } 450 | } 451 | if(w->dumpstr){ 452 | if(w->dumpdir) 453 | Bprint(b, "%s\n%s\n", w->dumpdir, w->dumpstr); 454 | else 455 | Bprint(b, "\n%s\n", w->dumpstr); 456 | } 457 | Continue2:; 458 | } 459 | } 460 | Bterm(b); 461 | close(fd); 462 | free(b); 463 | fbuffree(r); 464 | 465 | Rescue: 466 | fbuffree(buf); 467 | } 468 | 469 | static 470 | char* 471 | rdline(Biobuf *b, int *linep) 472 | { 473 | char *l; 474 | 475 | l = Brdline(b, '\n'); 476 | if(l) 477 | (*linep)++; 478 | return l; 479 | } 480 | 481 | /* 482 | * Get font names from load file so we don't load fonts we won't use 483 | */ 484 | void 485 | rowloadfonts(char *file) 486 | { 487 | int i; 488 | Biobuf *b; 489 | char *l; 490 | 491 | b = Bopen(file, OREAD); 492 | if(b == nil) 493 | return; 494 | /* current directory */ 495 | l = Brdline(b, '\n'); 496 | if(l == nil) 497 | goto Return; 498 | /* global fonts */ 499 | for(i=0; i<2; i++){ 500 | l = Brdline(b, '\n'); 501 | if(l == nil) 502 | goto Return; 503 | l[Blinelen(b)-1] = 0; 504 | if(*l && strcmp(l, fontnames[i])!=0){ 505 | free(fontnames[i]); 506 | fontnames[i] = estrdup(l); 507 | } 508 | } 509 | Return: 510 | Bterm(b); 511 | } 512 | 513 | int 514 | rowload(Row *row, char *file, int initing) 515 | { 516 | int i, j, line, y, nr, nfontr, n, ns, ndumped, dumpid, x, fd, done; 517 | double percent; 518 | Biobuf *b, *bout; 519 | char *buf, *l, *t, *fontname; 520 | Rune *r, *fontr; 521 | int rune; 522 | Column *c, *c1, *c2; 523 | uint q0, q1; 524 | Rectangle r1, r2; 525 | Window *w; 526 | 527 | buf = fbufalloc(); 528 | if(file == nil){ 529 | if(home == nil){ 530 | warning(nil, "can't find file for load: $home not defined\n"); 531 | goto Rescue1; 532 | } 533 | sprint(buf, "%s/acme.dump", home); 534 | file = buf; 535 | } 536 | b = Bopen(file, OREAD); 537 | if(b == nil){ 538 | warning(nil, "can't open load file %s: %r\n", file); 539 | goto Rescue1; 540 | } 541 | /* current directory */ 542 | line = 0; 543 | l = rdline(b, &line); 544 | if(l == nil) 545 | goto Rescue2; 546 | l[Blinelen(b)-1] = 0; 547 | if(chdir(l) < 0){ 548 | warning(nil, "can't chdir %s\n", l); 549 | goto Rescue2; 550 | } 551 | /* global fonts */ 552 | for(i=0; i<2; i++){ 553 | l = rdline(b, &line); 554 | if(l == nil) 555 | goto Rescue2; 556 | l[Blinelen(b)-1] = 0; 557 | if(*l && strcmp(l, fontnames[i])!=0) 558 | rfget(i, TRUE, i==0 && initing, l); 559 | } 560 | if(initing && row->ncol==0) 561 | rowinit(row, screen->clipr); 562 | l = rdline(b, &line); 563 | if(l == nil) 564 | goto Rescue2; 565 | j = Blinelen(b)/12; 566 | if(j<=0 || j>10) 567 | goto Rescue2; 568 | for(i=0; i=100) 571 | goto Rescue2; 572 | x = row->r.min.x+percent*Dx(row->r)/100+0.5; 573 | if(i < row->ncol){ 574 | if(i == 0) 575 | continue; 576 | c1 = row->col[i-1]; 577 | c2 = row->col[i]; 578 | r1 = c1->r; 579 | r2 = c2->r; 580 | if(xwhite, nil, ZP); 587 | colresize(c1, r1); 588 | colresize(c2, r2); 589 | r2.min.x = x-Border; 590 | r2.max.x = x; 591 | draw(screen, r2, display->black, nil, ZP); 592 | } 593 | if(i >= row->ncol) 594 | rowadd(row, nil, x); 595 | } 596 | done = 0; 597 | while(!done){ 598 | l = rdline(b, &line); 599 | if(l == nil) 600 | break; 601 | switch(l[0]){ 602 | case 'c': 603 | l[Blinelen(b)-1] = 0; 604 | i = atoi(l+1+0*12); 605 | r = bytetorune(l+1*12, &nr); 606 | ns = -1; 607 | for(n=0; ncol[i]->tag, 0, row->col[i]->tag.file->b.nc, TRUE); 614 | textinsert(&row->col[i]->tag, 0, r+n+1, nr-(n+1), TRUE); 615 | break; 616 | case 'w': 617 | l[Blinelen(b)-1] = 0; 618 | r = bytetorune(l+2, &nr); 619 | ns = -1; 620 | for(n=0; ntag, 0, row->tag.file->b.nc, TRUE); 627 | textinsert(&row->tag, 0, r, nr, TRUE); 628 | break; 629 | default: 630 | done = 1; 631 | break; 632 | } 633 | } 634 | for(;;){ 635 | if(l == nil) 636 | break; 637 | dumpid = 0; 638 | switch(l[0]){ 639 | case 'e': 640 | if(Blinelen(b) < 1+5*12+1) 641 | goto Rescue2; 642 | l = rdline(b, &line); /* ctl line; ignored */ 643 | if(l == nil) 644 | goto Rescue2; 645 | l = rdline(b, &line); /* directory */ 646 | if(l == nil) 647 | goto Rescue2; 648 | l[Blinelen(b)-1] = 0; 649 | if(*l == '\0'){ 650 | if(home == nil) 651 | r = bytetorune("./", &nr); 652 | else{ 653 | t = emalloc(strlen(home)+1+1); 654 | sprint(t, "%s/", home); 655 | r = bytetorune(t, &nr); 656 | free(t); 657 | } 658 | }else 659 | r = bytetorune(l, &nr); 660 | l = rdline(b, &line); /* command */ 661 | if(l == nil) 662 | goto Rescue2; 663 | t = emalloc(Blinelen(b)+1); 664 | memmove(t, l, Blinelen(b)); 665 | run(nil, t, r, nr, TRUE, nil, nil, FALSE); 666 | /* r is freed in run() */ 667 | goto Nextline; 668 | case 'f': 669 | if(Blinelen(b) < 1+5*12+1) 670 | goto Rescue2; 671 | fontname = l+1+5*12; 672 | ndumped = -1; 673 | break; 674 | case 'F': 675 | if(Blinelen(b) < 1+6*12+1) 676 | goto Rescue2; 677 | fontname = l+1+6*12; 678 | ndumped = atoi(l+1+5*12+1); 679 | break; 680 | case 'x': 681 | if(Blinelen(b) < 1+5*12+1) 682 | goto Rescue2; 683 | fontname = l+1+5*12; 684 | ndumped = -1; 685 | dumpid = atoi(l+1+1*12); 686 | break; 687 | default: 688 | goto Rescue2; 689 | } 690 | l[Blinelen(b)-1] = 0; 691 | fontr = nil; 692 | nfontr = 0; 693 | if(*fontname) 694 | fontr = bytetorune(fontname, &nfontr); 695 | i = atoi(l+1+0*12); 696 | j = atoi(l+1+1*12); 697 | q0 = atoi(l+1+2*12); 698 | q1 = atoi(l+1+3*12); 699 | percent = atof(l+1+4*12); 700 | if(i<0 || i>10) 701 | goto Rescue2; 702 | if(i > row->ncol) 703 | i = row->ncol; 704 | c = row->col[i]; 705 | y = c->r.min.y+(percent*Dy(c->r))/100+0.5; 706 | if(yr.min.y || y>=c->r.max.y) 707 | y = -1; 708 | if(dumpid == 0) 709 | w = coladd(c, nil, nil, y); 710 | else 711 | w = coladd(c, nil, lookid(dumpid, TRUE), y); 712 | if(w == nil) 713 | goto Nextline; 714 | w->dumpid = j; 715 | l = rdline(b, &line); 716 | if(l == nil) 717 | goto Rescue2; 718 | l[Blinelen(b)-1] = 0; 719 | r = bytetorune(l+5*12, &nr); 720 | ns = -1; 721 | for(n=0; ntag, w->tag.file->b.nc, r+n+1, nr-(n+1), TRUE); 734 | if(ndumped >= 0){ 735 | /* simplest thing is to put it in a file and load that */ 736 | sprint(buf, "/tmp/d%d.%.4sacme", getpid(), getuser()); 737 | fd = create(buf, OWRITE, 0600); 738 | if(fd < 0){ 739 | free(r); 740 | warning(nil, "can't create temp file: %r\n"); 741 | goto Rescue2; 742 | } 743 | bout = emalloc(sizeof(Biobuf)); 744 | Binit(bout, fd, OWRITE); 745 | for(n=0; nbody, 0, buf, 1); 762 | remove(buf); 763 | close(fd); 764 | w->body.file->mod = TRUE; 765 | for(n=0; nbody.file->ntext; n++) 766 | w->body.file->text[n]->w->dirty = TRUE; 767 | winsettag(w); 768 | }else if(dumpid==0 && r[ns+1]!='+' && r[ns+1]!='-') 769 | get(&w->body, nil, nil, FALSE, XXX, nil, 0); 770 | if(fontr){ 771 | fontx(&w->body, nil, nil, 0, 0, fontr, nfontr); 772 | free(fontr); 773 | } 774 | free(r); 775 | if(q0>w->body.file->b.nc || q1>w->body.file->b.nc || q0>q1) 776 | q0 = q1 = 0; 777 | textshow(&w->body, q0, q1, 1); 778 | w->maxlines = min(w->body.fr.nlines, max(w->maxlines, w->body.fr.maxlines)); 779 | xfidlog(w, "new"); 780 | Nextline: 781 | l = rdline(b, &line); 782 | } 783 | Bterm(b); 784 | fbuffree(buf); 785 | return TRUE; 786 | 787 | Rescue2: 788 | warning(nil, "bad load file %s:%d\n", file, line); 789 | Bterm(b); 790 | Rescue1: 791 | fbuffree(buf); 792 | return FALSE; 793 | } 794 | 795 | void 796 | allwindows(void (*f)(Window*, void*), void *arg) 797 | { 798 | int i, j; 799 | Column *c; 800 | 801 | for(i=0; inw; j++) 804 | (*f)(c->w[j], arg); 805 | } 806 | } 807 | -------------------------------------------------------------------------------- /acme/scrl.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "dat.h" 12 | #include "fns.h" 13 | 14 | static Image *scrtmp; 15 | 16 | static 17 | Rectangle 18 | scrpos(Rectangle r, uint p0, uint p1, uint tot) 19 | { 20 | Rectangle q; 21 | int h; 22 | 23 | q = r; 24 | h = q.max.y-q.min.y; 25 | if(tot == 0) 26 | return q; 27 | if(tot > 1024*1024){ 28 | tot>>=10; 29 | p0>>=10; 30 | p1>>=10; 31 | } 32 | if(p0 > 0) 33 | q.min.y += h*p0/tot; 34 | if(p1 < tot) 35 | q.max.y -= h*(tot-p1)/tot; 36 | if(q.max.y < q.min.y+2){ 37 | if(q.min.y+2 <= r.max.y) 38 | q.max.y = q.min.y+2; 39 | else 40 | q.min.y = q.max.y-2; 41 | } 42 | return q; 43 | } 44 | 45 | void 46 | scrlresize(void) 47 | { 48 | freeimage(scrtmp); 49 | scrtmp = allocimage(display, Rect(0, 0, 32, screen->r.max.y), screen->chan, 0, DNofill); 50 | if(scrtmp == nil) 51 | error("scroll alloc"); 52 | } 53 | 54 | void 55 | textscrdraw(Text *t) 56 | { 57 | Rectangle r, r1, r2; 58 | Image *b; 59 | 60 | if(t->w==nil || t!=&t->w->body) 61 | return; 62 | if(scrtmp == nil) 63 | scrlresize(); 64 | r = t->scrollr; 65 | b = scrtmp; 66 | r1 = r; 67 | r1.min.x = 0; 68 | r1.max.x = Dx(r); 69 | r2 = scrpos(r1, t->org, t->org+t->fr.nchars, t->file->b.nc); 70 | if(!eqrect(r2, t->lastsr)){ 71 | t->lastsr = r2; 72 | draw(b, r1, t->fr.cols[BORD], nil, ZP); 73 | draw(b, r2, t->fr.cols[BACK], nil, ZP); 74 | r2.min.x = r2.max.x-1; 75 | draw(b, r2, t->fr.cols[BORD], nil, ZP); 76 | draw(t->fr.b, r, b, nil, Pt(0, r1.min.y)); 77 | /*flushimage(display, 1); // BUG? */ 78 | } 79 | } 80 | 81 | void 82 | scrsleep(uint dt) 83 | { 84 | Timer *timer; 85 | static Alt alts[3]; 86 | 87 | timer = timerstart(dt); 88 | alts[0].c = timer->c; 89 | alts[0].v = nil; 90 | alts[0].op = CHANRCV; 91 | alts[1].c = mousectl->c; 92 | alts[1].v = &mousectl->m; 93 | alts[1].op = CHANRCV; 94 | alts[2].op = CHANEND; 95 | for(;;) 96 | switch(alt(alts)){ 97 | case 0: 98 | timerstop(timer); 99 | return; 100 | case 1: 101 | timercancel(timer); 102 | return; 103 | } 104 | } 105 | 106 | void 107 | textscroll(Text *t, int but) 108 | { 109 | uint p0, oldp0; 110 | Rectangle s; 111 | int x, y, my, h, first; 112 | 113 | s = insetrect(t->scrollr, 1); 114 | h = s.max.y-s.min.y; 115 | x = (s.min.x+s.max.x)/2; 116 | oldp0 = ~0; 117 | first = TRUE; 118 | do{ 119 | flushimage(display, 1); 120 | my = mouse->xy.y; 121 | if(my < s.min.y) 122 | my = s.min.y; 123 | if(my >= s.max.y) 124 | my = s.max.y; 125 | if(!eqpt(mouse->xy, Pt(x, my))){ 126 | moveto(mousectl, Pt(x, my)); 127 | readmouse(mousectl); /* absorb event generated by moveto() */ 128 | } 129 | if(but == 2){ 130 | y = my; 131 | p0 = (vlong)t->file->b.nc*(y-s.min.y)/h; 132 | if(p0 >= t->q1) 133 | p0 = textbacknl(t, p0, 2); 134 | if(oldp0 != p0) 135 | textsetorigin(t, p0, FALSE); 136 | oldp0 = p0; 137 | readmouse(mousectl); 138 | continue; 139 | } 140 | if(but == 1) 141 | p0 = textbacknl(t, t->org, (my-s.min.y)/t->fr.font->height); 142 | else 143 | p0 = t->org+frcharofpt(&t->fr, Pt(s.max.x, my)); 144 | if(oldp0 != p0) 145 | textsetorigin(t, p0, TRUE); 146 | oldp0 = p0; 147 | /* debounce */ 148 | if(first){ 149 | flushimage(display, 1); 150 | sleep(200); 151 | nbrecv(mousectl->c, &mousectl->m); 152 | first = FALSE; 153 | } 154 | scrsleep(80); 155 | }while(mouse->buttons & (1<<(but-1))); 156 | while(mouse->buttons) 157 | readmouse(mousectl); 158 | } 159 | -------------------------------------------------------------------------------- /acme/time.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "dat.h" 12 | #include "fns.h" 13 | 14 | static Channel* ctimer; /* chan(Timer*)[100] */ 15 | static Timer *timer; 16 | 17 | static 18 | uint 19 | msec(void) 20 | { 21 | return nsec()/1000000; 22 | } 23 | 24 | void 25 | timerstop(Timer *t) 26 | { 27 | t->next = timer; 28 | timer = t; 29 | } 30 | 31 | void 32 | timercancel(Timer *t) 33 | { 34 | t->cancel = TRUE; 35 | } 36 | 37 | static 38 | void 39 | timerproc(void *v) 40 | { 41 | int i, nt, na, dt, del; 42 | Timer **t, *x; 43 | uint old, new; 44 | 45 | USED(v); 46 | threadsetname("timerproc"); 47 | rfork(RFFDG); 48 | t = nil; 49 | na = 0; 50 | nt = 0; 51 | old = msec(); 52 | for(;;){ 53 | sleep(10); /* longer sleeps here delay recv on ctimer, but 10ms should not be noticeable */ 54 | new = msec(); 55 | dt = new-old; 56 | old = new; 57 | if(dt < 0) /* timer wrapped; go around, losing a tick */ 58 | continue; 59 | for(i=0; idt -= dt; 62 | del = FALSE; 63 | if(x->cancel){ 64 | timerstop(x); 65 | del = TRUE; 66 | }else if(x->dt <= 0){ 67 | /* 68 | * avoid possible deadlock if client is 69 | * now sending on ctimer 70 | */ 71 | if(nbsendul(x->c, 0) > 0) 72 | del = TRUE; 73 | } 74 | if(del){ 75 | memmove(&t[i], &t[i+1], (nt-i-1)*sizeof t[0]); 76 | --nt; 77 | --i; 78 | } 79 | } 80 | if(nt == 0){ 81 | x = recvp(ctimer); 82 | gotit: 83 | if(nt == na){ 84 | na += 10; 85 | t = realloc(t, na*sizeof(Timer*)); 86 | if(t == nil) 87 | error("timer realloc failed"); 88 | } 89 | t[nt++] = x; 90 | old = msec(); 91 | } 92 | if(nbrecv(ctimer, &x) > 0) 93 | goto gotit; 94 | } 95 | } 96 | 97 | void 98 | timerinit(void) 99 | { 100 | ctimer = chancreate(sizeof(Timer*), 100); 101 | chansetname(ctimer, "ctimer"); 102 | proccreate(timerproc, nil, STACK); 103 | } 104 | 105 | Timer* 106 | timerstart(int dt) 107 | { 108 | Timer *t; 109 | 110 | t = timer; 111 | if(t) 112 | timer = timer->next; 113 | else{ 114 | t = emalloc(sizeof(Timer)); 115 | t->c = chancreate(sizeof(int), 0); 116 | chansetname(t->c, "tc%p", t->c); 117 | } 118 | t->next = nil; 119 | t->dt = dt; 120 | t->cancel = FALSE; 121 | sendp(ctimer, t); 122 | return t; 123 | } 124 | -------------------------------------------------------------------------------- /acme/util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "dat.h" 12 | #include "fns.h" 13 | 14 | static Point prevmouse; 15 | static Window *mousew; 16 | 17 | Range 18 | range(int q0, int q1) 19 | { 20 | Range r; 21 | 22 | r.q0 = q0; 23 | r.q1 = q1; 24 | return r; 25 | } 26 | 27 | Runestr 28 | runestr(Rune *r, uint n) 29 | { 30 | Runestr rs; 31 | 32 | rs.r = r; 33 | rs.nr = n; 34 | return rs; 35 | } 36 | 37 | void 38 | cvttorunes(char *p, int n, Rune *r, int *nb, int *nr, int *nulls) 39 | { 40 | uchar *q; 41 | Rune *s; 42 | int j, w; 43 | 44 | /* 45 | * Always guaranteed that n bytes may be interpreted 46 | * without worrying about partial runes. This may mean 47 | * reading up to UTFmax-1 more bytes than n; the caller 48 | * knows this. If n is a firm limit, the caller should 49 | * set p[n] = 0. 50 | */ 51 | q = (uchar*)p; 52 | s = r; 53 | for(j=0; jfilemenu = FALSE; 99 | winsetname(w, r, n); 100 | xfidlog(w, "new"); 101 | } 102 | free(r); 103 | for(i=nincl; --i>=0; ){ 104 | n = runestrlen(incl[i]); 105 | r = runemalloc(n); 106 | runemove(r, incl[i], n); 107 | winaddincl(w, r, n); 108 | } 109 | w->autoindent = globalautoindent; 110 | return w; 111 | } 112 | 113 | /* make new window, if necessary; return with it locked */ 114 | Window* 115 | errorwin(Mntdir *md, int owner) 116 | { 117 | Window *w; 118 | 119 | for(;;){ 120 | if(md == nil) 121 | w = errorwin1(nil, 0, nil, 0); 122 | else 123 | w = errorwin1(md->dir, md->ndir, md->incl, md->nincl); 124 | winlock(w, owner); 125 | if(w->col != nil) 126 | break; 127 | /* window was deleted too fast */ 128 | winunlock(w); 129 | } 130 | return w; 131 | } 132 | 133 | /* 134 | * Incoming window should be locked. 135 | * It will be unlocked and returned window 136 | * will be locked in its place. 137 | */ 138 | Window* 139 | errorwinforwin(Window *w) 140 | { 141 | int i, n, nincl, owner; 142 | Rune **incl; 143 | Runestr dir; 144 | Text *t; 145 | 146 | t = &w->body; 147 | dir = dirname(t, nil, 0); 148 | if(dir.nr==1 && dir.r[0]=='.'){ /* sigh */ 149 | free(dir.r); 150 | dir.r = nil; 151 | dir.nr = 0; 152 | } 153 | incl = nil; 154 | nincl = w->nincl; 155 | if(nincl > 0){ 156 | incl = emalloc(nincl*sizeof(Rune*)); 157 | for(i=0; iincl[i]); 159 | incl[i] = runemalloc(n+1); 160 | runemove(incl[i], w->incl[i], n); 161 | } 162 | } 163 | owner = w->owner; 164 | winunlock(w); 165 | for(;;){ 166 | w = errorwin1(dir.r, dir.nr, incl, nincl); 167 | winlock(w, owner); 168 | if(w->col != nil) 169 | break; 170 | /* window deleted too fast */ 171 | winunlock(w); 172 | } 173 | return w; 174 | } 175 | 176 | typedef struct Warning Warning; 177 | 178 | struct Warning{ 179 | Mntdir *md; 180 | Buffer buf; 181 | Warning *next; 182 | }; 183 | 184 | static Warning *warnings; 185 | 186 | static 187 | void 188 | addwarningtext(Mntdir *md, Rune *r, int nr) 189 | { 190 | Warning *warn; 191 | 192 | for(warn = warnings; warn; warn=warn->next){ 193 | if(warn->md == md){ 194 | bufinsert(&warn->buf, warn->buf.nc, r, nr); 195 | return; 196 | } 197 | } 198 | warn = emalloc(sizeof(Warning)); 199 | warn->next = warnings; 200 | warn->md = md; 201 | if(md) 202 | fsysincid(md); 203 | warnings = warn; 204 | bufinsert(&warn->buf, 0, r, nr); 205 | nbsendp(cwarn, 0); 206 | } 207 | 208 | /* called while row is locked */ 209 | void 210 | flushwarnings(void) 211 | { 212 | Warning *warn, *next; 213 | Window *w; 214 | Text *t; 215 | int owner, nr, q0, n; 216 | Rune *r; 217 | 218 | for(warn=warnings; warn; warn=next) { 219 | w = errorwin(warn->md, 'E'); 220 | t = &w->body; 221 | owner = w->owner; 222 | if(owner == 0) 223 | w->owner = 'E'; 224 | wincommit(w, t); 225 | /* 226 | * Most commands don't generate much output. For instance, 227 | * Edit ,>cat goes through /dev/cons and is already in blocks 228 | * because of the i/o system, but a few can. Edit ,p will 229 | * put the entire result into a single hunk. So it's worth doing 230 | * this in blocks (and putting the text in a buffer in the first 231 | * place), to avoid a big memory footprint. 232 | */ 233 | r = fbufalloc(); 234 | q0 = t->file->b.nc; 235 | for(n = 0; n < warn->buf.nc; n += nr){ 236 | nr = warn->buf.nc - n; 237 | if(nr > RBUFSIZE) 238 | nr = RBUFSIZE; 239 | bufread(&warn->buf, n, r, nr); 240 | textbsinsert(t, t->file->b.nc, r, nr, TRUE, &nr); 241 | } 242 | textshow(t, q0, t->file->b.nc, 1); 243 | free(r); 244 | winsettag(t->w); 245 | textscrdraw(t); 246 | w->owner = owner; 247 | w->dirty = FALSE; 248 | winunlock(w); 249 | bufclose(&warn->buf); 250 | next = warn->next; 251 | if(warn->md) 252 | fsysdelid(warn->md); 253 | free(warn); 254 | } 255 | warnings = nil; 256 | } 257 | 258 | void 259 | warning(Mntdir *md, char *s, ...) 260 | { 261 | Rune *r; 262 | va_list arg; 263 | 264 | va_start(arg, s); 265 | r = runevsmprint(s, arg); 266 | va_end(arg); 267 | if(r == nil) 268 | error("runevsmprint failed"); 269 | addwarningtext(md, r, runestrlen(r)); 270 | free(r); 271 | } 272 | 273 | int 274 | runeeq(Rune *s1, uint n1, Rune *s2, uint n2) 275 | { 276 | if(n1 != n2) 277 | return FALSE; 278 | return memcmp(s1, s2, n1*sizeof(Rune)) == 0; 279 | } 280 | 281 | uint 282 | min(uint a, uint b) 283 | { 284 | if(a < b) 285 | return a; 286 | return b; 287 | } 288 | 289 | uint 290 | max(uint a, uint b) 291 | { 292 | if(a > b) 293 | return a; 294 | return b; 295 | } 296 | 297 | char* 298 | runetobyte(Rune *r, int n) 299 | { 300 | char *s; 301 | 302 | if(r == nil) 303 | return nil; 304 | s = emalloc(n*UTFmax+1); 305 | setmalloctag(s, getcallerpc(&r)); 306 | snprint(s, n*UTFmax+1, "%.*S", n, r); 307 | return s; 308 | } 309 | 310 | Rune* 311 | bytetorune(char *s, int *ip) 312 | { 313 | Rune *r; 314 | int nb, nr; 315 | 316 | nb = strlen(s); 317 | r = runemalloc(nb+1); 318 | cvttorunes(s, nb, r, &nb, &nr, nil); 319 | r[nr] = '\0'; 320 | *ip = nr; 321 | return r; 322 | } 323 | 324 | int 325 | isalnum(Rune c) 326 | { 327 | /* 328 | * Hard to get absolutely right. Use what we know about ASCII 329 | * and assume anything above the Latin control characters is 330 | * potentially an alphanumeric. 331 | */ 332 | if(c <= ' ') 333 | return FALSE; 334 | if(0x7F<=c && c<=0xA0) 335 | return FALSE; 336 | if(utfrune("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", c)) 337 | return FALSE; 338 | return TRUE; 339 | } 340 | 341 | int 342 | rgetc(void *v, uint n) 343 | { 344 | return ((Rune*)v)[n]; 345 | } 346 | 347 | int 348 | tgetc(void *a, uint n) 349 | { 350 | Text *t; 351 | 352 | t = a; 353 | if(n >= t->file->b.nc) 354 | return 0; 355 | return textreadc(t, n); 356 | } 357 | 358 | Rune* 359 | skipbl(Rune *r, int n, int *np) 360 | { 361 | while(n>0 && (*r==' ' || *r=='\t' || *r=='\n')){ 362 | --n; 363 | r++; 364 | } 365 | *np = n; 366 | return r; 367 | } 368 | 369 | Rune* 370 | findbl(Rune *r, int n, int *np) 371 | { 372 | while(n>0 && *r!=' ' && *r!='\t' && *r!='\n'){ 373 | --n; 374 | r++; 375 | } 376 | *np = n; 377 | return r; 378 | } 379 | 380 | void 381 | savemouse(Window *w) 382 | { 383 | prevmouse = mouse->xy; 384 | mousew = w; 385 | } 386 | 387 | int 388 | restoremouse(Window *w) 389 | { 390 | int did; 391 | 392 | did = 0; 393 | if(mousew!=nil && mousew==w) { 394 | moveto(mousectl, prevmouse); 395 | did = 1; 396 | } 397 | mousew = nil; 398 | return did; 399 | } 400 | 401 | void 402 | clearmouse() 403 | { 404 | mousew = nil; 405 | } 406 | 407 | char* 408 | estrdup(char *s) 409 | { 410 | char *t; 411 | 412 | t = strdup(s); 413 | if(t == nil) 414 | error("strdup failed"); 415 | setmalloctag(t, getcallerpc(&s)); 416 | return t; 417 | } 418 | 419 | void* 420 | emalloc(uint n) 421 | { 422 | void *p; 423 | 424 | p = malloc(n); 425 | if(p == nil) 426 | error("malloc failed"); 427 | setmalloctag(p, getcallerpc(&n)); 428 | memset(p, 0, n); 429 | return p; 430 | } 431 | 432 | void* 433 | erealloc(void *p, uint n) 434 | { 435 | p = realloc(p, n); 436 | if(p == nil) 437 | error("realloc failed"); 438 | setmalloctag(p, getcallerpc(&n)); 439 | return p; 440 | } 441 | 442 | /* 443 | * Heuristic city. 444 | */ 445 | Window* 446 | makenewwindow(Text *t) 447 | { 448 | Column *c; 449 | Window *w, *bigw, *emptyw; 450 | Text *emptyb; 451 | int i, y, el; 452 | 453 | if(activecol) 454 | c = activecol; 455 | else if(seltext && seltext->col) 456 | c = seltext->col; 457 | else if(t && t->col) 458 | c = t->col; 459 | else{ 460 | if(row.ncol==0 && rowadd(&row, nil, -1)==nil) 461 | error("can't make column"); 462 | c = row.col[row.ncol-1]; 463 | } 464 | activecol = c; 465 | if(t==nil || t->w==nil || c->nw==0) 466 | return coladd(c, nil, nil, -1); 467 | 468 | /* find biggest window and biggest blank spot */ 469 | emptyw = c->w[0]; 470 | bigw = emptyw; 471 | for(i=1; inw; i++){ 472 | w = c->w[i]; 473 | /* use >= to choose one near bottom of screen */ 474 | if(w->body.fr.maxlines >= bigw->body.fr.maxlines) 475 | bigw = w; 476 | if(w->body.fr.maxlines-w->body.fr.nlines >= emptyw->body.fr.maxlines-emptyw->body.fr.nlines) 477 | emptyw = w; 478 | } 479 | emptyb = &emptyw->body; 480 | el = emptyb->fr.maxlines-emptyb->fr.nlines; 481 | /* if empty space is big, use it */ 482 | if(el>15 || (el>3 && el>(bigw->body.fr.maxlines-1)/2)) 483 | y = emptyb->fr.r.min.y+emptyb->fr.nlines*font->height; 484 | else{ 485 | /* if this window is in column and isn't much smaller, split it */ 486 | if(t->col==c && Dy(t->w->r)>2*Dy(bigw->r)/3) 487 | bigw = t->w; 488 | y = (bigw->r.min.y + bigw->r.max.y)/2; 489 | } 490 | w = coladd(c, nil, nil, y); 491 | if(w->body.fr.maxlines < 2) 492 | colgrow(w->col, w, 1); 493 | return w; 494 | } 495 | -------------------------------------------------------------------------------- /acme/wind.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "dat.h" 12 | #include "fns.h" 13 | 14 | int winid; 15 | 16 | void 17 | wininit(Window *w, Window *clone, Rectangle r) 18 | { 19 | Rectangle r1, br; 20 | File *f; 21 | Reffont *rf; 22 | Rune *rp; 23 | int nc; 24 | 25 | w->tag.w = w; 26 | w->taglines = 1; 27 | w->tagexpand = TRUE; 28 | w->body.w = w; 29 | w->id = ++winid; 30 | incref(&w->ref); 31 | if(globalincref) 32 | incref(&w->ref); 33 | w->ctlfid = ~0; 34 | w->utflastqid = -1; 35 | r1 = r; 36 | 37 | w->tagtop = r; 38 | w->tagtop.max.y = r.min.y + font->height; 39 | r1.max.y = r1.min.y + w->taglines*font->height; 40 | 41 | incref(&reffont.ref); 42 | f = fileaddtext(nil, &w->tag); 43 | textinit(&w->tag, f, r1, &reffont, tagcols); 44 | w->tag.what = Tag; 45 | /* tag is a copy of the contents, not a tracked image */ 46 | if(clone){ 47 | textdelete(&w->tag, 0, w->tag.file->b.nc, TRUE); 48 | nc = clone->tag.file->b.nc; 49 | rp = runemalloc(nc); 50 | bufread(&clone->tag.file->b, 0, rp, nc); 51 | textinsert(&w->tag, 0, rp, nc, TRUE); 52 | free(rp); 53 | filereset(w->tag.file); 54 | textsetselect(&w->tag, nc, nc); 55 | } 56 | r1 = r; 57 | r1.min.y += w->taglines*font->height + 1; 58 | if(r1.max.y < r1.min.y) 59 | r1.max.y = r1.min.y; 60 | f = nil; 61 | if(clone){ 62 | f = clone->body.file; 63 | w->body.org = clone->body.org; 64 | w->isscratch = clone->isscratch; 65 | rf = rfget(FALSE, FALSE, FALSE, clone->body.reffont->f->name); 66 | }else 67 | rf = rfget(FALSE, FALSE, FALSE, nil); 68 | f = fileaddtext(f, &w->body); 69 | w->body.what = Body; 70 | textinit(&w->body, f, r1, rf, textcols); 71 | r1.min.y -= 1; 72 | r1.max.y = r1.min.y+1; 73 | draw(screen, r1, tagcols[BORD], nil, ZP); 74 | textscrdraw(&w->body); 75 | w->r = r; 76 | br.min = w->tag.scrollr.min; 77 | br.max.x = br.min.x + Dx(button->r); 78 | br.max.y = br.min.y + Dy(button->r); 79 | draw(screen, br, button, nil, button->r.min); 80 | w->filemenu = TRUE; 81 | w->maxlines = w->body.fr.maxlines; 82 | w->autoindent = globalautoindent; 83 | if(clone){ 84 | w->dirty = clone->dirty; 85 | w->autoindent = clone->autoindent; 86 | textsetselect(&w->body, clone->body.q0, clone->body.q1); 87 | winsettag(w); 88 | } 89 | } 90 | 91 | /* 92 | * Draw the appropriate button. 93 | */ 94 | void 95 | windrawbutton(Window *w) 96 | { 97 | Image *b; 98 | Rectangle br; 99 | 100 | b = button; 101 | if(!w->isdir && !w->isscratch && (w->body.file->mod || w->body.ncache)) 102 | b = modbutton; 103 | br.min = w->tag.scrollr.min; 104 | br.max.x = br.min.x + Dx(b->r); 105 | br.max.y = br.min.y + Dy(b->r); 106 | draw(screen, br, b, nil, b->r.min); 107 | } 108 | 109 | int 110 | delrunepos(Window *w) 111 | { 112 | int n; 113 | Rune rune; 114 | 115 | for(n=0; ntag.file->b.nc; n++) { 116 | bufread(&w->tag.file->b, n, &rune, 1); 117 | if(rune == ' ') 118 | break; 119 | } 120 | n += 2; 121 | if(n >= w->tag.file->b.nc) 122 | return -1; 123 | return n; 124 | } 125 | 126 | void 127 | movetodel(Window *w) 128 | { 129 | int n; 130 | 131 | n = delrunepos(w); 132 | if(n < 0) 133 | return; 134 | moveto(mousectl, addpt(frptofchar(&w->tag.fr, n), Pt(4, w->tag.fr.font->height-4))); 135 | } 136 | 137 | /* 138 | * Compute number of tag lines required 139 | * to display entire tag text. 140 | */ 141 | int 142 | wintaglines(Window *w, Rectangle r) 143 | { 144 | int n; 145 | Rune rune; 146 | Point p; 147 | 148 | if(!w->tagexpand && !w->showdel) 149 | return 1; 150 | w->showdel = FALSE; 151 | w->tag.fr.noredraw = 1; 152 | textresize(&w->tag, r, TRUE); 153 | w->tag.fr.noredraw = 0; 154 | w->tagsafe = FALSE; 155 | 156 | if(!w->tagexpand) { 157 | /* use just as many lines as needed to show the Del */ 158 | n = delrunepos(w); 159 | if(n < 0) 160 | return 1; 161 | p = subpt(frptofchar(&w->tag.fr, n), w->tag.fr.r.min); 162 | return 1 + p.y / w->tag.fr.font->height; 163 | } 164 | 165 | /* can't use more than we have */ 166 | if(w->tag.fr.nlines >= w->tag.fr.maxlines) 167 | return w->tag.fr.maxlines; 168 | 169 | /* if tag ends with \n, include empty line at end for typing */ 170 | n = w->tag.fr.nlines; 171 | if(w->tag.file->b.nc > 0){ 172 | bufread(&w->tag.file->b, w->tag.file->b.nc-1, &rune, 1); 173 | if(rune == '\n') 174 | n++; 175 | } 176 | if(n == 0) 177 | n = 1; 178 | return n; 179 | } 180 | 181 | int 182 | winresize(Window *w, Rectangle r, int safe, int keepextra) 183 | { 184 | int oy, y, mouseintag, mouseinbody; 185 | Point p; 186 | Rectangle r1; 187 | 188 | mouseintag = ptinrect(mouse->xy, w->tag.all); 189 | mouseinbody = ptinrect(mouse->xy, w->body.all); 190 | 191 | /* tagtop is first line of tag */ 192 | w->tagtop = r; 193 | w->tagtop.max.y = r.min.y+font->height; 194 | 195 | r1 = r; 196 | r1.max.y = min(r.max.y, r1.min.y + w->taglines*font->height); 197 | 198 | /* If needed, recompute number of lines in tag. */ 199 | if(!safe || !w->tagsafe || !eqrect(w->tag.all, r1)){ 200 | w->taglines = wintaglines(w, r); 201 | r1.max.y = min(r.max.y, r1.min.y + w->taglines*font->height); 202 | } 203 | 204 | /* If needed, resize & redraw tag. */ 205 | y = r1.max.y; 206 | if(!safe || !w->tagsafe || !eqrect(w->tag.all, r1)){ 207 | textresize(&w->tag, r1, TRUE); 208 | y = w->tag.fr.r.max.y; 209 | windrawbutton(w); 210 | w->tagsafe = TRUE; 211 | 212 | /* If mouse is in tag, pull up as tag closes. */ 213 | if(mouseintag && !ptinrect(mouse->xy, w->tag.all)){ 214 | p = mouse->xy; 215 | p.y = w->tag.all.max.y-3; 216 | moveto(mousectl, p); 217 | } 218 | 219 | /* If mouse is in body, push down as tag expands. */ 220 | if(mouseinbody && ptinrect(mouse->xy, w->tag.all)){ 221 | p = mouse->xy; 222 | p.y = w->tag.all.max.y+3; 223 | moveto(mousectl, p); 224 | } 225 | } 226 | 227 | /* If needed, resize & redraw body. */ 228 | r1 = r; 229 | r1.min.y = y; 230 | if(!safe || !eqrect(w->body.all, r1)){ 231 | oy = y; 232 | if(y+1+w->body.fr.font->height <= r.max.y){ /* room for one line */ 233 | r1.min.y = y; 234 | r1.max.y = y+1; 235 | draw(screen, r1, tagcols[BORD], nil, ZP); 236 | y++; 237 | r1.min.y = min(y, r.max.y); 238 | r1.max.y = r.max.y; 239 | }else{ 240 | r1.min.y = y; 241 | r1.max.y = y; 242 | } 243 | y = textresize(&w->body, r1, keepextra); 244 | w->r = r; 245 | w->r.max.y = y; 246 | textscrdraw(&w->body); 247 | w->body.all.min.y = oy; 248 | } 249 | w->maxlines = min(w->body.fr.nlines, max(w->maxlines, w->body.fr.maxlines)); 250 | return w->r.max.y; 251 | } 252 | 253 | void 254 | winlock1(Window *w, int owner) 255 | { 256 | incref(&w->ref); 257 | qlock(&w->lk); 258 | w->owner = owner; 259 | } 260 | 261 | void 262 | winlock(Window *w, int owner) 263 | { 264 | int i; 265 | File *f; 266 | 267 | f = w->body.file; 268 | for(i=0; intext; i++) 269 | winlock1(f->text[i]->w, owner); 270 | } 271 | 272 | void 273 | winunlock(Window *w) 274 | { 275 | int i; 276 | File *f; 277 | 278 | /* 279 | * subtle: loop runs backwards to avoid tripping over 280 | * winclose indirectly editing f->text and freeing f 281 | * on the last iteration of the loop. 282 | */ 283 | f = w->body.file; 284 | for(i=f->ntext-1; i>=0; i--){ 285 | w = f->text[i]->w; 286 | w->owner = 0; 287 | qunlock(&w->lk); 288 | winclose(w); 289 | } 290 | } 291 | 292 | void 293 | winmousebut(Window *w) 294 | { 295 | moveto(mousectl, addpt(w->tag.scrollr.min, 296 | divpt(Pt(Dx(w->tag.scrollr), font->height), 2))); 297 | } 298 | 299 | void 300 | windirfree(Window *w) 301 | { 302 | int i; 303 | Dirlist *dl; 304 | 305 | if(w->isdir){ 306 | for(i=0; indl; i++){ 307 | dl = w->dlp[i]; 308 | free(dl->r); 309 | free(dl); 310 | } 311 | free(w->dlp); 312 | } 313 | w->dlp = nil; 314 | w->ndl = 0; 315 | } 316 | 317 | void 318 | winclose(Window *w) 319 | { 320 | int i; 321 | 322 | if(decref(&w->ref) == 0){ 323 | xfidlog(w, "del"); 324 | windirfree(w); 325 | textclose(&w->tag); 326 | textclose(&w->body); 327 | if(activewin == w) 328 | activewin = nil; 329 | for(i=0; inincl; i++) 330 | free(w->incl[i]); 331 | free(w->incl); 332 | free(w->events); 333 | free(w); 334 | } 335 | } 336 | 337 | void 338 | windelete(Window *w) 339 | { 340 | Xfid *x; 341 | 342 | x = w->eventx; 343 | if(x){ 344 | w->nevents = 0; 345 | free(w->events); 346 | w->events = nil; 347 | w->eventx = nil; 348 | sendp(x->c, nil); /* wake him up */ 349 | } 350 | } 351 | 352 | void 353 | winundo(Window *w, int isundo) 354 | { 355 | Text *body; 356 | int i; 357 | File *f; 358 | Window *v; 359 | 360 | w->utflastqid = -1; 361 | body = &w->body; 362 | fileundo(body->file, isundo, &body->q0, &body->q1); 363 | textshow(body, body->q0, body->q1, 1); 364 | f = body->file; 365 | for(i=0; intext; i++){ 366 | v = f->text[i]->w; 367 | v->dirty = (f->seq != v->putseq); 368 | if(v != w){ 369 | v->body.q0 = v->body.fr.p0+v->body.org; 370 | v->body.q1 = v->body.fr.p1+v->body.org; 371 | } 372 | } 373 | winsettag(w); 374 | } 375 | 376 | void 377 | winsetname(Window *w, Rune *name, int n) 378 | { 379 | Text *t; 380 | Window *v; 381 | int i; 382 | static Rune Lslashguide[] = { '/', 'g', 'u', 'i', 'd', 'e', 0 }; 383 | static Rune Lpluserrors[] = { '+', 'E', 'r', 'r', 'o', 'r', 's', 0 }; 384 | 385 | t = &w->body; 386 | if(runeeq(t->file->name, t->file->nname, name, n) == TRUE) 387 | return; 388 | w->isscratch = FALSE; 389 | if(n>=6 && runeeq(Lslashguide, 6, name+(n-6), 6)) 390 | w->isscratch = TRUE; 391 | else if(n>=7 && runeeq(Lpluserrors, 7, name+(n-7), 7)) 392 | w->isscratch = TRUE; 393 | filesetname(t->file, name, n); 394 | for(i=0; ifile->ntext; i++){ 395 | v = t->file->text[i]->w; 396 | winsettag(v); 397 | v->isscratch = w->isscratch; 398 | } 399 | } 400 | 401 | void 402 | wintype(Window *w, Text *t, Rune r) 403 | { 404 | int i; 405 | 406 | texttype(t, r); 407 | if(t->what == Body) 408 | for(i=0; ifile->ntext; i++) 409 | textscrdraw(t->file->text[i]); 410 | winsettag(w); 411 | } 412 | 413 | void 414 | wincleartag(Window *w) 415 | { 416 | int i, n; 417 | Rune *r; 418 | 419 | /* w must be committed */ 420 | n = w->tag.file->b.nc; 421 | r = runemalloc(n); 422 | bufread(&w->tag.file->b, 0, r, n); 423 | for(i=0; itag, i, n, TRUE); 433 | free(r); 434 | w->tag.file->mod = FALSE; 435 | if(w->tag.q0 > i) 436 | w->tag.q0 = i; 437 | if(w->tag.q1 > i) 438 | w->tag.q1 = i; 439 | textsetselect(&w->tag, w->tag.q0, w->tag.q1); 440 | } 441 | 442 | void 443 | winsettag1(Window *w) 444 | { 445 | int i, j, k, n, bar, dirty, resize; 446 | Rune *new, *old, *r; 447 | uint q0, q1; 448 | static Rune Ldelsnarf[] = { ' ', 'D', 'e', 'l', ' ', 449 | 'S', 'n', 'a', 'r', 'f', 0 }; 450 | static Rune Lundo[] = { ' ', 'U', 'n', 'd', 'o', 0 }; 451 | static Rune Lredo[] = { ' ', 'R', 'e', 'd', 'o', 0 }; 452 | static Rune Lget[] = { ' ', 'G', 'e', 't', 0 }; 453 | static Rune Lput[] = { ' ', 'P', 'u', 't', 0 }; 454 | static Rune Llook[] = { ' ', 'L', 'o', 'o', 'k', ' ', 0 }; 455 | static Rune Lpipe[] = { ' ', '|', 0 }; 456 | 457 | /* there are races that get us here with stuff in the tag cache, so we take extra care to sync it */ 458 | if(w->tag.ncache!=0 || w->tag.file->mod) 459 | wincommit(w, &w->tag); /* check file name; also guarantees we can modify tag contents */ 460 | old = runemalloc(w->tag.file->b.nc+1); 461 | bufread(&w->tag.file->b, 0, old, w->tag.file->b.nc); 462 | old[w->tag.file->b.nc] = '\0'; 463 | for(i=0; itag.file->b.nc; i++) 464 | if(old[i]==' ' || old[i]=='\t') 465 | break; 466 | if(runeeq(old, i, w->body.file->name, w->body.file->nname) == FALSE){ 467 | textdelete(&w->tag, 0, i, TRUE); 468 | textinsert(&w->tag, 0, w->body.file->name, w->body.file->nname, TRUE); 469 | free(old); 470 | old = runemalloc(w->tag.file->b.nc+1); 471 | bufread(&w->tag.file->b, 0, old, w->tag.file->b.nc); 472 | old[w->tag.file->b.nc] = '\0'; 473 | } 474 | 475 | /* compute the text for the whole tag, replacing current only if it differs */ 476 | new = runemalloc(w->body.file->nname+100); 477 | i = 0; 478 | runemove(new+i, w->body.file->name, w->body.file->nname); 479 | i += w->body.file->nname; 480 | runemove(new+i, Ldelsnarf, 10); 481 | i += 10; 482 | if(w->filemenu){ 483 | if(w->body.needundo || w->body.file->delta.nc>0 || w->body.ncache){ 484 | runemove(new+i, Lundo, 5); 485 | i += 5; 486 | } 487 | if(w->body.file->epsilon.nc > 0){ 488 | runemove(new+i, Lredo, 5); 489 | i += 5; 490 | } 491 | dirty = w->body.file->nname && (w->body.ncache || w->body.file->seq!=w->putseq); 492 | if(!w->isdir && dirty){ 493 | runemove(new+i, Lput, 4); 494 | i += 4; 495 | } 496 | } 497 | if(w->isdir){ 498 | runemove(new+i, Lget, 4); 499 | i += 4; 500 | } 501 | runemove(new+i, Lpipe, 2); 502 | i += 2; 503 | r = runestrchr(old, '|'); 504 | if(r) 505 | k = r-old+1; 506 | else{ 507 | k = w->tag.file->b.nc; 508 | if(w->body.file->seq == 0){ 509 | runemove(new+i, Llook, 6); 510 | i += 6; 511 | } 512 | } 513 | new[i] = 0; 514 | 515 | /* replace tag if the new one is different */ 516 | resize = 0; 517 | if(runeeq(new, i, old, k) == FALSE){ 518 | resize = 1; 519 | n = k; 520 | if(n > i) 521 | n = i; 522 | for(j=0; jtag.q0; 526 | q1 = w->tag.q1; 527 | textdelete(&w->tag, j, k, TRUE); 528 | textinsert(&w->tag, j, new+j, i-j, TRUE); 529 | /* try to preserve user selection */ 530 | r = runestrchr(old, '|'); 531 | if(r){ 532 | bar = r-old; 533 | if(q0 > bar){ 534 | bar = (runestrchr(new, '|')-new)-bar; 535 | w->tag.q0 = q0+bar; 536 | w->tag.q1 = q1+bar; 537 | } 538 | } 539 | } 540 | free(old); 541 | free(new); 542 | w->tag.file->mod = FALSE; 543 | n = w->tag.file->b.nc+w->tag.ncache; 544 | if(w->tag.q0 > n) 545 | w->tag.q0 = n; 546 | if(w->tag.q1 > n) 547 | w->tag.q1 = n; 548 | textsetselect(&w->tag, w->tag.q0, w->tag.q1); 549 | windrawbutton(w); 550 | if(resize){ 551 | w->tagsafe = 0; 552 | winresize(w, w->r, TRUE, TRUE); 553 | } 554 | } 555 | 556 | void 557 | winsettag(Window *w) 558 | { 559 | int i; 560 | File *f; 561 | Window *v; 562 | 563 | f = w->body.file; 564 | for(i=0; intext; i++){ 565 | v = f->text[i]->w; 566 | if(v->col->safe || v->body.fr.maxlines>0) 567 | winsettag1(v); 568 | } 569 | } 570 | 571 | void 572 | wincommit(Window *w, Text *t) 573 | { 574 | Rune *r; 575 | int i; 576 | File *f; 577 | 578 | textcommit(t, TRUE); 579 | f = t->file; 580 | if(f->ntext > 1) 581 | for(i=0; intext; i++) 582 | textcommit(f->text[i], FALSE); /* no-op for t */ 583 | if(t->what == Body) 584 | return; 585 | r = runemalloc(w->tag.file->b.nc); 586 | bufread(&w->tag.file->b, 0, r, w->tag.file->b.nc); 587 | for(i=0; itag.file->b.nc; i++) 588 | if(r[i]==' ' || r[i]=='\t') 589 | break; 590 | if(runeeq(r, i, w->body.file->name, w->body.file->nname) == FALSE){ 591 | seq++; 592 | filemark(w->body.file); 593 | w->body.file->mod = TRUE; 594 | w->dirty = TRUE; 595 | winsetname(w, r, i); 596 | winsettag(w); 597 | } 598 | free(r); 599 | } 600 | 601 | void 602 | winaddincl(Window *w, Rune *r, int n) 603 | { 604 | char *a; 605 | Dir *d; 606 | Runestr rs; 607 | 608 | a = runetobyte(r, n); 609 | d = dirstat(a); 610 | if(d == nil){ 611 | if(a[0] == '/') 612 | goto Rescue; 613 | rs = dirname(&w->body, r, n); 614 | r = rs.r; 615 | n = rs.nr; 616 | free(a); 617 | a = runetobyte(r, n); 618 | d = dirstat(a); 619 | if(d == nil) 620 | goto Rescue; 621 | r = runerealloc(r, n+1); 622 | r[n] = 0; 623 | } 624 | free(a); 625 | if((d->qid.type&QTDIR) == 0){ 626 | free(d); 627 | warning(nil, "%s: not a directory\n", a); 628 | free(r); 629 | return; 630 | } 631 | free(d); 632 | w->nincl++; 633 | w->incl = realloc(w->incl, w->nincl*sizeof(Rune*)); 634 | memmove(w->incl+1, w->incl, (w->nincl-1)*sizeof(Rune*)); 635 | w->incl[0] = runemalloc(n+1); 636 | runemove(w->incl[0], r, n); 637 | free(r); 638 | return; 639 | 640 | Rescue: 641 | warning(nil, "%s: %r\n", a); 642 | free(r); 643 | free(a); 644 | return; 645 | } 646 | 647 | int 648 | winclean(Window *w, int conservative) 649 | { 650 | if(w->isscratch || w->isdir) /* don't whine if it's a guide file, error window, etc. */ 651 | return TRUE; 652 | if(!conservative && w->nopen[QWevent]>0) 653 | return TRUE; 654 | if(w->dirty){ 655 | if(w->body.file->nname) 656 | warning(nil, "%.*S modified\n", w->body.file->nname, w->body.file->name); 657 | else{ 658 | if(w->body.file->b.nc < 100) /* don't whine if it's too small */ 659 | return TRUE; 660 | warning(nil, "unnamed file modified\n"); 661 | } 662 | w->dirty = FALSE; 663 | return FALSE; 664 | } 665 | return TRUE; 666 | } 667 | 668 | char* 669 | winctlprint(Window *w, char *buf, int fonts) 670 | { 671 | sprint(buf, "%11d %11d %11d %11d %11d ", w->id, w->tag.file->b.nc, 672 | w->body.file->b.nc, w->isdir, w->dirty); 673 | if(fonts) 674 | return smprint("%s%11d %q %11d ", buf, Dx(w->body.fr.r), 675 | w->body.reffont->f->name, w->body.fr.maxtab); 676 | return buf; 677 | } 678 | 679 | void 680 | winevent(Window *w, char *fmt, ...) 681 | { 682 | int n; 683 | char *b; 684 | Xfid *x; 685 | va_list arg; 686 | 687 | if(w->nopen[QWevent] == 0) 688 | return; 689 | if(w->owner == 0) 690 | error("no window owner"); 691 | va_start(arg, fmt); 692 | b = vsmprint(fmt, arg); 693 | va_end(arg); 694 | if(b == nil) 695 | error("vsmprint failed"); 696 | n = strlen(b); 697 | w->events = erealloc(w->events, w->nevents+1+n); 698 | w->events[w->nevents++] = w->owner; 699 | memmove(w->events+w->nevents, b, n); 700 | free(b); 701 | w->nevents += n; 702 | x = w->eventx; 703 | if(x){ 704 | w->eventx = nil; 705 | sendp(x->c, nil); 706 | } 707 | } 708 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreasBriese/acmePimp/95305fa1f2ee0a59220d6ed97fbe1f534e417b3b/screenshot.png --------------------------------------------------------------------------------