blob: 1706e427174b089d98fe6c71c91636a1169f1645 [file] [log] [blame]
Bernhard Reutner-Fischerd9ed35c2006-05-19 12:38:30 +00001/* vi: set sw=4 ts=4: */
Rob Landley3b890392006-05-04 20:56:43 +00002/*
3 * Copyright (c) 2002 by David I. Bell
4 * Permission is granted to use, distribute, or modify this source,
5 * provided that this copyright notice remains intact.
6 *
7 * The "ed" built-in command (much simplified)
8 */
9
Rob Landley3b890392006-05-04 20:56:43 +000010#include "busybox.h"
11
Denis Vlasenko55f30b02007-03-24 22:42:29 +000012enum {
13 USERSIZE = sizeof(bb_common_bufsiz1) > 1024 ? 1024
14 : sizeof(bb_common_bufsiz1) - 1, /* max line length typed in by user */
15 INITBUF_SIZE = 1024, /* initial buffer size */
16};
17
Rob Landley3b890392006-05-04 20:56:43 +000018typedef struct LINE {
19 struct LINE *next;
20 struct LINE *prev;
21 int len;
22 char data[1];
23} LINE;
24
Denis Vlasenko55f30b02007-03-24 22:42:29 +000025#define searchString bb_common_bufsiz1
26
Rob Landley3b890392006-05-04 20:56:43 +000027static LINE lines, *curLine;
28static int curNum, lastNum, marks[26], dirty;
Denis Vlasenko55f30b02007-03-24 22:42:29 +000029static char *bufBase, *bufPtr, *fileName;
Rob Landley3b890392006-05-04 20:56:43 +000030static int bufUsed, bufSize;
31
32static void doCommands(void);
33static void subCommand(const char *cmd, int num1, int num2);
34static int getNum(const char **retcp, int *retHaveNum, int *retNum);
35static int setCurNum(int num);
36static int initEdit(void);
37static void termEdit(void);
38static void addLines(int num);
39static int insertLine(int num, const char *data, int len);
40static int deleteLines(int num1, int num2);
41static int printLines(int num1, int num2, int expandFlag);
42static int writeLines(const char *file, int num1, int num2);
43static int readLines(const char *file, int num);
44static int searchLines(const char *str, int num1, int num2);
45static LINE *findLine(int num);
46
47static int findString(const LINE *lp, const char * str, int len, int offset);
48
Denis Vlasenko06af2162007-02-03 17:28:39 +000049int ed_main(int argc, char **argv);
Rob Landley3b890392006-05-04 20:56:43 +000050int ed_main(int argc, char **argv)
51{
52 if (!initEdit())
53 return EXIT_FAILURE;
54
55 if (argc > 1) {
56 fileName = strdup(argv[1]);
57
58 if (fileName == NULL) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +000059 bb_error_msg("no memory");
Rob Landley3b890392006-05-04 20:56:43 +000060 termEdit();
61 return EXIT_SUCCESS;
62 }
63
64 if (!readLines(fileName, 1)) {
65 termEdit();
66 return EXIT_SUCCESS;
67 }
68
69 if (lastNum)
70 setCurNum(1);
71
72 dirty = FALSE;
73 }
74
75 doCommands();
76
77 termEdit();
78 return EXIT_SUCCESS;
79}
80
81/*
82 * Read commands until we are told to stop.
83 */
84static void doCommands(void)
85{
86 const char *cp;
87 char *endbuf, *newname, buf[USERSIZE];
88 int len, num1, num2, have1, have2;
89
Denis Vlasenko610c4aa2006-11-30 20:57:50 +000090 while (TRUE) {
Rob Landley3b890392006-05-04 20:56:43 +000091 printf(": ");
92 fflush(stdout);
93
94 if (fgets(buf, sizeof(buf), stdin) == NULL)
95 return;
96
97 len = strlen(buf);
98
99 if (len == 0)
100 return;
101
102 endbuf = &buf[len - 1];
103
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000104 if (*endbuf != '\n') {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000105 bb_error_msg("command line too long");
Rob Landley3b890392006-05-04 20:56:43 +0000106
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000107 do {
Rob Landley3b890392006-05-04 20:56:43 +0000108 len = fgetc(stdin);
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000109 } while ((len != EOF) && (len != '\n'));
Rob Landley3b890392006-05-04 20:56:43 +0000110
111 continue;
112 }
113
114 while ((endbuf > buf) && isblank(endbuf[-1]))
115 endbuf--;
116
117 *endbuf = '\0';
118
119 cp = buf;
120
121 while (isblank(*cp))
122 cp++;
123
124 have1 = FALSE;
125 have2 = FALSE;
126
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000127 if ((curNum == 0) && (lastNum > 0)) {
Rob Landley3b890392006-05-04 20:56:43 +0000128 curNum = 1;
129 curLine = lines.next;
130 }
131
132 if (!getNum(&cp, &have1, &num1))
133 continue;
134
135 while (isblank(*cp))
136 cp++;
137
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000138 if (*cp == ',') {
Rob Landley3b890392006-05-04 20:56:43 +0000139 cp++;
140
141 if (!getNum(&cp, &have2, &num2))
142 continue;
143
144 if (!have1)
145 num1 = 1;
146
147 if (!have2)
148 num2 = lastNum;
149
150 have1 = TRUE;
151 have2 = TRUE;
152 }
153
154 if (!have1)
155 num1 = curNum;
156
157 if (!have2)
158 num2 = num1;
159
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000160 switch (*cp++) {
Rob Landley3b890392006-05-04 20:56:43 +0000161 case 'a':
162 addLines(num1 + 1);
163 break;
164
165 case 'c':
166 deleteLines(num1, num2);
167 addLines(num1);
168 break;
169
170 case 'd':
171 deleteLines(num1, num2);
172 break;
173
174 case 'f':
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000175 if (*cp && !isblank(*cp)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000176 bb_error_msg("bad file command");
Rob Landley3b890392006-05-04 20:56:43 +0000177 break;
178 }
179
180 while (isblank(*cp))
181 cp++;
182
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000183 if (*cp == '\0') {
Rob Landley3b890392006-05-04 20:56:43 +0000184 if (fileName)
185 printf("\"%s\"\n", fileName);
186 else
187 printf("No file name\n");
Rob Landley3b890392006-05-04 20:56:43 +0000188 break;
189 }
190
191 newname = strdup(cp);
192
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000193 if (newname == NULL) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000194 bb_error_msg("no memory for file name");
Rob Landley3b890392006-05-04 20:56:43 +0000195 break;
196 }
197
198 if (fileName)
199 free(fileName);
200
201 fileName = newname;
202 break;
203
204 case 'i':
205 addLines(num1);
206 break;
207
208 case 'k':
209 while (isblank(*cp))
210 cp++;
211
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000212 if ((*cp < 'a') || (*cp > 'a') || cp[1]) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000213 bb_error_msg("bad mark name");
Rob Landley3b890392006-05-04 20:56:43 +0000214 break;
215 }
216
217 marks[*cp - 'a'] = num2;
218 break;
219
220 case 'l':
221 printLines(num1, num2, TRUE);
222 break;
223
224 case 'p':
225 printLines(num1, num2, FALSE);
226 break;
227
228 case 'q':
229 while (isblank(*cp))
230 cp++;
231
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000232 if (have1 || *cp) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000233 bb_error_msg("bad quit command");
Rob Landley3b890392006-05-04 20:56:43 +0000234 break;
235 }
236
237 if (!dirty)
238 return;
239
240 printf("Really quit? ");
241 fflush(stdout);
242
243 buf[0] = '\0';
244 fgets(buf, sizeof(buf), stdin);
245 cp = buf;
246
247 while (isblank(*cp))
248 cp++;
249
250 if ((*cp == 'y') || (*cp == 'Y'))
251 return;
252
253 break;
254
255 case 'r':
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000256 if (*cp && !isblank(*cp)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000257 bb_error_msg("bad read command");
Rob Landley3b890392006-05-04 20:56:43 +0000258 break;
259 }
260
261 while (isblank(*cp))
262 cp++;
263
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000264 if (*cp == '\0') {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000265 bb_error_msg("no file name");
Rob Landley3b890392006-05-04 20:56:43 +0000266 break;
267 }
268
269 if (!have1)
270 num1 = lastNum;
271
272 if (readLines(cp, num1 + 1))
273 break;
274
275 if (fileName == NULL)
276 fileName = strdup(cp);
277
278 break;
279
280 case 's':
281 subCommand(cp, num1, num2);
282 break;
283
284 case 'w':
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000285 if (*cp && !isblank(*cp)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000286 bb_error_msg("bad write command");
Rob Landley3b890392006-05-04 20:56:43 +0000287 break;
288 }
289
290 while (isblank(*cp))
291 cp++;
292
293 if (!have1) {
294 num1 = 1;
295 num2 = lastNum;
296 }
297
298 if (*cp == '\0')
299 cp = fileName;
300
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000301 if (cp == NULL) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000302 bb_error_msg("no file name specified");
Rob Landley3b890392006-05-04 20:56:43 +0000303 break;
304 }
Bernhard Reutner-Fischerd9ed35c2006-05-19 12:38:30 +0000305
Rob Landley3b890392006-05-04 20:56:43 +0000306 writeLines(cp, num1, num2);
307 break;
308
309 case 'z':
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000310 switch (*cp) {
Rob Landley3b890392006-05-04 20:56:43 +0000311 case '-':
312 printLines(curNum-21, curNum, FALSE);
313 break;
314 case '.':
315 printLines(curNum-11, curNum+10, FALSE);
316 break;
317 default:
318 printLines(curNum, curNum+21, FALSE);
319 break;
320 }
321 break;
322
323 case '.':
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000324 if (have1) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000325 bb_error_msg("no arguments allowed");
Rob Landley3b890392006-05-04 20:56:43 +0000326 break;
327 }
328
329 printLines(curNum, curNum, FALSE);
330 break;
Bernhard Reutner-Fischerd9ed35c2006-05-19 12:38:30 +0000331
Rob Landley3b890392006-05-04 20:56:43 +0000332 case '-':
333 if (setCurNum(curNum - 1))
334 printLines(curNum, curNum, FALSE);
335
336 break;
337
338 case '=':
339 printf("%d\n", num1);
340 break;
341
342 case '\0':
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000343 if (have1) {
Rob Landley3b890392006-05-04 20:56:43 +0000344 printLines(num2, num2, FALSE);
345 break;
346 }
347
348 if (setCurNum(curNum + 1))
349 printLines(curNum, curNum, FALSE);
350
351 break;
352
353 default:
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000354 bb_error_msg("unimplemented command");
Rob Landley3b890392006-05-04 20:56:43 +0000355 break;
356 }
357 }
358}
359
360
361/*
362 * Do the substitute command.
363 * The current line is set to the last substitution done.
364 */
365static void subCommand(const char * cmd, int num1, int num2)
366{
367 char *cp, *oldStr, *newStr, buf[USERSIZE];
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000368 int delim, oldLen, newLen, deltaLen, offset;
Rob Landley3b890392006-05-04 20:56:43 +0000369 LINE *lp, *nlp;
370 int globalFlag, printFlag, didSub, needPrint;
371
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000372 if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000373 bb_error_msg("bad line range for substitute");
Rob Landley3b890392006-05-04 20:56:43 +0000374 return;
375 }
376
377 globalFlag = FALSE;
378 printFlag = FALSE;
379 didSub = FALSE;
380 needPrint = FALSE;
381
382 /*
383 * Copy the command so we can modify it.
384 */
385 strcpy(buf, cmd);
386 cp = buf;
387
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000388 if (isblank(*cp) || (*cp == '\0')) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000389 bb_error_msg("bad delimiter for substitute");
Rob Landley3b890392006-05-04 20:56:43 +0000390 return;
391 }
392
393 delim = *cp++;
394 oldStr = cp;
395
396 cp = strchr(cp, delim);
397
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000398 if (cp == NULL) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000399 bb_error_msg("missing 2nd delimiter for substitute");
Rob Landley3b890392006-05-04 20:56:43 +0000400 return;
401 }
402
403 *cp++ = '\0';
404
405 newStr = cp;
406 cp = strchr(cp, delim);
407
408 if (cp)
409 *cp++ = '\0';
410 else
Denis Vlasenkoa41fdf32007-01-29 22:51:00 +0000411 cp = (char*)"";
Rob Landley3b890392006-05-04 20:56:43 +0000412
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000413 while (*cp) switch (*cp++) {
Rob Landley3b890392006-05-04 20:56:43 +0000414 case 'g':
415 globalFlag = TRUE;
416 break;
417
418 case 'p':
419 printFlag = TRUE;
420 break;
421
422 default:
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000423 bb_error_msg("unknown option for substitute");
Rob Landley3b890392006-05-04 20:56:43 +0000424 return;
425 }
426
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000427 if (*oldStr == '\0') {
428 if (searchString[0] == '\0') {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000429 bb_error_msg("no previous search string");
Rob Landley3b890392006-05-04 20:56:43 +0000430 return;
431 }
432
433 oldStr = searchString;
434 }
435
436 if (oldStr != searchString)
437 strcpy(searchString, oldStr);
438
439 lp = findLine(num1);
440
441 if (lp == NULL)
442 return;
443
444 oldLen = strlen(oldStr);
445 newLen = strlen(newStr);
446 deltaLen = newLen - oldLen;
447 offset = 0;
448 nlp = NULL;
449
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000450 while (num1 <= num2) {
Rob Landley3b890392006-05-04 20:56:43 +0000451 offset = findString(lp, oldStr, oldLen, offset);
452
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000453 if (offset < 0) {
454 if (needPrint) {
Rob Landley3b890392006-05-04 20:56:43 +0000455 printLines(num1, num1, FALSE);
456 needPrint = FALSE;
457 }
458
459 offset = 0;
460 lp = lp->next;
461 num1++;
462
463 continue;
464 }
465
466 needPrint = printFlag;
467 didSub = TRUE;
468 dirty = TRUE;
469
470 /*
471 * If the replacement string is the same size or shorter
472 * than the old string, then the substitution is easy.
473 */
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000474 if (deltaLen <= 0) {
Rob Landley3b890392006-05-04 20:56:43 +0000475 memcpy(&lp->data[offset], newStr, newLen);
476
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000477 if (deltaLen) {
Rob Landley3b890392006-05-04 20:56:43 +0000478 memcpy(&lp->data[offset + newLen],
479 &lp->data[offset + oldLen],
480 lp->len - offset - oldLen);
481
482 lp->len += deltaLen;
483 }
484
485 offset += newLen;
486
487 if (globalFlag)
488 continue;
489
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000490 if (needPrint) {
Rob Landley3b890392006-05-04 20:56:43 +0000491 printLines(num1, num1, FALSE);
492 needPrint = FALSE;
493 }
494
495 lp = lp->next;
496 num1++;
497
498 continue;
499 }
500
501 /*
502 * The new string is larger, so allocate a new line
503 * structure and use that. Link it in in place of
504 * the old line structure.
505 */
506 nlp = (LINE *) malloc(sizeof(LINE) + lp->len + deltaLen);
507
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000508 if (nlp == NULL) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000509 bb_error_msg("cannot get memory for line");
Rob Landley3b890392006-05-04 20:56:43 +0000510 return;
511 }
512
513 nlp->len = lp->len + deltaLen;
514
515 memcpy(nlp->data, lp->data, offset);
516
517 memcpy(&nlp->data[offset], newStr, newLen);
518
519 memcpy(&nlp->data[offset + newLen],
520 &lp->data[offset + oldLen],
521 lp->len - offset - oldLen);
522
523 nlp->next = lp->next;
524 nlp->prev = lp->prev;
525 nlp->prev->next = nlp;
526 nlp->next->prev = nlp;
527
528 if (curLine == lp)
529 curLine = nlp;
530
531 free(lp);
532 lp = nlp;
533
534 offset += newLen;
535
536 if (globalFlag)
537 continue;
538
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000539 if (needPrint) {
Rob Landley3b890392006-05-04 20:56:43 +0000540 printLines(num1, num1, FALSE);
541 needPrint = FALSE;
542 }
543
544 lp = lp->next;
545 num1++;
546 }
547
548 if (!didSub)
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000549 bb_error_msg("no substitutions found for \"%s\"", oldStr);
Rob Landley3b890392006-05-04 20:56:43 +0000550}
551
552
553/*
554 * Search a line for the specified string starting at the specified
555 * offset in the line. Returns the offset of the found string, or -1.
556 */
557static int findString( const LINE * lp, const char * str, int len, int offset)
558{
559 int left;
560 const char *cp, *ncp;
561
562 cp = &lp->data[offset];
563 left = lp->len - offset;
564
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000565 while (left >= len) {
Rob Landley3b890392006-05-04 20:56:43 +0000566 ncp = memchr(cp, *str, left);
567
568 if (ncp == NULL)
569 return -1;
570
571 left -= (ncp - cp);
572
573 if (left < len)
574 return -1;
575
576 cp = ncp;
577
578 if (memcmp(cp, str, len) == 0)
579 return (cp - lp->data);
580
581 cp++;
582 left--;
583 }
584
585 return -1;
586}
587
588
589/*
590 * Add lines which are typed in by the user.
591 * The lines are inserted just before the specified line number.
592 * The lines are terminated by a line containing a single dot (ugly!),
593 * or by an end of file.
594 */
595static void addLines(int num)
596{
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000597 int len;
598 char buf[USERSIZE + 1];
Rob Landley3b890392006-05-04 20:56:43 +0000599
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000600 while (fgets(buf, sizeof(buf), stdin)) {
Rob Landley3b890392006-05-04 20:56:43 +0000601 if ((buf[0] == '.') && (buf[1] == '\n') && (buf[2] == '\0'))
602 return;
603
604 len = strlen(buf);
605
606 if (len == 0)
607 return;
608
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000609 if (buf[len - 1] != '\n') {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000610 bb_error_msg("line too long");
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000611 do {
Rob Landley3b890392006-05-04 20:56:43 +0000612 len = fgetc(stdin);
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000613 } while ((len != EOF) && (len != '\n'));
Rob Landley3b890392006-05-04 20:56:43 +0000614 return;
615 }
616
617 if (!insertLine(num++, buf, len))
618 return;
619 }
620}
621
622
623/*
624 * Parse a line number argument if it is present. This is a sum
625 * or difference of numbers, '.', '$', 'x, or a search string.
Bernhard Reutner-Fischerd9ed35c2006-05-19 12:38:30 +0000626 * Returns TRUE if successful (whether or not there was a number).
Rob Landley3b890392006-05-04 20:56:43 +0000627 * Returns FALSE if there was a parsing error, with a message output.
628 * Whether there was a number is returned indirectly, as is the number.
629 * The character pointer which stopped the scan is also returned.
630 */
631static int getNum(const char **retcp, int *retHaveNum, int *retNum)
632{
633 const char *cp;
634 char *endStr, str[USERSIZE];
635 int haveNum, value, num, sign;
636
637 cp = *retcp;
638 haveNum = FALSE;
639 value = 0;
640 sign = 1;
641
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000642 while (TRUE) {
Rob Landley3b890392006-05-04 20:56:43 +0000643 while (isblank(*cp))
644 cp++;
645
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000646 switch (*cp) {
Rob Landley3b890392006-05-04 20:56:43 +0000647 case '.':
648 haveNum = TRUE;
649 num = curNum;
650 cp++;
651 break;
652
653 case '$':
654 haveNum = TRUE;
655 num = lastNum;
656 cp++;
657 break;
658
659 case '\'':
660 cp++;
661
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000662 if ((*cp < 'a') || (*cp > 'z')) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000663 bb_error_msg("bad mark name");
Rob Landley3b890392006-05-04 20:56:43 +0000664 return FALSE;
665 }
666
667 haveNum = TRUE;
668 num = marks[*cp++ - 'a'];
669 break;
670
671 case '/':
672 strcpy(str, ++cp);
673 endStr = strchr(str, '/');
674
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000675 if (endStr) {
Rob Landley3b890392006-05-04 20:56:43 +0000676 *endStr++ = '\0';
677 cp += (endStr - str);
678 }
679 else
680 cp = "";
681
682 num = searchLines(str, curNum, lastNum);
683
684 if (num == 0)
685 return FALSE;
686
687 haveNum = TRUE;
688 break;
689
690 default:
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000691 if (!isdigit(*cp)) {
Rob Landley3b890392006-05-04 20:56:43 +0000692 *retcp = cp;
693 *retHaveNum = haveNum;
694 *retNum = value;
Rob Landley3b890392006-05-04 20:56:43 +0000695 return TRUE;
696 }
697
698 num = 0;
699
700 while (isdigit(*cp))
701 num = num * 10 + *cp++ - '0';
702
703 haveNum = TRUE;
704 break;
705 }
706
707 value += num * sign;
708
709 while (isblank(*cp))
710 cp++;
711
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000712 switch (*cp) {
Rob Landley3b890392006-05-04 20:56:43 +0000713 case '-':
714 sign = -1;
715 cp++;
716 break;
717
718 case '+':
719 sign = 1;
720 cp++;
721 break;
722
723 default:
724 *retcp = cp;
725 *retHaveNum = haveNum;
726 *retNum = value;
Rob Landley3b890392006-05-04 20:56:43 +0000727 return TRUE;
728 }
729 }
730}
731
732
733/*
734 * Initialize everything for editing.
735 */
736static int initEdit(void)
737{
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000738 int i;
Rob Landley3b890392006-05-04 20:56:43 +0000739
740 bufSize = INITBUF_SIZE;
741 bufBase = malloc(bufSize);
742
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000743 if (bufBase == NULL) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000744 bb_error_msg("no memory for buffer");
Rob Landley3b890392006-05-04 20:56:43 +0000745 return FALSE;
746 }
747
748 bufPtr = bufBase;
749 bufUsed = 0;
750
751 lines.next = &lines;
752 lines.prev = &lines;
753
754 curLine = NULL;
755 curNum = 0;
756 lastNum = 0;
757 dirty = FALSE;
758 fileName = NULL;
759 searchString[0] = '\0';
760
761 for (i = 0; i < 26; i++)
762 marks[i] = 0;
763
764 return TRUE;
765}
766
767
768/*
769 * Finish editing.
770 */
771static void termEdit(void)
772{
773 if (bufBase)
774 free(bufBase);
775
776 bufBase = NULL;
777 bufPtr = NULL;
778 bufSize = 0;
779 bufUsed = 0;
780
781 if (fileName)
782 free(fileName);
783
784 fileName = NULL;
785
786 searchString[0] = '\0';
787
788 if (lastNum)
789 deleteLines(1, lastNum);
790
791 lastNum = 0;
792 curNum = 0;
793 curLine = NULL;
794}
795
796
797/*
798 * Read lines from a file at the specified line number.
799 * Returns TRUE if the file was successfully read.
800 */
801static int readLines(const char * file, int num)
802{
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000803 int fd, cc;
Rob Landley3b890392006-05-04 20:56:43 +0000804 int len, lineCount, charCount;
805 char *cp;
806
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000807 if ((num < 1) || (num > lastNum + 1)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000808 bb_error_msg("bad line for read");
Rob Landley3b890392006-05-04 20:56:43 +0000809 return FALSE;
810 }
811
812 fd = open(file, 0);
813
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000814 if (fd < 0) {
Rob Landley3b890392006-05-04 20:56:43 +0000815 perror(file);
Rob Landley3b890392006-05-04 20:56:43 +0000816 return FALSE;
817 }
818
819 bufPtr = bufBase;
820 bufUsed = 0;
821 lineCount = 0;
822 charCount = 0;
823 cc = 0;
824
825 printf("\"%s\", ", file);
826 fflush(stdout);
827
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000828 do {
Rob Landley3b890392006-05-04 20:56:43 +0000829 cp = memchr(bufPtr, '\n', bufUsed);
830
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000831 if (cp) {
Rob Landley3b890392006-05-04 20:56:43 +0000832 len = (cp - bufPtr) + 1;
833
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000834 if (!insertLine(num, bufPtr, len)) {
Rob Landley3b890392006-05-04 20:56:43 +0000835 close(fd);
Rob Landley3b890392006-05-04 20:56:43 +0000836 return FALSE;
837 }
838
839 bufPtr += len;
840 bufUsed -= len;
841 charCount += len;
842 lineCount++;
843 num++;
844
845 continue;
846 }
847
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000848 if (bufPtr != bufBase) {
Rob Landley3b890392006-05-04 20:56:43 +0000849 memcpy(bufBase, bufPtr, bufUsed);
850 bufPtr = bufBase + bufUsed;
851 }
852
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000853 if (bufUsed >= bufSize) {
Rob Landley3b890392006-05-04 20:56:43 +0000854 len = (bufSize * 3) / 2;
855 cp = realloc(bufBase, len);
856
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000857 if (cp == NULL) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000858 bb_error_msg("no memory for buffer");
Rob Landley3b890392006-05-04 20:56:43 +0000859 close(fd);
Rob Landley3b890392006-05-04 20:56:43 +0000860 return FALSE;
861 }
862
863 bufBase = cp;
864 bufPtr = bufBase + bufUsed;
865 bufSize = len;
866 }
867
868 cc = read(fd, bufPtr, bufSize - bufUsed);
869 bufUsed += cc;
870 bufPtr = bufBase;
871
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000872 } while (cc > 0);
Rob Landley3b890392006-05-04 20:56:43 +0000873
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000874 if (cc < 0) {
Rob Landley3b890392006-05-04 20:56:43 +0000875 perror(file);
876 close(fd);
Rob Landley3b890392006-05-04 20:56:43 +0000877 return FALSE;
878 }
879
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000880 if (bufUsed) {
881 if (!insertLine(num, bufPtr, bufUsed)) {
Rob Landley3b890392006-05-04 20:56:43 +0000882 close(fd);
Rob Landley3b890392006-05-04 20:56:43 +0000883 return -1;
884 }
885
886 lineCount++;
887 charCount += bufUsed;
888 }
889
890 close(fd);
891
892 printf("%d lines%s, %d chars\n", lineCount,
893 (bufUsed ? " (incomplete)" : ""), charCount);
894
895 return TRUE;
896}
897
898
899/*
900 * Write the specified lines out to the specified file.
901 * Returns TRUE if successful, or FALSE on an error with a message output.
902 */
903static int writeLines(const char * file, int num1, int num2)
904{
905 LINE *lp;
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000906 int fd, lineCount, charCount;
Rob Landley3b890392006-05-04 20:56:43 +0000907
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000908 if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000909 bb_error_msg("bad line range for write");
Rob Landley3b890392006-05-04 20:56:43 +0000910 return FALSE;
911 }
912
913 lineCount = 0;
914 charCount = 0;
915
916 fd = creat(file, 0666);
917
918 if (fd < 0) {
919 perror(file);
Rob Landley3b890392006-05-04 20:56:43 +0000920 return FALSE;
921 }
922
923 printf("\"%s\", ", file);
924 fflush(stdout);
925
926 lp = findLine(num1);
927
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000928 if (lp == NULL) {
Rob Landley3b890392006-05-04 20:56:43 +0000929 close(fd);
Rob Landley3b890392006-05-04 20:56:43 +0000930 return FALSE;
931 }
932
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000933 while (num1++ <= num2) {
934 if (write(fd, lp->data, lp->len) != lp->len) {
Rob Landley3b890392006-05-04 20:56:43 +0000935 perror(file);
936 close(fd);
Rob Landley3b890392006-05-04 20:56:43 +0000937 return FALSE;
938 }
939
940 charCount += lp->len;
941 lineCount++;
942 lp = lp->next;
943 }
944
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000945 if (close(fd) < 0) {
Rob Landley3b890392006-05-04 20:56:43 +0000946 perror(file);
Rob Landley3b890392006-05-04 20:56:43 +0000947 return FALSE;
948 }
949
950 printf("%d lines, %d chars\n", lineCount, charCount);
Rob Landley3b890392006-05-04 20:56:43 +0000951 return TRUE;
952}
953
954
955/*
956 * Print lines in a specified range.
957 * The last line printed becomes the current line.
958 * If expandFlag is TRUE, then the line is printed specially to
959 * show magic characters.
960 */
961static int printLines(int num1, int num2, int expandFlag)
962{
963 const LINE *lp;
964 const char *cp;
965 int ch, count;
966
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000967 if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000968 bb_error_msg("bad line range for print");
Rob Landley3b890392006-05-04 20:56:43 +0000969 return FALSE;
970 }
971
972 lp = findLine(num1);
973
974 if (lp == NULL)
975 return FALSE;
976
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000977 while (num1 <= num2) {
978 if (!expandFlag) {
Rob Landley3b890392006-05-04 20:56:43 +0000979 write(1, lp->data, lp->len);
980 setCurNum(num1++);
981 lp = lp->next;
982
983 continue;
984 }
985
986 /*
987 * Show control characters and characters with the
988 * high bit set specially.
989 */
990 cp = lp->data;
991 count = lp->len;
992
993 if ((count > 0) && (cp[count - 1] == '\n'))
994 count--;
995
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000996 while (count-- > 0) {
Rob Landley3b890392006-05-04 20:56:43 +0000997 ch = *cp++;
998
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000999 if (ch & 0x80) {
Rob Landley3b890392006-05-04 20:56:43 +00001000 fputs("M-", stdout);
1001 ch &= 0x7f;
1002 }
1003
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001004 if (ch < ' ') {
Rob Landley3b890392006-05-04 20:56:43 +00001005 fputc('^', stdout);
1006 ch += '@';
1007 }
1008
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001009 if (ch == 0x7f) {
Rob Landley3b890392006-05-04 20:56:43 +00001010 fputc('^', stdout);
1011 ch = '?';
1012 }
1013
1014 fputc(ch, stdout);
1015 }
1016
1017 fputs("$\n", stdout);
1018
1019 setCurNum(num1++);
1020 lp = lp->next;
1021 }
1022
1023 return TRUE;
1024}
1025
1026
1027/*
1028 * Insert a new line with the specified text.
1029 * The line is inserted so as to become the specified line,
1030 * thus pushing any existing and further lines down one.
1031 * The inserted line is also set to become the current line.
1032 * Returns TRUE if successful.
1033 */
1034static int insertLine(int num, const char * data, int len)
1035{
1036 LINE *newLp, *lp;
1037
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001038 if ((num < 1) || (num > lastNum + 1)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +00001039 bb_error_msg("inserting at bad line number");
Rob Landley3b890392006-05-04 20:56:43 +00001040 return FALSE;
1041 }
1042
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001043 newLp = malloc(sizeof(LINE) + len - 1);
Rob Landley3b890392006-05-04 20:56:43 +00001044
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001045 if (newLp == NULL) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +00001046 bb_error_msg("failed to allocate memory for line");
Rob Landley3b890392006-05-04 20:56:43 +00001047 return FALSE;
1048 }
1049
1050 memcpy(newLp->data, data, len);
1051 newLp->len = len;
1052
1053 if (num > lastNum)
1054 lp = &lines;
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001055 else {
Rob Landley3b890392006-05-04 20:56:43 +00001056 lp = findLine(num);
1057
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001058 if (lp == NULL) {
Rob Landley3b890392006-05-04 20:56:43 +00001059 free((char *) newLp);
Rob Landley3b890392006-05-04 20:56:43 +00001060 return FALSE;
1061 }
1062 }
1063
1064 newLp->next = lp;
1065 newLp->prev = lp->prev;
1066 lp->prev->next = newLp;
1067 lp->prev = newLp;
1068
1069 lastNum++;
1070 dirty = TRUE;
Rob Landley3b890392006-05-04 20:56:43 +00001071 return setCurNum(num);
1072}
1073
1074
1075/*
1076 * Delete lines from the given range.
1077 */
1078static int deleteLines(int num1, int num2)
1079{
1080 LINE *lp, *nlp, *plp;
1081 int count;
1082
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001083 if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +00001084 bb_error_msg("bad line numbers for delete");
Rob Landley3b890392006-05-04 20:56:43 +00001085 return FALSE;
1086 }
1087
1088 lp = findLine(num1);
1089
1090 if (lp == NULL)
1091 return FALSE;
1092
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001093 if ((curNum >= num1) && (curNum <= num2)) {
Rob Landley3b890392006-05-04 20:56:43 +00001094 if (num2 < lastNum)
1095 setCurNum(num2 + 1);
1096 else if (num1 > 1)
1097 setCurNum(num1 - 1);
1098 else
1099 curNum = 0;
1100 }
1101
1102 count = num2 - num1 + 1;
1103
1104 if (curNum > num2)
1105 curNum -= count;
1106
1107 lastNum -= count;
1108
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001109 while (count-- > 0) {
Rob Landley3b890392006-05-04 20:56:43 +00001110 nlp = lp->next;
1111 plp = lp->prev;
1112 plp->next = nlp;
1113 nlp->prev = plp;
1114 lp->next = NULL;
1115 lp->prev = NULL;
1116 lp->len = 0;
1117 free(lp);
1118 lp = nlp;
1119 }
1120
1121 dirty = TRUE;
1122
1123 return TRUE;
1124}
1125
1126
1127/*
1128 * Search for a line which contains the specified string.
1129 * If the string is NULL, then the previously searched for string
1130 * is used. The currently searched for string is saved for future use.
1131 * Returns the line number which matches, or 0 if there was no match
1132 * with an error printed.
1133 */
1134static int searchLines(const char *str, int num1, int num2)
1135{
1136 const LINE *lp;
1137 int len;
1138
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001139 if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +00001140 bb_error_msg("bad line numbers for search");
Rob Landley3b890392006-05-04 20:56:43 +00001141 return 0;
1142 }
1143
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001144 if (*str == '\0') {
1145 if (searchString[0] == '\0') {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +00001146 bb_error_msg("no previous search string");
Rob Landley3b890392006-05-04 20:56:43 +00001147 return 0;
1148 }
1149
1150 str = searchString;
1151 }
1152
1153 if (str != searchString)
1154 strcpy(searchString, str);
1155
1156 len = strlen(str);
1157
1158 lp = findLine(num1);
1159
1160 if (lp == NULL)
1161 return 0;
1162
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001163 while (num1 <= num2) {
Rob Landley3b890392006-05-04 20:56:43 +00001164 if (findString(lp, str, len, 0) >= 0)
1165 return num1;
1166
1167 num1++;
1168 lp = lp->next;
1169 }
1170
Denis Vlasenkod3d004d2006-10-27 09:02:31 +00001171 bb_error_msg("cannot find string \"%s\"", str);
Rob Landley3b890392006-05-04 20:56:43 +00001172 return 0;
1173}
1174
1175
1176/*
1177 * Return a pointer to the specified line number.
1178 */
1179static LINE *findLine(int num)
1180{
1181 LINE *lp;
1182 int lnum;
1183
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001184 if ((num < 1) || (num > lastNum)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +00001185 bb_error_msg("line number %d does not exist", num);
Rob Landley3b890392006-05-04 20:56:43 +00001186 return NULL;
1187 }
1188
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001189 if (curNum <= 0) {
Rob Landley3b890392006-05-04 20:56:43 +00001190 curNum = 1;
1191 curLine = lines.next;
1192 }
1193
1194 if (num == curNum)
1195 return curLine;
1196
1197 lp = curLine;
1198 lnum = curNum;
1199
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001200 if (num < (curNum / 2)) {
Rob Landley3b890392006-05-04 20:56:43 +00001201 lp = lines.next;
1202 lnum = 1;
1203 }
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001204 else if (num > ((curNum + lastNum) / 2)) {
Rob Landley3b890392006-05-04 20:56:43 +00001205 lp = lines.prev;
1206 lnum = lastNum;
1207 }
1208
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001209 while (lnum < num) {
Rob Landley3b890392006-05-04 20:56:43 +00001210 lp = lp->next;
1211 lnum++;
1212 }
1213
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001214 while (lnum > num) {
Rob Landley3b890392006-05-04 20:56:43 +00001215 lp = lp->prev;
1216 lnum--;
1217 }
Rob Landley3b890392006-05-04 20:56:43 +00001218 return lp;
1219}
1220
1221
1222/*
1223 * Set the current line number.
1224 * Returns TRUE if successful.
1225 */
1226static int setCurNum(int num)
1227{
1228 LINE *lp;
1229
1230 lp = findLine(num);
1231
1232 if (lp == NULL)
1233 return FALSE;
1234
1235 curNum = num;
1236 curLine = lp;
Rob Landley3b890392006-05-04 20:56:43 +00001237 return TRUE;
1238}