Skip to content
Newer
Older
100644 769 lines (696 sloc) 18.3 KB
cf92e3b @mbert Import Elvis 2.0 (written by Steve Kirkendall)
authored Dec 10, 2011
1 /* dmnormal.c */
2 /* Copyright 1995 by Steve Kirkendall */
3
4 char id_dmnormal[] = "$Id: dmnormal.c,v 2.29 1996/09/21 01:21:36 steve Exp $";
5
6 #include "elvis.h"
7
8 #if USE_PROTOTYPES
9 static DMINFO *init(WINDOW win);
10 static void term(DMINFO *info);
11 static MARK move(WINDOW w, MARK from, long linedelta, long column, BOOLEAN cmd);
12 static MARK wordmove(MARK cursor, long count, BOOLEAN backward, BOOLEAN whitespace);
13 static long mark2col(WINDOW w, MARK mark, BOOLEAN cmd);
14 static MARK setup(MARK top, long cursor, MARK bottom, DMINFO *info);
15 static MARK image(WINDOW w, MARK line, DMINFO *info, void (*draw)(CHAR *p, long qty, _char_ font, long offset));
16 static void indent(WINDOW w, MARK line, long linedelta);
17 static CHAR *tagatcursor(WINDOW win, MARK cursor);
18 static MARK tagload(CHAR *tagname, MARK from);
19 #endif
20
21 /* start the mode, and allocate modeinfo */
22 static DMINFO *init(win)
23 WINDOW win;
24 {
25 return NULL;
26 }
27
28 /* end the mode, and free the modeinfo */
29 static void term(info)
30 DMINFO *info; /* window-specific info about mode */
31 {
32 }
33
34 /* Move vertically, and to a given column (or as close to column as possible) */
35 static MARK move(w, from, linedelta, column, cmd)
36 WINDOW w; /* window where buffer is shown */
37 MARK from; /* old position */
38 long linedelta; /* change in line number */
39 long column; /* desired column */
40 BOOLEAN cmd; /* if True, we're in command mode; else input mode */
41 {
42 static MARKBUF tmp;
43 long col, lnum;
44 long offset;
45 CHAR *cp;
46
47 assert(w != NULL || column == 0);
48
49 /* move forward/back to the start of the line + linedelta */
50 lnum = markline(from) + linedelta;
51 if (lnum < 1)
52 lnum = 1;
53 else if (lnum > o_buflines(markbuffer(from)))
54 lnum = o_buflines(markbuffer(from));
55 offset = lowline(bufbufinfo(markbuffer(from)), lnum);
56
57 /* now move to the left far enough to find the desired column */
58 (void)scanalloc(&cp, marktmp(tmp, markbuffer(from), offset));
59 for (col = 0; w && cp && *cp != '\n' && col <= column; offset++, scannext(&cp))
60 {
61 /* add the width of this character */
62 if (*cp == '\t' && (!o_list(w) || w->state->acton))
63 {
64 col = col + o_tabstop(markbuffer(w->cursor)) - (col % o_tabstop(markbuffer(w->cursor)));
65 }
66 else if (*cp < ' ' || *cp == 127)
67 {
68 col += 2;
69 }
70 else
71 {
72 col++;
73 }
74 }
75
76 /* The above loop normally exits when we've PASSED the desired column,
77 * so we normally want to back up one character. Two important
78 * exceptions: In input mode, if we break out of that loop because we
79 * hit '\n' then we want to leave the cursor on the '\n' character.
80 * If we hit the end of the buffer, then we want to leave the cursor
81 * on the last character of the buffer but we also need to be careful
82 * about empty buffers.
83 */
84 if (col > 0 && !(!cmd && (!cp || *cp == '\n') && col <= column))
85 {
86 offset--;
87 }
88
89 /* return the mark */
90 scanfree(&cp);
91 return marktmp(tmp, markbuffer(from), offset);
92 }
93
94
95 /* Convert a mark to a column number */
96 static long mark2col(w, mark, cmd)
97 WINDOW w; /* window where buffer is shown */
98 MARK mark; /* mark to be converted */
99 BOOLEAN cmd; /* if True, we're in command mode; else input mode */
100 {
101 long col;
102 CHAR *cp;
103 MARK front;
104 long nchars;
105
106 /* if the buffer is empty, the column must be 0 */
107 if (o_bufchars(markbuffer(mark)) == 0)
108 {
109 return 0;
110 }
111
112 /* find the front of the line */
113 front = move(w, mark, 0, 0, cmd);
114 nchars = markoffset(mark) - markoffset(front);
115
116 /* in command mode, we leave the cursor on the last cell of any
117 * wide characters such as tabs. To accomplish this, we'll find
118 * the column of the following character, and then subtract 1.
119 */
120 if (cmd)
121 {
122 nchars++;
123 }
124
125 /* count character widths until we find the requested mark */
126 for (scanalloc(&cp, front), col = 0; cp && nchars > 0; nchars--, scannext(&cp))
127 {
128 if (*cp == '\t' && (!o_list(w) || w->state->acton))
129 {
130 col = col + o_tabstop(markbuffer(w->cursor)) - (col % o_tabstop(markbuffer(w->cursor)));
131 }
132 else if (*cp == '\n')
133 {
134 #if 1
135 col++;
136 #else
137 /* no change to col */
138 #endif
139 }
140 else if (*cp < ' ' || *cp == 127)
141 {
142 col += 2;
143 }
144 else
145 {
146 col++;
147 }
148 }
149 scanfree(&cp);
150
151 /* the other half of the "cmd" hack */
152 if (cmd && col > 0)
153 {
154 col--;
155 }
156
157 return col;
158 }
159
160
161 /* This function implements most of the logic for the visual <b>, <e>, and
162 * <w> commands. If it succedes, it adjusts the starting mark and returns
163 * it; if it fails, it returns NULL and leaves the starting mark unchanged.
164 */
165 static MARK wordmove(cursor, count, backward, whitespace)
166 MARK cursor; /* starting position */
167 long count; /* number of words to move by */
168 BOOLEAN backward; /* if True, move backward; else forward */
169 BOOLEAN whitespace; /* if True, trailing whitespace is included */
170 {
171 BOOLEAN inword, inpunct;
172 CHAR *cp;
173 long offset;
174 long end;
175
176 /* start the scan */
177 scanalloc(&cp, cursor);
178 offset = markoffset(cursor);
179 assert(cp != NULL);
180 end = o_bufchars(markbuffer(cursor));
181
182 /* figure out if we're in the middle of a word */
183 if (backward || !whitespace || isspace(*cp))
184 {
185 inword = inpunct = False;
186 }
187 else if (isalnum(*cp) || *cp == '_')
188 {
189 inword = True;
190 inpunct = False;
191 }
192 else
193 {
194 inword = False;
195 inpunct = True;
196 }
197
198 /* continue... */
199 if (backward)
200 {
201 /* move backward until we hit the top of the buffer, or
202 * the start of the desired word.
203 */
204 while (count > 0 && offset > 0)
205 {
206 scanprev(&cp);
207 assert(cp != NULL);
208 if (isspace(*cp))
209 {
210 if (inword || inpunct)
211 {
212 count--;
213 }
214 inpunct = inword = False;
215 }
216 else if (isalnum(*cp) || *cp == '_')
217 {
218 if (inpunct)
219 {
220 count--;
221 }
222 inword = True;
223 inpunct = False;
224 }
225 else
226 {
227 if (inword)
228 {
229 count--;
230 }
231 inword = False;
232 inpunct = True;
233 }
234 if (count > 0)
235 {
236 offset--;
237 }
238 }
239
240 /* if we hit offset==0 and were in a word, we found the start
241 * of the first word. Count it here.
242 */
243 if (offset == 0 && (inword || inpunct))
244 {
245 count--;
246 }
247 }
248 else
249 {
250 /* move forward until we hit the end of the buffer, or
251 * the start of the desired word.
252 */
253 while (count > 0 && offset < end - 1)
254 {
255 scannext(&cp);
256 assert(cp != NULL);
257 if (isspace(*cp))
258 {
259 if ((inword || inpunct) && !whitespace)
260 {
261 count--;
262 }
263 inword = inpunct = False;
264 if (count > 0)
265 {
266 offset++;
267 }
268 }
269 else if (isalnum(*cp) || *cp == '_')
270 {
271 if ((!inword && whitespace) || inpunct)
272 {
273 count--;
274 }
275 inword = True;
276 inpunct = False;
277 if (count > 0 || whitespace)
278 {
279 offset++;
280 }
281 }
282 else
283 {
284 if ((!inpunct && whitespace) || inword)
285 {
286 count--;
287 }
288 inword = False;
289 inpunct = True;
290 if (count > 0 || whitespace)
291 {
292 offset++;
293 }
294 }
295 }
296 }
297
298 /* cleanup */
299 scanfree(&cp);
300
301 /* If we were moving forward and had only one more word to go, then
302 * move the cursor to the last character in the buffer -- usually a
303 * newline character. Pretend the count was decremented to 0.
304 */
305 if (count == 1 && !backward && whitespace && end > 0)
306 {
307 offset = end - 1;
308 count = 0;
309 }
310
311 /* if the count didn't reach 0, we failed */
312 if (count > 0)
313 {
314 return NULL;
315 }
316
317 /* else set the cursor's offset */
318 assert(offset < end && offset >= 0);
319 marksetoffset(cursor, offset);
320 return cursor;
321 }
322
323 /* Choose a line to appear at the top of the screen, and return its mark.
324 * Also, initialize the info for the next line.
325 */
326 static MARK setup(top, cursor, bottom, info)
327 MARK top; /* where previous image started */
328 long cursor; /* offset of cursor position */
329 MARK bottom; /* where previous image ended */
330 DMINFO *info; /* window-specific info about mode */
331 {
332 static MARKBUF tmp;
333 long topoff;
334 long bottomoff;
335 long other;
336 long i;
337
338 /* if the cursor is still on the screen (or very near the bottom)
339 * then use the same top.
340 */
341 topoff = markoffset(move((WINDOW)0, top, 0, 0, True));
342 bottomoff = markoffset(move((WINDOW)0, bottom, o_nearscroll, 0, True));
343 if (cursor >= topoff && (cursor < bottomoff || bottomoff < markoffset(bottom) + 1))
344 {
345 return marktmp(tmp, markbuffer(top), topoff);
346 }
347
348 /* if the cursor is on the line before the top, then scroll back */
349 if (topoff > 0)
350 {
351 for (i = 1; i < o_nearscroll; i++)
352 {
353 other = markoffset(move((WINDOW)0, top, -i, 0, True));
354 if (cursor >= other && cursor < topoff)
355 {
356 return marktmp(tmp, markbuffer(top), other);
357 }
358 }
359 }
360
361 /* else try to center the line in the window */
362 other = cursor - (bottomoff - topoff) / 2;
363 if (other < 0)
364 {
365 other = 0;
366 }
367 other = markoffset(move((WINDOW)0, marktmp(tmp, markbuffer(top), other), 0, 0, True));
368 return marktmp(tmp, markbuffer(top), other);
369 }
370
371 static MARK image(w, line, info, draw)
372 WINDOW w; /* window where drawing will go */
373 MARK line; /* start of line to draw next */
374 DMINFO *info; /* window-specific info about mode */
375 void (*draw)P_((CHAR *p, long qty, _char_ font, long offset));
376 /* function for drawing a single character */
377 {
378 int col;
379 CHAR *cp;
380 CHAR tmpchar;
381 long offset;
382 static MARKBUF tmp;
383 long startoffset; /* offset of first contiguous normal char */
384 int qty; /* number of contiguous normal chars */
385 CHAR buf[100]; /* buffer, holds the contiguous normal chars */
386 int i;
387
388 /* initialize startoffset just to silence a compiler warning */
389 startoffset = 0;
390
391 /* for each character in the line... */
392 qty = 0;
393 for (col = 0, offset = markoffset(line), scanalloc(&cp, line); cp && *cp != '\n'; offset++, scannext(&cp))
394 {
395 /* some characters are handled specially */
396 if (*cp == '\f' && markoffset(w->cursor) == o_bufchars(markbuffer(w->cursor)))
397 {
398 /* when printing, a formfeed ends the line (and page) */
399 break;
400 }
401 else if (*cp == '\t' && (!o_list(w) || w->state->acton))
402 {
403 /* output any preceding normal characters */
404 if (qty > 0)
405 {
406 (*draw)(buf, qty, 'n', startoffset);
407 qty = 0;
408 }
409
410 /* display the tab character as a bunch of spaces */
411 i = o_tabstop(markbuffer(w->cursor));
412 i -= (col % i);
413 tmpchar = ' ';
414 (*draw)(&tmpchar, -i, 'n', offset);
415 col += i;
416 }
417 else if (*cp < ' ' || *cp == 127)
418 {
419 /* output any preceding normal characters */
420 if (qty > 0)
421 {
422 (*draw)(buf, qty, 'n', startoffset);
423 qty = 0;
424 }
425
426 /* control characters */
427 tmpchar = '^';
428 (*draw)(&tmpchar, 1, 'n', offset);
429 tmpchar = *cp ^ 0x40;
430 (*draw)(&tmpchar, 1, 'n', offset);
431 col += 2;
432 }
433 else
434 {
435 /* starting a new string of contiguous normal chars? */
436 if (qty == 0)
437 {
438 startoffset = offset;
439 }
440
441 /* add this char to the string */
442 buf[qty++] = *cp;
443 col++;
444
445 /* if buf[] is full, flush it now */
446 if (qty == QTY(buf))
447 {
448 (*draw)(buf, qty, 'n', startoffset);
449 qty = 0;
450 }
451 }
452 }
453
454 /* output any normal chars from the end of the line */
455 if (qty > 0)
456 {
457 (*draw)(buf, qty, 'n', startoffset);
458 qty = 0;
459 }
460
461 /* end the line */
462 if (o_list(w) && !w->state->acton && *cp == '\n')
463 {
464 (*draw)(toCHAR("$"), 1, 'n', offset);
465 }
466 (*draw)(cp ? cp : toCHAR("\n"), 1, 'n', offset);
467 if (cp)
468 {
469 offset++;
470 }
471 else
472 {
473 offset = o_bufchars(markbuffer(w->cursor));
474 }
475 scanfree(&cp);
476 return marktmp(tmp, markbuffer(w->cursor), offset);
477 }
478
479 /* This function implements autoindent. Given the MARK of a newly created
480 * line, insert a copy of the indentation from another line. The line whose
481 * indentation is to be copied is specified as a line delta. Usually, this
482 * will be -1 so the new line has the same indentation as a previous line.
483 * The <Shift-O> command uses a linedelta of 1 so the new line will have the
484 * same indentation as the following line.
485 */
486 static void indent(w, line, linedelta)
487 WINDOW w; /* windows whose options are used */
488 MARK line; /* new line to adjust */
489 long linedelta; /* -1 to copy from previous line, etc. */
490 {
491 MARKBUF from, to; /* bounds of whitespace in source line */
492 MARKBUF bline; /* copy of the "line" argument */
493 CHAR *cp; /* used for scanning whitespace */
494 BOOLEAN srcblank; /* is source indentation from a blank line? */
495
496 assert(o_autoindent(markbuffer(line)));
497 assert(markbuffer(w->cursor) == markbuffer(line));
498 /*assert(scanchar(w->cursor) == '\n');*/
499
500 /* find the start of the source line */
501 bline = *line;
502 from = *dispmove(w, linedelta, 0);
503 if (markoffset(&from) == markoffset(&bline))
504 {
505 /* can't find source line -- at edge of buffer, maybe? */
506 return;
507 }
508
509 /* find the end of the source line's whitespace */
510 for (scanalloc(&cp, &from); cp && (*cp == ' ' || *cp == '\t'); scannext(&cp))
511 {
512 }
513 if (!cp)
514 {
515 /* hit end of buffer without finding end of line -- do nothing */
516 scanfree(&cp);
517 return;
518 }
519 to = *scanmark(&cp);
520 srcblank = (BOOLEAN)(*cp == '\n');
521 scanfree(&cp);
522
523 if (markoffset(&from) != markoffset(&to))
524 {
525 /* copy the source whitespace into the new line */
526 bufpaste(&bline, &from, &to);
527
528 /* if the source line was blank, then delete its whitespace */
529 if (srcblank)
530 {
531 if (linedelta > 0L)
532 {
533 /* tweak from & to, due to bufpaste() */
534 markaddoffset(&to, markoffset(&to) - markoffset(&from));
535 marksetoffset(&to, markoffset(&from));
536 }
537 else
538 {
539 /* tweak bline for the following bufreplace() */
540 markaddoffset(&bline, markoffset(&from) - markoffset(&to));
541 }
542 bufreplace(&from, &to, NULL, 0L);
543 }
544
545 /* tweak the argument mark, to leave cursor after whitespace */
546 marksetoffset(line, markoffset(&bline) + markoffset(&to) - markoffset(&from));
547 bline = *line;
548 }
549
550 /* if the line had some other indentation before, then delete that */
551 for (scanalloc(&cp, line); cp && (*cp == ' ' || *cp == '\t'); scannext(&cp))
552 {
553 }
554 if (cp)
555 {
556 bufreplace(&bline, scanmark(&cp), NULL, 0);
557 }
558 scanfree(&cp);
559 }
560
561
562 /* Return a dynamically-allocated string containing the name of the tag at the
563 * cursor, or NULL if the cursor isn't on a tag.
564 */
565 static CHAR *tagatcursor(win, cursor)
566 WINDOW win;
567 MARK cursor;
568 {
569 MARKBUF curscopy; /* a copy of the cursor */
570 MARK word; /* mark for the front of the word */
571
572 /* find the ends of the word */
573 curscopy = *cursor;
574 word = wordatcursor(&curscopy);
575
576 /* if not on a word, then return NULL */
577 if (!word)
578 return NULL;
579
580 /* copy the word into RAM, and return it */
581 return bufmemory(word, &curscopy);
582 }
583
584
585 /* Lookup a tag name, load the file where that tag was defined, and return
586 * the MARK of its position within that buffer. If the tag can't be found
587 * or loaded for any reason, then issue an error message and return NULL.
588 */
589 static MARK tagload(tagname, from)
590 CHAR *tagname; /* name of tag to lookup */
591 MARK from; /* initial position of the cursor */
592 {
593 static MARKBUF retmark; /* the return value */
594 BUFFER buf; /* the buffer containing the tag */
595 CHAR tagline[1000]; /* a line from the tags file */
596 int bytes; /* number of bytes in I/O buffer */
597 char *pathname; /* full pathname of current "tags" file */
598 int len; /* significant length of tagname */
599 BOOLEAN fulllen; /* len is the full length of the tag */
600 BOOLEAN allnext; /* is the whole next line in the buffer? */
601 char *loadname; /* name of file to load */
602 EXINFO xinfb; /* dummy ex command, for parsing tag address */
603 BOOLEAN wasmagic; /* stores the normal value of o_magic */
604 CHAR *src, *dst;
605
606 /* find the significant length of tagname */
607 len = CHARlen(tagname);
608 fulllen = True;
609 if (len > o_taglength && o_taglength > 0)
610 {
611 len = o_taglength;
612 fulllen = False;
613 }
614
615 /* for each tags file named in the "tags" option... */
616 for (pathname = iopath(tochar8(o_tags), "tags", True);
617 pathname;
618 pathname = iopath(NULL, "tags", True))
619 {
620 /* open the tags file */
621 if (!ioopen(pathname, 'r', False, False, False))
622 {
623 return NULL;
624 }
625
626 /* Compare the tag of each line against the tagname */
627 bytes = ioread(tagline, QTY(tagline) - 1);
628 while (bytes > len
629 && (CHARncmp(tagname, tagline, (size_t)len)
630 || (fulllen && !isspace(tagline[len]))))
631 {
632 /* delete this line from tagline[] */
633 for (src = tagline; src < &tagline[bytes] && *src != '\n'; src++)
634 {
635 }
636 for (dst = tagline, src++, allnext = False; src < &tagline[bytes]; )
637 {
638 if (*src == '\n')
639 allnext = True;
640 *dst++ = *src++;
641 }
642 bytes = (int)(dst - tagline);
643
644 /* if the next line is incomplete, read some more text
645 * from the tags file.
646 */
647 if (!allnext || bytes <= len)
648 {
649 bytes += ioread(dst, (int)QTY(tagline) - bytes - 1);
650 }
651 }
652 (void)ioclose();
653
654 /* did we find the tag? */
655 if (bytes > len)
656 {
657 goto Found;
658 }
659 }
660 msg(MSG_ERROR, "[S]tag $1 not found", tagname);
661 return NULL;
662
663 Found:
664 /* skip past the tagname to find the start of the filename */
665 for (src = tagline; src < &tagline[bytes] && !isspace(*src); src++)
666 {
667 }
668 for ( ; src < &tagline[bytes] && isspace(*src); src++)
669 {
670 }
671
672 /* locate the end of the filename, and mark it with a NUL byte */
673 for (dst = src; dst < &tagline[bytes] && !isspace(*dst); dst++)
674 {
675 }
676 *dst++ = '\0';
677
678 /* if the filename isn't a full pathname, then assume it is in the
679 * same directory as the "tags" file.
680 */
681 if (!isalnum(*src))
682 {
683 loadname = tochar8(src);
684 }
685 else
686 {
687 loadname = dirpath(dirdir(pathname), tochar8(src));
688 }
689
690 /* find a buffer containing the file, or load the file into a buffer */
691 buf = buffind(toCHAR(loadname));
692 if (!buf)
693 {
694 if (dirperm(loadname) == DIR_NEW)
695 {
696 msg(MSG_ERROR, "[s]$1 doesn't exist", loadname);
697 return NULL;
698 }
699 buf = bufload(NULL, loadname, False);
700 if (!buf)
701 {
702 /* bufload() already gave error message */
703 return NULL;
704 }
705 }
706 if (o_bufchars(buf) == 0)
707 {
708 goto NotFound;
709 }
710
711 /* locate the tag address field */
712 for (src = dst; src < &tagline[bytes] && isspace(*src); src++)
713 {
714 }
715 for (dst = src; dst < &tagline[bytes] && *dst != '\n'; dst++)
716 {
717 }
718 *dst = '\0';
719
720 /* convert the tag address into a line number */
721 scanstring(&dst, src);
722 memset((char *)&xinfb, 0, sizeof xinfb);
723 (void)marktmp(xinfb.defaddr, buf, 0);
724 wasmagic = o_magic;
725 o_magic = False;
726 if (!exparseaddress(&dst, &xinfb))
727 {
728 scanfree(&dst);
729 o_magic = wasmagic;
730 goto NotFound;
731 }
732 scanfree(&dst);
733 o_magic = wasmagic;
734 (void)marktmp(retmark, buf, lowline(bufbufinfo(buf), xinfb.to));
735 exfree(&xinfb);
736
737 return &retmark;
738
739 NotFound:
740 msg(MSG_WARNING, "tag address out of date");
741 return marktmp(retmark, buf, 0L);
742 }
743
744
745 DISPMODE dmnormal =
746 {
747 "normal",
748 "Standard vi",
749 True, /* display generating can be optimized */
750 True, /* should use normal wordwrap */
751 0, /* no window options */
752 NULL, /* no descriptions of window options */
753 0, /* no global options */
754 NULL, /* no descriptions of global options */
755 NULL, /* no values of global options */
756 init,
757 term,
758 mark2col,
759 move,
760 wordmove,
761 setup,
762 image,
763 NULL, /* doesn't need a header */
764 indent,
765 tagatcursor,
766 tagload,
767 NULL /* no tagnext() function */
768 };
Something went wrong with that request. Please try again.