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.
10
/* If this include file is missing, you need the pcre package,
11
* and the pcre-devel package. */
14
/* Static variables for this file. */
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";
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 */
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.
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}. */
84
removeHiddenNumbers(pst p)
90
while((c = *s) != '\n') {
91
if(c != (uchar)InternalCodeChar) {
103
} while(isdigitByte(d));
108
if(strchr("<>{}", d)) {
112
/* This is not a code sequence I recognize. */
113
/* This should never happen; just move along. */
116
*t = c; /* terminating newline */
117
} /* removeHiddenNumbers */
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. */
132
fetchLineContext(int n, int show, int cx)
134
struct ebWindow *lw = sessionList[cx].lw;
138
pst p; /* the resulting copy of the string */
141
errorPrint("@invalid session %d in fetchLineContext()", cx);
144
if(n <= 0 || n > dol)
145
errorPrint("@invalid line number %d in fetchLineContext()", n);
147
t = map + LNWIDTH * n;
150
errorPrint("@line %d->%d became null", n, idx);
152
return textLines[idx];
153
p = clonePstring(textLines[idx]);
154
if(show && lw->browseMode)
155
removeHiddenNumbers(p);
157
} /* fetchLineContext */
160
fetchLine(int n, int show)
162
return fetchLineContext(n, show, context);
166
apparentSize(int cx, bool browsing)
171
if(cx <= 0 || cx >= MAXSESSION || (w = sessionList[cx].lw) == 0) {
172
setError("session %d is not active", cx);
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);
182
pst line = fetchLineContext(ln, -1, cx);
183
size += pstLength(line);
185
} /* loop over lines */
186
if(sessionList[cx].lw->nlMode)
192
currentBufferSize(void)
194
return apparentSize(context, cw->browseMode);
195
} /* currentBufferSize */
197
/* get the directory suffix for a file.
198
* This only makes sense in directory mode. */
200
dirSuffixContext(int n, int cx)
202
static char suffix[4];
203
struct ebWindow *lw = sessionList[cx].lw;
206
char *s = lw->map + LNWIDTH * n + 8;
216
} /* dirSuffixContext */
221
return dirSuffixContext(n, context);
224
/* Display a line to the screen, but no more than 500 chars. */
228
pst line = fetchLine(n, 1);
235
if(endMarks == 2 || endMarks && cmd == 'l')
238
while((c = *s++) != '\n') {
240
if(c == 0 || c == '\r' || c == '\x1b')
243
/* show tabs and backspaces, ed style */
252
printf("~%02X", c), cnt += 3;
254
printf("%c", c), ++cnt;
257
} /* loop over line */
261
printf("%s", dirSuffix(n));
262
if(endMarks == 2 || endMarks && cmd == 'l')
273
displayLine(cw->dot);
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. */
297
static uchar line[MAXTTYLINE];
304
if(!fgets((char *)line, sizeof (line), stdin)) {
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. */
339
} /* loop over input chars */
345
carrySubstitutionStrings(const struct ebWindow *w, struct ebWindow *nw)
347
if(!searchStringsAll)
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 */
355
/* Create a new window, with default variables. */
356
static struct ebWindow *
359
struct ebWindow *nw; /* the new window */
360
nw = allocZeroMem(sizeof (struct ebWindow));
362
carrySubstitutionStrings(cw, nw);
367
freeWindowLines(char *map)
372
for(t = map + LNWIDTH; *t; t += LNWIDTH) {
382
debugPrint(6, "freeWindowLines = %d", cnt);
383
} /* freeWindowLines */
385
/* Free any lines not used by the snapshot of the current session. */
387
freeUndoLines(const char *cmap)
389
char *map = undoWindow.map;
396
debugPrint(6, "freeUndoLines = null");
401
debugPrint(6, "freeUndoLines = win");
402
freeWindowLines(map);
407
/* This is pretty efficient most of the time,
408
* real inefficient sometimes. */
411
for(; *s && *t; s += LNWIDTH, t += LNWIDTH)
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) {
424
s -= LNWIDTH, t -= LNWIDTH;
427
/* Ok, who's left? */
428
for(s = map + LNWIDTH; *s; s += LNWIDTH) {
430
continue; /* in use */
431
for(t = cmap + LNWIDTH; *t; t += LNWIDTH)
435
continue; /* in use */
447
debugPrint(6, "freeUndoLines = %d", cnt);
448
} /* freeUndoLines */
451
freeWindow(struct ebWindow *w)
453
/* The next few are designed to do nothing if not in browseMode */
454
freeJavaContext(w->jsc);
455
freeWindowLines(w->r_map);
461
freeWindowLines(w->map);
464
nzFree(w->baseDirName);
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
*********************************************************************/
478
setError("session 0 is invalid");
481
if(cx >= MAXSESSION) {
482
setError("session %d is out of bounds, limit %d", cx, MAXSESSION - 1);
486
return true; /* ok */
487
setError("you are already in session %d", cx);
491
/* is a context active? */
495
if(cx <= 0 || cx >= MAXSESSION)
496
errorPrint("@session %d out of range in cxActive", cx);
497
if(sessionList[cx].lw)
499
setError("session %d is not active", cx);
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;
513
cxQuit(int cx, int action)
515
struct ebWindow *w = sessionList[cx].lw;
517
errorPrint("@quitting a nonactive session %d", cx);
519
/* We might be trashing data, make sure that's ok. */
521
!w->dirMode && lastq != cx && w->fileName && !isURL(w->fileName)) {
523
setError("expecting `w'");
525
setError("expecting `w' on session %d", cx);
528
/* warning message */
530
return true; /* just a check */
533
/* Don't need to retain the undo lines. */
534
freeWindowLines(undoWindow.map);
536
nzFree(preWindow.map);
542
struct ebWindow *p = w->prev;
546
sessionList[cx].fw = sessionList[cx].lw = 0;
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. */
557
cxSwitch(int cx, bool interactive)
559
bool created = false;
560
struct ebWindow *nw = sessionList[cx].lw; /* the new window */
563
nw = sessionList[cx].lw;
566
carrySubstitutionStrings(cw, nw);
569
freeUndoLines(cw->map);
570
cw->firstOpMode = false;
573
cs = sessionList + cx;
575
if(interactive && debugLevel) {
577
printf("new session\n");
578
else if(cw->fileName)
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;
589
/* Make sure we have room to store another n lines. */
597
for(j = 0; j < textLinesCount; ++j)
598
nzFree(textLines[j]);
601
textLinesCount = textLinesMax = 0;
607
int need = textLinesCount + n;
610
("Your limit of 1 million lines has been reached.\nSave your files, then exit and restart this program.");
613
if(need > textLinesMax) {
614
int newmax = textLinesMax * 3 / 2;
616
newmax = need + 8192;
620
debugPrint(4, "textLines realloc %d", newmax);
621
textLines = reallocMem(textLines, newmax * sizeof (pst));
623
textLines = allocMem(newmax * sizeof (pst));
625
textLinesMax = newmax;
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. */
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 */
641
gotoLocation(char *url, int delay, bool rf)
643
if(newlocation && delay >= newloc_d) {
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. */
659
addToMap(int start, int end, int destl)
663
int nlines = end - start;
665
errorPrint("@empty new piece in addToMap()");
666
for(i = 0; i < 26; ++i) {
667
int ln = cw->labels[i];
670
cw->labels[i] += nlines;
672
cw->dot = destl + nlines;
674
newmap = allocMem((cw->dol + 1) * LNWIDTH + 1);
676
strcpy(newmap, cw->map);
678
strcpy(newmap, LNSPACE);
679
i = j = (destl + 1) * LNWIDTH; /* insert new piece here */
681
sprintf(newmap + i, LNFORMAT, start);
686
strcat(newmap, cw->map + j);
689
cw->firstOpMode = undoable = true;
691
cw->changeMode = true;
694
/* Add a block of text into the buffer; uses addToMap(). */
696
addTextToBuffer(const pst inbuf, int length, int destl)
698
int i, j, linecount = 0;
700
for(i = 0; i < length; ++i)
703
if(!linesComing(linecount + 1))
707
if(inbuf[length - 1] != '\n') {
708
/* doesn't end in newline */
709
++linecount; /* last line wasn't counted */
710
if(destl == cw->dol) {
712
if(cmd != 'b' && !cw->binMode && !ismc)
713
printf("no trailing newline\n");
715
} /* missing newline */
716
start = end = textLinesCount;
718
while(i < length) { /* another line */
721
if(inbuf[i++] == '\n')
723
if(inbuf[i - 1] == '\n') {
725
textLines[end] = allocMem(i - j);
727
/* last line with no nl */
728
textLines[end] = allocMem(i - j + 1);
729
textLines[end][i - j] = '\n';
731
memcpy(textLines[end], inbuf + j, i - j);
733
} /* loop breaking inbuf into lines */
734
textLinesCount = end;
735
addToMap(start, end, destl);
737
} /* addTextToBuffer */
739
/* Pass input lines straight into the buffer, until the user enters . */
742
inputLinesIntoBuffer(void)
744
int start = textLinesCount;
751
while(line[0] != '.' || line[1] != '\n') {
754
textLines[end++] = clonePstring(line);
757
if(end == start) { /* no lines entered */
759
if(!cw->dot && cw->dol)
763
if(endRange == cw->dol)
765
textLinesCount = end;
766
addToMap(start, end, endRange);
768
} /* inputLinesIntoBuffer */
770
/* Delete a block of text. */
773
delText(int start, int end)
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];
793
if(cw->dot > cw->dol)
795
/* by convention an empty buffer has no map */
800
cw->firstOpMode = undoable = true;
802
cw->changeMode = true;
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. */
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);
817
sprintf(path, "%s/%s", cw->baseDirName, f);
827
("directories are readonly, type dw to enable directory writes");
830
if(dirWrite == 1 && !recycleBin) {
832
("could not create .recycle under your home directory, to hold the deleted files");
836
cnt = endRange - startRange + 1;
838
char *file, *t, *path, *ftype;
839
file = (char *)fetchLine(ln, 0);
840
t = strchr(file, '\n');
842
errorPrint("@no newline on directory entry %s", file);
844
path = makeAbsPath(file);
849
ftype = dirSuffix(ln);
850
if(dirWrite == 2 || *ftype == '@') {
852
setError("could not remove file %s", file);
858
sprintf(bin, "%s/%s", recycleBin, file);
859
if(rename(path, bin)) {
861
("Could not move %s to the recycle bin, set dx mode to actually remove the file",
873
/* Move or copy a block of text. */
874
/* Uses range variables, hence no parameters. */
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;
887
int lowcut, highcut, diff, i;
889
if(dl > sr && dl < er) {
890
setError("destination lies inside the block to be moved or copied");
893
if(cmd == 'm' && (dl == er || dl == sr)) {
895
setError("no change");
900
if(!linesComing(n_lines))
902
for(i = sr; i < er; ++i)
903
textLines[textLinesCount++] = fetchLine(i, 0);
904
addToMap(textLinesCount - n_lines, textLinesCount, destLine);
908
if(destLine == cw->dol || endRange == cw->dol)
910
/* All we really need do is rearrange the map. */
911
newmap = allocMem((cw->dol + 1) * LNWIDTH + 1);
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);
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);
923
/* now for the labels */
933
for(i = 0; i < 26; ++i) {
934
int ln = cw->labels[i];
939
if(ln >= startRange && ln <= endRange) {
940
ln += (dl < sr ? -diff : diff);
942
ln += (dl < sr ? n_lines : -n_lines);
945
} /* loop over labels */
948
cw->dot += (dl < sr ? -diff : diff);
949
cw->firstOpMode = undoable = true;
951
cw->changeMode = true;
955
/* Join lines from startRange to endRange. */
961
if(startRange == endRange) {
962
setError("cannot join one line");
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);
982
textLines[textLinesCount] = newline;
983
sprintf(cw->map + startRange * LNWIDTH, LNFORMAT, textLinesCount);
985
delText(startRange + 1, endRange);
986
cw->dot = startRange;
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. */
993
readFile(const char *filename, const char *post)
995
char *rbuf; /* read buffer */
996
int readSize; /* should agree with fileSize */
997
int fh; /* file handle */
999
bool rc; /* return code */
1002
if(memEqualCI(filename, "file://", 7))
1005
setError("missing file name");
1009
if(isURL(filename)) {
1010
const char *domain = getHostURL(filename);
1012
return false; /* some kind of error */
1014
setError("empty domain in url");
1020
rc = httpConnect(0, filename);
1023
/* The error could have occured after redirection */
1024
nzFree(changeFileName);
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. */
1032
fileSize = readSize = serverDataLen;
1034
if(fileSize == 0) { /* empty file */
1040
} else { /* url or file */
1042
/* reading a file from disk */
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))
1058
printf("directory mode\n");
1060
if(start == end) { /* empty directory */
1066
addToMap(start, end, endRange);
1068
/* change 0 to nl and count bytes */
1070
for(j = start; j < end; ++j) {
1072
pst t = textLines[j];
1073
char *abspath = makeAbsPath((char *)t);
1080
len = t - textLines[j];
1081
fileSize += len + 1;
1083
continue; /* should never happen */
1084
ftype = fileTypeByName(abspath, true);
1087
s = cw->map + (endRange + 1 + j - start) * LNWIDTH + 8;
1088
if(isupperByte(ftype)) { /* symbolic link */
1090
*t = '@', *++t = '\n';
1095
ftype = tolower(ftype);
1110
*t = c, *++t = '\n';
1114
} /* loop fixing files in the directory scan */
1117
/* reading a directory */
1118
nopound = cloneString(filename);
1119
rbuf = strchr(nopound, '#');
1122
rc = fileIntoMemory(nopound, &rbuf, &fileSize);
1127
if(fileSize == 0) { /* empty file */
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. */
1141
for(i = 0; i < fileSize; ++i) {
1146
if(bincount * 4 - 10 < fileSize) {
1147
/* looks like text. In DOS, we should have compressed crlf.
1148
* Let's do that now. */
1150
for(i = j = 0; i < fileSize - 1; ++i) {
1152
if(c == '\r' && rbuf[i + 1] == '\n')
1158
} else if(binaryDetect & !cw->binMode) {
1159
printf("binary data\n");
1163
rc = addTextToBuffer((const pst)rbuf, fileSize, endRange);
1168
/* Write a range to a file. */
1170
writeFile(const char *name, int mode)
1173
if(memEqualCI(name, "file://", 7))
1176
setError("missing file name");
1180
setError("cannot write to a url");
1184
setError("writing an empty file");
1187
/* mode should be TRUNC or APPEND */
1188
mode |= O_WRONLY | O_CREAT;
1191
fh = open(name, mode, 0666);
1193
setError("cannot create %s", name);
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);
1202
if(i == cw->dol && cw->nlMode)
1204
if(write(fh, p, len) < len) {
1206
setError("cannot write to %s", name);
1212
/* must write this line with the suffix on the end */
1214
if(write(fh, p, len) < len)
1219
if(write(fh, suf, len) < len)
1225
} /* loop over lines */
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;
1237
struct ebWindow *lw;
1238
int i, start, end, fardol;
1244
lw = sessionList[cx].lw;
1248
if(!linesComing(fardol))
1250
if(cw->dol == endRange)
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);
1258
char *suf = dirSuffixContext(i, cx);
1259
char *q = allocMem(len + 3);
1263
strcpy(q + len, suf);
1267
textLines[end++] = p;
1269
} /* loop over lines in the "other" context */
1270
textLinesCount = end;
1271
addToMap(start, end, endRange);
1274
if(cw->dol == endRange)
1277
if(binaryDetect & !cw->binMode && lw->binMode) {
1279
printf("binary data\n");
1285
writeContext(int cx)
1287
struct ebWindow *lw;
1291
int fardol = endRange - startRange + 1;
1294
if(!linesComing(fardol))
1298
if(cxActive(cx) && !cxQuit(cx, 2))
1302
lw = sessionList[cx].lw;
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));
1310
sprintf(newmap + j * LNWIDTH, LNFORMAT, textLinesCount);
1313
char *suf = dirSuffix(i);
1314
q = allocMem(len + 3);
1318
strcpy((char *)q + len, suf);
1319
len = strlen((char *)q);
1322
textLines[textLinesCount++] = p;
1326
lw->binMode = cw->binMode;
1327
if(cw->nlMode && endRange == cw->dol) {
1331
} /* nonempty range */
1332
lw->dot = lw->dol = fardol;
1335
} /* writeContext */
1338
debrowseSuffix(char *s)
1343
if(*s == '.' && stringEqual(s, ".browse")) {
1349
} /* debrowseSuffix */
1352
shellEscape(const char *line)
1358
int linesize, pass, n;
1360
char subshell[ABSPATH];
1362
/* preferred shell */
1363
sh = getenv("SHELL");
1367
linesize = strlen(line);
1369
/* interactive shell */
1370
if(!isInteractive) {
1371
setError("session is not interactive");
1375
system(line); /* don't know how to spawn a shell here */
1377
sprintf(subshell, "exec %s -i", sh);
1384
/* Make substitutions within the command line. */
1385
for(pass = 1; pass <= 2; ++pass) {
1386
for(t = line; *t; ++t) {
1389
if(t > line && isalnumByte(t[-1]))
1392
if(key && isalnumByte(t[2]))
1400
linesize += strlen(cw->fileName);
1402
strcpy(s, cw->fileName);
1408
if(key == '.' || key == '-' || key == '+') {
1414
if(n > cw->dol || n == 0) {
1415
setError("line %c is out of range", key);
1421
p = fetchLine(n, -1);
1422
linesize += pstLength(p) - 1;
1424
p = fetchLine(n, 1);
1425
if(perl2c((char *)p)) {
1427
setError("cannot embed nulls in a shell command");
1430
strcpy(s, (char *)p);
1436
/* '. current line */
1437
if(islowerByte(key)) {
1438
n = cw->labels[key - 'a'];
1440
setError("label %c not set", key);
1445
/* 'x the line labeled x */
1451
} /* loop over chars */
1454
s = newline = allocMem(linesize + 1);
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. */
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-'.$+/?";
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. */
1483
regexpCheck(const char *line, bool isleft, bool ebmuck,
1484
char **rexp, const char **split)
1485
{ /* result parameters */
1486
static char re[MAXRE + 20];
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++;
1500
if(!strchr(valid_delim, delim)) {
1501
setError("invalid delimiter");
1509
if(c == delim || c == 0) {
1511
setError("no remembered search string");
1514
strcpy(re, cw->lhs);
1518
/* Interpret lead * or lone [ as literal */
1519
if(strchr("*?+", c) || c == '[' && !line[1]) {
1525
} else if(c == '%' && (line[1] == delim || line[1] == 0)) {
1527
setError("no remembered replacement string");
1530
strcpy(re, cw->rhs);
1537
if(e >= re + MAXRE - 3) {
1538
setError("regular expression too long");
1546
setError("line ends in backslash");
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 == '|')) {
1555
ondeck = false, was_ques = true;
1557
++paren, ondeck = false, was_ques = true;
1561
setError("Unexpected closing )");
1567
if(d == delim || ebmuck && !isleft && d == '&') {
1571
/* Nothing special; we retain the escape character. */
1573
if(isleft && d >= '0' && d <= '7' && (*line < '0' || *line > '7'))
1579
/* escaping backslash */
1580
/* Break out if we hit the delimiter. */
1584
/* Remember, I reverse the sense of ()| */
1586
if(ebmuck && (c == '(' || c == ')' || c == '|') || c == '^' && line != start && !cc) /* don't know why we have to do this */
1588
if(c == '$' && d && d != delim)
1592
if(c == '$' && !isleft && isdigitByte(d)) {
1593
if(d == '0' || isdigitByte(line[2])) {
1594
setError("replacement string can only use $1 through $9");
1598
/* dollar digit on the right */
1599
if(!isleft && c == '&' && ebmuck) {
1606
/* push the character */
1610
/* No more checks for the rhs */
1614
if(cc) { /* character class */
1622
/* Skip all these checks for javascript,
1623
* it probably has the expression right anyways. */
1627
/* Modifiers must have a preceding character.
1628
* Except ? which can reduce the greediness of the others. */
1629
if(c == '?' && !was_ques) {
1636
if(c == '?' || c == '*' || c == '+')
1638
if(c == '{' && isdigitByte(d)) {
1639
const char *t = line + 1;
1640
while(isdigitByte(*t))
1644
while(isdigitByte(*t))
1652
strncpy(e, line, mod);
1658
setError("%s modifier has no preceding character", e - mod - 1);
1667
} /* loop over chars in the pattern */
1674
setError("no closing ]");
1678
setError("no closing )");
1684
strcpy(cw->lhs, re);
1687
strcpy(cw->rhs, re);
1691
debugPrint(7, "%s regexp %s", (isleft ? "search" : "replace"), re);
1695
/* regexp variables */
1696
static int re_count;
1697
static const char *re_error;
1698
static int re_offset;
1700
static int re_vector[11 * 3];
1701
static pcre *re_cc; /* compiled */
1703
/* Get the start or end of a range.
1704
* Pass the line containing the address. */
1706
getRangePart(const char *line, int *lineno, const char **split)
1707
{ /* result parameters */
1708
int ln = cw->dot; /* this is where we start */
1711
if(isdigitByte(first)) {
1712
ln = strtol(line, (char **)&line, 10);
1713
} else if(first == '.') {
1715
/* ln is already set */
1716
} else if(first == '$') {
1719
} else if(first == '\'' && islowerByte(line[1])) {
1720
ln = cw->labels[line[1] - 'a'];
1722
setError("label %c not set", line[1]);
1726
} else if(first == '/' || first == '?') {
1728
char *re; /* regular expression */
1729
bool ci = caseInsensitive;
1730
char incr; /* forward or back */
1731
/* Don't look through an empty buffer. */
1733
setError("empty buffer");
1736
if(!regexpCheck(line, true, true, &re, &line))
1738
if(*line == first) {
1744
/* second delimiter */
1745
/* Earlier versions of pcre don't support this. */
1746
/* re_opt = PCRE_NO_AUTO_CAPTURE; */
1749
re_opt |= PCRE_CASELESS;
1750
re_cc = pcre_compile(re, re_opt, &re_error, &re_offset, 0);
1752
setError("error in regular expression, %s", re_error);
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);
1766
subject = (char *)fetchLine(ln, 1);
1768
pcre_exec(re_cc, 0, subject, pstLength((pst) subject) - 1, 0, 0,
1774
("unexpected error while evaluating the regular expression at line %d",
1776
return (globSub = false);
1782
setError("search string not found");
1785
} /* loop over lines */
1787
/* and ln is the line that matches */
1790
/* search pattern */
1791
/* Now add or subtract from this number */
1792
while((first = *line) == '+' || first == '-') {
1795
if(isdigitByte(*line))
1796
add = strtol(line, (char **)&line, 10);
1797
ln += (first == '+' ? add : -add);
1801
setError("line number too large");
1805
setError("negative line number");
1812
} /* getRangePart */
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. */
1818
doGlobal(const char *line)
1820
int gcnt = 0; /* global count */
1821
bool ci = caseInsensitive;
1825
char *re; /* regular expression */
1826
int i, origdot, yesdot, nodot;
1829
setError("no regular expression after %c", icmd);
1833
if(!regexpCheck(line, true, true, &re, &line))
1835
if(*line != delim) {
1836
setError("missing delimiter");
1844
/* clean up any previous stars */
1845
for(t = cw->map + LNWIDTH; *t; t += LNWIDTH)
1848
/* Find the lines that match the pattern. */
1851
re_opt |= PCRE_CASELESS;
1852
re_cc = pcre_compile(re, re_opt, &re_error, &re_offset, 0);
1854
setError("error in regular expression, %s", re_error);
1857
for(i = startRange; i <= endRange; ++i) {
1858
char *subject = (char *)fetchLine(i, 1);
1860
pcre_exec(re_cc, 0, subject, pstLength((pst) subject), 0, 0,
1866
("unexpected error while evaluating the regular expression at line %d",
1870
if(re_count < 0 && cmd == 'v' || re_count >= 0 && cmd == 'g') {
1872
cw->map[i * LNWIDTH + 6] = '*';
1874
} /* loop over line */
1879
'g' ? "no lines match the g pattern" :
1880
"all lines match the v pattern");
1884
/* apply the subcommand to every line with a star */
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;
1900
change = true, --gcnt;
1902
cw->dot = i; /* so we can run the command at this line */
1903
if(runCommand(line)) {
1905
/* try this line again, in case we deleted or moved it somewhere else */
1909
/* error in subcommand might turn global flag off */
1911
nodot = i, yesdot = 0;
1913
} /* serious error */
1914
} /* subcommand succeeds or fails */
1915
} /* loop over lines */
1916
} /* loop making changes */
1920
/* yesdot could be 0, even on success, if all lines are deleted via g/re/d */
1921
if(yesdot || !cw->dol) {
1923
if((cmd == 's' || cmd == 'i') && subPrint == 1)
1930
setError("none of the marked lines were successfully modified");
1932
if(!errorMsg[0] && intFlag)
1934
return (errorMsg[0] == 0);
1938
fieldNumProblem(const char *desc, char c, int n, int nt)
1941
setError("no %s present", desc);
1945
setError("multiple %s present, please use %c1 through %c%d", desc, c, c,
1950
setError("%d is out of range, please use %c1 through %c%d", n, c, c,
1953
setError("%d is out of range, please use %c1, or simply %c", n, c, c);
1954
} /* fieldNumProblem */
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. */
1962
char replaceLine[REPLACELINELEN];
1963
static char *replaceLineEnd;
1964
static int replaceLineLen;
1966
replaceText(const char *line, int len, const char *rhs,
1967
bool ebmuck, int nth, bool global, int ln)
1969
int offset = 0, lastoffset, instance = 0;
1971
char *r = replaceLine;
1972
char *r_end = replaceLine + REPLACELINELEN - 8;
1973
const char *s = line, *s_end, *t;
1977
/* find the next match */
1978
re_count = pcre_exec(re_cc, 0, line, len, offset, 0, re_vector, 33);
1981
("unexpected error while evaluating the regular expression at line %d",
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");
1994
if(!global &&instance != nth)
1997
/* copy up to the match point */
1998
s_end = line + re_vector[0];
2000
if(r + span >= r_end)
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)
2013
memcpy(r, line + re_vector[0], span);
2015
caseShift(r, rhs[0]);
2023
/* copy rhs, watching for $n */
2059
if(d >= '0' && d <= '7') {
2060
int octal = d - '0';
2062
if(d >= '0' && d <= '7') {
2064
octal = 8 * octal + d - '0';
2066
if(d >= '0' && d <= '7') {
2068
octal = 8 * octal + d - '0';
2079
if(c == '$' && isdigitByte(d)) {
2085
y = re_vector[2 * d];
2086
z = re_vector[2 * d + 1];
2090
if(r + span >= r_end)
2092
memcpy(r, line + y, span);
2102
} /* loop matching the regular expression */
2106
if(!global &&instance < nth)
2109
/* We got a match, copy the last span. */
2112
if(r + span >= r_end)
2117
replaceLineLen = r - replaceLine;
2121
setError("line exceeds %d bytes after substitution", REPLACELINELEN);
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. */
2136
substituteText(const char *line)
2139
bool bl_mode = false; /* running the bl command */
2140
bool g_mode = false; /* s/x/y/g */
2141
bool ci = caseInsensitive;
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];
2151
subPrint = 1; /* default is to print the last line substituted */
2153
if(stringEqual(line, "`bl"))
2154
bl_mode = true, breakLineSetup();
2157
/* watch for s2/x/y/ for the second input field */
2158
if(isdigitByte(*line))
2159
whichField = strtol(line, (char **)&line, 10);
2161
setError("no regular expression after %c", icmd);
2165
if(cw->dirMode && !dirWrite) {
2167
("directories are readonly, type dw to enable directory writes");
2171
if(!regexpCheck(line, true, true, &re, &line))
2175
setError("missing delimiter");
2178
if(!regexpCheck(line, false, true, &re, &line))
2182
if(*line) { /* third delimiter */
2201
if(isdigitByte(c)) {
2203
setError("multiple numbers after the third delimiter");
2206
nth = strtol(line, (char **)&line, 10);
2210
("unexpected substitution suffix after the third delimiter");
2212
} /* loop gathering suffix flags */
2215
("cannot use both a numeric suffix and the `g' suffix simultaneously");
2218
} /* closing delimiter */
2219
if(nth == 0 && !g_mode)
2224
re_opt |= PCRE_CASELESS;
2225
re_cc = pcre_compile(lhs, re_opt, &re_error, &re_offset, 0);
2227
setError("error in regular expression, %s", re_error);
2233
} /* bl_mode or not */
2238
for(ln = startRange; ln <= endRange && !intFlag; ++ln) {
2239
char *p = (char *)fetchLine(ln, -1);
2240
int len = pstLength((pst) p);
2244
if(!breakLine(p, len, &newlen)) {
2246
("sorry, cannot apply the bl command to lines longer than %d bytes",
2250
/* empty line is not allowed */
2252
replaceLine[newlen++] = '\n';
2253
/* perhaps no changes were made */
2254
if(newlen == len && !memcmp(p, replaceLine, len))
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;
2263
if(cw->browseMode) {
2265
findInputField(p, 1, whichField, &total, &tagno);
2267
fieldNumProblem("input fields", 'i', whichField, total);
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);
2275
s = strchr(s, '<') + 1;
2276
t = strstr(s, "\2000>");
2279
j = replaceText(s, t - s, rhs, true, nth, g_mode, ln);
2281
j = replaceText(p, len - 1, rhs, true, nth, g_mode, ln);
2289
/* Did we split this line into many lines? */
2290
linecount = slashcount = nullcount = 0;
2291
for(t = replaceLine; t < replaceLineEnd; ++t) {
2300
if(!linesComing(linecount + 1))
2304
/* move the file, then update the text */
2305
char src[ABSPATH], *dest;
2306
if(slashcount + nullcount + linecount) {
2308
("cannot embed slash, newline, or null in a directory name");
2311
p[len - 1] = 0; /* temporary */
2317
*replaceLineEnd = 0;
2318
dest = makeAbsPath(replaceLine);
2321
if(!stringEqual(src, dest)) {
2322
if(fileTypeByName(dest, true)) {
2323
setError("destination file already exists");
2326
if(rename(src, dest)) {
2327
setError("cannot rename file to %s", dest);
2330
} /* source and dest are different */
2333
if(cw->browseMode) {
2335
setError("cannot embed nulls in an input field");
2339
setError("cannot embed newlines in an input field");
2342
replaceLine[replaceLineLen] = 0;
2343
/* We're managing our own printing, so leave notify = 0 */
2344
if(!infReplace(tagno, replaceLine, 0))
2348
replaceLine[replaceLineLen] = '\n';
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);
2359
/* Becomes many lines, this is the tricky case. */
2360
save_nlMode = cw->nlMode;
2362
addTextToBuffer((pst) replaceLine, replaceLineLen + 1, ln - 1);
2363
cw->nlMode = save_nlMode;
2364
endRange += linecount;
2366
/* There's a quirk when adding newline to the end of a buffer
2367
* that had no newline at the end before. */
2369
ln == cw->dol && replaceLine[replaceLineLen - 1] == '\n') {
2374
} /* browse or not */
2379
cw->firstOpMode = undoable = true;
2381
cw->changeMode = true;
2382
} /* loop over lines in the range */
2394
setError(bl_mode ? "no changes" : "no match");
2398
cw->dot = lastSubst;
2399
if(subPrint == 1 && !globSub)
2407
} /* substituteText */
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
*********************************************************************/
2418
twoLetter(const char *line, const char **runThis)
2420
static char newline[MAXTTYLINE];
2427
if(stringEqual(line, "qt"))
2431
if(stringEqual(line, "ws")) return stripChild();
2432
if(stringEqual(line, "us")) return unstripChild();
2435
if(line[0] == 'd' && line[1] == 'b' && isdigitByte(line[2]) && !line[3]) {
2436
debugLevel = line[2] - '0';
2440
if(line[0] == 'u' && line[1] == 'a' && isdigitByte(line[2]) && !line[3]) {
2441
char *t = userAgents[line[2] - '0'];
2444
setError("agent number %c is not defined", line[2]);
2448
if(helpMessagesOn || debugLevel >= 1)
2453
/* ^^^^ is the same as ^4 */
2454
if(line[0] == '^' && line[1] == '^') {
2455
const char *t = line + 2;
2459
sprintf(newline, "^%d", t - line);
2464
if(line[0] == 'm' && (i = stringIsNum(line + 1)) >= 0) {
2465
sprintf(newline, "^M%d", i);
2469
if(line[0] == 'c' && line[1] == 'd') {
2471
if(!c || isspaceByte(c)) {
2472
const char *t = line + 2;
2475
cmd = 'e'; /* so error messages are printed */
2477
char cwdbuf[ABSPATH];
2479
if(!getcwd(cwdbuf, sizeof (cwdbuf))) {
2480
setError("could not %s directory",
2481
(c ? "print new" : "establish current"));
2491
setError("invalid directory");
2496
if(line[0] == 'p' && line[1] == 'b') {
2498
if(!c || c == '.') {
2499
const struct MIMETYPE *mt;
2501
const char *suffix = 0;
2502
bool trailPercent = false;
2504
setError("cannot play an empty buffer");
2511
suffix = strrchr(cw->fileName, '.');
2514
("file has no suffix, use mt.xxx to specify your own suffix");
2519
if(strlen(suffix) > 5) {
2520
setError("suffix is limited to 5 characters");
2523
mt = findMimeBySuffix(suffix);
2526
("suffix .%s is not a recognized mime type, please check your config file.",
2530
if(mt->program[strlen(mt->program) - 1] == '%')
2531
trailPercent = true;
2532
cmd = pluginCommand(mt, 0, suffix);
2533
rc = bufferToProgram(cmd, suffix, trailPercent);
2539
if(stringEqual(line, "rf")) {
2542
setError("no file name or url to refresh");
2548
sprintf(newline, "%c %s", cmd, cw->fileName);
2549
debrowseSuffix(newline);
2553
if(stringEqual(line, "ub") || stringEqual(line, "et")) {
2554
bool ub = (line[0] == 'u');
2556
if(!cw->browseMode) {
2557
setError("not in browse mode");
2560
freeUndoLines(cw->map);
2562
nzFree(preWindow.map);
2564
cw->firstOpMode = undoable = false;
2565
cw->browseMode = false;
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;
2575
for(i = 1; i <= cw->dol; ++i) {
2576
int ln = atoi(cw->map + i * LNWIDTH);
2577
removeHiddenNumbers(textLines[ln]);
2579
freeWindowLines(cw->r_map);
2584
freeJavaContext(cw->jsc);
2595
fileSize = apparentSize(context, false);
2599
if(stringEqual(line, "ip")) {
2604
if(!cw->iplist || cw->iplist[0] == -1) {
2608
for(i = 0; (ip = cw->iplist[i]) != -1; ++i) {
2609
puts(tcp_ip_dots(ip));
2615
if(stringEqual(line, "f/") || stringEqual(line, "w/")) {
2619
setError("no file name or url to refresh");
2622
t = strrchr(cw->fileName, '/');
2624
setError("file name does not contain /");
2629
setError("file name ends in /");
2632
sprintf(newline, "%c `%s", cmd, t);
2636
if(line[0] == 'f' && line[2] == 0 &&
2637
(line[1] == 'd' || line[1] == 'k' || line[1] == 't')) {
2640
if(!cw->browseMode) {
2641
setError("not in browse mode");
2645
s = cw->ft, t = "title";
2647
s = cw->fd, t = "description";
2649
s = cw->fk, t = "keywords";
2653
printf("no %s\n", t);
2657
if(line[0] == 's' && line[1] == 'm') {
2658
const char *t = line + 2;
2667
account = strtol(t, (char **)&t, 10);
2670
return sendMailCurrent(account, dosig);
2672
setError("invalid characters after the sm command");
2677
if(stringEqual(line, "sg")) {
2678
searchStringsAll = true;
2680
puts("substitutions global");
2684
if(stringEqual(line, "sl")) {
2685
searchStringsAll = false;
2687
puts("substitutions local");
2691
if(stringEqual(line, "ci")) {
2692
caseInsensitive = true;
2694
puts("case insensitive");
2698
if(stringEqual(line, "cs")) {
2699
caseInsensitive = false;
2701
puts("case sensitive");
2705
if(stringEqual(line, "dr")) {
2708
puts("directories readonly");
2712
if(stringEqual(line, "dw")) {
2715
puts("directories writable");
2719
if(stringEqual(line, "dx")) {
2722
puts("directories writable with delete");
2726
if(stringEqual(line, "hr")) {
2727
allowRedirection ^= 1;
2728
if(helpMessagesOn || debugLevel >= 1)
2729
puts(allowRedirection ? "http redirection" : "no http redirection");
2733
if(stringEqual(line, "sr")) {
2735
if(helpMessagesOn || debugLevel >= 1)
2736
puts(sendReferrer ? "send referrer" : "do not send referrer");
2740
if(stringEqual(line, "js")) {
2742
if(helpMessagesOn || debugLevel >= 1)
2743
puts(allowJS ? "javascript enabled" : "javascript disabled");
2747
if(stringEqual(line, "bd")) {
2749
if(helpMessagesOn || debugLevel >= 1)
2750
puts(binaryDetect ? "watching for binary files" :
2751
"treating binary like text");
2755
if(line[0] == 'f' && line[1] == 'm' &&
2756
line[2] && strchr("pad", line[2]) && !line[3]) {
2762
if(helpMessagesOn || debugLevel >= 1) {
2764
puts("passive mode");
2766
puts("active mode");
2768
puts("passive/active mode");
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);
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");
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");
2798
if(stringEqual(line, "eo")) {
2801
puts("end markers off");
2805
if(stringEqual(line, "el")) {
2808
puts("end markers on listed lines");
2812
if(stringEqual(line, "ep")) {
2815
puts("end markers on");
2820
return 2; /* no change */
2823
/* Return the number of unbalanced punctuation marks.
2824
* This is used by the next routine. */
2826
unbalanced(char c, char d, int ln, int *back_p, int *for_p)
2827
{ /* result parameters */
2829
char *p = (char *)fetchLine(ln, 1);
2831
int backward, forward;
2837
for(t = p; *t != '\n'; ++t) {
2840
if(*t == d && open) {
2849
backward = forward = 0;
2850
for(t = p; *t != '\n'; ++t) {
2862
/* Find the line that balances the unbalanced punctuation. */
2864
balanceLine(const char *line)
2866
char c, d; /* open and close */
2868
static char openlist[] = "{([<`";
2869
static char closelist[] = "})]>'";
2870
static char alllist[] = "{}()[]<>`'";
2873
int i, direction, forward, backward;
2876
if(!strchr(alllist, c) || line[1]) {
2877
setError("you must specify exactly one of %s after the B command",
2881
if(t = strchr(openlist, c)) {
2882
d = closelist[t - openlist];
2886
t = strchr(closelist, d);
2887
c = openlist[t - closelist];
2890
unbalanced(c, d, endRange, &backward, &forward);
2892
if((level = forward) == 0) {
2893
setError("line does not contain an open %c", c);
2897
if((level = backward) == 0) {
2898
setError("line does not contain an open %c", d);
2904
/* Look for anything unbalanced, probably a brace. */
2905
for(i = 0; i <= 2; ++i) {
2908
unbalanced(c, d, endRange, &backward, &forward);
2909
if(backward && forward) {
2911
("both %c and %c are unbalanced on this line, try B%c or B%c",
2915
level = backward + forward;
2925
("line does not contain an unbalanced brace, parenthesis, or bracket");
2928
} /* explicit character passed in, or look for one */
2930
selected = (direction > 0 ? c : d);
2932
/* search for the balancing line */
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) {
2942
level += (forward - backward) * direction;
2943
} /* loop over lines */
2945
setError("cannot find the line that balances %c", selected);
2949
/* Unfold the buffer into one long, allocated string. */
2951
unfoldBuffer(int cx, bool cr, char **data, int *len)
2956
int size = apparentSize(cx, false);
2959
w = sessionList[cx].lw;
2961
setError("session %d is currently in browse mode", cx);
2965
setError("session %d is currently in directory mode", cx);
2970
/* a few bytes more, just for safety */
2971
buf = allocMem(size + 4);
2973
for(ln = 1; ln <= w->dol; ++ln) {
2974
pst line = fetchLineContext(ln, -1, cx);
2975
l = pstLength(line) - 1;
2977
memcpy(buf, line, l);
2982
if(l && buf[-2] == '\r')
2986
} /* loop over lines */
2987
if(w->dol && w->nlMode) {
2994
} /* unfoldBuffer */
3000
char *a = initString(&a_l);
3002
char c, *p, *s, *t, *q, *line, *h;
3003
int j, k = 0, tagno;
3006
if(cw->browseMode && endRange) {
3008
line = (char *)fetchLine(endRange, -1);
3009
for(p = line; (c = *p) != '\n'; ++p) {
3010
if(c != (char)InternalCodeChar)
3012
if(!isdigitByte(p[1]))
3014
j = strtol(p + 1, &s, 10);
3019
findField(line, 0, k, 0, &tagno, &h, &ev);
3021
continue; /* should never happen */
3023
click = tagHandler(tagno, "onclick");
3024
dclick = tagHandler(tagno, "ondblclick");
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] == '}')
3031
/* Ok, everything between p and s exclusive is the description */
3034
if(stringEqual(h, "#")) {
3039
if(memEqualCI(h, "mailto:", 7)) {
3040
stringAndBytes(&a, &a_l, p + 1, s - p - 1);
3041
stringAndChar(&a, &a_l, ':');
3043
t = s + strcspn(s, "?");
3044
stringAndBytes(&a, &a_l, s, t - s);
3045
stringAndChar(&a, &a_l, '\n');
3050
stringAndString(&a, &a_l, "<a href=");
3052
if(memEqualCI(h, "javascript:", 11)) {
3053
stringAndString(&a, &a_l, "javascript:>\n");
3054
} else if(!*h && (click | dclick)) {
3056
sprintf(buf, "%s>\n", click ? "onclick" : "ondblclick");
3057
stringAndString(&a, &a_l, buf);
3060
stringAndString(&a, &a_l, h);
3061
stringAndString(&a, &a_l, ">\n");
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 */
3071
if(!a_l) { /* nothing found yet */
3073
setError("no file name");
3076
h = cloneString(cw->fileName);
3078
stringAndString(&a, &a_l, "<a href=");
3079
stringAndString(&a, &a_l, h);
3080
stringAndString(&a, &a_l, ">\n");
3081
s = (char *)getDataURL(h);
3084
t = s + strcspn(s, "\1?#");
3085
if(t > s && t[-1] == '/')
3088
q = strrchr(s, '/');
3091
stringAndBytes(&a, &a_l, s, t - s);
3092
stringAndString(&a, &a_l, "\n</a>\n");
3095
/* using the filename */
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. */
3106
runCommand(const char *line)
3108
int i, j, n, writeMode;
3110
void *ev; /* event variables */
3112
bool postSpace = false, didRange = false;
3114
int cx = 0; /* numeric suffix as in s/x/y/3 or w2 */
3117
static char newline[MAXTTYLINE];
3118
static char *allocatedLine = 0;
3121
nzFree(allocatedLine);
3124
nzFree(currentReferrer);
3125
currentReferrer = cloneString(cw->fileName);
3126
js_redirects = false;
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 */
3138
return shellEscape(line + 1);
3140
/* Watch for successive q commands. */
3141
lastq = lastqq, lastqq = 0;
3144
/* special 2 letter commands - most of these change operational modes */
3145
j = twoLetter(line, &line);
3149
/* not global command */
3150
startRange = endRange = cw->dot; /* default range */
3151
/* Just hit return to read the next line. */
3155
++startRange, ++endRange;
3156
if(endRange > cw->dol) {
3157
setError("end of buffer");
3172
startRange = cw->dot;
3175
if(first == 'j' || first == 'J') {
3177
endRange = startRange + 1;
3178
if(endRange > cw->dol) {
3179
setError("no more lines to join");
3185
startRange = endRange = cw->dol;
3187
if(first == 'w' || first == 'v' || first == 'g' &&
3188
line[1] && strchr(valid_delim, line[1])) {
3197
if(!getRangePart(line, &startRange, &line))
3198
return (globSub = false);
3199
endRange = startRange;
3200
if(line[0] == ',') {
3202
endRange = cw->dol; /* new default */
3204
if(first && strchr(valid_laddr, first)) {
3205
if(!getRangePart(line, &endRange, &line))
3206
return (globSub = false);
3210
if(endRange < startRange) {
3211
setError("bad range");
3215
/* change uc into a substitute command, converting the whole line */
3218
if((first == 'u' || first == 'l' || first == 'm') && line[1] == 'c' &&
3220
sprintf(newline, "s/.*/%cc/", first);
3223
/* Breakline is actually a substitution of lines. */
3224
if(stringEqual(line, "bl")) {
3226
setError("cannot break lines in directory mode");
3229
if(cw->browseMode) {
3230
setError("cannot break lines in browse mode");
3236
/* get the command */
3244
if(!strchr(valid_cmd, cmd)) {
3245
setError("unknown command %c", cmd);
3246
return (globSub = false);
3250
writeMode = O_TRUNC;
3251
if(cmd == 'w' && first == '+')
3252
writeMode = O_APPEND, first = *++line;
3254
if(cw->dirMode && !strchr(dir_cmd, cmd)) {
3255
setError("%c not available in directory mode", icmd);
3256
return (globSub = false);
3258
if(cw->browseMode && !strchr(browse_cmd, cmd)) {
3259
setError("%c not available in browse mode", icmd);
3260
return (globSub = false);
3262
if(startRange == 0 && !strchr(zero_cmd, cmd)) {
3263
setError("zero line number");
3264
return (globSub = false);
3266
while(isspaceByte(first))
3267
postSpace = true, first = *++line;
3268
if(strchr(spaceplus_cmd, cmd) && !postSpace && first) {
3270
while(isdigitByte(*s))
3273
setError("no space after command");
3274
return (globSub = false);
3277
if(globSub && !strchr(global_cmd, cmd)) {
3278
setError("the %c command cannot be applied globally", icmd);
3279
return (globSub = false);
3282
/* move/copy destination, the third address */
3283
if(cmd == 't' || cmd == 'm') {
3287
if(!strchr(valid_laddr, first)) {
3288
setError("invalid move/copy destination");
3289
return (globSub = false);
3291
if(!getRangePart(line, &destLine, &line))
3292
return (globSub = false);
3294
} /* was there something after m or t */
3298
/* Any command other than a lone b resets history */
3299
if(cmd != 'b' || first) {
3300
fetchHistory(0, 0); /* reset history */
3303
/* env variable and wild card expansion */
3304
if(strchr("brewf", cmd) && first && !isURL(line)) {
3305
if(!envFile(line, &line))
3311
if(isdigitByte(first)) {
3312
last_z = strtol(line, (char **)&line, 10);
3317
startRange = endRange + 1;
3318
endRange = startRange;
3319
if(startRange > cw->dol) {
3320
setError("line number too large");
3324
endRange += last_z - 1;
3325
if(endRange > cw->dol)
3330
/* the a+ feature, when you thought you were in append mode */
3332
if(stringEqual(line, "+"))
3339
if(first && strchr(nofollow_cmd, cmd)) {
3340
setError("unexpected text after the %c command", icmd);
3341
return (globSub = false);
3350
if(helpMessagesOn ^= 1)
3351
debugPrint(1, "help messages on");
3355
if(strchr("lpn", cmd)) {
3356
for(i = startRange; i <= endRange; ++i) {
3366
printf("%d\n", endRange);
3371
return balanceLine(line);
3375
struct ebWindow *uw = &undoWindow;
3377
if(!cw->firstOpMode) {
3378
setError("nothing to undo");
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;
3387
swapmap = uw->map, uw->map = cw->map, cw->map = swapmap;
3392
if(!islowerByte(first) || line[1]) {
3393
setError("please enter k[a-z]");
3396
if(startRange < endRange) {
3397
setError("cannot label an entire range");
3400
cw->labels[first - 'a'] = endRange;
3405
/* Find suffix, as in 27,59w2 */
3407
cx = stringIsNum(line);
3409
setError("%s 0 is invalid", cmd == '^' ? "backing up" : "session");
3425
setError("unexpected text after the q command");
3433
/* look around for another active session */
3435
if(++cx == MAXSESSION)
3439
if(!sessionList[cx].lw)
3443
} /* loop over sessions */
3452
s = sessionList[cx].lw->fileName;
3453
printf("%s", s ? s : "no file");
3454
if(sessionList[cx].lw->binMode)
3455
printf(" [binary]");
3458
} /* another session */
3461
setError("cannot change the name of a directory");
3464
nzFree(cw->fileName);
3465
cw->fileName = cloneString(line);
3468
printf("%s", s ? s : "no file");
3470
printf(" [binary]");
3476
if(cx) { /* write to another buffer */
3477
if(writeMode == O_APPEND) {
3478
setError("cannot append to another buffer");
3481
return writeContext(cx);
3484
line = cw->fileName;
3486
setError("no file specified");
3489
if(cw->dirMode && stringEqual(line, cw->fileName)) {
3491
("cannot write to the directory; files are modified as you go");
3494
return writeFile(line, writeMode);
3497
if(cmd == '^') { /* back key, pop the stack */
3499
setError("unexpected text after the ^ command");
3505
struct ebWindow *prev = cw->prev;
3507
setError("no previous text");
3510
if(!cxQuit(context, 1))
3512
carrySubstitutionStrings(cw, prev);
3513
sessionList[context].lw = cw = prev;
3520
if(cmd == 'M') { /* move this to another session */
3522
setError("unexpected text after the M command");
3526
setError("destination session not specified");
3530
setError("no previous text, cannot back up");
3535
if(cxActive(cx) && !cxQuit(cx, 2))
3537
/* Magic with pointers, hang on to your hat. */
3538
sessionList[cx].fw = sessionList[cx].lw = cw;
3548
if(!cxQuit(context, 0))
3550
if(!(a = showLinks()))
3552
freeUndoLines(cw->map);
3554
nzFree(preWindow.map);
3556
cw->firstOpMode = cw->changeMode = false;
3561
rc = addTextToBuffer((pst) a, strlen(a), 0);
3563
undoable = cw->changeMode = false;
3564
fileSize = apparentSize(context, false);
3568
if(cmd == '<') { /* run a function */
3569
return runEbFunction(line);
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");
3580
p = (char *)fetchLine(endRange, -1);
3581
j = pstLength((pst) p);
3583
p[j] = 0; /* temporary */
3584
dirline = makeAbsPath(p);
3589
/* I don't think we need to make a copy here. */
3593
/* g in directory mode */
3602
printf("session %d\n", context);
3605
/* more e to come */
3608
if(cmd == 'g') { /* see if it's a go command */
3611
bool click, dclick, over;
3612
bool jsh, jsgo, jsdead;
3616
j = strtol(line, (char **)&s, 10);
3617
if(j >= 0 && (!*s || stringEqual(s, "?"))) {
3618
jsh = jsgo = nogo = false;
3619
jsdead = cw->jsdead;
3623
if(endRange > startRange) {
3624
setError("cannot apply the g command to a range");
3627
p = (char *)fetchLine(endRange, -1);
3629
findField(p, 0, j, &n, &tagno, &h, &ev);
3630
debugPrint(5, "findField returns %d, %s", tagno, h);
3632
fieldNumProblem("links", 'g', j, n);
3635
jsh = memEqualCI(h, "javascript:", 11);
3637
over = tagHandler(tagno, "onmouseover");
3638
click = tagHandler(tagno, "onclick");
3639
dclick = tagHandler(tagno, "ondblclick");
3644
nogo = stringEqual(h, "#");
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);
3650
puts("javascript is disabled, no action taken");
3652
puts("javascript is disabled, going straight to the url");
3655
line = allocatedLine = h;
3661
/* The program might depend on the mouseover code running first */
3663
rc = handlerGo(ev, "onmouseover");
3669
/* This is the only handler where false tells the browser to do something else. */
3671
set_property_string(jwin, "status", h);
3673
rc = handlerGo(ev, "onclick");
3681
rc = javaParseExecute(jwin, h, 0, 0);
3693
/* Some shorthand, like s,2 to split the line at the second comma */
3695
strcpy(newline, "//%");
3697
} else if(strchr(",.;:!?)-\"", first) &&
3698
(!line[1] || isdigitByte(line[1]) && !line[2])) {
3700
esc[0] = esc[1] = 0;
3701
if(first == '.' || first == '?')
3703
sprintf(newline, "/%s%c +/%c\\n%s%s",
3704
esc, first, first, (line[1] ? "/" : ""), line + 1);
3705
debugPrint(7, "shorthand regexp %s", newline);
3712
if((cmd == 'i' || cmd == 's') && first) {
3716
cx = strtol(s, (char **)&s, 10);
3718
if(c && (strchr(valid_delim, c) || cmd == 'i' && strchr("*<?=", c))) {
3719
if(!cw->browseMode && (cmd == 'i' || cx)) {
3720
setError("not in browse mode");
3723
if(endRange > startRange && cmd == 'i') {
3724
setError("cannot apply the i%c command to a range", c);
3727
if(cmd == 'i' && strchr("?=<*", c)) {
3732
debugPrint(5, "scmd = %c", scmd);
3734
p = (char *)fetchLine(cw->dot, -1);
3740
findInputField(p, j, cx, &n, &tagno);
3741
debugPrint(5, "findField returns %d.%d", n, tagno);
3743
fieldNumProblem((c == '*' ? "buttons" : "input fields"),
3748
infShow(tagno, line);
3752
bool fromfile = false;
3754
setError("cannot use i< in a global command");
3755
return (globSub = false);
3759
setError("no file specified");
3762
n = stringIsNum(line);
3766
if(!cxCompare(n) || !cxActive(n))
3768
dol = sessionList[n].lw->dol;
3770
setError("buffer %d is empty", n);
3774
setError("buffer %d contains more than one line",
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);
3788
if(!envFile(line, &line))
3790
fd = open(line, O_RDONLY | O_TEXT);
3792
setError("cannot open %s", line);
3795
n = read(fd, newline, sizeof (newline));
3798
setError("cannot read from %s", line);
3802
for(j = 0; j < n; ++j) {
3803
if(newline[j] == 0) {
3804
setError("input text contains nulls", line);
3807
if(newline[j] == '\r' && !fromfile &&
3808
j < n - 1 && newline[j + 1] != '\n') {
3810
("line contains an embeded carriage return");
3813
if(newline[j] == '\r' || newline[j] == '\n')
3816
if(j == sizeof (newline)) {
3817
setError("first line of %s is too long", line);
3826
if(!infPush(tagno, &allocatedLine))
3830
/* No url means it was a reset button */
3833
line = allocatedLine;
3840
setError("unexpected text after the %c command", icmd);
3846
if(cmd == 'e' || cmd == 'b' && first && first != '#') {
3847
if(cw->fileName && !noStack && sameURL(line, cw->fileName)) {
3848
if(stringEqual(line, cw->fileName)) {
3850
("file is currently in buffer - please use the rf command to refresh");
3853
/* Same url, but a different #section */
3854
s = strchr(line, '#');
3855
if(!s) { /* no section specified */
3867
/* Different URL, go get it. */
3868
/* did you make changes that you didn't write? */
3869
if(!cxQuit(context, 0))
3871
freeUndoLines(cw->map);
3873
nzFree(preWindow.map);
3875
cw->firstOpMode = cw->changeMode = false;
3876
startRange = endRange = 0;
3877
changeFileName = 0; /* should already be zero */
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;
3885
decodeMailURL(line, &addr, &subj, &body);
3887
ql += 4; /* to:\n */
3888
ql += subj ? strlen(subj) : 5;
3889
ql += 9; /* subject:\n */
3892
q = allocMem(ql + 1);
3893
sprintf(q, "to:%s\nSubject:%s\n%s", addr, subj ? subj : "Hello",
3895
j = addTextToBuffer((pst) q, ql, 0);
3902
("SendMail link. Compose your mail, type sm to send, then ^ to get back.\n");
3904
w->fileName = cloneString(line);
3905
if(icmd == 'g' && !nogo && isURL(cw->fileName))
3906
debugPrint(2, "*%s", cw->fileName);
3907
j = readFile(cw->fileName, "");
3909
w->firstOpMode = w->changeMode = false;
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)) {
3918
if(noStack && cw->prev) {
3937
if(changeFileName) {
3938
nzFree(w->fileName);
3939
w->fileName = changeFileName;
3942
/* Some files we just can't browse */
3943
if(!cw->dol || cw->binMode | cw->dirMode)
3951
if(!cw->browseMode) {
3953
setError("cannot browse a binary file");
3957
setError("cannot browse a directory");
3961
setError("cannot browse an empty file");
3965
debugPrint(1, "%d", fileSize);
3968
if(!browseCurrentBuffer()) {
3970
setError("this doesn't look like browsable text");
3976
setError("already browsing");
3981
if(!refreshDelay(newloc_d, newlocation)) {
3982
nzFree(newlocation);
3984
} else if(fetchHistory(cw->fileName, newlocation) < 0) {
3985
nzFree(newlocation);
3990
noStack = newloc_rf;
3991
nzFree(allocatedLine);
3992
line = allocatedLine = newlocation;
3993
debugPrint(2, "redirect %s", line);
3998
puts("redirection interrupted by user");
4005
/* Jump to the #section, if specified in the url */
4006
s = strchr(line, '#');
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. */
4016
/* Print the file size before we print the line. */
4018
debugPrint(1, "%d", fileSize);
4021
for(i = 1; i <= cw->dol; ++i) {
4022
char *p = (char *)fetchLine(i, -1);
4023
if(lineHasTag(p, s)) {
4029
setError("label %s not found", s);
4033
if(!globSub) { /* get ready for subsequent undo */
4034
struct ebWindow *pw = &preWindow;
4037
memcpy(pw->labels, cw->labels, 26 * sizeof (int));
4038
pw->binMode = cw->binMode;
4039
pw->nlMode = cw->nlMode;
4040
pw->dirMode = cw->dirMode;
4042
if(!cw->map || !stringEqual(pw->map, cw->map)) {
4047
if(cw->map && !pw->map)
4048
pw->map = cloneString(cw->map);
4050
/* data saved for undo */
4051
if(cmd == 'g' || cmd == 'v') {
4052
return doGlobal(line);
4055
if(cmd == 'm' || cmd == 't') {
4061
rc = infReplace(tagno, line, 1);
4066
if(cw->browseMode) {
4067
setError("i not available in browse mode");
4071
--startRange, --endRange;
4075
delText(startRange, endRange);
4076
endRange = --startRange;
4082
setError("cannot run an insert command from an edbrowse function");
4085
return inputLinesIntoBuffer();
4095
if(endRange == cw->dol)
4097
delText(startRange, endRange);
4101
if(cmd == 'j' || cmd == 'J') {
4107
return readContext(cx);
4109
j = readFile(line, "");
4114
setError("no file specified");
4119
j = substituteText(line);
4129
setError("command %c not yet implemented", icmd);
4130
return (globSub = false);
4134
edbrowseCommand(const char *line, bool script)
4137
globSub = intFlag = false;
4141
rc = runCommand(line);
4143
debugPrint(1, "%d", fileSize);
4147
showErrorConditional(cmd);
4151
struct ebWindow *pw = &preWindow;
4152
struct ebWindow *uw = &undoWindow;
4153
debugPrint(6, "undoable");
4156
if(uw->map && pw->map && stringEqual(uw->map, pw->map)) {
4159
debugPrint(6, "success freeUndo");
4160
freeUndoLines(pw->map);
4164
memcpy(uw->labels, pw->labels, 26 * sizeof (int));
4165
uw->binMode = pw->binMode;
4166
uw->nlMode = pw->nlMode;
4167
uw->dirMode = pw->dirMode;
4171
} /* edbrowseCommand */
4173
/* Take some text, usually empty, and put it in a side buffer. */
4175
sideBuffer(int cx, const char *text, const char *bufname, bool autobrowse)
4182
for(cx = 1; cx < MAXSESSION; ++cx)
4183
if(!sessionList[cx].lw)
4185
if(cx == MAXSESSION) {
4187
"warning: no buffers available to handle the ancillary window");
4191
cxSwitch(cx, false);
4193
cw->fileName = cloneString(bufname);
4194
debrowseSuffix(cw->fileName);
4197
rc = addTextToBuffer((pst) text, strlen(text), 0);
4200
"warning: could not preload <buffer %d> with its initial text",
4203
/* This is html; we need to render it.
4204
* I'm disabling javascript in this window.
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. */
4211
browseCurrentBuffer();
4213
} /* browse the side window */
4215
/* put text in the side window */
4216
/* back to original context */
4217
cxSwitch(svcx, false);
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. */
4233
struct inputChange {
4234
struct inputChange *next, *prev;
4238
static struct listHead inputChangesPending = {
4239
&inputChangesPending, &inputChangesPending
4241
static struct inputChange *ic;
4244
browseCurrentBuffer(void)
4246
char *rawbuf, *newbuf;
4248
bool rc, remote = false, do_ip = false;
4249
bool save_ch = cw->changeMode;
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())
4263
if(!unfoldBuffer(context, false, &rawbuf, &rawsize))
4264
return false; /* should never happen */
4265
prepareForBrowse(rawbuf, rawsize);
4267
/* No harm in running this code in mail client, but no help either,
4268
* and it begs for bugs, so leave it out. */
4270
freeUndoLines(cw->map);
4272
nzFree(preWindow.map);
4274
cw->firstOpMode = false;
4276
/* There shouldn't be anything in the input pending list, but clear
4277
* it out, just to be safe. */
4278
freeList(&inputChangesPending);
4282
newbuf = emailParse(rawbuf);
4288
if(memEqualCI(newbuf, "<html>\n", 7)) {
4289
/* double browse, mail then html */
4292
rawsize = strlen(rawbuf);
4293
prepareForBrowse(rawbuf, rawsize);
4298
cw->jsdead = !javaOK(cw->fileName);
4300
cw->jsc = createJavaContext();
4301
nzFree(newlocation); /* should already be 0 */
4303
newbuf = htmlParse(rawbuf, remote);
4306
cw->browseMode = true;
4307
cw->rnlMode = cw->nlMode;
4309
cw->r_dot = cw->dot, cw->r_dol = cw->dol;
4310
cw->dot = cw->dol = 0;
4311
cw->r_map = cw->map;
4313
memcpy(cw->r_labels, cw->labels, sizeof (cw->labels));
4314
memset(cw->labels, 0, sizeof (cw->labels));
4316
rc = addTextToBuffer((pst) newbuf, j, 0);
4318
cw->firstOpMode = undoable = false;
4319
cw->changeMode = save_ch;
4323
j = strlen(cw->fileName);
4324
cw->fileName = reallocMem(cw->fileName, j + 8);
4325
strcat(cw->fileName, ".browse");
4330
} /* should never happen */
4331
fileSize = apparentSize(context, true);
4334
/* apply any input changes pending */
4335
foreach(ic, inputChangesPending)
4336
updateFieldInBuffer(ic->tagno, ic->value, 0, false);
4337
freeList(&inputChangesPending);
4343
} /* browseCurrentBuffer */
4346
locateTagInBuffer(int tagno, int *ln_p, char **p_p, char **s_p, char **t_p)
4353
foreachback(ic, inputChangesPending) {
4354
if(ic->tagno != tagno)
4357
*t_p = ic->value + strlen(ic->value);
4358
/* we don't need to set the others in this special case */
4363
/* still rendering the page */
4364
sprintf(search, "%c%d<", InternalCodeChar, tagno);
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)
4371
if(!memcmp(s, search, n))
4375
continue; /* not here, try next line */
4376
s = strchr(s, '<') + 1;
4377
t = strstr(s, "\2000>");
4379
errorPrint("@no closing > at line %d", ln);
4388
} /* locateTagInBuffer */
4390
/* Update an input field in the current buffer.
4391
* The input field may not be here, if you've deleted some lines. */
4393
updateFieldInBuffer(int tagno, const char *newtext, int notify, bool required)
4395
int ln, idx, n, plen;
4396
char *p, *s, *t, *new;
4397
char newidx[LNWIDTH + 1];
4399
if(parsePage) { /* we don't even have the buffer yet */
4400
ic = allocMem(sizeof (struct inputChange) + strlen(newtext));
4402
strcpy(ic->value, newtext);
4403
addToListBack(&inputChangesPending, ic);
4406
/* still rendering the page */
4407
if(locateTagInBuffer(tagno, &ln, &p, &s, &t)) {
4408
n = (plen = pstLength((pst) p)) + strlen(newtext) - (t - s);
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 */
4421
printf("line %d has been updated\n", ln);
4422
cw->firstOpMode = undoable = true;
4427
errorPrint("fieldInBuffer could not find tag %d newtext %s", tagno,
4429
} /* updateFieldInBuffer */
4431
/* This is the inverse of the above function, fetch instead of update. */
4433
getFieldFromBuffer(int tagno)
4437
if(locateTagInBuffer(tagno, &ln, &p, &s, &t)) {
4438
return pullString1(s, t);
4441
/* line has been deleted, revert to the reset value */
4443
} /* getFieldFromBuffer */
4446
fieldIsChecked(int tagno)
4449
char *p, *s, *t, *new;
4450
if(locateTagInBuffer(tagno, &ln, &p, &s, &t))
4453
} /* fieldIsChecked */