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