mark.c revision 296373
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
13static 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
29static LMARK *mark_find __P((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 __P((SCR *, EXF *));
67 */
68int
69mark_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 __P((SCR *, EXF *));
88 */
89int
90mark_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 __P((SCR *, ARG_CHAR_T, MARK *, mtype_t));
112 */
113int
114mark_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 __P((SCR *, ARG_CHAR_T, MARK *, int));
157 */
158int
159mark_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 */
200static LMARK *
201mark_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 __P((SCR *, lnop_t, recno_t));
224 */
225int
226mark_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