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 }