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 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 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 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 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 * 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 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