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 13 static const char sccsid[] = "@(#)v_paragraph.c 10.7 (Berkeley) 3/6/96"; 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/common.h" 28 #include "vi.h" 29 30 #define INTEXT_CHECK { \ 31 if (len == 0 || v_isempty(p, len)) { \ 32 if (!--cnt) \ 33 goto found; \ 34 pstate = P_INBLANK; \ 35 } \ 36 /* \ 37 * !!! \ 38 * Historic documentation (USD:15-11, 4.2) said that formfeed \ 39 * characters (^L) in the first column delimited paragraphs. \ 40 * The historic vi code mentions formfeed characters, but never \ 41 * implements them. It seems reasonable, do it. \ 42 */ \ 43 if (p[0] == '\014') { \ 44 if (!--cnt) \ 45 goto found; \ 46 continue; \ 47 } \ 48 if (p[0] != '.' || len < 2) \ 49 continue; \ 50 for (lp = VIP(sp)->ps; *lp != '\0'; lp += 2) \ 51 if (lp[0] == p[1] && \ 52 (lp[1] == ' ' && len == 2 || lp[1] == p[2]) && \ 53 !--cnt) \ 54 goto found; \ 55 } 56 57 /* 58 * v_paragraphf -- [count]} 59 * Move forward count paragraphs. 60 * 61 * Paragraphs are empty lines after text, formfeed characters, or values 62 * from the paragraph or section options. 63 * 64 * PUBLIC: int v_paragraphf __P((SCR *, VICMD *)); 65 */ 66 int 67 v_paragraphf(sp, vp) 68 SCR *sp; 69 VICMD *vp; 70 { 71 enum { P_INTEXT, P_INBLANK } pstate; 72 size_t lastlen, len; 73 recno_t cnt, lastlno, lno; 74 int isempty; 75 char *p, *lp; 76 77 /* 78 * !!! 79 * If the starting cursor position is at or before any non-blank 80 * characters in the line, i.e. the movement is cutting all of the 81 * line's text, the buffer is in line mode. It's a lot easier to 82 * check here, because we know that the end is going to be the start 83 * or end of a line. 84 * 85 * This was historical practice in vi, with a single exception. If 86 * the paragraph movement was from the start of the last line to EOF, 87 * then all the characters were deleted from the last line, but the 88 * line itself remained. If somebody complains, don't pause, don't 89 * hesitate, just hit them. 90 */ 91 if (ISMOTION(vp)) 92 if (vp->m_start.cno == 0) 93 F_SET(vp, VM_LMODE); 94 else { 95 vp->m_stop = vp->m_start; 96 vp->m_stop.cno = 0; 97 if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno)) 98 return (1); 99 if (vp->m_start.cno <= vp->m_stop.cno) 100 F_SET(vp, VM_LMODE); 101 } 102 103 /* Figure out what state we're currently in. */ 104 lno = vp->m_start.lno; 105 if (db_get(sp, lno, 0, &p, &len)) 106 goto eof; 107 108 /* 109 * If we start in text, we want to switch states 110 * (2 * N - 1) times, in non-text, (2 * N) times. 111 */ 112 cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; 113 cnt *= 2; 114 if (len == 0 || v_isempty(p, len)) 115 pstate = P_INBLANK; 116 else { 117 --cnt; 118 pstate = P_INTEXT; 119 } 120 121 for (;;) { 122 lastlno = lno; 123 lastlen = len; 124 if (db_get(sp, ++lno, 0, &p, &len)) 125 goto eof; 126 switch (pstate) { 127 case P_INTEXT: 128 INTEXT_CHECK; 129 break; 130 case P_INBLANK: 131 if (len == 0 || v_isempty(p, len)) 132 break; 133 if (--cnt) { 134 pstate = P_INTEXT; 135 break; 136 } 137 /* 138 * !!! 139 * Non-motion commands move to the end of the range, 140 * delete and yank stay at the start. Ignore others. 141 * Adjust the end of the range for motion commands; 142 * historically, a motion component was to the end of 143 * the previous line, whereas the movement command was 144 * to the start of the new "paragraph". 145 */ 146 found: if (ISMOTION(vp)) { 147 vp->m_stop.lno = lastlno; 148 vp->m_stop.cno = lastlen ? lastlen - 1 : 0; 149 vp->m_final = vp->m_start; 150 } else { 151 vp->m_stop.lno = lno; 152 vp->m_stop.cno = 0; 153 vp->m_final = vp->m_stop; 154 } 155 return (0); 156 default: 157 abort(); 158 } 159 } 160 161 /* 162 * !!! 163 * Adjust end of the range for motion commands; EOF is a movement 164 * sink. The } command historically moved to the end of the last 165 * line, not the beginning, from any position before the end of the 166 * last line. It also historically worked on empty files, so we 167 * have to make it okay. 168 */ 169 eof: if (vp->m_start.lno == lno || vp->m_start.lno == lno - 1) { 170 if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { 171 if (!isempty) 172 return (1); 173 vp->m_start.cno = 0; 174 return (0); 175 } 176 if (vp->m_start.cno == (len ? len - 1 : 0)) { 177 v_eof(sp, NULL); 178 return (1); 179 } 180 } 181 /* 182 * !!! 183 * Non-motion commands move to the end of the range, delete 184 * and yank stay at the start. Ignore others. 185 * 186 * If deleting the line (which happens if deleting to EOF), then 187 * cursor movement is to the first nonblank. 188 */ 189 if (ISMOTION(vp) && ISCMD(vp->rkp, 'd')) { 190 F_CLR(vp, VM_RCM_MASK); 191 F_SET(vp, VM_RCM_SETFNB); 192 } 193 vp->m_stop.lno = lno - 1; 194 vp->m_stop.cno = len ? len - 1 : 0; 195 vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; 196 return (0); 197 } 198 199 /* 200 * v_paragraphb -- [count]{ 201 * Move backward count paragraphs. 202 * 203 * PUBLIC: int v_paragraphb __P((SCR *, VICMD *)); 204 */ 205 int 206 v_paragraphb(sp, vp) 207 SCR *sp; 208 VICMD *vp; 209 { 210 enum { P_INTEXT, P_INBLANK } pstate; 211 size_t len; 212 recno_t cnt, lno; 213 char *p, *lp; 214 215 /* 216 * !!! 217 * Check for SOF. The historic vi didn't complain if users hit SOF 218 * repeatedly, unless it was part of a motion command. There is no 219 * question but that Emerson's editor of choice was vi. 220 * 221 * The { command historically moved to the beginning of the first 222 * line if invoked on the first line. 223 * 224 * !!! 225 * If the starting cursor position is in the first column (backward 226 * paragraph movements did NOT historically pay attention to non-blank 227 * characters) i.e. the movement is cutting the entire line, the buffer 228 * is in line mode. Cuts from the beginning of the line also did not 229 * cut the current line, but started at the previous EOL. 230 * 231 * Correct for a left motion component while we're thinking about it. 232 */ 233 lno = vp->m_start.lno; 234 235 if (ISMOTION(vp)) 236 if (vp->m_start.cno == 0) { 237 if (vp->m_start.lno == 1) { 238 v_sof(sp, &vp->m_start); 239 return (1); 240 } else 241 --vp->m_start.lno; 242 F_SET(vp, VM_LMODE); 243 } else 244 --vp->m_start.cno; 245 246 if (vp->m_start.lno <= 1) 247 goto sof; 248 249 /* Figure out what state we're currently in. */ 250 if (db_get(sp, lno, 0, &p, &len)) 251 goto sof; 252 253 /* 254 * If we start in text, we want to switch states 255 * (2 * N - 1) times, in non-text, (2 * N) times. 256 */ 257 cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; 258 cnt *= 2; 259 if (len == 0 || v_isempty(p, len)) 260 pstate = P_INBLANK; 261 else { 262 --cnt; 263 pstate = P_INTEXT; 264 265 /* 266 * !!! 267 * If the starting cursor is past the first column, 268 * the current line is checked for a paragraph. 269 */ 270 if (vp->m_start.cno > 0) 271 ++lno; 272 } 273 274 for (;;) { 275 if (db_get(sp, --lno, 0, &p, &len)) 276 goto sof; 277 switch (pstate) { 278 case P_INTEXT: 279 INTEXT_CHECK; 280 break; 281 case P_INBLANK: 282 if (len != 0 && !v_isempty(p, len)) { 283 if (!--cnt) 284 goto found; 285 pstate = P_INTEXT; 286 } 287 break; 288 default: 289 abort(); 290 } 291 } 292 293 /* SOF is a movement sink. */ 294 sof: lno = 1; 295 296 found: vp->m_stop.lno = lno; 297 vp->m_stop.cno = 0; 298 299 /* 300 * All commands move to the end of the range. (We already 301 * adjusted the start of the range for motion commands). 302 */ 303 vp->m_final = vp->m_stop; 304 return (0); 305 } 306 307 /* 308 * v_buildps -- 309 * Build the paragraph command search pattern. 310 * 311 * PUBLIC: int v_buildps __P((SCR *, char *, char *)); 312 */ 313 int 314 v_buildps(sp, p_p, s_p) 315 SCR *sp; 316 char *p_p, *s_p; 317 { 318 VI_PRIVATE *vip; 319 size_t p_len, s_len; 320 char *p; 321 322 /* 323 * The vi paragraph command searches for either a paragraph or 324 * section option macro. 325 */ 326 p_len = p_p == NULL ? 0 : strlen(p_p); 327 s_len = s_p == NULL ? 0 : strlen(s_p); 328 329 if (p_len == 0 && s_len == 0) 330 return (0); 331 332 MALLOC_RET(sp, p, char *, p_len + s_len + 1); 333 334 vip = VIP(sp); 335 if (vip->ps != NULL) 336 free(vip->ps); 337 338 if (p_p != NULL) 339 memmove(p, p_p, p_len + 1); 340 if (s_p != NULL) 341 memmove(p + p_len, s_p, s_len + 1); 342 vip->ps = p; 343 return (0); 344 } 345