Commit | Line | Data |
---|---|---|
15637ed4 RG |
1 | /* move1.c */ |
2 | ||
3 | /* Author: | |
4 | * Steve Kirkendall | |
5 | * 14407 SW Teal Blvd. #C | |
6 | * Beaverton, OR 97005 | |
7 | * kirkenda@cs.pdx.edu | |
8 | */ | |
9 | ||
10 | ||
11 | /* This file contains most movement functions */ | |
12 | ||
13 | #include "config.h" | |
14 | #include "vi.h" | |
15 | #include "ctype.h" | |
16 | ||
17 | MARK m_updnto(m, cnt, cmd) | |
18 | MARK m; /* movement is relative to this mark */ | |
19 | long cnt; /* a numeric argument */ | |
08746e8b | 20 | int cmd; /* the command character */ |
15637ed4 RG |
21 | { |
22 | DEFAULT(cmd == 'G' ? nlines : 1L); | |
23 | ||
24 | /* move up or down 'cnt' lines */ | |
25 | switch (cmd) | |
26 | { | |
27 | case ctrl('P'): | |
28 | case '-': | |
29 | case 'k': | |
30 | m -= MARK_AT_LINE(cnt); | |
31 | break; | |
32 | ||
33 | case 'G': | |
34 | if (cnt < 1L || cnt > nlines) | |
35 | { | |
36 | msg("Only %ld lines", nlines); | |
37 | return MARK_UNSET; | |
38 | } | |
39 | m = MARK_AT_LINE(cnt); | |
40 | break; | |
41 | ||
42 | case '_': | |
43 | cnt--; | |
44 | /* fall through... */ | |
45 | ||
46 | default: | |
47 | m += MARK_AT_LINE(cnt); | |
48 | } | |
49 | ||
50 | /* if that left us screwed up, then fail */ | |
51 | if (m < MARK_FIRST || markline(m) > nlines) | |
52 | { | |
53 | return MARK_UNSET; | |
54 | } | |
55 | ||
56 | return m; | |
57 | } | |
58 | ||
59 | /*ARGSUSED*/ | |
60 | MARK m_right(m, cnt, key, prevkey) | |
61 | MARK m; /* movement is relative to this mark */ | |
62 | long cnt; /* a numeric argument */ | |
63 | int key; /* movement keystroke */ | |
64 | int prevkey;/* operator keystroke, or 0 if none */ | |
65 | { | |
66 | int idx; /* index of the new cursor position */ | |
67 | ||
68 | DEFAULT(1); | |
69 | ||
70 | /* If used with an operator, then move 1 less character, since the 'l' | |
71 | * command includes the character that it moves onto. | |
72 | */ | |
73 | if (prevkey != '\0') | |
74 | { | |
75 | cnt--; | |
76 | } | |
77 | ||
78 | /* move to right, if that's OK */ | |
79 | pfetch(markline(m)); | |
80 | idx = markidx(m) + cnt; | |
81 | if (idx < plen) | |
82 | { | |
83 | m += cnt; | |
84 | } | |
85 | else | |
86 | { | |
87 | return MARK_UNSET; | |
88 | } | |
89 | ||
90 | return m; | |
91 | } | |
92 | ||
93 | /*ARGSUSED*/ | |
94 | MARK m_left(m, cnt) | |
95 | MARK m; /* movement is relative to this mark */ | |
96 | long cnt; /* a numeric argument */ | |
97 | { | |
98 | DEFAULT(1); | |
99 | ||
100 | /* move to the left, if that's OK */ | |
101 | if (markidx(m) >= cnt) | |
102 | { | |
103 | m -= cnt; | |
104 | } | |
105 | else | |
106 | { | |
107 | return MARK_UNSET; | |
108 | } | |
109 | ||
110 | return m; | |
111 | } | |
112 | ||
113 | /*ARGSUSED*/ | |
114 | MARK m_tocol(m, cnt, cmd) | |
115 | MARK m; /* movement is relative to this mark */ | |
116 | long cnt; /* a numeric argument */ | |
117 | int cmd; /* either ctrl('X') or '|' */ | |
118 | { | |
119 | char *text; /* text of the line */ | |
120 | int col; /* column number */ | |
121 | int idx; /* index into the line */ | |
122 | ||
123 | ||
124 | /* if doing ^X, then adjust for sideways scrolling */ | |
125 | if (cmd == ctrl('X')) | |
126 | { | |
127 | DEFAULT(*o_columns & 0xff); | |
128 | cnt += leftcol; | |
129 | } | |
130 | else | |
131 | { | |
132 | DEFAULT(1); | |
133 | } | |
134 | ||
135 | /* internally, columns are numbered 0..COLS-1, not 1..COLS */ | |
136 | cnt--; | |
137 | ||
138 | /* if 0, that's easy */ | |
139 | if (cnt == 0) | |
140 | { | |
141 | m &= ~(BLKSIZE - 1); | |
142 | return m; | |
143 | } | |
144 | ||
145 | /* find that column within the line */ | |
146 | pfetch(markline(m)); | |
147 | text = ptext; | |
148 | for (col = idx = 0; col < cnt && *text; text++, idx++) | |
149 | { | |
150 | if (*text == '\t' && !*o_list) | |
151 | { | |
152 | col += *o_tabstop; | |
153 | col -= col % *o_tabstop; | |
154 | } | |
155 | else if (UCHAR(*text) < ' ' || *text == '\177') | |
156 | { | |
157 | col += 2; | |
158 | } | |
159 | #ifndef NO_CHARATTR | |
160 | else if (text[0] == '\\' && text[1] == 'f' && text[2] && *o_charattr) | |
161 | { | |
162 | text += 2; /* plus one more as part of for loop */ | |
163 | } | |
164 | #endif | |
165 | else | |
166 | { | |
167 | col++; | |
168 | } | |
169 | } | |
170 | if (!*text) | |
171 | { | |
172 | /* the desired column was past the end of the line, so | |
173 | * act like the user pressed "$" instead. | |
174 | */ | |
175 | return m | (BLKSIZE - 1); | |
176 | } | |
177 | else | |
178 | { | |
179 | m = (m & ~(BLKSIZE - 1)) + idx; | |
180 | } | |
181 | return m; | |
182 | } | |
183 | ||
184 | /*ARGSUSED*/ | |
185 | MARK m_front(m, cnt) | |
186 | MARK m; /* movement is relative to this mark */ | |
187 | long cnt; /* a numeric argument (ignored) */ | |
188 | { | |
189 | char *scan; | |
190 | ||
191 | /* move to the first non-whitespace character */ | |
192 | pfetch(markline(m)); | |
193 | scan = ptext; | |
194 | m &= ~(BLKSIZE - 1); | |
195 | while (*scan == ' ' || *scan == '\t') | |
196 | { | |
197 | scan++; | |
198 | m++; | |
199 | } | |
200 | ||
201 | return m; | |
202 | } | |
203 | ||
204 | /*ARGSUSED*/ | |
205 | MARK m_rear(m, cnt) | |
206 | MARK m; /* movement is relative to this mark */ | |
207 | long cnt; /* a numeric argument (ignored) */ | |
208 | { | |
209 | /* Try to move *EXTREMELY* far to the right. It is fervently hoped | |
210 | * that other code will convert this to a more reasonable MARK before | |
211 | * anything tries to actually use it. (See adjmove() in vi.c) | |
212 | */ | |
213 | return m | (BLKSIZE - 1); | |
214 | } | |
215 | ||
216 | #ifndef NO_SENTENCE | |
217 | static int isperiod(ptr) | |
218 | char *ptr; /* pointer to possible sentence-ender */ | |
219 | { | |
220 | /* if not '.', '?', or '!', then it isn't a sentence ender */ | |
221 | if (*ptr != '.' && *ptr != '?' && *ptr != '!') | |
222 | { | |
223 | return FALSE; | |
224 | } | |
225 | ||
226 | /* skip any intervening ')', ']', or '"' characters */ | |
227 | do | |
228 | { | |
229 | ptr++; | |
230 | } while (*ptr == ')' || *ptr == ']' || *ptr == '"'); | |
231 | ||
232 | /* do we have two spaces or EOL? */ | |
233 | if (!*ptr || ptr[0] == ' ' && ptr[1] == ' ') | |
234 | { | |
235 | return TRUE; | |
236 | } | |
237 | return FALSE; | |
238 | } | |
239 | ||
240 | /*ARGSUSED*/ | |
241 | MARK m_sentence(m, cnt, cmd) | |
242 | MARK m; /* movement is relative to this mark */ | |
243 | long cnt; /* a numeric argument */ | |
244 | int cmd; /* either '(' or ')' */ | |
245 | { | |
246 | REG char *text; | |
247 | REG long l; | |
08746e8b AM |
248 | #ifndef CRUNCH |
249 | /* figure out where the paragraph boundary is */ | |
250 | MARK pp = m_paragraph(m, 1L, cmd=='(' ? '{' : '}'); | |
251 | #endif | |
15637ed4 RG |
252 | |
253 | DEFAULT(1); | |
254 | ||
255 | /* If '(' command, then move back one word, so that if we hit '(' at | |
256 | * the start of a sentence we don't simply stop at the end of the | |
257 | * previous sentence and bounce back to the start of this one again. | |
258 | */ | |
259 | if (cmd == '(') | |
260 | { | |
261 | m = m_bword(m, 1L, 'b'); | |
262 | if (!m) | |
263 | { | |
264 | return m; | |
265 | } | |
266 | } | |
267 | ||
268 | /* get the current line */ | |
269 | l = markline(m); | |
270 | pfetch(l); | |
271 | text = ptext + markidx(m); | |
272 | ||
273 | /* for each requested sentence... */ | |
274 | while (cnt-- > 0) | |
275 | { | |
276 | /* search forward for one of [.?!] followed by spaces or EOL */ | |
277 | do | |
278 | { | |
279 | if (cmd == ')') | |
280 | { | |
281 | /* move forward, wrap at end of line */ | |
282 | if (!text[0]) | |
283 | { | |
08746e8b | 284 | if (l == nlines) |
15637ed4 | 285 | { |
08746e8b | 286 | goto BreakBreak; |
15637ed4 RG |
287 | } |
288 | l++; | |
289 | pfetch(l); | |
290 | text = ptext; | |
291 | } | |
292 | else | |
293 | { | |
294 | text++; | |
295 | } | |
296 | } | |
297 | else | |
298 | { | |
299 | /* move backward, wrap at beginning of line */ | |
300 | if (text == ptext) | |
301 | { | |
302 | do | |
303 | { | |
08746e8b | 304 | if (l == 1L) |
15637ed4 | 305 | { |
08746e8b | 306 | goto BreakBreak; |
15637ed4 RG |
307 | } |
308 | l--; | |
309 | pfetch(l); | |
310 | } while (!*ptext); | |
311 | text = ptext + plen - 1; | |
312 | } | |
313 | else | |
314 | { | |
315 | text--; | |
316 | } | |
317 | } | |
318 | } while (!isperiod(text)); | |
319 | } | |
08746e8b | 320 | BreakBreak: |
15637ed4 RG |
321 | |
322 | /* construct a mark for this location */ | |
323 | m = buildmark(text); | |
324 | ||
325 | /* move forward to the first word of the next sentence */ | |
326 | m = m_fword(m, 1L, 'w', '\0'); | |
08746e8b AM |
327 | if (m == MARK_UNSET) |
328 | { | |
329 | m = MARK_EOF; | |
330 | } | |
331 | ||
332 | #ifndef CRUNCH | |
333 | /* don't cross the paragraph boundary */ | |
334 | if (pp && ((cmd=='(') ? (m<pp) : (m>pp))) | |
335 | { | |
336 | m = pp; | |
337 | } | |
338 | #endif | |
15637ed4 RG |
339 | |
340 | return m; | |
341 | } | |
342 | #endif | |
343 | ||
344 | MARK m_paragraph(m, cnt, cmd) | |
345 | MARK m; /* movement is relative to this mark */ | |
346 | long cnt; /* a numeric argument */ | |
347 | int cmd; /* either '{' or '}' */ | |
348 | { | |
349 | char *text; /* text of the current line */ | |
350 | char *pscn; /* used to scan thru value of "paragraphs" option */ | |
351 | long l, ol; /* current line number, original line number */ | |
352 | int dir; /* -1 if we're moving up, or 1 if down */ | |
353 | char col0; /* character to expect in column 0 */ | |
08746e8b | 354 | long limit; /* line where searching must stop */ |
15637ed4 RG |
355 | #ifndef NO_SENTENCE |
356 | # define SENTENCE(x) (x) | |
357 | char *list; /* either o_sections or o_paragraph */ | |
358 | #else | |
359 | # define SENTENCE(x) | |
08746e8b AM |
360 | #endif |
361 | #ifndef CRUNCH | |
362 | MARK ss; | |
15637ed4 RG |
363 | #endif |
364 | ||
365 | DEFAULT(1); | |
366 | ||
367 | /* set the direction, based on the command */ | |
368 | switch (cmd) | |
369 | { | |
370 | case '{': | |
371 | dir = -1; | |
372 | col0 = '\0'; | |
373 | SENTENCE(list = o_paragraphs); | |
08746e8b AM |
374 | #ifndef CRUNCH |
375 | ss = m_paragraph(m, 1L, '<'); | |
376 | if (ss) | |
377 | limit = markline(ss); | |
378 | else | |
379 | #endif | |
380 | limit = 1L; | |
15637ed4 RG |
381 | break; |
382 | ||
383 | case '}': | |
384 | dir = 1; | |
385 | col0 = '\0'; | |
386 | SENTENCE(list = o_paragraphs); | |
08746e8b AM |
387 | #ifndef CRUNCH |
388 | ss = m_paragraph(m, 1L, '>'); | |
389 | if (ss) | |
390 | limit = markline(ss); | |
391 | else | |
392 | #endif | |
393 | limit = nlines; | |
15637ed4 RG |
394 | break; |
395 | ||
396 | case '[': | |
397 | if (getkey(0) != '[') | |
398 | { | |
399 | return MARK_UNSET; | |
400 | } | |
08746e8b AM |
401 | /* fall through... */ |
402 | case '<': | |
15637ed4 RG |
403 | dir = -1; |
404 | col0 = '{'; | |
405 | SENTENCE(list = o_sections); | |
08746e8b | 406 | limit = 1L; |
15637ed4 RG |
407 | break; |
408 | ||
409 | case ']': | |
410 | if (getkey(0) != ']') | |
411 | { | |
412 | return MARK_UNSET; | |
413 | } | |
08746e8b AM |
414 | /* fall through... */ |
415 | case '>': | |
15637ed4 RG |
416 | dir = 1; |
417 | col0 = '{'; | |
418 | SENTENCE(list = o_sections); | |
08746e8b | 419 | limit = nlines; |
15637ed4 RG |
420 | break; |
421 | } | |
422 | ol = l = markline(m); | |
423 | ||
424 | /* for each paragraph that we want to travel through... */ | |
08746e8b | 425 | while (l != limit && cnt-- > 0) |
15637ed4 RG |
426 | { |
427 | /* skip blank lines between paragraphs */ | |
08746e8b | 428 | while (l != limit && col0 == *(text = fetchline(l))) |
15637ed4 RG |
429 | { |
430 | l += dir; | |
431 | } | |
432 | ||
433 | /* skip non-blank lines that aren't paragraph separators | |
434 | */ | |
435 | do | |
436 | { | |
437 | #ifndef NO_SENTENCE | |
438 | if (*text == '.' && l != ol) | |
439 | { | |
440 | for (pscn = list; pscn[0] && pscn[1]; pscn += 2) | |
441 | { | |
442 | if (pscn[0] == text[1] && pscn[1] == text[2]) | |
443 | { | |
444 | pscn = (char *)0; | |
445 | goto BreakBreak; | |
446 | } | |
447 | } | |
448 | } | |
449 | #endif | |
450 | l += dir; | |
08746e8b | 451 | } while (l != limit && col0 != *(text = fetchline(l))); |
15637ed4 RG |
452 | BreakBreak: ; |
453 | } | |
454 | ||
08746e8b AM |
455 | m = MARK_AT_LINE(l); |
456 | #ifdef DEBUG2 | |
457 | debout("m_paragraph() returning %ld.%d\n", markline(m), markidx(m)); | |
458 | #endif | |
15637ed4 RG |
459 | return m; |
460 | } | |
461 | ||
462 | ||
463 | /*ARGSUSED*/ | |
464 | MARK m_match(m, cnt) | |
465 | MARK m; /* movement is relative to this mark */ | |
466 | long cnt; /* a numeric argument (normally 0) */ | |
467 | { | |
468 | long l; | |
469 | REG char *text; | |
470 | REG char match; | |
471 | REG char nest; | |
472 | REG int count; | |
473 | ||
474 | #ifndef NO_EXTENSIONS | |
475 | /* if we're given a number, then treat it as a percentage of the file */ | |
476 | if (cnt > 0) | |
477 | { | |
478 | /* make sure it is a reasonable number */ | |
479 | if (cnt > 100) | |
480 | { | |
481 | msg("can only be from 1%% to 100%%"); | |
482 | return MARK_UNSET; | |
483 | } | |
484 | ||
485 | /* return the appropriate line number */ | |
486 | l = (nlines - 1L) * cnt / 100L + 1L; | |
487 | return MARK_AT_LINE(l); | |
488 | } | |
489 | #endif /* undef NO_EXTENSIONS */ | |
490 | ||
491 | /* get the current line */ | |
492 | l = markline(m); | |
493 | pfetch(l); | |
494 | text = ptext + markidx(m); | |
495 | ||
496 | /* search forward within line for one of "[](){}" */ | |
497 | for (match = '\0'; !match && *text; text++) | |
498 | { | |
499 | /* tricky way to recognize 'em in ASCII */ | |
500 | nest = *text; | |
501 | if ((nest & 0xdf) == ']' || (nest & 0xdf) == '[') | |
502 | { | |
503 | match = nest ^ ('[' ^ ']'); | |
504 | } | |
505 | else if ((nest & 0xfe) == '(') | |
506 | { | |
507 | match = nest ^ ('(' ^ ')'); | |
508 | } | |
509 | else | |
510 | { | |
511 | match = 0; | |
512 | } | |
513 | } | |
514 | if (!match) | |
515 | { | |
516 | return MARK_UNSET; | |
517 | } | |
518 | text--; | |
519 | ||
520 | /* search forward or backward for match */ | |
521 | if (match == '(' || match == '[' || match == '{') | |
522 | { | |
523 | /* search backward */ | |
524 | for (count = 1; count > 0; ) | |
525 | { | |
526 | /* wrap at beginning of line */ | |
527 | if (text == ptext) | |
528 | { | |
529 | do | |
530 | { | |
531 | if (l <= 1L) | |
532 | { | |
533 | return MARK_UNSET; | |
534 | } | |
535 | l--; | |
536 | pfetch(l); | |
537 | } while (!*ptext); | |
538 | text = ptext + plen - 1; | |
539 | } | |
540 | else | |
541 | { | |
542 | text--; | |
543 | } | |
544 | ||
545 | /* check the char */ | |
546 | if (*text == match) | |
547 | count--; | |
548 | else if (*text == nest) | |
549 | count++; | |
550 | } | |
551 | } | |
552 | else | |
553 | { | |
554 | /* search forward */ | |
555 | for (count = 1; count > 0; ) | |
556 | { | |
557 | /* wrap at end of line */ | |
558 | if (!*text) | |
559 | { | |
560 | if (l >= nlines) | |
561 | { | |
562 | return MARK_UNSET; | |
563 | } | |
564 | l++; | |
565 | pfetch(l); | |
566 | text = ptext; | |
567 | } | |
568 | else | |
569 | { | |
570 | text++; | |
571 | } | |
572 | ||
573 | /* check the char */ | |
574 | if (*text == match) | |
575 | count--; | |
576 | else if (*text == nest) | |
577 | count++; | |
578 | } | |
579 | } | |
580 | ||
581 | /* construct a mark for this place */ | |
582 | m = buildmark(text); | |
583 | return m; | |
584 | } | |
585 | ||
586 | /*ARGSUSED*/ | |
587 | MARK m_tomark(m, cnt, key) | |
588 | MARK m; /* movement is relative to this mark */ | |
589 | long cnt; /* (ignored) */ | |
590 | int key; /* keystroke - the mark to move to */ | |
591 | { | |
592 | /* mark '' is a special case */ | |
593 | if (key == '\'' || key == '`') | |
594 | { | |
595 | if (mark[26] == MARK_UNSET) | |
596 | { | |
597 | return MARK_FIRST; | |
598 | } | |
599 | else | |
600 | { | |
601 | return mark[26]; | |
602 | } | |
603 | } | |
604 | ||
605 | /* if not a valid mark number, don't move */ | |
606 | if (key < 'a' || key > 'z') | |
607 | { | |
608 | return MARK_UNSET; | |
609 | } | |
610 | ||
611 | /* return the selected mark -- may be MARK_UNSET */ | |
612 | if (!mark[key - 'a']) | |
613 | { | |
614 | msg("mark '%c is unset", key); | |
615 | } | |
616 | return mark[key - 'a']; | |
617 | } | |
618 |