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
mark_init(sp,ep)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
mark_end(sp,ep)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
mark_get(sp,key,mp,mtype)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
mark_set(sp,key,value,userset)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 *
mark_find(sp,key)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
mark_insdel(sp,op,lno)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