1 2 3 /* This version of microEmacs is based on the public domain C 4 * version written by Dave G. Conroy. 5 * The D programming language version is written by Walter Bright. 6 * http://www.digitalmars.com/d/ 7 * This program is in the public domain. 8 */ 9 10 /* 11 * Buffer management. 12 * Some of the functions are internal, 13 * and some are actually attached to user 14 * keys. Like everyone else, they set hints 15 * for the display system. 16 */ 17 18 module buffer; 19 20 import std.stdio; 21 import std.path; 22 23 import core.memory; 24 25 import ed; 26 import line; 27 import display; 28 import main; 29 import window; 30 31 32 /* 33 * Text is kept in buffers. A buffer header, described below, exists for every 34 * buffer in the system. The buffers are kept in a big list, so that commands 35 * that search for a buffer by name can find the buffer header. There is a 36 * safe store for the dot and mark in the header, but this is only valid if 37 * the buffer is not being displayed (that is, if "b_nwnd" is 0). The text for 38 * the buffer is kept in a circularly linked list of lines, with a pointer to 39 * the header line in "b_linep". 40 */ 41 struct BUFFER { 42 LINE* b_dotp; // Link to "." LINE structure 43 uint b_doto; // Offset of "." in above LINE 44 LINE* b_markp; // The same as the above two, 45 uint b_marko; // but for the "mark" 46 LINE* b_linep; // Link to the header LINE 47 uint b_nwnd; // Count of windows on buffer 48 ubyte b_flag; // Flags 49 string b_fname; // File name 50 string b_bname; // Buffer name 51 Language b_lang = Language.text; // for color syntax highlighting 52 53 /********************************** 54 * Set filename associated with buffer. 55 * Determine the language based on the filename. 56 */ 57 void setFilename(string fname) 58 { 59 if (filenameCmp(b_fname, fname)) 60 { 61 const lang = filenameCmp(extension(fname), ".d") == 0 ? Language.D : 62 filenameCmp(extension(fname), ".di") == 0 ? Language.D : 63 filenameCmp(extension(fname), ".c") == 0 ? Language.C : 64 filenameCmp(extension(fname), ".cpp") == 0 ? Language.CPP : 65 filenameCmp(extension(fname), ".c++") == 0 ? Language.CPP : 66 Language.text; 67 if (b_lang != lang) 68 { 69 b_lang = lang; 70 } 71 } 72 b_fname = fname; 73 } 74 } 75 76 enum 77 { 78 BFTEMP = 0x01, // Internal temporary buffer 79 BFCHG = 0x02, // Changed since last write 80 BFRDONLY = 0x04, // Buffer is read only 81 BFNOCR = 0x08, // last line in buffer has no 82 // trailing CR 83 } 84 85 enum Language 86 { 87 text = 0, // plain text (must be 0) 88 D, // D programming language 89 C, // C programming language 90 CPP, // C++ programming language 91 } 92 93 __gshared BUFFER*[] buffers; 94 95 /* 96 * Attach a buffer to a window. The 97 * values of dot and mark come from the buffer 98 * if the use count is 0. Otherwise, they come 99 * from some other window. 100 */ 101 int usebuffer(bool f, int n) 102 { 103 string bufn; 104 105 auto s = mlreply("Use buffer: ", null, bufn); 106 if (s != TRUE) 107 return (s); 108 auto bp = buffer_find(bufn, TRUE, 0); 109 if (bp == null) 110 return (FALSE); 111 return buffer_switch(bp); 112 } 113 114 /********************************* 115 * Make next buffer in list the current one. 116 * Put it into the current window if it isn't displayed. 117 */ 118 119 int buffer_next(bool f, int n) 120 { 121 foreach (i, bp; buffers) 122 { 123 if (bp == curbp) 124 { 125 i = i + 1; 126 if (i == buffers.length) 127 i = 0; 128 return buffer_switch(buffers[i]); 129 } 130 } 131 return FALSE; 132 } 133 134 /*************************** 135 * Switch to buffer bp. 136 * Returns: 137 * TRUE or FALSE 138 */ 139 140 int buffer_switch(BUFFER* bp) 141 { 142 if (--curbp.b_nwnd == 0) { /* Last use. */ 143 curbp.b_dotp = curwp.w_dotp; 144 curbp.b_doto = curwp.w_doto; 145 curbp.b_markp = curwp.w_markp; 146 curbp.b_marko = curwp.w_marko; 147 } 148 curbp = bp; /* Switch. */ 149 curwp.w_bufp = bp; 150 curwp.w_linep = bp.b_linep; /* For macros, ignored. */ 151 curwp.w_flag |= WFMODE|WFFORCE|WFHARD; /* Quite nasty. */ 152 if (bp.b_nwnd++ == 0) { /* First use. */ 153 curwp.w_dotp = bp.b_dotp; 154 curwp.w_doto = bp.b_doto; 155 curwp.w_markp = bp.b_markp; 156 curwp.w_marko = bp.b_marko; 157 return (TRUE); 158 } 159 else 160 { 161 /* Look for the existing window onto buffer bp */ 162 foreach (wp; windows) 163 { 164 if (wp!=curwp && wp.w_bufp==bp) 165 { 166 curwp.w_dotp = wp.w_dotp; 167 curwp.w_doto = wp.w_doto; 168 curwp.w_markp = wp.w_markp; 169 curwp.w_marko = wp.w_marko; 170 break; 171 } 172 } 173 } 174 return TRUE; 175 } 176 177 /* 178 * Dispose of a buffer, by name. 179 * Ask for the name. Look it up (don't get too 180 * upset if it isn't there at all!). Get quite upset 181 * if the buffer is being displayed. Clear the buffer (ask 182 * if the buffer has been changed). Then free the header 183 * line and the buffer header. Bound to "C-X K". 184 */ 185 int killbuffer(bool f, int n) 186 { 187 BUFFER *bp; 188 int s; 189 string bufn; 190 191 if ((s=mlreply("Kill buffer: ", null, bufn)) != TRUE) 192 return (s); 193 if ((bp=buffer_find(bufn, FALSE, 0)) == null) /* Easy if unknown. */ 194 return (TRUE); 195 return buffer_remove(bp); 196 } 197 198 /********************** 199 * Remove buffer bp. 200 * Returns: 201 * 0 failed 202 * !=0 succeeded 203 */ 204 205 int buffer_remove(BUFFER* bp) 206 { 207 if (bp.b_nwnd != 0) { /* Error if on screen. */ 208 mlwrite("Buffer is being displayed"); 209 return (FALSE); 210 } 211 if (!buffer_clear(bp)) /* Blow text away */ 212 return FALSE; 213 214 //delete bp.b_linep; /* Release header line. */ 215 core.memory.GC.free(bp.b_linep); 216 217 foreach (i, b; buffers) 218 { 219 if (b == bp) 220 { 221 buffers[i .. $ - 1] = buffers[i + 1 .. $]; 222 buffers = buffers[0 .. $ - 1]; 223 break; 224 } 225 } 226 227 //delete bp; /* Release buffer block */ 228 core.memory.GC.free(bp); 229 230 return (TRUE); 231 } 232 233 /* 234 * List all of the active 235 * buffers. First update the special 236 * buffer that holds the list. Next make 237 * sure at least 1 window is displaying the 238 * buffer list, splitting the screen if this 239 * is what it takes. Lastly, repaint all of 240 * the windows that are displaying the 241 * list. Bound to "C-X C-B". 242 */ 243 int listbuffers(bool f, int n) 244 { 245 BUFFER *bp; 246 int s; 247 248 if ((s=makelist()) != TRUE) 249 return (s); 250 if (blistp.b_nwnd == 0) { /* Not on screen yet. */ 251 WINDOW *wp; 252 if ((wp=wpopup()) == null) 253 return (FALSE); 254 bp = wp.w_bufp; 255 if (--bp.b_nwnd == 0) { 256 bp.b_dotp = wp.w_dotp; 257 bp.b_doto = wp.w_doto; 258 bp.b_markp = wp.w_markp; 259 bp.b_marko = wp.w_marko; 260 } 261 wp.w_bufp = blistp; 262 ++blistp.b_nwnd; 263 } 264 foreach (wp; windows) 265 { 266 if (wp.w_bufp == blistp) { 267 wp.w_linep = lforw(blistp.b_linep); 268 wp.w_dotp = lforw(blistp.b_linep); 269 wp.w_doto = 0; 270 wp.w_markp = null; 271 wp.w_marko = 0; 272 wp.w_flag |= WFMODE|WFHARD; 273 } 274 } 275 return (TRUE); 276 } 277 278 /* 279 * This routine rebuilds the 280 * text in the special secret buffer 281 * that holds the buffer list. It is called 282 * by the list buffers command. Return TRUE 283 * if everything works. Return FALSE if there 284 * is an error (if there is no memory). 285 */ 286 int makelist() 287 { 288 LINE *lp; 289 int nbytes; 290 int s; 291 int type; 292 char[6+1] b; 293 char[128] line; 294 295 blistp.b_flag &= ~BFCHG; /* Don't complain! */ 296 if ((s=buffer_clear(blistp)) != TRUE) /* Blow old text away */ 297 return (s); 298 blistp.b_fname = null; 299 if (addline("C Size Buffer File") == FALSE 300 || addline("- ---- ------ ----") == FALSE) 301 return (FALSE); 302 /* For all buffers */ 303 foreach (bp; buffers) 304 { 305 if ((bp.b_flag&BFTEMP) != 0) { /* Skip magic ones. */ 306 continue; 307 } 308 int i = 0; /* Start at left edge */ 309 if ((bp.b_flag&BFCHG) != 0) /* "*" if changed */ 310 line[i++] = '*'; 311 else 312 line[i++] = ' '; 313 line[i++] = ' '; /* Gap. */ 314 nbytes = 0; /* Count bytes in buf. */ 315 lp = lforw(bp.b_linep); 316 while (lp != bp.b_linep) { 317 nbytes += llength(lp)+1; 318 lp = lforw(lp); 319 } 320 buffer_itoa(b, 6, nbytes); /* 6 digit buffer size. */ 321 line[i .. i + b.length] = b; 322 i += b.length; 323 line[i++] = ' '; /* Gap. */ 324 line[i .. i + bp.b_bname.length] = bp.b_bname; // buffer name 325 i += bp.b_bname.length; 326 if (bp.b_fname.length) 327 { 328 while (i < 25) 329 line[i++] = ' '; 330 line[i++] = ' '; 331 foreach (c; bp.b_bname) 332 { 333 if (i < line.length) 334 line[i++] = c; 335 } 336 } 337 /* Add to the buffer. */ 338 if (addline(line[0 .. i].idup) == FALSE) 339 return (FALSE); 340 } 341 return (TRUE); /* All done */ 342 } 343 344 void buffer_itoa(char[] buf, int width, int num) 345 { 346 buf[width] = 0; /* End of string. */ 347 while (num >= 10) { /* Conditional digits. */ 348 buf[--width] = cast(char)((num%10) + '0'); 349 num /= 10; 350 } 351 buf[--width] = cast(char)(num + '0'); // Always 1 digit. 352 while (width != 0) /* Pad with blanks. */ 353 buf[--width] = ' '; 354 } 355 356 /* 357 * The argument "text" points to 358 * a string. Append this line to the 359 * buffer list buffer. Handcraft the EOL 360 * on the end. Return TRUE if it worked and 361 * FALSE if you ran out of room. 362 */ 363 int addline(string text) 364 { 365 LINE *lp; 366 367 if ((lp=line_realloc(null, cast(int)text.length)) == null) 368 return (FALSE); 369 for (int i=0; i<text.length; ++i) 370 lputc(lp, i, text[i]); 371 blistp.b_linep.l_bp.l_fp = lp; /* Hook onto the end */ 372 lp.l_bp = blistp.b_linep.l_bp; 373 blistp.b_linep.l_bp = lp; 374 lp.l_fp = blistp.b_linep; 375 if (blistp.b_dotp == blistp.b_linep) /* If "." is at the end */ 376 blistp.b_dotp = lp; /* move it to new line */ 377 return (TRUE); 378 } 379 380 /* 381 * Look through the list of 382 * buffers. Return TRUE if there 383 * are any changed buffers. Buffers 384 * that hold magic internal stuff are 385 * not considered; who cares if the 386 * list of buffer names is hacked. 387 * Return FALSE if no buffers 388 * have been changed. 389 */ 390 int anycb() 391 { 392 foreach (bp; buffers) 393 { 394 if ((bp.b_flag & (BFTEMP | BFCHG)) == BFCHG) 395 return TRUE; 396 } 397 return FALSE; 398 } 399 400 /******************************** 401 * Find a buffer, by name. Return a pointer 402 * to the BUFFER structure associated with it. If 403 * the named buffer is found, but is a TEMP buffer (like 404 * the buffer list) complain. If the buffer is not found 405 * and the "cflag" is TRUE, create it. The "bflag" is 406 * the settings for the flags in in buffer. 407 * If bflag specifies a TEMP buffer, then a TEMP buffer can be selected. 408 */ 409 410 BUFFER *buffer_find(string bname, int cflag, int bflag) 411 { 412 foreach (bp; buffers) 413 { 414 if (globMatch(bname, bp.b_bname)) 415 { 416 if ((bflag & BFTEMP) == 0 && (bp.b_flag & BFTEMP) != 0) 417 { 418 mlwrite("Cannot select builtin buffer"); 419 return (null); 420 } 421 return (bp); 422 } 423 } 424 if (cflag != FALSE) 425 { 426 auto lp = new LINE; 427 auto bp = new BUFFER; 428 buffers ~= bp; 429 bp.b_dotp = lp; 430 bp.b_flag = cast(ubyte)bflag; 431 bp.b_linep = lp; 432 bp.b_fname = ""; 433 bp.b_bname = bname; 434 lp.l_fp = lp; 435 lp.l_bp = lp; 436 return bp; 437 } 438 return null; 439 } 440 441 /* 442 * This routine blows away all of the text 443 * in a buffer. If the buffer is marked as changed 444 * then we ask if it is ok to blow it away; this is 445 * to save the user the grief of losing text. The 446 * window chain is nearly always wrong if this gets 447 * called; the caller must arrange for the updates 448 * that are required. Return TRUE if everything 449 * looks good. 450 */ 451 int buffer_clear(BUFFER* bp) 452 { 453 LINE *lp; 454 int s; 455 456 /*if (bp.b_flag & BFRDONLY) 457 return FALSE;*/ 458 if ((bp.b_flag&BFTEMP) == 0 /* Not scratch buffer. */ 459 && (bp.b_flag&BFCHG) != 0 /* Something changed */ 460 && (s=mlyesno("Discard changes [y/n]? ")) != TRUE) 461 return (s); 462 bp.b_flag &= ~BFCHG; /* Not changed */ 463 while ((lp=lforw(bp.b_linep)) != bp.b_linep) 464 line_free(lp); 465 bp.b_dotp = bp.b_linep; /* Fix "." */ 466 bp.b_doto = 0; 467 bp.b_markp = null; /* Invalidate "mark" */ 468 bp.b_marko = 0; 469 return (TRUE); 470 }