xref: /dragonfly/contrib/nvi2/common/mark.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 <stdlib.h>
21 #include <string.h>
22 
23 #include "common.h"
24 
25 static LMARK *mark_find(SCR *, ARG_CHAR_T);
26 
27 /*
28  * Marks are maintained in a key sorted singly linked list.  We can't
29  * use arrays because we have no idea how big an index key could be.
30  * The underlying assumption is that users don't have more than, say,
31  * 10 marks at any one time, so this will be is fast enough.
32  *
33  * Marks are fixed, and modifications to the line don't update the mark's
34  * position in the line.  This can be hard.  If you add text to the line,
35  * place a mark in that text, undo the addition and use ` to move to the
36  * mark, the location will have disappeared.  It's tempting to try to adjust
37  * the mark with the changes in the line, but this is hard to do, especially
38  * if we've given the line to v_ntext.c:v_ntext() for editing.  Historic vi
39  * would move to the first non-blank on the line when the mark location was
40  * past the end of the line.  This can be complicated by deleting to a mark
41  * that has disappeared using the ` command.  Historic vi treated this as
42  * a line-mode motion and deleted the line.  This implementation complains to
43  * the user.
44  *
45  * In historic vi, marks returned if the operation was undone, unless the
46  * mark had been subsequently reset.  Tricky.  This is hard to start with,
47  * but in the presence of repeated undo it gets nasty.  When a line is
48  * deleted, we delete (and log) any marks on that line.  An undo will create
49  * the mark.  Any mark creations are noted as to whether the user created
50  * it or if it was created by an undo.  The former cannot be reset by another
51  * undo, but the latter may.
52  *
53  * All of these routines translate ABSMARK2 to ABSMARK1.  Setting either of
54  * the absolute mark locations sets both, so that "m'" and "m`" work like
55  * they, ah, for lack of a better word, "should".
56  */
57 
58 /*
59  * mark_init --
60  *        Set up the marks.
61  *
62  * PUBLIC: int mark_init(SCR *, EXF *);
63  */
64 int
mark_init(SCR * sp,EXF * ep)65 mark_init(SCR *sp, EXF *ep)
66 {
67           /*
68            * !!!
69            * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
70            *
71            * Set up the marks.
72            */
73           SLIST_INIT(ep->marks);
74           return (0);
75 }
76 
77 /*
78  * mark_end --
79  *        Free up the marks.
80  *
81  * PUBLIC: int mark_end(SCR *, EXF *);
82  */
83 int
mark_end(SCR * sp,EXF * ep)84 mark_end(SCR *sp, EXF *ep)
85 {
86           LMARK *lmp;
87 
88           /*
89            * !!!
90            * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
91            */
92           while ((lmp = SLIST_FIRST(ep->marks)) != NULL) {
93                     SLIST_REMOVE_HEAD(ep->marks, q);
94                     free(lmp);
95           }
96           return (0);
97 }
98 
99 /*
100  * mark_get --
101  *        Get the location referenced by a mark.
102  *
103  * PUBLIC: int mark_get(SCR *, ARG_CHAR_T, MARK *, mtype_t);
104  */
105 int
mark_get(SCR * sp,ARG_CHAR_T key,MARK * mp,mtype_t mtype)106 mark_get(SCR *sp, ARG_CHAR_T key, MARK *mp, mtype_t mtype)
107 {
108           LMARK *lmp;
109 
110           if (key == ABSMARK2)
111                     key = ABSMARK1;
112 
113           lmp = mark_find(sp, key);
114           if (lmp == NULL || lmp->name != key) {
115                     msgq(sp, mtype, "017|Mark %s: not set", KEY_NAME(sp, key));
116                     return (1);
117           }
118           if (F_ISSET(lmp, MARK_DELETED)) {
119                     msgq(sp, mtype,
120                         "018|Mark %s: the line was deleted", KEY_NAME(sp, key));
121                     return (1);
122           }
123 
124           /*
125            * !!!
126            * The absolute mark is initialized to lno 1/cno 0, and historically
127            * you could use it in an empty file.  Make such a mark always work.
128            */
129           if ((lmp->lno != 1 || lmp->cno != 0) && !db_exist(sp, lmp->lno)) {
130                     msgq(sp, mtype,
131                         "019|Mark %s: cursor position no longer exists",
132                         KEY_NAME(sp, key));
133                     return (1);
134           }
135           mp->lno = lmp->lno;
136           mp->cno = lmp->cno;
137           return (0);
138 }
139 
140 /*
141  * mark_set --
142  *        Set the location referenced by a mark.
143  *
144  * PUBLIC: int mark_set(SCR *, ARG_CHAR_T, MARK *, int);
145  */
146 int
mark_set(SCR * sp,ARG_CHAR_T key,MARK * value,int userset)147 mark_set(SCR *sp, ARG_CHAR_T key, MARK *value, int userset)
148 {
149           LMARK *lmp, *lmt;
150 
151           if (key == ABSMARK2)
152                     key = ABSMARK1;
153 
154           /*
155            * The rules are simple.  If the user is setting a mark (if it's a
156            * new mark this is always true), it always happens.  If not, it's
157            * an undo, and we set it if it's not already set or if it was set
158            * by a previous undo.
159            */
160           lmp = mark_find(sp, key);
161           if (lmp == NULL || lmp->name != key) {
162                     MALLOC_RET(sp, lmt, sizeof(LMARK));
163                     if (lmp == NULL) {
164                               SLIST_INSERT_HEAD(sp->ep->marks, lmt, q);
165                     } else
166                               SLIST_INSERT_AFTER(lmp, lmt, q);
167                     lmp = lmt;
168           } else if (!userset &&
169               !F_ISSET(lmp, MARK_DELETED) && F_ISSET(lmp, MARK_USERSET))
170                     return (0);
171 
172           lmp->lno = value->lno;
173           lmp->cno = value->cno;
174           lmp->name = key;
175           lmp->flags = userset ? MARK_USERSET : 0;
176           return (0);
177 }
178 
179 /*
180  * mark_find --
181  *        Find the requested mark, or, the slot immediately before
182  *        where it would go.
183  */
184 static LMARK *
mark_find(SCR * sp,ARG_CHAR_T key)185 mark_find(SCR *sp, ARG_CHAR_T key)
186 {
187           LMARK *lmp, *lastlmp = NULL;
188 
189           /*
190            * Return the requested mark or the slot immediately before
191            * where it should go.
192            */
193           SLIST_FOREACH(lmp, sp->ep->marks, q) {
194                     if (lmp->name >= key)
195                               return (lmp->name == key ? lmp : lastlmp);
196                     lastlmp = lmp;
197           }
198           return (lastlmp);
199 }
200 
201 /*
202  * mark_insdel --
203  *        Update the marks based on an insertion or deletion.
204  *
205  * PUBLIC: int mark_insdel(SCR *, lnop_t, recno_t);
206  */
207 int
mark_insdel(SCR * sp,lnop_t op,recno_t lno)208 mark_insdel(SCR *sp, lnop_t op, recno_t lno)
209 {
210           LMARK *lmp;
211           recno_t lline;
212 
213           switch (op) {
214           case LINE_APPEND:
215                     /* All insert/append operations are done as inserts. */
216                     abort();
217           case LINE_DELETE:
218                     SLIST_FOREACH(lmp, sp->ep->marks, q)
219                               if (lmp->lno >= lno)
220                                         if (lmp->lno == lno) {
221                                                   F_SET(lmp, MARK_DELETED);
222                                                   (void)log_mark(sp, lmp);
223                                         } else
224                                                   --lmp->lno;
225                     break;
226           case LINE_INSERT:
227                     /*
228                      * XXX
229                      * Very nasty special case.  If the file was empty, then we're
230                      * adding the first line, which is a replacement.  So, we don't
231                      * modify the marks.  This is a hack to make:
232                      *
233                      *        mz:r!echo foo<carriage-return>'z
234                      *
235                      * work, i.e. historically you could mark the "line" in an empty
236                      * file and replace it, and continue to use the mark.  Insane,
237                      * well, yes, I know, but someone complained.
238                      *
239                      * Check for line #2 before going to the end of the file.
240                      */
241                     if (!db_exist(sp, 2)) {
242                               if (db_last(sp, &lline))
243                                         return (1);
244                               if (lline == 1)
245                                         return (0);
246                     }
247 
248                     SLIST_FOREACH(lmp, sp->ep->marks, q)
249                               if (lmp->lno >= lno)
250                                         ++lmp->lno;
251                     break;
252           case LINE_RESET:
253                     break;
254           }
255           return (0);
256 }
257