~ubuntu-branches/ubuntu/gutsy/edbrowse/gutsy

« back to all changes in this revision

Viewing changes to buffers.c

  • Committer: Bazaar Package Importer
  • Author(s): Kapil Hari Paranjape
  • Date: 2006-10-20 10:47:30 UTC
  • mfrom: (1.1.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20061020104730-o7vxbrypwaz932dt
Tags: 3.1.2-1
* New upstream version (3.1.2). Closes: #306486.
  - programs now written in C
  - support for javascript.
* debian/control:
  - added Kapil Hari Paranjape to Uploaders.
  - Build-depends on "libssl-dev", "libmozjs-dev", "libpcre3-dev".
  - Standards-Version to 3.7.2. No changes required.
* debian/rules:
  - add "noopt" feature.
  - set CFLAGS and LIBS.
  - Put $(MAKE) into the build rules.
* debian/copyright: Edited to add the current copyright which
  is GPL with the exception for linking with OpenSSL.
* debian/docs: added "README".
* debian/examples: added "jsrt".

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* buffers.c
 
2
 * Text buffer support routines, manage text and edit sessions.
 
3
 * Copyright (c) Karl Dahlke, 2006
 
4
 * This file is part of the edbrowse project, released under GPL.
 
5
 */
 
6
 
 
7
#include "eb.h"
 
8
#include "tcp.h"
 
9
 
 
10
/* If this include file is missing, you need the pcre package,
 
11
 * and the pcre-devel package. */
 
12
#include <pcre.h>
 
13
 
 
14
/* Static variables for this file. */
 
15
 
 
16
/* The valid edbrowse commands. */
 
17
static const char valid_cmd[] = "aAbBcdefghHijJklmMnpqrstuvwz=^<";
 
18
/* Commands that can be done in browse mode. */
 
19
static const char browse_cmd[] = "AbBdefghHijJklmMnpqsuvwz=^<";
 
20
/* Commands for directory mode. */
 
21
static const char dir_cmd[] = "AbdefghHklMnpqsvwz=^<";
 
22
/* Commands that work at line number 0, in an empty file. */
 
23
static const char zero_cmd[] = "aAbefhHMqruw=^<";
 
24
/* Commands that expect a space afterward. */
 
25
static const char spaceplus_cmd[] = "befrw";
 
26
/* Commands that should have no text after them. */
 
27
static const char nofollow_cmd[] = "aAcdhHjlmnptu=";
 
28
/* Commands that can be done after a g// global directive. */
 
29
static const char global_cmd[] = "dijJlmnpst";
 
30
 
 
31
static struct ebWindow preWindow, undoWindow;
 
32
static int startRange, endRange;        /* as in 57,89p */
 
33
static int destLine;            /* as in 57,89m226 */
 
34
static int last_z = 1;
 
35
static char cmd, icmd, scmd;
 
36
static uchar subPrint;          /* print lines after substitutions */
 
37
static bool noStack;            /* don't stack up edit sessions */
 
38
static bool globSub;            /* in the midst of a g// command */
 
39
static bool inscript;           /* run from inside an edbrowse function */
 
40
static int lastq, lastqq;
 
41
static char icmd;               /* input command, usually the same as cmd */
 
42
 
 
43
 
 
44
/*********************************************************************
 
45
/* If a rendered line contains a hyperlink, the link is indicated
 
46
 * by a code that is stored inline.
 
47
 * If the hyperlink is number 17 on the list of hyperlinks for this window,
 
48
 * it is indicated by 0x80 17 { text }.
 
49
 * The "text" is what you see on the page, what you click on.
 
50
 * {Click here for more information}.
 
51
 * And the braces tell you it's a hyperlink.
 
52
 * That's just my convention.
 
53
 * The prior chars are for internal use only.
 
54
 * I'm assuming these chars don't/won't appear on the rendered page.
 
55
 * Yeah, sometimes nonascii chars appear, especially if the page is in
 
56
 * a European language, but I just assume a rendered page will not contain
 
57
 * the sequence: 0x80 number {
 
58
 * In fact I assume the rendered text won't contain 0x80 at all.
 
59
 * So I use this char to demark encoded constructs within the lines.
 
60
 * And why do I encode things right in the text?
 
61
 * Well it took me a few versions to reach this point.
 
62
 * But it makes so much sense!
 
63
 * If I move a line, the referenced hyperlink moves with it.
 
64
 * I don't have to update some other structure that says,
 
65
 * "At line 73, characters 29 through 47, that's hyperlink 17.
 
66
 * I use to do it that way, and wow, what a lot of overhead
 
67
 * when you move lines about, or delete them, or make substitutions.
 
68
 * Yes, you really need to change rendered html text,
 
69
 * because that's how you fill out forms.
 
70
 * Add just one letter to the first name in your fill out form,
 
71
 * and the hyperlink that comes later on in the line shifts down.
 
72
 * I use to go back and update the pointers,
 
73
 * so that the hyperlink started at offset 30, rather than 29.
 
74
 * That was a lot of work, and very error prone.
 
75
 * Finally I got smart, and coded the html tags inline.
 
76
 * They can't get lost as text is modified.  They move with the text.
 
77
 *
 
78
 * So now, certain sequences in the text are for internal use only.
 
79
 * This routine strips out these sequences, for display.
 
80
 * After all, you don't want to see those code characters.
 
81
 * You just want to see {Click here for more information}. */
 
82
 
 
83
static void
 
84
removeHiddenNumbers(pst p)
 
85
{
 
86
    pst s, t, u;
 
87
    uchar c, d;
 
88
 
 
89
    s = t = p;
 
90
    while((c = *s) != '\n') {
 
91
        if(c != (uchar)InternalCodeChar) {
 
92
          addchar:
 
93
            *t++ = c;
 
94
            ++s;
 
95
            continue;
 
96
        }
 
97
        u = s + 1;
 
98
        d = *u;
 
99
        if(!isdigitByte(d))
 
100
            goto addchar;
 
101
        do {
 
102
            d = *++u;
 
103
        } while(isdigitByte(d));
 
104
        if(d == '*') {
 
105
            s = u + 1;
 
106
            continue;
 
107
        }
 
108
        if(strchr("<>{}", d)) {
 
109
            s = u;
 
110
            continue;
 
111
        }
 
112
/* This is not a code sequence I recognize. */
 
113
/* This should never happen; just move along. */
 
114
        goto addchar;
 
115
    }                           /* loop over p */
 
116
    *t = c;                     /* terminating newline */
 
117
}                               /* removeHiddenNumbers */
 
118
 
 
119
/* Fetch line n from the current buffer, or perhaps another buffer.
 
120
 * This returns an allocated copy of the string,
 
121
 * and you need to free it when you're done.
 
122
 * Good grief, how inefficient!
 
123
 * I know, but perl use to do it, and I never noticed the slowdown.
 
124
 * This is not the Linux kernel, we don't need to be ultra-efficient.
 
125
 * We just need to be consistent in our programming efforts.
 
126
 * Sometimes the returned line is changed,
 
127
 * and if it happens sometimes, we may as well make the new copy
 
128
 * all the time, even if the line doesn't change, to be consistent.
 
129
 * You can supress the copy feature with -1. */
 
130
 
 
131
static pst
 
132
fetchLineContext(int n, int show, int cx)
 
133
{
 
134
    struct ebWindow *lw = sessionList[cx].lw;
 
135
    char *map, *t;
 
136
    int dol, idx;
 
137
    unsigned len;
 
138
    pst p;                      /* the resulting copy of the string */
 
139
 
 
140
    if(!lw)
 
141
        errorPrint("@invalid session %d in fetchLineContext()", cx);
 
142
    map = lw->map;
 
143
    dol = lw->dol;
 
144
    if(n <= 0 || n > dol)
 
145
        errorPrint("@invalid line number %d in fetchLineContext()", n);
 
146
 
 
147
    t = map + LNWIDTH * n;
 
148
    idx = atoi(t);
 
149
    if(!textLines[idx])
 
150
        errorPrint("@line %d->%d became null", n, idx);
 
151
    if(show < 0)
 
152
        return textLines[idx];
 
153
    p = clonePstring(textLines[idx]);
 
154
    if(show && lw->browseMode)
 
155
        removeHiddenNumbers(p);
 
156
    return p;
 
157
}                               /* fetchLineContext */
 
158
 
 
159
pst
 
160
fetchLine(int n, int show)
 
161
{
 
162
    return fetchLineContext(n, show, context);
 
163
}                               /* fetchLine */
 
164
 
 
165
static int
 
166
apparentSize(int cx, bool browsing)
 
167
{
 
168
    char c;
 
169
    int i, ln, size;
 
170
    struct ebWindow *w;
 
171
    if(cx <= 0 || cx >= MAXSESSION || (w = sessionList[cx].lw) == 0) {
 
172
        setError("session %d is not active", cx);
 
173
        return -1;
 
174
    }
 
175
    size = 0;
 
176
    for(ln = 1; ln <= w->dol; ++ln) {
 
177
        if(browsing && sessionList[cx].lw->browseMode) {
 
178
            pst line = fetchLineContext(ln, 1, cx);
 
179
            size += pstLength(line);
 
180
            free(line);
 
181
        } else {
 
182
            pst line = fetchLineContext(ln, -1, cx);
 
183
            size += pstLength(line);
 
184
        }
 
185
    }                           /* loop over lines */
 
186
    if(sessionList[cx].lw->nlMode)
 
187
        --size;
 
188
    return size;
 
189
}                               /* apparentSize */
 
190
 
 
191
int
 
192
currentBufferSize(void)
 
193
{
 
194
    return apparentSize(context, cw->browseMode);
 
195
}                               /* currentBufferSize */
 
196
 
 
197
/* get the directory suffix for a file.
 
198
 * This only makes sense in directory mode. */
 
199
static char *
 
200
dirSuffixContext(int n, int cx)
 
201
{
 
202
    static char suffix[4];
 
203
    struct ebWindow *lw = sessionList[cx].lw;
 
204
    suffix[0] = 0;
 
205
    if(lw->dirMode) {
 
206
        char *s = lw->map + LNWIDTH * n + 8;
 
207
        suffix[0] = s[0];
 
208
        if(suffix[0] == ' ')
 
209
            suffix[0] = 0;
 
210
        suffix[1] = s[1];
 
211
        if(suffix[1] == ' ')
 
212
            suffix[1] = 0;
 
213
        suffix[2] = 0;
 
214
    }
 
215
    return suffix;
 
216
}                               /* dirSuffixContext */
 
217
 
 
218
static char *
 
219
dirSuffix(int n)
 
220
{
 
221
    return dirSuffixContext(n, context);
 
222
}                               /* dirSuffix */
 
223
 
 
224
/* Display a line to the screen, but no more than 500 chars. */
 
225
void
 
226
displayLine(int n)
 
227
{
 
228
    pst line = fetchLine(n, 1);
 
229
    pst s = line;
 
230
    int cnt = 0;
 
231
    uchar c;
 
232
 
 
233
    if(cmd == 'n')
 
234
        printf("%d ", n);
 
235
    if(endMarks == 2 || endMarks && cmd == 'l')
 
236
        printf("^");
 
237
 
 
238
    while((c = *s++) != '\n') {
 
239
        bool expand = false;
 
240
        if(c == 0 || c == '\r' || c == '\x1b')
 
241
            expand = true;
 
242
        if(cmd == 'l') {
 
243
/* show tabs and backspaces, ed style */
 
244
            if(c == '\b')
 
245
                c = '<';
 
246
            if(c == '\t')
 
247
                c = '>';
 
248
            if(!isprintByte(c))
 
249
                expand = true;
 
250
        }                       /* list */
 
251
        if(expand)
 
252
            printf("~%02X", c), cnt += 3;
 
253
        else
 
254
            printf("%c", c), ++cnt;
 
255
        if(cnt >= 500)
 
256
            break;
 
257
    }                           /* loop over line */
 
258
 
 
259
    if(cnt >= 500)
 
260
        printf("...");
 
261
    printf("%s", dirSuffix(n));
 
262
    if(endMarks == 2 || endMarks && cmd == 'l')
 
263
        printf("$");
 
264
    printf("\n");
 
265
 
 
266
    free(line);
 
267
}                               /* displayLine */
 
268
 
 
269
static void
 
270
printDot(void)
 
271
{
 
272
    if(cw->dot)
 
273
        displayLine(cw->dot);
 
274
    else
 
275
        printf("empty\n");
 
276
}                               /* printDot */
 
277
 
 
278
/* Get a line from standard in.  Need not be a terminal.
 
279
 * Each input line is limited to 255 chars.
 
280
 * On Unix cooked mode, that's as long as a line can be anyways.
 
281
 * This routine returns the line in a static string.
 
282
 * If you want to keep it, better make a copy.
 
283
 * ~xx is converted from hex, a way to enter nonascii chars.
 
284
 * This is the opposite of displayLine() above.
 
285
 * But you can't enter a newline this way; I won't permit it.
 
286
 * The only newline is the one corresponding to the end of your text,
 
287
 * when you hit enter. This terminates the line.
 
288
 * As we described earlier, this is a perl string.
 
289
 * It may contain nulls, and is terminated by newline.
 
290
 * The program exits on EOF.
 
291
 * If you hit interrupt at this point, I print a message
 
292
 * and ask for your line again. */
 
293
 
 
294
pst
 
295
inputLine(void)
 
296
{
 
297
    static uchar line[MAXTTYLINE];
 
298
    int i, j;
 
299
    uchar c, d, e;
 
300
 
 
301
  top:
 
302
    intFlag = false;
 
303
    inInput = true;
 
304
    if(!fgets((char *)line, sizeof (line), stdin)) {
 
305
        if(intFlag)
 
306
            goto top;
 
307
        printf("EOF\n");
 
308
        exit(1);
 
309
    }
 
310
    inInput = false;
 
311
    intFlag = false;
 
312
 
 
313
    i = j = 0;
 
314
    while(i < sizeof (line) - 1 && (c = line[i]) != '\n') {
 
315
/* A bug in my keyboard causes nulls to be entered from time to time. */
 
316
        if(c == 0)
 
317
            c = ' ';
 
318
        if(c != '~') {
 
319
          addchar:
 
320
            line[j++] = c;
 
321
            ++i;
 
322
            continue;
 
323
        }
 
324
        d = line[i + 1];
 
325
        if(d == '~') {
 
326
            ++i;
 
327
            goto addchar;
 
328
        }
 
329
        if(!isxdigit(d))
 
330
            goto addchar;
 
331
        e = line[i + 2];
 
332
        if(!isxdigit(e))
 
333
            goto addchar;
 
334
        c = fromHex(d, e);
 
335
        if(c == '\n')
 
336
            c = 0;
 
337
        i += 2;
 
338
        goto addchar;
 
339
    }                           /* loop over input chars */
 
340
    line[j] = '\n';
 
341
    return line;
 
342
}                               /* inputLine */
 
343
 
 
344
static void
 
345
carrySubstitutionStrings(const struct ebWindow *w, struct ebWindow *nw)
 
346
{
 
347
    if(!searchStringsAll)
 
348
        return;
 
349
    nw->lhs_yes = w->lhs_yes;
 
350
    strcpy(nw->lhs, w->lhs);
 
351
    nw->rhs_yes = w->rhs_yes;
 
352
    strcpy(nw->rhs, w->rhs);
 
353
}                               /* carrySubstitutionStrings */
 
354
 
 
355
/* Create a new window, with default variables. */
 
356
static struct ebWindow *
 
357
createWindow(void)
 
358
{
 
359
    struct ebWindow *nw;        /* the new window */
 
360
    nw = allocZeroMem(sizeof (struct ebWindow));
 
361
    if(cw)
 
362
        carrySubstitutionStrings(cw, nw);
 
363
    return nw;
 
364
}                               /* createWindow */
 
365
 
 
366
static void
 
367
freeWindowLines(char *map)
 
368
{
 
369
    char *t;
 
370
    int cnt = 0;
 
371
    if(map) {
 
372
        for(t = map + LNWIDTH; *t; t += LNWIDTH) {
 
373
            int ln = atoi(t);
 
374
            if(textLines[ln]) {
 
375
                free(textLines[ln]);
 
376
                ++cnt;
 
377
                textLines[ln] = 0;
 
378
            }
 
379
        }
 
380
        free(map);
 
381
    }
 
382
    debugPrint(6, "freeWindowLines = %d", cnt);
 
383
}                               /* freeWindowLines */
 
384
 
 
385
/* Free any lines not used by the snapshot of the current session. */
 
386
void
 
387
freeUndoLines(const char *cmap)
 
388
{
 
389
    char *map = undoWindow.map;
 
390
    char *s;
 
391
    const char *t;
 
392
    int ln;
 
393
    int cnt = 0;
 
394
 
 
395
    if(!map) {
 
396
        debugPrint(6, "freeUndoLines = null");
 
397
        return;
 
398
    }
 
399
 
 
400
    if(!cmap) {
 
401
        debugPrint(6, "freeUndoLines = win");
 
402
        freeWindowLines(map);
 
403
        undoWindow.map = 0;
 
404
        return;
 
405
    }
 
406
 
 
407
/* This is pretty efficient most of the time,
 
408
 * real inefficient sometimes. */
 
409
    s = map + LNWIDTH;
 
410
    t = cmap + LNWIDTH;
 
411
    for(; *s && *t; s += LNWIDTH, t += LNWIDTH)
 
412
        if(!memcmp(s, t, 6))
 
413
            *s = '*';
 
414
        else
 
415
            break;
 
416
 
 
417
    if(*s) {                    /* more to do */
 
418
        s = map + strlen(map);
 
419
        t = cmap + strlen(cmap);
 
420
        s -= LNWIDTH, t -= LNWIDTH;
 
421
        while(s > map && t > cmap) {
 
422
            if(!memcmp(s, t, 6))
 
423
                *s = '*';
 
424
            s -= LNWIDTH, t -= LNWIDTH;
 
425
        }
 
426
 
 
427
/* Ok, who's left? */
 
428
        for(s = map + LNWIDTH; *s; s += LNWIDTH) {
 
429
            if(*s == '*')
 
430
                continue;       /* in use */
 
431
            for(t = cmap + LNWIDTH; *t; t += LNWIDTH)
 
432
                if(!memcmp(s, t, 6))
 
433
                    break;
 
434
            if(*t)
 
435
                continue;       /* in use */
 
436
            ln = atoi(s);
 
437
            if(textLines[ln]) {
 
438
                free(textLines[ln]);
 
439
                textLines[ln] = 0;
 
440
                ++cnt;
 
441
            }
 
442
        }
 
443
    }
 
444
 
 
445
    free(map);
 
446
    undoWindow.map = 0;
 
447
    debugPrint(6, "freeUndoLines = %d", cnt);
 
448
}                               /* freeUndoLines */
 
449
 
 
450
static void
 
451
freeWindow(struct ebWindow *w)
 
452
{
 
453
/* The next few are designed to do nothing if not in browseMode */
 
454
    freeJavaContext(w->jsc);
 
455
    freeWindowLines(w->r_map);
 
456
    nzFree(w->dw);
 
457
    nzFree(w->ft);
 
458
    nzFree(w->fd);
 
459
    nzFree(w->fk);
 
460
    freeTags(w->tags);
 
461
    freeWindowLines(w->map);
 
462
    nzFree(w->fileName);
 
463
    nzFree(w->referrer);
 
464
    nzFree(w->baseDirName);
 
465
    free(w);
 
466
}                               /* freeWindow */
 
467
 
 
468
/*********************************************************************
 
469
Here are a few routines to switch contexts from one buffer to another.
 
470
This is how the user edits multiple sessions, or browses multiple
 
471
web pages, simultaneously.
 
472
*********************************************************************/
 
473
 
 
474
bool
 
475
cxCompare(int cx)
 
476
{
 
477
    if(cx == 0) {
 
478
        setError("session 0 is invalid");
 
479
        return false;
 
480
    }
 
481
    if(cx >= MAXSESSION) {
 
482
        setError("session %d is out of bounds, limit %d", cx, MAXSESSION - 1);
 
483
        return false;
 
484
    }
 
485
    if(cx != context)
 
486
        return true;            /* ok */
 
487
    setError("you are already in session %d", cx);
 
488
    return false;
 
489
}                               /*cxCompare */
 
490
 
 
491
/* is a context active? */
 
492
bool
 
493
cxActive(int cx)
 
494
{
 
495
    if(cx <= 0 || cx >= MAXSESSION)
 
496
        errorPrint("@session %d out of range in cxActive", cx);
 
497
    if(sessionList[cx].lw)
 
498
        return true;
 
499
    setError("session %d is not active", cx);
 
500
    return false;
 
501
}                               /* cxActive */
 
502
 
 
503
static void
 
504
cxInit(int cx)
 
505
{
 
506
    struct ebWindow *lw = createWindow();
 
507
    if(sessionList[cx].lw)
 
508
        errorPrint("@double init on session %d", cx);
 
509
    sessionList[cx].fw = sessionList[cx].lw = lw;
 
510
}                               /* cxInit */
 
511
 
 
512
bool
 
513
cxQuit(int cx, int action)
 
514
{
 
515
    struct ebWindow *w = sessionList[cx].lw;
 
516
    if(!w)
 
517
        errorPrint("@quitting a nonactive session %d", cx);
 
518
 
 
519
/* We might be trashing data, make sure that's ok. */
 
520
    if(w->changeMode &&
 
521
       !w->dirMode && lastq != cx && w->fileName && !isURL(w->fileName)) {
 
522
        lastqq = cx;
 
523
        setError("expecting `w'");
 
524
        if(cx != context)
 
525
            setError("expecting `w' on session %d", cx);
 
526
        return false;
 
527
    }
 
528
    /* warning message */
 
529
    if(!action)
 
530
        return true;            /* just a check */
 
531
 
 
532
    if(cx == context) {
 
533
/* Don't need to retain the undo lines. */
 
534
        freeWindowLines(undoWindow.map);
 
535
        undoWindow.map = 0;
 
536
        nzFree(preWindow.map);
 
537
        preWindow.map = 0;
 
538
    }
 
539
 
 
540
    if(action == 2) {
 
541
        while(w) {
 
542
            struct ebWindow *p = w->prev;
 
543
            freeWindow(w);
 
544
            w = p;
 
545
        }
 
546
        sessionList[cx].fw = sessionList[cx].lw = 0;
 
547
    } else
 
548
        freeWindow(w);
 
549
 
 
550
    return true;
 
551
}                               /* cxQuit */
 
552
 
 
553
/* Switch to another edit session.
 
554
 * This assumes cxCompare has succeeded - we're moving to a different context.
 
555
 * Pass the context number and an interactive flag. */
 
556
void
 
557
cxSwitch(int cx, bool interactive)
 
558
{
 
559
    bool created = false;
 
560
    struct ebWindow *nw = sessionList[cx].lw;   /* the new window */
 
561
    if(!nw) {
 
562
        cxInit(cx);
 
563
        nw = sessionList[cx].lw;
 
564
        created = true;
 
565
    } else
 
566
        carrySubstitutionStrings(cw, nw);
 
567
 
 
568
    if(cw) {
 
569
        freeUndoLines(cw->map);
 
570
        cw->firstOpMode = false;
 
571
    }
 
572
    cw = nw;
 
573
    cs = sessionList + cx;
 
574
    context = cx;
 
575
    if(interactive && debugLevel) {
 
576
        if(created)
 
577
            printf("new session\n");
 
578
        else if(cw->fileName)
 
579
            puts(cw->fileName);
 
580
        else
 
581
            puts("no file");
 
582
    }
 
583
 
 
584
/* The next line is required when this function is called from main(),
 
585
 * when the first arg is a url and there is a second arg. */
 
586
    startRange = endRange = cw->dot;
 
587
}                               /* cxSwitch */
 
588
 
 
589
/* Make sure we have room to store another n lines. */
 
590
 
 
591
void
 
592
linesReset(void)
 
593
{
 
594
    int j;
 
595
    if(!textLines)
 
596
        return;
 
597
    for(j = 0; j < textLinesCount; ++j)
 
598
        nzFree(textLines[j]);
 
599
    nzFree(textLines);
 
600
    textLines = 0;
 
601
    textLinesCount = textLinesMax = 0;
 
602
}                               /* linesReset */
 
603
 
 
604
bool
 
605
linesComing(int n)
 
606
{
 
607
    int need = textLinesCount + n;
 
608
    if(need > LNMAX) {
 
609
        setError
 
610
           ("Your limit of 1 million lines has been reached.\nSave your files, then exit and restart this program.");
 
611
        return false;
 
612
    }
 
613
    if(need > textLinesMax) {
 
614
        int newmax = textLinesMax * 3 / 2;
 
615
        if(need > newmax)
 
616
            newmax = need + 8192;
 
617
        if(newmax > LNMAX)
 
618
            newmax = LNMAX;
 
619
        if(textLinesMax) {
 
620
            debugPrint(4, "textLines realloc %d", newmax);
 
621
            textLines = reallocMem(textLines, newmax * sizeof (pst));
 
622
        } else {
 
623
            textLines = allocMem(newmax * sizeof (pst));
 
624
        }
 
625
        textLinesMax = newmax;
 
626
    }
 
627
    /* overflow requires realloc */
 
628
    /* We now have room for n new lines, but you have to add n
 
629
     * to textLines Count, once you have brought in the lines. */
 
630
    return true;
 
631
}                               /* linesComing */
 
632
 
 
633
/* This function is called for web redirection, by the refresh command,
 
634
 * or by window.location = new_url. */
 
635
static char *newlocation;
 
636
static int newloc_d;            /* possible delay */
 
637
static bool newloc_rf;          /* refresh the buffer */
 
638
bool js_redirects;
 
639
 
 
640
void
 
641
gotoLocation(char *url, int delay, bool rf)
 
642
{
 
643
    if(newlocation && delay >= newloc_d) {
 
644
        nzFree(url);
 
645
        return;
 
646
    }
 
647
    nzFree(newlocation);
 
648
    newlocation = url;
 
649
    newloc_d = delay;
 
650
    newloc_rf = rf;
 
651
    if(!delay)
 
652
        js_redirects = true;
 
653
}                               /* gotoLocation */
 
654
 
 
655
/* Adjust the map of line numbers -- we have inserted text.
 
656
 * Also shift the downstream labels.
 
657
 * Pass the string containing the new line numbers, and the dest line number. */
 
658
static void
 
659
addToMap(int start, int end, int destl)
 
660
{
 
661
    char *newmap;
 
662
    int i, j;
 
663
    int nlines = end - start;
 
664
    if(nlines == 0)
 
665
        errorPrint("@empty new piece in addToMap()");
 
666
    for(i = 0; i < 26; ++i) {
 
667
        int ln = cw->labels[i];
 
668
        if(ln <= destl)
 
669
            continue;
 
670
        cw->labels[i] += nlines;
 
671
    }
 
672
    cw->dot = destl + nlines;
 
673
    cw->dol += nlines;
 
674
    newmap = allocMem((cw->dol + 1) * LNWIDTH + 1);
 
675
    if(cw->map)
 
676
        strcpy(newmap, cw->map);
 
677
    else
 
678
        strcpy(newmap, LNSPACE);
 
679
    i = j = (destl + 1) * LNWIDTH;      /* insert new piece here */
 
680
    while(start < end) {
 
681
        sprintf(newmap + i, LNFORMAT, start);
 
682
        ++start;
 
683
        i += LNWIDTH;
 
684
    }
 
685
    if(cw->map)
 
686
        strcat(newmap, cw->map + j);
 
687
    nzFree(cw->map);
 
688
    cw->map = newmap;
 
689
    cw->firstOpMode = undoable = true;
 
690
    if(!cw->browseMode)
 
691
        cw->changeMode = true;
 
692
}                               /* addToMap */
 
693
 
 
694
/* Add a block of text into the buffer; uses addToMap(). */
 
695
bool
 
696
addTextToBuffer(const pst inbuf, int length, int destl)
 
697
{
 
698
    int i, j, linecount = 0;
 
699
    int start, end;
 
700
    for(i = 0; i < length; ++i)
 
701
        if(inbuf[i] == '\n')
 
702
            ++linecount;
 
703
    if(!linesComing(linecount + 1))
 
704
        return false;
 
705
    if(destl == cw->dol)
 
706
        cw->nlMode = false;
 
707
    if(inbuf[length - 1] != '\n') {
 
708
/* doesn't end in newline */
 
709
        ++linecount;            /* last line wasn't counted */
 
710
        if(destl == cw->dol) {
 
711
            cw->nlMode = true;
 
712
            if(cmd != 'b' && !cw->binMode && !ismc)
 
713
                printf("no trailing newline\n");
 
714
        }
 
715
    }                           /* missing newline */
 
716
    start = end = textLinesCount;
 
717
    i = 0;
 
718
    while(i < length) {         /* another line */
 
719
        j = i;
 
720
        while(i < length)
 
721
            if(inbuf[i++] == '\n')
 
722
                break;
 
723
        if(inbuf[i - 1] == '\n') {
 
724
/* normal line */
 
725
            textLines[end] = allocMem(i - j);
 
726
        } else {
 
727
/* last line with no nl */
 
728
            textLines[end] = allocMem(i - j + 1);
 
729
            textLines[end][i - j] = '\n';
 
730
        }
 
731
        memcpy(textLines[end], inbuf + j, i - j);
 
732
        ++end;
 
733
    }                           /* loop breaking inbuf into lines */
 
734
    textLinesCount = end;
 
735
    addToMap(start, end, destl);
 
736
    return true;
 
737
}                               /* addTextToBuffer */
 
738
 
 
739
/* Pass input lines straight into the buffer, until the user enters . */
 
740
 
 
741
static bool
 
742
inputLinesIntoBuffer(void)
 
743
{
 
744
    int start = textLinesCount;
 
745
    int end = start;
 
746
    pst line;
 
747
    if(linePending[0])
 
748
        line = linePending;
 
749
    else
 
750
        line = inputLine();
 
751
    while(line[0] != '.' || line[1] != '\n') {
 
752
        if(!linesComing(1))
 
753
            return false;
 
754
        textLines[end++] = clonePstring(line);
 
755
        line = inputLine();
 
756
    }
 
757
    if(end == start) {          /* no lines entered */
 
758
        cw->dot = endRange;
 
759
        if(!cw->dot && cw->dol)
 
760
            cw->dot = 1;
 
761
        return true;
 
762
    }
 
763
    if(endRange == cw->dol)
 
764
        cw->nlMode = false;
 
765
    textLinesCount = end;
 
766
    addToMap(start, end, endRange);
 
767
    return true;
 
768
}                               /* inputLinesIntoBuffer */
 
769
 
 
770
/* Delete a block of text. */
 
771
 
 
772
static void
 
773
delText(int start, int end)
 
774
{
 
775
    int i, j;
 
776
    if(end == cw->dol)
 
777
        cw->nlMode = false;
 
778
    j = end - start + 1;
 
779
    strcpy(cw->map + start * LNWIDTH, cw->map + (end + 1) * LNWIDTH);
 
780
/* move the labels */
 
781
    for(i = 0; i < 26; ++i) {
 
782
        int ln = cw->labels[i];
 
783
        if(ln < start)
 
784
            continue;
 
785
        if(ln <= end) {
 
786
            cw->labels[i] = 0;
 
787
            continue;
 
788
        }
 
789
        cw->labels[i] -= j;
 
790
    }
 
791
    cw->dol -= j;
 
792
    cw->dot = start;
 
793
    if(cw->dot > cw->dol)
 
794
        cw->dot = cw->dol;
 
795
/* by convention an empty buffer has no map */
 
796
    if(!cw->dol) {
 
797
        free(cw->map);
 
798
        cw->map = 0;
 
799
    }
 
800
    cw->firstOpMode = undoable = true;
 
801
    if(!cw->browseMode)
 
802
        cw->changeMode = true;
 
803
}                               /* delText */
 
804
 
 
805
/* Delete files from a directory as you delete lines.
 
806
 * Set dw to move them to your recycle bin.
 
807
 * Set dx to delete them outright. */
 
808
 
 
809
static char *
 
810
makeAbsPath(char *f)
 
811
{
 
812
    static char path[ABSPATH];
 
813
    if(strlen(cw->baseDirName) + strlen(f) > ABSPATH - 2) {
 
814
        setError("absolute path name too long, limit %d chars", ABSPATH);
 
815
        return 0;
 
816
    }
 
817
    sprintf(path, "%s/%s", cw->baseDirName, f);
 
818
    return path;
 
819
}                               /* makeAbsPath */
 
820
 
 
821
static bool
 
822
delFiles(void)
 
823
{
 
824
    int ln, cnt;
 
825
    if(!dirWrite) {
 
826
        setError
 
827
           ("directories are readonly, type dw to enable directory writes");
 
828
        return false;
 
829
    }
 
830
    if(dirWrite == 1 && !recycleBin) {
 
831
        setError
 
832
           ("could not create .recycle under your home directory, to hold the deleted files");
 
833
        return false;
 
834
    }
 
835
    ln = startRange;
 
836
    cnt = endRange - startRange + 1;
 
837
    while(cnt--) {
 
838
        char *file, *t, *path, *ftype;
 
839
        file = (char *)fetchLine(ln, 0);
 
840
        t = strchr(file, '\n');
 
841
        if(!t)
 
842
            errorPrint("@no newline on directory entry %s", file);
 
843
        *t = 0;
 
844
        path = makeAbsPath(file);
 
845
        if(!path) {
 
846
            free(file);
 
847
            return false;
 
848
        }
 
849
        ftype = dirSuffix(ln);
 
850
        if(dirWrite == 2 || *ftype == '@') {
 
851
            if(unlink(path)) {
 
852
                setError("could not remove file %s", file);
 
853
                free(file);
 
854
                return false;
 
855
            }
 
856
        } else {
 
857
            char bin[ABSPATH];
 
858
            sprintf(bin, "%s/%s", recycleBin, file);
 
859
            if(rename(path, bin)) {
 
860
                setError
 
861
                   ("Could not move %s to the recycle bin, set dx mode to actually remove the file",
 
862
                   file);
 
863
                free(file);
 
864
                return false;
 
865
            }
 
866
        }
 
867
        free(file);
 
868
        delText(ln, ln);
 
869
    }
 
870
    return true;
 
871
}                               /* delFiles */
 
872
 
 
873
/* Move or copy a block of text. */
 
874
/* Uses range variables, hence no parameters. */
 
875
static bool
 
876
moveCopy(void)
 
877
{
 
878
    int sr = startRange;
 
879
    int er = endRange + 1;
 
880
    int dl = destLine + 1;
 
881
    int i_sr = sr * LNWIDTH;    /* indexes into map */
 
882
    int i_er = er * LNWIDTH;
 
883
    int i_dl = dl * LNWIDTH;
 
884
    int n_lines = er - sr;
 
885
    char *map = cw->map;
 
886
    char *newmap;
 
887
    int lowcut, highcut, diff, i;
 
888
 
 
889
    if(dl > sr && dl < er) {
 
890
        setError("destination lies inside the block to be moved or copied");
 
891
        return false;
 
892
    }
 
893
    if(cmd == 'm' && (dl == er || dl == sr)) {
 
894
        if(globSub)
 
895
            setError("no change");
 
896
        return false;
 
897
    }
 
898
 
 
899
    if(cmd == 't') {
 
900
        if(!linesComing(n_lines))
 
901
            return false;
 
902
        for(i = sr; i < er; ++i)
 
903
            textLines[textLinesCount++] = fetchLine(i, 0);
 
904
        addToMap(textLinesCount - n_lines, textLinesCount, destLine);
 
905
        return true;
 
906
    }
 
907
    /* copy */
 
908
    if(destLine == cw->dol || endRange == cw->dol)
 
909
        cw->nlMode = false;
 
910
/* All we really need do is rearrange the map. */
 
911
    newmap = allocMem((cw->dol + 1) * LNWIDTH + 1);
 
912
    strcpy(newmap, map);
 
913
    if(dl < sr) {
 
914
        memcpy(newmap + i_dl, map + i_sr, i_er - i_sr);
 
915
        memcpy(newmap + i_dl + i_er - i_sr, map + i_dl, i_sr - i_dl);
 
916
    } else {
 
917
        memcpy(newmap + i_sr, map + i_er, i_dl - i_er);
 
918
        memcpy(newmap + i_sr + i_dl - i_er, map + i_sr, i_er - i_sr);
 
919
    }
 
920
    free(cw->map);
 
921
    cw->map = newmap;
 
922
 
 
923
/* now for the labels */
 
924
    if(dl < sr) {
 
925
        lowcut = dl;
 
926
        highcut = er;
 
927
        diff = sr - dl;
 
928
    } else {
 
929
        lowcut = sr;
 
930
        highcut = dl;
 
931
        diff = dl - er;
 
932
    }
 
933
    for(i = 0; i < 26; ++i) {
 
934
        int ln = cw->labels[i];
 
935
        if(ln < lowcut)
 
936
            continue;
 
937
        if(ln >= highcut)
 
938
            continue;
 
939
        if(ln >= startRange && ln <= endRange) {
 
940
            ln += (dl < sr ? -diff : diff);
 
941
        } else {
 
942
            ln += (dl < sr ? n_lines : -n_lines);
 
943
        }
 
944
        cw->labels[i] = ln;
 
945
    }                           /* loop over labels */
 
946
 
 
947
    cw->dot = endRange;
 
948
    cw->dot += (dl < sr ? -diff : diff);
 
949
    cw->firstOpMode = undoable = true;
 
950
    if(!cw->browseMode)
 
951
        cw->changeMode = true;
 
952
    return true;
 
953
}                               /* moveCopy */
 
954
 
 
955
/* Join lines from startRange to endRange. */
 
956
static bool
 
957
joinText(void)
 
958
{
 
959
    int j, size;
 
960
    pst newline, t;
 
961
    if(startRange == endRange) {
 
962
        setError("cannot join one line");
 
963
        return false;
 
964
    }
 
965
    if(!linesComing(1))
 
966
        return false;
 
967
    size = 0;
 
968
    for(j = startRange; j <= endRange; ++j)
 
969
        size += pstLength(fetchLine(j, -1));
 
970
    t = newline = allocMem(size);
 
971
    for(j = startRange; j <= endRange; ++j) {
 
972
        pst p = fetchLine(j, -1);
 
973
        size = pstLength(p);
 
974
        memcpy(t, p, size);
 
975
        t += size;
 
976
        if(j < endRange) {
 
977
            t[-1] = ' ';
 
978
            if(cmd == 'j')
 
979
                --t;
 
980
        }
 
981
    }
 
982
    textLines[textLinesCount] = newline;
 
983
    sprintf(cw->map + startRange * LNWIDTH, LNFORMAT, textLinesCount);
 
984
    ++textLinesCount;
 
985
    delText(startRange + 1, endRange);
 
986
    cw->dot = startRange;
 
987
    return true;
 
988
}                               /* joinText */
 
989
 
 
990
/* Read a file into the current buffer, or a URL.
 
991
 * Post/get data is passed, via the second parameter, if it's a URL. */
 
992
bool
 
993
readFile(const char *filename, const char *post)
 
994
{
 
995
    char *rbuf;                 /* read buffer */
 
996
    int readSize;               /* should agree with fileSize */
 
997
    int fh;                     /* file handle */
 
998
    int i, bincount;
 
999
    bool rc;                    /* return code */
 
1000
    char *nopound;
 
1001
 
 
1002
    if(memEqualCI(filename, "file://", 7))
 
1003
        filename += 7;
 
1004
    if(!*filename) {
 
1005
        setError("missing file name");
 
1006
        return false;
 
1007
    }
 
1008
 
 
1009
    if(isURL(filename)) {
 
1010
        const char *domain = getHostURL(filename);
 
1011
        if(!domain)
 
1012
            return false;       /* some kind of error */
 
1013
        if(!*domain) {
 
1014
            setError("empty domain in url");
 
1015
            return false;
 
1016
        }
 
1017
        serverData = 0;
 
1018
        serverDataLen = 0;
 
1019
 
 
1020
        rc = httpConnect(0, filename);
 
1021
 
 
1022
        if(!rc) {
 
1023
/* The error could have occured after redirection */
 
1024
            nzFree(changeFileName);
 
1025
            changeFileName = 0;
 
1026
            return false;
 
1027
        }
 
1028
 
 
1029
/* We got some data.  Any warnings along the way have been printed,
 
1030
 * like 404 file not found, but it's still worth continuing. */
 
1031
        rbuf = serverData;
 
1032
        fileSize = readSize = serverDataLen;
 
1033
 
 
1034
        if(fileSize == 0) {     /* empty file */
 
1035
            nzFree(rbuf);
 
1036
            cw->dot = endRange;
 
1037
            return true;
 
1038
        }
 
1039
 
 
1040
    } else {                    /* url or file */
 
1041
 
 
1042
/* reading a file from disk */
 
1043
        fileSize = 0;
 
1044
        if(fileTypeByName(filename, false) == 'd') {
 
1045
/* directory scan */
 
1046
            int len, j, start, end;
 
1047
            cw->baseDirName = cloneString(filename);
 
1048
/* get rid of trailing slash */
 
1049
            len = strlen(cw->baseDirName);
 
1050
            if(len && cw->baseDirName[len - 1] == '/')
 
1051
                cw->baseDirName[len - 1] = 0;
 
1052
/* Understand that the empty string now means / */
 
1053
/* get the files, or fail if there is a problem */
 
1054
            if(!sortedDirList(filename, &start, &end))
 
1055
                return false;
 
1056
            if(!cw->dol) {
 
1057
                cw->dirMode = true;
 
1058
                printf("directory mode\n");
 
1059
            }
 
1060
            if(start == end) {  /* empty directory */
 
1061
                cw->dot = endRange;
 
1062
                fileSize = 0;
 
1063
                return true;
 
1064
            }
 
1065
 
 
1066
            addToMap(start, end, endRange);
 
1067
 
 
1068
/* change 0 to nl and count bytes */
 
1069
            fileSize = 0;
 
1070
            for(j = start; j < end; ++j) {
 
1071
                char *s, c, ftype;
 
1072
                pst t = textLines[j];
 
1073
                char *abspath = makeAbsPath((char *)t);
 
1074
                while(*t) {
 
1075
                    if(*t == '\n')
 
1076
                        *t = '\t';
 
1077
                    ++t;
 
1078
                }
 
1079
                *t = '\n';
 
1080
                len = t - textLines[j];
 
1081
                fileSize += len + 1;
 
1082
                if(!abspath)
 
1083
                    continue;   /* should never happen */
 
1084
                ftype = fileTypeByName(abspath, true);
 
1085
                if(!ftype)
 
1086
                    continue;
 
1087
                s = cw->map + (endRange + 1 + j - start) * LNWIDTH + 8;
 
1088
                if(isupperByte(ftype)) {        /* symbolic link */
 
1089
                    if(!cw->dirMode)
 
1090
                        *t = '@', *++t = '\n';
 
1091
                    else
 
1092
                        *s++ = '@';
 
1093
                    ++fileSize;
 
1094
                }
 
1095
                ftype = tolower(ftype);
 
1096
                c = 0;
 
1097
                if(ftype == 'd')
 
1098
                    c = '/';
 
1099
                if(ftype == 's')
 
1100
                    c = '^';
 
1101
                if(ftype == 'c')
 
1102
                    c = '<';
 
1103
                if(ftype == 'b')
 
1104
                    c = '*';
 
1105
                if(ftype == 'p')
 
1106
                    c = '|';
 
1107
                if(!c)
 
1108
                    continue;
 
1109
                if(!cw->dirMode)
 
1110
                    *t = c, *++t = '\n';
 
1111
                else
 
1112
                    *s++ = c;
 
1113
                ++fileSize;
 
1114
            }                   /* loop fixing files in the directory scan */
 
1115
            return true;
 
1116
        }
 
1117
        /* reading a directory */
 
1118
        nopound = cloneString(filename);
 
1119
        rbuf = strchr(nopound, '#');
 
1120
        if(rbuf)
 
1121
            *rbuf = 0;
 
1122
        rc = fileIntoMemory(nopound, &rbuf, &fileSize);
 
1123
        nzFree(nopound);
 
1124
        if(!rc)
 
1125
            return false;
 
1126
        serverData = rbuf;
 
1127
        if(fileSize == 0) {     /* empty file */
 
1128
            free(rbuf);
 
1129
            cw->dot = endRange;
 
1130
            return true;
 
1131
        }                       /* empty */
 
1132
    }                           /* file or URL */
 
1133
 
 
1134
/* We got some data, from a file or from the internet.
 
1135
 * Count the binary characters and decide if this is, on the whole,
 
1136
 * binary or text.  I allow some nonascii chars,
 
1137
 * like you might see in Spanish or German, and still call it text,
 
1138
 * but if there's too many such chars, I call it binary.
 
1139
 * It's not an exact science. */
 
1140
    bincount = 0;
 
1141
    for(i = 0; i < fileSize; ++i) {
 
1142
        char c = rbuf[i];
 
1143
        if(c <= 0)
 
1144
            ++bincount;
 
1145
    }
 
1146
    if(bincount * 4 - 10 < fileSize) {
 
1147
/* looks like text.  In DOS, we should have compressed crlf.
 
1148
 * Let's do that now. */
 
1149
#ifdef DOSLIKE
 
1150
        for(i = j = 0; i < fileSize - 1; ++i) {
 
1151
            char c = rbuf[i];
 
1152
            if(c == '\r' && rbuf[i + 1] == '\n')
 
1153
                continue;
 
1154
            rbuf[j++] = c;
 
1155
        }
 
1156
        fileSize = j;
 
1157
#endif
 
1158
    } else if(binaryDetect & !cw->binMode) {
 
1159
        printf("binary data\n");
 
1160
        cw->binMode = true;
 
1161
    }
 
1162
 
 
1163
    rc = addTextToBuffer((const pst)rbuf, fileSize, endRange);
 
1164
    free(rbuf);
 
1165
    return rc;
 
1166
}                               /* readFile */
 
1167
 
 
1168
/* Write a range to a file. */
 
1169
static bool
 
1170
writeFile(const char *name, int mode)
 
1171
{
 
1172
    int i, fh;
 
1173
    if(memEqualCI(name, "file://", 7))
 
1174
        name += 7;
 
1175
    if(!*name) {
 
1176
        setError("missing file name");
 
1177
        return false;
 
1178
    }
 
1179
    if(isURL(name)) {
 
1180
        setError("cannot write to a url");
 
1181
        return false;
 
1182
    }
 
1183
    if(!cw->dol) {
 
1184
        setError("writing an empty file");
 
1185
        return false;
 
1186
    }
 
1187
/* mode should be TRUNC or APPEND */
 
1188
    mode |= O_WRONLY | O_CREAT;
 
1189
    if(cw->binMode)
 
1190
        mode |= O_BINARY;
 
1191
    fh = open(name, mode, 0666);
 
1192
    if(fh < 0) {
 
1193
        setError("cannot create %s", name);
 
1194
        return false;
 
1195
    }
 
1196
    fileSize = 0;
 
1197
    for(i = startRange; i <= endRange; ++i) {
 
1198
        pst p = fetchLine(i, (cw->browseMode ? 1 : -1));
 
1199
        int len = pstLength(p);
 
1200
        char *suf = dirSuffix(i);
 
1201
        if(!suf[0]) {
 
1202
            if(i == cw->dol && cw->nlMode)
 
1203
                --len;
 
1204
            if(write(fh, p, len) < len) {
 
1205
              badwrite:
 
1206
                setError("cannot write to %s", name);
 
1207
                close(fh);
 
1208
                return false;
 
1209
            }
 
1210
            fileSize += len;
 
1211
        } else {
 
1212
/* must write this line with the suffix on the end */
 
1213
            --len;
 
1214
            if(write(fh, p, len) < len)
 
1215
                goto badwrite;
 
1216
            fileSize += len;
 
1217
            strcat(suf, "\n");
 
1218
            len = strlen(suf);
 
1219
            if(write(fh, suf, len) < len)
 
1220
                goto badwrite;
 
1221
            fileSize += len;
 
1222
        }
 
1223
        if(cw->browseMode)
 
1224
            free(p);
 
1225
    }                           /* loop over lines */
 
1226
    close(fh);
 
1227
/* This is not an undoable operation, nor does it change data.
 
1228
 * In fact the data is "no longer modified" if we have written all of it. */
 
1229
    if(startRange == 1 && endRange == cw->dol)
 
1230
        cw->changeMode = false;
 
1231
    return true;
 
1232
}                               /* writeFile */
 
1233
 
 
1234
static bool
 
1235
readContext(int cx)
 
1236
{
 
1237
    struct ebWindow *lw;
 
1238
    int i, start, end, fardol;
 
1239
    if(!cxCompare(cx))
 
1240
        return false;
 
1241
    if(!cxActive(cx))
 
1242
        return false;
 
1243
    fileSize = 0;
 
1244
    lw = sessionList[cx].lw;
 
1245
    fardol = lw->dol;
 
1246
    if(!fardol)
 
1247
        return true;
 
1248
    if(!linesComing(fardol))
 
1249
        return false;
 
1250
    if(cw->dol == endRange)
 
1251
        cw->nlMode = false;
 
1252
    start = end = textLinesCount;
 
1253
    for(i = 1; i <= fardol; ++i) {
 
1254
        pst p = fetchLineContext(i, (lw->dirMode ? -1 : 1), cx);
 
1255
        int len = pstLength(p);
 
1256
        pst q;
 
1257
        if(lw->dirMode) {
 
1258
            char *suf = dirSuffixContext(i, cx);
 
1259
            char *q = allocMem(len + 3);
 
1260
            memcpy(q, p, len);
 
1261
            --len;
 
1262
            strcat(suf, "\n");
 
1263
            strcpy(q + len, suf);
 
1264
            len = strlen(q);
 
1265
            p = (pst) q;
 
1266
        }
 
1267
        textLines[end++] = p;
 
1268
        fileSize += len;
 
1269
    }                           /* loop over lines in the "other" context */
 
1270
    textLinesCount = end;
 
1271
    addToMap(start, end, endRange);
 
1272
    if(lw->nlMode) {
 
1273
        --fileSize;
 
1274
        if(cw->dol == endRange)
 
1275
            cw->nlMode = true;
 
1276
    }
 
1277
    if(binaryDetect & !cw->binMode && lw->binMode) {
 
1278
        cw->binMode = true;
 
1279
        printf("binary data\n");
 
1280
    }
 
1281
    return true;
 
1282
}                               /* readContext */
 
1283
 
 
1284
static bool
 
1285
writeContext(int cx)
 
1286
{
 
1287
    struct ebWindow *lw;
 
1288
    int i, j, len;
 
1289
    char *newmap;
 
1290
    pst p;
 
1291
    int fardol = endRange - startRange + 1;
 
1292
    if(!startRange)
 
1293
        fardol = 0;
 
1294
    if(!linesComing(fardol))
 
1295
        return false;
 
1296
    if(!cxCompare(cx))
 
1297
        return false;
 
1298
    if(cxActive(cx) && !cxQuit(cx, 2))
 
1299
        return false;
 
1300
 
 
1301
    cxInit(cx);
 
1302
    lw = sessionList[cx].lw;
 
1303
    fileSize = 0;
 
1304
    if(startRange) {
 
1305
        newmap = allocMem((fardol + 1) * LNWIDTH + 1);
 
1306
        strcpy(newmap, LNSPACE);
 
1307
        for(i = startRange, j = 1; i <= endRange; ++i, ++j) {
 
1308
            p = fetchLine(i, (cw->dirMode ? -1 : 1));
 
1309
            len = pstLength(p);
 
1310
            sprintf(newmap + j * LNWIDTH, LNFORMAT, textLinesCount);
 
1311
            if(cw->dirMode) {
 
1312
                pst q;
 
1313
                char *suf = dirSuffix(i);
 
1314
                q = allocMem(len + 3);
 
1315
                memcpy(q, p, len);
 
1316
                --len;
 
1317
                strcat(suf, "\n");
 
1318
                strcpy((char *)q + len, suf);
 
1319
                len = strlen((char *)q);
 
1320
                p = q;
 
1321
            }
 
1322
            textLines[textLinesCount++] = p;
 
1323
            fileSize += len;
 
1324
        }
 
1325
        lw->map = newmap;
 
1326
        lw->binMode = cw->binMode;
 
1327
        if(cw->nlMode && endRange == cw->dol) {
 
1328
            lw->nlMode = true;
 
1329
            --fileSize;
 
1330
        }
 
1331
    }                           /* nonempty range */
 
1332
    lw->dot = lw->dol = fardol;
 
1333
 
 
1334
    return true;
 
1335
}                               /* writeContext */
 
1336
 
 
1337
static void
 
1338
debrowseSuffix(char *s)
 
1339
{
 
1340
    if(!s)
 
1341
        return;
 
1342
    while(*s) {
 
1343
        if(*s == '.' && stringEqual(s, ".browse")) {
 
1344
            *s = 0;
 
1345
            return;
 
1346
        }
 
1347
        ++s;
 
1348
    }
 
1349
}                               /* debrowseSuffix */
 
1350
 
 
1351
static bool
 
1352
shellEscape(const char *line)
 
1353
{
 
1354
    char *newline, *s;
 
1355
    const char *t;
 
1356
    pst p;
 
1357
    char key;
 
1358
    int linesize, pass, n;
 
1359
    char *sh;
 
1360
    char subshell[ABSPATH];
 
1361
 
 
1362
/* preferred shell */
 
1363
    sh = getenv("SHELL");
 
1364
    if(!sh || !*sh)
 
1365
        sh = "/bin/sh";
 
1366
 
 
1367
    linesize = strlen(line);
 
1368
    if(!linesize) {
 
1369
/* interactive shell */
 
1370
        if(!isInteractive) {
 
1371
            setError("session is not interactive");
 
1372
            return false;
 
1373
        }
 
1374
#ifdef DOSLIKE
 
1375
        system(line);           /* don't know how to spawn a shell here */
 
1376
#else
 
1377
        sprintf(subshell, "exec %s -i", sh);
 
1378
        system(subshell);
 
1379
#endif
 
1380
        printf("ok\n");
 
1381
        return true;
 
1382
    }
 
1383
 
 
1384
/* Make substitutions within the command line. */
 
1385
    for(pass = 1; pass <= 2; ++pass) {
 
1386
        for(t = line; *t; ++t) {
 
1387
            if(*t != '\'')
 
1388
                goto addchar;
 
1389
            if(t > line && isalnumByte(t[-1]))
 
1390
                goto addchar;
 
1391
            key = t[1];
 
1392
            if(key && isalnumByte(t[2]))
 
1393
                goto addchar;
 
1394
 
 
1395
            if(key == '_') {
 
1396
                ++t;
 
1397
                if(!cw->fileName)
 
1398
                    continue;
 
1399
                if(pass == 1) {
 
1400
                    linesize += strlen(cw->fileName);
 
1401
                } else {
 
1402
                    strcpy(s, cw->fileName);
 
1403
                    s += strlen(s);
 
1404
                }
 
1405
                continue;
 
1406
            }
 
1407
            /* '_ filename */
 
1408
            if(key == '.' || key == '-' || key == '+') {
 
1409
                n = cw->dot;
 
1410
                if(key == '-')
 
1411
                    --n;
 
1412
                if(key == '+')
 
1413
                    ++n;
 
1414
                if(n > cw->dol || n == 0) {
 
1415
                    setError("line %c is out of range", key);
 
1416
                    return false;
 
1417
                }
 
1418
              frombuf:
 
1419
                ++t;
 
1420
                if(pass == 1) {
 
1421
                    p = fetchLine(n, -1);
 
1422
                    linesize += pstLength(p) - 1;
 
1423
                } else {
 
1424
                    p = fetchLine(n, 1);
 
1425
                    if(perl2c((char *)p)) {
 
1426
                        free(p);
 
1427
                        setError("cannot embed nulls in a shell command");
 
1428
                        return false;
 
1429
                    }
 
1430
                    strcpy(s, (char *)p);
 
1431
                    s += strlen(s);
 
1432
                    free(p);
 
1433
                }
 
1434
                continue;
 
1435
            }
 
1436
            /* '. current line */
 
1437
            if(islowerByte(key)) {
 
1438
                n = cw->labels[key - 'a'];
 
1439
                if(!n) {
 
1440
                    setError("label %c not set", key);
 
1441
                    return false;
 
1442
                }
 
1443
                goto frombuf;
 
1444
            }
 
1445
            /* 'x the line labeled x */
 
1446
          addchar:
 
1447
            if(pass == 1)
 
1448
                ++linesize;
 
1449
            else
 
1450
                *s++ = *t;
 
1451
        }                       /* loop over chars */
 
1452
 
 
1453
        if(pass == 1)
 
1454
            s = newline = allocMem(linesize + 1);
 
1455
        else
 
1456
            *s = 0;
 
1457
    }                           /* two passes */
 
1458
 
 
1459
/* Run the command.  Note that this routine returns success
 
1460
 * even if the shell command failed.
 
1461
 * Edbrowse succeeds if it is *able* to run the system command. */
 
1462
    system(newline);
 
1463
    printf("ok\n");
 
1464
    free(newline);
 
1465
    return true;
 
1466
}                               /* shellEscape */
 
1467
 
 
1468
/* Valid delimiters for search/substitute.
 
1469
 * note that \ is conspicuously absent, not a valid delimiter.
 
1470
 * I alsso avoid nestable delimiters such as parentheses.
 
1471
 * And no alphanumerics please -- too confusing.
 
1472
 * ed allows it, but I don't. */
 
1473
static const char valid_delim[] = "_=!;:`\"',/?@-";
 
1474
/* And a valid char for starting a line address */
 
1475
static const char valid_laddr[] = "0123456789-'.$+/?";
 
1476
 
 
1477
/* Check the syntax of a regular expression, before we pass it to pcre.
 
1478
 * The first char is the delimiter -- we stop at the next delimiter.
 
1479
 * A pointer to the second delimiter is returned, along with the
 
1480
 * (possibly reformatted) regular expression. */
 
1481
 
 
1482
static bool
 
1483
regexpCheck(const char *line, bool isleft, bool ebmuck,
 
1484
   char **rexp, const char **split)
 
1485
{                               /* result parameters */
 
1486
    static char re[MAXRE + 20];
 
1487
    const char *start;
 
1488
    char *e = re;
 
1489
    char c, d;
 
1490
/* Remember whether a char is "on deck", ready to be modified by * etc. */
 
1491
    bool ondeck = false;
 
1492
    bool was_ques = true;       /* previous modifier was ? */
 
1493
    bool cc = false;            /* are we in a [...] character class */
 
1494
    int mod;                    /* length of modifier */
 
1495
    int paren = 0;              /* nesting level of parentheses */
 
1496
/* We wouldn't be here if the line was empty. */
 
1497
    char delim = *line++;
 
1498
 
 
1499
    *rexp = re;
 
1500
    if(!strchr(valid_delim, delim)) {
 
1501
        setError("invalid delimiter");
 
1502
        return false;
 
1503
    }
 
1504
    start = line;
 
1505
 
 
1506
    c = *line;
 
1507
    if(ebmuck) {
 
1508
        if(isleft) {
 
1509
            if(c == delim || c == 0) {
 
1510
                if(!cw->lhs_yes) {
 
1511
                    setError("no remembered search string");
 
1512
                    return false;
 
1513
                }
 
1514
                strcpy(re, cw->lhs);
 
1515
                *split = line;
 
1516
                return true;
 
1517
            }
 
1518
/* Interpret lead * or lone [ as literal */
 
1519
            if(strchr("*?+", c) || c == '[' && !line[1]) {
 
1520
                *e++ = '\\';
 
1521
                *e++ = c;
 
1522
                ++line;
 
1523
                ondeck = true;
 
1524
            }
 
1525
        } else if(c == '%' && (line[1] == delim || line[1] == 0)) {
 
1526
            if(!cw->rhs_yes) {
 
1527
                setError("no remembered replacement string");
 
1528
                return false;
 
1529
            }
 
1530
            strcpy(re, cw->rhs);
 
1531
            *split = line + 1;
 
1532
            return true;
 
1533
        }
 
1534
    }
 
1535
    /* ebmuck tricks */
 
1536
    while(c = *line) {
 
1537
        if(e >= re + MAXRE - 3) {
 
1538
            setError("regular expression too long");
 
1539
            return false;
 
1540
        }
 
1541
        d = line[1];
 
1542
 
 
1543
        if(c == '\\') {
 
1544
            line += 2;
 
1545
            if(d == 0) {
 
1546
                setError("line ends in backslash");
 
1547
                return false;
 
1548
            }
 
1549
            ondeck = true;
 
1550
            was_ques = false;
 
1551
/* I can't think of any reason to remove the escaping \ from any character,
 
1552
 * except ()|, where we reverse the sense of escape. */
 
1553
            if(ebmuck && isleft && !cc && (d == '(' || d == ')' || d == '|')) {
 
1554
                if(d == '|')
 
1555
                    ondeck = false, was_ques = true;
 
1556
                if(d == '(')
 
1557
                    ++paren, ondeck = false, was_ques = true;
 
1558
                if(d == ')')
 
1559
                    --paren;
 
1560
                if(paren < 0) {
 
1561
                    setError("Unexpected closing )");
 
1562
                    return false;
 
1563
                }
 
1564
                *e++ = d;
 
1565
                continue;
 
1566
            }
 
1567
            if(d == delim || ebmuck && !isleft && d == '&') {
 
1568
                *e++ = d;
 
1569
                continue;
 
1570
            }
 
1571
/* Nothing special; we retain the escape character. */
 
1572
            *e++ = c;
 
1573
            if(isleft && d >= '0' && d <= '7' && (*line < '0' || *line > '7'))
 
1574
                *e++ = '0';
 
1575
            *e++ = d;
 
1576
            continue;
 
1577
        }
 
1578
 
 
1579
        /* escaping backslash */
 
1580
        /* Break out if we hit the delimiter. */
 
1581
        if(c == delim)
 
1582
            break;
 
1583
 
 
1584
/* Remember, I reverse the sense of ()| */
 
1585
        if(isleft) {
 
1586
            if(ebmuck && (c == '(' || c == ')' || c == '|') || c == '^' && line != start && !cc)        /* don't know why we have to do this */
 
1587
                *e++ = '\\';
 
1588
            if(c == '$' && d && d != delim)
 
1589
                *e++ = '\\';
 
1590
        }
 
1591
 
 
1592
        if(c == '$' && !isleft && isdigitByte(d)) {
 
1593
            if(d == '0' || isdigitByte(line[2])) {
 
1594
                setError("replacement string can only use $1 through $9");
 
1595
                return false;
 
1596
            }
 
1597
        }
 
1598
        /* dollar digit on the right */
 
1599
        if(!isleft && c == '&' && ebmuck) {
 
1600
            *e++ = '$';
 
1601
            *e++ = '0';
 
1602
            ++line;
 
1603
            continue;
 
1604
        }
 
1605
 
 
1606
/* push the character */
 
1607
        *e++ = c;
 
1608
        ++line;
 
1609
 
 
1610
/* No more checks for the rhs */
 
1611
        if(!isleft)
 
1612
            continue;
 
1613
 
 
1614
        if(cc) {                /* character class */
 
1615
            if(c == ']')
 
1616
                cc = false;
 
1617
            continue;
 
1618
        }
 
1619
        if(c == '[')
 
1620
            cc = true;
 
1621
 
 
1622
/* Skip all these checks for javascript,
 
1623
 * it probably has the expression right anyways. */
 
1624
        if(!ebmuck)
 
1625
            continue;
 
1626
 
 
1627
/* Modifiers must have a preceding character.
 
1628
 * Except ? which can reduce the greediness of the others. */
 
1629
        if(c == '?' && !was_ques) {
 
1630
            ondeck = false;
 
1631
            was_ques = true;
 
1632
            continue;
 
1633
        }
 
1634
 
 
1635
        mod = 0;
 
1636
        if(c == '?' || c == '*' || c == '+')
 
1637
            mod = 1;
 
1638
        if(c == '{' && isdigitByte(d)) {
 
1639
            const char *t = line + 1;
 
1640
            while(isdigitByte(*t))
 
1641
                ++t;
 
1642
            if(*t == ',')
 
1643
                ++t;
 
1644
            while(isdigitByte(*t))
 
1645
                ++t;
 
1646
            if(*t == '}')
 
1647
                mod = t + 2 - line;
 
1648
        }
 
1649
        if(mod) {
 
1650
            --mod;
 
1651
            if(mod) {
 
1652
                strncpy(e, line, mod);
 
1653
                e += mod;
 
1654
                line += mod;
 
1655
            }
 
1656
            if(!ondeck) {
 
1657
                *e = 0;
 
1658
                setError("%s modifier has no preceding character", e - mod - 1);
 
1659
                return false;
 
1660
            }
 
1661
            ondeck = false;
 
1662
            continue;
 
1663
        }
 
1664
        /* modifier */
 
1665
        ondeck = true;
 
1666
        was_ques = false;
 
1667
    }                           /* loop over chars in the pattern */
 
1668
    *e = 0;
 
1669
 
 
1670
    *split = line;
 
1671
 
 
1672
    if(ebmuck) {
 
1673
        if(cc) {
 
1674
            setError("no closing ]");
 
1675
            return false;
 
1676
        }
 
1677
        if(paren) {
 
1678
            setError("no closing )");
 
1679
            return false;
 
1680
        }
 
1681
 
 
1682
        if(isleft) {
 
1683
            cw->lhs_yes = true;
 
1684
            strcpy(cw->lhs, re);
 
1685
        } else {
 
1686
            cw->rhs_yes = true;
 
1687
            strcpy(cw->rhs, re);
 
1688
        }
 
1689
    }
 
1690
 
 
1691
    debugPrint(7, "%s regexp %s", (isleft ? "search" : "replace"), re);
 
1692
    return true;
 
1693
}                               /* regexpCheck */
 
1694
 
 
1695
/* regexp variables */
 
1696
static int re_count;
 
1697
static const char *re_error;
 
1698
static int re_offset;
 
1699
static int re_opt;
 
1700
static int re_vector[11 * 3];
 
1701
static pcre *re_cc;             /* compiled */
 
1702
 
 
1703
/* Get the start or end of a range.
 
1704
 * Pass the line containing the address. */
 
1705
static bool
 
1706
getRangePart(const char *line, int *lineno, const char **split)
 
1707
{                               /* result parameters */
 
1708
    int ln = cw->dot;           /* this is where we start */
 
1709
    char first = *line;
 
1710
 
 
1711
    if(isdigitByte(first)) {
 
1712
        ln = strtol(line, (char **)&line, 10);
 
1713
    } else if(first == '.') {
 
1714
        ++line;
 
1715
/* ln is already set */
 
1716
    } else if(first == '$') {
 
1717
        ++line;
 
1718
        ln = cw->dol;
 
1719
    } else if(first == '\'' && islowerByte(line[1])) {
 
1720
        ln = cw->labels[line[1] - 'a'];
 
1721
        if(!ln) {
 
1722
            setError("label %c not set", line[1]);
 
1723
            return false;
 
1724
        }
 
1725
        line += 2;
 
1726
    } else if(first == '/' || first == '?') {
 
1727
 
 
1728
        char *re;               /* regular expression */
 
1729
        bool ci = caseInsensitive;
 
1730
        char incr;              /* forward or back */
 
1731
/* Don't look through an empty buffer. */
 
1732
        if(cw->dol == 0) {
 
1733
            setError("empty buffer");
 
1734
            return false;
 
1735
        }
 
1736
        if(!regexpCheck(line, true, true, &re, &line))
 
1737
            return false;
 
1738
        if(*line == first) {
 
1739
            ++line;
 
1740
            if(*line == 'i')
 
1741
                ci = true, ++line;
 
1742
        }
 
1743
 
 
1744
        /* second delimiter */
 
1745
        /* Earlier versions of pcre don't support this. */
 
1746
        /* re_opt = PCRE_NO_AUTO_CAPTURE; */
 
1747
        re_opt = 0;
 
1748
        if(ci)
 
1749
            re_opt |= PCRE_CASELESS;
 
1750
        re_cc = pcre_compile(re, re_opt, &re_error, &re_offset, 0);
 
1751
        if(!re_cc) {
 
1752
            setError("error in regular expression, %s", re_error);
 
1753
            return false;
 
1754
        }
 
1755
/* We should probably study the pattern, if the file is large.
 
1756
 * But then again, it's probably not worth it,
 
1757
 * since the expressions are simple, and the lines are short. */
 
1758
        incr = (first == '/' ? 1 : -1);
 
1759
        while(true) {
 
1760
            char *subject;
 
1761
            ln += incr;
 
1762
            if(ln > cw->dol)
 
1763
                ln = 1;
 
1764
            if(ln == 0)
 
1765
                ln = cw->dol;
 
1766
            subject = (char *)fetchLine(ln, 1);
 
1767
            re_count =
 
1768
               pcre_exec(re_cc, 0, subject, pstLength((pst) subject) - 1, 0, 0,
 
1769
               re_vector, 33);
 
1770
            free(subject);
 
1771
            if(re_count < -1) {
 
1772
                pcre_free(re_cc);
 
1773
                setError
 
1774
                   ("unexpected error while evaluating the regular expression at line %d",
 
1775
                   ln);
 
1776
                return (globSub = false);
 
1777
            }
 
1778
            if(re_count >= 0)
 
1779
                break;
 
1780
            if(ln == cw->dot) {
 
1781
                pcre_free(re_cc);
 
1782
                setError("search string not found");
 
1783
                return false;
 
1784
            }
 
1785
        }                       /* loop over lines */
 
1786
        pcre_free(re_cc);
 
1787
/* and ln is the line that matches */
 
1788
    }
 
1789
 
 
1790
    /* search pattern */
 
1791
    /* Now add or subtract from this number */
 
1792
    while((first = *line) == '+' || first == '-') {
 
1793
        int add = 1;
 
1794
        ++line;
 
1795
        if(isdigitByte(*line))
 
1796
            add = strtol(line, (char **)&line, 10);
 
1797
        ln += (first == '+' ? add : -add);
 
1798
    }
 
1799
 
 
1800
    if(ln > cw->dol) {
 
1801
        setError("line number too large");
 
1802
        return false;
 
1803
    }
 
1804
    if(ln < 0) {
 
1805
        setError("negative line number");
 
1806
        return false;
 
1807
    }
 
1808
 
 
1809
    *lineno = ln;
 
1810
    *split = line;
 
1811
    return true;
 
1812
}                               /* getRangePart */
 
1813
 
 
1814
/* Apply a regular expression to each line, and then execute
 
1815
 * a command for each matching, or nonmatching, line.
 
1816
 * This is the global feature, g/re/p, which gives us the word grep. */
 
1817
static bool
 
1818
doGlobal(const char *line)
 
1819
{
 
1820
    int gcnt = 0;               /* global count */
 
1821
    bool ci = caseInsensitive;
 
1822
    bool change;
 
1823
    char delim = *line;
 
1824
    char *t;
 
1825
    char *re;                   /* regular expression */
 
1826
    int i, origdot, yesdot, nodot;
 
1827
 
 
1828
    if(!delim) {
 
1829
        setError("no regular expression after %c", icmd);
 
1830
        return false;
 
1831
    }
 
1832
 
 
1833
    if(!regexpCheck(line, true, true, &re, &line))
 
1834
        return false;
 
1835
    if(*line != delim) {
 
1836
        setError("missing delimiter");
 
1837
        return false;
 
1838
    }
 
1839
    ++line;
 
1840
    if(*line == 'i')
 
1841
        ++line, ci = true;
 
1842
    skipWhite(&line);
 
1843
 
 
1844
/* clean up any previous stars */
 
1845
    for(t = cw->map + LNWIDTH; *t; t += LNWIDTH)
 
1846
        t[6] = ' ';
 
1847
 
 
1848
/* Find the lines that match the pattern. */
 
1849
    re_opt = 0;
 
1850
    if(ci)
 
1851
        re_opt |= PCRE_CASELESS;
 
1852
    re_cc = pcre_compile(re, re_opt, &re_error, &re_offset, 0);
 
1853
    if(!re_cc) {
 
1854
        setError("error in regular expression, %s", re_error);
 
1855
        return false;
 
1856
    }
 
1857
    for(i = startRange; i <= endRange; ++i) {
 
1858
        char *subject = (char *)fetchLine(i, 1);
 
1859
        re_count =
 
1860
           pcre_exec(re_cc, 0, subject, pstLength((pst) subject), 0, 0,
 
1861
           re_vector, 33);
 
1862
        free(subject);
 
1863
        if(re_count < -1) {
 
1864
            pcre_free(re_cc);
 
1865
            setError
 
1866
               ("unexpected error while evaluating the regular expression at line %d",
 
1867
               i);
 
1868
            return false;
 
1869
        }
 
1870
        if(re_count < 0 && cmd == 'v' || re_count >= 0 && cmd == 'g') {
 
1871
            ++gcnt;
 
1872
            cw->map[i * LNWIDTH + 6] = '*';
 
1873
        }
 
1874
    }                           /* loop over line */
 
1875
    pcre_free(re_cc);
 
1876
 
 
1877
    if(!gcnt) {
 
1878
        setError(cmd ==
 
1879
           'g' ? "no lines match the g pattern" :
 
1880
           "all lines match the v pattern");
 
1881
        return false;
 
1882
    }
 
1883
 
 
1884
/* apply the subcommand to every line with a star */
 
1885
    globSub = true;
 
1886
    setError(0);
 
1887
    if(!*line)
 
1888
        line = "p";
 
1889
    origdot = cw->dot;
 
1890
    yesdot = nodot = 0;
 
1891
    change = true;
 
1892
    while(gcnt && change) {
 
1893
        change = false;         /* kinda like bubble sort */
 
1894
        for(i = 1; i <= cw->dol; ++i) {
 
1895
            t = cw->map + i * LNWIDTH + 6;
 
1896
            if(*t != '*')
 
1897
                continue;
 
1898
            if(intFlag)
 
1899
                goto done;
 
1900
            change = true, --gcnt;
 
1901
            *t = ' ';
 
1902
            cw->dot = i;        /* so we can run the command at this line */
 
1903
            if(runCommand(line)) {
 
1904
                yesdot = cw->dot;
 
1905
/* try this line again, in case we deleted or moved it somewhere else */
 
1906
                if(undoable)
 
1907
                    --i, t -= LNWIDTH;
 
1908
            } else {
 
1909
/* error in subcommand might turn global flag off */
 
1910
                if(!globSub) {
 
1911
                    nodot = i, yesdot = 0;
 
1912
                    goto done;
 
1913
                }               /* serious error */
 
1914
            }                   /* subcommand succeeds or fails */
 
1915
        }                       /* loop over lines */
 
1916
    }                           /* loop making changes */
 
1917
  done:
 
1918
 
 
1919
    globSub = false;
 
1920
/* yesdot could be 0, even on success, if all lines are deleted via g/re/d */
 
1921
    if(yesdot || !cw->dol) {
 
1922
        cw->dot = yesdot;
 
1923
        if((cmd == 's' || cmd == 'i') && subPrint == 1)
 
1924
            printDot();
 
1925
    } else if(nodot) {
 
1926
        cw->dot = nodot;
 
1927
    } else {
 
1928
        cw->dot = origdot;
 
1929
        if(!errorMsg[0])
 
1930
            setError("none of the marked lines were successfully modified");
 
1931
    }
 
1932
    if(!errorMsg[0] && intFlag)
 
1933
        setError(opint);
 
1934
    return (errorMsg[0] == 0);
 
1935
}                               /* doGlobal */
 
1936
 
 
1937
static void
 
1938
fieldNumProblem(const char *desc, char c, int n, int nt)
 
1939
{
 
1940
    if(!nt) {
 
1941
        setError("no %s present", desc);
 
1942
        return;
 
1943
    }
 
1944
    if(!n) {
 
1945
        setError("multiple %s present, please use %c1 through %c%d", desc, c, c,
 
1946
           nt);
 
1947
        return;
 
1948
    }
 
1949
    if(nt > 1)
 
1950
        setError("%d is out of range, please use %c1 through %c%d", n, c, c,
 
1951
           nt);
 
1952
    else
 
1953
        setError("%d is out of range, please use %c1, or simply %c", n, c, c);
 
1954
}                               /* fieldNumProblem */
 
1955
 
 
1956
/* Perform a substitution on a given line.
 
1957
 * The lhs has been compiled, and the rhs is passed in for replacement.
 
1958
 * Refer to the static variable re_cc for the compiled lhs.
 
1959
 * The replacement line is static, with a fixed length.
 
1960
 * Return 0 for no match, 1 for a replacement, and -1 for a real problem. */
 
1961
 
 
1962
char replaceLine[REPLACELINELEN];
 
1963
static char *replaceLineEnd;
 
1964
static int replaceLineLen;
 
1965
static int
 
1966
replaceText(const char *line, int len, const char *rhs,
 
1967
   bool ebmuck, int nth, bool global, int ln)
 
1968
{
 
1969
    int offset = 0, lastoffset, instance = 0;
 
1970
    int span;
 
1971
    char *r = replaceLine;
 
1972
    char *r_end = replaceLine + REPLACELINELEN - 8;
 
1973
    const char *s = line, *s_end, *t;
 
1974
    char c, d;
 
1975
 
 
1976
    while(true) {
 
1977
/* find the next match */
 
1978
        re_count = pcre_exec(re_cc, 0, line, len, offset, 0, re_vector, 33);
 
1979
        if(re_count < -1) {
 
1980
            setError
 
1981
               ("unexpected error while evaluating the regular expression at line %d",
 
1982
               ln);
 
1983
            return -1;
 
1984
        }
 
1985
        if(re_count < 0)
 
1986
            break;
 
1987
        ++instance;             /* found another match */
 
1988
        lastoffset = offset;
 
1989
        offset = re_vector[1];  /* ready for next iteration */
 
1990
        if(offset == lastoffset && (nth > 1 || global)) {
 
1991
            setError("cannot replace multiple instances of the empty string");
 
1992
            return -1;
 
1993
        }
 
1994
        if(!global &&instance != nth)
 
1995
            continue;
 
1996
 
 
1997
/* copy up to the match point */
 
1998
        s_end = line + re_vector[0];
 
1999
        span = s_end - s;
 
2000
        if(r + span >= r_end)
 
2001
            goto longvar;
 
2002
        memcpy(r, s, span);
 
2003
        r += span;
 
2004
        s = line + offset;
 
2005
 
 
2006
/* Now copy over the rhs */
 
2007
/* Special case lc mc uc */
 
2008
        if(ebmuck && (rhs[0] == 'l' || rhs[0] == 'm' || rhs[0] == 'u') &&
 
2009
           rhs[1] == 'c' && rhs[2] == 0) {
 
2010
            span = re_vector[1] - re_vector[0];
 
2011
            if(r + span + 1 > r_end)
 
2012
                goto longvar;
 
2013
            memcpy(r, line + re_vector[0], span);
 
2014
            r[span] = 0;
 
2015
            caseShift(r, rhs[0]);
 
2016
            r += span;
 
2017
            if(!global)
 
2018
                break;
 
2019
            continue;
 
2020
        }
 
2021
 
 
2022
        /* case shift */
 
2023
        /* copy rhs, watching for $n */
 
2024
        t = rhs;
 
2025
        while(c = *t) {
 
2026
            if(r >= r_end)
 
2027
                goto longvar;
 
2028
            d = t[1];
 
2029
            if(c == '\\') {
 
2030
                t += 2;
 
2031
                if(d == '$') {
 
2032
                    *r++ = d;
 
2033
                    continue;
 
2034
                }
 
2035
                if(d == 'n') {
 
2036
                    *r++ = '\n';
 
2037
                    continue;
 
2038
                }
 
2039
                if(d == 't') {
 
2040
                    *r++ = '\t';
 
2041
                    continue;
 
2042
                }
 
2043
                if(d == 'b') {
 
2044
                    *r++ = '\b';
 
2045
                    continue;
 
2046
                }
 
2047
                if(d == 'r') {
 
2048
                    *r++ = '\r';
 
2049
                    continue;
 
2050
                }
 
2051
                if(d == 'f') {
 
2052
                    *r++ = '\f';
 
2053
                    continue;
 
2054
                }
 
2055
                if(d == 'a') {
 
2056
                    *r++ = '\a';
 
2057
                    continue;
 
2058
                }
 
2059
                if(d >= '0' && d <= '7') {
 
2060
                    int octal = d - '0';
 
2061
                    d = *t;
 
2062
                    if(d >= '0' && d <= '7') {
 
2063
                        ++t;
 
2064
                        octal = 8 * octal + d - '0';
 
2065
                        d = *t;
 
2066
                        if(d >= '0' && d <= '7') {
 
2067
                            ++t;
 
2068
                            octal = 8 * octal + d - '0';
 
2069
                        }
 
2070
                    }
 
2071
                    *r++ = octal;
 
2072
                    continue;
 
2073
                }               /* octal */
 
2074
                if(!ebmuck)
 
2075
                    *r++ = '\\';
 
2076
                *r++ = d;
 
2077
                continue;
 
2078
            }                   /* backslash */
 
2079
            if(c == '$' && isdigitByte(d)) {
 
2080
                int y, z;
 
2081
                t += 2;
 
2082
                d -= '0';
 
2083
                if(d > re_count)
 
2084
                    continue;
 
2085
                y = re_vector[2 * d];
 
2086
                z = re_vector[2 * d + 1];
 
2087
                if(y < 0)
 
2088
                    continue;
 
2089
                span = z - y;
 
2090
                if(r + span >= r_end)
 
2091
                    goto longvar;
 
2092
                memcpy(r, line + y, span);
 
2093
                r += span;
 
2094
                continue;
 
2095
            }
 
2096
            *r++ = c;
 
2097
            ++t;
 
2098
        }
 
2099
 
 
2100
        if(!global)
 
2101
            break;
 
2102
    }                           /* loop matching the regular expression */
 
2103
 
 
2104
    if(!instance)
 
2105
        return false;
 
2106
    if(!global &&instance < nth)
 
2107
        return false;
 
2108
 
 
2109
/* We got a match, copy the last span. */
 
2110
    s_end = line + len;
 
2111
    span = s_end - s;
 
2112
    if(r + span >= r_end)
 
2113
        goto longvar;
 
2114
    memcpy(r, s, span);
 
2115
    r += span;
 
2116
    replaceLineEnd = r;
 
2117
    replaceLineLen = r - replaceLine;
 
2118
    return true;
 
2119
 
 
2120
  longvar:
 
2121
    setError("line exceeds %d bytes after substitution", REPLACELINELEN);
 
2122
    return -1;
 
2123
}                               /* replaceText */
 
2124
 
 
2125
/* Substitute text on the lines in startRange through endRange.
 
2126
 * We could be changing the text in an input field.
 
2127
 * If so, we'll call infReplace().
 
2128
 * Also, we might be indirectory mode, whence we must rename the file.
 
2129
 * This is a complicated function!
 
2130
 * The return can be true or false, with the usual meaning,
 
2131
 * but also a return of -1, which is failure,
 
2132
 * and an indication that we need to abort any g// in progress.
 
2133
 * It's a serious problem. */
 
2134
 
 
2135
static int
 
2136
substituteText(const char *line)
 
2137
{
 
2138
    int whichField = 0;
 
2139
    bool bl_mode = false;       /* running the bl command */
 
2140
    bool g_mode = false;        /* s/x/y/g */
 
2141
    bool ci = caseInsensitive;
 
2142
    bool save_nlMode;
 
2143
    char c, *s, *t;
 
2144
    int nth = 0;                /* s/x/y/7 */
 
2145
    int lastSubst = 0;          /* last successful substitution */
 
2146
    char *re;                   /* the parsed regular expression */
 
2147
    int ln;                     /* line number */
 
2148
    int j, linecount, slashcount, nullcount, tagno, total;
 
2149
    char lhs[MAXRE], rhs[MAXRE];
 
2150
 
 
2151
    subPrint = 1;               /* default is to print the last line substituted */
 
2152
    re_cc = 0;
 
2153
    if(stringEqual(line, "`bl"))
 
2154
        bl_mode = true, breakLineSetup();
 
2155
 
 
2156
    if(!bl_mode) {
 
2157
/* watch for s2/x/y/ for the second input field */
 
2158
        if(isdigitByte(*line))
 
2159
            whichField = strtol(line, (char **)&line, 10);
 
2160
        if(!*line) {
 
2161
            setError("no regular expression after %c", icmd);
 
2162
            return -1;
 
2163
        }
 
2164
 
 
2165
        if(cw->dirMode && !dirWrite) {
 
2166
            setError
 
2167
               ("directories are readonly, type dw to enable directory writes");
 
2168
            return -1;
 
2169
        }
 
2170
 
 
2171
        if(!regexpCheck(line, true, true, &re, &line))
 
2172
            return -1;
 
2173
        strcpy(lhs, re);
 
2174
        if(!*line) {
 
2175
            setError("missing delimiter");
 
2176
            return -1;
 
2177
        }
 
2178
        if(!regexpCheck(line, false, true, &re, &line))
 
2179
            return -1;
 
2180
        strcpy(rhs, re);
 
2181
 
 
2182
        if(*line) {             /* third delimiter */
 
2183
            ++line;
 
2184
            subPrint = 0;
 
2185
            while(c = *line) {
 
2186
                if(c == 'g') {
 
2187
                    g_mode = true;
 
2188
                    ++line;
 
2189
                    continue;
 
2190
                }
 
2191
                if(c == 'i') {
 
2192
                    ci = true;
 
2193
                    ++line;
 
2194
                    continue;
 
2195
                }
 
2196
                if(c == 'p') {
 
2197
                    subPrint = 2;
 
2198
                    ++line;
 
2199
                    continue;
 
2200
                }
 
2201
                if(isdigitByte(c)) {
 
2202
                    if(nth) {
 
2203
                        setError("multiple numbers after the third delimiter");
 
2204
                        return -1;
 
2205
                    }
 
2206
                    nth = strtol(line, (char **)&line, 10);
 
2207
                    continue;
 
2208
                }               /* number */
 
2209
                setError
 
2210
                   ("unexpected substitution suffix after the third delimiter");
 
2211
                return -1;
 
2212
            }                   /* loop gathering suffix flags */
 
2213
            if(g_mode && nth) {
 
2214
                setError
 
2215
                   ("cannot use both a numeric suffix and the `g' suffix simultaneously");
 
2216
                return -1;
 
2217
            }
 
2218
        }                       /* closing delimiter */
 
2219
        if(nth == 0 && !g_mode)
 
2220
            nth = 1;
 
2221
 
 
2222
        re_opt = 0;
 
2223
        if(ci)
 
2224
            re_opt |= PCRE_CASELESS;
 
2225
        re_cc = pcre_compile(lhs, re_opt, &re_error, &re_offset, 0);
 
2226
        if(!re_cc) {
 
2227
            setError("error in regular expression, %s", re_error);
 
2228
            return -1;
 
2229
        }
 
2230
    } else {
 
2231
 
 
2232
        subPrint = 0;
 
2233
    }                           /* bl_mode or not */
 
2234
 
 
2235
    if(!globSub)
 
2236
        setError(0);
 
2237
 
 
2238
    for(ln = startRange; ln <= endRange && !intFlag; ++ln) {
 
2239
        char *p = (char *)fetchLine(ln, -1);
 
2240
        int len = pstLength((pst) p);
 
2241
 
 
2242
        if(bl_mode) {
 
2243
            int newlen;
 
2244
            if(!breakLine(p, len, &newlen)) {
 
2245
                setError
 
2246
                   ("sorry, cannot apply the bl command to lines longer than %d bytes",
 
2247
                   REPLACELINELEN);
 
2248
                return -1;
 
2249
            }
 
2250
/* empty line is not allowed */
 
2251
            if(!newlen)
 
2252
                replaceLine[newlen++] = '\n';
 
2253
/* perhaps no changes were made */
 
2254
            if(newlen == len && !memcmp(p, replaceLine, len))
 
2255
                continue;
 
2256
            replaceLineLen = newlen;
 
2257
            replaceLineEnd = replaceLine + newlen;
 
2258
/* But the regular substitute doesn't have the \n on the end.
 
2259
 * We need to make this one conform. */
 
2260
            --replaceLineEnd, --replaceLineLen;
 
2261
        } else {
 
2262
 
 
2263
            if(cw->browseMode) {
 
2264
                char search[20];
 
2265
                findInputField(p, 1, whichField, &total, &tagno);
 
2266
                if(!tagno) {
 
2267
                    fieldNumProblem("input fields", 'i', whichField, total);
 
2268
                    continue;
 
2269
                }
 
2270
                sprintf(search, "%c%d<", InternalCodeChar, tagno);
 
2271
/* Ok, if the line contains a null, this ain't gonna work. */
 
2272
                s = strstr(p, search);
 
2273
                if(!s)
 
2274
                    continue;
 
2275
                s = strchr(s, '<') + 1;
 
2276
                t = strstr(s, "\2000>");
 
2277
                if(!t)
 
2278
                    continue;
 
2279
                j = replaceText(s, t - s, rhs, true, nth, g_mode, ln);
 
2280
            } else {
 
2281
                j = replaceText(p, len - 1, rhs, true, nth, g_mode, ln);
 
2282
            }
 
2283
            if(j < 0)
 
2284
                goto abort;
 
2285
            if(!j)
 
2286
                continue;
 
2287
        }
 
2288
 
 
2289
/* Did we split this line into many lines? */
 
2290
        linecount = slashcount = nullcount = 0;
 
2291
        for(t = replaceLine; t < replaceLineEnd; ++t) {
 
2292
            c = *t;
 
2293
            if(c == '\n')
 
2294
                ++linecount;
 
2295
            if(c == 0)
 
2296
                ++nullcount;
 
2297
            if(c == '/')
 
2298
                ++slashcount;
 
2299
        }
 
2300
        if(!linesComing(linecount + 1))
 
2301
            goto abort;
 
2302
 
 
2303
        if(cw->dirMode) {
 
2304
/* move the file, then update the text */
 
2305
            char src[ABSPATH], *dest;
 
2306
            if(slashcount + nullcount + linecount) {
 
2307
                setError
 
2308
                   ("cannot embed slash, newline, or null in a directory name");
 
2309
                goto abort;
 
2310
            }
 
2311
            p[len - 1] = 0;     /* temporary */
 
2312
            t = makeAbsPath(p);
 
2313
            p[len - 1] = '\n';
 
2314
            if(!t)
 
2315
                goto abort;
 
2316
            strcpy(src, t);
 
2317
            *replaceLineEnd = 0;
 
2318
            dest = makeAbsPath(replaceLine);
 
2319
            if(!dest)
 
2320
                goto abort;
 
2321
            if(!stringEqual(src, dest)) {
 
2322
                if(fileTypeByName(dest, true)) {
 
2323
                    setError("destination file already exists");
 
2324
                    goto abort;
 
2325
                }
 
2326
                if(rename(src, dest)) {
 
2327
                    setError("cannot rename file to %s", dest);
 
2328
                    goto abort;
 
2329
                }
 
2330
            }                   /* source and dest are different */
 
2331
        }
 
2332
        /* directory */
 
2333
        if(cw->browseMode) {
 
2334
            if(nullcount) {
 
2335
                setError("cannot embed nulls in an input field");
 
2336
                goto abort;
 
2337
            }
 
2338
            if(linecount) {
 
2339
                setError("cannot embed newlines in an input field");
 
2340
                goto abort;
 
2341
            }
 
2342
            replaceLine[replaceLineLen] = 0;
 
2343
/* We're managing our own printing, so leave notify = 0 */
 
2344
            if(!infReplace(tagno, replaceLine, 0))
 
2345
                goto abort;
 
2346
        } else {
 
2347
 
 
2348
            replaceLine[replaceLineLen] = '\n';
 
2349
            if(!linecount) {
 
2350
/* normal substitute */
 
2351
                char newnum[LNWIDTH];
 
2352
                textLines[textLinesCount] = allocMem(replaceLineLen + 1);
 
2353
                memcpy(textLines[textLinesCount], replaceLine,
 
2354
                   replaceLineLen + 1);
 
2355
                sprintf(newnum, "%06d", textLinesCount);
 
2356
                memcpy(cw->map + ln * LNWIDTH, newnum, 6);
 
2357
                ++textLinesCount;
 
2358
            } else {
 
2359
/* Becomes many lines, this is the tricky case. */
 
2360
                save_nlMode = cw->nlMode;
 
2361
                delText(ln, ln);
 
2362
                addTextToBuffer((pst) replaceLine, replaceLineLen + 1, ln - 1);
 
2363
                cw->nlMode = save_nlMode;
 
2364
                endRange += linecount;
 
2365
                ln += linecount;
 
2366
/* There's a quirk when adding newline to the end of a buffer
 
2367
 * that had no newline at the end before. */
 
2368
                if(cw->nlMode &&
 
2369
                   ln == cw->dol && replaceLine[replaceLineLen - 1] == '\n') {
 
2370
                    delText(ln, ln);
 
2371
                    --ln, --endRange;
 
2372
                }
 
2373
            }
 
2374
        }                       /* browse or not */
 
2375
 
 
2376
        if(subPrint == 2)
 
2377
            displayLine(ln);
 
2378
        lastSubst = ln;
 
2379
        cw->firstOpMode = undoable = true;
 
2380
        if(!cw->browseMode)
 
2381
            cw->changeMode = true;
 
2382
    }                           /* loop over lines in the range */
 
2383
    if(re_cc)
 
2384
        pcre_free(re_cc);
 
2385
 
 
2386
    if(intFlag) {
 
2387
        setError(opint);
 
2388
        return -1;
 
2389
    }
 
2390
 
 
2391
    if(!lastSubst) {
 
2392
        if(!globSub) {
 
2393
            if(!errorMsg[0])
 
2394
                setError(bl_mode ? "no changes" : "no match");
 
2395
        }
 
2396
        return false;
 
2397
    }
 
2398
    cw->dot = lastSubst;
 
2399
    if(subPrint == 1 && !globSub)
 
2400
        printDot();
 
2401
    return true;
 
2402
 
 
2403
  abort:
 
2404
    if(re_cc)
 
2405
        pcre_free(re_cc);
 
2406
    return -1;
 
2407
}                               /* substituteText */
 
2408
 
 
2409
/*********************************************************************
 
2410
Implement various two letter commands.
 
2411
Most of these set and clear modes.
 
2412
Return 1 or 0 for success or failure as usual.
 
2413
But return 2 if there is a new command to run.
 
2414
The second parameter is a result parameter, the new command.
 
2415
*********************************************************************/
 
2416
 
 
2417
static int
 
2418
twoLetter(const char *line, const char **runThis)
 
2419
{
 
2420
    static char newline[MAXTTYLINE];
 
2421
    char c;
 
2422
    bool rc;
 
2423
    int i;
 
2424
 
 
2425
    *runThis = newline;
 
2426
 
 
2427
    if(stringEqual(line, "qt"))
 
2428
        exit(0);
 
2429
 
 
2430
/*
 
2431
if(stringEqual(line, "ws")) return stripChild();
 
2432
if(stringEqual(line, "us")) return unstripChild();
 
2433
*/
 
2434
 
 
2435
    if(line[0] == 'd' && line[1] == 'b' && isdigitByte(line[2]) && !line[3]) {
 
2436
        debugLevel = line[2] - '0';
 
2437
        return true;
 
2438
    }
 
2439
 
 
2440
    if(line[0] == 'u' && line[1] == 'a' && isdigitByte(line[2]) && !line[3]) {
 
2441
        char *t = userAgents[line[2] - '0'];
 
2442
        cmd = 'e';
 
2443
        if(!t) {
 
2444
            setError("agent number %c is not defined", line[2]);
 
2445
            return false;
 
2446
        }
 
2447
        currentAgent = t;
 
2448
        if(helpMessagesOn || debugLevel >= 1)
 
2449
            puts(currentAgent);
 
2450
        return true;
 
2451
    }
 
2452
 
 
2453
/* ^^^^ is the same as ^4 */
 
2454
    if(line[0] == '^' && line[1] == '^') {
 
2455
        const char *t = line + 2;
 
2456
        while(*t == '^')
 
2457
            ++t;
 
2458
        if(!*t) {
 
2459
            sprintf(newline, "^%d", t - line);
 
2460
            return 2;
 
2461
        }
 
2462
    }
 
2463
 
 
2464
    if(line[0] == 'm' && (i = stringIsNum(line + 1)) >= 0) {
 
2465
        sprintf(newline, "^M%d", i);
 
2466
        return 2;
 
2467
    }
 
2468
 
 
2469
    if(line[0] == 'c' && line[1] == 'd') {
 
2470
        c = line[2];
 
2471
        if(!c || isspaceByte(c)) {
 
2472
            const char *t = line + 2;
 
2473
            skipWhite(&t);
 
2474
            c = *t;
 
2475
            cmd = 'e';          /* so error messages are printed */
 
2476
            if(!c) {
 
2477
                char cwdbuf[ABSPATH];
 
2478
              pwd:
 
2479
                if(!getcwd(cwdbuf, sizeof (cwdbuf))) {
 
2480
                    setError("could not %s directory",
 
2481
                       (c ? "print new" : "establish current"));
 
2482
                    return false;
 
2483
                }
 
2484
                puts(cwdbuf);
 
2485
                return true;
 
2486
            }
 
2487
            if(!envFile(t, &t))
 
2488
                return false;
 
2489
            if(!chdir(t))
 
2490
                goto pwd;
 
2491
            setError("invalid directory");
 
2492
            return false;
 
2493
        }
 
2494
    }
 
2495
 
 
2496
    if(line[0] == 'p' && line[1] == 'b') {
 
2497
        c = line[2];
 
2498
        if(!c || c == '.') {
 
2499
            const struct MIMETYPE *mt;
 
2500
            char *cmd;
 
2501
            const char *suffix = 0;
 
2502
            bool trailPercent = false;
 
2503
            if(!cw->dol) {
 
2504
                setError("cannot play an empty buffer");
 
2505
                return false;
 
2506
            }
 
2507
            if(c == '.') {
 
2508
                suffix = line + 3;
 
2509
            } else {
 
2510
                if(cw->fileName)
 
2511
                    suffix = strrchr(cw->fileName, '.');
 
2512
                if(!suffix) {
 
2513
                    setError
 
2514
                       ("file has no suffix, use mt.xxx to specify your own suffix");
 
2515
                    return false;
 
2516
                }
 
2517
                ++suffix;
 
2518
            }
 
2519
            if(strlen(suffix) > 5) {
 
2520
                setError("suffix is limited to 5 characters");
 
2521
                return false;
 
2522
            }
 
2523
            mt = findMimeBySuffix(suffix);
 
2524
            if(!mt) {
 
2525
                setError
 
2526
                   ("suffix .%s is not a recognized mime type, please check your config file.",
 
2527
                   suffix);
 
2528
                return false;
 
2529
            }
 
2530
            if(mt->program[strlen(mt->program) - 1] == '%')
 
2531
                trailPercent = true;
 
2532
            cmd = pluginCommand(mt, 0, suffix);
 
2533
            rc = bufferToProgram(cmd, suffix, trailPercent);
 
2534
            nzFree(cmd);
 
2535
            return rc;
 
2536
        }
 
2537
    }
 
2538
 
 
2539
    if(stringEqual(line, "rf")) {
 
2540
        cmd = 'e';
 
2541
        if(!cw->fileName) {
 
2542
            setError("no file name or url to refresh");
 
2543
            return false;
 
2544
        }
 
2545
        if(cw->browseMode)
 
2546
            cmd = 'b';
 
2547
        noStack = true;
 
2548
        sprintf(newline, "%c %s", cmd, cw->fileName);
 
2549
        debrowseSuffix(newline);
 
2550
        return 2;
 
2551
    }
 
2552
    /* rf */
 
2553
    if(stringEqual(line, "ub") || stringEqual(line, "et")) {
 
2554
        bool ub = (line[0] == 'u');
 
2555
        cmd = 'e';
 
2556
        if(!cw->browseMode) {
 
2557
            setError("not in browse mode");
 
2558
            return false;
 
2559
        }
 
2560
        freeUndoLines(cw->map);
 
2561
        undoWindow.map = 0;
 
2562
        nzFree(preWindow.map);
 
2563
        preWindow.map = 0;
 
2564
        cw->firstOpMode = undoable = false;
 
2565
        cw->browseMode = false;
 
2566
        cw->iplist = 0;
 
2567
        if(ub) {
 
2568
            debrowseSuffix(cw->fileName);
 
2569
            cw->nlMode = cw->rnlMode;
 
2570
            cw->dot = cw->r_dot, cw->dol = cw->r_dol;
 
2571
            memcpy(cw->labels, cw->r_labels, sizeof (cw->labels));
 
2572
            freeWindowLines(cw->map);
 
2573
            cw->map = cw->r_map;
 
2574
        } else {
 
2575
            for(i = 1; i <= cw->dol; ++i) {
 
2576
                int ln = atoi(cw->map + i * LNWIDTH);
 
2577
                removeHiddenNumbers(textLines[ln]);
 
2578
            }
 
2579
            freeWindowLines(cw->r_map);
 
2580
        }
 
2581
        cw->r_map = 0;
 
2582
        freeTags(cw->tags);
 
2583
        cw->tags = 0;
 
2584
        freeJavaContext(cw->jsc);
 
2585
        cw->jsc = 0;
 
2586
        nzFree(cw->dw);
 
2587
        cw->dw = 0;
 
2588
        nzFree(cw->ft);
 
2589
        cw->ft = 0;
 
2590
        nzFree(cw->fd);
 
2591
        cw->fd = 0;
 
2592
        nzFree(cw->fk);
 
2593
        cw->fk = 0;
 
2594
        if(ub)
 
2595
            fileSize = apparentSize(context, false);
 
2596
        return true;
 
2597
    }
 
2598
    /* ub */
 
2599
    if(stringEqual(line, "ip")) {
 
2600
        jMyContext();
 
2601
        sethostent(1);
 
2602
        allIPs();
 
2603
        endhostent();
 
2604
        if(!cw->iplist || cw->iplist[0] == -1) {
 
2605
            puts("none");
 
2606
        } else {
 
2607
            long ip;
 
2608
            for(i = 0; (ip = cw->iplist[i]) != -1; ++i) {
 
2609
                puts(tcp_ip_dots(ip));
 
2610
            }
 
2611
        }
 
2612
        return true;
 
2613
    }
 
2614
    /* ip */
 
2615
    if(stringEqual(line, "f/") || stringEqual(line, "w/")) {
 
2616
        char *t;
 
2617
        cmd = line[0];
 
2618
        if(!cw->fileName) {
 
2619
            setError("no file name or url to refresh");
 
2620
            return false;
 
2621
        }
 
2622
        t = strrchr(cw->fileName, '/');
 
2623
        if(!t) {
 
2624
            setError("file name does not contain /");
 
2625
            return false;
 
2626
        }
 
2627
        ++t;
 
2628
        if(!*t) {
 
2629
            setError("file name ends in /");
 
2630
            return false;
 
2631
        }
 
2632
        sprintf(newline, "%c `%s", cmd, t);
 
2633
        return 2;
 
2634
    }
 
2635
 
 
2636
    if(line[0] == 'f' && line[2] == 0 &&
 
2637
       (line[1] == 'd' || line[1] == 'k' || line[1] == 't')) {
 
2638
        const char *s, *t;
 
2639
        cmd = 'e';
 
2640
        if(!cw->browseMode) {
 
2641
            setError("not in browse mode");
 
2642
            return false;
 
2643
        }
 
2644
        if(line[1] == 't')
 
2645
            s = cw->ft, t = "title";
 
2646
        if(line[1] == 'd')
 
2647
            s = cw->fd, t = "description";
 
2648
        if(line[1] == 'k')
 
2649
            s = cw->fk, t = "keywords";
 
2650
        if(s)
 
2651
            puts(s);
 
2652
        else
 
2653
            printf("no %s\n", t);
 
2654
        return true;
 
2655
    }
 
2656
 
 
2657
    if(line[0] == 's' && line[1] == 'm') {
 
2658
        const char *t = line + 2;
 
2659
        bool dosig = true;
 
2660
        int account = 0;
 
2661
        cmd = 'e';
 
2662
        if(*t == '-') {
 
2663
            dosig = false;
 
2664
            ++t;
 
2665
        }
 
2666
        if(isdigitByte(*t))
 
2667
            account = strtol(t, (char **)&t, 10);
 
2668
        if(!*t) {
 
2669
/* send mail */
 
2670
            return sendMailCurrent(account, dosig);
 
2671
        } else {
 
2672
            setError("invalid characters after the sm command");
 
2673
            return false;
 
2674
        }
 
2675
    }
 
2676
 
 
2677
    if(stringEqual(line, "sg")) {
 
2678
        searchStringsAll = true;
 
2679
        if(helpMessagesOn)
 
2680
            puts("substitutions global");
 
2681
        return true;
 
2682
    }
 
2683
 
 
2684
    if(stringEqual(line, "sl")) {
 
2685
        searchStringsAll = false;
 
2686
        if(helpMessagesOn)
 
2687
            puts("substitutions local");
 
2688
        return true;
 
2689
    }
 
2690
 
 
2691
    if(stringEqual(line, "ci")) {
 
2692
        caseInsensitive = true;
 
2693
        if(helpMessagesOn)
 
2694
            puts("case insensitive");
 
2695
        return true;
 
2696
    }
 
2697
 
 
2698
    if(stringEqual(line, "cs")) {
 
2699
        caseInsensitive = false;
 
2700
        if(helpMessagesOn)
 
2701
            puts("case sensitive");
 
2702
        return true;
 
2703
    }
 
2704
 
 
2705
    if(stringEqual(line, "dr")) {
 
2706
        dirWrite = 0;
 
2707
        if(helpMessagesOn)
 
2708
            puts("directories readonly");
 
2709
        return true;
 
2710
    }
 
2711
 
 
2712
    if(stringEqual(line, "dw")) {
 
2713
        dirWrite = 1;
 
2714
        if(helpMessagesOn)
 
2715
            puts("directories writable");
 
2716
        return true;
 
2717
    }
 
2718
 
 
2719
    if(stringEqual(line, "dx")) {
 
2720
        dirWrite = 2;
 
2721
        if(helpMessagesOn)
 
2722
            puts("directories writable with delete");
 
2723
        return true;
 
2724
    }
 
2725
 
 
2726
    if(stringEqual(line, "hr")) {
 
2727
        allowRedirection ^= 1;
 
2728
        if(helpMessagesOn || debugLevel >= 1)
 
2729
            puts(allowRedirection ? "http redirection" : "no http redirection");
 
2730
        return true;
 
2731
    }
 
2732
 
 
2733
    if(stringEqual(line, "sr")) {
 
2734
        sendReferrer ^= 1;
 
2735
        if(helpMessagesOn || debugLevel >= 1)
 
2736
            puts(sendReferrer ? "send referrer" : "do not send referrer");
 
2737
        return true;
 
2738
    }
 
2739
 
 
2740
    if(stringEqual(line, "js")) {
 
2741
        allowJS ^= 1;
 
2742
        if(helpMessagesOn || debugLevel >= 1)
 
2743
            puts(allowJS ? "javascript enabled" : "javascript disabled");
 
2744
        return true;
 
2745
    }
 
2746
 
 
2747
    if(stringEqual(line, "bd")) {
 
2748
        binaryDetect ^= 1;
 
2749
        if(helpMessagesOn || debugLevel >= 1)
 
2750
            puts(binaryDetect ? "watching for binary files" :
 
2751
               "treating binary like text");
 
2752
        return true;
 
2753
    }
 
2754
 
 
2755
    if(line[0] == 'f' && line[1] == 'm' &&
 
2756
       line[2] && strchr("pad", line[2]) && !line[3]) {
 
2757
        ftpMode = 0;
 
2758
        if(line[2] == 'p')
 
2759
            ftpMode = 'F';
 
2760
        if(line[2] == 'a')
 
2761
            ftpMode = 'E';
 
2762
        if(helpMessagesOn || debugLevel >= 1) {
 
2763
            if(ftpMode == 'F')
 
2764
                puts("passive mode");
 
2765
            if(ftpMode == 'E')
 
2766
                puts("active mode");
 
2767
            if(ftpMode == 0)
 
2768
                puts("passive/active mode");
 
2769
        }
 
2770
        return true;
 
2771
    }
 
2772
    /* fm */
 
2773
    if(stringEqual(line, "vs")) {
 
2774
        verifyCertificates ^= 1;
 
2775
        if(helpMessagesOn || debugLevel >= 1)
 
2776
            puts(verifyCertificates ? "verify ssl connections" :
 
2777
               "don't verify ssl connections (less secure)");
 
2778
        ssl_must_verify(verifyCertificates);
 
2779
        return true;
 
2780
    }
 
2781
 
 
2782
    if(stringEqual(line, "hf")) {
 
2783
        showHiddenFiles ^= 1;
 
2784
        if(helpMessagesOn || debugLevel >= 1)
 
2785
            puts(showHiddenFiles ? "show hidden files in directory mode" :
 
2786
               "don't show hidden files in directory mode");
 
2787
        return true;
 
2788
    }
 
2789
 
 
2790
    if(stringEqual(line, "tn")) {
 
2791
        textAreaDosNewlines ^= 1;
 
2792
        if(helpMessagesOn || debugLevel >= 1)
 
2793
            puts(textAreaDosNewlines ? "text areas use dos newlines" :
 
2794
               "text areas use unix newlines");
 
2795
        return true;
 
2796
    }
 
2797
 
 
2798
    if(stringEqual(line, "eo")) {
 
2799
        endMarks = 0;
 
2800
        if(helpMessagesOn)
 
2801
            puts("end markers off");
 
2802
        return true;
 
2803
    }
 
2804
 
 
2805
    if(stringEqual(line, "el")) {
 
2806
        endMarks = 1;
 
2807
        if(helpMessagesOn)
 
2808
            puts("end markers on listed lines");
 
2809
        return true;
 
2810
    }
 
2811
 
 
2812
    if(stringEqual(line, "ep")) {
 
2813
        endMarks = 2;
 
2814
        if(helpMessagesOn)
 
2815
            puts("end markers on");
 
2816
        return true;
 
2817
    }
 
2818
 
 
2819
    *runThis = line;
 
2820
    return 2;                   /* no change */
 
2821
}                               /* twoLetter */
 
2822
 
 
2823
/* Return the number of unbalanced punctuation marks.
 
2824
 * This is used by the next routine. */
 
2825
static void
 
2826
unbalanced(char c, char d, int ln, int *back_p, int *for_p)
 
2827
{                               /* result parameters */
 
2828
    char *t, *open;
 
2829
    char *p = (char *)fetchLine(ln, 1);
 
2830
    bool change;
 
2831
    int backward, forward;
 
2832
 
 
2833
    change = true;
 
2834
    while(change) {
 
2835
        change = false;
 
2836
        open = 0;
 
2837
        for(t = p; *t != '\n'; ++t) {
 
2838
            if(*t == c)
 
2839
                open = t;
 
2840
            if(*t == d && open) {
 
2841
                *open = 0;
 
2842
                *t = 0;
 
2843
                change = true;
 
2844
                open = 0;
 
2845
            }
 
2846
        }
 
2847
    }
 
2848
 
 
2849
    backward = forward = 0;
 
2850
    for(t = p; *t != '\n'; ++t) {
 
2851
        if(*t == c)
 
2852
            ++forward;
 
2853
        if(*t == d)
 
2854
            ++backward;
 
2855
    }
 
2856
 
 
2857
    free(p);
 
2858
    *back_p = backward;
 
2859
    *for_p = forward;
 
2860
}                               /* unbalanced */
 
2861
 
 
2862
/* Find the line that balances the unbalanced punctuation. */
 
2863
static bool
 
2864
balanceLine(const char *line)
 
2865
{
 
2866
    char c, d;                  /* open and close */
 
2867
    char selected;
 
2868
    static char openlist[] = "{([<`";
 
2869
    static char closelist[] = "})]>'";
 
2870
    static char alllist[] = "{}()[]<>`'";
 
2871
    char *t;
 
2872
    int level = 0;
 
2873
    int i, direction, forward, backward;
 
2874
 
 
2875
    if(c = *line) {
 
2876
        if(!strchr(alllist, c) || line[1]) {
 
2877
            setError("you must specify exactly one of %s after the B command",
 
2878
               alllist);
 
2879
            return false;
 
2880
        }
 
2881
        if(t = strchr(openlist, c)) {
 
2882
            d = closelist[t - openlist];
 
2883
            direction = 1;
 
2884
        } else {
 
2885
            d = c;
 
2886
            t = strchr(closelist, d);
 
2887
            c = openlist[t - closelist];
 
2888
            direction = -1;
 
2889
        }
 
2890
        unbalanced(c, d, endRange, &backward, &forward);
 
2891
        if(direction > 0) {
 
2892
            if((level = forward) == 0) {
 
2893
                setError("line does not contain an open %c", c);
 
2894
                return false;
 
2895
            }
 
2896
        } else {
 
2897
            if((level = backward) == 0) {
 
2898
                setError("line does not contain an open %c", d);
 
2899
                return false;
 
2900
            }
 
2901
        }
 
2902
    } else {
 
2903
 
 
2904
/* Look for anything unbalanced, probably a brace. */
 
2905
        for(i = 0; i <= 2; ++i) {
 
2906
            c = openlist[i];
 
2907
            d = closelist[i];
 
2908
            unbalanced(c, d, endRange, &backward, &forward);
 
2909
            if(backward && forward) {
 
2910
                setError
 
2911
                   ("both %c and %c are unbalanced on this line, try B%c or B%c",
 
2912
                   c, d, c, d);
 
2913
                return false;
 
2914
            }
 
2915
            level = backward + forward;
 
2916
            if(!level)
 
2917
                continue;
 
2918
            direction = 1;
 
2919
            if(backward)
 
2920
                direction = -1;
 
2921
            break;
 
2922
        }
 
2923
        if(!level) {
 
2924
            setError
 
2925
               ("line does not contain an unbalanced brace, parenthesis, or bracket");
 
2926
            return false;
 
2927
        }
 
2928
    }                           /* explicit character passed in, or look for one */
 
2929
 
 
2930
    selected = (direction > 0 ? c : d);
 
2931
 
 
2932
/* search for the balancing line */
 
2933
    i = endRange;
 
2934
    while((i += direction) > 0 && i <= cw->dol) {
 
2935
        unbalanced(c, d, i, &backward, &forward);
 
2936
        if(direction > 0 && backward >= level ||
 
2937
           direction < 0 && forward >= level) {
 
2938
            cw->dot = i;
 
2939
            printDot();
 
2940
            return true;
 
2941
        }
 
2942
        level += (forward - backward) * direction;
 
2943
    }                           /* loop over lines */
 
2944
 
 
2945
    setError("cannot find the line that balances %c", selected);
 
2946
    return false;
 
2947
}                               /* balanceLine */
 
2948
 
 
2949
/* Unfold the buffer into one long, allocated string. */
 
2950
bool
 
2951
unfoldBuffer(int cx, bool cr, char **data, int *len)
 
2952
{
 
2953
    char *buf;
 
2954
    int l, ln;
 
2955
    struct ebWindow *w;
 
2956
    int size = apparentSize(cx, false);
 
2957
    if(size < 0)
 
2958
        return false;
 
2959
    w = sessionList[cx].lw;
 
2960
    if(w->browseMode) {
 
2961
        setError("session %d is currently in browse mode", cx);
 
2962
        return false;
 
2963
    }
 
2964
    if(w->dirMode) {
 
2965
        setError("session %d is currently in directory mode", cx);
 
2966
        return false;
 
2967
    }
 
2968
    if(cr)
 
2969
        size += w->dol;
 
2970
/* a few bytes more, just for safety */
 
2971
    buf = allocMem(size + 4);
 
2972
    *data = buf;
 
2973
    for(ln = 1; ln <= w->dol; ++ln) {
 
2974
        pst line = fetchLineContext(ln, -1, cx);
 
2975
        l = pstLength(line) - 1;
 
2976
        if(l) {
 
2977
            memcpy(buf, line, l);
 
2978
            buf += l;
 
2979
        }
 
2980
        if(cr) {
 
2981
            *buf++ = '\r';
 
2982
            if(l && buf[-2] == '\r')
 
2983
                --buf, --size;
 
2984
        }
 
2985
        *buf++ = '\n';
 
2986
    }                           /* loop over lines */
 
2987
    if(w->dol && w->nlMode) {
 
2988
        if(cr)
 
2989
            --size;
 
2990
    }
 
2991
    *len = size;
 
2992
    (*data)[size] = 0;
 
2993
    return true;
 
2994
}                               /* unfoldBuffer */
 
2995
 
 
2996
static char *
 
2997
showLinks(void)
 
2998
{
 
2999
    int a_l;
 
3000
    char *a = initString(&a_l);
 
3001
    bool click, dclick;
 
3002
    char c, *p, *s, *t, *q, *line, *h;
 
3003
    int j, k = 0, tagno;
 
3004
    void *ev;
 
3005
 
 
3006
    if(cw->browseMode && endRange) {
 
3007
        jMyContext();
 
3008
        line = (char *)fetchLine(endRange, -1);
 
3009
        for(p = line; (c = *p) != '\n'; ++p) {
 
3010
            if(c != (char)InternalCodeChar)
 
3011
                continue;
 
3012
            if(!isdigitByte(p[1]))
 
3013
                continue;
 
3014
            j = strtol(p + 1, &s, 10);
 
3015
            if(*s != '{')
 
3016
                continue;
 
3017
            p = s;
 
3018
            ++k;
 
3019
            findField(line, 0, k, 0, &tagno, &h, &ev);
 
3020
            if(tagno != j)
 
3021
                continue;       /* should never happen */
 
3022
 
 
3023
            click = tagHandler(tagno, "onclick");
 
3024
            dclick = tagHandler(tagno, "ondblclick");
 
3025
 
 
3026
/* find the closing brace */
 
3027
/* It might not be there, could be on the next line. */
 
3028
            for(s = p + 1; (c = *s) != '\n'; ++s)
 
3029
                if(c == (char)InternalCodeChar && s[1] == '0' && s[2] == '}')
 
3030
                    break;
 
3031
/* Ok, everything between p and s exclusive is the description */
 
3032
            if(!h)
 
3033
                h = EMPTYSTRING;
 
3034
            if(stringEqual(h, "#")) {
 
3035
                nzFree(h);
 
3036
                h = EMPTYSTRING;
 
3037
            }
 
3038
 
 
3039
            if(memEqualCI(h, "mailto:", 7)) {
 
3040
                stringAndBytes(&a, &a_l, p + 1, s - p - 1);
 
3041
                stringAndChar(&a, &a_l, ':');
 
3042
                s = h + 7;
 
3043
                t = s + strcspn(s, "?");
 
3044
                stringAndBytes(&a, &a_l, s, t - s);
 
3045
                stringAndChar(&a, &a_l, '\n');
 
3046
                nzFree(h);
 
3047
                continue;
 
3048
            }
 
3049
            /* mail link */
 
3050
            stringAndString(&a, &a_l, "<a href=");
 
3051
 
 
3052
            if(memEqualCI(h, "javascript:", 11)) {
 
3053
                stringAndString(&a, &a_l, "javascript:>\n");
 
3054
            } else if(!*h && (click | dclick)) {
 
3055
                char buf[20];
 
3056
                sprintf(buf, "%s>\n", click ? "onclick" : "ondblclick");
 
3057
                stringAndString(&a, &a_l, buf);
 
3058
            } else {
 
3059
                if(*h)
 
3060
                    stringAndString(&a, &a_l, h);
 
3061
                stringAndString(&a, &a_l, ">\n");
 
3062
            }
 
3063
 
 
3064
            nzFree(h);
 
3065
/* next line is the description of the bookmark */
 
3066
            stringAndBytes(&a, &a_l, p + 1, s - p - 1);
 
3067
            stringAndString(&a, &a_l, "\n</a>\n");
 
3068
        }                       /* loop looking for hyperlinks */
 
3069
    }
 
3070
    /* browse mode */
 
3071
    if(!a_l) {                  /* nothing found yet */
 
3072
        if(!cw->fileName) {
 
3073
            setError("no file name");
 
3074
            return 0;
 
3075
        }
 
3076
        h = cloneString(cw->fileName);
 
3077
        debrowseSuffix(h);
 
3078
        stringAndString(&a, &a_l, "<a href=");
 
3079
        stringAndString(&a, &a_l, h);
 
3080
        stringAndString(&a, &a_l, ">\n");
 
3081
        s = (char *)getDataURL(h);
 
3082
        if(!s || !*s)
 
3083
            s = h;
 
3084
        t = s + strcspn(s, "\1?#");
 
3085
        if(t > s && t[-1] == '/')
 
3086
            --t;
 
3087
        *t = 0;
 
3088
        q = strrchr(s, '/');
 
3089
        if(q && q < t)
 
3090
            s = q + 1;
 
3091
        stringAndBytes(&a, &a_l, s, t - s);
 
3092
        stringAndString(&a, &a_l, "\n</a>\n");
 
3093
        nzFree(h);
 
3094
    }
 
3095
    /* using the filename */
 
3096
    return a;
 
3097
}                               /* showLinks */
 
3098
 
 
3099
/* Run the entered edbrowse command.
 
3100
 * This is indirectly recursive, as in g/x/d
 
3101
 * Pass in the ed command, and return success or failure.
 
3102
 * We assume it has been turned into a C string.
 
3103
 * This means no embeded nulls.
 
3104
 * If you want to use null in a search or substitute, use \0. */
 
3105
bool
 
3106
runCommand(const char *line)
 
3107
{
 
3108
    int i, j, n, writeMode;
 
3109
    struct ebWindow *w;
 
3110
    void *ev;                   /* event variables */
 
3111
    bool nogo, rc;
 
3112
    bool postSpace = false, didRange = false;
 
3113
    char first;
 
3114
    int cx = 0;                 /* numeric suffix as in s/x/y/3 or w2 */
 
3115
    int tagno;
 
3116
    const char *s;
 
3117
    static char newline[MAXTTYLINE];
 
3118
    static char *allocatedLine = 0;
 
3119
 
 
3120
    if(allocatedLine) {
 
3121
        nzFree(allocatedLine);
 
3122
        allocatedLine = 0;
 
3123
    }
 
3124
    nzFree(currentReferrer);
 
3125
    currentReferrer = cloneString(cw->fileName);
 
3126
    js_redirects = false;
 
3127
 
 
3128
    cmd = icmd = 'p';
 
3129
    skipWhite(&line);
 
3130
    first = *line;
 
3131
 
 
3132
    if(!globSub) {
 
3133
/* Allow things like comment, or shell escape, but not if we're
 
3134
 * in the midst of a global substitute, as in g/x/ !echo hello world */
 
3135
        if(first == '#')
 
3136
            return true;
 
3137
        if(first == '!')
 
3138
            return shellEscape(line + 1);
 
3139
 
 
3140
/* Watch for successive q commands. */
 
3141
        lastq = lastqq, lastqq = 0;
 
3142
        noStack = false;
 
3143
 
 
3144
/* special 2 letter commands - most of these change operational modes */
 
3145
        j = twoLetter(line, &line);
 
3146
        if(j != 2)
 
3147
            return j;
 
3148
    }
 
3149
    /* not global command */
 
3150
    startRange = endRange = cw->dot;    /* default range */
 
3151
/* Just hit return to read the next line. */
 
3152
    first = *line;
 
3153
    if(first == 0) {
 
3154
        didRange = true;
 
3155
        ++startRange, ++endRange;
 
3156
        if(endRange > cw->dol) {
 
3157
            setError("end of buffer");
 
3158
            return false;
 
3159
        }
 
3160
    }
 
3161
    if(first == ',') {
 
3162
        didRange = true;
 
3163
        ++line;
 
3164
        startRange = 1;
 
3165
        if(cw->dol == 0)
 
3166
            startRange = 0;
 
3167
        endRange = cw->dol;
 
3168
    }
 
3169
    if(first == ';') {
 
3170
        didRange = true;
 
3171
        ++line;
 
3172
        startRange = cw->dot;
 
3173
        endRange = cw->dol;
 
3174
    }
 
3175
    if(first == 'j' || first == 'J') {
 
3176
        didRange = true;
 
3177
        endRange = startRange + 1;
 
3178
        if(endRange > cw->dol) {
 
3179
            setError("no more lines to join");
 
3180
            return false;
 
3181
        }
 
3182
    }
 
3183
    if(first == '=') {
 
3184
        didRange = true;
 
3185
        startRange = endRange = cw->dol;
 
3186
    }
 
3187
    if(first == 'w' || first == 'v' || first == 'g' &&
 
3188
       line[1] && strchr(valid_delim, line[1])) {
 
3189
        didRange = true;
 
3190
        startRange = 1;
 
3191
        if(cw->dol == 0)
 
3192
            startRange = 0;
 
3193
        endRange = cw->dol;
 
3194
    }
 
3195
 
 
3196
    if(!didRange) {
 
3197
        if(!getRangePart(line, &startRange, &line))
 
3198
            return (globSub = false);
 
3199
        endRange = startRange;
 
3200
        if(line[0] == ',') {
 
3201
            ++line;
 
3202
            endRange = cw->dol; /* new default */
 
3203
            first = *line;
 
3204
            if(first && strchr(valid_laddr, first)) {
 
3205
                if(!getRangePart(line, &endRange, &line))
 
3206
                    return (globSub = false);
 
3207
            }
 
3208
        }
 
3209
    }
 
3210
    if(endRange < startRange) {
 
3211
        setError("bad range");
 
3212
        return false;
 
3213
    }
 
3214
 
 
3215
/* change uc into a substitute command, converting the whole line */
 
3216
    skipWhite(&line);
 
3217
    first = *line;
 
3218
    if((first == 'u' || first == 'l' || first == 'm') && line[1] == 'c' &&
 
3219
       line[2] == 0) {
 
3220
        sprintf(newline, "s/.*/%cc/", first);
 
3221
        line = newline;
 
3222
    }
 
3223
/* Breakline is actually a substitution of lines. */
 
3224
    if(stringEqual(line, "bl")) {
 
3225
        if(cw->dirMode) {
 
3226
            setError("cannot break lines in directory mode");
 
3227
            return false;
 
3228
        }
 
3229
        if(cw->browseMode) {
 
3230
            setError("cannot break lines in browse mode");
 
3231
            return false;
 
3232
        }
 
3233
        line = "s`bl";
 
3234
    }
 
3235
 
 
3236
/* get the command */
 
3237
    cmd = *line;
 
3238
    if(cmd)
 
3239
        ++line;
 
3240
    else
 
3241
        cmd = 'p';
 
3242
    icmd = cmd;
 
3243
 
 
3244
    if(!strchr(valid_cmd, cmd)) {
 
3245
        setError("unknown command %c", cmd);
 
3246
        return (globSub = false);
 
3247
    }
 
3248
 
 
3249
    first = *line;
 
3250
    writeMode = O_TRUNC;
 
3251
    if(cmd == 'w' && first == '+')
 
3252
        writeMode = O_APPEND, first = *++line;
 
3253
 
 
3254
    if(cw->dirMode && !strchr(dir_cmd, cmd)) {
 
3255
        setError("%c not available in directory mode", icmd);
 
3256
        return (globSub = false);
 
3257
    }
 
3258
    if(cw->browseMode && !strchr(browse_cmd, cmd)) {
 
3259
        setError("%c not available in browse mode", icmd);
 
3260
        return (globSub = false);
 
3261
    }
 
3262
    if(startRange == 0 && !strchr(zero_cmd, cmd)) {
 
3263
        setError("zero line number");
 
3264
        return (globSub = false);
 
3265
    }
 
3266
    while(isspaceByte(first))
 
3267
        postSpace = true, first = *++line;
 
3268
    if(strchr(spaceplus_cmd, cmd) && !postSpace && first) {
 
3269
        s = line;
 
3270
        while(isdigitByte(*s))
 
3271
            ++s;
 
3272
        if(*s) {
 
3273
            setError("no space after command");
 
3274
            return (globSub = false);
 
3275
        }
 
3276
    }
 
3277
    if(globSub && !strchr(global_cmd, cmd)) {
 
3278
        setError("the %c command cannot be applied globally", icmd);
 
3279
        return (globSub = false);
 
3280
    }
 
3281
 
 
3282
/* move/copy destination, the third address */
 
3283
    if(cmd == 't' || cmd == 'm') {
 
3284
        if(!first) {
 
3285
            destLine = cw->dot;
 
3286
        } else {
 
3287
            if(!strchr(valid_laddr, first)) {
 
3288
                setError("invalid move/copy destination");
 
3289
                return (globSub = false);
 
3290
            }
 
3291
            if(!getRangePart(line, &destLine, &line))
 
3292
                return (globSub = false);
 
3293
            first = *line;
 
3294
        }                       /* was there something after m or t */
 
3295
    }
 
3296
 
 
3297
    /* m or t */
 
3298
    /* Any command other than a lone b resets history */
 
3299
    if(cmd != 'b' || first) {
 
3300
        fetchHistory(0, 0);     /* reset history */
 
3301
    }
 
3302
 
 
3303
/* env variable and wild card expansion */
 
3304
    if(strchr("brewf", cmd) && first && !isURL(line)) {
 
3305
        if(!envFile(line, &line))
 
3306
            return false;
 
3307
        first = *line;
 
3308
    }
 
3309
 
 
3310
    if(cmd == 'z') {
 
3311
        if(isdigitByte(first)) {
 
3312
            last_z = strtol(line, (char **)&line, 10);
 
3313
            if(!last_z)
 
3314
                last_z = 1;
 
3315
            first = *line;
 
3316
        }
 
3317
        startRange = endRange + 1;
 
3318
        endRange = startRange;
 
3319
        if(startRange > cw->dol) {
 
3320
            setError("line number too large");
 
3321
            return false;
 
3322
        }
 
3323
        cmd = 'p';
 
3324
        endRange += last_z - 1;
 
3325
        if(endRange > cw->dol)
 
3326
            endRange = cw->dol;
 
3327
    }
 
3328
 
 
3329
    /* z */
 
3330
    /* the a+ feature, when you thought you were in append mode */
 
3331
    if(cmd == 'a') {
 
3332
        if(stringEqual(line, "+"))
 
3333
            ++line, first = 0;
 
3334
        else
 
3335
            linePending[0] = 0;
 
3336
    } else
 
3337
        linePending[0] = 0;
 
3338
 
 
3339
    if(first && strchr(nofollow_cmd, cmd)) {
 
3340
        setError("unexpected text after the %c command", icmd);
 
3341
        return (globSub = false);
 
3342
    }
 
3343
 
 
3344
    if(cmd == 'h') {
 
3345
        showError();
 
3346
        return true;
 
3347
    }
 
3348
    /* h */
 
3349
    if(cmd == 'H') {
 
3350
        if(helpMessagesOn ^= 1)
 
3351
            debugPrint(1, "help messages on");
 
3352
        return true;
 
3353
    }
 
3354
    /* H */
 
3355
    if(strchr("lpn", cmd)) {
 
3356
        for(i = startRange; i <= endRange; ++i) {
 
3357
            displayLine(i);
 
3358
            cw->dot = i;
 
3359
            if(intFlag)
 
3360
                break;
 
3361
        }
 
3362
        return true;
 
3363
    }
 
3364
    /* lpn */
 
3365
    if(cmd == '=') {
 
3366
        printf("%d\n", endRange);
 
3367
        return true;
 
3368
    }
 
3369
    /* = */
 
3370
    if(cmd == 'B') {
 
3371
        return balanceLine(line);
 
3372
    }
 
3373
    /* B */
 
3374
    if(cmd == 'u') {
 
3375
        struct ebWindow *uw = &undoWindow;
 
3376
        char *swapmap;
 
3377
        if(!cw->firstOpMode) {
 
3378
            setError("nothing to undo");
 
3379
            return false;
 
3380
        }
 
3381
/* swap, so we can undo our undo, if need be */
 
3382
        i = uw->dot, uw->dot = cw->dot, cw->dot = i;
 
3383
        i = uw->dol, uw->dol = cw->dol, cw->dol = i;
 
3384
        for(j = 0; j < 26; ++j) {
 
3385
            i = uw->labels[j], uw->labels[j] = cw->labels[j], cw->labels[j] = i;
 
3386
        }
 
3387
        swapmap = uw->map, uw->map = cw->map, cw->map = swapmap;
 
3388
        return true;
 
3389
    }
 
3390
    /* u */
 
3391
    if(cmd == 'k') {
 
3392
        if(!islowerByte(first) || line[1]) {
 
3393
            setError("please enter k[a-z]");
 
3394
            return false;
 
3395
        }
 
3396
        if(startRange < endRange) {
 
3397
            setError("cannot label an entire range");
 
3398
            return false;
 
3399
        }
 
3400
        cw->labels[first - 'a'] = endRange;
 
3401
        return true;
 
3402
    }
 
3403
 
 
3404
    /* k */
 
3405
    /* Find suffix, as in 27,59w2 */
 
3406
    if(!postSpace) {
 
3407
        cx = stringIsNum(line);
 
3408
        if(!cx) {
 
3409
            setError("%s 0 is invalid", cmd == '^' ? "backing up" : "session");
 
3410
            return false;
 
3411
        }
 
3412
        if(cx < 0)
 
3413
            cx = 0;
 
3414
    }
 
3415
 
 
3416
    if(cmd == 'q') {
 
3417
        if(cx) {
 
3418
            if(!cxCompare(cx))
 
3419
                return false;
 
3420
            if(!cxActive(cx))
 
3421
                return false;
 
3422
        } else {
 
3423
            cx = context;
 
3424
            if(first) {
 
3425
                setError("unexpected text after the q command");
 
3426
                return false;
 
3427
            }
 
3428
        }
 
3429
        if(!cxQuit(cx, 2))
 
3430
            return false;
 
3431
        if(cx != context)
 
3432
            return true;
 
3433
/* look around for another active session */
 
3434
        while(true) {
 
3435
            if(++cx == MAXSESSION)
 
3436
                cx = 1;
 
3437
            if(cx == context)
 
3438
                exit(0);
 
3439
            if(!sessionList[cx].lw)
 
3440
                continue;
 
3441
            cxSwitch(cx, true);
 
3442
            return true;
 
3443
        }                       /* loop over sessions */
 
3444
    }
 
3445
    /* q */
 
3446
    if(cmd == 'f') {
 
3447
        if(cx) {
 
3448
            if(!cxCompare(cx))
 
3449
                return false;
 
3450
            if(!cxActive(cx))
 
3451
                return false;
 
3452
            s = sessionList[cx].lw->fileName;
 
3453
            printf("%s", s ? s : "no file");
 
3454
            if(sessionList[cx].lw->binMode)
 
3455
                printf(" [binary]");
 
3456
            printf("\n");
 
3457
            return true;
 
3458
        }                       /* another session */
 
3459
        if(first) {
 
3460
            if(cw->dirMode) {
 
3461
                setError("cannot change the name of a directory");
 
3462
                return false;
 
3463
            }
 
3464
            nzFree(cw->fileName);
 
3465
            cw->fileName = cloneString(line);
 
3466
        }
 
3467
        s = cw->fileName;
 
3468
        printf("%s", s ? s : "no file");
 
3469
        if(cw->binMode)
 
3470
            printf(" [binary]");
 
3471
        printf("\n");
 
3472
        return true;
 
3473
    }
 
3474
    /* f */
 
3475
    if(cmd == 'w') {
 
3476
        if(cx) {                /* write to another buffer */
 
3477
            if(writeMode == O_APPEND) {
 
3478
                setError("cannot append to another buffer");
 
3479
                return false;
 
3480
            }
 
3481
            return writeContext(cx);
 
3482
        }
 
3483
        if(!first)
 
3484
            line = cw->fileName;
 
3485
        if(!line) {
 
3486
            setError("no file specified");
 
3487
            return false;
 
3488
        }
 
3489
        if(cw->dirMode && stringEqual(line, cw->fileName)) {
 
3490
            setError
 
3491
               ("cannot write to the directory; files are modified as you go");
 
3492
            return false;
 
3493
        }
 
3494
        return writeFile(line, writeMode);
 
3495
    }
 
3496
    /* w */
 
3497
    if(cmd == '^') {            /* back key, pop the stack */
 
3498
        if(first && !cx) {
 
3499
            setError("unexpected text after the ^ command");
 
3500
            return false;
 
3501
        }
 
3502
        if(!cx)
 
3503
            cx = 1;
 
3504
        while(cx) {
 
3505
            struct ebWindow *prev = cw->prev;
 
3506
            if(!prev) {
 
3507
                setError("no previous text");
 
3508
                return false;
 
3509
            }
 
3510
            if(!cxQuit(context, 1))
 
3511
                return false;
 
3512
            carrySubstitutionStrings(cw, prev);
 
3513
            sessionList[context].lw = cw = prev;
 
3514
            --cx;
 
3515
        }
 
3516
        printDot();
 
3517
        return true;
 
3518
    }
 
3519
    /* ^ */
 
3520
    if(cmd == 'M') {            /* move this to another session */
 
3521
        if(first && !cx) {
 
3522
            setError("unexpected text after the M command");
 
3523
            return false;
 
3524
        }
 
3525
        if(!first) {
 
3526
            setError("destination session not specified");
 
3527
            return false;
 
3528
        }
 
3529
        if(!cw->prev) {
 
3530
            setError("no previous text, cannot back up");
 
3531
            return false;
 
3532
        }
 
3533
        if(!cxCompare(cx))
 
3534
            return false;
 
3535
        if(cxActive(cx) && !cxQuit(cx, 2))
 
3536
            return false;
 
3537
/* Magic with pointers, hang on to your hat. */
 
3538
        sessionList[cx].fw = sessionList[cx].lw = cw;
 
3539
        cs->lw = cw->prev;
 
3540
        cw->prev = 0;
 
3541
        cw = cs->lw;
 
3542
        printDot();
 
3543
        return true;
 
3544
    }
 
3545
    /* M */
 
3546
    if(cmd == 'A') {
 
3547
        char *a;
 
3548
        if(!cxQuit(context, 0))
 
3549
            return false;
 
3550
        if(!(a = showLinks()))
 
3551
            return false;
 
3552
        freeUndoLines(cw->map);
 
3553
        undoWindow.map = 0;
 
3554
        nzFree(preWindow.map);
 
3555
        preWindow.map = 0;
 
3556
        cw->firstOpMode = cw->changeMode = false;
 
3557
        w = createWindow();
 
3558
        w->prev = cw;
 
3559
        cw = w;
 
3560
        cs->lw = w;
 
3561
        rc = addTextToBuffer((pst) a, strlen(a), 0);
 
3562
        nzFree(a);
 
3563
        undoable = cw->changeMode = false;
 
3564
        fileSize = apparentSize(context, false);
 
3565
        return rc;
 
3566
    }
 
3567
    /* A */
 
3568
    if(cmd == '<') {            /* run a function */
 
3569
        return runEbFunction(line);
 
3570
    }
 
3571
 
 
3572
    /* < */
 
3573
    /* go to a file in a directory listing */
 
3574
    if(cmd == 'g' && cw->dirMode && !first) {
 
3575
        char *p, *dirline, *endline;
 
3576
        if(endRange > startRange) {
 
3577
            setError("cannot apply the g command to a range");
 
3578
            return false;
 
3579
        }
 
3580
        p = (char *)fetchLine(endRange, -1);
 
3581
        j = pstLength((pst) p);
 
3582
        --j;
 
3583
        p[j] = 0;               /* temporary */
 
3584
        dirline = makeAbsPath(p);
 
3585
        p[j] = '\n';
 
3586
        cmd = 'e';
 
3587
        if(!dirline)
 
3588
            return false;
 
3589
/* I don't think we need to make a copy here. */
 
3590
        line = dirline;
 
3591
        first = *line;
 
3592
    }
 
3593
    /* g in directory mode */
 
3594
    if(cmd == 'e') {
 
3595
        if(cx) {
 
3596
            if(!cxCompare(cx))
 
3597
                return false;
 
3598
            cxSwitch(cx, true);
 
3599
            return true;
 
3600
        }
 
3601
        if(!first) {
 
3602
            printf("session %d\n", context);
 
3603
            return true;
 
3604
        }
 
3605
/* more e to come */
 
3606
    }
 
3607
 
 
3608
    if(cmd == 'g') {            /* see if it's a go command */
 
3609
        char *p, *h;
 
3610
        int tagno;
 
3611
        bool click, dclick, over;
 
3612
        bool jsh, jsgo, jsdead;
 
3613
        s = line;
 
3614
        j = 0;
 
3615
        if(first)
 
3616
            j = strtol(line, (char **)&s, 10);
 
3617
        if(j >= 0 && (!*s || stringEqual(s, "?"))) {
 
3618
            jsh = jsgo = nogo = false;
 
3619
            jsdead = cw->jsdead;
 
3620
            if(!cw->jsc)
 
3621
                jsdead = true;
 
3622
            cmd = 'b';
 
3623
            if(endRange > startRange) {
 
3624
                setError("cannot apply the g command to a range");
 
3625
                return false;
 
3626
            }
 
3627
            p = (char *)fetchLine(endRange, -1);
 
3628
            jMyContext();
 
3629
            findField(p, 0, j, &n, &tagno, &h, &ev);
 
3630
            debugPrint(5, "findField returns %d, %s", tagno, h);
 
3631
            if(!h) {
 
3632
                fieldNumProblem("links", 'g', j, n);
 
3633
                return false;
 
3634
            }
 
3635
            jsh = memEqualCI(h, "javascript:", 11);
 
3636
            if(tagno) {
 
3637
                over = tagHandler(tagno, "onmouseover");
 
3638
                click = tagHandler(tagno, "onclick");
 
3639
                dclick = tagHandler(tagno, "ondblclick");
 
3640
            }
 
3641
            if(click)
 
3642
                jsgo = true;
 
3643
            jsgo |= jsh;
 
3644
            nogo = stringEqual(h, "#");
 
3645
            nogo |= jsh;
 
3646
            debugPrint(5, "go%d nogo%d jsh%d dead%d", jsgo, nogo, jsh, jsdead);
 
3647
            debugPrint(5, "click %d dclick %d over %d", click, dclick, over);
 
3648
            if(jsgo & jsdead) {
 
3649
                if(nogo)
 
3650
                    puts("javascript is disabled, no action taken");
 
3651
                else
 
3652
                    puts("javascript is disabled, going straight to the url");
 
3653
                jsgo = jsh = false;
 
3654
            }
 
3655
            line = allocatedLine = h;
 
3656
            first = *line;
 
3657
            setError(0);
 
3658
            rc = false;
 
3659
            if(jsgo) {
 
3660
                jSyncup();
 
3661
/* The program might depend on the mouseover code running first */
 
3662
                if(over) {
 
3663
                    rc = handlerGo(ev, "onmouseover");
 
3664
                    jsdw();
 
3665
                    if(newlocation)
 
3666
                        goto redirect;
 
3667
                }
 
3668
            }
 
3669
/* This is the only handler where false tells the browser to do something else. */
 
3670
            if(!rc && !jsdead)
 
3671
                set_property_string(jwin, "status", h);
 
3672
            if(jsgo && click) {
 
3673
                rc = handlerGo(ev, "onclick");
 
3674
                jsdw();
 
3675
                if(newlocation)
 
3676
                    goto redirect;
 
3677
                if(!rc)
 
3678
                    return true;
 
3679
            }
 
3680
            if(jsh) {
 
3681
                rc = javaParseExecute(jwin, h, 0, 0);
 
3682
                jsdw();
 
3683
                if(newlocation)
 
3684
                    goto redirect;
 
3685
                return true;
 
3686
            }
 
3687
            if(nogo)
 
3688
                return true;
 
3689
        }                       /* go command */
 
3690
    }
 
3691
 
 
3692
    if(cmd == 's') {
 
3693
/* Some shorthand, like s,2 to split the line at the second comma */
 
3694
        if(!first) {
 
3695
            strcpy(newline, "//%");
 
3696
            line = newline;
 
3697
        } else if(strchr(",.;:!?)-\"", first) &&
 
3698
           (!line[1] || isdigitByte(line[1]) && !line[2])) {
 
3699
            char esc[2];
 
3700
            esc[0] = esc[1] = 0;
 
3701
            if(first == '.' || first == '?')
 
3702
                esc[0] = '\\';
 
3703
            sprintf(newline, "/%s%c +/%c\\n%s%s",
 
3704
               esc, first, first, (line[1] ? "/" : ""), line + 1);
 
3705
            debugPrint(7, "shorthand regexp %s", newline);
 
3706
            line = newline;
 
3707
        }
 
3708
        first = *line;
 
3709
    }
 
3710
 
 
3711
    scmd = ' ';
 
3712
    if((cmd == 'i' || cmd == 's') && first) {
 
3713
        char c;
 
3714
        s = line;
 
3715
        if(isdigitByte(*s))
 
3716
            cx = strtol(s, (char **)&s, 10);
 
3717
        c = *s;
 
3718
        if(c && (strchr(valid_delim, c) || cmd == 'i' && strchr("*<?=", c))) {
 
3719
            if(!cw->browseMode && (cmd == 'i' || cx)) {
 
3720
                setError("not in browse mode");
 
3721
                return false;
 
3722
            }
 
3723
            if(endRange > startRange && cmd == 'i') {
 
3724
                setError("cannot apply the i%c command to a range", c);
 
3725
                return false;
 
3726
            }
 
3727
            if(cmd == 'i' && strchr("?=<*", c)) {
 
3728
                char *p;
 
3729
                scmd = c;
 
3730
                line = s + 1;
 
3731
                first = *line;
 
3732
                debugPrint(5, "scmd = %c", scmd);
 
3733
                cw->dot = endRange;
 
3734
                p = (char *)fetchLine(cw->dot, -1);
 
3735
                j = 1;
 
3736
                if(scmd == '*')
 
3737
                    j = 2;
 
3738
                if(scmd == '?')
 
3739
                    j = 3;
 
3740
                findInputField(p, j, cx, &n, &tagno);
 
3741
                debugPrint(5, "findField returns %d.%d", n, tagno);
 
3742
                if(!tagno) {
 
3743
                    fieldNumProblem((c == '*' ? "buttons" : "input fields"),
 
3744
                       'i', cx, n);
 
3745
                    return false;
 
3746
                }
 
3747
                if(scmd == '?') {
 
3748
                    infShow(tagno, line);
 
3749
                    return true;
 
3750
                }
 
3751
                if(c == '<') {
 
3752
                    bool fromfile = false;
 
3753
                    if(globSub) {
 
3754
                        setError("cannot use i< in a global command");
 
3755
                        return (globSub = false);
 
3756
                    }
 
3757
                    skipWhite(&line);
 
3758
                    if(!*line) {
 
3759
                        setError("no file specified");
 
3760
                        return false;
 
3761
                    }
 
3762
                    n = stringIsNum(line);
 
3763
                    if(n >= 0) {
 
3764
                        char *p;
 
3765
                        int plen, dol;
 
3766
                        if(!cxCompare(n) || !cxActive(n))
 
3767
                            return false;
 
3768
                        dol = sessionList[n].lw->dol;
 
3769
                        if(!dol) {
 
3770
                            setError("buffer %d is empty", n);
 
3771
                            return false;
 
3772
                        }
 
3773
                        if(dol > 1) {
 
3774
                            setError("buffer %d contains more than one line",
 
3775
                               n);
 
3776
                            return false;
 
3777
                        }
 
3778
                        p = (char *)fetchLineContext(1, 1, n);
 
3779
                        plen = pstLength((pst) p);
 
3780
                        if(plen > sizeof (newline))
 
3781
                            plen = sizeof (newline);
 
3782
                        memcpy(newline, p, plen);
 
3783
                        n = plen;
 
3784
                        nzFree(p);
 
3785
                    } else {
 
3786
                        int fd;
 
3787
                        fromfile = true;
 
3788
                        if(!envFile(line, &line))
 
3789
                            return false;
 
3790
                        fd = open(line, O_RDONLY | O_TEXT);
 
3791
                        if(fd < 0) {
 
3792
                            setError("cannot open %s", line);
 
3793
                            return false;
 
3794
                        }
 
3795
                        n = read(fd, newline, sizeof (newline));
 
3796
                        close(fd);
 
3797
                        if(n < 0) {
 
3798
                            setError("cannot read from %s", line);
 
3799
                            return false;
 
3800
                        }
 
3801
                    }
 
3802
                    for(j = 0; j < n; ++j) {
 
3803
                        if(newline[j] == 0) {
 
3804
                            setError("input text contains nulls", line);
 
3805
                            return false;
 
3806
                        }
 
3807
                        if(newline[j] == '\r' && !fromfile &&
 
3808
                           j < n - 1 && newline[j + 1] != '\n') {
 
3809
                            setError
 
3810
                               ("line contains an embeded carriage return");
 
3811
                            return false;
 
3812
                        }
 
3813
                        if(newline[j] == '\r' || newline[j] == '\n')
 
3814
                            break;
 
3815
                    }
 
3816
                    if(j == sizeof (newline)) {
 
3817
                        setError("first line of %s is too long", line);
 
3818
                        return false;
 
3819
                    }
 
3820
                    newline[j] = 0;
 
3821
                    line = newline;
 
3822
                    scmd = '=';
 
3823
                }
 
3824
                if(c == '*') {
 
3825
                    jSyncup();
 
3826
                    if(!infPush(tagno, &allocatedLine))
 
3827
                        return false;
 
3828
                    if(newlocation)
 
3829
                        goto redirect;
 
3830
/* No url means it was a reset button */
 
3831
                    if(!allocatedLine)
 
3832
                        return true;
 
3833
                    line = allocatedLine;
 
3834
                    first = *line;
 
3835
                    cmd = 'b';
 
3836
                }
 
3837
            } else
 
3838
                cmd = 's';
 
3839
        } else {
 
3840
            setError("unexpected text after the %c command", icmd);
 
3841
            return false;
 
3842
        }
 
3843
    }
 
3844
 
 
3845
  rebrowse:
 
3846
    if(cmd == 'e' || cmd == 'b' && first && first != '#') {
 
3847
        if(cw->fileName && !noStack && sameURL(line, cw->fileName)) {
 
3848
            if(stringEqual(line, cw->fileName)) {
 
3849
                setError
 
3850
                   ("file is currently in buffer - please use the rf command to refresh");
 
3851
                return false;
 
3852
            }
 
3853
/* Same url, but a different #section */
 
3854
            s = strchr(line, '#');
 
3855
            if(!s) {            /* no section specified */
 
3856
                cw->dot = 1;
 
3857
                if(!cw->dol)
 
3858
                    cw->dot = 0;
 
3859
                printDot();
 
3860
                return true;
 
3861
            }
 
3862
            line = s;
 
3863
            first = '#';
 
3864
            goto browse;
 
3865
        }
 
3866
 
 
3867
/* Different URL, go get it. */
 
3868
/* did you make changes that you didn't write? */
 
3869
        if(!cxQuit(context, 0))
 
3870
            return false;
 
3871
        freeUndoLines(cw->map);
 
3872
        undoWindow.map = 0;
 
3873
        nzFree(preWindow.map);
 
3874
        preWindow.map = 0;
 
3875
        cw->firstOpMode = cw->changeMode = false;
 
3876
        startRange = endRange = 0;
 
3877
        changeFileName = 0;     /* should already be zero */
 
3878
        w = createWindow();
 
3879
        cw = w;                 /* we might wind up putting this back */
 
3880
/* Check for sendmail link */
 
3881
        if(cmd == 'b' && memEqualCI(line, "mailto:", 7)) {
 
3882
            char *addr, *subj, *body;
 
3883
            char *q;
 
3884
            int ql;
 
3885
            decodeMailURL(line, &addr, &subj, &body);
 
3886
            ql = strlen(addr);
 
3887
            ql += 4;            /* to:\n */
 
3888
            ql += subj ? strlen(subj) : 5;
 
3889
            ql += 9;            /* subject:\n */
 
3890
            if(body)
 
3891
                ql += strlen(body);
 
3892
            q = allocMem(ql + 1);
 
3893
            sprintf(q, "to:%s\nSubject:%s\n%s", addr, subj ? subj : "Hello",
 
3894
               body ? body : "");
 
3895
            j = addTextToBuffer((pst) q, ql, 0);
 
3896
            nzFree(q);
 
3897
            nzFree(addr);
 
3898
            nzFree(subj);
 
3899
            nzFree(body);
 
3900
            if(j)
 
3901
                printf
 
3902
                   ("SendMail link.  Compose your mail, type sm to send, then ^ to get back.\n");
 
3903
        } else {
 
3904
            w->fileName = cloneString(line);
 
3905
            if(icmd == 'g' && !nogo && isURL(cw->fileName))
 
3906
                debugPrint(2, "*%s", cw->fileName);
 
3907
            j = readFile(cw->fileName, "");
 
3908
        }
 
3909
        w->firstOpMode = w->changeMode = false;
 
3910
        undoable = false;
 
3911
        cw = cs->lw;
 
3912
/* Don't push a new session if we were trying to read a url,
 
3913
 * and didn't get anything.  This is a feature that I'm
 
3914
 * not sure if I really like. */
 
3915
        if(!serverData && isURL(w->fileName)) {
 
3916
            fileSize = -1;
 
3917
            freeWindow(w);
 
3918
            if(noStack && cw->prev) {
 
3919
                w = cw;
 
3920
                cw = w->prev;
 
3921
                cs->lw = cw;
 
3922
                freeWindow(w);
 
3923
            }
 
3924
            return j;
 
3925
        }
 
3926
        if(noStack) {
 
3927
            w->prev = cw->prev;
 
3928
            cxQuit(context, 1);
 
3929
        } else {
 
3930
            w->prev = cw;
 
3931
        }
 
3932
        cs->lw = cw = w;
 
3933
        if(!w->prev)
 
3934
            cs->fw = w;
 
3935
        if(!j)
 
3936
            return false;
 
3937
        if(changeFileName) {
 
3938
            nzFree(w->fileName);
 
3939
            w->fileName = changeFileName;
 
3940
            changeFileName = 0;
 
3941
        }
 
3942
/* Some files we just can't browse */
 
3943
        if(!cw->dol || cw->binMode | cw->dirMode)
 
3944
            cmd = 'e';
 
3945
        if(cmd == 'e')
 
3946
            return true;
 
3947
    }
 
3948
 
 
3949
  browse:
 
3950
    if(cmd == 'b') {
 
3951
        if(!cw->browseMode) {
 
3952
            if(cw->binMode) {
 
3953
                setError("cannot browse a binary file");
 
3954
                return false;
 
3955
            }
 
3956
            if(cw->dirMode) {
 
3957
                setError("cannot browse a directory");
 
3958
                return false;
 
3959
            }
 
3960
            if(!cw->dol) {
 
3961
                setError("cannot browse an empty file");
 
3962
                return false;
 
3963
            }
 
3964
            if(fileSize >= 0) {
 
3965
                debugPrint(1, "%d", fileSize);
 
3966
                fileSize = -1;
 
3967
            }
 
3968
            if(!browseCurrentBuffer()) {
 
3969
                if(icmd == 'b') {
 
3970
                    setError("this doesn't look like browsable text");
 
3971
                    return false;
 
3972
                }
 
3973
                return true;
 
3974
            }
 
3975
        } else if(!first) {
 
3976
            setError("already browsing");
 
3977
            return false;
 
3978
        }
 
3979
 
 
3980
        if(newlocation) {
 
3981
            if(!refreshDelay(newloc_d, newlocation)) {
 
3982
                nzFree(newlocation);
 
3983
                newlocation = 0;
 
3984
            } else if(fetchHistory(cw->fileName, newlocation) < 0) {
 
3985
                nzFree(newlocation);
 
3986
                newlocation = 0;
 
3987
                showError();
 
3988
            } else {
 
3989
              redirect:
 
3990
                noStack = newloc_rf;
 
3991
                nzFree(allocatedLine);
 
3992
                line = allocatedLine = newlocation;
 
3993
                debugPrint(2, "redirect %s", line);
 
3994
                newlocation = 0;
 
3995
                icmd = cmd = 'b';
 
3996
                first = *line;
 
3997
                if(intFlag) {
 
3998
                    puts("redirection interrupted by user");
 
3999
                    return true;
 
4000
                }
 
4001
                goto rebrowse;
 
4002
            }
 
4003
        }
 
4004
 
 
4005
/* Jump to the #section, if specified in the url */
 
4006
        s = strchr(line, '#');
 
4007
        if(!s)
 
4008
            return true;
 
4009
        ++s;
 
4010
/* Sometimes there's a 3 in the midst of a long url,
 
4011
 * probably with post data.  It really screws things up.
 
4012
 * Here is a kludge to avoid this problem.
 
4013
 * Some day I need to figure this out. */
 
4014
        if(strlen(s) > 24)
 
4015
            return true;
 
4016
/* Print the file size before we print the line. */
 
4017
        if(fileSize >= 0) {
 
4018
            debugPrint(1, "%d", fileSize);
 
4019
            fileSize = -1;
 
4020
        }
 
4021
        for(i = 1; i <= cw->dol; ++i) {
 
4022
            char *p = (char *)fetchLine(i, -1);
 
4023
            if(lineHasTag(p, s)) {
 
4024
                cw->dot = i;
 
4025
                printDot();
 
4026
                return true;
 
4027
            }
 
4028
        }
 
4029
        setError("label %s not found", s);
 
4030
        return false;
 
4031
    }
 
4032
    /* b */
 
4033
    if(!globSub) {              /* get ready for subsequent undo */
 
4034
        struct ebWindow *pw = &preWindow;
 
4035
        pw->dot = cw->dot;
 
4036
        pw->dol = cw->dol;
 
4037
        memcpy(pw->labels, cw->labels, 26 * sizeof (int));
 
4038
        pw->binMode = cw->binMode;
 
4039
        pw->nlMode = cw->nlMode;
 
4040
        pw->dirMode = cw->dirMode;
 
4041
        if(pw->map) {
 
4042
            if(!cw->map || !stringEqual(pw->map, cw->map)) {
 
4043
                free(pw->map);
 
4044
                pw->map = 0;
 
4045
            }
 
4046
        }
 
4047
        if(cw->map && !pw->map)
 
4048
            pw->map = cloneString(cw->map);
 
4049
    }
 
4050
    /* data saved for undo */
 
4051
    if(cmd == 'g' || cmd == 'v') {
 
4052
        return doGlobal(line);
 
4053
    }
 
4054
    /* g or v */
 
4055
    if(cmd == 'm' || cmd == 't') {
 
4056
        return moveCopy();
 
4057
    }
 
4058
 
 
4059
    if(cmd == 'i') {
 
4060
        if(scmd == '=') {
 
4061
            rc = infReplace(tagno, line, 1);
 
4062
            if(newlocation)
 
4063
                goto redirect;
 
4064
            return rc;
 
4065
        }
 
4066
        if(cw->browseMode) {
 
4067
            setError("i not available in browse mode");
 
4068
            return false;
 
4069
        }
 
4070
        cmd = 'a';
 
4071
        --startRange, --endRange;
 
4072
    }
 
4073
    /* i */
 
4074
    if(cmd == 'c') {
 
4075
        delText(startRange, endRange);
 
4076
        endRange = --startRange;
 
4077
        cmd = 'a';
 
4078
    }
 
4079
    /* c */
 
4080
    if(cmd == 'a') {
 
4081
        if(inscript) {
 
4082
            setError("cannot run an insert command from an edbrowse function");
 
4083
            return false;
 
4084
        }
 
4085
        return inputLinesIntoBuffer();
 
4086
    }
 
4087
    /* a */
 
4088
    if(cmd == 'd') {
 
4089
        if(cw->dirMode) {
 
4090
            j = delFiles();
 
4091
            if(!j)
 
4092
                globSub = false;
 
4093
            return j;
 
4094
        }
 
4095
        if(endRange == cw->dol)
 
4096
            cw->nlMode = false;
 
4097
        delText(startRange, endRange);
 
4098
        return true;
 
4099
    }
 
4100
    /* d */
 
4101
    if(cmd == 'j' || cmd == 'J') {
 
4102
        return joinText();
 
4103
    }
 
4104
    /* j */
 
4105
    if(cmd == 'r') {
 
4106
        if(cx)
 
4107
            return readContext(cx);
 
4108
        if(first) {
 
4109
            j = readFile(line, "");
 
4110
            if(!serverData)
 
4111
                fileSize = -1;
 
4112
            return j;
 
4113
        }
 
4114
        setError("no file specified");
 
4115
        return false;
 
4116
    }
 
4117
 
 
4118
    if(cmd == 's') {
 
4119
        j = substituteText(line);
 
4120
        if(j < 0) {
 
4121
            globSub = false;
 
4122
            j = false;
 
4123
        }
 
4124
        if(newlocation)
 
4125
            goto redirect;
 
4126
        return j;
 
4127
    }
 
4128
    /* s */
 
4129
    setError("command %c not yet implemented", icmd);
 
4130
    return (globSub = false);
 
4131
}                               /* runCommand */
 
4132
 
 
4133
bool
 
4134
edbrowseCommand(const char *line, bool script)
 
4135
{
 
4136
    bool rc;
 
4137
    globSub = intFlag = false;
 
4138
    inscript = script;
 
4139
    fileSize = -1;
 
4140
    skipWhite(&line);
 
4141
    rc = runCommand(line);
 
4142
    if(fileSize >= 0)
 
4143
        debugPrint(1, "%d", fileSize);
 
4144
    fileSize = -1;
 
4145
    if(!rc) {
 
4146
        if(!script)
 
4147
            showErrorConditional(cmd);
 
4148
        eeCheck();
 
4149
    }
 
4150
    if(undoable) {
 
4151
        struct ebWindow *pw = &preWindow;
 
4152
        struct ebWindow *uw = &undoWindow;
 
4153
        debugPrint(6, "undoable");
 
4154
        uw->dot = pw->dot;
 
4155
        uw->dol = pw->dol;
 
4156
        if(uw->map && pw->map && stringEqual(uw->map, pw->map)) {
 
4157
            free(pw->map);
 
4158
        } else {
 
4159
            debugPrint(6, "success freeUndo");
 
4160
            freeUndoLines(pw->map);
 
4161
            uw->map = pw->map;
 
4162
        }
 
4163
        pw->map = 0;
 
4164
        memcpy(uw->labels, pw->labels, 26 * sizeof (int));
 
4165
        uw->binMode = pw->binMode;
 
4166
        uw->nlMode = pw->nlMode;
 
4167
        uw->dirMode = pw->dirMode;
 
4168
        undoable = false;
 
4169
    }
 
4170
    return rc;
 
4171
}                               /* edbrowseCommand */
 
4172
 
 
4173
/* Take some text, usually empty, and put it in a side buffer. */
 
4174
int
 
4175
sideBuffer(int cx, const char *text, const char *bufname, bool autobrowse)
 
4176
{
 
4177
    int svcx = context;
 
4178
    bool rc;
 
4179
    if(cx) {
 
4180
        cxQuit(cx, 2);
 
4181
    } else {
 
4182
        for(cx = 1; cx < MAXSESSION; ++cx)
 
4183
            if(!sessionList[cx].lw)
 
4184
                break;
 
4185
        if(cx == MAXSESSION) {
 
4186
            debugPrint(0,
 
4187
               "warning: no buffers available to handle the ancillary window");
 
4188
            return 0;
 
4189
        }
 
4190
    }
 
4191
    cxSwitch(cx, false);
 
4192
    if(bufname) {
 
4193
        cw->fileName = cloneString(bufname);
 
4194
        debrowseSuffix(cw->fileName);
 
4195
    }
 
4196
    if(*text) {
 
4197
        rc = addTextToBuffer((pst) text, strlen(text), 0);
 
4198
        if(!rc)
 
4199
            debugPrint(0,
 
4200
               "warning: could not preload <buffer %d> with its initial text",
 
4201
               cx);
 
4202
        if(autobrowse) {
 
4203
/* This is html; we need to render it.
 
4204
 * I'm disabling javascript in this window.
 
4205
 * Why?
 
4206
 * Because this window is being created by javascript,
 
4207
 * and if we call more javascript, well, I don't think
 
4208
 * any of that code is reentrant.
 
4209
 * Smells like a disaster in the making. */
 
4210
            allowJS = false;
 
4211
            browseCurrentBuffer();
 
4212
            allowJS = true;
 
4213
        }                       /* browse the side window */
 
4214
    }
 
4215
    /* put text in the side window */
 
4216
    /* back to original context */
 
4217
    cxSwitch(svcx, false);
 
4218
    return cx;
 
4219
}                               /* sideBuffer */
 
4220
 
 
4221
/* Bailing wire and duct tape, here it comes.
 
4222
 * You'd never know I was a professional programmer.  :-)
 
4223
 * Ok, the text buffer, that you edit, is the final arbiter on most
 
4224
 * (but not all) input fields.  And when javascript changes
 
4225
 * one of these values, it is copied back to the text buffer,
 
4226
 * where you can see it, and modify it as you like.
 
4227
 * But what happens if that javascript is running while the html is parsed,
 
4228
 * and before the page even exists?
 
4229
 * I hadn't thought of that.
 
4230
 * The following is a cache of input fields that have been changed,
 
4231
 * before we even had a chance to render the page. */
 
4232
 
 
4233
struct inputChange {
 
4234
    struct inputChange *next, *prev;
 
4235
    int tagno;
 
4236
    char value[4];
 
4237
};
 
4238
static struct listHead inputChangesPending = {
 
4239
    &inputChangesPending, &inputChangesPending
 
4240
};
 
4241
static struct inputChange *ic;
 
4242
 
 
4243
bool
 
4244
browseCurrentBuffer(void)
 
4245
{
 
4246
    char *rawbuf, *newbuf;
 
4247
    int rawsize, j;
 
4248
    bool rc, remote = false, do_ip = false;
 
4249
    bool save_ch = cw->changeMode;
 
4250
    uchar bmode = 0;
 
4251
 
 
4252
    if(cw->fileName)
 
4253
        remote = isURL(cw->fileName);
 
4254
/* A mail message often contains lots of html tags,
 
4255
 * so we need to check for email headers first. */
 
4256
    if(!remote && emailTest())
 
4257
        bmode = 1;
 
4258
    else if(htmlTest())
 
4259
        bmode = 2;
 
4260
    else
 
4261
        return false;
 
4262
 
 
4263
    if(!unfoldBuffer(context, false, &rawbuf, &rawsize))
 
4264
        return false;           /* should never happen */
 
4265
    prepareForBrowse(rawbuf, rawsize);
 
4266
 
 
4267
/* No harm in running this code in mail client, but no help either,
 
4268
 * and it begs for bugs, so leave it out. */
 
4269
    if(!ismc) {
 
4270
        freeUndoLines(cw->map);
 
4271
        undoWindow.map = 0;
 
4272
        nzFree(preWindow.map);
 
4273
        preWindow.map = 0;
 
4274
        cw->firstOpMode = false;
 
4275
 
 
4276
/* There shouldn't be anything in the input pending list, but clear
 
4277
 * it out, just to be safe. */
 
4278
        freeList(&inputChangesPending);
 
4279
    }
 
4280
 
 
4281
    if(bmode == 1) {
 
4282
        newbuf = emailParse(rawbuf);
 
4283
        do_ip = true;
 
4284
        if(!ipbFile)
 
4285
            do_ip = false;
 
4286
        if(passMail)
 
4287
            do_ip = false;
 
4288
        if(memEqualCI(newbuf, "<html>\n", 7)) {
 
4289
/* double browse, mail then html */
 
4290
            bmode = 2;
 
4291
            rawbuf = newbuf;
 
4292
            rawsize = strlen(rawbuf);
 
4293
            prepareForBrowse(rawbuf, rawsize);
 
4294
        }
 
4295
    }
 
4296
 
 
4297
    if(bmode == 2) {
 
4298
        cw->jsdead = !javaOK(cw->fileName);
 
4299
        if(!cw->jsdead)
 
4300
            cw->jsc = createJavaContext();
 
4301
        nzFree(newlocation);    /* should already be 0 */
 
4302
        newlocation = 0;
 
4303
        newbuf = htmlParse(rawbuf, remote);
 
4304
    }
 
4305
 
 
4306
    cw->browseMode = true;
 
4307
    cw->rnlMode = cw->nlMode;
 
4308
    cw->nlMode = false;
 
4309
    cw->r_dot = cw->dot, cw->r_dol = cw->dol;
 
4310
    cw->dot = cw->dol = 0;
 
4311
    cw->r_map = cw->map;
 
4312
    cw->map = 0;
 
4313
    memcpy(cw->r_labels, cw->labels, sizeof (cw->labels));
 
4314
    memset(cw->labels, 0, sizeof (cw->labels));
 
4315
    j = strlen(newbuf);
 
4316
    rc = addTextToBuffer((pst) newbuf, j, 0);
 
4317
    free(newbuf);
 
4318
    cw->firstOpMode = undoable = false;
 
4319
    cw->changeMode = save_ch;
 
4320
    cw->iplist = 0;
 
4321
 
 
4322
    if(cw->fileName) {
 
4323
        j = strlen(cw->fileName);
 
4324
        cw->fileName = reallocMem(cw->fileName, j + 8);
 
4325
        strcat(cw->fileName, ".browse");
 
4326
    }
 
4327
    if(!rc) {
 
4328
        fileSize = -1;
 
4329
        return false;
 
4330
    }                           /* should never happen */
 
4331
    fileSize = apparentSize(context, true);
 
4332
 
 
4333
    if(bmode == 2) {
 
4334
/* apply any input changes pending */
 
4335
        foreach(ic, inputChangesPending)
 
4336
           updateFieldInBuffer(ic->tagno, ic->value, 0, false);
 
4337
        freeList(&inputChangesPending);
 
4338
    }
 
4339
 
 
4340
    if(do_ip & ismc)
 
4341
        allIPs();
 
4342
    return true;
 
4343
}                               /* browseCurrentBuffer */
 
4344
 
 
4345
static bool
 
4346
locateTagInBuffer(int tagno, int *ln_p, char **p_p, char **s_p, char **t_p)
 
4347
{
 
4348
    int ln, n;
 
4349
    char *p, *s, *t, c;
 
4350
    char search[20];
 
4351
 
 
4352
    if(parsePage) {
 
4353
        foreachback(ic, inputChangesPending) {
 
4354
            if(ic->tagno != tagno)
 
4355
                continue;
 
4356
            *s_p = ic->value;
 
4357
            *t_p = ic->value + strlen(ic->value);
 
4358
/* we don't need to set the others in this special case */
 
4359
            return true;
 
4360
        }
 
4361
        return false;
 
4362
    }
 
4363
    /* still rendering the page */
 
4364
    sprintf(search, "%c%d<", InternalCodeChar, tagno);
 
4365
    n = strlen(search);
 
4366
    for(ln = 1; ln <= cw->dol; ++ln) {
 
4367
        p = (char *)fetchLine(ln, -1);
 
4368
        for(s = p; (c = *s) != '\n'; ++s) {
 
4369
            if(c != (char)InternalCodeChar)
 
4370
                continue;
 
4371
            if(!memcmp(s, search, n))
 
4372
                break;
 
4373
        }
 
4374
        if(c == '\n')
 
4375
            continue;           /* not here, try next line */
 
4376
        s = strchr(s, '<') + 1;
 
4377
        t = strstr(s, "\2000>");
 
4378
        if(!t)
 
4379
            errorPrint("@no closing > at line %d", ln);
 
4380
        *ln_p = ln;
 
4381
        *p_p = p;
 
4382
        *s_p = s;
 
4383
        *t_p = t;
 
4384
        return true;
 
4385
    }
 
4386
 
 
4387
    return false;
 
4388
}                               /* locateTagInBuffer */
 
4389
 
 
4390
/* Update an input field in the current buffer.
 
4391
 * The input field may not be here, if you've deleted some lines. */
 
4392
void
 
4393
updateFieldInBuffer(int tagno, const char *newtext, int notify, bool required)
 
4394
{
 
4395
    int ln, idx, n, plen;
 
4396
    char *p, *s, *t, *new;
 
4397
    char newidx[LNWIDTH + 1];
 
4398
 
 
4399
    if(parsePage) {             /* we don't even have the buffer yet */
 
4400
        ic = allocMem(sizeof (struct inputChange) + strlen(newtext));
 
4401
        ic->tagno = tagno;
 
4402
        strcpy(ic->value, newtext);
 
4403
        addToListBack(&inputChangesPending, ic);
 
4404
        return;
 
4405
    }
 
4406
    /* still rendering the page */
 
4407
    if(locateTagInBuffer(tagno, &ln, &p, &s, &t)) {
 
4408
        n = (plen = pstLength((pst) p)) + strlen(newtext) - (t - s);
 
4409
        new = allocMem(n);
 
4410
        memcpy(new, p, s - p);
 
4411
        strcpy(new + (s - p), newtext);
 
4412
        memcpy(new + strlen(new), t, plen - (t - p));
 
4413
        idx = textLinesCount++;
 
4414
        sprintf(newidx, LNFORMAT, idx);
 
4415
        memcpy(cw->map + ln * LNWIDTH, newidx, LNWIDTH);
 
4416
        textLines[idx] = (pst) new;
 
4417
/* In case a javascript routine updates a field that you weren't expecting */
 
4418
        if(notify == 1)
 
4419
            displayLine(ln);
 
4420
        if(notify == 2)
 
4421
            printf("line %d has been updated\n", ln);
 
4422
        cw->firstOpMode = undoable = true;
 
4423
        return;
 
4424
    }
 
4425
    /* tag found */
 
4426
    if(required)
 
4427
        errorPrint("fieldInBuffer could not find tag %d newtext %s", tagno,
 
4428
           newtext);
 
4429
}                               /* updateFieldInBuffer */
 
4430
 
 
4431
/* This is the inverse of the above function, fetch instead of update. */
 
4432
char *
 
4433
getFieldFromBuffer(int tagno)
 
4434
{
 
4435
    int ln;
 
4436
    char *p, *s, *t;
 
4437
    if(locateTagInBuffer(tagno, &ln, &p, &s, &t)) {
 
4438
        return pullString1(s, t);
 
4439
    }
 
4440
    /* tag found */
 
4441
    /* line has been deleted, revert to the reset value */
 
4442
    return 0;
 
4443
}                               /* getFieldFromBuffer */
 
4444
 
 
4445
int
 
4446
fieldIsChecked(int tagno)
 
4447
{
 
4448
    int ln;
 
4449
    char *p, *s, *t, *new;
 
4450
    if(locateTagInBuffer(tagno, &ln, &p, &s, &t))
 
4451
        return (*s == '+');
 
4452
    return -1;
 
4453
}                               /* fieldIsChecked */