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[] = "@(#)mark.c	10.13 (Berkeley) 7/19/96";
14#endif /* not lint */
15
16#include <sys/types.h>
17#include <sys/queue.h>
18
19#include <bitstring.h>
20#include <errno.h>
21#include <limits.h>
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
25
26#include "common.h"
27
28static LMARK *mark_find __P((SCR *, ARG_CHAR_T));
29
30/*
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.
35 *
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.
47 *
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.
55 *
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".
59 */
60
61/*
62 * mark_init --
63 *	Set up the marks.
64 *
65 * PUBLIC: int mark_init __P((SCR *, EXF *));
66 */
67int
68mark_init(sp, ep)
69	SCR *sp;
70	EXF *ep;
71{
72	/*
73	 * !!!
74	 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
75	 *
76	 * Set up the marks.
77	 */
78	LIST_INIT(&ep->marks);
79	return (0);
80}
81
82/*
83 * mark_end --
84 *	Free up the marks.
85 *
86 * PUBLIC: int mark_end __P((SCR *, EXF *));
87 */
88int
89mark_end(sp, ep)
90	SCR *sp;
91	EXF *ep;
92{
93	LMARK *lmp;
94
95	/*
96	 * !!!
97	 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
98	 */
99	while ((lmp = ep->marks.lh_first) != NULL) {
100		LIST_REMOVE(lmp, q);
101		free(lmp);
102	}
103	return (0);
104}
105
106/*
107 * mark_get --
108 *	Get the location referenced by a mark.
109 *
110 * PUBLIC: int mark_get __P((SCR *, ARG_CHAR_T, MARK *, mtype_t));
111 */
112int
113mark_get(sp, key, mp, mtype)
114	SCR *sp;
115	ARG_CHAR_T key;
116	MARK *mp;
117	mtype_t mtype;
118{
119	LMARK *lmp;
120
121	if (key == ABSMARK2)
122		key = ABSMARK1;
123
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);
128	}
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);
133	}
134
135	/*
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.
139	 */
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);
145	}
146	mp->lno = lmp->lno;
147	mp->cno = lmp->cno;
148	return (0);
149}
150
151/*
152 * mark_set --
153 *	Set the location referenced by a mark.
154 *
155 * PUBLIC: int mark_set __P((SCR *, ARG_CHAR_T, MARK *, int));
156 */
157int
158mark_set(sp, key, value, userset)
159	SCR *sp;
160	ARG_CHAR_T key;
161	MARK *value;
162	int userset;
163{
164	LMARK *lmp, *lmt;
165
166	if (key == ABSMARK2)
167		key = ABSMARK1;
168
169	/*
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.
174	 */
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);
186
187	lmp->lno = value->lno;
188	lmp->cno = value->cno;
189	lmp->name = key;
190	lmp->flags = userset ? MARK_USERSET : 0;
191	return (0);
192}
193
194/*
195 * mark_find --
196 *	Find the requested mark, or, the slot immediately before
197 *	where it would go.
198 */
199static LMARK *
200mark_find(sp, key)
201	SCR *sp;
202	ARG_CHAR_T key;
203{
204	LMARK *lmp, *lastlmp;
205
206	/*
207	 * Return the requested mark or the slot immediately before
208	 * where it should go.
209	 */
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);
215}
216
217/*
218 * mark_insdel --
219 *	Update the marks based on an insertion or deletion.
220 *
221 * PUBLIC: int mark_insdel __P((SCR *, lnop_t, recno_t));
222 */
223int
224mark_insdel(sp, op, lno)
225	SCR *sp;
226	lnop_t op;
227	recno_t lno;
228{
229	LMARK *lmp;
230	recno_t lline;
231
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:
247		/*
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:
252		 *
253		 *	mz:r!echo foo<carriage-return>'z
254		 *
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.
258		 *
259		 * Check for line #2 before going to the end of the file.
260		 */
261		if (!db_exist(sp, 2)) {
262			if (db_last(sp, &lline))
263				return (1);
264			if (lline == 1)
265				return (0);
266		}
267
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;
275	}
276	return (0);
277}
278