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