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 * The routines in this file 12 * handle the reading and writing of 13 * disk files. All of details about the 14 * reading and writing of the disk are 15 * in "fileio.c". 16 */ 17 18 module file; 19 20 import core.stdc.stdlib; 21 22 import std.stdio; 23 import std.path; 24 import std..string; 25 import std.utf; 26 import std.file; 27 28 import ed; 29 import main; 30 import region; 31 import window; 32 import fileio; 33 import line; 34 import buffer; 35 import display; 36 import random; 37 38 /********************************** 39 * Save current file. Get next file and read it into the 40 * current buffer. Next file is either: 41 * o next argument from command line 42 * o input from the user 43 */ 44 45 int filenext(bool f, int n) 46 { int s; 47 48 if (filesave(f,n) == FALSE) /* save current file */ 49 return FALSE; 50 if (gargi < gargs.length) /* if more files on command line */ 51 { 52 s = readin(gargs[gargi]); 53 gargi++; 54 } 55 else /* get file name from user */ 56 s = fileread(f,n); 57 curbp.b_bname = makename(curbp.b_fname); 58 return s; 59 } 60 61 /********************************** 62 * Insert a file into the current buffer. 63 */ 64 65 int Dinsertfile(bool f, int n) 66 { 67 string fnamed; 68 69 if (mlreply("Insert file: ", null, fnamed) == FALSE) 70 return FALSE; 71 72 string fname = toUTF8(fnamed); 73 try 74 { 75 fname = std.path.expandTilde(fname); 76 auto fp = File(fname); 77 mlwrite("[Reading file]"); 78 int nline = 0; 79 char[] line; 80 size_t s; 81 while ((s = fp.readln(line)) != 0) 82 { 83 foreach(char c; line) 84 { 85 if (c == '\r' || c == '\n') 86 break; 87 if (line_insert(1,c) == FALSE) 88 return FALSE; 89 } 90 if (random_newline(FALSE,1) == FALSE) 91 return FALSE; 92 ++nline; 93 } 94 fp.close(); 95 if (nline == 1) 96 mlwrite("[Read 1 line]"); 97 else 98 mlwrite("[Read %d lines]", nline); 99 return TRUE; 100 } 101 catch (Exception e) 102 { 103 mlwrite(e.toString()); 104 return FALSE; 105 } 106 finally 107 { 108 foreach (wp; windows) 109 { 110 if (wp.w_bufp == curbp) { 111 wp.w_flag |= WFMODE|WFHARD; 112 } 113 } 114 } 115 } 116 117 /* 118 * Read a file into the current 119 * buffer. This is really easy; all you do it 120 * find the name of the file, and call the standard 121 * "read a file into the current buffer" code. 122 * Bound to "C-X C-R". 123 */ 124 int fileread(bool f, int n) 125 { 126 int s; 127 string fname; 128 129 if ((s=mlreply("Read file: ", null, fname)) != TRUE) 130 return (s); 131 return (readin(fname)); 132 } 133 134 /* 135 * Select a file for editing. 136 * Look around to see if you can find the 137 * file in another buffer; if you can find it 138 * just switch to the buffer. If you cannot find 139 * the file, create a new buffer, read in the 140 * text, and switch to the new buffer. 141 * Bound to GOLD E. 142 */ 143 int filevisit(bool f, int n) 144 { 145 string fname; 146 147 return mlreply("Visit file: ", null, fname) && 148 window_split(f,n) && 149 file_readin(fname); 150 } 151 152 int file_readin(string fname) 153 { 154 LINE *lp; 155 int i; 156 int s; 157 string bname; 158 159 /* If there is an existing buffer with the same file name, simply */ 160 /* switch to it instead of reading the file again. */ 161 foreach (bp; buffers) 162 { 163 /* Always redo temporary buffers, check for filename match. */ 164 if ((bp.b_flag&BFTEMP)==0 && globMatch(bp.b_fname, fname)) 165 { 166 /* If the current buffer now becomes undisplayed */ 167 if (--curbp.b_nwnd == 0) 168 { 169 curbp.b_dotp = curwp.w_dotp; 170 curbp.b_doto = curwp.w_doto; 171 curbp.b_markp = curwp.w_markp; 172 curbp.b_marko = curwp.w_marko; 173 } 174 curbp = bp; 175 curwp.w_bufp = bp; 176 if (bp.b_nwnd++ == 0) /* if buffer not already displayed */ 177 { 178 curwp.w_dotp = bp.b_dotp; 179 curwp.w_doto = bp.b_doto; 180 curwp.w_markp = bp.b_markp; 181 curwp.w_marko = bp.b_marko; 182 } 183 else 184 { 185 /* Set dot to be at place where other window has it */ 186 foreach (wp; windows) 187 { 188 if (wp!=curwp && wp.w_bufp==bp) 189 { 190 curwp.w_dotp = wp.w_dotp; 191 curwp.w_doto = wp.w_doto; 192 curwp.w_markp = wp.w_markp; 193 curwp.w_marko = wp.w_marko; 194 break; 195 } 196 } 197 } 198 199 /* Adjust frame so dot is at center */ 200 lp = curwp.w_dotp; 201 i = curwp.w_ntrows/2; 202 while (i-- && lback(lp)!=curbp.b_linep) 203 lp = lback(lp); 204 curwp.w_linep = lp; 205 206 curwp.w_flag |= WFMODE|WFHARD; 207 mlwrite("[Old buffer]"); 208 return TRUE; 209 } 210 } 211 212 bname = makename(fname); /* New buffer name. */ 213 BUFFER* bp; 214 while ((bp=buffer_find(bname, FALSE, 0)) != null) 215 { 216 s = mlreply("Buffer name: ", null, bname); 217 if (s == ABORT) /* ^G to just quit */ 218 return (s); 219 if (s == FALSE) { /* CR to clobber it */ 220 bname = makename(fname); 221 break; 222 } 223 } 224 if (bp==null && (bp=buffer_find(bname, TRUE, 0))==null) 225 { mlwrite("Cannot create buffer"); 226 return (FALSE); 227 } 228 if (--curbp.b_nwnd == 0) /* Undisplay */ 229 { curbp.b_dotp = curwp.w_dotp; 230 curbp.b_doto = curwp.w_doto; 231 curbp.b_markp = curwp.w_markp; 232 curbp.b_marko = curwp.w_marko; 233 } 234 curbp = bp; /* Switch to it. */ 235 curwp.w_bufp = bp; 236 curbp.b_nwnd++; 237 return (readin(fname)); /* Read it in. */ 238 } 239 240 /* 241 * Read file "fname" into the current 242 * buffer, blowing away any text found there. Called 243 * by both the read and visit commands. Return the final 244 * status of the read. Also called by the mainline, 245 * to read in a file specified on the command line as 246 * an argument. 247 */ 248 int readin(string dfname) 249 { 250 auto bp = curbp; // Cheap. 251 auto b = buffer_clear(bp); // Might be old. 252 if (b != TRUE) 253 return b; 254 bp.b_flag &= ~(BFTEMP|BFCHG); 255 bp.setFilename(dfname); 256 257 /* Determine if file is read-only */ 258 auto fname = std.path.expandTilde(toUTF8(dfname)); 259 if (ffreadonly(fname)) /* is file read-only? */ 260 bp.b_flag |= BFRDONLY; 261 else 262 bp.b_flag &= ~BFRDONLY; 263 264 try 265 { 266 if (!std.file.exists(fname)) 267 { 268 mlwrite("[New file]"); 269 return TRUE; 270 } 271 auto fp = File(fname); 272 mlwrite("[Reading file]"); 273 int nline = 0; 274 char[] line; 275 size_t s; 276 bool first = true; 277 while ((s = fp.readln(line)) != 0) 278 { 279 if (line.length && line[$ - 1] == '\n') 280 line = line[0 .. $ - 1]; 281 if (line.length && line[$ - 1] == '\r') 282 line = line[0 .. $ - 1]; 283 284 LINE *lp1; 285 LINE *lp2; 286 287 if ((lp1=line_realloc(null,0)) == null) { 288 s = FIOERR; /* Keep message on the */ 289 break; /* display. */ 290 } 291 lp2 = lback(curbp.b_linep); 292 lp2.l_fp = lp1; 293 lp1.l_fp = curbp.b_linep; 294 lp1.l_bp = lp2; 295 curbp.b_linep.l_bp = lp1; 296 if (first && line.length >= 3 && line[0] == 0xEF && line[1] == 0xBB && line[2] == 0xBF) 297 line = line[3..$]; // skip BOM 298 lp1.l_text = line[].dup; 299 300 first = false; 301 ++nline; 302 } 303 fp.close(); 304 if (nline == 1) 305 mlwrite("[Read 1 line]"); 306 else 307 mlwrite("[Read %d lines]", nline); 308 return TRUE; 309 } 310 catch (Exception e) 311 { 312 mlwrite(e.toString()); 313 return FALSE; 314 } 315 finally 316 { 317 foreach (wp; windows) 318 { 319 if (wp.w_bufp == curbp) { 320 wp.w_linep = lforw(curbp.b_linep); 321 wp.w_dotp = lforw(curbp.b_linep); 322 wp.w_doto = 0; 323 wp.w_markp = null; 324 wp.w_marko = 0; 325 wp.w_flag |= WFMODE|WFHARD; 326 } 327 } 328 } 329 } 330 331 /* 332 * Take a file name, and from it 333 * fabricate a buffer name. This routine knows 334 * about the syntax of file names on the target system. 335 * I suppose that this information could be put in 336 * a better place than a line of code. 337 */ 338 string makename(string fname) 339 { 340 return fname; 341 } 342 343 /* 344 * Ask for a file name, and write the 345 * contents of the current buffer or region to that file. 346 * Update the remembered file name and clear the 347 * buffer changed flag. This handling of file names 348 * is different from the earlier versions, and 349 * is more compatible with Gosling EMACS than 350 * with ITS EMACS. Bound to "C-X C-W". 351 */ 352 int filewrite(bool f, int n) 353 { 354 int s; 355 string fname; 356 357 if ((s=mlreply("Write file: ", null, fname)) != TRUE) 358 return (s); 359 if (curwp.w_markp) /* if marking a region */ 360 { REGION region; 361 362 if (!getregion(®ion)) 363 return FALSE; 364 return file_writeregion(fname,®ion); 365 } 366 else 367 { 368 if ((s=writeout(fname)) == TRUE) { 369 curbp.setFilename(fname); 370 fileunmodify(f,n); 371 } 372 } 373 return (s); 374 } 375 376 /**************************** 377 * Mark a file as being unmodified. 378 */ 379 380 int fileunmodify(bool f, int n) 381 { 382 curbp.b_flag &= ~BFCHG; 383 384 /* Update mode lines. */ 385 foreach (wp; windows) 386 { 387 if (wp.w_bufp == curbp) 388 wp.w_flag |= WFMODE; 389 } 390 return TRUE; 391 } 392 393 /* 394 * Save the contents of the current 395 * buffer in its associated file. No nothing 396 * if nothing has changed (this may be a bug, not a 397 * feature). Error if there is no remembered file 398 * name for the buffer. Bound to "C-X C-S". May 399 * get called by "C-Z". 400 */ 401 int filesave(bool f, int n) 402 { 403 WINDOW *wp; 404 int s; 405 406 if ((curbp.b_flag&BFCHG) == 0) /* Return, no changes. */ 407 return (TRUE); 408 if (curbp.b_fname.length == 0) { /* Must have a name. */ 409 mlwrite("No file name"); 410 return (FALSE); 411 } 412 if ((s=writeout(curbp.b_fname)) == TRUE) { 413 fileunmodify(f,n); 414 } 415 return (s); 416 } 417 418 /* 419 * Save the contents of each and every modified 420 * buffer. Does nothing if the buffer is temporary 421 * or has no filename. 422 */ 423 int filemodify(bool f, int n) 424 { 425 int s = TRUE; 426 427 auto oldbp = curbp; 428 foreach (bp; buffers) 429 { 430 curbp = bp; 431 if((curbp.b_flag&BFCHG) == 0 || /* if no changes */ 432 curbp.b_flag & BFTEMP || /* if temporary */ 433 curbp.b_fname.length == 0) /* Must have a name */ 434 continue; 435 if((s&=writeout(curbp.b_fname)) == TRUE ) 436 fileunmodify(f,n); 437 } 438 curbp = oldbp; 439 return( s ); 440 } 441 442 /* 443 * This function performs the details of file 444 * writing. Uses the file management routines in the 445 * "fileio.c" package. The number of lines written is 446 * displayed. Sadly, it looks inside a LINE; provide 447 * a macro for this. Most of the grief is error 448 * checking of some sort. 449 */ 450 int writeout(string dfn) 451 { 452 auto fn = std.path.expandTilde(toUTF8(dfn)); 453 /* 454 * Supply backups when writing files. 455 */ 456 version (Windows) 457 { 458 auto backupname = std.path.setExtension(fn, "bak"); 459 } 460 else 461 { 462 auto backupname = buildPath(dirName(fn), ".B" ~ baseName(fn)); 463 } 464 465 try 466 { 467 std.file.remove(backupname); // Remove old backup file 468 } 469 catch (Throwable o) 470 { 471 } 472 473 if (ffrename(fn, backupname) != FIOSUC) 474 return FALSE; // Make new backup file 475 476 try 477 { 478 auto f = File(fn, "w"); 479 480 if ( ffchmod( fn, backupname ) != FIOSUC ) /* Set protection */ 481 { f.close(); 482 return( FALSE ); 483 } 484 485 auto lp = lforw(curbp.b_linep); // First line. 486 int nline = 0; // Number of lines. 487 while (lp != curbp.b_linep) { 488 f.writeln(toUTF8(lp.l_text[0 .. llength(lp)])); 489 ++nline; 490 lp = lforw(lp); 491 } 492 493 f.close(); 494 if (nline == 1) 495 mlwrite("[Wrote 1 line]"); 496 else 497 mlwrite("[Wrote %d lines]", nline); 498 return TRUE; 499 } 500 catch (Exception e) 501 { 502 mlwrite(e.toString()); 503 return FALSE; 504 } 505 } 506 507 /* 508 * The command allows the user 509 * to modify the file name associated with 510 * the current buffer. It is like the "f" command 511 * in UNIX "ed". The operation is simple; just zap 512 * the name in the BUFFER structure, and mark the windows 513 * as needing an update. You can type a blank line at the 514 * prompt if you wish. 515 */ 516 int filename(bool f, int n) 517 { 518 int s; 519 string fname; 520 521 if ((s=mlreply("New File Name: ", null, fname)) == ABORT) 522 return (s); 523 curbp.setFilename( s == FALSE ? null : fname); 524 foreach (wp; windows) 525 { // Update mode lines. 526 if (wp.w_bufp == curbp) 527 wp.w_flag |= WFMODE; 528 } 529 return (TRUE); 530 } 531 532 /******************************* 533 * Write region out to file. 534 */ 535 536 int file_writeregion(string dfilename, REGION* region) 537 { 538 auto lp = region.r_linep; /* First line. */ 539 auto loffs = region.r_offset; 540 auto size = region.r_size; 541 int nline = 0; /* Number of lines. */ 542 543 try 544 { 545 auto filename = std.path.expandTilde(toUTF8(dfilename)); 546 auto f = File(filename, "w"); 547 while (size > 0) 548 { 549 auto nchars = llength(lp) - loffs; 550 if (nchars > size) /* if last line is not a full line */ 551 nchars = size; 552 f.writeln(toUTF8(lp.l_text[loffs .. loffs + nchars])); 553 size -= nchars + 1; 554 ++nline; 555 lp = lforw(lp); 556 loffs = 0; 557 } 558 f.close(); 559 if (nline == 1) 560 mlwrite("[Wrote 1 line]"); 561 else 562 mlwrite("[Wrote %d lines]", nline); 563 return TRUE; 564 } 565 catch (Exception e) 566 { 567 mlwrite(e.toString()); 568 return FALSE; 569 } 570 } 571