blob: 3aca7591224410289fc1479ddda78818f64c7022 [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
12#define USERSIZE 1024 /* max line length typed in by user */
13#define INITBUF_SIZE 1024 /* initial buffer size */
14typedef struct LINE {
15 struct LINE *next;
16 struct LINE *prev;
17 int len;
18 char data[1];
19} LINE;
20
21static LINE lines, *curLine;
22static int curNum, lastNum, marks[26], dirty;
23static char *bufBase, *bufPtr, *fileName, searchString[USERSIZE];
24static int bufUsed, bufSize;
25
26static void doCommands(void);
27static void subCommand(const char *cmd, int num1, int num2);
28static int getNum(const char **retcp, int *retHaveNum, int *retNum);
29static int setCurNum(int num);
30static int initEdit(void);
31static void termEdit(void);
32static void addLines(int num);
33static int insertLine(int num, const char *data, int len);
34static int deleteLines(int num1, int num2);
35static int printLines(int num1, int num2, int expandFlag);
36static int writeLines(const char *file, int num1, int num2);
37static int readLines(const char *file, int num);
38static int searchLines(const char *str, int num1, int num2);
39static LINE *findLine(int num);
40
41static int findString(const LINE *lp, const char * str, int len, int offset);
42
43int ed_main(int argc, char **argv)
44{
45 if (!initEdit())
46 return EXIT_FAILURE;
47
48 if (argc > 1) {
49 fileName = strdup(argv[1]);
50
51 if (fileName == NULL) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +000052 bb_error_msg("no memory");
Rob Landley3b890392006-05-04 20:56:43 +000053 termEdit();
54 return EXIT_SUCCESS;
55 }
56
57 if (!readLines(fileName, 1)) {
58 termEdit();
59 return EXIT_SUCCESS;
60 }
61
62 if (lastNum)
63 setCurNum(1);
64
65 dirty = FALSE;
66 }
67
68 doCommands();
69
70 termEdit();
71 return EXIT_SUCCESS;
72}
73
74/*
75 * Read commands until we are told to stop.
76 */
77static void doCommands(void)
78{
79 const char *cp;
80 char *endbuf, *newname, buf[USERSIZE];
81 int len, num1, num2, have1, have2;
82
Denis Vlasenko610c4aa2006-11-30 20:57:50 +000083 while (TRUE) {
Rob Landley3b890392006-05-04 20:56:43 +000084 printf(": ");
85 fflush(stdout);
86
87 if (fgets(buf, sizeof(buf), stdin) == NULL)
88 return;
89
90 len = strlen(buf);
91
92 if (len == 0)
93 return;
94
95 endbuf = &buf[len - 1];
96
Denis Vlasenko610c4aa2006-11-30 20:57:50 +000097 if (*endbuf != '\n') {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +000098 bb_error_msg("command line too long");
Rob Landley3b890392006-05-04 20:56:43 +000099
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000100 do {
Rob Landley3b890392006-05-04 20:56:43 +0000101 len = fgetc(stdin);
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000102 } while ((len != EOF) && (len != '\n'));
Rob Landley3b890392006-05-04 20:56:43 +0000103
104 continue;
105 }
106
107 while ((endbuf > buf) && isblank(endbuf[-1]))
108 endbuf--;
109
110 *endbuf = '\0';
111
112 cp = buf;
113
114 while (isblank(*cp))
115 cp++;
116
117 have1 = FALSE;
118 have2 = FALSE;
119
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000120 if ((curNum == 0) && (lastNum > 0)) {
Rob Landley3b890392006-05-04 20:56:43 +0000121 curNum = 1;
122 curLine = lines.next;
123 }
124
125 if (!getNum(&cp, &have1, &num1))
126 continue;
127
128 while (isblank(*cp))
129 cp++;
130
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000131 if (*cp == ',') {
Rob Landley3b890392006-05-04 20:56:43 +0000132 cp++;
133
134 if (!getNum(&cp, &have2, &num2))
135 continue;
136
137 if (!have1)
138 num1 = 1;
139
140 if (!have2)
141 num2 = lastNum;
142
143 have1 = TRUE;
144 have2 = TRUE;
145 }
146
147 if (!have1)
148 num1 = curNum;
149
150 if (!have2)
151 num2 = num1;
152
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000153 switch (*cp++) {
Rob Landley3b890392006-05-04 20:56:43 +0000154 case 'a':
155 addLines(num1 + 1);
156 break;
157
158 case 'c':
159 deleteLines(num1, num2);
160 addLines(num1);
161 break;
162
163 case 'd':
164 deleteLines(num1, num2);
165 break;
166
167 case 'f':
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000168 if (*cp && !isblank(*cp)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000169 bb_error_msg("bad file command");
Rob Landley3b890392006-05-04 20:56:43 +0000170 break;
171 }
172
173 while (isblank(*cp))
174 cp++;
175
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000176 if (*cp == '\0') {
Rob Landley3b890392006-05-04 20:56:43 +0000177 if (fileName)
178 printf("\"%s\"\n", fileName);
179 else
180 printf("No file name\n");
Rob Landley3b890392006-05-04 20:56:43 +0000181 break;
182 }
183
184 newname = strdup(cp);
185
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000186 if (newname == NULL) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000187 bb_error_msg("no memory for file name");
Rob Landley3b890392006-05-04 20:56:43 +0000188 break;
189 }
190
191 if (fileName)
192 free(fileName);
193
194 fileName = newname;
195 break;
196
197 case 'i':
198 addLines(num1);
199 break;
200
201 case 'k':
202 while (isblank(*cp))
203 cp++;
204
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000205 if ((*cp < 'a') || (*cp > 'a') || cp[1]) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000206 bb_error_msg("bad mark name");
Rob Landley3b890392006-05-04 20:56:43 +0000207 break;
208 }
209
210 marks[*cp - 'a'] = num2;
211 break;
212
213 case 'l':
214 printLines(num1, num2, TRUE);
215 break;
216
217 case 'p':
218 printLines(num1, num2, FALSE);
219 break;
220
221 case 'q':
222 while (isblank(*cp))
223 cp++;
224
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000225 if (have1 || *cp) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000226 bb_error_msg("bad quit command");
Rob Landley3b890392006-05-04 20:56:43 +0000227 break;
228 }
229
230 if (!dirty)
231 return;
232
233 printf("Really quit? ");
234 fflush(stdout);
235
236 buf[0] = '\0';
237 fgets(buf, sizeof(buf), stdin);
238 cp = buf;
239
240 while (isblank(*cp))
241 cp++;
242
243 if ((*cp == 'y') || (*cp == 'Y'))
244 return;
245
246 break;
247
248 case 'r':
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000249 if (*cp && !isblank(*cp)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000250 bb_error_msg("bad read command");
Rob Landley3b890392006-05-04 20:56:43 +0000251 break;
252 }
253
254 while (isblank(*cp))
255 cp++;
256
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000257 if (*cp == '\0') {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000258 bb_error_msg("no file name");
Rob Landley3b890392006-05-04 20:56:43 +0000259 break;
260 }
261
262 if (!have1)
263 num1 = lastNum;
264
265 if (readLines(cp, num1 + 1))
266 break;
267
268 if (fileName == NULL)
269 fileName = strdup(cp);
270
271 break;
272
273 case 's':
274 subCommand(cp, num1, num2);
275 break;
276
277 case 'w':
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000278 if (*cp && !isblank(*cp)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000279 bb_error_msg("bad write command");
Rob Landley3b890392006-05-04 20:56:43 +0000280 break;
281 }
282
283 while (isblank(*cp))
284 cp++;
285
286 if (!have1) {
287 num1 = 1;
288 num2 = lastNum;
289 }
290
291 if (*cp == '\0')
292 cp = fileName;
293
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000294 if (cp == NULL) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000295 bb_error_msg("no file name specified");
Rob Landley3b890392006-05-04 20:56:43 +0000296 break;
297 }
Bernhard Reutner-Fischerd9ed35c2006-05-19 12:38:30 +0000298
Rob Landley3b890392006-05-04 20:56:43 +0000299 writeLines(cp, num1, num2);
300 break;
301
302 case 'z':
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000303 switch (*cp) {
Rob Landley3b890392006-05-04 20:56:43 +0000304 case '-':
305 printLines(curNum-21, curNum, FALSE);
306 break;
307 case '.':
308 printLines(curNum-11, curNum+10, FALSE);
309 break;
310 default:
311 printLines(curNum, curNum+21, FALSE);
312 break;
313 }
314 break;
315
316 case '.':
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000317 if (have1) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000318 bb_error_msg("no arguments allowed");
Rob Landley3b890392006-05-04 20:56:43 +0000319 break;
320 }
321
322 printLines(curNum, curNum, FALSE);
323 break;
Bernhard Reutner-Fischerd9ed35c2006-05-19 12:38:30 +0000324
Rob Landley3b890392006-05-04 20:56:43 +0000325 case '-':
326 if (setCurNum(curNum - 1))
327 printLines(curNum, curNum, FALSE);
328
329 break;
330
331 case '=':
332 printf("%d\n", num1);
333 break;
334
335 case '\0':
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000336 if (have1) {
Rob Landley3b890392006-05-04 20:56:43 +0000337 printLines(num2, num2, FALSE);
338 break;
339 }
340
341 if (setCurNum(curNum + 1))
342 printLines(curNum, curNum, FALSE);
343
344 break;
345
346 default:
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000347 bb_error_msg("unimplemented command");
Rob Landley3b890392006-05-04 20:56:43 +0000348 break;
349 }
350 }
351}
352
353
354/*
355 * Do the substitute command.
356 * The current line is set to the last substitution done.
357 */
358static void subCommand(const char * cmd, int num1, int num2)
359{
360 char *cp, *oldStr, *newStr, buf[USERSIZE];
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000361 int delim, oldLen, newLen, deltaLen, offset;
Rob Landley3b890392006-05-04 20:56:43 +0000362 LINE *lp, *nlp;
363 int globalFlag, printFlag, didSub, needPrint;
364
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000365 if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000366 bb_error_msg("bad line range for substitute");
Rob Landley3b890392006-05-04 20:56:43 +0000367 return;
368 }
369
370 globalFlag = FALSE;
371 printFlag = FALSE;
372 didSub = FALSE;
373 needPrint = FALSE;
374
375 /*
376 * Copy the command so we can modify it.
377 */
378 strcpy(buf, cmd);
379 cp = buf;
380
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000381 if (isblank(*cp) || (*cp == '\0')) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000382 bb_error_msg("bad delimiter for substitute");
Rob Landley3b890392006-05-04 20:56:43 +0000383 return;
384 }
385
386 delim = *cp++;
387 oldStr = cp;
388
389 cp = strchr(cp, delim);
390
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000391 if (cp == NULL) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000392 bb_error_msg("missing 2nd delimiter for substitute");
Rob Landley3b890392006-05-04 20:56:43 +0000393 return;
394 }
395
396 *cp++ = '\0';
397
398 newStr = cp;
399 cp = strchr(cp, delim);
400
401 if (cp)
402 *cp++ = '\0';
403 else
404 cp = "";
405
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000406 while (*cp) switch (*cp++) {
Rob Landley3b890392006-05-04 20:56:43 +0000407 case 'g':
408 globalFlag = TRUE;
409 break;
410
411 case 'p':
412 printFlag = TRUE;
413 break;
414
415 default:
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000416 bb_error_msg("unknown option for substitute");
Rob Landley3b890392006-05-04 20:56:43 +0000417 return;
418 }
419
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000420 if (*oldStr == '\0') {
421 if (searchString[0] == '\0') {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000422 bb_error_msg("no previous search string");
Rob Landley3b890392006-05-04 20:56:43 +0000423 return;
424 }
425
426 oldStr = searchString;
427 }
428
429 if (oldStr != searchString)
430 strcpy(searchString, oldStr);
431
432 lp = findLine(num1);
433
434 if (lp == NULL)
435 return;
436
437 oldLen = strlen(oldStr);
438 newLen = strlen(newStr);
439 deltaLen = newLen - oldLen;
440 offset = 0;
441 nlp = NULL;
442
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000443 while (num1 <= num2) {
Rob Landley3b890392006-05-04 20:56:43 +0000444 offset = findString(lp, oldStr, oldLen, offset);
445
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000446 if (offset < 0) {
447 if (needPrint) {
Rob Landley3b890392006-05-04 20:56:43 +0000448 printLines(num1, num1, FALSE);
449 needPrint = FALSE;
450 }
451
452 offset = 0;
453 lp = lp->next;
454 num1++;
455
456 continue;
457 }
458
459 needPrint = printFlag;
460 didSub = TRUE;
461 dirty = TRUE;
462
463 /*
464 * If the replacement string is the same size or shorter
465 * than the old string, then the substitution is easy.
466 */
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000467 if (deltaLen <= 0) {
Rob Landley3b890392006-05-04 20:56:43 +0000468 memcpy(&lp->data[offset], newStr, newLen);
469
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000470 if (deltaLen) {
Rob Landley3b890392006-05-04 20:56:43 +0000471 memcpy(&lp->data[offset + newLen],
472 &lp->data[offset + oldLen],
473 lp->len - offset - oldLen);
474
475 lp->len += deltaLen;
476 }
477
478 offset += newLen;
479
480 if (globalFlag)
481 continue;
482
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000483 if (needPrint) {
Rob Landley3b890392006-05-04 20:56:43 +0000484 printLines(num1, num1, FALSE);
485 needPrint = FALSE;
486 }
487
488 lp = lp->next;
489 num1++;
490
491 continue;
492 }
493
494 /*
495 * The new string is larger, so allocate a new line
496 * structure and use that. Link it in in place of
497 * the old line structure.
498 */
499 nlp = (LINE *) malloc(sizeof(LINE) + lp->len + deltaLen);
500
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000501 if (nlp == NULL) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000502 bb_error_msg("cannot get memory for line");
Rob Landley3b890392006-05-04 20:56:43 +0000503 return;
504 }
505
506 nlp->len = lp->len + deltaLen;
507
508 memcpy(nlp->data, lp->data, offset);
509
510 memcpy(&nlp->data[offset], newStr, newLen);
511
512 memcpy(&nlp->data[offset + newLen],
513 &lp->data[offset + oldLen],
514 lp->len - offset - oldLen);
515
516 nlp->next = lp->next;
517 nlp->prev = lp->prev;
518 nlp->prev->next = nlp;
519 nlp->next->prev = nlp;
520
521 if (curLine == lp)
522 curLine = nlp;
523
524 free(lp);
525 lp = nlp;
526
527 offset += newLen;
528
529 if (globalFlag)
530 continue;
531
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000532 if (needPrint) {
Rob Landley3b890392006-05-04 20:56:43 +0000533 printLines(num1, num1, FALSE);
534 needPrint = FALSE;
535 }
536
537 lp = lp->next;
538 num1++;
539 }
540
541 if (!didSub)
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000542 bb_error_msg("no substitutions found for \"%s\"", oldStr);
Rob Landley3b890392006-05-04 20:56:43 +0000543}
544
545
546/*
547 * Search a line for the specified string starting at the specified
548 * offset in the line. Returns the offset of the found string, or -1.
549 */
550static int findString( const LINE * lp, const char * str, int len, int offset)
551{
552 int left;
553 const char *cp, *ncp;
554
555 cp = &lp->data[offset];
556 left = lp->len - offset;
557
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000558 while (left >= len) {
Rob Landley3b890392006-05-04 20:56:43 +0000559 ncp = memchr(cp, *str, left);
560
561 if (ncp == NULL)
562 return -1;
563
564 left -= (ncp - cp);
565
566 if (left < len)
567 return -1;
568
569 cp = ncp;
570
571 if (memcmp(cp, str, len) == 0)
572 return (cp - lp->data);
573
574 cp++;
575 left--;
576 }
577
578 return -1;
579}
580
581
582/*
583 * Add lines which are typed in by the user.
584 * The lines are inserted just before the specified line number.
585 * The lines are terminated by a line containing a single dot (ugly!),
586 * or by an end of file.
587 */
588static void addLines(int num)
589{
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000590 int len;
591 char buf[USERSIZE + 1];
Rob Landley3b890392006-05-04 20:56:43 +0000592
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000593 while (fgets(buf, sizeof(buf), stdin)) {
Rob Landley3b890392006-05-04 20:56:43 +0000594 if ((buf[0] == '.') && (buf[1] == '\n') && (buf[2] == '\0'))
595 return;
596
597 len = strlen(buf);
598
599 if (len == 0)
600 return;
601
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000602 if (buf[len - 1] != '\n') {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000603 bb_error_msg("line too long");
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000604 do {
Rob Landley3b890392006-05-04 20:56:43 +0000605 len = fgetc(stdin);
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000606 } while ((len != EOF) && (len != '\n'));
Rob Landley3b890392006-05-04 20:56:43 +0000607 return;
608 }
609
610 if (!insertLine(num++, buf, len))
611 return;
612 }
613}
614
615
616/*
617 * Parse a line number argument if it is present. This is a sum
618 * or difference of numbers, '.', '$', 'x, or a search string.
Bernhard Reutner-Fischerd9ed35c2006-05-19 12:38:30 +0000619 * Returns TRUE if successful (whether or not there was a number).
Rob Landley3b890392006-05-04 20:56:43 +0000620 * Returns FALSE if there was a parsing error, with a message output.
621 * Whether there was a number is returned indirectly, as is the number.
622 * The character pointer which stopped the scan is also returned.
623 */
624static int getNum(const char **retcp, int *retHaveNum, int *retNum)
625{
626 const char *cp;
627 char *endStr, str[USERSIZE];
628 int haveNum, value, num, sign;
629
630 cp = *retcp;
631 haveNum = FALSE;
632 value = 0;
633 sign = 1;
634
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000635 while (TRUE) {
Rob Landley3b890392006-05-04 20:56:43 +0000636 while (isblank(*cp))
637 cp++;
638
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000639 switch (*cp) {
Rob Landley3b890392006-05-04 20:56:43 +0000640 case '.':
641 haveNum = TRUE;
642 num = curNum;
643 cp++;
644 break;
645
646 case '$':
647 haveNum = TRUE;
648 num = lastNum;
649 cp++;
650 break;
651
652 case '\'':
653 cp++;
654
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000655 if ((*cp < 'a') || (*cp > 'z')) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000656 bb_error_msg("bad mark name");
Rob Landley3b890392006-05-04 20:56:43 +0000657 return FALSE;
658 }
659
660 haveNum = TRUE;
661 num = marks[*cp++ - 'a'];
662 break;
663
664 case '/':
665 strcpy(str, ++cp);
666 endStr = strchr(str, '/');
667
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000668 if (endStr) {
Rob Landley3b890392006-05-04 20:56:43 +0000669 *endStr++ = '\0';
670 cp += (endStr - str);
671 }
672 else
673 cp = "";
674
675 num = searchLines(str, curNum, lastNum);
676
677 if (num == 0)
678 return FALSE;
679
680 haveNum = TRUE;
681 break;
682
683 default:
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000684 if (!isdigit(*cp)) {
Rob Landley3b890392006-05-04 20:56:43 +0000685 *retcp = cp;
686 *retHaveNum = haveNum;
687 *retNum = value;
Rob Landley3b890392006-05-04 20:56:43 +0000688 return TRUE;
689 }
690
691 num = 0;
692
693 while (isdigit(*cp))
694 num = num * 10 + *cp++ - '0';
695
696 haveNum = TRUE;
697 break;
698 }
699
700 value += num * sign;
701
702 while (isblank(*cp))
703 cp++;
704
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000705 switch (*cp) {
Rob Landley3b890392006-05-04 20:56:43 +0000706 case '-':
707 sign = -1;
708 cp++;
709 break;
710
711 case '+':
712 sign = 1;
713 cp++;
714 break;
715
716 default:
717 *retcp = cp;
718 *retHaveNum = haveNum;
719 *retNum = value;
Rob Landley3b890392006-05-04 20:56:43 +0000720 return TRUE;
721 }
722 }
723}
724
725
726/*
727 * Initialize everything for editing.
728 */
729static int initEdit(void)
730{
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000731 int i;
Rob Landley3b890392006-05-04 20:56:43 +0000732
733 bufSize = INITBUF_SIZE;
734 bufBase = malloc(bufSize);
735
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000736 if (bufBase == NULL) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000737 bb_error_msg("no memory for buffer");
Rob Landley3b890392006-05-04 20:56:43 +0000738 return FALSE;
739 }
740
741 bufPtr = bufBase;
742 bufUsed = 0;
743
744 lines.next = &lines;
745 lines.prev = &lines;
746
747 curLine = NULL;
748 curNum = 0;
749 lastNum = 0;
750 dirty = FALSE;
751 fileName = NULL;
752 searchString[0] = '\0';
753
754 for (i = 0; i < 26; i++)
755 marks[i] = 0;
756
757 return TRUE;
758}
759
760
761/*
762 * Finish editing.
763 */
764static void termEdit(void)
765{
766 if (bufBase)
767 free(bufBase);
768
769 bufBase = NULL;
770 bufPtr = NULL;
771 bufSize = 0;
772 bufUsed = 0;
773
774 if (fileName)
775 free(fileName);
776
777 fileName = NULL;
778
779 searchString[0] = '\0';
780
781 if (lastNum)
782 deleteLines(1, lastNum);
783
784 lastNum = 0;
785 curNum = 0;
786 curLine = NULL;
787}
788
789
790/*
791 * Read lines from a file at the specified line number.
792 * Returns TRUE if the file was successfully read.
793 */
794static int readLines(const char * file, int num)
795{
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000796 int fd, cc;
Rob Landley3b890392006-05-04 20:56:43 +0000797 int len, lineCount, charCount;
798 char *cp;
799
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000800 if ((num < 1) || (num > lastNum + 1)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000801 bb_error_msg("bad line for read");
Rob Landley3b890392006-05-04 20:56:43 +0000802 return FALSE;
803 }
804
805 fd = open(file, 0);
806
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000807 if (fd < 0) {
Rob Landley3b890392006-05-04 20:56:43 +0000808 perror(file);
Rob Landley3b890392006-05-04 20:56:43 +0000809 return FALSE;
810 }
811
812 bufPtr = bufBase;
813 bufUsed = 0;
814 lineCount = 0;
815 charCount = 0;
816 cc = 0;
817
818 printf("\"%s\", ", file);
819 fflush(stdout);
820
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000821 do {
Rob Landley3b890392006-05-04 20:56:43 +0000822 cp = memchr(bufPtr, '\n', bufUsed);
823
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000824 if (cp) {
Rob Landley3b890392006-05-04 20:56:43 +0000825 len = (cp - bufPtr) + 1;
826
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000827 if (!insertLine(num, bufPtr, len)) {
Rob Landley3b890392006-05-04 20:56:43 +0000828 close(fd);
Rob Landley3b890392006-05-04 20:56:43 +0000829 return FALSE;
830 }
831
832 bufPtr += len;
833 bufUsed -= len;
834 charCount += len;
835 lineCount++;
836 num++;
837
838 continue;
839 }
840
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000841 if (bufPtr != bufBase) {
Rob Landley3b890392006-05-04 20:56:43 +0000842 memcpy(bufBase, bufPtr, bufUsed);
843 bufPtr = bufBase + bufUsed;
844 }
845
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000846 if (bufUsed >= bufSize) {
Rob Landley3b890392006-05-04 20:56:43 +0000847 len = (bufSize * 3) / 2;
848 cp = realloc(bufBase, len);
849
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000850 if (cp == NULL) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000851 bb_error_msg("no memory for buffer");
Rob Landley3b890392006-05-04 20:56:43 +0000852 close(fd);
Rob Landley3b890392006-05-04 20:56:43 +0000853 return FALSE;
854 }
855
856 bufBase = cp;
857 bufPtr = bufBase + bufUsed;
858 bufSize = len;
859 }
860
861 cc = read(fd, bufPtr, bufSize - bufUsed);
862 bufUsed += cc;
863 bufPtr = bufBase;
864
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000865 } while (cc > 0);
Rob Landley3b890392006-05-04 20:56:43 +0000866
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000867 if (cc < 0) {
Rob Landley3b890392006-05-04 20:56:43 +0000868 perror(file);
869 close(fd);
Rob Landley3b890392006-05-04 20:56:43 +0000870 return FALSE;
871 }
872
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000873 if (bufUsed) {
874 if (!insertLine(num, bufPtr, bufUsed)) {
Rob Landley3b890392006-05-04 20:56:43 +0000875 close(fd);
Rob Landley3b890392006-05-04 20:56:43 +0000876 return -1;
877 }
878
879 lineCount++;
880 charCount += bufUsed;
881 }
882
883 close(fd);
884
885 printf("%d lines%s, %d chars\n", lineCount,
886 (bufUsed ? " (incomplete)" : ""), charCount);
887
888 return TRUE;
889}
890
891
892/*
893 * Write the specified lines out to the specified file.
894 * Returns TRUE if successful, or FALSE on an error with a message output.
895 */
896static int writeLines(const char * file, int num1, int num2)
897{
898 LINE *lp;
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000899 int fd, lineCount, charCount;
Rob Landley3b890392006-05-04 20:56:43 +0000900
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000901 if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000902 bb_error_msg("bad line range for write");
Rob Landley3b890392006-05-04 20:56:43 +0000903 return FALSE;
904 }
905
906 lineCount = 0;
907 charCount = 0;
908
909 fd = creat(file, 0666);
910
911 if (fd < 0) {
912 perror(file);
Rob Landley3b890392006-05-04 20:56:43 +0000913 return FALSE;
914 }
915
916 printf("\"%s\", ", file);
917 fflush(stdout);
918
919 lp = findLine(num1);
920
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000921 if (lp == NULL) {
Rob Landley3b890392006-05-04 20:56:43 +0000922 close(fd);
Rob Landley3b890392006-05-04 20:56:43 +0000923 return FALSE;
924 }
925
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000926 while (num1++ <= num2) {
927 if (write(fd, lp->data, lp->len) != lp->len) {
Rob Landley3b890392006-05-04 20:56:43 +0000928 perror(file);
929 close(fd);
Rob Landley3b890392006-05-04 20:56:43 +0000930 return FALSE;
931 }
932
933 charCount += lp->len;
934 lineCount++;
935 lp = lp->next;
936 }
937
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000938 if (close(fd) < 0) {
Rob Landley3b890392006-05-04 20:56:43 +0000939 perror(file);
Rob Landley3b890392006-05-04 20:56:43 +0000940 return FALSE;
941 }
942
943 printf("%d lines, %d chars\n", lineCount, charCount);
Rob Landley3b890392006-05-04 20:56:43 +0000944 return TRUE;
945}
946
947
948/*
949 * Print lines in a specified range.
950 * The last line printed becomes the current line.
951 * If expandFlag is TRUE, then the line is printed specially to
952 * show magic characters.
953 */
954static int printLines(int num1, int num2, int expandFlag)
955{
956 const LINE *lp;
957 const char *cp;
958 int ch, count;
959
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000960 if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000961 bb_error_msg("bad line range for print");
Rob Landley3b890392006-05-04 20:56:43 +0000962 return FALSE;
963 }
964
965 lp = findLine(num1);
966
967 if (lp == NULL)
968 return FALSE;
969
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000970 while (num1 <= num2) {
971 if (!expandFlag) {
Rob Landley3b890392006-05-04 20:56:43 +0000972 write(1, lp->data, lp->len);
973 setCurNum(num1++);
974 lp = lp->next;
975
976 continue;
977 }
978
979 /*
980 * Show control characters and characters with the
981 * high bit set specially.
982 */
983 cp = lp->data;
984 count = lp->len;
985
986 if ((count > 0) && (cp[count - 1] == '\n'))
987 count--;
988
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000989 while (count-- > 0) {
Rob Landley3b890392006-05-04 20:56:43 +0000990 ch = *cp++;
991
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000992 if (ch & 0x80) {
Rob Landley3b890392006-05-04 20:56:43 +0000993 fputs("M-", stdout);
994 ch &= 0x7f;
995 }
996
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000997 if (ch < ' ') {
Rob Landley3b890392006-05-04 20:56:43 +0000998 fputc('^', stdout);
999 ch += '@';
1000 }
1001
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001002 if (ch == 0x7f) {
Rob Landley3b890392006-05-04 20:56:43 +00001003 fputc('^', stdout);
1004 ch = '?';
1005 }
1006
1007 fputc(ch, stdout);
1008 }
1009
1010 fputs("$\n", stdout);
1011
1012 setCurNum(num1++);
1013 lp = lp->next;
1014 }
1015
1016 return TRUE;
1017}
1018
1019
1020/*
1021 * Insert a new line with the specified text.
1022 * The line is inserted so as to become the specified line,
1023 * thus pushing any existing and further lines down one.
1024 * The inserted line is also set to become the current line.
1025 * Returns TRUE if successful.
1026 */
1027static int insertLine(int num, const char * data, int len)
1028{
1029 LINE *newLp, *lp;
1030
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001031 if ((num < 1) || (num > lastNum + 1)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +00001032 bb_error_msg("inserting at bad line number");
Rob Landley3b890392006-05-04 20:56:43 +00001033 return FALSE;
1034 }
1035
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001036 newLp = malloc(sizeof(LINE) + len - 1);
Rob Landley3b890392006-05-04 20:56:43 +00001037
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001038 if (newLp == NULL) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +00001039 bb_error_msg("failed to allocate memory for line");
Rob Landley3b890392006-05-04 20:56:43 +00001040 return FALSE;
1041 }
1042
1043 memcpy(newLp->data, data, len);
1044 newLp->len = len;
1045
1046 if (num > lastNum)
1047 lp = &lines;
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001048 else {
Rob Landley3b890392006-05-04 20:56:43 +00001049 lp = findLine(num);
1050
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001051 if (lp == NULL) {
Rob Landley3b890392006-05-04 20:56:43 +00001052 free((char *) newLp);
Rob Landley3b890392006-05-04 20:56:43 +00001053 return FALSE;
1054 }
1055 }
1056
1057 newLp->next = lp;
1058 newLp->prev = lp->prev;
1059 lp->prev->next = newLp;
1060 lp->prev = newLp;
1061
1062 lastNum++;
1063 dirty = TRUE;
Rob Landley3b890392006-05-04 20:56:43 +00001064 return setCurNum(num);
1065}
1066
1067
1068/*
1069 * Delete lines from the given range.
1070 */
1071static int deleteLines(int num1, int num2)
1072{
1073 LINE *lp, *nlp, *plp;
1074 int count;
1075
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001076 if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +00001077 bb_error_msg("bad line numbers for delete");
Rob Landley3b890392006-05-04 20:56:43 +00001078 return FALSE;
1079 }
1080
1081 lp = findLine(num1);
1082
1083 if (lp == NULL)
1084 return FALSE;
1085
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001086 if ((curNum >= num1) && (curNum <= num2)) {
Rob Landley3b890392006-05-04 20:56:43 +00001087 if (num2 < lastNum)
1088 setCurNum(num2 + 1);
1089 else if (num1 > 1)
1090 setCurNum(num1 - 1);
1091 else
1092 curNum = 0;
1093 }
1094
1095 count = num2 - num1 + 1;
1096
1097 if (curNum > num2)
1098 curNum -= count;
1099
1100 lastNum -= count;
1101
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001102 while (count-- > 0) {
Rob Landley3b890392006-05-04 20:56:43 +00001103 nlp = lp->next;
1104 plp = lp->prev;
1105 plp->next = nlp;
1106 nlp->prev = plp;
1107 lp->next = NULL;
1108 lp->prev = NULL;
1109 lp->len = 0;
1110 free(lp);
1111 lp = nlp;
1112 }
1113
1114 dirty = TRUE;
1115
1116 return TRUE;
1117}
1118
1119
1120/*
1121 * Search for a line which contains the specified string.
1122 * If the string is NULL, then the previously searched for string
1123 * is used. The currently searched for string is saved for future use.
1124 * Returns the line number which matches, or 0 if there was no match
1125 * with an error printed.
1126 */
1127static int searchLines(const char *str, int num1, int num2)
1128{
1129 const LINE *lp;
1130 int len;
1131
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001132 if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +00001133 bb_error_msg("bad line numbers for search");
Rob Landley3b890392006-05-04 20:56:43 +00001134 return 0;
1135 }
1136
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001137 if (*str == '\0') {
1138 if (searchString[0] == '\0') {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +00001139 bb_error_msg("no previous search string");
Rob Landley3b890392006-05-04 20:56:43 +00001140 return 0;
1141 }
1142
1143 str = searchString;
1144 }
1145
1146 if (str != searchString)
1147 strcpy(searchString, str);
1148
1149 len = strlen(str);
1150
1151 lp = findLine(num1);
1152
1153 if (lp == NULL)
1154 return 0;
1155
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001156 while (num1 <= num2) {
Rob Landley3b890392006-05-04 20:56:43 +00001157 if (findString(lp, str, len, 0) >= 0)
1158 return num1;
1159
1160 num1++;
1161 lp = lp->next;
1162 }
1163
Denis Vlasenkod3d004d2006-10-27 09:02:31 +00001164 bb_error_msg("cannot find string \"%s\"", str);
Rob Landley3b890392006-05-04 20:56:43 +00001165 return 0;
1166}
1167
1168
1169/*
1170 * Return a pointer to the specified line number.
1171 */
1172static LINE *findLine(int num)
1173{
1174 LINE *lp;
1175 int lnum;
1176
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001177 if ((num < 1) || (num > lastNum)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +00001178 bb_error_msg("line number %d does not exist", num);
Rob Landley3b890392006-05-04 20:56:43 +00001179 return NULL;
1180 }
1181
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001182 if (curNum <= 0) {
Rob Landley3b890392006-05-04 20:56:43 +00001183 curNum = 1;
1184 curLine = lines.next;
1185 }
1186
1187 if (num == curNum)
1188 return curLine;
1189
1190 lp = curLine;
1191 lnum = curNum;
1192
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001193 if (num < (curNum / 2)) {
Rob Landley3b890392006-05-04 20:56:43 +00001194 lp = lines.next;
1195 lnum = 1;
1196 }
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001197 else if (num > ((curNum + lastNum) / 2)) {
Rob Landley3b890392006-05-04 20:56:43 +00001198 lp = lines.prev;
1199 lnum = lastNum;
1200 }
1201
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001202 while (lnum < num) {
Rob Landley3b890392006-05-04 20:56:43 +00001203 lp = lp->next;
1204 lnum++;
1205 }
1206
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001207 while (lnum > num) {
Rob Landley3b890392006-05-04 20:56:43 +00001208 lp = lp->prev;
1209 lnum--;
1210 }
Rob Landley3b890392006-05-04 20:56:43 +00001211 return lp;
1212}
1213
1214
1215/*
1216 * Set the current line number.
1217 * Returns TRUE if successful.
1218 */
1219static int setCurNum(int num)
1220{
1221 LINE *lp;
1222
1223 lp = findLine(num);
1224
1225 if (lp == NULL)
1226 return FALSE;
1227
1228 curNum = num;
1229 curLine = lp;
Rob Landley3b890392006-05-04 20:56:43 +00001230 return TRUE;
1231}