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