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