blob: 42adca40938e256ffc5d38225764ce8fc474ff24 [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
Denis Vlasenko06af2162007-02-03 17:28:39 +000043int ed_main(int argc, char **argv);
Rob Landley3b890392006-05-04 20:56:43 +000044int ed_main(int argc, char **argv)
45{
46 if (!initEdit())
47 return EXIT_FAILURE;
48
49 if (argc > 1) {
50 fileName = strdup(argv[1]);
51
52 if (fileName == NULL) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +000053 bb_error_msg("no memory");
Rob Landley3b890392006-05-04 20:56:43 +000054 termEdit();
55 return EXIT_SUCCESS;
56 }
57
58 if (!readLines(fileName, 1)) {
59 termEdit();
60 return EXIT_SUCCESS;
61 }
62
63 if (lastNum)
64 setCurNum(1);
65
66 dirty = FALSE;
67 }
68
69 doCommands();
70
71 termEdit();
72 return EXIT_SUCCESS;
73}
74
75/*
76 * Read commands until we are told to stop.
77 */
78static void doCommands(void)
79{
80 const char *cp;
81 char *endbuf, *newname, buf[USERSIZE];
82 int len, num1, num2, have1, have2;
83
Denis Vlasenko610c4aa2006-11-30 20:57:50 +000084 while (TRUE) {
Rob Landley3b890392006-05-04 20:56:43 +000085 printf(": ");
86 fflush(stdout);
87
88 if (fgets(buf, sizeof(buf), stdin) == NULL)
89 return;
90
91 len = strlen(buf);
92
93 if (len == 0)
94 return;
95
96 endbuf = &buf[len - 1];
97
Denis Vlasenko610c4aa2006-11-30 20:57:50 +000098 if (*endbuf != '\n') {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +000099 bb_error_msg("command line too long");
Rob Landley3b890392006-05-04 20:56:43 +0000100
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000101 do {
Rob Landley3b890392006-05-04 20:56:43 +0000102 len = fgetc(stdin);
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000103 } while ((len != EOF) && (len != '\n'));
Rob Landley3b890392006-05-04 20:56:43 +0000104
105 continue;
106 }
107
108 while ((endbuf > buf) && isblank(endbuf[-1]))
109 endbuf--;
110
111 *endbuf = '\0';
112
113 cp = buf;
114
115 while (isblank(*cp))
116 cp++;
117
118 have1 = FALSE;
119 have2 = FALSE;
120
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000121 if ((curNum == 0) && (lastNum > 0)) {
Rob Landley3b890392006-05-04 20:56:43 +0000122 curNum = 1;
123 curLine = lines.next;
124 }
125
126 if (!getNum(&cp, &have1, &num1))
127 continue;
128
129 while (isblank(*cp))
130 cp++;
131
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000132 if (*cp == ',') {
Rob Landley3b890392006-05-04 20:56:43 +0000133 cp++;
134
135 if (!getNum(&cp, &have2, &num2))
136 continue;
137
138 if (!have1)
139 num1 = 1;
140
141 if (!have2)
142 num2 = lastNum;
143
144 have1 = TRUE;
145 have2 = TRUE;
146 }
147
148 if (!have1)
149 num1 = curNum;
150
151 if (!have2)
152 num2 = num1;
153
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000154 switch (*cp++) {
Rob Landley3b890392006-05-04 20:56:43 +0000155 case 'a':
156 addLines(num1 + 1);
157 break;
158
159 case 'c':
160 deleteLines(num1, num2);
161 addLines(num1);
162 break;
163
164 case 'd':
165 deleteLines(num1, num2);
166 break;
167
168 case 'f':
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000169 if (*cp && !isblank(*cp)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000170 bb_error_msg("bad file command");
Rob Landley3b890392006-05-04 20:56:43 +0000171 break;
172 }
173
174 while (isblank(*cp))
175 cp++;
176
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000177 if (*cp == '\0') {
Rob Landley3b890392006-05-04 20:56:43 +0000178 if (fileName)
179 printf("\"%s\"\n", fileName);
180 else
181 printf("No file name\n");
Rob Landley3b890392006-05-04 20:56:43 +0000182 break;
183 }
184
185 newname = strdup(cp);
186
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000187 if (newname == NULL) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000188 bb_error_msg("no memory for file name");
Rob Landley3b890392006-05-04 20:56:43 +0000189 break;
190 }
191
192 if (fileName)
193 free(fileName);
194
195 fileName = newname;
196 break;
197
198 case 'i':
199 addLines(num1);
200 break;
201
202 case 'k':
203 while (isblank(*cp))
204 cp++;
205
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000206 if ((*cp < 'a') || (*cp > 'a') || cp[1]) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000207 bb_error_msg("bad mark name");
Rob Landley3b890392006-05-04 20:56:43 +0000208 break;
209 }
210
211 marks[*cp - 'a'] = num2;
212 break;
213
214 case 'l':
215 printLines(num1, num2, TRUE);
216 break;
217
218 case 'p':
219 printLines(num1, num2, FALSE);
220 break;
221
222 case 'q':
223 while (isblank(*cp))
224 cp++;
225
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000226 if (have1 || *cp) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000227 bb_error_msg("bad quit command");
Rob Landley3b890392006-05-04 20:56:43 +0000228 break;
229 }
230
231 if (!dirty)
232 return;
233
234 printf("Really quit? ");
235 fflush(stdout);
236
237 buf[0] = '\0';
238 fgets(buf, sizeof(buf), stdin);
239 cp = buf;
240
241 while (isblank(*cp))
242 cp++;
243
244 if ((*cp == 'y') || (*cp == 'Y'))
245 return;
246
247 break;
248
249 case 'r':
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000250 if (*cp && !isblank(*cp)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000251 bb_error_msg("bad read command");
Rob Landley3b890392006-05-04 20:56:43 +0000252 break;
253 }
254
255 while (isblank(*cp))
256 cp++;
257
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000258 if (*cp == '\0') {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000259 bb_error_msg("no file name");
Rob Landley3b890392006-05-04 20:56:43 +0000260 break;
261 }
262
263 if (!have1)
264 num1 = lastNum;
265
266 if (readLines(cp, num1 + 1))
267 break;
268
269 if (fileName == NULL)
270 fileName = strdup(cp);
271
272 break;
273
274 case 's':
275 subCommand(cp, num1, num2);
276 break;
277
278 case 'w':
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000279 if (*cp && !isblank(*cp)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000280 bb_error_msg("bad write command");
Rob Landley3b890392006-05-04 20:56:43 +0000281 break;
282 }
283
284 while (isblank(*cp))
285 cp++;
286
287 if (!have1) {
288 num1 = 1;
289 num2 = lastNum;
290 }
291
292 if (*cp == '\0')
293 cp = fileName;
294
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000295 if (cp == NULL) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000296 bb_error_msg("no file name specified");
Rob Landley3b890392006-05-04 20:56:43 +0000297 break;
298 }
Bernhard Reutner-Fischerd9ed35c2006-05-19 12:38:30 +0000299
Rob Landley3b890392006-05-04 20:56:43 +0000300 writeLines(cp, num1, num2);
301 break;
302
303 case 'z':
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000304 switch (*cp) {
Rob Landley3b890392006-05-04 20:56:43 +0000305 case '-':
306 printLines(curNum-21, curNum, FALSE);
307 break;
308 case '.':
309 printLines(curNum-11, curNum+10, FALSE);
310 break;
311 default:
312 printLines(curNum, curNum+21, FALSE);
313 break;
314 }
315 break;
316
317 case '.':
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000318 if (have1) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000319 bb_error_msg("no arguments allowed");
Rob Landley3b890392006-05-04 20:56:43 +0000320 break;
321 }
322
323 printLines(curNum, curNum, FALSE);
324 break;
Bernhard Reutner-Fischerd9ed35c2006-05-19 12:38:30 +0000325
Rob Landley3b890392006-05-04 20:56:43 +0000326 case '-':
327 if (setCurNum(curNum - 1))
328 printLines(curNum, curNum, FALSE);
329
330 break;
331
332 case '=':
333 printf("%d\n", num1);
334 break;
335
336 case '\0':
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000337 if (have1) {
Rob Landley3b890392006-05-04 20:56:43 +0000338 printLines(num2, num2, FALSE);
339 break;
340 }
341
342 if (setCurNum(curNum + 1))
343 printLines(curNum, curNum, FALSE);
344
345 break;
346
347 default:
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000348 bb_error_msg("unimplemented command");
Rob Landley3b890392006-05-04 20:56:43 +0000349 break;
350 }
351 }
352}
353
354
355/*
356 * Do the substitute command.
357 * The current line is set to the last substitution done.
358 */
359static void subCommand(const char * cmd, int num1, int num2)
360{
361 char *cp, *oldStr, *newStr, buf[USERSIZE];
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000362 int delim, oldLen, newLen, deltaLen, offset;
Rob Landley3b890392006-05-04 20:56:43 +0000363 LINE *lp, *nlp;
364 int globalFlag, printFlag, didSub, needPrint;
365
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000366 if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000367 bb_error_msg("bad line range for substitute");
Rob Landley3b890392006-05-04 20:56:43 +0000368 return;
369 }
370
371 globalFlag = FALSE;
372 printFlag = FALSE;
373 didSub = FALSE;
374 needPrint = FALSE;
375
376 /*
377 * Copy the command so we can modify it.
378 */
379 strcpy(buf, cmd);
380 cp = buf;
381
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000382 if (isblank(*cp) || (*cp == '\0')) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000383 bb_error_msg("bad delimiter for substitute");
Rob Landley3b890392006-05-04 20:56:43 +0000384 return;
385 }
386
387 delim = *cp++;
388 oldStr = cp;
389
390 cp = strchr(cp, delim);
391
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000392 if (cp == NULL) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000393 bb_error_msg("missing 2nd delimiter for substitute");
Rob Landley3b890392006-05-04 20:56:43 +0000394 return;
395 }
396
397 *cp++ = '\0';
398
399 newStr = cp;
400 cp = strchr(cp, delim);
401
402 if (cp)
403 *cp++ = '\0';
404 else
Denis Vlasenkoa41fdf32007-01-29 22:51:00 +0000405 cp = (char*)"";
Rob Landley3b890392006-05-04 20:56:43 +0000406
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000407 while (*cp) switch (*cp++) {
Rob Landley3b890392006-05-04 20:56:43 +0000408 case 'g':
409 globalFlag = TRUE;
410 break;
411
412 case 'p':
413 printFlag = TRUE;
414 break;
415
416 default:
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000417 bb_error_msg("unknown option for substitute");
Rob Landley3b890392006-05-04 20:56:43 +0000418 return;
419 }
420
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000421 if (*oldStr == '\0') {
422 if (searchString[0] == '\0') {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000423 bb_error_msg("no previous search string");
Rob Landley3b890392006-05-04 20:56:43 +0000424 return;
425 }
426
427 oldStr = searchString;
428 }
429
430 if (oldStr != searchString)
431 strcpy(searchString, oldStr);
432
433 lp = findLine(num1);
434
435 if (lp == NULL)
436 return;
437
438 oldLen = strlen(oldStr);
439 newLen = strlen(newStr);
440 deltaLen = newLen - oldLen;
441 offset = 0;
442 nlp = NULL;
443
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000444 while (num1 <= num2) {
Rob Landley3b890392006-05-04 20:56:43 +0000445 offset = findString(lp, oldStr, oldLen, offset);
446
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000447 if (offset < 0) {
448 if (needPrint) {
Rob Landley3b890392006-05-04 20:56:43 +0000449 printLines(num1, num1, FALSE);
450 needPrint = FALSE;
451 }
452
453 offset = 0;
454 lp = lp->next;
455 num1++;
456
457 continue;
458 }
459
460 needPrint = printFlag;
461 didSub = TRUE;
462 dirty = TRUE;
463
464 /*
465 * If the replacement string is the same size or shorter
466 * than the old string, then the substitution is easy.
467 */
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000468 if (deltaLen <= 0) {
Rob Landley3b890392006-05-04 20:56:43 +0000469 memcpy(&lp->data[offset], newStr, newLen);
470
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000471 if (deltaLen) {
Rob Landley3b890392006-05-04 20:56:43 +0000472 memcpy(&lp->data[offset + newLen],
473 &lp->data[offset + oldLen],
474 lp->len - offset - oldLen);
475
476 lp->len += deltaLen;
477 }
478
479 offset += newLen;
480
481 if (globalFlag)
482 continue;
483
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000484 if (needPrint) {
Rob Landley3b890392006-05-04 20:56:43 +0000485 printLines(num1, num1, FALSE);
486 needPrint = FALSE;
487 }
488
489 lp = lp->next;
490 num1++;
491
492 continue;
493 }
494
495 /*
496 * The new string is larger, so allocate a new line
497 * structure and use that. Link it in in place of
498 * the old line structure.
499 */
500 nlp = (LINE *) malloc(sizeof(LINE) + lp->len + deltaLen);
501
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000502 if (nlp == NULL) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000503 bb_error_msg("cannot get memory for line");
Rob Landley3b890392006-05-04 20:56:43 +0000504 return;
505 }
506
507 nlp->len = lp->len + deltaLen;
508
509 memcpy(nlp->data, lp->data, offset);
510
511 memcpy(&nlp->data[offset], newStr, newLen);
512
513 memcpy(&nlp->data[offset + newLen],
514 &lp->data[offset + oldLen],
515 lp->len - offset - oldLen);
516
517 nlp->next = lp->next;
518 nlp->prev = lp->prev;
519 nlp->prev->next = nlp;
520 nlp->next->prev = nlp;
521
522 if (curLine == lp)
523 curLine = nlp;
524
525 free(lp);
526 lp = nlp;
527
528 offset += newLen;
529
530 if (globalFlag)
531 continue;
532
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000533 if (needPrint) {
Rob Landley3b890392006-05-04 20:56:43 +0000534 printLines(num1, num1, FALSE);
535 needPrint = FALSE;
536 }
537
538 lp = lp->next;
539 num1++;
540 }
541
542 if (!didSub)
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000543 bb_error_msg("no substitutions found for \"%s\"", oldStr);
Rob Landley3b890392006-05-04 20:56:43 +0000544}
545
546
547/*
548 * Search a line for the specified string starting at the specified
549 * offset in the line. Returns the offset of the found string, or -1.
550 */
551static int findString( const LINE * lp, const char * str, int len, int offset)
552{
553 int left;
554 const char *cp, *ncp;
555
556 cp = &lp->data[offset];
557 left = lp->len - offset;
558
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000559 while (left >= len) {
Rob Landley3b890392006-05-04 20:56:43 +0000560 ncp = memchr(cp, *str, left);
561
562 if (ncp == NULL)
563 return -1;
564
565 left -= (ncp - cp);
566
567 if (left < len)
568 return -1;
569
570 cp = ncp;
571
572 if (memcmp(cp, str, len) == 0)
573 return (cp - lp->data);
574
575 cp++;
576 left--;
577 }
578
579 return -1;
580}
581
582
583/*
584 * Add lines which are typed in by the user.
585 * The lines are inserted just before the specified line number.
586 * The lines are terminated by a line containing a single dot (ugly!),
587 * or by an end of file.
588 */
589static void addLines(int num)
590{
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000591 int len;
592 char buf[USERSIZE + 1];
Rob Landley3b890392006-05-04 20:56:43 +0000593
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000594 while (fgets(buf, sizeof(buf), stdin)) {
Rob Landley3b890392006-05-04 20:56:43 +0000595 if ((buf[0] == '.') && (buf[1] == '\n') && (buf[2] == '\0'))
596 return;
597
598 len = strlen(buf);
599
600 if (len == 0)
601 return;
602
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000603 if (buf[len - 1] != '\n') {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000604 bb_error_msg("line too long");
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000605 do {
Rob Landley3b890392006-05-04 20:56:43 +0000606 len = fgetc(stdin);
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000607 } while ((len != EOF) && (len != '\n'));
Rob Landley3b890392006-05-04 20:56:43 +0000608 return;
609 }
610
611 if (!insertLine(num++, buf, len))
612 return;
613 }
614}
615
616
617/*
618 * Parse a line number argument if it is present. This is a sum
619 * or difference of numbers, '.', '$', 'x, or a search string.
Bernhard Reutner-Fischerd9ed35c2006-05-19 12:38:30 +0000620 * Returns TRUE if successful (whether or not there was a number).
Rob Landley3b890392006-05-04 20:56:43 +0000621 * Returns FALSE if there was a parsing error, with a message output.
622 * Whether there was a number is returned indirectly, as is the number.
623 * The character pointer which stopped the scan is also returned.
624 */
625static int getNum(const char **retcp, int *retHaveNum, int *retNum)
626{
627 const char *cp;
628 char *endStr, str[USERSIZE];
629 int haveNum, value, num, sign;
630
631 cp = *retcp;
632 haveNum = FALSE;
633 value = 0;
634 sign = 1;
635
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000636 while (TRUE) {
Rob Landley3b890392006-05-04 20:56:43 +0000637 while (isblank(*cp))
638 cp++;
639
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000640 switch (*cp) {
Rob Landley3b890392006-05-04 20:56:43 +0000641 case '.':
642 haveNum = TRUE;
643 num = curNum;
644 cp++;
645 break;
646
647 case '$':
648 haveNum = TRUE;
649 num = lastNum;
650 cp++;
651 break;
652
653 case '\'':
654 cp++;
655
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000656 if ((*cp < 'a') || (*cp > 'z')) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000657 bb_error_msg("bad mark name");
Rob Landley3b890392006-05-04 20:56:43 +0000658 return FALSE;
659 }
660
661 haveNum = TRUE;
662 num = marks[*cp++ - 'a'];
663 break;
664
665 case '/':
666 strcpy(str, ++cp);
667 endStr = strchr(str, '/');
668
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000669 if (endStr) {
Rob Landley3b890392006-05-04 20:56:43 +0000670 *endStr++ = '\0';
671 cp += (endStr - str);
672 }
673 else
674 cp = "";
675
676 num = searchLines(str, curNum, lastNum);
677
678 if (num == 0)
679 return FALSE;
680
681 haveNum = TRUE;
682 break;
683
684 default:
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000685 if (!isdigit(*cp)) {
Rob Landley3b890392006-05-04 20:56:43 +0000686 *retcp = cp;
687 *retHaveNum = haveNum;
688 *retNum = value;
Rob Landley3b890392006-05-04 20:56:43 +0000689 return TRUE;
690 }
691
692 num = 0;
693
694 while (isdigit(*cp))
695 num = num * 10 + *cp++ - '0';
696
697 haveNum = TRUE;
698 break;
699 }
700
701 value += num * sign;
702
703 while (isblank(*cp))
704 cp++;
705
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000706 switch (*cp) {
Rob Landley3b890392006-05-04 20:56:43 +0000707 case '-':
708 sign = -1;
709 cp++;
710 break;
711
712 case '+':
713 sign = 1;
714 cp++;
715 break;
716
717 default:
718 *retcp = cp;
719 *retHaveNum = haveNum;
720 *retNum = value;
Rob Landley3b890392006-05-04 20:56:43 +0000721 return TRUE;
722 }
723 }
724}
725
726
727/*
728 * Initialize everything for editing.
729 */
730static int initEdit(void)
731{
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000732 int i;
Rob Landley3b890392006-05-04 20:56:43 +0000733
734 bufSize = INITBUF_SIZE;
735 bufBase = malloc(bufSize);
736
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000737 if (bufBase == NULL) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000738 bb_error_msg("no memory for buffer");
Rob Landley3b890392006-05-04 20:56:43 +0000739 return FALSE;
740 }
741
742 bufPtr = bufBase;
743 bufUsed = 0;
744
745 lines.next = &lines;
746 lines.prev = &lines;
747
748 curLine = NULL;
749 curNum = 0;
750 lastNum = 0;
751 dirty = FALSE;
752 fileName = NULL;
753 searchString[0] = '\0';
754
755 for (i = 0; i < 26; i++)
756 marks[i] = 0;
757
758 return TRUE;
759}
760
761
762/*
763 * Finish editing.
764 */
765static void termEdit(void)
766{
767 if (bufBase)
768 free(bufBase);
769
770 bufBase = NULL;
771 bufPtr = NULL;
772 bufSize = 0;
773 bufUsed = 0;
774
775 if (fileName)
776 free(fileName);
777
778 fileName = NULL;
779
780 searchString[0] = '\0';
781
782 if (lastNum)
783 deleteLines(1, lastNum);
784
785 lastNum = 0;
786 curNum = 0;
787 curLine = NULL;
788}
789
790
791/*
792 * Read lines from a file at the specified line number.
793 * Returns TRUE if the file was successfully read.
794 */
795static int readLines(const char * file, int num)
796{
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000797 int fd, cc;
Rob Landley3b890392006-05-04 20:56:43 +0000798 int len, lineCount, charCount;
799 char *cp;
800
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000801 if ((num < 1) || (num > lastNum + 1)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000802 bb_error_msg("bad line for read");
Rob Landley3b890392006-05-04 20:56:43 +0000803 return FALSE;
804 }
805
806 fd = open(file, 0);
807
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000808 if (fd < 0) {
Rob Landley3b890392006-05-04 20:56:43 +0000809 perror(file);
Rob Landley3b890392006-05-04 20:56:43 +0000810 return FALSE;
811 }
812
813 bufPtr = bufBase;
814 bufUsed = 0;
815 lineCount = 0;
816 charCount = 0;
817 cc = 0;
818
819 printf("\"%s\", ", file);
820 fflush(stdout);
821
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000822 do {
Rob Landley3b890392006-05-04 20:56:43 +0000823 cp = memchr(bufPtr, '\n', bufUsed);
824
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000825 if (cp) {
Rob Landley3b890392006-05-04 20:56:43 +0000826 len = (cp - bufPtr) + 1;
827
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000828 if (!insertLine(num, bufPtr, len)) {
Rob Landley3b890392006-05-04 20:56:43 +0000829 close(fd);
Rob Landley3b890392006-05-04 20:56:43 +0000830 return FALSE;
831 }
832
833 bufPtr += len;
834 bufUsed -= len;
835 charCount += len;
836 lineCount++;
837 num++;
838
839 continue;
840 }
841
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000842 if (bufPtr != bufBase) {
Rob Landley3b890392006-05-04 20:56:43 +0000843 memcpy(bufBase, bufPtr, bufUsed);
844 bufPtr = bufBase + bufUsed;
845 }
846
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000847 if (bufUsed >= bufSize) {
Rob Landley3b890392006-05-04 20:56:43 +0000848 len = (bufSize * 3) / 2;
849 cp = realloc(bufBase, len);
850
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000851 if (cp == NULL) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000852 bb_error_msg("no memory for buffer");
Rob Landley3b890392006-05-04 20:56:43 +0000853 close(fd);
Rob Landley3b890392006-05-04 20:56:43 +0000854 return FALSE;
855 }
856
857 bufBase = cp;
858 bufPtr = bufBase + bufUsed;
859 bufSize = len;
860 }
861
862 cc = read(fd, bufPtr, bufSize - bufUsed);
863 bufUsed += cc;
864 bufPtr = bufBase;
865
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000866 } while (cc > 0);
Rob Landley3b890392006-05-04 20:56:43 +0000867
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000868 if (cc < 0) {
Rob Landley3b890392006-05-04 20:56:43 +0000869 perror(file);
870 close(fd);
Rob Landley3b890392006-05-04 20:56:43 +0000871 return FALSE;
872 }
873
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000874 if (bufUsed) {
875 if (!insertLine(num, bufPtr, bufUsed)) {
Rob Landley3b890392006-05-04 20:56:43 +0000876 close(fd);
Rob Landley3b890392006-05-04 20:56:43 +0000877 return -1;
878 }
879
880 lineCount++;
881 charCount += bufUsed;
882 }
883
884 close(fd);
885
886 printf("%d lines%s, %d chars\n", lineCount,
887 (bufUsed ? " (incomplete)" : ""), charCount);
888
889 return TRUE;
890}
891
892
893/*
894 * Write the specified lines out to the specified file.
895 * Returns TRUE if successful, or FALSE on an error with a message output.
896 */
897static int writeLines(const char * file, int num1, int num2)
898{
899 LINE *lp;
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000900 int fd, lineCount, charCount;
Rob Landley3b890392006-05-04 20:56:43 +0000901
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000902 if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000903 bb_error_msg("bad line range for write");
Rob Landley3b890392006-05-04 20:56:43 +0000904 return FALSE;
905 }
906
907 lineCount = 0;
908 charCount = 0;
909
910 fd = creat(file, 0666);
911
912 if (fd < 0) {
913 perror(file);
Rob Landley3b890392006-05-04 20:56:43 +0000914 return FALSE;
915 }
916
917 printf("\"%s\", ", file);
918 fflush(stdout);
919
920 lp = findLine(num1);
921
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000922 if (lp == NULL) {
Rob Landley3b890392006-05-04 20:56:43 +0000923 close(fd);
Rob Landley3b890392006-05-04 20:56:43 +0000924 return FALSE;
925 }
926
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000927 while (num1++ <= num2) {
928 if (write(fd, lp->data, lp->len) != lp->len) {
Rob Landley3b890392006-05-04 20:56:43 +0000929 perror(file);
930 close(fd);
Rob Landley3b890392006-05-04 20:56:43 +0000931 return FALSE;
932 }
933
934 charCount += lp->len;
935 lineCount++;
936 lp = lp->next;
937 }
938
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000939 if (close(fd) < 0) {
Rob Landley3b890392006-05-04 20:56:43 +0000940 perror(file);
Rob Landley3b890392006-05-04 20:56:43 +0000941 return FALSE;
942 }
943
944 printf("%d lines, %d chars\n", lineCount, charCount);
Rob Landley3b890392006-05-04 20:56:43 +0000945 return TRUE;
946}
947
948
949/*
950 * Print lines in a specified range.
951 * The last line printed becomes the current line.
952 * If expandFlag is TRUE, then the line is printed specially to
953 * show magic characters.
954 */
955static int printLines(int num1, int num2, int expandFlag)
956{
957 const LINE *lp;
958 const char *cp;
959 int ch, count;
960
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000961 if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +0000962 bb_error_msg("bad line range for print");
Rob Landley3b890392006-05-04 20:56:43 +0000963 return FALSE;
964 }
965
966 lp = findLine(num1);
967
968 if (lp == NULL)
969 return FALSE;
970
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000971 while (num1 <= num2) {
972 if (!expandFlag) {
Rob Landley3b890392006-05-04 20:56:43 +0000973 write(1, lp->data, lp->len);
974 setCurNum(num1++);
975 lp = lp->next;
976
977 continue;
978 }
979
980 /*
981 * Show control characters and characters with the
982 * high bit set specially.
983 */
984 cp = lp->data;
985 count = lp->len;
986
987 if ((count > 0) && (cp[count - 1] == '\n'))
988 count--;
989
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000990 while (count-- > 0) {
Rob Landley3b890392006-05-04 20:56:43 +0000991 ch = *cp++;
992
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000993 if (ch & 0x80) {
Rob Landley3b890392006-05-04 20:56:43 +0000994 fputs("M-", stdout);
995 ch &= 0x7f;
996 }
997
Denis Vlasenko610c4aa2006-11-30 20:57:50 +0000998 if (ch < ' ') {
Rob Landley3b890392006-05-04 20:56:43 +0000999 fputc('^', stdout);
1000 ch += '@';
1001 }
1002
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001003 if (ch == 0x7f) {
Rob Landley3b890392006-05-04 20:56:43 +00001004 fputc('^', stdout);
1005 ch = '?';
1006 }
1007
1008 fputc(ch, stdout);
1009 }
1010
1011 fputs("$\n", stdout);
1012
1013 setCurNum(num1++);
1014 lp = lp->next;
1015 }
1016
1017 return TRUE;
1018}
1019
1020
1021/*
1022 * Insert a new line with the specified text.
1023 * The line is inserted so as to become the specified line,
1024 * thus pushing any existing and further lines down one.
1025 * The inserted line is also set to become the current line.
1026 * Returns TRUE if successful.
1027 */
1028static int insertLine(int num, const char * data, int len)
1029{
1030 LINE *newLp, *lp;
1031
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001032 if ((num < 1) || (num > lastNum + 1)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +00001033 bb_error_msg("inserting at bad line number");
Rob Landley3b890392006-05-04 20:56:43 +00001034 return FALSE;
1035 }
1036
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001037 newLp = malloc(sizeof(LINE) + len - 1);
Rob Landley3b890392006-05-04 20:56:43 +00001038
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001039 if (newLp == NULL) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +00001040 bb_error_msg("failed to allocate memory for line");
Rob Landley3b890392006-05-04 20:56:43 +00001041 return FALSE;
1042 }
1043
1044 memcpy(newLp->data, data, len);
1045 newLp->len = len;
1046
1047 if (num > lastNum)
1048 lp = &lines;
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001049 else {
Rob Landley3b890392006-05-04 20:56:43 +00001050 lp = findLine(num);
1051
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001052 if (lp == NULL) {
Rob Landley3b890392006-05-04 20:56:43 +00001053 free((char *) newLp);
Rob Landley3b890392006-05-04 20:56:43 +00001054 return FALSE;
1055 }
1056 }
1057
1058 newLp->next = lp;
1059 newLp->prev = lp->prev;
1060 lp->prev->next = newLp;
1061 lp->prev = newLp;
1062
1063 lastNum++;
1064 dirty = TRUE;
Rob Landley3b890392006-05-04 20:56:43 +00001065 return setCurNum(num);
1066}
1067
1068
1069/*
1070 * Delete lines from the given range.
1071 */
1072static int deleteLines(int num1, int num2)
1073{
1074 LINE *lp, *nlp, *plp;
1075 int count;
1076
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001077 if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +00001078 bb_error_msg("bad line numbers for delete");
Rob Landley3b890392006-05-04 20:56:43 +00001079 return FALSE;
1080 }
1081
1082 lp = findLine(num1);
1083
1084 if (lp == NULL)
1085 return FALSE;
1086
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001087 if ((curNum >= num1) && (curNum <= num2)) {
Rob Landley3b890392006-05-04 20:56:43 +00001088 if (num2 < lastNum)
1089 setCurNum(num2 + 1);
1090 else if (num1 > 1)
1091 setCurNum(num1 - 1);
1092 else
1093 curNum = 0;
1094 }
1095
1096 count = num2 - num1 + 1;
1097
1098 if (curNum > num2)
1099 curNum -= count;
1100
1101 lastNum -= count;
1102
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001103 while (count-- > 0) {
Rob Landley3b890392006-05-04 20:56:43 +00001104 nlp = lp->next;
1105 plp = lp->prev;
1106 plp->next = nlp;
1107 nlp->prev = plp;
1108 lp->next = NULL;
1109 lp->prev = NULL;
1110 lp->len = 0;
1111 free(lp);
1112 lp = nlp;
1113 }
1114
1115 dirty = TRUE;
1116
1117 return TRUE;
1118}
1119
1120
1121/*
1122 * Search for a line which contains the specified string.
1123 * If the string is NULL, then the previously searched for string
1124 * is used. The currently searched for string is saved for future use.
1125 * Returns the line number which matches, or 0 if there was no match
1126 * with an error printed.
1127 */
1128static int searchLines(const char *str, int num1, int num2)
1129{
1130 const LINE *lp;
1131 int len;
1132
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001133 if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +00001134 bb_error_msg("bad line numbers for search");
Rob Landley3b890392006-05-04 20:56:43 +00001135 return 0;
1136 }
1137
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001138 if (*str == '\0') {
1139 if (searchString[0] == '\0') {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +00001140 bb_error_msg("no previous search string");
Rob Landley3b890392006-05-04 20:56:43 +00001141 return 0;
1142 }
1143
1144 str = searchString;
1145 }
1146
1147 if (str != searchString)
1148 strcpy(searchString, str);
1149
1150 len = strlen(str);
1151
1152 lp = findLine(num1);
1153
1154 if (lp == NULL)
1155 return 0;
1156
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001157 while (num1 <= num2) {
Rob Landley3b890392006-05-04 20:56:43 +00001158 if (findString(lp, str, len, 0) >= 0)
1159 return num1;
1160
1161 num1++;
1162 lp = lp->next;
1163 }
1164
Denis Vlasenkod3d004d2006-10-27 09:02:31 +00001165 bb_error_msg("cannot find string \"%s\"", str);
Rob Landley3b890392006-05-04 20:56:43 +00001166 return 0;
1167}
1168
1169
1170/*
1171 * Return a pointer to the specified line number.
1172 */
1173static LINE *findLine(int num)
1174{
1175 LINE *lp;
1176 int lnum;
1177
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001178 if ((num < 1) || (num > lastNum)) {
Denis Vlasenkod3d004d2006-10-27 09:02:31 +00001179 bb_error_msg("line number %d does not exist", num);
Rob Landley3b890392006-05-04 20:56:43 +00001180 return NULL;
1181 }
1182
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001183 if (curNum <= 0) {
Rob Landley3b890392006-05-04 20:56:43 +00001184 curNum = 1;
1185 curLine = lines.next;
1186 }
1187
1188 if (num == curNum)
1189 return curLine;
1190
1191 lp = curLine;
1192 lnum = curNum;
1193
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001194 if (num < (curNum / 2)) {
Rob Landley3b890392006-05-04 20:56:43 +00001195 lp = lines.next;
1196 lnum = 1;
1197 }
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001198 else if (num > ((curNum + lastNum) / 2)) {
Rob Landley3b890392006-05-04 20:56:43 +00001199 lp = lines.prev;
1200 lnum = lastNum;
1201 }
1202
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001203 while (lnum < num) {
Rob Landley3b890392006-05-04 20:56:43 +00001204 lp = lp->next;
1205 lnum++;
1206 }
1207
Denis Vlasenko610c4aa2006-11-30 20:57:50 +00001208 while (lnum > num) {
Rob Landley3b890392006-05-04 20:56:43 +00001209 lp = lp->prev;
1210 lnum--;
1211 }
Rob Landley3b890392006-05-04 20:56:43 +00001212 return lp;
1213}
1214
1215
1216/*
1217 * Set the current line number.
1218 * Returns TRUE if successful.
1219 */
1220static int setCurNum(int num)
1221{
1222 LINE *lp;
1223
1224 lp = findLine(num);
1225
1226 if (lp == NULL)
1227 return FALSE;
1228
1229 curNum = num;
1230 curLine = lp;
Rob Landley3b890392006-05-04 20:56:43 +00001231 return TRUE;
1232}