|
|
1.1 root 1: /*
2: * Copyright (c) 1980 Regents of the University of California.
3: * All rights reserved. The Berkeley software License Agreement
4: * specifies the terms and conditions for redistribution.
5: */
6:
7: #ifndef lint
8: static char *sccsid = "@(#)ex_vops2.c 6.8 (Berkeley) 6/7/85";
9: #endif not lint
10:
11: #include "ex.h"
12: #include "ex_tty.h"
13: #include "ex_vis.h"
14:
15: /*
16: * Low level routines for operations sequences,
17: * and mostly, insert mode (and a subroutine
18: * to read an input line, including in the echo area.)
19: */
20: extern char *vUA1, *vUA2; /* mjm: extern; also in ex_vops.c */
21: extern char *vUD1, *vUD2; /* mjm: extern; also in ex_vops.c */
22:
23: /*
24: * Obleeperate characters in hardcopy
25: * open with \'s.
26: */
27: bleep(i, cp)
28: register int i;
29: char *cp;
30: {
31:
32: i -= column(cp);
33: do
34: putchar('\\' | QUOTE);
35: while (--i >= 0);
36: rubble = 1;
37: }
38:
39: /*
40: * Common code for middle part of delete
41: * and change operating on parts of lines.
42: */
43: vdcMID()
44: {
45: register char *cp;
46:
47: squish();
48: setLAST();
49: if (FIXUNDO)
50: vundkind = VCHNG, CP(vutmp, linebuf);
51: if (wcursor < cursor)
52: cp = wcursor, wcursor = cursor, cursor = cp;
53: vUD1 = vUA1 = vUA2 = cursor; vUD2 = wcursor;
54: return (column(wcursor - 1));
55: }
56:
57: /*
58: * Take text from linebuf and stick it
59: * in the VBSIZE buffer BUF. Used to save
60: * deleted text of part of line.
61: */
62: takeout(BUF)
63: char *BUF;
64: {
65: register char *cp;
66:
67: if (wcursor < linebuf)
68: wcursor = linebuf;
69: if (cursor == wcursor) {
70: beep();
71: return;
72: }
73: if (wcursor < cursor) {
74: cp = wcursor;
75: wcursor = cursor;
76: cursor = cp;
77: }
78: setBUF(BUF);
79: if ((BUF[0] & (QUOTE|TRIM)) == OVERBUF)
80: beep();
81: }
82:
83: /*
84: * Are we at the end of the printed representation of the
85: * line? Used internally in hardcopy open.
86: */
87: ateopr()
88: {
89: register int i, c;
90: register char *cp = vtube[destline] + destcol;
91:
92: for (i = WCOLS - destcol; i > 0; i--) {
93: c = *cp++;
94: if (c == 0)
95: return (1);
96: if (c != ' ' && (c & QUOTE) == 0)
97: return (0);
98: }
99: return (1);
100: }
101:
102: /*
103: * Append.
104: *
105: * This routine handles the top level append, doing work
106: * as each new line comes in, and arranging repeatability.
107: * It also handles append with repeat counts, and calculation
108: * of autoindents for new lines.
109: */
110: bool vaifirst;
111: bool gobbled;
112: char *ogcursor;
113:
114: vappend(ch, cnt, indent)
115: int ch; /* mjm: char --> int */
116: int cnt, indent;
117: {
118: register int i;
119: register char *gcursor;
120: bool escape;
121: int repcnt, savedoomed;
122: short oldhold = hold;
123: int oldmask;
124:
125: /*
126: * Before a move in hardopen when the line is dirty
127: * or we are in the middle of the printed representation,
128: * we retype the line to the left of the cursor so the
129: * insert looks clean.
130: */
131: if (ch != 'o' && state == HARDOPEN && (rubble || !ateopr())) {
132: rubble = 1;
133: gcursor = cursor;
134: i = *gcursor;
135: *gcursor = ' ';
136: wcursor = gcursor;
137: vmove();
138: *gcursor = i;
139: }
140: vaifirst = indent == 0;
141:
142: /*
143: * Handle replace character by (eventually)
144: * limiting the number of input characters allowed
145: * in the vgetline routine.
146: */
147: if (ch == 'r')
148: repcnt = 2;
149: else
150: repcnt = 0;
151:
152: /*
153: * If an autoindent is specified, then
154: * generate a mixture of blanks to tabs to implement
155: * it and place the cursor after the indent.
156: * Text read by the vgetline routine will be placed in genbuf,
157: * so the indent is generated there.
158: */
159: if (value(AUTOINDENT) && indent != 0) {
160: gcursor = genindent(indent);
161: *gcursor = 0;
162: vgotoCL(qcolumn(cursor - 1, genbuf));
163: } else {
164: gcursor = genbuf;
165: *gcursor = 0;
166: if (ch == 'o')
167: vfixcurs();
168: }
169:
170: /*
171: * Prepare for undo. Pointers delimit inserted portion of line.
172: */
173: vUA1 = vUA2 = cursor;
174:
175: /*
176: * If we are not in a repeated command and a ^@ comes in
177: * then this means the previous inserted text.
178: * If there is none or it was too long to be saved,
179: * then beep() and also arrange to undo any damage done
180: * so far (e.g. if we are a change.)
181: */
182: if ((vglobp && *vglobp == 0) || peekbr()) {
183: if ((INS[0] & (QUOTE|TRIM)) == OVERBUF) {
184: beep();
185: if (!splitw)
186: ungetkey('u');
187: doomed = 0;
188: hold = oldhold;
189: return;
190: }
191: /*
192: * Unread input from INS.
193: * An escape will be generated at end of string.
194: * Hold off n^^2 type update on dumb terminals.
195: */
196: vglobp = INS;
197: hold |= HOLDQIK;
198: } else if (vglobp == 0)
199: /*
200: * Not a repeated command, get
201: * a new inserted text for repeat.
202: */
203: INS[0] = 0;
204:
205: /*
206: * For wrapmargin to hack away second space after a '.'
207: * when the first space caused a line break we keep
208: * track that this happened in gobblebl, which says
209: * to gobble up a blank silently.
210: */
211: gobblebl = 0;
212:
213: oldmask = sigblock(sigmask(SIGWINCH));
214: /*
215: * Text gathering loop.
216: * New text goes into genbuf starting at gcursor.
217: * cursor preserves place in linebuf where text will eventually go.
218: */
219: if (*cursor == 0 || state == CRTOPEN)
220: hold |= HOLDROL;
221: for (;;) {
222: if (ch == 'r' && repcnt == 0)
223: escape = 0;
224: else {
225: gcursor = vgetline(repcnt, gcursor, &escape, ch);
226:
227: /*
228: * After an append, stick information
229: * about the ^D's and ^^D's and 0^D's in
230: * the repeated text buffer so repeated
231: * inserts of stuff indented with ^D as backtab's
232: * can work.
233: */
234: if (HADUP)
235: addtext("^");
236: else if (HADZERO)
237: addtext("0");
238: while (CDCNT > 0)
239: addtext("\204"), CDCNT--;
240: if (gobbled)
241: addtext(" ");
242: addtext(ogcursor);
243: }
244: repcnt = 0;
245:
246: /*
247: * Smash the generated and preexisting indents together
248: * and generate one cleanly made out of tabs and spaces
249: * if we are using autoindent.
250: */
251: if (!vaifirst && value(AUTOINDENT)) {
252: i = fixindent(indent);
253: if (!HADUP)
254: indent = i;
255: gcursor = strend(genbuf);
256: }
257:
258: /*
259: * Limit the repetition count based on maximum
260: * possible line length; do output implied
261: * by further count (> 1) and cons up the new line
262: * in linebuf.
263: */
264: cnt = vmaxrep(ch, cnt);
265: CP(gcursor + 1, cursor);
266: do {
267: CP(cursor, genbuf);
268: if (cnt > 1) {
269: int oldhold = hold;
270:
271: Outchar = vinschar;
272: hold |= HOLDQIK;
273: printf("%s", genbuf);
274: hold = oldhold;
275: Outchar = vputchar;
276: }
277: cursor += gcursor - genbuf;
278: } while (--cnt > 0);
279: endim();
280: vUA2 = cursor;
281: if (escape != '\n')
282: CP(cursor, gcursor + 1);
283:
284: /*
285: * If doomed characters remain, clobber them,
286: * and reopen the line to get the display exact.
287: */
288: if (state != HARDOPEN) {
289: DEPTH(vcline) = 0;
290: savedoomed = doomed;
291: if (doomed > 0) {
292: register int cind = cindent();
293:
294: physdc(cind, cind + doomed);
295: doomed = 0;
296: }
297: i = vreopen(LINE(vcline), lineDOT(), vcline);
298: #ifdef TRACE
299: if (trace)
300: fprintf(trace, "restoring doomed from %d to %d\n", doomed, savedoomed);
301: #endif
302: if (ch == 'R')
303: doomed = savedoomed;
304: }
305:
306: /*
307: * All done unless we are continuing on to another line.
308: */
309: if (escape != '\n')
310: break;
311:
312: /*
313: * Set up for the new line.
314: * First save the current line, then construct a new
315: * first image for the continuation line consisting
316: * of any new autoindent plus the pushed ahead text.
317: */
318: killU();
319: addtext(gobblebl ? " " : "\n");
320: vsave();
321: cnt = 1;
322: if (value(AUTOINDENT)) {
323: #ifdef LISPCODE
324: if (value(LISP))
325: indent = lindent(dot + 1);
326: else
327: #endif
328: if (!HADUP && vaifirst)
329: indent = whitecnt(linebuf);
330: vaifirst = 0;
331: strcLIN(vpastwh(gcursor + 1));
332: gcursor = genindent(indent);
333: *gcursor = 0;
334: if (gcursor + strlen(linebuf) > &genbuf[LBSIZE - 2])
335: gcursor = genbuf;
336: CP(gcursor, linebuf);
337: } else {
338: CP(genbuf, gcursor + 1);
339: gcursor = genbuf;
340: }
341:
342: /*
343: * If we started out as a single line operation and are now
344: * turning into a multi-line change, then we had better yank
345: * out dot before it changes so that undo will work
346: * correctly later.
347: */
348: if (FIXUNDO && vundkind == VCHNG) {
349: vremote(1, yank, 0);
350: undap1--;
351: }
352:
353: /*
354: * Now do the append of the new line in the buffer,
355: * and update the display. If slowopen
356: * we don't do very much.
357: */
358: vdoappend(genbuf);
359: vundkind = VMANYINS;
360: vcline++;
361: if (state != VISUAL)
362: vshow(dot, NOLINE);
363: else {
364: i += LINE(vcline - 1);
365: vopen(dot, i);
366: if (value(SLOWOPEN))
367: vscrap();
368: else
369: vsync1(LINE(vcline));
370: }
371: strcLIN(gcursor);
372: *gcursor = 0;
373: cursor = linebuf;
374: vgotoCL(qcolumn(cursor - 1, genbuf));
375: }
376:
377: /*
378: * All done with insertion, position the cursor
379: * and sync the screen.
380: */
381: hold = oldhold;
382: if (cursor > linebuf)
383: cursor--;
384: if (state != HARDOPEN)
385: vsyncCL();
386: else if (cursor > linebuf)
387: back1();
388: doomed = 0;
389: wcursor = cursor;
390: vmove();
391: (void)sigsetmask(oldmask);
392: }
393:
394: /*
395: * Subroutine for vgetline to back up a single character position,
396: * backwards around end of lines (vgoto can't hack columns which are
397: * less than 0 in general).
398: */
399: back1()
400: {
401:
402: vgoto(destline - 1, WCOLS + destcol - 1);
403: }
404:
405: /*
406: * Get a line into genbuf after gcursor.
407: * Cnt limits the number of input characters
408: * accepted and is used for handling the replace
409: * single character command. Aescaped is the location
410: * where we stick a termination indicator (whether we
411: * ended with an ESCAPE or a newline/return.
412: *
413: * We do erase-kill type processing here and also
414: * are careful about the way we do this so that it is
415: * repeatable. (I.e. so that your kill doesn't happen,
416: * when you repeat an insert if it was escaped with \ the
417: * first time you did it. commch is the command character
418: * involved, including the prompt for readline.
419: */
420: char *
421: vgetline(cnt, gcursor, aescaped, commch)
422: int cnt;
423: register char *gcursor;
424: bool *aescaped;
425: char commch;
426: {
427: register int c, ch;
428: register char *cp;
429: int x, y, iwhite, backsl=0;
430: char *iglobp;
431: char cstr[2];
432: int (*OO)() = Outchar;
433:
434: /*
435: * Clear the output state and counters
436: * for autoindent backwards motion (counts of ^D, etc.)
437: * Remember how much white space at beginning of line so
438: * as not to allow backspace over autoindent.
439: */
440: *aescaped = 0;
441: ogcursor = gcursor;
442: flusho();
443: CDCNT = 0;
444: HADUP = 0;
445: HADZERO = 0;
446: gobbled = 0;
447: iwhite = whitecnt(genbuf);
448: iglobp = vglobp;
449:
450: /*
451: * Carefully avoid using vinschar in the echo area.
452: */
453: if (splitw)
454: Outchar = vputchar;
455: else {
456: Outchar = vinschar;
457: vprepins();
458: }
459: for (;;) {
460: backsl = 0;
461: if (gobblebl)
462: gobblebl--;
463: if (cnt != 0) {
464: cnt--;
465: if (cnt == 0)
466: goto vadone;
467: }
468: c = getkey();
469: if (c != ATTN)
470: c &= (QUOTE|TRIM);
471: ch = c;
472: maphopcnt = 0;
473: if (vglobp == 0 && Peekkey == 0 && commch != 'r')
474: while ((ch = map(c, immacs)) != c) {
475: c = ch;
476: if (!value(REMAP))
477: break;
478: if (++maphopcnt > 256)
479: error("Infinite macro loop");
480: }
481: if (!iglobp) {
482:
483: /*
484: * Erase-kill type processing.
485: * Only happens if we were not reading
486: * from untyped input when we started.
487: * Map users erase to ^H, kill to -1 for switch.
488: */
489: #ifndef USG3TTY
490: if (c == tty.sg_erase)
491: c = CTRL(h);
492: else if (c == tty.sg_kill)
493: c = -1;
494: #else
495: if (c == tty.c_cc[VERASE])
496: c = CTRL(h);
497: else if (c == tty.c_cc[VKILL])
498: c = -1;
499: #endif
500: switch (c) {
501:
502: /*
503: * ^? Interrupt drops you back to visual
504: * command mode with an unread interrupt
505: * still in the input buffer.
506: *
507: * ^\ Quit does the same as interrupt.
508: * If you are a ex command rather than
509: * a vi command this will drop you
510: * back to command mode for sure.
511: */
512: case ATTN:
513: case QUIT:
514: ungetkey(c);
515: goto vadone;
516:
517: /*
518: * ^H Backs up a character in the input.
519: *
520: * BUG: Can't back around line boundaries.
521: * This is hard because stuff has
522: * already been saved for repeat.
523: */
524: case CTRL(h):
525: bakchar:
526: cp = gcursor - 1;
527: if (cp < ogcursor) {
528: if (splitw) {
529: /*
530: * Backspacing over readecho
531: * prompt. Pretend delete but
532: * don't beep.
533: */
534: ungetkey(c);
535: goto vadone;
536: }
537: beep();
538: continue;
539: }
540: goto vbackup;
541:
542: /*
543: * ^W Back up a white/non-white word.
544: */
545: case CTRL(w):
546: wdkind = 1;
547: for (cp = gcursor; cp > ogcursor && isspace(cp[-1]); cp--)
548: continue;
549: for (c = wordch(cp - 1);
550: cp > ogcursor && wordof(c, cp - 1); cp--)
551: continue;
552: goto vbackup;
553:
554: /*
555: * users kill Kill input on this line, back to
556: * the autoindent.
557: */
558: case -1:
559: cp = ogcursor;
560: vbackup:
561: if (cp == gcursor) {
562: beep();
563: continue;
564: }
565: endim();
566: *cp = 0;
567: c = cindent();
568: vgotoCL(qcolumn(cursor - 1, genbuf));
569: if (doomed >= 0)
570: doomed += c - cindent();
571: gcursor = cp;
572: continue;
573:
574: /*
575: * \ Followed by erase or kill
576: * maps to just the erase or kill.
577: */
578: case '\\':
579: x = destcol, y = destline;
580: putchar('\\');
581: vcsync();
582: c = getkey();
583: #ifndef USG3TTY
584: if (c == tty.sg_erase || c == tty.sg_kill)
585: #else
586: if (c == tty.c_cc[VERASE]
587: || c == tty.c_cc[VKILL])
588: #endif
589: {
590: vgoto(y, x);
591: if (doomed >= 0)
592: doomed++;
593: goto def;
594: }
595: ungetkey(c), c = '\\';
596: backsl = 1;
597: break;
598:
599: /*
600: * ^Q Super quote following character
601: * Only ^@ is verboten (trapped at
602: * a lower level) and \n forces a line
603: * split so doesn't really go in.
604: *
605: * ^V Synonym for ^Q
606: */
607: case CTRL(q):
608: case CTRL(v):
609: x = destcol, y = destline;
610: putchar('^');
611: vgoto(y, x);
612: c = getkey();
613: #ifdef TIOCSETC
614: if (c == ATTN)
615: c = nttyc.t_intrc;
616: #endif
617: if (c != NL) {
618: if (doomed >= 0)
619: doomed++;
620: goto def;
621: }
622: break;
623: }
624: }
625:
626: /*
627: * If we get a blank not in the echo area
628: * consider splitting the window in the wrapmargin.
629: */
630: if (c != NL && !splitw) {
631: if (c == ' ' && gobblebl) {
632: gobbled = 1;
633: continue;
634: }
635: if (value(WRAPMARGIN) &&
636: (outcol >= OCOLUMNS - value(WRAPMARGIN) ||
637: backsl && outcol==0) &&
638: commch != 'r') {
639: /*
640: * At end of word and hit wrapmargin.
641: * Move the word to next line and keep going.
642: */
643: wdkind = 1;
644: *gcursor++ = c;
645: if (backsl)
646: *gcursor++ = getkey();
647: *gcursor = 0;
648: /*
649: * Find end of previous word if we are past it.
650: */
651: for (cp=gcursor; cp>ogcursor && isspace(cp[-1]); cp--)
652: ;
653: if (outcol+(backsl?OCOLUMNS:0) - (gcursor-cp) >= OCOLUMNS - value(WRAPMARGIN)) {
654: /*
655: * Find beginning of previous word.
656: */
657: for (; cp>ogcursor && !isspace(cp[-1]); cp--)
658: ;
659: if (cp <= ogcursor) {
660: /*
661: * There is a single word that
662: * is too long to fit. Just
663: * let it pass, but beep for
664: * each new letter to warn
665: * the luser.
666: */
667: c = *--gcursor;
668: *gcursor = 0;
669: beep();
670: goto dontbreak;
671: }
672: /*
673: * Save it for next line.
674: */
675: macpush(cp, 0);
676: cp--;
677: }
678: macpush("\n", 0);
679: /*
680: * Erase white space before the word.
681: */
682: while (cp > ogcursor && isspace(cp[-1]))
683: cp--; /* skip blank */
684: gobblebl = 3;
685: goto vbackup;
686: }
687: dontbreak:;
688: }
689:
690: /*
691: * Word abbreviation mode.
692: */
693: cstr[0] = c;
694: if (anyabbrs && gcursor > ogcursor && !wordch(cstr) && wordch(gcursor-1)) {
695: int wdtype, abno;
696:
697: cstr[1] = 0;
698: wdkind = 1;
699: cp = gcursor - 1;
700: for (wdtype = wordch(cp - 1);
701: cp > ogcursor && wordof(wdtype, cp - 1); cp--)
702: ;
703: *gcursor = 0;
704: for (abno=0; abbrevs[abno].mapto; abno++) {
705: if (eq(cp, abbrevs[abno].cap)) {
706: macpush(cstr, 0);
707: macpush(abbrevs[abno].mapto);
708: goto vbackup;
709: }
710: }
711: }
712:
713: switch (c) {
714:
715: /*
716: * ^M Except in repeat maps to \n.
717: */
718: case CR:
719: if (vglobp)
720: goto def;
721: c = '\n';
722: /* presto chango ... */
723:
724: /*
725: * \n Start new line.
726: */
727: case NL:
728: *aescaped = c;
729: goto vadone;
730:
731: /*
732: * escape End insert unless repeat and more to repeat.
733: */
734: case ESCAPE:
735: if (lastvgk)
736: goto def;
737: goto vadone;
738:
739: /*
740: * ^D Backtab.
741: * ^T Software forward tab.
742: *
743: * Unless in repeat where this means these
744: * were superquoted in.
745: */
746: case CTRL(d):
747: case CTRL(t):
748: if (vglobp)
749: goto def;
750: /* fall into ... */
751:
752: /*
753: * ^D|QUOTE Is a backtab (in a repeated command).
754: */
755: case CTRL(d) | QUOTE:
756: *gcursor = 0;
757: cp = vpastwh(genbuf);
758: c = whitecnt(genbuf);
759: if (ch == CTRL(t)) {
760: /*
761: * ^t just generates new indent replacing
762: * current white space rounded up to soft
763: * tab stop increment.
764: */
765: if (cp != gcursor)
766: /*
767: * BUG: Don't hack ^T except
768: * right after initial
769: * white space.
770: */
771: continue;
772: cp = genindent(iwhite = backtab(c + value(SHIFTWIDTH) + 1));
773: ogcursor = cp;
774: goto vbackup;
775: }
776: /*
777: * ^D works only if we are at the (end of) the
778: * generated autoindent. We count the ^D for repeat
779: * purposes.
780: */
781: if (c == iwhite && c != 0)
782: if (cp == gcursor) {
783: iwhite = backtab(c);
784: CDCNT++;
785: ogcursor = cp = genindent(iwhite);
786: goto vbackup;
787: } else if (&cp[1] == gcursor &&
788: (*cp == '^' || *cp == '0')) {
789: /*
790: * ^^D moves to margin, then back
791: * to current indent on next line.
792: *
793: * 0^D moves to margin and then
794: * stays there.
795: */
796: HADZERO = *cp == '0';
797: ogcursor = cp = genbuf;
798: HADUP = 1 - HADZERO;
799: CDCNT = 1;
800: endim();
801: back1();
802: vputchar(' ');
803: goto vbackup;
804: }
805: if (vglobp && vglobp - iglobp >= 2 &&
806: (vglobp[-2] == '^' || vglobp[-2] == '0')
807: && gcursor == ogcursor + 1)
808: goto bakchar;
809: continue;
810:
811: default:
812: /*
813: * Possibly discard control inputs.
814: */
815: if (!vglobp && junk(c)) {
816: beep();
817: continue;
818: }
819: def:
820: if (!backsl) {
821: int cnt;
822: putchar(c);
823: flush();
824: }
825: if (gcursor > &genbuf[LBSIZE - 2])
826: error("Line too long");
827: *gcursor++ = c & TRIM;
828: vcsync();
829: if (value(SHOWMATCH) && !iglobp)
830: if (c == ')' || c == '}')
831: lsmatch(gcursor);
832: continue;
833: }
834: }
835: vadone:
836: *gcursor = 0;
837: if (Outchar != termchar)
838: Outchar = OO;
839: endim();
840: return (gcursor);
841: }
842:
843: int vgetsplit();
844: char *vsplitpt;
845:
846: /*
847: * Append the line in buffer at lp
848: * to the buffer after dot.
849: */
850: vdoappend(lp)
851: char *lp;
852: {
853: register int oing = inglobal;
854:
855: vsplitpt = lp;
856: inglobal = 1;
857: ignore(append(vgetsplit, dot));
858: inglobal = oing;
859: }
860:
861: /*
862: * Subroutine for vdoappend to pass to append.
863: */
864: vgetsplit()
865: {
866:
867: if (vsplitpt == 0)
868: return (EOF);
869: strcLIN(vsplitpt);
870: vsplitpt = 0;
871: return (0);
872: }
873:
874: /*
875: * Vmaxrep determines the maximum repetitition factor
876: * allowed that will yield total line length less than
877: * LBSIZE characters and also does hacks for the R command.
878: */
879: vmaxrep(ch, cnt)
880: char ch;
881: register int cnt;
882: {
883: register int len, replen;
884:
885: if (cnt > LBSIZE - 2)
886: cnt = LBSIZE - 2;
887: replen = strlen(genbuf);
888: if (ch == 'R') {
889: len = strlen(cursor);
890: if (replen < len)
891: len = replen;
892: CP(cursor, cursor + len);
893: vUD2 += len;
894: }
895: len = strlen(linebuf);
896: if (len + cnt * replen <= LBSIZE - 2)
897: return (cnt);
898: cnt = (LBSIZE - 2 - len) / replen;
899: if (cnt == 0) {
900: vsave();
901: error("Line too long");
902: }
903: return (cnt);
904: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.