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