1 /* $OpenBSD: mark.c,v 1.7 2006/01/08 21:05:39 miod 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 #ifndef lint 15 static const char sccsid[] = "@(#)mark.c 10.13 (Berkeley) 7/19/96"; 16 #endif /* not lint */ 17 18 #include <sys/types.h> 19 #include <sys/queue.h> 20 21 #include <bitstring.h> 22 #include <errno.h> 23 #include <limits.h> 24 #include <stdio.h> 25 #include <stdlib.h> 26 #include <string.h> 27 28 #include "common.h" 29 30 static LMARK *mark_find(SCR *, ARG_CHAR_T); 31 32 /* 33 * Marks are maintained in a key sorted doubly linked list. We can't 34 * use arrays because we have no idea how big an index key could be. 35 * The underlying assumption is that users don't have more than, say, 36 * 10 marks at any one time, so this will be is fast enough. 37 * 38 * Marks are fixed, and modifications to the line don't update the mark's 39 * position in the line. This can be hard. If you add text to the line, 40 * place a mark in that text, undo the addition and use ` to move to the 41 * mark, the location will have disappeared. It's tempting to try to adjust 42 * the mark with the changes in the line, but this is hard to do, especially 43 * if we've given the line to v_ntext.c:v_ntext() for editing. Historic vi 44 * would move to the first non-blank on the line when the mark location was 45 * past the end of the line. This can be complicated by deleting to a mark 46 * that has disappeared using the ` command. Historic vi treated this as 47 * a line-mode motion and deleted the line. This implementation complains to 48 * the user. 49 * 50 * In historic vi, marks returned if the operation was undone, unless the 51 * mark had been subsequently reset. Tricky. This is hard to start with, 52 * but in the presence of repeated undo it gets nasty. When a line is 53 * deleted, we delete (and log) any marks on that line. An undo will create 54 * the mark. Any mark creations are noted as to whether the user created 55 * it or if it was created by an undo. The former cannot be reset by another 56 * undo, but the latter may. 57 * 58 * All of these routines translate ABSMARK2 to ABSMARK1. Setting either of 59 * the absolute mark locations sets both, so that "m'" and "m`" work like 60 * they, ah, for lack of a better word, "should". 61 */ 62 63 /* 64 * mark_init -- 65 * Set up the marks. 66 * 67 * PUBLIC: int mark_init(SCR *, EXF *); 68 */ 69 int 70 mark_init(sp, ep) 71 SCR *sp; 72 EXF *ep; 73 { 74 /* 75 * !!! 76 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER. 77 * 78 * Set up the marks. 79 */ 80 LIST_INIT(&ep->marks); 81 return (0); 82 } 83 84 /* 85 * mark_end -- 86 * Free up the marks. 87 * 88 * PUBLIC: int mark_end(SCR *, EXF *); 89 */ 90 int 91 mark_end(sp, ep) 92 SCR *sp; 93 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(SCR *, ARG_CHAR_T, MARK *, mtype_t); 113 */ 114 int 115 mark_get(sp, key, mp, mtype) 116 SCR *sp; 117 ARG_CHAR_T key; 118 MARK *mp; 119 mtype_t mtype; 120 { 121 LMARK *lmp; 122 123 if (key == ABSMARK2) 124 key = ABSMARK1; 125 126 lmp = mark_find(sp, key); 127 if (lmp == NULL || lmp->name != key) { 128 msgq(sp, mtype, "017|Mark %s: not set", KEY_NAME(sp, key)); 129 return (1); 130 } 131 if (F_ISSET(lmp, MARK_DELETED)) { 132 msgq(sp, mtype, 133 "018|Mark %s: the line was deleted", KEY_NAME(sp, key)); 134 return (1); 135 } 136 137 /* 138 * !!! 139 * The absolute mark is initialized to lno 1/cno 0, and historically 140 * you could use it in an empty file. Make such a mark always work. 141 */ 142 if ((lmp->lno != 1 || lmp->cno != 0) && !db_exist(sp, lmp->lno)) { 143 msgq(sp, mtype, 144 "019|Mark %s: cursor position no longer exists", 145 KEY_NAME(sp, key)); 146 return (1); 147 } 148 mp->lno = lmp->lno; 149 mp->cno = lmp->cno; 150 return (0); 151 } 152 153 /* 154 * mark_set -- 155 * Set the location referenced by a mark. 156 * 157 * PUBLIC: int mark_set(SCR *, ARG_CHAR_T, MARK *, int); 158 */ 159 int 160 mark_set(sp, key, value, userset) 161 SCR *sp; 162 ARG_CHAR_T key; 163 MARK *value; 164 int userset; 165 { 166 LMARK *lmp, *lmt; 167 168 if (key == ABSMARK2) 169 key = ABSMARK1; 170 171 /* 172 * The rules are simple. If the user is setting a mark (if it's a 173 * new mark this is always true), it always happens. If not, it's 174 * an undo, and we set it if it's not already set or if it was set 175 * by a previous undo. 176 */ 177 lmp = mark_find(sp, key); 178 if (lmp == NULL || lmp->name != key) { 179 MALLOC_RET(sp, lmt, LMARK *, sizeof(LMARK)); 180 if (lmp == NULL) { 181 LIST_INSERT_HEAD(&sp->ep->marks, lmt, q); 182 } else 183 LIST_INSERT_AFTER(lmp, lmt, q); 184 lmp = lmt; 185 } else if (!userset && 186 !F_ISSET(lmp, MARK_DELETED) && F_ISSET(lmp, MARK_USERSET)) 187 return (0); 188 189 lmp->lno = value->lno; 190 lmp->cno = value->cno; 191 lmp->name = key; 192 lmp->flags = userset ? MARK_USERSET : 0; 193 return (0); 194 } 195 196 /* 197 * mark_find -- 198 * Find the requested mark, or, the slot immediately before 199 * where it would go. 200 */ 201 static LMARK * 202 mark_find(sp, key) 203 SCR *sp; 204 ARG_CHAR_T key; 205 { 206 LMARK *lmp, *lastlmp; 207 208 /* 209 * Return the requested mark or the slot immediately before 210 * where it should go. 211 */ 212 for (lastlmp = NULL, lmp = LIST_FIRST(&sp->ep->marks); 213 lmp != NULL; lastlmp = lmp, lmp = LIST_NEXT(lmp, q)) 214 if (lmp->name >= key) 215 return (lmp->name == key ? lmp : lastlmp); 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(SCR *, lnop_t, recno_t); 224 */ 225 int 226 mark_insdel(sp, op, lno) 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 LIST_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 } 247 break; 248 case LINE_INSERT: 249 /* 250 * XXX 251 * Very nasty special case. If the file was empty, then we're 252 * adding the first line, which is a replacement. So, we don't 253 * modify the marks. This is a hack to make: 254 * 255 * mz:r!echo foo<carriage-return>'z 256 * 257 * work, i.e. historically you could mark the "line" in an empty 258 * file and replace it, and continue to use the mark. Insane, 259 * well, yes, I know, but someone complained. 260 * 261 * Check for line #2 before going to the end of the file. 262 */ 263 if (!db_exist(sp, 2)) { 264 if (db_last(sp, &lline)) 265 return (1); 266 if (lline == 1) 267 return (0); 268 } 269 270 LIST_FOREACH(lmp, &sp->ep->marks, q) 271 if (lmp->lno >= lno) 272 ++lmp->lno; 273 break; 274 case LINE_RESET: 275 break; 276 } 277 return (0); 278 } 279