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