xref: /386bsd/usr/src/usr.bin/elvis/modify.c (revision a2142627)
1 /* modify.c */
2 
3 /* This file contains the low-level file modification functions:
4  *	delete(frommark, tomark)	- removes line or portions of lines
5  *	add(frommark, text)		- inserts new text
6  *	change(frommark, tomark, text)	- delete, then add
7  */
8 
9 #include "config.h"
10 #include "vi.h"
11 
12 #ifdef DEBUG2
13 # include <stdio.h>
14 static FILE *dbg;
15 
16 /*VARARGS1*/
debout(msg,arg1,arg2,arg3,arg4,arg5)17 debout(msg, arg1, arg2, arg3, arg4, arg5)
18 	char	*msg, *arg1, *arg2, *arg3, *arg4, *arg5;
19 {
20 	if (!dbg)
21 	{
22 		dbg = fopen("debug.out", "w");
23 		if (!dbg)
24 			return;
25 		setbuf(dbg, (FILE *)0);
26 	}
27 	fprintf(dbg, msg, arg1, arg2, arg3, arg4, arg5);
28 }
29 #endif /* DEBUG2 */
30 
31 /* delete a range of text from the file */
delete(frommark,tomark)32 void delete(frommark, tomark)
33 	MARK		frommark;	/* first char to be deleted */
34 	MARK		tomark;		/* AFTER last char to be deleted */
35 {
36 	int		i;		/* used to move thru logical blocks */
37 	REG char	*scan;		/* used to scan thru text of the blk */
38 	REG char	*cpy;		/* used when copying chars */
39 	BLK		*blk;		/* a text block */
40 	long		l;		/* a line number */
41 	MARK		m;		/* a traveling version of frommark */
42 
43 #ifdef DEBUG2
44 	debout("delete(%ld.%d, %ld.%d)\n", markline(frommark), markidx(frommark), markline(tomark), markidx(tomark));
45 #endif
46 
47 	/* if not deleting anything, quit now */
48 	if (frommark == tomark)
49 	{
50 		return;
51 	}
52 
53 	/* This is a change */
54 	changes++;
55 	significant = TRUE;
56 
57 	/* supply clues to the redraw module */
58 	redrawrange(markline(frommark), markline(tomark), markline(frommark));
59 
60 	/* adjust marks 'a through 'z and '' as needed */
61 	l = markline(tomark);
62 	for (i = 0; i < NMARKS; i++)
63 	{
64 		if (mark[i] < frommark)
65 		{
66 			continue;
67 		}
68 		else if (mark[i] < tomark)
69 		{
70 			mark[i] = MARK_UNSET;
71 		}
72 		else if (markline(mark[i]) == l)
73 		{
74 			if (markline(frommark) == l)
75 			{
76 				mark[i] -= markidx(tomark) - markidx(frommark);
77 			}
78 			else
79 			{
80 				mark[i] -= markidx(tomark);
81 			}
82 		}
83 		else
84 		{
85 			mark[i] -= MARK_AT_LINE(l - markline(frommark));
86 		}
87 	}
88 
89 	/* Reporting... */
90 	if (markidx(frommark) == 0 && markidx(tomark) == 0)
91 	{
92 		rptlines = markline(tomark) - markline(frommark);
93 		rptlabel = "deleted";
94 	}
95 
96 	/* find the block containing frommark */
97 	l = markline(frommark);
98 	for (i = 1; lnum[i] < l; i++)
99 	{
100 	}
101 
102 	/* process each affected block... */
103 	for (m = frommark;
104 	     m < tomark && lnum[i] < INFINITY;
105 	     m = MARK_AT_LINE(lnum[i - 1] + 1))
106 	{
107 		/* fetch the block */
108 		blk = blkget(i);
109 
110 		/* find the mark in the block */
111 		scan = blk->c;
112 		for (l = markline(m) - lnum[i - 1] - 1; l > 0; l--)
113 		{
114 			while (*scan++ != '\n')
115 			{
116 			}
117 		}
118 		scan += markidx(m);
119 
120 		/* figure out where the changes to this block end */
121 		if (markline(tomark) > lnum[i])
122 		{
123 			cpy = blk->c + BLKSIZE;
124 		}
125 		else if (markline(tomark) == markline(m))
126 		{
127 			cpy = scan - markidx(m) + markidx(tomark);
128 		}
129 		else
130 		{
131 			cpy = scan;
132 			for (l = markline(tomark) - markline(m);
133 			     l > 0;
134 			     l--)
135 			{
136 				while (*cpy++ != '\n')
137 				{
138 				}
139 			}
140 			cpy += markidx(tomark);
141 		}
142 
143 		/* delete the stuff by moving chars within this block */
144 		while (cpy < blk->c + BLKSIZE)
145 		{
146 			*scan++ = *cpy++;
147 		}
148 		while (scan < blk->c + BLKSIZE)
149 		{
150 			*scan++ = '\0';
151 		}
152 
153 		/* adjust tomark to allow for lines deleted from this block */
154 		tomark -= MARK_AT_LINE(lnum[i] + 1 - markline(m));
155 
156 		/* if this block isn't empty now, then advance i */
157 		if (*blk->c)
158 		{
159 			i++;
160 		}
161 
162 		/* the buffer has changed.  Update hdr and lnum. */
163 		blkdirty(blk);
164 	}
165 
166 	/* must have at least 1 line */
167 	if (nlines == 0)
168 	{
169 		blk = blkadd(1);
170 		blk->c[0] = '\n';
171 		blkdirty(blk);
172 		cursor = MARK_FIRST;
173 	}
174 }
175 
176 
177 /* add some text at a specific place in the file */
add(atmark,newtext)178 void add(atmark, newtext)
179 	MARK		atmark;		/* where to insert the new text */
180 	char		*newtext;	/* NUL-terminated string to insert */
181 {
182 	REG char	*scan;		/* used to move through string */
183 	REG char	*build;		/* used while copying chars */
184 	int		addlines;	/* number of lines we're adding */
185 	int		lastpart;	/* size of last partial line */
186 	BLK		*blk;		/* the block to be modified */
187 	int		blkno;		/* the logical block# of (*blk) */
188 	REG char	*newptr;	/* where new text starts in blk */
189 	BLK		buf;		/* holds chars from orig blk */
190 	BLK		linebuf;	/* holds part of line that didn't fit */
191 	BLK		*following;	/* the BLK following the last BLK */
192 	int		i;
193 	long		l;
194 
195 #ifdef DEBUG2
196 	debout("add(%ld.%d, \"%s\")\n", markline(atmark), markidx(atmark), newtext);
197 #endif
198 #ifdef lint
199 	buf.c[0] = 0;
200 #endif
201 	/* if not adding anything, return now */
202 	if (!*newtext)
203 	{
204 		return;
205 	}
206 
207 	/* This is a change */
208 	changes++;
209 	significant = TRUE;
210 
211 	/* count the number of lines in the new text */
212 	for (scan = newtext, lastpart = addlines = 0; *scan; )
213 	{
214 		if (*scan++ == '\n')
215 		{
216 			addlines++;
217 			lastpart = 0;
218 		}
219 		else
220 		{
221 			lastpart++;
222 		}
223 	}
224 
225 	/* Reporting... */
226 	if (lastpart == 0 && markidx(atmark) == 0)
227 	{
228 		rptlines = addlines;
229 		rptlabel = "added";
230 	}
231 
232 	/* extract the line# from atmark */
233 	l = markline(atmark);
234 
235 	/* supply clues to the redraw module */
236 	if ((markidx(atmark) == 0 && lastpart == 0) || addlines == 0)
237 	{
238 		redrawrange(l, l, l + addlines);
239 	}
240 	else
241 	{
242 		/* make sure the last line gets redrawn -- it was
243 		 * split, so its appearance has changed
244 		 */
245 		redrawrange(l, l + 1L, l + addlines + 1L);
246 	}
247 
248 	/* adjust marks 'a through 'z and '' as needed */
249 	for (i = 0; i < NMARKS; i++)
250 	{
251 		if (mark[i] < atmark)
252 		{
253 			/* earlier line, or earlier in same line: no change */
254 			continue;
255 		}
256 		else if (markline(mark[i]) > l)
257 		{
258 			/* later line: move down a whole number of lines */
259 			mark[i] += MARK_AT_LINE(addlines);
260 		}
261 		else
262 		{
263 			/* later in same line */
264 			if (addlines > 0)
265 			{
266 				/* multi-line add, which split this line:
267 				 * move down, and possibly left or right,
268 				 * depending on where the split was and how
269 				 * much text was inserted after the last \n
270 				 */
271 				mark[i] += MARK_AT_LINE(addlines) + lastpart - markidx(atmark);
272 			}
273 			else
274 			{
275 				/* totally within this line: move right */
276 				mark[i] += lastpart;
277 			}
278 		}
279 	}
280 
281 	/* get the block to be modified */
282 	for (blkno = 1; lnum[blkno] < l && lnum[blkno + 1] < INFINITY; blkno++)
283 	{
284 	}
285 	blk = blkget(blkno);
286 	buf = *blk;
287 
288 	/* figure out where the new text starts */
289 	for (newptr = buf.c, l = markline(atmark) - lnum[blkno - 1] - 1;
290 	     l > 0;
291 	     l--)
292 	{
293 		while (*newptr++ != '\n')
294 		{
295 		}
296 	}
297 	newptr += markidx(atmark);
298 
299 	/* keep start of old block */
300 	build = blk->c + (int)(newptr - buf.c);
301 
302 	/* fill this block (or blocks) from the newtext string */
303 	while (*newtext)
304 	{
305 		while (*newtext && build < blk->c + BLKSIZE - 1)
306 		{
307 			*build++ = *newtext++;
308 		}
309 		if (*newtext)
310 		{
311 			/* save the excess */
312 			for (scan = linebuf.c + BLKSIZE;
313 			     build > blk->c && build[-1] != '\n';
314 			     )
315 			{
316 				*--scan = *--build;
317 			}
318 
319 			/* write the block */
320 			while (build < blk->c + BLKSIZE)
321 			{
322 				*build++ = '\0';
323 			}
324 			blkdirty(blk);
325 
326 			/* add another block */
327 			blkno++;
328 			blk = blkadd(blkno);
329 
330 			/* copy in the excess from last time */
331 			for (build = blk->c; scan < linebuf.c + BLKSIZE; )
332 			{
333 				*build++ = *scan++;
334 			}
335 		}
336 	}
337 
338 	/* fill this block(s) from remainder of orig block */
339 	while (newptr < buf.c + BLKSIZE && *newptr)
340 	{
341 		while (newptr < buf.c + BLKSIZE
342 		    && *newptr
343 		    && build < blk->c + BLKSIZE - 1)
344 		{
345 			*build++ = *newptr++;
346 		}
347 		if (newptr < buf.c + BLKSIZE && *newptr)
348 		{
349 			/* save the excess */
350 			for (scan = linebuf.c + BLKSIZE;
351 			     build > blk->c && build[-1] != '\n';
352 			     )
353 			{
354 				*--scan = *--build;
355 			}
356 
357 			/* write the block */
358 			while (build < blk->c + BLKSIZE)
359 			{
360 				*build++ = '\0';
361 			}
362 			blkdirty(blk);
363 
364 			/* add another block */
365 			blkno++;
366 			blk = blkadd(blkno);
367 
368 			/* copy in the excess from last time */
369 			for (build = blk->c; scan < linebuf.c + BLKSIZE; )
370 			{
371 				*build++ = *scan++;
372 			}
373 		}
374 	}
375 
376 	/* see if we can combine our last block with the following block */
377 	if (lnum[blkno] < nlines && lnum[blkno + 1] - lnum[blkno] < (BLKSIZE >> 6))
378 	{
379 		/* hey, we probably can!  Get the following block & see... */
380 		following = blkget(blkno + 1);
381 		if (strlen(following->c) + (build - blk->c) < BLKSIZE - 1)
382 		{
383 			/* we can!  Copy text from following to blk */
384 			for (scan = following->c; *scan; )
385 			{
386 				*build++ = *scan++;
387 			}
388 			while (build < blk->c + BLKSIZE)
389 			{
390 				*build++ = '\0';
391 			}
392 			blkdirty(blk);
393 
394 			/* pretend the following was the last blk */
395 			blk = following;
396 			build = blk->c;
397 		}
398 	}
399 
400 	/* that last block is dirty by now */
401 	while (build < blk->c + BLKSIZE)
402 	{
403 		*build++ = '\0';
404 	}
405 	blkdirty(blk);
406 }
407 
408 
409 /* change the text of a file */
change(frommark,tomark,newtext)410 void change(frommark, tomark, newtext)
411 	MARK	frommark, tomark;
412 	char	*newtext;
413 {
414 	int	i;
415 	long	l;
416 	char	*text;
417 	BLK	*blk;
418 
419 #ifdef DEBUG2
420 	debout("change(%ld.%d, %ld.%d, \"%s\")\n", markline(frommark), markidx(frommark), markline(tomark), markidx(tomark), newtext);
421 #endif
422 
423 	/* optimize for single-character replacement */
424 	if (frommark + 1 == tomark && newtext[0] && !newtext[1] && newtext[0] != '\n')
425 	{
426 		/* find the block containing frommark */
427 		l = markline(frommark);
428 		for (i = 1; lnum[i] < l; i++)
429 		{
430 		}
431 
432 		/* get the block */
433 		blk = blkget(i);
434 
435 		/* find the line within the block */
436 		for (text = blk->c, i = l - lnum[i - 1] - 1; i > 0; text++)
437 		{
438 			if (*text == '\n')
439 			{
440 				i--;
441 			}
442 		}
443 
444 		/* replace the char */
445 		text += markidx(frommark);
446 		if (*text == newtext[0])
447 		{
448 			/* no change was needed - same char */
449 			return;
450 		}
451 		else if (*text != '\n')
452 		{
453 			/* This is a change */
454 			changes++;
455 			significant = TRUE;
456 			ChangeText
457 			{
458 				*text = newtext[0];
459 				blkdirty(blk);
460 			}
461 			redrawrange(markline(frommark), markline(tomark), markline(frommark));
462 			return;
463 		}
464 		/* else it is a complex change involving newline... */
465 	}
466 
467 	/* couldn't optimize, so do delete & add */
468 	ChangeText
469 	{
470 		delete(frommark, tomark);
471 		add(frommark, newtext);
472 		rptlabel = "changed";
473 	}
474 }
475