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