xref: /openbsd/usr.bin/vi/common/line.c (revision 898184e3)
1 /*	$OpenBSD: line.c,v 1.9 2009/10/27 23:59:47 deraadt Exp $	*/
2 
3 /*-
4  * Copyright (c) 1992, 1993, 1994
5  *	The Regents of the University of California.  All rights reserved.
6  * Copyright (c) 1992, 1993, 1994, 1995, 1996
7  *	Keith Bostic.  All rights reserved.
8  *
9  * See the LICENSE file for redistribution information.
10  */
11 
12 #include "config.h"
13 
14 #include <sys/types.h>
15 #include <sys/queue.h>
16 #include <sys/time.h>
17 
18 #include <bitstring.h>
19 #include <errno.h>
20 #include <limits.h>
21 #include <stdio.h>
22 #include <string.h>
23 
24 #include "common.h"
25 #include "../vi/vi.h"
26 
27 static int scr_update(SCR *, recno_t, lnop_t, int);
28 
29 /*
30  * db_eget --
31  *	Front-end to db_get, special case handling for empty files.
32  *
33  * PUBLIC: int db_eget(SCR *, recno_t, char **, size_t *, int *);
34  */
35 int
36 db_eget(sp, lno, pp, lenp, isemptyp)
37 	SCR *sp;
38 	recno_t lno;				/* Line number. */
39 	char **pp;				/* Pointer store. */
40 	size_t *lenp;				/* Length store. */
41 	int *isemptyp;
42 {
43 	recno_t l1;
44 
45 	if (isemptyp != NULL)
46 		*isemptyp = 0;
47 
48 	/* If the line exists, simply return it. */
49 	if (!db_get(sp, lno, 0, pp, lenp))
50 		return (0);
51 
52 	/*
53 	 * If the user asked for line 0 or line 1, i.e. the only possible
54 	 * line in an empty file, find the last line of the file; db_last
55 	 * fails loudly.
56 	 */
57 	if ((lno == 0 || lno == 1) && db_last(sp, &l1))
58 		return (1);
59 
60 	/* If the file isn't empty, fail loudly. */
61 	if ((lno != 0 && lno != 1) || l1 != 0) {
62 		db_err(sp, lno);
63 		return (1);
64 	}
65 
66 	if (isemptyp != NULL)
67 		*isemptyp = 1;
68 
69 	return (1);
70 }
71 
72 /*
73  * db_get --
74  *	Look in the text buffers for a line, followed by the cache, followed
75  *	by the database.
76  *
77  * PUBLIC: int db_get(SCR *, recno_t, u_int32_t, char **, size_t *);
78  */
79 int
80 db_get(sp, lno, flags, pp, lenp)
81 	SCR *sp;
82 	recno_t lno;				/* Line number. */
83 	u_int32_t flags;
84 	char **pp;				/* Pointer store. */
85 	size_t *lenp;				/* Length store. */
86 {
87 	DBT data, key;
88 	EXF *ep;
89 	TEXT *tp;
90 	recno_t l1, l2;
91 
92 	/*
93 	 * The underlying recno stuff handles zero by returning NULL, but
94 	 * have to have an OOB condition for the look-aside into the input
95 	 * buffer anyway.
96 	 */
97 	if (lno == 0)
98 		goto err1;
99 
100 	/* Check for no underlying file. */
101 	if ((ep = sp->ep) == NULL) {
102 		ex_emsg(sp, NULL, EXM_NOFILEYET);
103 		goto err3;
104 	}
105 
106 	if (LF_ISSET(DBG_NOCACHE))
107 		goto nocache;
108 
109 	/*
110 	 * Look-aside into the TEXT buffers and see if the line we want
111 	 * is there.
112 	 */
113 	if (F_ISSET(sp, SC_TINPUT)) {
114 		l1 = ((TEXT *)CIRCLEQ_FIRST(&sp->tiq))->lno;
115 		l2 = ((TEXT *)CIRCLEQ_LAST(&sp->tiq))->lno;
116 		if (l1 <= lno && l2 >= lno) {
117 #if defined(DEBUG) && 0
118 	TRACE(sp, "retrieve TEXT buffer line %lu\n", (u_long)lno);
119 #endif
120 			for (tp = CIRCLEQ_FIRST(&sp->tiq);
121 			    tp->lno != lno; tp = CIRCLEQ_NEXT(tp, q));
122 			if (lenp != NULL)
123 				*lenp = tp->len;
124 			if (pp != NULL)
125 				*pp = tp->lb;
126 			return (0);
127 		}
128 		/*
129 		 * Adjust the line number for the number of lines used
130 		 * by the text input buffers.
131 		 */
132 		if (lno > l2)
133 			lno -= l2 - l1;
134 	}
135 
136 	/* Look-aside into the cache, and see if the line we want is there. */
137 	if (lno == ep->c_lno) {
138 #if defined(DEBUG) && 0
139 	TRACE(sp, "retrieve cached line %lu\n", (u_long)lno);
140 #endif
141 		if (lenp != NULL)
142 			*lenp = ep->c_len;
143 		if (pp != NULL)
144 			*pp = ep->c_lp;
145 		return (0);
146 	}
147 	ep->c_lno = OOBLNO;
148 
149 nocache:
150 	/* Get the line from the underlying database. */
151 	key.data = &lno;
152 	key.size = sizeof(lno);
153 	switch (ep->db->get(ep->db, &key, &data, 0)) {
154         case -1:
155 		goto err2;
156 	case 1:
157 err1:		if (LF_ISSET(DBG_FATAL))
158 err2:			db_err(sp, lno);
159 err3:		if (lenp != NULL)
160 			*lenp = 0;
161 		if (pp != NULL)
162 			*pp = NULL;
163 		return (1);
164 	}
165 
166 	/* Reset the cache. */
167 	ep->c_lno = lno;
168 	ep->c_len = data.size;
169 	ep->c_lp = data.data;
170 
171 #if defined(DEBUG) && 0
172 	TRACE(sp, "retrieve DB line %lu\n", (u_long)lno);
173 #endif
174 	if (lenp != NULL)
175 		*lenp = data.size;
176 	if (pp != NULL)
177 		*pp = ep->c_lp;
178 	return (0);
179 }
180 
181 /*
182  * db_delete --
183  *	Delete a line from the file.
184  *
185  * PUBLIC: int db_delete(SCR *, recno_t);
186  */
187 int
188 db_delete(sp, lno)
189 	SCR *sp;
190 	recno_t lno;
191 {
192 	DBT key;
193 	EXF *ep;
194 
195 #if defined(DEBUG) && 0
196 	TRACE(sp, "delete line %lu\n", (u_long)lno);
197 #endif
198 	/* Check for no underlying file. */
199 	if ((ep = sp->ep) == NULL) {
200 		ex_emsg(sp, NULL, EXM_NOFILEYET);
201 		return (1);
202 	}
203 
204 	/* Update marks, @ and global commands. */
205 	if (mark_insdel(sp, LINE_DELETE, lno))
206 		return (1);
207 	if (ex_g_insdel(sp, LINE_DELETE, lno))
208 		return (1);
209 
210 	/* Log change. */
211 	log_line(sp, lno, LOG_LINE_DELETE);
212 
213 	/* Update file. */
214 	key.data = &lno;
215 	key.size = sizeof(lno);
216 	SIGBLOCK;
217 	if (ep->db->del(ep->db, &key, 0) == 1) {
218 		msgq(sp, M_SYSERR,
219 		    "003|unable to delete line %lu", (u_long)lno);
220 		return (1);
221 	}
222 	SIGUNBLOCK;
223 
224 	/* Flush the cache, update line count, before screen update. */
225 	if (lno <= ep->c_lno)
226 		ep->c_lno = OOBLNO;
227 	if (ep->c_nlines != OOBLNO)
228 		--ep->c_nlines;
229 
230 	/* File now modified. */
231 	if (F_ISSET(ep, F_FIRSTMODIFY))
232 		(void)rcv_init(sp);
233 	F_SET(ep, F_MODIFIED);
234 
235 	/* Update screen. */
236 	return (scr_update(sp, lno, LINE_DELETE, 1));
237 }
238 
239 /*
240  * db_append --
241  *	Append a line into the file.
242  *
243  * PUBLIC: int db_append(SCR *, int, recno_t, char *, size_t);
244  */
245 int
246 db_append(sp, update, lno, p, len)
247 	SCR *sp;
248 	int update;
249 	recno_t lno;
250 	char *p;
251 	size_t len;
252 {
253 	DBT data, key;
254 	EXF *ep;
255 	int rval;
256 
257 #if defined(DEBUG) && 0
258 	TRACE(sp, "append to %lu: len %u {%.*s}\n", lno, len, MIN(len, 20), p);
259 #endif
260 	/* Check for no underlying file. */
261 	if ((ep = sp->ep) == NULL) {
262 		ex_emsg(sp, NULL, EXM_NOFILEYET);
263 		return (1);
264 	}
265 
266 	/* Update file. */
267 	key.data = &lno;
268 	key.size = sizeof(lno);
269 	data.data = p;
270 	data.size = len;
271 	SIGBLOCK;
272 	if (ep->db->put(ep->db, &key, &data, R_IAFTER) == -1) {
273 		msgq(sp, M_SYSERR,
274 		    "004|unable to append to line %lu", (u_long)lno);
275 		return (1);
276 	}
277 	SIGUNBLOCK;
278 
279 	/* Flush the cache, update line count, before screen update. */
280 	if (lno < ep->c_lno)
281 		ep->c_lno = OOBLNO;
282 	if (ep->c_nlines != OOBLNO)
283 		++ep->c_nlines;
284 
285 	/* File now dirty. */
286 	if (F_ISSET(ep, F_FIRSTMODIFY))
287 		(void)rcv_init(sp);
288 	F_SET(ep, F_MODIFIED);
289 
290 	/* Log change. */
291 	log_line(sp, lno + 1, LOG_LINE_APPEND);
292 
293 	/* Update marks, @ and global commands. */
294 	rval = 0;
295 	if (mark_insdel(sp, LINE_INSERT, lno + 1))
296 		rval = 1;
297 	if (ex_g_insdel(sp, LINE_INSERT, lno + 1))
298 		rval = 1;
299 
300 	/*
301 	 * Update screen.
302 	 *
303 	 * XXX
304 	 * Nasty hack.  If multiple lines are input by the user, they aren't
305 	 * committed until an <ESC> is entered.  The problem is the screen was
306 	 * updated/scrolled as each line was entered.  So, when this routine
307 	 * is called to copy the new lines from the cut buffer into the file,
308 	 * it has to know not to update the screen again.
309 	 */
310 	return (scr_update(sp, lno, LINE_APPEND, update) || rval);
311 }
312 
313 /*
314  * db_insert --
315  *	Insert a line into the file.
316  *
317  * PUBLIC: int db_insert(SCR *, recno_t, char *, size_t);
318  */
319 int
320 db_insert(sp, lno, p, len)
321 	SCR *sp;
322 	recno_t lno;
323 	char *p;
324 	size_t len;
325 {
326 	DBT data, key;
327 	EXF *ep;
328 	int rval;
329 
330 #if defined(DEBUG) && 0
331 	TRACE(sp, "insert before %lu: len %lu {%.*s}\n",
332 	    (u_long)lno, (u_long)len, MIN(len, 20), p);
333 #endif
334 	/* Check for no underlying file. */
335 	if ((ep = sp->ep) == NULL) {
336 		ex_emsg(sp, NULL, EXM_NOFILEYET);
337 		return (1);
338 	}
339 
340 	/* Update file. */
341 	key.data = &lno;
342 	key.size = sizeof(lno);
343 	data.data = p;
344 	data.size = len;
345 	SIGBLOCK;
346 	if (ep->db->put(ep->db, &key, &data, R_IBEFORE) == -1) {
347 		msgq(sp, M_SYSERR,
348 		    "005|unable to insert at line %lu", (u_long)lno);
349 		return (1);
350 	}
351 	SIGUNBLOCK;
352 
353 	/* Flush the cache, update line count, before screen update. */
354 	if (lno >= ep->c_lno)
355 		ep->c_lno = OOBLNO;
356 	if (ep->c_nlines != OOBLNO)
357 		++ep->c_nlines;
358 
359 	/* File now dirty. */
360 	if (F_ISSET(ep, F_FIRSTMODIFY))
361 		(void)rcv_init(sp);
362 	F_SET(ep, F_MODIFIED);
363 
364 	/* Log change. */
365 	log_line(sp, lno, LOG_LINE_INSERT);
366 
367 	/* Update marks, @ and global commands. */
368 	rval = 0;
369 	if (mark_insdel(sp, LINE_INSERT, lno))
370 		rval = 1;
371 	if (ex_g_insdel(sp, LINE_INSERT, lno))
372 		rval = 1;
373 
374 	/* Update screen. */
375 	return (scr_update(sp, lno, LINE_INSERT, 1) || rval);
376 }
377 
378 /*
379  * db_set --
380  *	Store a line in the file.
381  *
382  * PUBLIC: int db_set(SCR *, recno_t, char *, size_t);
383  */
384 int
385 db_set(sp, lno, p, len)
386 	SCR *sp;
387 	recno_t lno;
388 	char *p;
389 	size_t len;
390 {
391 	DBT data, key;
392 	EXF *ep;
393 
394 #if defined(DEBUG) && 0
395 	TRACE(sp, "replace line %lu: len %lu {%.*s}\n",
396 	    (u_long)lno, (u_long)len, MIN(len, 20), p);
397 #endif
398 
399 	/* Check for no underlying file. */
400 	if ((ep = sp->ep) == NULL) {
401 		ex_emsg(sp, NULL, EXM_NOFILEYET);
402 		return (1);
403 	}
404 
405 	/* Log before change. */
406 	log_line(sp, lno, LOG_LINE_RESET_B);
407 
408 	/* Update file. */
409 	key.data = &lno;
410 	key.size = sizeof(lno);
411 	data.data = p;
412 	data.size = len;
413 	SIGBLOCK;
414 	if (ep->db->put(ep->db, &key, &data, 0) == -1) {
415 		msgq(sp, M_SYSERR,
416 		    "006|unable to store line %lu", (u_long)lno);
417 		return (1);
418 	}
419 	SIGUNBLOCK;
420 
421 	/* Flush the cache, before logging or screen update. */
422 	if (lno == ep->c_lno)
423 		ep->c_lno = OOBLNO;
424 
425 	/* File now dirty. */
426 	if (F_ISSET(ep, F_FIRSTMODIFY))
427 		(void)rcv_init(sp);
428 	F_SET(ep, F_MODIFIED);
429 
430 	/* Log after change. */
431 	log_line(sp, lno, LOG_LINE_RESET_F);
432 
433 	/* Update screen. */
434 	return (scr_update(sp, lno, LINE_RESET, 1));
435 }
436 
437 /*
438  * db_exist --
439  *	Return if a line exists.
440  *
441  * PUBLIC: int db_exist(SCR *, recno_t);
442  */
443 int
444 db_exist(sp, lno)
445 	SCR *sp;
446 	recno_t lno;
447 {
448 	EXF *ep;
449 
450 	/* Check for no underlying file. */
451 	if ((ep = sp->ep) == NULL) {
452 		ex_emsg(sp, NULL, EXM_NOFILEYET);
453 		return (1);
454 	}
455 
456 	if (lno == OOBLNO)
457 		return (0);
458 
459 	/*
460 	 * Check the last-line number cache.  Adjust the cached line
461 	 * number for the lines used by the text input buffers.
462 	 */
463 	if (ep->c_nlines != OOBLNO)
464 		return (lno <= (F_ISSET(sp, SC_TINPUT) ?
465 		    ep->c_nlines + (((TEXT *)CIRCLEQ_LAST(&sp->tiq))->lno -
466 		    ((TEXT *)CIRCLEQ_FIRST(&sp->tiq))->lno) : ep->c_nlines));
467 
468 	/* Go get the line. */
469 	return (!db_get(sp, lno, 0, NULL, NULL));
470 }
471 
472 /*
473  * db_last --
474  *	Return the number of lines in the file.
475  *
476  * PUBLIC: int db_last(SCR *, recno_t *);
477  */
478 int
479 db_last(sp, lnop)
480 	SCR *sp;
481 	recno_t *lnop;
482 {
483 	DBT data, key;
484 	EXF *ep;
485 	recno_t lno;
486 
487 	/* Check for no underlying file. */
488 	if ((ep = sp->ep) == NULL) {
489 		ex_emsg(sp, NULL, EXM_NOFILEYET);
490 		return (1);
491 	}
492 
493 	/*
494 	 * Check the last-line number cache.  Adjust the cached line
495 	 * number for the lines used by the text input buffers.
496 	 */
497 	if (ep->c_nlines != OOBLNO) {
498 		*lnop = ep->c_nlines;
499 		if (F_ISSET(sp, SC_TINPUT))
500 			*lnop += ((TEXT *)CIRCLEQ_LAST(&sp->tiq))->lno -
501 			    ((TEXT *)CIRCLEQ_FIRST(&sp->tiq))->lno;
502 		return (0);
503 	}
504 
505 	key.data = &lno;
506 	key.size = sizeof(lno);
507 
508 	switch (ep->db->seq(ep->db, &key, &data, R_LAST)) {
509         case -1:
510 		msgq(sp, M_SYSERR, "007|unable to get last line");
511 		*lnop = 0;
512 		return (1);
513         case 1:
514 		*lnop = 0;
515 		return (0);
516 	default:
517 		break;
518 	}
519 
520 	/* Fill the cache. */
521 	memcpy(&lno, key.data, sizeof(lno));
522 	ep->c_nlines = ep->c_lno = lno;
523 	ep->c_len = data.size;
524 	ep->c_lp = data.data;
525 
526 	/* Return the value. */
527 	*lnop = (F_ISSET(sp, SC_TINPUT) &&
528 	    ((TEXT *)CIRCLEQ_LAST(&sp->tiq))->lno > lno ?
529 	    ((TEXT *)CIRCLEQ_LAST(&sp->tiq))->lno : lno);
530 	return (0);
531 }
532 
533 /*
534  * db_err --
535  *	Report a line error.
536  *
537  * PUBLIC: void db_err(SCR *, recno_t);
538  */
539 void
540 db_err(sp, lno)
541 	SCR *sp;
542 	recno_t lno;
543 {
544 	msgq(sp, M_ERR,
545 	    "008|Error: unable to retrieve line %lu", (u_long)lno);
546 }
547 
548 /*
549  * scr_update --
550  *	Update all of the screens that are backed by the file that
551  *	just changed.
552  */
553 static int
554 scr_update(sp, lno, op, current)
555 	SCR *sp;
556 	recno_t lno;
557 	lnop_t op;
558 	int current;
559 {
560 	EXF *ep;
561 	SCR *tsp;
562 
563 	if (F_ISSET(sp, SC_EX))
564 		return (0);
565 
566 	ep = sp->ep;
567 	if (ep->refcnt != 1)
568 		CIRCLEQ_FOREACH(tsp, &sp->gp->dq, q)
569 			if (sp != tsp && tsp->ep == ep)
570 				if (vs_change(tsp, lno, op))
571 					return (1);
572 	return (current ? vs_change(sp, lno, op) : 0);
573 }
574