Commit | Line | Data |
---|---|---|
15637ed4 RG |
1 | /* cmd1.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 some of the EX commands - mostly ones that deal with | |
12 | * files, options, etc. -- anything except text. | |
13 | */ | |
14 | ||
15 | #include "config.h" | |
16 | #include "ctype.h" | |
17 | #include "vi.h" | |
6e657cf2 AM |
18 | #ifdef REGEX |
19 | # include <regex.h> | |
20 | #else | |
21 | # include "regexp.h" | |
22 | #endif /* REGEX */ | |
15637ed4 | 23 | |
08746e8b AM |
24 | #ifndef NO_TAGSTACK |
25 | /* These describe the current state of the tag related commands */ | |
26 | #define MAXTAGS 15 | |
27 | ||
28 | struct Tag_item { | |
29 | MARK tag_mark; | |
30 | char *tag_file; | |
31 | }; | |
32 | ||
33 | static struct Tag_item tag_stack[MAXTAGS]; | |
34 | static int curr_tag = -1; | |
35 | #endif /* !NO_TAGSTACK */ | |
36 | ||
15637ed4 RG |
37 | #ifdef DEBUG |
38 | /* print the selected lines with info on the blocks */ | |
39 | /*ARGSUSED*/ | |
40 | void cmd_debug(frommark, tomark, cmd, bang, extra) | |
41 | MARK frommark; | |
42 | MARK tomark; | |
43 | CMD cmd; | |
44 | int bang; | |
45 | char *extra; | |
46 | { | |
47 | REG char *scan; | |
48 | REG long l; | |
49 | REG int i; | |
50 | int len; | |
51 | ||
52 | /* scan lnum[] to determine which block its in */ | |
53 | l = markline(frommark); | |
54 | for (i = 1; l > lnum[i]; i++) | |
55 | { | |
56 | } | |
57 | ||
58 | do | |
59 | { | |
60 | /* fetch text of the block containing that line */ | |
61 | scan = blkget(i)->c; | |
62 | ||
63 | /* calculate its length */ | |
64 | if (scan[BLKSIZE - 1]) | |
65 | { | |
66 | len = BLKSIZE; | |
67 | } | |
68 | else | |
69 | { | |
70 | len = strlen(scan); | |
71 | } | |
72 | ||
73 | /* print block stats */ | |
74 | msg("##### hdr[%d]=%d, lnum[%d-1]=%ld, lnum[%d]=%ld (%ld lines)", | |
75 | i, hdr.n[i], i, lnum[i-1], i, lnum[i], lnum[i] - lnum[i - 1]); | |
76 | msg("##### len=%d, buf=0x%lx, %sdirty", | |
77 | len, scan, ((int *)scan)[MAXBLKS + 1] ? "" : "not "); | |
78 | if (bang) | |
79 | { | |
80 | while (--len >= 0) | |
81 | { | |
82 | addch(*scan); | |
83 | scan++; | |
84 | } | |
85 | } | |
86 | exrefresh(); | |
87 | ||
88 | /* next block */ | |
89 | i++; | |
90 | } while (i < MAXBLKS && lnum[i] && lnum[i - 1] < markline(tomark)); | |
91 | } | |
92 | ||
93 | ||
94 | /* This function checks a lot of conditions to make sure they aren't screwy */ | |
95 | /*ARGSUSED*/ | |
96 | void cmd_validate(frommark, tomark, cmd, bang, extra) | |
97 | MARK frommark; | |
98 | MARK tomark; | |
99 | CMD cmd; | |
100 | int bang; | |
101 | char *extra; | |
102 | { | |
103 | char *scan; | |
104 | int i; | |
105 | int nlcnt; /* used to count newlines */ | |
106 | int len; /* counts non-NUL characters */ | |
107 | ||
108 | /* check lnum[0] */ | |
109 | if (lnum[0] != 0L) | |
110 | { | |
111 | msg("lnum[0] = %ld", lnum[0]); | |
112 | } | |
113 | ||
114 | /* check each block */ | |
115 | for (i = 1; lnum[i] <= nlines; i++) | |
116 | { | |
117 | scan = blkget(i)->c; | |
118 | if (scan[BLKSIZE - 1]) | |
119 | { | |
120 | msg("block %d has no NUL at the end", i); | |
121 | } | |
122 | else | |
123 | { | |
124 | for (nlcnt = len = 0; *scan; scan++, len++) | |
125 | { | |
126 | if (*scan == '\n') | |
127 | { | |
128 | nlcnt++; | |
129 | } | |
130 | } | |
131 | if (scan[-1] != '\n') | |
132 | { | |
133 | msg("block %d doesn't end with '\\n' (length %d)", i, len); | |
134 | } | |
135 | if (bang || nlcnt != lnum[i] - lnum[i - 1]) | |
136 | { | |
137 | msg("block %d (line %ld?) has %d lines, but should have %ld", | |
138 | i, lnum[i - 1] + 1L, nlcnt, lnum[i] - lnum[i - 1]); | |
139 | } | |
140 | } | |
141 | exrefresh(); | |
142 | } | |
143 | ||
144 | /* check lnum again */ | |
145 | if (lnum[i] != INFINITY) | |
146 | { | |
147 | msg("hdr.n[%d] = %d, but lnum[%d] = %ld", | |
148 | i, hdr.n[i], i, lnum[i]); | |
149 | } | |
150 | ||
151 | msg("# = \"%s\", %% = \"%s\"", prevorig, origname); | |
152 | msg("V_from=%ld.%d, cursor=%ld.%d", markline(V_from), markidx(V_from), markline(cursor), markidx(cursor)); | |
153 | } | |
154 | #endif /* DEBUG */ | |
155 | ||
156 | ||
157 | /*ARGSUSED*/ | |
158 | void cmd_mark(frommark, tomark, cmd, bang, extra) | |
159 | MARK frommark; | |
160 | MARK tomark; | |
161 | CMD cmd; | |
162 | int bang; | |
163 | char *extra; | |
164 | { | |
165 | /* validate the name of the mark */ | |
166 | if (*extra == '"') | |
167 | { | |
168 | extra++; | |
169 | } | |
170 | /* valid mark names are lowercase ascii characters */ | |
171 | if (!isascii(*extra) || !islower(*extra) || extra[1]) | |
172 | { | |
173 | msg("Invalid mark name"); | |
174 | return; | |
175 | } | |
176 | ||
177 | mark[*extra - 'a'] = tomark; | |
178 | } | |
179 | ||
180 | /*ARGSUSED*/ | |
181 | void cmd_write(frommark, tomark, cmd, bang, extra) | |
182 | MARK frommark; | |
183 | MARK tomark; | |
184 | CMD cmd; | |
185 | int bang; | |
186 | char *extra; | |
187 | { | |
188 | int fd; | |
189 | int append; /* boolean: write in "append" mode? */ | |
190 | REG long l; | |
191 | REG char *scan; | |
192 | REG int i; | |
193 | ||
194 | /* if writing to a filter, then let filter() handle it */ | |
195 | if (*extra == '!') | |
196 | { | |
197 | filter(frommark, tomark, extra + 1, FALSE); | |
198 | return; | |
199 | } | |
200 | ||
201 | /* if all lines are to be written, use tmpsave() */ | |
202 | if (frommark == MARK_FIRST && tomark == MARK_LAST && cmd == CMD_WRITE) | |
203 | { | |
204 | tmpsave(extra, bang); | |
205 | return; | |
206 | } | |
207 | ||
208 | /* see if we're going to do this in append mode or not */ | |
209 | append = FALSE; | |
210 | if (extra[0] == '>' && extra[1] == '>') | |
211 | { | |
212 | extra += 2; | |
213 | append = TRUE; | |
214 | } | |
215 | ||
216 | /* either the file must not exist, or we must have a ! or be appending */ | |
08746e8b | 217 | if (*extra && access(extra, 0) == 0 && !bang && !append) |
15637ed4 RG |
218 | { |
219 | msg("File already exists - Use :w! to overwrite"); | |
220 | return; | |
221 | } | |
222 | ||
223 | /* else do it line-by-line, like cmd_print() */ | |
224 | if (append) | |
225 | { | |
226 | #ifdef O_APPEND | |
227 | fd = open(extra, O_WRONLY|O_APPEND); | |
228 | #else | |
229 | fd = open(extra, O_WRONLY); | |
230 | if (fd >= 0) | |
231 | { | |
232 | lseek(fd, 0L, 2); | |
233 | } | |
234 | #endif | |
235 | } | |
236 | else | |
237 | { | |
238 | fd = -1; /* so we know the file isn't open yet */ | |
239 | } | |
240 | ||
241 | if (fd < 0) | |
242 | { | |
243 | fd = creat(extra, FILEPERMS); | |
244 | if (fd < 0) | |
245 | { | |
246 | msg("Can't write to \"%s\"", extra); | |
247 | return; | |
248 | } | |
249 | } | |
250 | for (l = markline(frommark); l <= markline(tomark); l++) | |
251 | { | |
252 | /* get the next line */ | |
253 | scan = fetchline(l); | |
254 | i = strlen(scan); | |
255 | scan[i++] = '\n'; | |
256 | ||
257 | /* print the line */ | |
258 | if (twrite(fd, scan, i) < i) | |
259 | { | |
260 | msg("Write failed"); | |
261 | break; | |
262 | } | |
263 | } | |
264 | rptlines = markline(tomark) - markline(frommark) + 1; | |
265 | rptlabel = "written"; | |
266 | close(fd); | |
267 | } | |
268 | ||
269 | ||
270 | /*ARGSUSED*/ | |
271 | void cmd_shell(frommark, tomark, cmd, bang, extra) | |
272 | MARK frommark, tomark; | |
273 | CMD cmd; | |
274 | int bang; | |
275 | char *extra; | |
276 | { | |
b86699c9 AM |
277 | #ifdef BSD |
278 | static char *prevextra = NULL; | |
279 | #else | |
15637ed4 | 280 | static char prevextra[80]; |
b86699c9 | 281 | #endif |
15637ed4 RG |
282 | |
283 | /* special case: ":sh" means ":!sh" */ | |
284 | if (cmd == CMD_SHELL) | |
285 | { | |
286 | extra = o_shell; | |
287 | frommark = tomark = 0L; | |
288 | } | |
289 | ||
290 | /* if extra is "!", substitute previous command */ | |
291 | if (*extra == '!') | |
292 | { | |
b86699c9 AM |
293 | #ifdef BSD |
294 | if (prevextra == NULL) | |
295 | #else | |
296 | if (*prevextra == '\0') | |
297 | #endif | |
15637ed4 RG |
298 | { |
299 | msg("No previous shell command to substitute for '!'"); | |
300 | return; | |
301 | } | |
b86699c9 AM |
302 | #ifdef BSD |
303 | else if ((prevextra = (char *) realloc(prevextra, | |
304 | strlen(prevextra) + strlen(extra))) != NULL) { | |
305 | strcat(prevextra, extra + 1); | |
306 | extra = prevextra; | |
b86699c9 AM |
307 | } |
308 | #else | |
15637ed4 | 309 | extra = prevextra; |
b86699c9 AM |
310 | #endif |
311 | ||
15637ed4 | 312 | } |
b86699c9 AM |
313 | else if (cmd == CMD_BANG && |
314 | #ifdef BSD | |
315 | (prevextra = (char *) realloc(prevextra, strlen(extra) + 1)) != NULL) | |
316 | #else | |
317 | strlen(extra) < sizeof(prevextra) - 1) | |
318 | #endif | |
15637ed4 RG |
319 | { |
320 | strcpy(prevextra, extra); | |
321 | } | |
322 | ||
323 | /* warn the user if the file hasn't been saved yet */ | |
324 | if (*o_warn && tstflag(file, MODIFIED)) | |
325 | { | |
326 | if (mode == MODE_VI) | |
327 | { | |
328 | mode = MODE_COLON; | |
329 | } | |
330 | msg("Warning: \"%s\" has been modified but not yet saved", origname); | |
331 | } | |
332 | ||
333 | /* if no lines were specified, just run the command */ | |
334 | suspend_curses(); | |
335 | if (frommark == 0L) | |
336 | { | |
337 | system(extra); | |
338 | } | |
339 | else /* pipe lines from the file through the command */ | |
340 | { | |
341 | filter(frommark, tomark, extra, TRUE); | |
342 | } | |
343 | ||
344 | /* resume curses quietly for MODE_EX, but noisily otherwise */ | |
345 | resume_curses(mode == MODE_EX); | |
346 | } | |
347 | ||
348 | ||
349 | /*ARGSUSED*/ | |
350 | void cmd_global(frommark, tomark, cmd, bang, extra) | |
351 | MARK frommark, tomark; | |
352 | CMD cmd; | |
353 | int bang; | |
354 | char *extra; /* rest of the command line */ | |
355 | { | |
356 | char *cmdptr; /* the command from the command line */ | |
357 | char cmdln[100]; /* copy of the command from the command line */ | |
358 | char *line; /* a line from the file */ | |
359 | long l; /* used as a counter to move through lines */ | |
360 | long lqty; /* quantity of lines to be scanned */ | |
361 | long nchanged; /* number of lines changed */ | |
6e657cf2 AM |
362 | #ifdef REGEX |
363 | regex_t *re, *optpat(); | |
364 | #else | |
15637ed4 | 365 | regexp *re; /* the compiled search expression */ |
6e657cf2 | 366 | #endif |
15637ed4 RG |
367 | |
368 | /* can't nest global commands */ | |
369 | if (doingglobal) | |
370 | { | |
371 | msg("Can't nest global commands."); | |
372 | rptlines = -1L; | |
373 | return; | |
374 | } | |
375 | ||
376 | /* ":g! ..." is the same as ":v ..." */ | |
377 | if (bang) | |
378 | { | |
379 | cmd = CMD_VGLOBAL; | |
380 | } | |
381 | ||
382 | /* make sure we got a search pattern */ | |
08746e8b | 383 | if (*extra == ' ' || *extra == '\n') |
15637ed4 RG |
384 | { |
385 | msg("Usage: %c /regular expression/ command", cmd == CMD_GLOBAL ? 'g' : 'v'); | |
386 | return; | |
387 | } | |
388 | ||
389 | /* parse & compile the search pattern */ | |
390 | cmdptr = parseptrn(extra); | |
8d532f44 AM |
391 | #ifdef REGEX |
392 | re = optpat(extra + 1); | |
393 | #else | |
15637ed4 RG |
394 | if (!extra[1]) |
395 | { | |
396 | msg("Can't use empty regular expression with '%c' command", cmd == CMD_GLOBAL ? 'g' : 'v'); | |
397 | return; | |
398 | } | |
399 | re = regcomp(extra + 1); | |
6e657cf2 | 400 | #endif |
15637ed4 RG |
401 | if (!re) |
402 | { | |
403 | /* regcomp found & described an error */ | |
404 | return; | |
405 | } | |
15637ed4 RG |
406 | /* for each line in the range */ |
407 | doingglobal = TRUE; | |
408 | ChangeText | |
409 | { | |
410 | /* NOTE: we have to go through the lines in a forward order, | |
411 | * otherwise "g/re/p" would look funny. *BUT* for "g/re/d" | |
412 | * to work, simply adding 1 to the line# on each loop won't | |
413 | * work. The solution: count lines relative to the end of | |
414 | * the file. Think about it. | |
415 | */ | |
416 | for (l = nlines - markline(frommark), | |
417 | lqty = markline(tomark) - markline(frommark) + 1L, | |
418 | nchanged = 0L; | |
419 | lqty > 0 && nlines - l >= 0 && nchanged >= 0L; | |
420 | l--, lqty--) | |
421 | { | |
422 | /* fetch the line */ | |
423 | line = fetchline(nlines - l); | |
424 | ||
425 | /* if it contains the search pattern... */ | |
6e657cf2 AM |
426 | #ifdef REGEX |
427 | if ((!regexec(re, line, 0, NULL, 0)) == (cmd == CMD_GLOBAL)) | |
428 | #else | |
15637ed4 | 429 | if ((!regexec(re, line, 1)) == (cmd != CMD_GLOBAL)) |
6e657cf2 | 430 | #endif |
15637ed4 RG |
431 | { |
432 | /* move the cursor to that line */ | |
433 | cursor = MARK_AT_LINE(nlines - l); | |
434 | ||
435 | /* do the ex command (without mucking up | |
436 | * the original copy of the command line) | |
437 | */ | |
438 | strcpy(cmdln, cmdptr); | |
439 | rptlines = 0L; | |
440 | doexcmd(cmdln); | |
441 | nchanged += rptlines; | |
442 | } | |
443 | } | |
444 | } | |
445 | doingglobal = FALSE; | |
446 | ||
6e657cf2 | 447 | #ifndef REGEX |
15637ed4 | 448 | /* free the regexp */ |
08746e8b | 449 | _free_(re); |
6e657cf2 | 450 | #endif |
15637ed4 RG |
451 | |
452 | /* Reporting...*/ | |
453 | rptlines = nchanged; | |
454 | } | |
455 | ||
456 | ||
457 | /*ARGSUSED*/ | |
458 | void cmd_file(frommark, tomark, cmd, bang, extra) | |
459 | MARK frommark, tomark; | |
460 | CMD cmd; | |
461 | int bang; | |
462 | char *extra; | |
463 | { | |
464 | #ifndef CRUNCH | |
465 | /* if we're given a new filename, use it as this file's name */ | |
466 | if (extra && *extra) | |
467 | { | |
468 | strcpy(origname, extra); | |
469 | storename(origname); | |
470 | setflag(file, NOTEDITED); | |
471 | } | |
472 | #endif | |
473 | if (cmd == CMD_FILE) | |
474 | { | |
475 | #ifndef CRUNCH | |
08746e8b | 476 | msg("\"%s\" %s%s%s line %ld of %ld [%ld%%]", |
15637ed4 | 477 | #else |
08746e8b | 478 | msg("\"%s\" %s%s line %ld of %ld [%ld%%]", |
15637ed4 RG |
479 | #endif |
480 | *origname ? origname : "[NO FILE]", | |
481 | tstflag(file, MODIFIED) ? "[MODIFIED]" : "", | |
482 | #ifndef CRUNCH | |
483 | tstflag(file, NOTEDITED) ?"[NOT EDITED]":"", | |
484 | #endif | |
485 | tstflag(file, READONLY) ? "[READONLY]" : "", | |
15637ed4 | 486 | markline(frommark), |
08746e8b | 487 | nlines, |
15637ed4 RG |
488 | markline(frommark) * 100 / nlines); |
489 | } | |
490 | #ifndef CRUNCH | |
491 | else if (markline(frommark) != markline(tomark)) | |
492 | { | |
493 | msg("range \"%ld,%ld\" contains %ld lines", | |
494 | markline(frommark), | |
495 | markline(tomark), | |
496 | markline(tomark) - markline(frommark) + 1L); | |
497 | } | |
498 | #endif | |
499 | else | |
500 | { | |
501 | msg("%ld", markline(frommark)); | |
502 | } | |
503 | } | |
504 | ||
505 | ||
506 | /*ARGSUSED*/ | |
507 | void cmd_edit(frommark, tomark, cmd, bang, extra) | |
508 | MARK frommark, tomark; | |
509 | CMD cmd; | |
510 | int bang; | |
511 | char *extra; | |
512 | { | |
513 | long line = 1L; /* might be set to prevline */ | |
514 | #ifndef CRUNCH | |
515 | char *init = (char *)0; | |
516 | #endif | |
517 | ||
518 | ||
519 | /* if ":vi", then switch to visual mode, and if no file is named | |
520 | * then don't switch files. | |
521 | */ | |
522 | if (cmd == CMD_VISUAL) | |
523 | { | |
524 | mode = MODE_VI; | |
525 | msg(""); | |
526 | if (!*extra) | |
527 | { | |
528 | return; | |
529 | } | |
530 | } | |
531 | ||
532 | /* Editing previous file? Then start at previous line */ | |
533 | if (!strcmp(extra, prevorig)) | |
534 | { | |
535 | line = prevline; | |
536 | } | |
537 | ||
538 | #ifndef CRUNCH | |
539 | /* if we were given an explicit starting line, then start there */ | |
540 | if (*extra == '+') | |
541 | { | |
542 | for (init = ++extra; !isspace(*extra); extra++) | |
543 | { | |
544 | } | |
545 | while (isspace(*extra)) | |
546 | { | |
547 | *extra++ = '\0'; | |
548 | } | |
549 | if (!*init) | |
550 | { | |
551 | init = "$"; | |
552 | } | |
553 | if (!extra) | |
554 | { | |
555 | extra = origname; | |
556 | } | |
557 | } | |
558 | #endif /* not CRUNCH */ | |
559 | ||
560 | /* switch files */ | |
561 | if (tmpabort(bang)) | |
562 | { | |
563 | tmpstart(extra); | |
564 | if (line <= nlines && line >= 1L) | |
565 | { | |
566 | cursor = MARK_AT_LINE(line); | |
567 | } | |
568 | #ifndef CRUNCH | |
569 | if (init) | |
570 | { | |
571 | doexcmd(init); | |
572 | } | |
573 | #endif | |
574 | } | |
575 | else | |
576 | { | |
577 | msg("Use edit! to abort changes, or w to save changes"); | |
578 | ||
579 | /* so we can say ":e!#" next time... */ | |
580 | strcpy(prevorig, extra); | |
581 | prevline = 1L; | |
582 | } | |
583 | } | |
584 | ||
585 | /* This code is also used for rewind -- GB */ | |
586 | ||
587 | /*ARGSUSED*/ | |
588 | void cmd_next(frommark, tomark, cmd, bang, extra) | |
589 | MARK frommark, tomark; | |
590 | CMD cmd; | |
591 | int bang; | |
592 | char *extra; | |
593 | { | |
594 | int i, j; | |
595 | char *scan; | |
596 | ||
597 | /* if extra stuff given, use ":args" to define a new args list */ | |
598 | if (cmd == CMD_NEXT && extra && *extra) | |
599 | { | |
600 | cmd_args(frommark, tomark, cmd, bang, extra); | |
601 | } | |
602 | ||
603 | /* move to the next arg */ | |
604 | if (cmd == CMD_NEXT) | |
605 | { | |
606 | i = argno + 1; | |
607 | } | |
608 | else if (cmd == CMD_PREVIOUS) | |
609 | { | |
610 | i = argno - 1; | |
611 | } | |
612 | else /* cmd == CMD_REWIND */ | |
613 | { | |
614 | i = 0; | |
615 | } | |
616 | if (i < 0 || i >= nargs) | |
617 | { | |
618 | msg("No %sfiles to edit", cmd == CMD_REWIND ? "" : "more "); | |
619 | return; | |
620 | } | |
621 | ||
622 | /* find & isolate the name of the file to edit */ | |
623 | for (j = i, scan = args; j > 0; j--) | |
624 | { | |
625 | while(*scan++) | |
626 | { | |
627 | } | |
628 | } | |
629 | ||
630 | /* switch to the next file */ | |
631 | if (tmpabort(bang)) | |
632 | { | |
633 | tmpstart(scan); | |
634 | argno = i; | |
635 | } | |
636 | else | |
637 | { | |
638 | msg("Use :%s! to abort changes, or w to save changes", | |
639 | cmd == CMD_NEXT ? "next" : | |
640 | cmd == CMD_PREVIOUS ? "previous" : | |
641 | "rewind"); | |
642 | } | |
643 | } | |
644 | ||
08746e8b AM |
645 | /* also called for :wq -- always writes back in this case */ |
646 | /* also called for :q -- never writes back in that case */ | |
15637ed4 RG |
647 | /*ARGSUSED*/ |
648 | void cmd_xit(frommark, tomark, cmd, bang, extra) | |
649 | MARK frommark, tomark; | |
650 | CMD cmd; | |
651 | int bang; | |
652 | char *extra; | |
653 | { | |
654 | static long whenwarned; /* when the user was last warned of extra files */ | |
655 | int oldflag; | |
656 | ||
08746e8b AM |
657 | /* Unless the command is ":q", save the file if it has been modified */ |
658 | if (cmd != CMD_QUIT | |
659 | && (cmd == CMD_WQUIT || tstflag(file, MODIFIED)) | |
660 | && !tmpsave((char *)0, FALSE) && !bang) | |
661 | { | |
662 | msg("Could not save file -- use quit! to abort changes, or w filename"); | |
663 | return; | |
664 | } | |
665 | ||
666 | /* If there are more files to edit, then warn user */ | |
667 | if (argno >= 0 && argno + 1 < nargs /* more args */ | |
668 | && whenwarned != changes /* user not already warned */ | |
669 | && (!bang || cmd != CMD_QUIT)) /* command not ":q!" */ | |
15637ed4 RG |
670 | { |
671 | msg("More files to edit -- Use \":n\" to go to next file"); | |
672 | whenwarned = changes; | |
673 | return; | |
674 | } | |
675 | ||
08746e8b AM |
676 | /* Discard the temp file. Note that we should already have saved the |
677 | * the file, unless the command is ":q", so the only way that tmpabort | |
678 | * could fail would be if you did a ":q" on a modified file. | |
679 | */ | |
680 | oldflag = *o_autowrite; | |
681 | *o_autowrite = FALSE; | |
682 | if (tmpabort(bang)) | |
15637ed4 | 683 | { |
08746e8b | 684 | mode = MODE_QUIT; |
15637ed4 RG |
685 | } |
686 | else | |
687 | { | |
08746e8b | 688 | msg("Use q! to abort changes, or wq to save changes"); |
15637ed4 | 689 | } |
08746e8b | 690 | *o_autowrite = oldflag; |
15637ed4 RG |
691 | } |
692 | ||
693 | ||
694 | /*ARGSUSED*/ | |
695 | void cmd_args(frommark, tomark, cmd, bang, extra) | |
696 | MARK frommark, tomark; | |
697 | CMD cmd; | |
698 | int bang; | |
699 | char *extra; | |
700 | { | |
701 | char *scan; | |
702 | int col; | |
703 | int arg; | |
704 | int scrolled = FALSE; | |
705 | int width; | |
706 | ||
707 | /* if no extra names given, or just current name, then report the args | |
708 | * we have now. | |
709 | */ | |
710 | if (!extra || !*extra) | |
711 | { | |
712 | /* empty args list? */ | |
713 | if (nargs == 1 && !*args) | |
714 | { | |
715 | return; | |
716 | } | |
717 | ||
718 | /* list the arguments */ | |
719 | for (scan = args, col = arg = 0; | |
720 | arg < nargs; | |
721 | scan += width + 1, col += width, arg++) | |
722 | { | |
723 | width = strlen(scan); | |
724 | if (col + width >= COLS - 4) | |
725 | { | |
726 | addch('\n'); | |
727 | col = 0; | |
728 | scrolled = TRUE; | |
729 | } | |
730 | else if (col > 0) | |
731 | { | |
732 | addch(' '); | |
733 | col++; | |
734 | } | |
735 | if (arg == argno) | |
736 | { | |
737 | addch('['); | |
738 | addstr(scan); | |
739 | addch(']'); | |
740 | col += 2; | |
741 | } | |
742 | else | |
743 | { | |
744 | addstr(scan); | |
745 | } | |
746 | } | |
747 | ||
748 | /* write a trailing newline */ | |
749 | if ((mode == MODE_EX || mode == MODE_COLON || scrolled) && col) | |
750 | { | |
751 | addch('\n'); | |
752 | } | |
753 | exrefresh(); | |
754 | } | |
755 | else /* new args list given */ | |
756 | { | |
757 | for (scan = args, nargs = 1; *extra; ) | |
758 | { | |
759 | if (isspace(*extra)) | |
760 | { | |
761 | *scan++ = '\0'; | |
762 | while (isspace(*extra)) | |
763 | { | |
764 | extra++; | |
765 | } | |
766 | if (*extra) | |
767 | { | |
768 | nargs++; | |
769 | } | |
770 | } | |
771 | else | |
772 | { | |
773 | *scan++ = *extra++; | |
774 | } | |
775 | } | |
776 | *scan = '\0'; | |
777 | ||
778 | /* reset argno to before the first, so :next will go to first */ | |
779 | argno = -1; | |
780 | ||
781 | if (nargs != 1) | |
782 | { | |
783 | msg("%d files to edit", nargs); | |
784 | } | |
785 | } | |
786 | } | |
787 | ||
788 | ||
789 | /*ARGSUSED*/ | |
790 | void cmd_cd(frommark, tomark, cmd, bang, extra) | |
791 | MARK frommark, tomark; | |
792 | CMD cmd; | |
793 | int bang; | |
794 | char *extra; | |
795 | { | |
15637ed4 RG |
796 | #ifndef CRUNCH |
797 | /* if current file is modified, and no '!' was given, then error */ | |
798 | if (tstflag(file, MODIFIED) && !bang) | |
799 | { | |
800 | msg("File modified; use \"cd! %s\" to switch anyway", extra); | |
801 | } | |
802 | #endif | |
803 | ||
804 | /* default directory name is $HOME */ | |
805 | if (!*extra) | |
806 | { | |
08746e8b | 807 | extra = gethome((char *)0); |
15637ed4 RG |
808 | if (!extra) |
809 | { | |
810 | msg("environment variable $HOME not set"); | |
811 | return; | |
812 | } | |
813 | } | |
814 | ||
815 | /* go to the directory */ | |
816 | if (chdir(extra) < 0) | |
817 | { | |
818 | perror(extra); | |
819 | } | |
820 | } | |
821 | ||
822 | ||
823 | /*ARGSUSED*/ | |
824 | void cmd_map(frommark, tomark, cmd, bang, extra) | |
825 | MARK frommark, tomark; | |
826 | CMD cmd; | |
827 | int bang; | |
828 | char *extra; | |
829 | { | |
830 | char *mapto; | |
831 | char *build, *scan; | |
832 | #ifndef NO_FKEY | |
833 | static char *fnames[NFKEYS] = | |
834 | { | |
835 | "#10", "#1", "#2", "#3", "#4", | |
836 | "#5", "#6", "#7", "#8", "#9", | |
837 | # ifndef NO_SHIFT_FKEY | |
838 | "#10s", "#1s", "#2s", "#3s", "#4s", | |
839 | "#5s", "#6s", "#7s", "#8s", "#9s", | |
840 | # ifndef NO_CTRL_FKEY | |
841 | "#10c", "#1c", "#2c", "#3c", "#4c", | |
842 | "#5c", "#6c", "#7c", "#8c", "#9c", | |
843 | # ifndef NO_ALT_FKEY | |
844 | "#10a", "#1a", "#2a", "#3a", "#4a", | |
845 | "#5a", "#6a", "#7a", "#8a", "#9a", | |
846 | # endif | |
847 | # endif | |
848 | # endif | |
849 | }; | |
850 | int key; | |
851 | #endif | |
852 | ||
853 | /* "map" with no extra will dump the map table contents */ | |
854 | if (!*extra) | |
855 | { | |
856 | #ifndef NO_ABBR | |
857 | if (cmd == CMD_ABBR) | |
858 | { | |
859 | dumpkey(bang ? WHEN_EX|WHEN_VIINP|WHEN_VIREP : WHEN_VIINP|WHEN_VIREP, TRUE); | |
860 | } | |
861 | else | |
862 | #endif | |
863 | { | |
864 | dumpkey(bang ? WHEN_VIINP|WHEN_VIREP : WHEN_VICMD, FALSE); | |
865 | } | |
866 | } | |
867 | else | |
868 | { | |
869 | /* "extra" is key to map, followed by what it maps to */ | |
870 | ||
871 | /* handle quoting inside the "raw" string */ | |
872 | for (build = mapto = extra; | |
873 | *mapto && (*mapto != ' ' && *mapto != '\t'); | |
874 | *build++ = *mapto++) | |
875 | { | |
876 | if (*mapto == ctrl('V') && mapto[1]) | |
877 | { | |
878 | mapto++; | |
879 | } | |
880 | } | |
881 | ||
882 | /* skip whitespace, and mark the end of the "raw" string */ | |
883 | while ((*mapto == ' ' || *mapto == '\t')) | |
884 | { | |
885 | *mapto++ = '\0'; | |
886 | } | |
887 | *build = '\0'; | |
888 | ||
889 | /* strip ^Vs from the "cooked" string */ | |
890 | for (scan = build = mapto; *scan; *build++ = *scan++) | |
891 | { | |
892 | if (*scan == ctrl('V') && scan[1]) | |
893 | { | |
894 | scan++; | |
895 | } | |
896 | } | |
897 | *build = '\0'; | |
898 | ||
899 | #ifndef NO_FKEY | |
900 | /* if the mapped string is '#' and a number, then assume | |
901 | * the user wanted that function key | |
902 | */ | |
903 | if (extra[0] == '#' && isdigit(extra[1])) | |
904 | { | |
905 | key = atoi(extra + 1) % 10; | |
906 | # ifndef NO_SHIFT_FKEY | |
907 | build = extra + strlen(extra) - 1; | |
908 | if (*build == 's') | |
909 | key += 10; | |
910 | # ifndef NO_CTRL_FKEY | |
911 | else if (*build == 'c') | |
912 | key += 20; | |
913 | # ifndef NO_ALT_FKEY | |
914 | else if (*build == 'a') | |
915 | key += 30; | |
916 | # endif | |
917 | # endif | |
918 | # endif | |
919 | if (FKEY[key]) | |
920 | mapkey(FKEY[key], mapto, bang ? WHEN_VIINP|WHEN_VIREP : WHEN_VICMD, fnames[key]); | |
921 | else | |
922 | msg("This terminal has no %s key", fnames[key]); | |
923 | } | |
924 | else | |
925 | #endif | |
926 | #ifndef NO_ABBR | |
927 | if (cmd == CMD_ABBR || cmd == CMD_UNABBR) | |
928 | { | |
929 | mapkey(extra, mapto, bang ? WHEN_EX|WHEN_VIINP|WHEN_VIREP : WHEN_VIINP|WHEN_VIREP, "abbr"); | |
930 | } | |
931 | else | |
932 | #endif | |
933 | { | |
934 | mapkey(extra, mapto, bang ? WHEN_VIINP|WHEN_VIREP : WHEN_VICMD, (char *)0); | |
935 | } | |
936 | } | |
937 | } | |
938 | ||
939 | ||
940 | /*ARGSUSED*/ | |
941 | void cmd_set(frommark, tomark, cmd, bang, extra) | |
942 | MARK frommark, tomark; | |
943 | CMD cmd; | |
944 | int bang; | |
945 | char *extra; | |
946 | { | |
947 | if (!*extra) | |
948 | { | |
949 | dumpopts(FALSE);/* "FALSE" means "don't dump all" - only set */ | |
950 | } | |
951 | else if (!strcmp(extra, "all")) | |
952 | { | |
953 | dumpopts(TRUE); /* "TRUE" means "dump all" - even unset vars */ | |
954 | } | |
955 | else | |
956 | { | |
957 | setopts(extra); | |
958 | ||
959 | /* That option may have affected the appearence of text */ | |
960 | changes++; | |
961 | } | |
962 | } | |
963 | ||
964 | /*ARGSUSED*/ | |
965 | void cmd_tag(frommark, tomark, cmd, bang, extra) | |
966 | MARK frommark, tomark; | |
967 | CMD cmd; | |
968 | int bang; | |
969 | char *extra; | |
970 | { | |
971 | int fd; /* file descriptor used to read the file */ | |
972 | char *scan; /* used to scan through the tmpblk.c */ | |
973 | #ifdef INTERNAL_TAGS | |
974 | char *cmp; /* char of tag name we're comparing, or NULL */ | |
975 | char *end; /* marks the end of chars in tmpblk.c */ | |
08746e8b AM |
976 | char file[128]; /* name of file containing tag */ |
977 | int found; /* whether the tag has been found */ | |
978 | int file_exists; /* whether any tag file exists */ | |
979 | char *s, *t; | |
15637ed4 | 980 | #else |
08746e8b AM |
981 | # ifndef NO_TAGSTACK |
982 | char *s; | |
983 | # endif | |
15637ed4 RG |
984 | int i; |
985 | #endif | |
986 | #ifndef NO_MAGIC | |
987 | char wasmagic; /* preserves the original state of o_magic */ | |
988 | #endif | |
989 | static char prevtag[30]; | |
990 | ||
991 | /* if no tag is given, use the previous tag */ | |
992 | if (!extra || !*extra) | |
993 | { | |
994 | if (!*prevtag) | |
995 | { | |
996 | msg("No previous tag"); | |
997 | return; | |
998 | } | |
999 | extra = prevtag; | |
1000 | } | |
1001 | else | |
1002 | { | |
1003 | strncpy(prevtag, extra, sizeof prevtag); | |
1004 | prevtag[sizeof prevtag - 1] = '\0'; | |
1005 | } | |
1006 | ||
1007 | #ifndef INTERNAL_TAGS | |
1008 | /* use "ref" to look up the tag info for this tag */ | |
1009 | sprintf(tmpblk.c, "ref -t %s%s %s", (*origname ? "-f" : ""),origname, prevtag); | |
1010 | fd = rpipe(tmpblk.c, 0); | |
1011 | if (fd < 0) | |
1012 | { | |
1013 | msg("Can't run \"%s\"", tmpblk.c); | |
1014 | return; | |
1015 | } | |
1016 | ||
1017 | /* try to read the tag info */ | |
1018 | for (scan = tmpblk.c; | |
1019 | (i = tread(fd, scan, scan - tmpblk.c + BLKSIZE)) > 0; | |
1020 | scan += i) | |
1021 | { | |
1022 | } | |
1023 | *scan = '\0'; | |
1024 | ||
1025 | /* close the pipe. abort if error */ | |
08746e8b | 1026 | if (rpclose(fd) != 0) |
15637ed4 | 1027 | { |
08746e8b | 1028 | msg("Trouble running \"ref\" -- Can't do tag lookup"); |
15637ed4 RG |
1029 | return; |
1030 | } | |
08746e8b | 1031 | else if (scan < tmpblk.c + 3) |
15637ed4 | 1032 | { |
08746e8b | 1033 | msg("tag \"%s\" not found", extra); |
15637ed4 RG |
1034 | return; |
1035 | } | |
1036 | ||
08746e8b AM |
1037 | #else /* use internal code to look up the tag */ |
1038 | found = 0; | |
1039 | file_exists = 0; | |
1040 | s = o_tags; | |
1041 | while (!found && *s != 0) { | |
1042 | while (isspace(*s)) s++; | |
1043 | for(t = file; s && *s && !isspace(*s); s++) | |
1044 | *t++ = *s; | |
1045 | *t = '\0'; | |
1046 | ||
1047 | /* open the next tags file */ | |
1048 | fd = open(file, O_RDONLY); | |
1049 | if (fd < 0) | |
1050 | continue; | |
1051 | else | |
1052 | file_exists = 1; | |
15637ed4 | 1053 | |
08746e8b AM |
1054 | /* Hmmm... this would have been a lot easier with <stdio.h> */ |
1055 | ||
1056 | /* find the line with our tag in it */ | |
1057 | for(scan = end = tmpblk.c, cmp = extra; ; scan++) | |
15637ed4 | 1058 | { |
08746e8b | 1059 | /* read a block, if necessary */ |
15637ed4 RG |
1060 | if (scan >= end) |
1061 | { | |
08746e8b AM |
1062 | end = tmpblk.c + tread(fd, tmpblk.c, BLKSIZE); |
1063 | scan = tmpblk.c; | |
1064 | if (scan >= end) | |
1065 | { | |
1066 | close(fd); | |
1067 | break; | |
1068 | } | |
15637ed4 | 1069 | } |
08746e8b AM |
1070 | |
1071 | /* if we're comparing, compare... */ | |
1072 | if (cmp) | |
15637ed4 | 1073 | { |
08746e8b AM |
1074 | /* matched??? wow! */ |
1075 | if (!*cmp && *scan == '\t') | |
1076 | { | |
1077 | if ((s = strrchr(file, '/')) != 0 || | |
1078 | (s = strrchr(file, '\\')) != 0) | |
1079 | ++s; | |
1080 | else | |
1081 | s = file; | |
1082 | *s = '\0'; | |
1083 | found = 1; | |
1084 | break; | |
1085 | } | |
1086 | if (*cmp++ != *scan) | |
1087 | { | |
1088 | /* failed! skip to newline */ | |
1089 | cmp = (char *)0; | |
1090 | } | |
15637ed4 | 1091 | } |
08746e8b AM |
1092 | |
1093 | /* if we're skipping to newline, do it fast! */ | |
1094 | if (!cmp) | |
15637ed4 | 1095 | { |
08746e8b AM |
1096 | while (scan < end && *scan != '\n') |
1097 | { | |
1098 | scan++; | |
1099 | } | |
1100 | if (scan < end) | |
1101 | { | |
1102 | cmp = extra; | |
1103 | } | |
15637ed4 RG |
1104 | } |
1105 | } | |
08746e8b | 1106 | } |
15637ed4 | 1107 | |
08746e8b AM |
1108 | if (!file_exists) { |
1109 | msg("No tags file"); | |
1110 | return; | |
1111 | } | |
1112 | ||
1113 | if (!found) { | |
1114 | msg("tag \"%s\" not found", extra); | |
1115 | return; | |
15637ed4 RG |
1116 | } |
1117 | ||
1118 | /* found it! get the rest of the line into memory */ | |
1119 | for (cmp = tmpblk.c, scan++; scan < end && *scan != '\n'; ) | |
1120 | { | |
1121 | *cmp++ = *scan++; | |
1122 | } | |
1123 | if (scan == end) | |
1124 | { | |
1125 | tread(fd, cmp, BLKSIZE - (int)(cmp - tmpblk.c)); | |
1126 | } | |
1127 | else | |
1128 | *cmp = *scan; | |
1129 | ||
1130 | /* we can close the tags file now */ | |
1131 | close(fd); | |
1132 | #endif /* INTERNAL_TAGS */ | |
1133 | ||
1134 | /* extract the filename from the line, and edit the file */ | |
1135 | for (scan = tmpblk.c; *scan != '\t'; scan++) | |
1136 | { | |
1137 | } | |
1138 | *scan++ = '\0'; | |
1139 | if (strcmp(origname, tmpblk.c) != 0) | |
1140 | { | |
1141 | if (!tmpabort(bang)) | |
1142 | { | |
1143 | msg("Use :tag! to abort changes, or :w to save changes"); | |
1144 | return; | |
1145 | } | |
1146 | tmpstart(tmpblk.c); | |
08746e8b AM |
1147 | #ifdef NO_TAGSTACK |
1148 | } | |
1149 | #else /* tagstack enabled */ | |
1150 | s = prevorig; | |
1151 | } | |
1152 | else | |
1153 | s = origname; | |
1154 | ||
1155 | if (frommark != MARK_UNSET && *s && *o_tagstack) | |
1156 | { | |
1157 | curr_tag++; | |
1158 | if (curr_tag >= MAXTAGS) | |
1159 | { | |
1160 | /* discard the oldest tag position */ | |
1161 | free(tag_stack[0].tag_file); | |
1162 | for (curr_tag = 0; curr_tag < MAXTAGS - 1; curr_tag++) | |
1163 | { | |
1164 | tag_stack[curr_tag] = tag_stack[curr_tag + 1]; | |
1165 | } | |
1166 | /* at this point, curr_tag = MAXTAGS-1 */ | |
1167 | } | |
1168 | tag_stack[curr_tag].tag_file = (char *) malloc(strlen(s) + 1); | |
1169 | strcpy(tag_stack[curr_tag].tag_file, s); | |
1170 | tag_stack[curr_tag].tag_mark = frommark; | |
15637ed4 | 1171 | } |
08746e8b | 1172 | #endif |
15637ed4 RG |
1173 | |
1174 | /* move to the desired line (or to line 1 if that fails) */ | |
1175 | #ifndef NO_MAGIC | |
1176 | wasmagic = *o_magic; | |
1177 | *o_magic = FALSE; | |
1178 | #endif | |
1179 | cursor = MARK_FIRST; | |
1180 | linespec(scan, &cursor); | |
1181 | if (cursor == MARK_UNSET) | |
1182 | { | |
1183 | cursor = MARK_FIRST; | |
1184 | msg("Tag's address is out of date"); | |
1185 | } | |
1186 | #ifndef NO_MAGIC | |
1187 | *o_magic = wasmagic; | |
1188 | #endif | |
1189 | } | |
1190 | ||
1191 | ||
08746e8b AM |
1192 | #ifndef NO_TAGSTACK |
1193 | /*ARGSUSED*/ | |
1194 | void cmd_pop(frommark, tomark, cmd, bang, extra) | |
1195 | MARK frommark, tomark; | |
1196 | CMD cmd; | |
1197 | int bang; | |
1198 | char *extra; | |
1199 | { | |
1200 | char buf[8]; | |
1201 | ||
1202 | if (!*o_tagstack) | |
1203 | { | |
1204 | msg("Tagstack not enabled"); | |
1205 | return; | |
1206 | } | |
1207 | ||
1208 | if (curr_tag < 0) | |
1209 | msg("Tagstack empty"); | |
1210 | else | |
1211 | { | |
1212 | if (strcmp(origname, tag_stack[curr_tag].tag_file) != 0) | |
1213 | { | |
1214 | if (!tmpabort(bang)) | |
1215 | { | |
1216 | msg("Use :pop! to abort changes, or :w to save changes"); | |
1217 | return; | |
1218 | } | |
1219 | tmpstart(tag_stack[curr_tag].tag_file); | |
1220 | } | |
1221 | cursor = tag_stack[curr_tag].tag_mark; | |
1222 | if (cursor < MARK_FIRST || cursor > MARK_LAST + BLKSIZE) | |
1223 | { | |
1224 | cursor = MARK_FIRST; | |
1225 | } | |
1226 | free(tag_stack[curr_tag--].tag_file); | |
1227 | } | |
1228 | } | |
1229 | #endif | |
15637ed4 RG |
1230 | |
1231 | ||
1232 | ||
1233 | /* describe this version of the program */ | |
1234 | /*ARGSUSED*/ | |
1235 | void cmd_version(frommark, tomark, cmd, bang, extra) | |
1236 | MARK frommark; | |
1237 | MARK tomark; | |
1238 | CMD cmd; | |
1239 | int bang; | |
1240 | char *extra; | |
1241 | { | |
1242 | msg("%s", VERSION); | |
1243 | #ifdef CREDIT | |
1244 | msg("%s", CREDIT); | |
1245 | #endif | |
1246 | #ifdef CREDIT2 | |
1247 | msg("%s", CREDIT2); | |
1248 | #endif | |
1249 | #ifdef COMPILED_BY | |
1250 | msg("Compiled by %s", COMPILED_BY); | |
1251 | #endif | |
1252 | #ifdef COPYING | |
1253 | msg("%s", COPYING); | |
1254 | #endif | |
1255 | } | |
1256 | ||
1257 | ||
1258 | #ifndef NO_MKEXRC | |
1259 | /* make a .exrc file which describes the current configuration */ | |
1260 | /*ARGSUSED*/ | |
1261 | void cmd_mkexrc(frommark, tomark, cmd, bang, extra) | |
1262 | MARK frommark; | |
1263 | MARK tomark; | |
1264 | CMD cmd; | |
1265 | int bang; | |
1266 | char *extra; | |
1267 | { | |
1268 | int fd; | |
1269 | ||
1270 | /* the default name for the .exrc file EXRC */ | |
1271 | if (!*extra) | |
1272 | { | |
1273 | extra = EXRC; | |
1274 | } | |
1275 | ||
1276 | /* create the .exrc file */ | |
1277 | fd = creat(extra, FILEPERMS); | |
1278 | if (fd < 0) | |
1279 | { | |
1280 | msg("Couldn't create a new \"%s\" file", extra); | |
1281 | return; | |
1282 | } | |
1283 | ||
1284 | /* save stuff */ | |
1285 | saveopts(fd); | |
1286 | savemaps(fd, FALSE); | |
1287 | #ifndef NO_ABBR | |
1288 | savemaps(fd, TRUE); | |
1289 | #endif | |
1290 | #ifndef NO_DIGRAPH | |
1291 | savedigs(fd); | |
1292 | #endif | |
1293 | #ifndef NO_COLOR | |
1294 | savecolor(fd); | |
1295 | #endif | |
1296 | ||
1297 | /* close the file */ | |
1298 | close(fd); | |
1299 | msg("Configuration saved"); | |
1300 | } | |
1301 | #endif | |
1302 | ||
1303 | #ifndef NO_DIGRAPH | |
1304 | /*ARGSUSED*/ | |
1305 | void cmd_digraph(frommark, tomark, cmd, bang, extra) | |
1306 | MARK frommark; | |
1307 | MARK tomark; | |
1308 | CMD cmd; | |
1309 | int bang; | |
1310 | char *extra; | |
1311 | { | |
1312 | do_digraph(bang, extra); | |
1313 | } | |
1314 | #endif | |
1315 | ||
1316 | ||
1317 | #ifndef NO_ERRLIST | |
1318 | static char errfile[256]; /* the name of a file containing an error */ | |
1319 | static long errline; /* the line number for an error */ | |
1320 | static int errfd = -2; /* fd of the errlist file */ | |
1321 | ||
1322 | /* This static function tries to parse an error message. | |
1323 | * | |
1324 | * For most compilers, the first word is taken to be the name of the erroneous | |
1325 | * file, and the first number after that is taken to be the line number where | |
1326 | * the error was detected. The description of the error follows, possibly | |
1327 | * preceded by an "error ... :" or "warning ... :" label which is skipped. | |
1328 | * | |
1329 | * For Coherent, error messages look like "line#: filename: message". | |
1330 | * | |
1331 | * For non-error lines, or unparsable error lines, this function returns NULL. | |
1332 | * Normally, though, it alters errfile and errline, and returns a pointer to | |
1333 | * the description. | |
1334 | */ | |
1335 | static char *parse_errmsg(text) | |
1336 | REG char *text; | |
1337 | { | |
1338 | REG char *cpy; | |
1339 | long atol(); | |
1340 | # if COHERENT || TOS /* any Mark Williams compiler */ | |
1341 | /* Get the line number. If no line number, then ignore this line. */ | |
1342 | errline = atol(text); | |
1343 | if (errline == 0L) | |
1344 | return (char *)0; | |
1345 | ||
1346 | /* Skip to the start of the filename */ | |
1347 | while (*text && *text++ != ':') | |
1348 | { | |
1349 | } | |
1350 | if (!*text++) | |
1351 | return (char *)0; | |
1352 | ||
1353 | /* copy the filename to errfile */ | |
1354 | for (cpy = errfile; *text && (*cpy++ = *text++) != ':'; ) | |
1355 | { | |
1356 | } | |
1357 | if (!*text++) | |
1358 | return (char *)0; | |
1359 | cpy[-1] = '\0'; | |
1360 | ||
1361 | return text; | |
1362 | # else /* not a Mark Williams compiler */ | |
1363 | char *errmsg; | |
1364 | ||
1365 | /* the error message is the whole line, by default */ | |
1366 | errmsg = text; | |
1367 | ||
1368 | /* skip leading garbage */ | |
1369 | while (*text && !isalnum(*text)) | |
1370 | { | |
1371 | text++; | |
1372 | } | |
1373 | ||
1374 | /* copy over the filename */ | |
1375 | cpy = errfile; | |
1376 | while(isalnum(*text) || *text == '.') | |
1377 | { | |
1378 | *cpy++ = *text++; | |
1379 | } | |
1380 | *cpy = '\0'; | |
1381 | ||
1382 | /* ignore the name "Error" and filenames that contain a '/' */ | |
1383 | if (*text == '/' || !*errfile || !strcmp(errfile + 1, "rror") || access(errfile, 0) < 0) | |
1384 | { | |
1385 | return (char *)0; | |
1386 | } | |
1387 | ||
1388 | /* skip garbage between filename and line number */ | |
1389 | while (*text && !isdigit(*text)) | |
1390 | { | |
1391 | text++; | |
1392 | } | |
1393 | ||
1394 | /* if the number is part of a larger word, then ignore this line */ | |
08746e8b | 1395 | if (*text && (isalpha(text[-1]) || text[-1] == '_')) |
15637ed4 RG |
1396 | { |
1397 | return (char *)0; | |
1398 | } | |
1399 | ||
1400 | /* get the error line */ | |
1401 | errline = 0L; | |
1402 | while (isdigit(*text)) | |
1403 | { | |
1404 | errline *= 10; | |
1405 | errline += (*text - '0'); | |
1406 | text++; | |
1407 | } | |
1408 | ||
1409 | /* any line which lacks a filename or line number should be ignored */ | |
1410 | if (!errfile[0] || !errline) | |
1411 | { | |
1412 | return (char *)0; | |
1413 | } | |
1414 | ||
1415 | /* locate the beginning of the error description */ | |
1416 | while (*text && !isspace(*text)) | |
1417 | { | |
1418 | text++; | |
1419 | } | |
1420 | while (*text) | |
1421 | { | |
1422 | # ifndef CRUNCH | |
1423 | /* skip "error #:" and "warning #:" clauses */ | |
1424 | if (!strncmp(text + 1, "rror ", 5) | |
1425 | || !strncmp(text + 1, "arning ", 7) | |
1426 | || !strncmp(text + 1, "atal error", 10)) | |
1427 | { | |
1428 | do | |
1429 | { | |
1430 | text++; | |
1431 | } while (*text && *text != ':'); | |
1432 | continue; | |
1433 | } | |
1434 | # endif | |
1435 | ||
1436 | /* anything other than whitespace or a colon is important */ | |
1437 | if (!isspace(*text) && *text != ':') | |
1438 | { | |
1439 | errmsg = text; | |
1440 | break; | |
1441 | } | |
1442 | ||
1443 | /* else keep looking... */ | |
1444 | text++; | |
1445 | } | |
1446 | ||
1447 | return errmsg; | |
1448 | # endif /* not COHERENT */ | |
1449 | } | |
1450 | ||
1451 | /*ARGSUSED*/ | |
1452 | void cmd_errlist(frommark, tomark, cmd, bang, extra) | |
1453 | MARK frommark, tomark; | |
1454 | CMD cmd; | |
1455 | int bang; | |
1456 | char *extra; | |
1457 | { | |
1458 | static long endline;/* original number of lines in this file */ | |
1459 | static long offset; /* offset of the next line in the errlist file */ | |
1460 | int i; | |
1461 | char *errmsg; | |
1462 | ||
1463 | /* if a new errlist file is named, open it */ | |
1464 | if (extra && extra[0]) | |
1465 | { | |
1466 | /* close the old one */ | |
1467 | if (errfd >= 0) | |
1468 | { | |
1469 | close(errfd); | |
1470 | } | |
1471 | ||
1472 | /* open the new one */ | |
1473 | errfd = open(extra, O_RDONLY); | |
1474 | offset = 0L; | |
1475 | endline = nlines; | |
1476 | } | |
1477 | else if (errfd < 0) | |
1478 | { | |
1479 | /* open the default file */ | |
1480 | errfd = open(ERRLIST, O_RDONLY); | |
1481 | offset = 0L; | |
1482 | endline = nlines; | |
1483 | } | |
1484 | ||
1485 | /* do we have an errlist file now? */ | |
1486 | if (errfd < 0) | |
1487 | { | |
1488 | msg("There is no errlist file"); | |
1489 | beep(); | |
1490 | return; | |
1491 | } | |
1492 | ||
1493 | /* find the next error message in the file */ | |
1494 | do | |
1495 | { | |
1496 | /* read the next line from the errlist */ | |
1497 | lseek(errfd, offset, 0); | |
1498 | if (tread(errfd, tmpblk.c, (unsigned)BLKSIZE) <= 0) | |
1499 | { | |
1500 | msg("No more errors"); | |
1501 | beep(); | |
1502 | close(errfd); | |
1503 | errfd = -2; | |
1504 | return; | |
1505 | } | |
1506 | for (i = 0; tmpblk.c[i] != '\n'; i++) | |
1507 | { | |
1508 | } | |
1509 | tmpblk.c[i++] = 0; | |
1510 | ||
1511 | /* look for an error message in the line */ | |
1512 | errmsg = parse_errmsg(tmpblk.c); | |
1513 | if (!errmsg) | |
1514 | { | |
1515 | offset += i; | |
1516 | } | |
1517 | ||
1518 | } while (!errmsg); | |
1519 | ||
1520 | /* switch to the file containing the error, if this isn't it */ | |
1521 | if (strcmp(origname, errfile)) | |
1522 | { | |
1523 | if (!tmpabort(bang)) | |
1524 | { | |
1525 | msg("Use :er! to abort changes, or :w to save changes"); | |
1526 | beep(); | |
1527 | return; | |
1528 | } | |
1529 | tmpstart(errfile); | |
1530 | endline = nlines; | |
1531 | } | |
1532 | else if (endline == 0L) | |
1533 | { | |
1534 | endline = nlines; | |
1535 | } | |
1536 | ||
1537 | /* go to the line where the error was detected */ | |
1538 | cursor = MARK_AT_LINE(errline + (nlines - endline)); | |
1539 | if (cursor > MARK_LAST) | |
1540 | { | |
1541 | cursor = MARK_LAST; | |
1542 | } | |
1543 | if (mode == MODE_VI) | |
1544 | { | |
1545 | redraw(cursor, FALSE); | |
1546 | } | |
1547 | ||
1548 | /* display the error message */ | |
1549 | #ifdef CRUNCH | |
1550 | msg("%.70s", errmsg); | |
1551 | #else | |
1552 | if (nlines > endline) | |
1553 | { | |
1554 | msg("line %ld(+%ld): %.60s", errline, nlines - endline, errmsg); | |
1555 | } | |
1556 | else if (nlines < endline) | |
1557 | { | |
1558 | msg("line %ld(-%ld): %.60s", errline, endline - nlines, errmsg); | |
1559 | } | |
1560 | else | |
1561 | { | |
1562 | msg("line %ld: %.65s", errline, errmsg); | |
1563 | } | |
1564 | #endif | |
1565 | ||
1566 | /* remember where the NEXT error line will start */ | |
1567 | offset += i; | |
1568 | } | |
1569 | ||
1570 | ||
1571 | /*ARGSUSED*/ | |
1572 | void cmd_make(frommark, tomark, cmd, bang, extra) | |
1573 | MARK frommark, tomark; | |
1574 | CMD cmd; | |
1575 | int bang; | |
1576 | char *extra; | |
1577 | { | |
1578 | BLK buf; | |
1579 | ||
1580 | /* if the file hasn't been saved, then complain unless ! */ | |
1581 | if (tstflag(file, MODIFIED) && !bang) | |
1582 | { | |
1583 | msg("\"%s\" not saved yet", origname); | |
1584 | return; | |
1585 | } | |
1586 | ||
1587 | /* build the command */ | |
1588 | sprintf(buf.c, "%s %s %s%s", (cmd == CMD_CC ? o_cc : o_make), extra, REDIRECT, ERRLIST); | |
1589 | qaddstr(buf.c); | |
1590 | addch('\n'); | |
1591 | ||
1592 | /* close the old errlist file, if any */ | |
1593 | if (errfd >= 0) | |
1594 | { | |
1595 | close(errfd); | |
1596 | errfd = -3; | |
1597 | } | |
1598 | ||
08746e8b AM |
1599 | #if MINT |
1600 | /* I guess MiNT can't depend on the shell for redirection? */ | |
1601 | close(creat(ERRLIST, 0666)); | |
1602 | if ((fd = open(ERRLIST, O_RDWR)) == -1) | |
1603 | { | |
1604 | unlink(ERRLIST); | |
1605 | return; | |
1606 | } | |
1607 | suspend_curses(); | |
1608 | old2 = dup(2); | |
1609 | dup2(fd, 2); | |
1610 | system(buf.c); | |
1611 | dup2(old2, 2); | |
1612 | close(old2); | |
1613 | close(fd); | |
1614 | #else | |
15637ed4 RG |
1615 | /* run the command, with curses temporarily disabled */ |
1616 | suspend_curses(); | |
1617 | system(buf.c); | |
08746e8b | 1618 | #endif |
15637ed4 RG |
1619 | resume_curses(mode == MODE_EX); |
1620 | if (mode == MODE_COLON) | |
08746e8b AM |
1621 | /* ':' hit instead of CR, so let him escape... -nox */ |
1622 | return; | |
15637ed4 RG |
1623 | |
1624 | /* run the "errlist" command */ | |
1625 | cmd_errlist(MARK_UNSET, MARK_UNSET, cmd, bang, ERRLIST); | |
08746e8b AM |
1626 | |
1627 | /* avoid spurious `Hit <RETURN>' after 1st error message -nox */ | |
1628 | /* (which happened when cmd_errlist didn't have to change files...) */ | |
1629 | if (mode == MODE_VI) | |
1630 | refresh(); | |
15637ed4 RG |
1631 | } |
1632 | #endif | |
1633 | ||
1634 | ||
1635 | ||
1636 | #ifndef NO_COLOR | |
1637 | ||
1638 | /* figure out the number of text colors we use with this configuration */ | |
1639 | # ifndef NO_POPUP | |
1640 | # ifndef NO_VISIBLE | |
1641 | # define NCOLORS 7 | |
1642 | # else | |
1643 | # define NCOLORS 6 | |
1644 | # endif | |
1645 | # else | |
1646 | # ifndef NO_VISIBLE | |
1647 | # define NCOLORS 6 | |
1648 | # else | |
1649 | # define NCOLORS 5 | |
1650 | # endif | |
1651 | # endif | |
1652 | ||
1653 | /* the attribute bytes used in each of "when"s */ | |
1654 | static char bytes[NCOLORS]; | |
1655 | ||
1656 | static struct | |
1657 | { | |
1658 | char *word; /* a legal word */ | |
1659 | int type; /* what type of word this is */ | |
1660 | int val; /* some other value */ | |
1661 | } | |
1662 | words[] = | |
1663 | { | |
1664 | {"normal", 1, A_NORMAL}, /* all "when" names must come */ | |
1665 | {"standout", 1, A_STANDOUT}, /* at the top of the list. */ | |
1666 | {"bold", 1, A_BOLD}, /* The first 3 must be normal,*/ | |
1667 | {"underlined", 1, A_UNDERLINE}, /* standout, and bold; the */ | |
1668 | {"italics", 1, A_ALTCHARSET}, /* remaining names follow. */ | |
1669 | #ifndef NO_POPUP | |
1670 | {"popup", 1, A_POPUP}, | |
1671 | #endif | |
1672 | #ifndef NO_VISIBLE | |
1673 | {"visible", 1, A_VISIBLE}, | |
1674 | #endif | |
1675 | ||
1676 | {"black", 3, 0x00}, /* The color names start right*/ | |
1677 | {"blue", 3, 0x01}, /* after the "when" names. */ | |
1678 | {"green", 3, 0x02}, | |
1679 | {"cyan", 3, 0x03}, | |
1680 | {"red", 3, 0x04}, | |
1681 | {"magenta", 3, 0x05}, | |
1682 | {"brown", 3, 0x06}, | |
1683 | {"white", 3, 0x07}, | |
1684 | {"yellow", 3, 0x0E}, /* bright brown */ | |
1685 | {"gray", 3, 0x08}, /* bright black? of course! */ | |
1686 | {"grey", 3, 0x08}, | |
1687 | ||
1688 | {"bright", 2, 0x08}, | |
1689 | {"light", 2, 0x08}, | |
1690 | {"blinking", 2, 0x80}, | |
1691 | {"on", 0, 0}, | |
1692 | {"n", 1, A_NORMAL}, | |
1693 | {"s", 1, A_STANDOUT}, | |
1694 | {"b", 1, A_BOLD}, | |
1695 | {"u", 1, A_UNDERLINE}, | |
1696 | {"i", 1, A_ALTCHARSET}, | |
1697 | #ifndef NO_POPUP | |
1698 | {"p", 1, A_POPUP}, | |
1699 | {"menu", 1, A_POPUP}, | |
1700 | #endif | |
1701 | #ifndef NO_VISIBLE | |
1702 | {"v", 1, A_VISIBLE}, | |
1703 | #endif | |
1704 | {(char *)0, 0, 0} | |
1705 | }; | |
1706 | ||
1707 | /*ARGSUSED*/ | |
1708 | void cmd_color(frommark, tomark, cmd, bang, extra) | |
1709 | MARK frommark, tomark; | |
1710 | CMD cmd; | |
1711 | int bang; | |
1712 | char *extra; | |
1713 | { | |
1714 | int attrbyte; | |
1715 | int cmode; | |
1716 | int nowbg; /* BOOLEAN: is the next color background? */ | |
1717 | ||
1718 | REG char *scan; | |
1719 | REG i; | |
1720 | ||
1721 | ||
1722 | #ifndef CRUNCH | |
1723 | /* if no args are given, then report the current colors */ | |
1724 | if (!*extra) | |
1725 | { | |
1726 | /* if no colors are set, then say so */ | |
1727 | if (!bytes[0]) | |
1728 | { | |
1729 | msg("no colors have been set"); | |
1730 | return; | |
1731 | } | |
1732 | ||
1733 | /* report all five color combinations */ | |
1734 | for (i = 0; i < NCOLORS; i++) | |
1735 | { | |
1736 | qaddstr("color "); | |
1737 | qaddstr(words[i].word); | |
1738 | qaddch(' '); | |
1739 | if (bytes[i] & 0x80) | |
1740 | qaddstr("blinking "); | |
1741 | switch (bytes[i] & 0xf) | |
1742 | { | |
1743 | case 0x08: qaddstr("gray"); break; | |
1744 | case 0x0e: qaddstr("yellow"); break; | |
1745 | case 0x0f: qaddstr("bright white");break; | |
1746 | default: | |
1747 | if (bytes[i] & 0x08) | |
1748 | qaddstr("light "); | |
1749 | qaddstr(words[(bytes[i] & 0x07) + NCOLORS].word); | |
1750 | } | |
1751 | qaddstr(" on "); | |
1752 | qaddstr(words[((bytes[i] >> 4) & 0x07) + NCOLORS].word); | |
1753 | addch('\n'); | |
1754 | exrefresh(); | |
1755 | } | |
1756 | return; | |
1757 | } | |
1758 | #endif | |
1759 | ||
1760 | /* The default background color is the same as "normal" chars. | |
1761 | * There is no default foreground color. | |
1762 | */ | |
1763 | cmode = A_NORMAL; | |
1764 | attrbyte = bytes[0] & 0x70; | |
1765 | nowbg = FALSE; | |
1766 | ||
1767 | /* parse each word in the "extra" text */ | |
1768 | for (scan = extra; *extra; extra = scan) | |
1769 | { | |
1770 | /* locate the end of the word */ | |
1771 | while (*scan && *scan != ' ') | |
1772 | { | |
1773 | scan++; | |
1774 | } | |
1775 | ||
1776 | /* skip whitespace at the end of the word */ | |
1777 | while(*scan == ' ') | |
1778 | { | |
1779 | *scan++ = '\0'; | |
1780 | } | |
1781 | ||
1782 | /* lookup the word */ | |
1783 | for (i = 0; words[i].word && strcmp(words[i].word, extra); i++) | |
1784 | { | |
1785 | } | |
1786 | ||
1787 | /* if not a word, then complain */ | |
1788 | if (!words[i].word) | |
1789 | { | |
1790 | msg("Invalid color name: %s", extra); | |
1791 | return; | |
1792 | } | |
1793 | ||
1794 | /* process the word */ | |
1795 | switch (words[i].type) | |
1796 | { | |
1797 | case 1: | |
1798 | cmode = words[i].val; | |
1799 | break; | |
1800 | ||
1801 | case 2: | |
1802 | attrbyte |= words[i].val; | |
1803 | break; | |
1804 | ||
1805 | case 3: | |
1806 | if (nowbg) | |
1807 | attrbyte = ((attrbyte & ~0x70) | ((words[i].val & 0x07) << 4)); | |
1808 | else | |
1809 | attrbyte |= words[i].val; | |
1810 | nowbg = TRUE; | |
1811 | break; | |
1812 | } | |
1813 | } | |
1814 | ||
1815 | /* if nowbg isn't set now, then we were never given a foreground color */ | |
1816 | if (!nowbg) | |
1817 | { | |
1818 | msg("usage: color [when] [\"bright\"] [\"blinking\"] foreground [background]"); | |
1819 | return; | |
1820 | } | |
1821 | ||
1822 | /* the first ":color" command MUST define the "normal" colors */ | |
1823 | if (!bytes[0]) | |
1824 | cmode = A_NORMAL; | |
1825 | ||
1826 | /* we should now have a cmode and an attribute byte... */ | |
1827 | ||
1828 | /* set the color */ | |
1829 | setcolor(cmode, attrbyte); | |
1830 | ||
1831 | /* remember what we just did */ | |
1832 | bytes[cmode] = attrbyte; | |
1833 | ||
1834 | /* if the other colors haven't been set yet, then set them to defaults */ | |
1835 | if (!bytes[1]) | |
1836 | { | |
1837 | /* standout is the opposite of normal */ | |
1838 | bytes[1] = ((attrbyte << 4) & 0x70 | (attrbyte >> 4) & 0x07); | |
1839 | setcolor(A_STANDOUT, bytes[1]); | |
1840 | ||
1841 | /* if "normal" isn't bright, then bold defaults to normal+bright | |
1842 | * else bold defaults to bright white. | |
1843 | */ | |
1844 | bytes[2] = attrbyte | ((attrbyte & 0x08) ? 0x0f : 0x08); | |
1845 | setcolor(A_BOLD, bytes[2]); | |
1846 | ||
1847 | /* all others default to the "standout" colors, without blinking */ | |
1848 | for (i = 3; i < NCOLORS; i++) | |
1849 | { | |
1850 | bytes[i] = (bytes[1] & 0x7f); | |
1851 | setcolor(words[i].val, bytes[i]); | |
1852 | } | |
1853 | } | |
1854 | ||
1855 | /* force a redraw, so we see the new colors */ | |
1856 | redraw(MARK_UNSET, FALSE); | |
1857 | } | |
1858 | ||
1859 | ||
1860 | ||
1861 | void savecolor(fd) | |
1862 | int fd; /* file descriptor to write colors to */ | |
1863 | { | |
1864 | int i; | |
1865 | char buf[80]; | |
1866 | ||
1867 | /* if no colors are set, then return */ | |
1868 | if (!bytes[0]) | |
1869 | { | |
1870 | return; | |
1871 | } | |
1872 | ||
1873 | /* save all five color combinations */ | |
1874 | for (i = 0; i < NCOLORS; i++) | |
1875 | { | |
1876 | strcpy(buf, "color "); | |
1877 | strcat(buf, words[i].word); | |
1878 | strcat(buf, " "); | |
1879 | if (bytes[i] & 0x80) | |
1880 | strcat(buf, "blinking "); | |
1881 | switch (bytes[i] & 0xf) | |
1882 | { | |
1883 | case 0x08: strcat(buf, "gray"); break; | |
1884 | case 0x0e: strcat(buf, "yellow"); break; | |
1885 | case 0x0f: strcat(buf, "bright white");break; | |
1886 | default: | |
1887 | if (bytes[i] & 0x08) | |
1888 | strcat(buf, "light "); | |
1889 | strcat(buf, words[(bytes[i] & 0x07) + NCOLORS].word); | |
1890 | } | |
1891 | strcat(buf, " on "); | |
1892 | strcat(buf, words[((bytes[i] >> 4) & 0x07) + NCOLORS].word); | |
1893 | strcat(buf, "\n"); | |
1894 | twrite(fd, buf, (unsigned)strlen(buf)); | |
1895 | } | |
1896 | } | |
1897 | #endif | |
1898 | ||
1899 | #ifdef SIGTSTP | |
1900 | /* temporarily suspend elvis */ | |
1901 | /*ARGSUSED*/ | |
1902 | void cmd_suspend(frommark, tomark, cmd, bang, extra) | |
1903 | MARK frommark; | |
1904 | MARK tomark; | |
1905 | CMD cmd; | |
1906 | int bang; | |
1907 | char *extra; | |
1908 | { | |
08746e8b | 1909 | SIGTYPE (*func)(); /* stores the previous setting of SIGTSTP */ |
15637ed4 | 1910 | |
08746e8b | 1911 | #if ANY_UNIX |
15637ed4 RG |
1912 | /* the Bourne shell can't handle ^Z */ |
1913 | if (!strcmp(o_shell, "/bin/sh")) | |
1914 | { | |
1915 | msg("The /bin/sh shell doesn't support ^Z"); | |
1916 | return; | |
1917 | } | |
1918 | #endif | |
1919 | ||
15637ed4 RG |
1920 | move(LINES - 1, 0); |
1921 | if (tstflag(file, MODIFIED)) | |
1922 | { | |
1923 | addstr("Warning: \""); | |
1924 | addstr(origname); | |
1925 | addstr("\" modified but not yet saved"); | |
1926 | clrtoeol(); | |
1927 | } | |
1928 | refresh(); | |
1929 | suspend_curses(); | |
08746e8b | 1930 | func = signal(SIGTSTP, SIG_DFL); |
15637ed4 RG |
1931 | kill (0, SIGTSTP); |
1932 | ||
1933 | /* the process stops and resumes here */ | |
1934 | ||
1935 | signal(SIGTSTP, func); | |
1936 | resume_curses(TRUE); | |
1937 | if (mode == MODE_VI || mode == MODE_COLON) | |
1938 | redraw(MARK_UNSET, FALSE); | |
1939 | else | |
1940 | refresh (); | |
1941 | } | |
1942 | #endif |