Merge from vendor branch TNFTP:
[dragonfly.git] / contrib / nvi / common / mark.c
blob0ac1fc28bf9c8f8e531b0a76ec143e0c1b77480f
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.
7 * See the LICENSE file for redistribution information.
8 */
10 #include "config.h"
12 #ifndef lint
13 static const char sccsid[] = "@(#)mark.c 10.13 (Berkeley) 7/19/96";
14 #endif /* not lint */
16 #include <sys/types.h>
17 #include <sys/queue.h>
19 #include <bitstring.h>
20 #include <errno.h>
21 #include <limits.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
26 #include "common.h"
28 static LMARK *mark_find __P((SCR *, ARG_CHAR_T));
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.
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.
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.
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".
62 * mark_init --
63 * Set up the marks.
65 * PUBLIC: int mark_init __P((SCR *, EXF *));
67 int
68 mark_init(sp, ep)
69 SCR *sp;
70 EXF *ep;
73 * !!!
74 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
76 * Set up the marks.
78 LIST_INIT(&ep->marks);
79 return (0);
83 * mark_end --
84 * Free up the marks.
86 * PUBLIC: int mark_end __P((SCR *, EXF *));
88 int
89 mark_end(sp, ep)
90 SCR *sp;
91 EXF *ep;
93 LMARK *lmp;
96 * !!!
97 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
99 while ((lmp = ep->marks.lh_first) != NULL) {
100 LIST_REMOVE(lmp, q);
101 free(lmp);
103 return (0);
107 * mark_get --
108 * Get the location referenced by a mark.
110 * PUBLIC: int mark_get __P((SCR *, ARG_CHAR_T, MARK *, mtype_t));
113 mark_get(sp, key, mp, mtype)
114 SCR *sp;
115 ARG_CHAR_T key;
116 MARK *mp;
117 mtype_t mtype;
119 LMARK *lmp;
121 if (key == ABSMARK2)
122 key = ABSMARK1;
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);
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);
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.
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);
146 mp->lno = lmp->lno;
147 mp->cno = lmp->cno;
148 return (0);
152 * mark_set --
153 * Set the location referenced by a mark.
155 * PUBLIC: int mark_set __P((SCR *, ARG_CHAR_T, MARK *, int));
158 mark_set(sp, key, value, userset)
159 SCR *sp;
160 ARG_CHAR_T key;
161 MARK *value;
162 int userset;
164 LMARK *lmp, *lmt;
166 if (key == ABSMARK2)
167 key = ABSMARK1;
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.
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);
187 lmp->lno = value->lno;
188 lmp->cno = value->cno;
189 lmp->name = key;
190 lmp->flags = userset ? MARK_USERSET : 0;
191 return (0);
195 * mark_find --
196 * Find the requested mark, or, the slot immediately before
197 * where it would go.
199 static LMARK *
200 mark_find(sp, key)
201 SCR *sp;
202 ARG_CHAR_T key;
204 LMARK *lmp, *lastlmp;
207 * Return the requested mark or the slot immediately before
208 * where it should go.
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);
218 * mark_insdel --
219 * Update the marks based on an insertion or deletion.
221 * PUBLIC: int mark_insdel __P((SCR *, lnop_t, recno_t));
224 mark_insdel(sp, op, lno)
225 SCR *sp;
226 lnop_t op;
227 recno_t lno;
229 LMARK *lmp;
230 recno_t lline;
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:
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:
253 * mz:r!echo foo<carriage-return>'z
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.
259 * Check for line #2 before going to the end of the file.
261 if (!db_exist(sp, 2)) {
262 if (db_last(sp, &lline))
263 return (1);
264 if (lline == 1)
265 return (0);
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;
276 return (0);