xref: /netbsd-src/external/bsd/nvi/dist/common/mark.c (revision 6a493d6bc668897c91594964a732d38505b70cbb)
1 /*	$NetBSD: mark.c,v 1.3 2013/11/25 22:43:46 christos Exp $ */
2 /*-
3  * Copyright (c) 1992, 1993, 1994
4  *	The Regents of the University of California.  All rights reserved.
5  * Copyright (c) 1992, 1993, 1994, 1995, 1996
6  *	Keith Bostic.  All rights reserved.
7  *
8  * See the LICENSE file for redistribution information.
9  */
10 
11 #include "config.h"
12 
13 #ifndef lint
14 static const char sccsid[] = "Id: mark.c,v 10.15 2001/06/25 15:19:11 skimo Exp  (Berkeley) Date: 2001/06/25 15:19:11 ";
15 #endif /* not lint */
16 
17 #include <sys/types.h>
18 #include <sys/queue.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 __P((SCR *, ARG_CHAR_T));
30 
31 /*
32  * Marks are maintained in a key sorted doubly 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 __P((SCR *, EXF *));
67  */
68 int
69 mark_init(SCR *sp, EXF *ep)
70 {
71 	/*
72 	 * !!!
73 	 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
74 	 *
75 	 * Set up the marks.
76 	 */
77 	LIST_INIT(&ep->marks);
78 	return (0);
79 }
80 
81 /*
82  * mark_end --
83  *	Free up the marks.
84  *
85  * PUBLIC: int mark_end __P((SCR *, EXF *));
86  */
87 int
88 mark_end(SCR *sp, EXF *ep)
89 {
90 	LMARK *lmp;
91 
92 	/*
93 	 * !!!
94 	 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
95 	 */
96 	while ((lmp = LIST_FIRST(&ep->marks)) != NULL) {
97 		LIST_REMOVE(lmp, q);
98 		free(lmp);
99 	}
100 	return (0);
101 }
102 
103 /*
104  * mark_get --
105  *	Get the location referenced by a mark.
106  *
107  * PUBLIC: int mark_get __P((SCR *, ARG_CHAR_T, MARK *, mtype_t));
108  */
109 int
110 mark_get(SCR *sp, ARG_CHAR_T key, MARK *mp, mtype_t mtype)
111 {
112 	LMARK *lmp;
113 
114 	if (key == ABSMARK2)
115 		key = ABSMARK1;
116 
117 	lmp = mark_find(sp, key);
118 	if (lmp == NULL || (ARG_CHAR_T)lmp->name != key) {
119 		msgq(sp, mtype, "017|Mark %s: not set", KEY_NAME(sp, key));
120                 return (1);
121 	}
122 	if (F_ISSET(lmp, MARK_DELETED)) {
123 		msgq(sp, mtype,
124 		    "018|Mark %s: the line was deleted", KEY_NAME(sp, key));
125                 return (1);
126 	}
127 
128 	/*
129 	 * !!!
130 	 * The absolute mark is initialized to lno 1/cno 0, and historically
131 	 * you could use it in an empty file.  Make such a mark always work.
132 	 */
133 	if ((lmp->lno != 1 || lmp->cno != 0) && !db_exist(sp, lmp->lno)) {
134 		msgq(sp, mtype,
135 		    "019|Mark %s: cursor position no longer exists",
136 		    KEY_NAME(sp, key));
137 		return (1);
138 	}
139 	mp->lno = lmp->lno;
140 	mp->cno = lmp->cno;
141 	return (0);
142 }
143 
144 /*
145  * mark_set --
146  *	Set the location referenced by a mark.
147  *
148  * PUBLIC: int mark_set __P((SCR *, ARG_CHAR_T, MARK *, int));
149  */
150 int
151 mark_set(SCR *sp, ARG_CHAR_T key, MARK *value, int userset)
152 {
153 	LMARK *lmp, *lmt;
154 
155 	if (key == ABSMARK2)
156 		key = ABSMARK1;
157 
158 	/*
159 	 * The rules are simple.  If the user is setting a mark (if it's a
160 	 * new mark this is always true), it always happens.  If not, it's
161 	 * an undo, and we set it if it's not already set or if it was set
162 	 * by a previous undo.
163 	 */
164 	lmp = mark_find(sp, key);
165 	if (lmp == NULL || (ARG_CHAR_T)lmp->name != key) {
166 		MALLOC_RET(sp, lmt, LMARK *, sizeof(LMARK));
167 		if (lmp == NULL) {
168 			LIST_INSERT_HEAD(&sp->ep->marks, lmt, q);
169 		} else
170 			LIST_INSERT_AFTER(lmp, lmt, q);
171 		lmp = lmt;
172 	} else if (!userset &&
173 	    !F_ISSET(lmp, MARK_DELETED) && F_ISSET(lmp, MARK_USERSET))
174 		return (0);
175 
176 	lmp->lno = value->lno;
177 	lmp->cno = value->cno;
178 	lmp->name = key;
179 	lmp->flags = userset ? MARK_USERSET : 0;
180 	return (0);
181 }
182 
183 /*
184  * mark_find --
185  *	Find the requested mark, or, the slot immediately before
186  *	where it would go.
187  */
188 static LMARK *
189 mark_find(SCR *sp, ARG_CHAR_T key)
190 {
191 	LMARK *lmp, *lastlmp;
192 
193 	/*
194 	 * Return the requested mark or the slot immediately before
195 	 * where it should go.
196 	 */
197 	for (lastlmp = NULL, lmp = LIST_FIRST(&sp->ep->marks);
198 	    lmp != NULL; lastlmp = lmp, lmp = LIST_NEXT(lmp, q))
199 		if ((ARG_CHAR_T)lmp->name >= key)
200 			return ((ARG_CHAR_T)lmp->name == key ? lmp : lastlmp);
201 	return (lastlmp);
202 }
203 
204 /*
205  * mark_insdel --
206  *	Update the marks based on an insertion or deletion.
207  *
208  * PUBLIC: int mark_insdel __P((SCR *, lnop_t, db_recno_t));
209  */
210 int
211 mark_insdel(SCR *sp, lnop_t op, db_recno_t lno)
212 {
213 	LMARK *lmp;
214 	db_recno_t lline;
215 
216 	switch (op) {
217 	case LINE_APPEND:
218 		/* All insert/append operations are done as inserts. */
219 		abort();
220 	case LINE_DELETE:
221 		LIST_FOREACH(lmp, &sp->ep->marks, q)
222 			if (lmp->lno >= lno) {
223 				if (lmp->lno == lno) {
224 					F_SET(lmp, MARK_DELETED);
225 					(void)log_mark(sp, lmp);
226 				} else
227 					--lmp->lno;
228 			}
229 		break;
230 	case LINE_INSERT:
231 		/*
232 		 * XXX
233 		 * Very nasty special case.  If the file was empty, then we're
234 		 * adding the first line, which is a replacement.  So, we don't
235 		 * modify the marks.  This is a hack to make:
236 		 *
237 		 *	mz:r!echo foo<carriage-return>'z
238 		 *
239 		 * work, i.e. historically you could mark the "line" in an empty
240 		 * file and replace it, and continue to use the mark.  Insane,
241 		 * well, yes, I know, but someone complained.
242 		 *
243 		 * Check for line #2 before going to the end of the file.
244 		 */
245 		if (!db_exist(sp, 2)) {
246 			if (db_last(sp, &lline))
247 				return (1);
248 			if (lline == 1)
249 				return (0);
250 		}
251 
252 		LIST_FOREACH(lmp, &sp->ep->marks, q)
253 			if (lmp->lno >= lno)
254 				++lmp->lno;
255 		break;
256 	case LINE_RESET:
257 		break;
258 	}
259 	return (0);
260 }
261