Allow a NULL pattern in global commands (i.e., g//) to reuse the pattern
[unix-history] / usr.bin / elvis / move1.c
CommitLineData
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
17MARK 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*/
60MARK 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*/
94MARK 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*/
114MARK 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*/
185MARK 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*/
205MARK 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
217static 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*/
241MARK 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 320BreakBreak:
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
344MARK 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
452BreakBreak: ;
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*/
464MARK 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*/
587MARK 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