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(&region))
363 	    return FALSE;
364 	return file_writeregion(fname,&region);
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