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