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_sentence.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 <ctype.h> 22 #include <limits.h> 23 #include <stdio.h> 24 25 #include "../common/common.h" 26 #include "vi.h" 27 28 /* 29 * !!! 30 * In historic vi, a sentence was delimited by a '.', '?' or '!' character 31 * followed by TWO spaces or a newline. One or more empty lines was also 32 * treated as a separate sentence. The Berkeley documentation for historical 33 * vi states that any number of ')', ']', '"' and '\'' characters can be 34 * between the delimiter character and the spaces or end of line, however, 35 * the historical implementation did not handle additional '"' characters. 36 * We follow the documentation here, not the implementation. 37 * 38 * Once again, historical vi didn't do sentence movements associated with 39 * counts consistently, mostly in the presence of lines containing only 40 * white-space characters. 41 * 42 * This implementation also permits a single tab to delimit sentences, and 43 * treats lines containing only white-space characters as empty lines. 44 * Finally, tabs are eaten (along with spaces) when skipping to the start 45 * of the text following a "sentence". 46 */ 47 48 /* 49 * v_sentencef -- [count]) 50 * Move forward count sentences. 51 * 52 * PUBLIC: int v_sentencef __P((SCR *, VICMD *)); 53 */ 54 int 55 v_sentencef(sp, vp) 56 SCR *sp; 57 VICMD *vp; 58 { 59 enum { BLANK, NONE, PERIOD } state; 60 VCS cs; 61 size_t len; 62 u_long cnt; 63 64 cs.cs_lno = vp->m_start.lno; 65 cs.cs_cno = vp->m_start.cno; 66 if (cs_init(sp, &cs)) 67 return (1); 68 69 cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; 70 71 /* 72 * !!! 73 * If in white-space, the next start of sentence counts as one. 74 * This may not handle " . " correctly, but it's real unclear 75 * what correctly means in that case. 76 */ 77 if (cs.cs_flags == CS_EMP || cs.cs_flags == 0 && isblank(cs.cs_ch)) { 78 if (cs_fblank(sp, &cs)) 79 return (1); 80 if (--cnt == 0) { 81 if (vp->m_start.lno != cs.cs_lno || 82 vp->m_start.cno != cs.cs_cno) 83 goto okret; 84 return (1); 85 } 86 } 87 88 for (state = NONE;;) { 89 if (cs_next(sp, &cs)) 90 return (1); 91 if (cs.cs_flags == CS_EOF) 92 break; 93 if (cs.cs_flags == CS_EOL) { 94 if ((state == PERIOD || state == BLANK) && --cnt == 0) { 95 if (cs_next(sp, &cs)) 96 return (1); 97 if (cs.cs_flags == 0 && 98 isblank(cs.cs_ch) && cs_fblank(sp, &cs)) 99 return (1); 100 goto okret; 101 } 102 state = NONE; 103 continue; 104 } 105 if (cs.cs_flags == CS_EMP) { /* An EMP is two sentences. */ 106 if (--cnt == 0) 107 goto okret; 108 if (cs_fblank(sp, &cs)) 109 return (1); 110 if (--cnt == 0) 111 goto okret; 112 state = NONE; 113 continue; 114 } 115 switch (cs.cs_ch) { 116 case '.': 117 case '?': 118 case '!': 119 state = PERIOD; 120 break; 121 case ')': 122 case ']': 123 case '"': 124 case '\'': 125 if (state != PERIOD) 126 state = NONE; 127 break; 128 case '\t': 129 if (state == PERIOD) 130 state = BLANK; 131 /* FALLTHROUGH */ 132 case ' ': 133 if (state == PERIOD) { 134 state = BLANK; 135 break; 136 } 137 if (state == BLANK && --cnt == 0) { 138 if (cs_fblank(sp, &cs)) 139 return (1); 140 goto okret; 141 } 142 /* FALLTHROUGH */ 143 default: 144 state = NONE; 145 break; 146 } 147 } 148 149 /* EOF is a movement sink, but it's an error not to have moved. */ 150 if (vp->m_start.lno == cs.cs_lno && vp->m_start.cno == cs.cs_cno) { 151 v_eof(sp, NULL); 152 return (1); 153 } 154 155 okret: vp->m_stop.lno = cs.cs_lno; 156 vp->m_stop.cno = cs.cs_cno; 157 158 /* 159 * !!! 160 * Historic, uh, features, yeah, that's right, call 'em features. 161 * If the starting and ending cursor positions are at the first 162 * column in their lines, i.e. the movement is cutting entire lines, 163 * the buffer is in line mode, and the ending position is the last 164 * character of the previous line. Note check to make sure that 165 * it's not within a single line. 166 * 167 * Non-motion commands move to the end of the range. Delete and 168 * yank stay at the start. Ignore others. Adjust the end of the 169 * range for motion commands. 170 */ 171 if (ISMOTION(vp)) { 172 if (vp->m_start.cno == 0 && 173 (cs.cs_flags != 0 || vp->m_stop.cno == 0)) { 174 if (vp->m_start.lno < vp->m_stop.lno) { 175 if (db_get(sp, 176 --vp->m_stop.lno, DBG_FATAL, NULL, &len)) 177 return (1); 178 vp->m_stop.cno = len ? len - 1 : 0; 179 } 180 F_SET(vp, VM_LMODE); 181 } else 182 --vp->m_stop.cno; 183 vp->m_final = vp->m_start; 184 } else 185 vp->m_final = vp->m_stop; 186 return (0); 187 } 188 189 /* 190 * v_sentenceb -- [count]( 191 * Move backward count sentences. 192 * 193 * PUBLIC: int v_sentenceb __P((SCR *, VICMD *)); 194 */ 195 int 196 v_sentenceb(sp, vp) 197 SCR *sp; 198 VICMD *vp; 199 { 200 VCS cs; 201 recno_t slno; 202 size_t len, scno; 203 u_long cnt; 204 int last; 205 206 /* 207 * !!! 208 * Historic vi permitted the user to hit SOF repeatedly. 209 */ 210 if (vp->m_start.lno == 1 && vp->m_start.cno == 0) 211 return (0); 212 213 cs.cs_lno = vp->m_start.lno; 214 cs.cs_cno = vp->m_start.cno; 215 if (cs_init(sp, &cs)) 216 return (1); 217 218 cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; 219 220 /* 221 * !!! 222 * In empty lines, skip to the previous non-white-space character. 223 * If in text, skip to the prevous white-space character. Believe 224 * it or not, in the paragraph: 225 * ab cd. 226 * AB CD. 227 * if the cursor is on the 'A' or 'B', ( moves to the 'a'. If it 228 * is on the ' ', 'C' or 'D', it moves to the 'A'. Yes, Virginia, 229 * Berkeley was once a major center of drug activity. 230 */ 231 if (cs.cs_flags == CS_EMP) { 232 if (cs_bblank(sp, &cs)) 233 return (1); 234 for (;;) { 235 if (cs_prev(sp, &cs)) 236 return (1); 237 if (cs.cs_flags != CS_EOL) 238 break; 239 } 240 } else if (cs.cs_flags == 0 && !isblank(cs.cs_ch)) 241 for (;;) { 242 if (cs_prev(sp, &cs)) 243 return (1); 244 if (cs.cs_flags != 0 || isblank(cs.cs_ch)) 245 break; 246 } 247 248 for (last = 0;;) { 249 if (cs_prev(sp, &cs)) 250 return (1); 251 if (cs.cs_flags == CS_SOF) /* SOF is a movement sink. */ 252 break; 253 if (cs.cs_flags == CS_EOL) { 254 last = 1; 255 continue; 256 } 257 if (cs.cs_flags == CS_EMP) { 258 if (--cnt == 0) 259 goto ret; 260 if (cs_bblank(sp, &cs)) 261 return (1); 262 last = 0; 263 continue; 264 } 265 switch (cs.cs_ch) { 266 case '.': 267 case '?': 268 case '!': 269 if (!last || --cnt != 0) { 270 last = 0; 271 continue; 272 } 273 274 ret: slno = cs.cs_lno; 275 scno = cs.cs_cno; 276 277 /* 278 * Move to the start of the sentence, skipping blanks 279 * and special characters. 280 */ 281 do { 282 if (cs_next(sp, &cs)) 283 return (1); 284 } while (!cs.cs_flags && 285 (cs.cs_ch == ')' || cs.cs_ch == ']' || 286 cs.cs_ch == '"' || cs.cs_ch == '\'')); 287 if ((cs.cs_flags || isblank(cs.cs_ch)) && 288 cs_fblank(sp, &cs)) 289 return (1); 290 291 /* 292 * If it was ". xyz", with the cursor on the 'x', or 293 * "end. ", with the cursor in the spaces, or the 294 * beginning of a sentence preceded by an empty line, 295 * we can end up where we started. Fix it. 296 */ 297 if (vp->m_start.lno != cs.cs_lno || 298 vp->m_start.cno != cs.cs_cno) 299 goto okret; 300 301 /* 302 * Well, if an empty line preceded possible blanks 303 * and the sentence, it could be a real sentence. 304 */ 305 for (;;) { 306 if (cs_prev(sp, &cs)) 307 return (1); 308 if (cs.cs_flags == CS_EOL) 309 continue; 310 if (cs.cs_flags == 0 && isblank(cs.cs_ch)) 311 continue; 312 break; 313 } 314 if (cs.cs_flags == CS_EMP) 315 goto okret; 316 317 /* But it wasn't; try again. */ 318 ++cnt; 319 cs.cs_lno = slno; 320 cs.cs_cno = scno; 321 last = 0; 322 break; 323 case '\t': 324 last = 1; 325 break; 326 default: 327 last = 328 cs.cs_flags == CS_EOL || isblank(cs.cs_ch) || 329 cs.cs_ch == ')' || cs.cs_ch == ']' || 330 cs.cs_ch == '"' || cs.cs_ch == '\'' ? 1 : 0; 331 } 332 } 333 334 okret: vp->m_stop.lno = cs.cs_lno; 335 vp->m_stop.cno = cs.cs_cno; 336 337 /* 338 * !!! 339 * If the starting and stopping cursor positions are at the first 340 * columns in the line, i.e. the movement is cutting an entire line, 341 * the buffer is in line mode, and the starting position is the last 342 * character of the previous line. 343 * 344 * All commands move to the end of the range. Adjust the start of 345 * the range for motion commands. 346 */ 347 if (ISMOTION(vp)) 348 if (vp->m_start.cno == 0 && 349 (cs.cs_flags != 0 || vp->m_stop.cno == 0)) { 350 if (db_get(sp, 351 --vp->m_start.lno, DBG_FATAL, NULL, &len)) 352 return (1); 353 vp->m_start.cno = len ? len - 1 : 0; 354 F_SET(vp, VM_LMODE); 355 } else 356 --vp->m_start.cno; 357 vp->m_final = vp->m_stop; 358 return (0); 359 } 360