2
* Text buffer support routines, manage text and edit sessions.
3
* Copyright (c) Karl Dahlke, 2008
4
* This file is part of the edbrowse project, released under GPL.
9
/* If this include file is missing, you need the pcre package,
10
* and the pcre-devel package. */
13
/* Static variables for this file. */
15
/* The valid edbrowse commands. */
16
static const char valid_cmd[] = "aAbBcdDefghHijJklmMnpqrstuvwXz=^<";
17
/* Commands that can be done in browse mode. */
18
static const char browse_cmd[] = "AbBdDefghHijJklmMnpqsuvwXz=^<";
19
/* Commands for sql mode. */
20
static const char sql_cmd[] = "AadDefghHiklmnpqrsvwXz=^<";
21
/* Commands for directory mode. */
22
static const char dir_cmd[] = "AdDefghHklmnpqsvwXz=^<";
23
/* Commands that work at line number 0, in an empty file. */
24
static const char zero_cmd[] = "aAbefhHMqruw=^<";
25
/* Commands that expect a space afterward. */
26
static const char spaceplus_cmd[] = "befrw";
27
/* Commands that should have no text after them. */
28
static const char nofollow_cmd[] = "aAcdDhHjlmnptuX=";
29
/* Commands that can be done after a g// global directive. */
30
static const char global_cmd[] = "dDijJlmnpstX";
32
static struct ebWindow preWindow, undoWindow;
33
static int startRange, endRange; /* as in 57,89p */
34
static int destLine; /* as in 57,89m226 */
35
static int last_z = 1;
36
static char cmd, icmd, scmd;
37
static uchar subPrint; /* print lines after substitutions */
38
static bool noStack; /* don't stack up edit sessions */
39
static bool globSub; /* in the midst of a g// command */
40
static bool inscript; /* run from inside an edbrowse function */
41
static int lastq, lastqq;
42
static char icmd; /* input command, usually the same as cmd */
45
/*********************************************************************
46
/* If a rendered line contains a hyperlink, the link is indicated
47
* by a code that is stored inline.
48
* If the hyperlink is number 17 on the list of hyperlinks for this window,
49
* it is indicated by InternalCodeChar 17 { text }.
50
* The "text" is what you see on the page, what you click on.
51
* {Click here for more information}.
52
* And the braces tell you it's a hyperlink.
53
* That's just my convention.
54
* The prior chars are for internal use only.
55
* I'm assuming these chars don't/won't appear on the rendered page.
56
* Yeah, sometimes nonascii chars appear, especially if the page is in
57
* a European language, but I just assume a rendered page will not contain
58
* the sequence: InternalCodeChar number {
59
* In fact I assume the rendered text won't contain InternalCodeChar at all.
60
* So I use this char to demark encoded constructs within the lines.
61
* And why do I encode things right in the text?
62
* Well it took me a few versions to reach this point.
63
* But it makes so much sense!
64
* If I move a line, the referenced hyperlink moves with it.
65
* I don't have to update some other structure that says,
66
* "At line 73, characters 29 through 47, that's hyperlink 17.
67
* I use to do it that way, and wow, what a lot of overhead
68
* when you move lines about, or delete them, or make substitutions.
69
* Yes, you really need to change rendered html text,
70
* because that's how you fill out forms.
71
* Add just one letter to the first name in your fill out form,
72
* and the hyperlink that comes later on in the line shifts down.
73
* I use to go back and update the pointers,
74
* so that the hyperlink started at offset 30, rather than 29.
75
* That was a lot of work, and very error prone.
76
* Finally I got smart, and coded the html tags inline.
77
* They can't get lost as text is modified. They move with the text.
79
* So now, certain sequences in the text are for internal use only.
80
* This routine strips out these sequences, for display.
81
* After all, you don't want to see those code characters.
82
* You just want to see {Click here for more information}. */
85
removeHiddenNumbers(pst p)
91
while((c = *s) != '\n') {
92
if(c != InternalCodeChar) {
104
} while(isdigitByte(d));
109
if(strchr("<>{}", d)) {
113
/* This is not a code sequence I recognize. */
114
/* This should never happen; just move along. */
117
*t = c; /* terminating newline */
118
} /* removeHiddenNumbers */
120
/* Fetch line n from the current buffer, or perhaps another buffer.
121
* This returns an allocated copy of the string,
122
* and you need to free it when you're done.
123
* Good grief, how inefficient!
124
* I know, but perl use to do it, and I never noticed the slowdown.
125
* This is not the Linux kernel, we don't need to be ultra-efficient.
126
* We just need to be consistent in our programming efforts.
127
* Sometimes the returned line is changed,
128
* and if it happens sometimes, we may as well make the new copy
129
* all the time, even if the line doesn't change, to be consistent.
130
* You can supress the copy feature with -1. */
133
fetchLineContext(int n, int show, int cx)
135
struct ebWindow *lw = sessionList[cx].lw;
139
pst p; /* the resulting copy of the string */
142
i_printfExit(MSG_InvalidSession, cx);
145
if(n <= 0 || n > dol)
146
i_printfExit(MSG_InvalidLineNb, n);
148
t = map + LNWIDTH * n;
151
i_printfExit(MSG_LineNull, n, idx);
153
return textLines[idx];
154
p = clonePstring(textLines[idx]);
155
if(show && lw->browseMode)
156
removeHiddenNumbers(p);
158
} /* fetchLineContext */
161
fetchLine(int n, int show)
163
return fetchLineContext(n, show, context);
167
apparentSize(int cx, bool browsing)
172
if(cx <= 0 || cx >= MAXSESSION || (w = sessionList[cx].lw) == 0) {
173
setError(MSG_SessionInactive, cx);
177
for(ln = 1; ln <= w->dol; ++ln) {
178
if(browsing && sessionList[cx].lw->browseMode) {
179
pst line = fetchLineContext(ln, 1, cx);
180
size += pstLength(line);
183
pst line = fetchLineContext(ln, -1, cx);
184
size += pstLength(line);
186
} /* loop over lines */
187
if(sessionList[cx].lw->nlMode)
193
currentBufferSize(void)
195
return apparentSize(context, cw->browseMode);
196
} /* currentBufferSize */
198
/* get the directory suffix for a file.
199
* This only makes sense in directory mode. */
201
dirSuffixContext(int n, int cx)
203
static char suffix[4];
204
struct ebWindow *lw = sessionList[cx].lw;
207
char *s = lw->map + LNWIDTH * n + 8;
217
} /* dirSuffixContext */
222
return dirSuffixContext(n, context);
225
/* Display a line to the screen, but no more than 500 chars. */
229
pst line = fetchLine(n, 1);
236
if(endMarks == 2 || endMarks && cmd == 'l')
239
while((c = *s++) != '\n') {
241
if(c == 0 || c == '\r' || c == '\x1b')
244
/* show tabs and backspaces, ed style */
249
if(c < ' ' || c == 0x7f || c >= 0x80 && listNA)
253
printf("~%02X", c), cnt += 3;
255
printf("%c", c), ++cnt;
258
} /* loop over line */
262
printf("%s", dirSuffix(n));
263
if(endMarks == 2 || endMarks && cmd == 'l')
274
displayLine(cw->dot);
279
/* Get a line from standard in. Need not be a terminal.
280
* Each input line is limited to 255 chars.
281
* On Unix cooked mode, that's as long as a line can be anyways.
282
* This routine returns the line in a static string.
283
* If you want to keep it, better make a copy.
284
* ~xx is converted from hex, a way to enter nonascii chars.
285
* This is the opposite of displayLine() above.
286
* But you can't enter a newline this way; I won't permit it.
287
* The only newline is the one corresponding to the end of your text,
288
* when you hit enter. This terminates the line.
289
* As we described earlier, this is a perl string.
290
* It may contain nulls, and is terminated by newline.
291
* The program exits on EOF.
292
* If you hit interrupt at this point, I print a message
293
* and ask for your line again. */
298
static uchar line[MAXTTYLINE];
305
if(!fgets((char *)line, sizeof (line), stdin)) {
315
while(i < sizeof (line) - 1 && (c = line[i]) != '\n') {
316
/* A bug in my keyboard causes nulls to be entered from time to time. */
340
} /* loop over input chars */
346
char lhs[MAXRE], rhs[MAXRE];
347
bool lhs_yes, rhs_yes;
351
saveSubstitutionStrings(void)
353
if(!searchStringsAll)
357
globalSubs.lhs_yes = cw->lhs_yes;
358
strcpy(globalSubs.lhs, cw->lhs);
359
globalSubs.rhs_yes = cw->rhs_yes;
360
strcpy(globalSubs.rhs, cw->rhs);
361
} /* saveSubstitutionStrings */
364
restoreSubstitutionStrings(struct ebWindow *nw)
366
if(!searchStringsAll)
370
nw->lhs_yes = globalSubs.lhs_yes;
371
strcpy(nw->lhs, globalSubs.lhs);
372
nw->rhs_yes = globalSubs.rhs_yes;
373
strcpy(nw->rhs, globalSubs.rhs);
374
} /* restoreSubstitutionStrings */
376
/* Create a new window, with default variables. */
377
static struct ebWindow *
380
struct ebWindow *nw; /* the new window */
381
nw = allocZeroMem(sizeof (struct ebWindow));
382
saveSubstitutionStrings();
383
restoreSubstitutionStrings(nw);
388
freeWindowLines(char *map)
393
for(t = map + LNWIDTH; *t; t += LNWIDTH) {
403
debugPrint(6, "freeWindowLines = %d", cnt);
404
} /* freeWindowLines */
406
/* quick sort compare */
408
qscmp(const void *s, const void *t)
410
return memcmp(s, t, 6);
413
/* Free any lines not used by the snapshot of the current session. */
415
freeUndoLines(const char *cmap)
417
char *map = undoWindow.map;
420
int diff, ln, cnt = 0;
423
debugPrint(6, "freeUndoLines = null");
428
debugPrint(6, "freeUndoLines = win");
429
freeWindowLines(map);
434
/* sort both arrays, run comm, and find out which lines are not needed any more,
436
* Use quick sort; some files are 100,000 lines long.
437
* I have to copy the second array, so I can sort it.
438
* The first I can sort in place, cause it's going to get thrown away. */
440
cmap2 = cloneString(cmap);
441
qsort(map + LNWIDTH, (strlen(map) / LNWIDTH) - 1, LNWIDTH, qscmp);
442
qsort(cmap2 + LNWIDTH, (strlen(cmap2) / LNWIDTH) - 1, LNWIDTH, qscmp);
447
diff = memcmp(s, t, 6);
457
/* This line isn't being used any more. */
480
debugPrint(6, "freeUndoLines = %d", cnt);
481
} /* freeUndoLines */
484
freeWindow(struct ebWindow *w)
486
/* The next few are designed to do nothing if not in browseMode */
487
freeJavaContext(w->jsc);
488
freeWindowLines(w->r_map);
495
freeWindowLines(w->map);
498
nzFree(w->baseDirName);
502
/*********************************************************************
503
Here are a few routines to switch contexts from one buffer to another.
504
This is how the user edits multiple sessions, or browses multiple
505
web pages, simultaneously.
506
*********************************************************************/
512
setError(MSG_Session0);
515
if(cx >= MAXSESSION) {
516
setError(MSG_SessionHigh, cx, MAXSESSION - 1);
520
return true; /* ok */
521
setError(MSG_SessionCurrent, cx);
525
/* is a context active? */
529
if(cx <= 0 || cx >= MAXSESSION)
530
i_printfExit(MSG_SessionOutRange, cx);
531
if(sessionList[cx].lw)
533
setError(MSG_SessionInactive, cx);
540
struct ebWindow *lw = createWindow();
541
if(sessionList[cx].lw)
542
i_printfExit(MSG_DoubleInit, cx);
543
sessionList[cx].fw = sessionList[cx].lw = lw;
547
cxQuit(int cx, int action)
549
struct ebWindow *w = sessionList[cx].lw;
551
i_printfExit(MSG_QuitNoActive, cx);
553
/* We might be trashing data, make sure that's ok. */
555
!(w->dirMode | w->sqlMode) && lastq != cx && w->fileName &&
556
!isURL(w->fileName)) {
558
setError(MSG_ExpectW);
560
setError(MSG_ExpectWX, cx);
565
return true; /* just a test */
568
/* Don't need to retain the undo lines. */
569
freeWindowLines(undoWindow.map);
571
nzFree(preWindow.map);
577
struct ebWindow *p = w->prev;
581
sessionList[cx].fw = sessionList[cx].lw = 0;
591
/* Switch to another edit session.
592
* This assumes cxCompare has succeeded - we're moving to a different context.
593
* Pass the context number and an interactive flag. */
595
cxSwitch(int cx, bool interactive)
597
bool created = false;
598
struct ebWindow *nw = sessionList[cx].lw; /* the new window */
601
nw = sessionList[cx].lw;
604
saveSubstitutionStrings();
605
restoreSubstitutionStrings(nw);
609
freeUndoLines(cw->map);
610
cw->firstOpMode = false;
613
cs = sessionList + cx;
615
if(interactive && debugLevel) {
617
i_puts(MSG_SessionNew);
618
else if(cw->fileName)
624
/* The next line is required when this function is called from main(),
625
* when the first arg is a url and there is a second arg. */
626
startRange = endRange = cw->dot;
629
/* Make sure we have room to store another n lines. */
637
for(j = 0; j < textLinesCount; ++j)
638
nzFree(textLines[j]);
641
textLinesCount = textLinesMax = 0;
647
int need = textLinesCount + n;
649
setError(MSG_LineLimit);
652
if(need > textLinesMax) {
653
int newmax = textLinesMax * 3 / 2;
655
newmax = need + 8192;
659
debugPrint(4, "textLines realloc %d", newmax);
660
textLines = reallocMem(textLines, newmax * sizeof (pst));
662
textLines = allocMem(newmax * sizeof (pst));
664
textLinesMax = newmax;
666
/* overflow requires realloc */
667
/* We now have room for n new lines, but you have to add n
668
* to textLines Count, once you have brought in the lines. */
672
/* This function is called for web redirection, by the refresh command,
673
* or by window.location = new_url. */
674
static char *newlocation;
675
static int newloc_d; /* possible delay */
676
static bool newloc_rf; /* refresh the buffer */
680
gotoLocation(char *url, int delay, bool rf)
682
if(newlocation && delay >= newloc_d) {
694
/* Adjust the map of line numbers -- we have inserted text.
695
* Also shift the downstream labels.
696
* Pass the string containing the new line numbers, and the dest line number. */
698
addToMap(int start, int end, int destl)
702
int nlines = end - start;
704
i_printfExit(MSG_EmptyPiece);
705
for(i = 0; i < 26; ++i) {
706
int ln = cw->labels[i];
709
cw->labels[i] += nlines;
711
cw->dot = destl + nlines;
713
newmap = allocMem((cw->dol + 1) * LNWIDTH + 1);
715
strcpy(newmap, cw->map);
717
strcpy(newmap, LNSPACE);
718
i = j = (destl + 1) * LNWIDTH; /* insert new piece here */
720
sprintf(newmap + i, LNFORMAT, start);
725
strcat(newmap, cw->map + j);
728
cw->firstOpMode = undoable = true;
730
cw->changeMode = true;
733
/* Add a block of text into the buffer; uses addToMap(). */
735
addTextToBuffer(const pst inbuf, int length, int destl)
737
int i, j, linecount = 0;
739
for(i = 0; i < length; ++i)
742
if(!linesComing(linecount + 1))
746
if(inbuf[length - 1] != '\n') {
747
/* doesn't end in newline */
748
++linecount; /* last line wasn't counted */
749
if(destl == cw->dol) {
751
if(cmd != 'b' && !cw->binMode && !ismc)
752
i_puts(MSG_NoTrailing);
754
} /* missing newline */
755
start = end = textLinesCount;
757
while(i < length) { /* another line */
760
if(inbuf[i++] == '\n')
762
if(inbuf[i - 1] == '\n') {
764
textLines[end] = allocMem(i - j);
766
/* last line with no nl */
767
textLines[end] = allocMem(i - j + 1);
768
textLines[end][i - j] = '\n';
770
memcpy(textLines[end], inbuf + j, i - j);
772
} /* loop breaking inbuf into lines */
773
textLinesCount = end;
774
addToMap(start, end, destl);
776
} /* addTextToBuffer */
778
/* Pass input lines straight into the buffer, until the user enters . */
781
inputLinesIntoBuffer(void)
783
int start = textLinesCount;
790
while(line[0] != '.' || line[1] != '\n') {
793
textLines[end++] = clonePstring(line);
796
if(end == start) { /* no lines entered */
798
if(!cw->dot && cw->dol)
802
if(endRange == cw->dol)
804
textLinesCount = end;
805
addToMap(start, end, endRange);
807
} /* inputLinesIntoBuffer */
809
/* Delete a block of text. */
812
delText(int start, int end)
818
strcpy(cw->map + start * LNWIDTH, cw->map + (end + 1) * LNWIDTH);
819
/* move the labels */
820
for(i = 0; i < 26; ++i) {
821
int ln = cw->labels[i];
832
if(cw->dot > cw->dol)
834
/* by convention an empty buffer has no map */
839
cw->firstOpMode = undoable = true;
841
cw->changeMode = true;
844
/* Delete files from a directory as you delete lines.
845
* Set dw to move them to your recycle bin.
846
* Set dx to delete them outright. */
851
static char path[ABSPATH];
852
if(strlen(cw->baseDirName) + strlen(f) > ABSPATH - 2) {
853
setError(MSG_PathNameLong, ABSPATH);
856
sprintf(path, "%s/%s", cw->baseDirName, f);
866
setError(MSG_DirNoWrite);
870
if(dirWrite == 1 && !recycleBin) {
871
setError(MSG_NoRecycle);
876
cnt = endRange - startRange + 1;
878
char *file, *t, *path, *ftype;
879
file = (char *)fetchLine(ln, 0);
880
t = strchr(file, '\n');
882
i_printfExit(MSG_NoNlOnDir, file);
884
path = makeAbsPath(file);
890
ftype = dirSuffix(ln);
891
if(dirWrite == 2 && *ftype == '/') {
892
setError(MSG_NoDirDelete);
897
if(dirWrite == 2 || *ftype && strchr("@<*^|", *ftype)) {
900
setError(MSG_NoRemove, file);
906
sprintf(bin, "%s/%s", recycleBin, file);
907
if(rename(path, bin)) {
912
setError(MSG_CopyMoveDir);
916
if(!fileIntoMemory(path, &rmbuf, &rmlen)) {
920
if(!memoryOutToFile(bin, rmbuf, rmlen,
921
MSG_TempNoCreate2, MSG_NoWrite2)) {
930
setError(MSG_NoMoveToTrash, file);
943
/* Move or copy a block of text. */
944
/* Uses range variables, hence no parameters. */
949
int er = endRange + 1;
950
int dl = destLine + 1;
951
int i_sr = sr * LNWIDTH; /* indexes into map */
952
int i_er = er * LNWIDTH;
953
int i_dl = dl * LNWIDTH;
954
int n_lines = er - sr;
957
int lowcut, highcut, diff, i;
959
if(dl > sr && dl < er) {
960
setError(MSG_DestInBlock);
963
if(cmd == 'm' && (dl == er || dl == sr)) {
965
setError(MSG_NoChange);
970
if(!linesComing(n_lines))
972
for(i = sr; i < er; ++i)
973
textLines[textLinesCount++] = fetchLine(i, 0);
974
addToMap(textLinesCount - n_lines, textLinesCount, destLine);
978
if(destLine == cw->dol || endRange == cw->dol)
980
/* All we really need do is rearrange the map. */
981
newmap = allocMem((cw->dol + 1) * LNWIDTH + 1);
984
memcpy(newmap + i_dl, map + i_sr, i_er - i_sr);
985
memcpy(newmap + i_dl + i_er - i_sr, map + i_dl, i_sr - i_dl);
987
memcpy(newmap + i_sr, map + i_er, i_dl - i_er);
988
memcpy(newmap + i_sr + i_dl - i_er, map + i_sr, i_er - i_sr);
993
/* now for the labels */
1003
for(i = 0; i < 26; ++i) {
1004
int ln = cw->labels[i];
1009
if(ln >= startRange && ln <= endRange) {
1010
ln += (dl < sr ? -diff : diff);
1012
ln += (dl < sr ? n_lines : -n_lines);
1015
} /* loop over labels */
1018
cw->dot += (dl < sr ? -diff : diff);
1019
cw->firstOpMode = undoable = true;
1021
cw->changeMode = true;
1025
/* Join lines from startRange to endRange. */
1031
if(startRange == endRange) {
1032
setError(MSG_Join1);
1038
for(j = startRange; j <= endRange; ++j)
1039
size += pstLength(fetchLine(j, -1));
1040
t = newline = allocMem(size);
1041
for(j = startRange; j <= endRange; ++j) {
1042
pst p = fetchLine(j, -1);
1043
size = pstLength(p);
1052
textLines[textLinesCount] = newline;
1053
sprintf(cw->map + startRange * LNWIDTH, LNFORMAT, textLinesCount);
1055
delText(startRange + 1, endRange);
1056
cw->dot = startRange;
1060
/* Read a file, or url, into the current buffer.
1061
* Post/get data is passed, via the second parameter, if it's a URL. */
1063
readFile(const char *filename, const char *post)
1065
char *rbuf; /* read buffer */
1066
int readSize; /* should agree with fileSize */
1067
int fh; /* file handle */
1068
bool rc; /* return code */
1069
bool is8859, isutf8;
1075
if(memEqualCI(filename, "file://", 7)) {
1078
setError(MSG_MissingFileName);
1084
if(isURL(filename)) {
1085
const char *domain = getHostURL(filename);
1087
return false; /* some kind of error */
1089
setError(MSG_DomainEmpty);
1093
rc = httpConnect(0, filename);
1096
/* The error could have occured after redirection */
1097
nzFree(changeFileName);
1102
/* We got some data. Any warnings along the way have been printed,
1103
* like 404 file not found, but it's still worth continuing. */
1105
fileSize = readSize = serverDataLen;
1107
if(fileSize == 0) { /* empty file */
1116
if(isSQL(filename)) {
1117
const char *t1, *t2;
1119
setError(MSG_DBOtherFile);
1122
t1 = strchr(cw->fileName, ']');
1123
t2 = strchr(filename, ']');
1124
if(t1 - cw->fileName != t2 - filename ||
1125
memcmp(cw->fileName, filename, t2 - filename)) {
1126
setError(MSG_DBOtherTable);
1129
rc = sqlReadRows(filename, &rbuf);
1132
if(!cw->dol && cmd != 'r') {
1133
cw->sqlMode = false;
1134
nzFree(cw->fileName);
1140
fileSize = strlen(rbuf);
1141
if(rbuf == EMPTYSTRING)
1147
/* reading a file from disk */
1149
if(fileTypeByName(filename, false) == 'd') {
1150
/* directory scan */
1151
int len, j, start, end;
1152
cw->baseDirName = cloneString(filename);
1153
/* get rid of trailing slash */
1154
len = strlen(cw->baseDirName);
1155
if(len && cw->baseDirName[len - 1] == '/')
1156
cw->baseDirName[len - 1] = 0;
1157
/* Understand that the empty string now means / */
1158
/* get the files, or fail if there is a problem */
1159
if(!sortedDirList(filename, &start, &end))
1163
i_puts(MSG_DirMode);
1165
if(start == end) { /* empty directory */
1171
addToMap(start, end, endRange);
1173
/* change 0 to nl and count bytes */
1175
for(j = start; j < end; ++j) {
1177
pst t = textLines[j];
1178
char *abspath = makeAbsPath((char *)t);
1185
len = t - textLines[j];
1186
fileSize += len + 1;
1188
continue; /* should never happen */
1189
ftype = fileTypeByName(abspath, true);
1192
s = cw->map + (endRange + 1 + j - start) * LNWIDTH + 8;
1193
if(isupperByte(ftype)) { /* symbolic link */
1195
*t = '@', *++t = '\n';
1200
ftype = tolower(ftype);
1215
*t = c, *++t = '\n';
1219
} /* loop fixing files in the directory scan */
1222
/* reading a directory */
1223
nopound = cloneString(filename);
1224
rbuf = strchr(nopound, '#');
1227
rc = fileIntoMemory(nopound, &rbuf, &fileSize);
1232
if(fileSize == 0) { /* empty file */
1240
if(!looksBinary(rbuf, fileSize)) {
1242
/* looks like text. In DOS, we should have compressed crlf.
1243
* Let's do that now. */
1245
for(i = j = 0; i < fileSize - 1; ++i) {
1247
if(c == '\r' && rbuf[i + 1] == '\n')
1254
/* Classify this incoming text as ascii or 8859 or utf8 */
1255
looks_8859_utf8(rbuf, fileSize, &is8859, &isutf8);
1256
debugPrint(3, "text type is %s",
1257
(isutf8 ? "utf8" : (is8859 ? "8859" : "ascii")));
1258
if(cons_utf8 && is8859) {
1259
if(debugLevel >= 2 || debugLevel == 1 && !isURL(filename))
1260
i_puts(MSG_ConvUtf8);
1261
iso2utf(rbuf, fileSize, &tbuf, &fileSize);
1265
if(!cons_utf8 && isutf8) {
1266
if(debugLevel >= 2 || debugLevel == 1 && !isURL(filename))
1267
i_puts(MSG_Conv8859);
1268
utf2iso(rbuf, fileSize, &tbuf, &fileSize);
1274
cw->utf8Mode = true;
1275
debugPrint(3, "setting utf8 mode");
1278
cw->iso8859Mode = true;
1279
debugPrint(3, "setting 8859 mode");
1283
} else if(binaryDetect & !cw->binMode) {
1284
i_puts(MSG_BinaryData);
1289
rc = addTextToBuffer((const pst)rbuf, fileSize, endRange);
1296
/* Write a range to a file. */
1298
writeFile(const char *name, int mode)
1302
if(memEqualCI(name, "file://", 7))
1306
setError(MSG_MissingFileName);
1311
setError(MSG_NoWriteURL);
1316
setError(MSG_WriteDB);
1321
setError(MSG_WriteEmpty);
1325
/* mode should be TRUNC or APPEND */
1326
mode |= O_WRONLY | O_CREAT;
1330
fh = open(name, mode, 0666);
1332
setError(MSG_NoCreate2, name);
1336
if(name == cw->fileName && iuConvert) {
1337
/* should we locale convert back? */
1338
if(cw->iso8859Mode && cons_utf8)
1340
i_puts(MSG_Conv8859);
1341
if(cw->utf8Mode && !cons_utf8)
1343
i_puts(MSG_ConvUtf8);
1347
for(i = startRange; i <= endRange; ++i) {
1348
pst p = fetchLine(i, (cw->browseMode ? 1 : -1));
1349
int len = pstLength(p);
1350
char *suf = dirSuffix(i);
1353
bool alloc_p = cw->browseMode;
1357
if(i == cw->dol && cw->nlMode)
1360
if(name == cw->fileName && iuConvert) {
1361
if(cw->iso8859Mode && cons_utf8) {
1362
utf2iso((char *)p, len, &tp, &tlen);
1367
if(write(fh, p, tlen) < tlen)
1372
if(cw->utf8Mode && !cons_utf8) {
1373
iso2utf((char *)p, len, &tp, &tlen);
1378
if(write(fh, p, tlen) < tlen)
1384
if(write(fh, p, len) < len)
1389
/* must write this line with the suffix on the end */
1391
if(write(fh, p, len) < len) {
1399
if(write(fh, suf, len) < len)
1406
setError(MSG_NoWrite2, name);
1411
} /* loop over lines */
1414
/* This is not an undoable operation, nor does it change data.
1415
* In fact the data is "no longer modified" if we have written all of it. */
1416
if(startRange == 1 && endRange == cw->dol)
1417
cw->changeMode = false;
1424
struct ebWindow *lw;
1425
int i, start, end, fardol;
1431
lw = sessionList[cx].lw;
1435
if(!linesComing(fardol))
1437
if(cw->dol == endRange)
1439
start = end = textLinesCount;
1440
for(i = 1; i <= fardol; ++i) {
1441
pst p = fetchLineContext(i, (lw->dirMode ? -1 : 1), cx);
1442
int len = pstLength(p);
1445
char *suf = dirSuffixContext(i, cx);
1446
char *q = allocMem(len + 3);
1450
strcpy(q + len, suf);
1454
textLines[end++] = p;
1456
} /* loop over lines in the "other" context */
1457
textLinesCount = end;
1458
addToMap(start, end, endRange);
1461
if(cw->dol == endRange)
1464
if(binaryDetect & !cw->binMode && lw->binMode) {
1466
i_puts(MSG_BinaryData);
1472
writeContext(int cx)
1474
struct ebWindow *lw;
1478
int fardol = endRange - startRange + 1;
1481
if(!linesComing(fardol))
1485
if(cxActive(cx) && !cxQuit(cx, 2))
1489
lw = sessionList[cx].lw;
1492
newmap = allocMem((fardol + 1) * LNWIDTH + 1);
1493
strcpy(newmap, LNSPACE);
1494
for(i = startRange, j = 1; i <= endRange; ++i, ++j) {
1495
p = fetchLine(i, (cw->dirMode ? -1 : 1));
1497
sprintf(newmap + j * LNWIDTH, LNFORMAT, textLinesCount);
1500
char *suf = dirSuffix(i);
1501
q = allocMem(len + 3);
1505
strcpy((char *)q + len, suf);
1506
len = strlen((char *)q);
1509
textLines[textLinesCount++] = p;
1513
lw->binMode = cw->binMode;
1514
if(cw->nlMode && endRange == cw->dol) {
1518
} /* nonempty range */
1519
lw->dot = lw->dol = fardol;
1522
} /* writeContext */
1525
debrowseSuffix(char *s)
1530
if(*s == '.' && stringEqual(s, ".browse")) {
1536
} /* debrowseSuffix */
1539
shellEscape(const char *line)
1545
int linesize, pass, n;
1547
char subshell[ABSPATH];
1549
/* preferred shell */
1550
sh = getenv("SHELL");
1554
linesize = strlen(line);
1556
/* interactive shell */
1557
if(!isInteractive) {
1558
setError(MSG_SessionBackground);
1562
system(line); /* don't know how to spawn a shell here */
1564
sprintf(subshell, "exec %s -i", sh);
1571
/* Make substitutions within the command line. */
1572
for(pass = 1; pass <= 2; ++pass) {
1573
for(t = line; *t; ++t) {
1576
if(t > line && isalnumByte(t[-1]))
1579
if(key && isalnumByte(t[2]))
1587
linesize += strlen(cw->fileName);
1589
strcpy(s, cw->fileName);
1595
if(key == '.' || key == '-' || key == '+') {
1601
if(n > cw->dol || n == 0) {
1602
setError(MSG_OutOfRange, key);
1608
p = fetchLine(n, -1);
1609
linesize += pstLength(p) - 1;
1611
p = fetchLine(n, 1);
1612
if(perl2c((char *)p)) {
1614
setError(MSG_ShellNull);
1617
strcpy(s, (char *)p);
1623
/* '. current line */
1624
if(islowerByte(key)) {
1625
n = cw->labels[key - 'a'];
1627
setError(MSG_NoLabel, key);
1632
/* 'x the line labeled x */
1638
} /* loop over chars */
1641
s = newline = allocMem(linesize + 1);
1646
/* Run the command. Note that this routine returns success
1647
* even if the shell command failed.
1648
* Edbrowse succeeds if it is *able* to run the system command. */
1655
/* Valid delimiters for search/substitute.
1656
* note that \ is conspicuously absent, not a valid delimiter.
1657
* I alsso avoid nestable delimiters such as parentheses.
1658
* And no alphanumerics please -- too confusing.
1659
* ed allows it, but I don't. */
1660
static const char valid_delim[] = "_=!;:`\"',/?@-";
1661
/* And a valid char for starting a line address */
1662
static const char valid_laddr[] = "0123456789-'.$+/?";
1664
/* Check the syntax of a regular expression, before we pass it to pcre.
1665
* The first char is the delimiter -- we stop at the next delimiter.
1666
* A pointer to the second delimiter is returned, along with the
1667
* (possibly reformatted) regular expression. */
1670
regexpCheck(const char *line, bool isleft, bool ebmuck,
1671
char **rexp, const char **split)
1672
{ /* result parameters */
1673
static char re[MAXRE + 20];
1677
/* Remember whether a char is "on deck", ready to be modified by * etc. */
1678
bool ondeck = false;
1679
bool was_ques = true; /* previous modifier was ? */
1680
bool cc = false; /* are we in a [...] character class */
1681
int mod; /* length of modifier */
1682
int paren = 0; /* nesting level of parentheses */
1683
/* We wouldn't be here if the line was empty. */
1684
char delim = *line++;
1687
if(!strchr(valid_delim, delim)) {
1688
setError(MSG_BadDelimit);
1696
if(c == delim || c == 0) {
1698
setError(MSG_NoSearchString);
1701
strcpy(re, cw->lhs);
1705
/* Interpret lead * or lone [ as literal */
1706
if(strchr("*?+", c) || c == '[' && !line[1]) {
1712
} else if(c == '%' && (line[1] == delim || line[1] == 0)) {
1714
setError(MSG_NoReplaceString);
1717
strcpy(re, cw->rhs);
1724
if(e >= re + MAXRE - 3) {
1725
setError(MSG_RexpLong);
1733
setError(MSG_LineBackslash);
1738
/* I can't think of any reason to remove the escaping \ from any character,
1739
* except ()|, where we reverse the sense of escape. */
1740
if(ebmuck && isleft && !cc && (d == '(' || d == ')' || d == '|')) {
1742
ondeck = false, was_ques = true;
1744
++paren, ondeck = false, was_ques = true;
1748
setError(MSG_UnexpectedRight);
1754
if(d == delim || ebmuck && !isleft && d == '&') {
1758
/* Nothing special; we retain the escape character. */
1760
if(isleft && d >= '0' && d <= '7' && (*line < '0' || *line > '7'))
1766
/* escaping backslash */
1767
/* Break out if we hit the delimiter. */
1771
/* Remember, I reverse the sense of ()| */
1773
if(ebmuck && (c == '(' || c == ')' || c == '|') || c == '^' && line != start && !cc) /* don't know why we have to do this */
1775
if(c == '$' && d && d != delim)
1779
if(c == '$' && !isleft && isdigitByte(d)) {
1780
if(d == '0' || isdigitByte(line[2])) {
1781
setError(MSG_RexpDollar);
1785
/* dollar digit on the right */
1786
if(!isleft && c == '&' && ebmuck) {
1793
/* push the character */
1797
/* No more checks for the rhs */
1801
if(cc) { /* character class */
1809
/* Skip all these checks for javascript,
1810
* it probably has the expression right anyways. */
1814
/* Modifiers must have a preceding character.
1815
* Except ? which can reduce the greediness of the others. */
1816
if(c == '?' && !was_ques) {
1823
if(c == '?' || c == '*' || c == '+')
1825
if(c == '{' && isdigitByte(d)) {
1826
const char *t = line + 1;
1827
while(isdigitByte(*t))
1831
while(isdigitByte(*t))
1839
strncpy(e, line, mod);
1845
setError(MSG_RexpModifier, e - mod - 1);
1854
} /* loop over chars in the pattern */
1861
setError(MSG_NoBracket);
1865
setError(MSG_NoParen);
1871
strcpy(cw->lhs, re);
1874
strcpy(cw->rhs, re);
1878
debugPrint(7, "%s regexp %s", (isleft ? "search" : "replace"), re);
1882
/* regexp variables */
1883
static int re_count;
1884
static int re_vector[11 * 3];
1885
static pcre *re_cc; /* compiled */
1888
regexpCompile(const char *re, bool ci)
1890
static char try8 = 0; /* 1 is utf8 on, -1 is utf8 off */
1891
const char *re_error;
1896
/* Do we need PCRE_NO_AUTO_CAPTURE? */
1899
re_opt |= PCRE_CASELESS;
1901
if(cons_utf8 && !cw->binMode && try8 >= 0) {
1903
const char *s = getenv("PCREUTF8");
1904
if(s && stringEqual(s, "off")) {
1910
re_opt |= PCRE_UTF8;
1913
re_cc = pcre_compile(re, re_opt, &re_error, &re_offset, 0);
1914
if(!re_cc && try8 > 0 && strstr(re_error, "PCRE_UTF8 support")) {
1915
i_puts(MSG_PcreUtf8);
1921
setError(MSG_RexpError, re_error);
1922
} /* regexpCompile */
1924
/* Get the start or end of a range.
1925
* Pass the line containing the address. */
1927
getRangePart(const char *line, int *lineno, const char **split)
1928
{ /* result parameters */
1929
int ln = cw->dot; /* this is where we start */
1932
if(isdigitByte(first)) {
1933
ln = strtol(line, (char **)&line, 10);
1934
} else if(first == '.') {
1936
/* ln is already set */
1937
} else if(first == '$') {
1940
} else if(first == '\'' && islowerByte(line[1])) {
1941
ln = cw->labels[line[1] - 'a'];
1943
setError(MSG_NoLabel, line[1]);
1947
} else if(first == '/' || first == '?') {
1949
char *re; /* regular expression */
1950
bool ci = caseInsensitive;
1951
char incr; /* forward or back */
1952
/* Don't look through an empty buffer. */
1954
setError(MSG_EmptyBuffer);
1957
if(!regexpCheck(line, true, true, &re, &line))
1959
if(*line == first) {
1965
/* second delimiter */
1966
regexpCompile(re, ci);
1969
/* We should probably study the pattern, if the file is large.
1970
* But then again, it's probably not worth it,
1971
* since the expressions are simple, and the lines are short. */
1972
incr = (first == '/' ? 1 : -1);
1980
subject = (char *)fetchLine(ln, 1);
1982
pcre_exec(re_cc, 0, subject, pstLength((pst) subject) - 1, 0, 0,
1987
setError(MSG_RexpError2, ln);
1988
return (globSub = false);
1994
setError(MSG_NotFound);
1997
} /* loop over lines */
1999
/* and ln is the line that matches */
2002
/* search pattern */
2003
/* Now add or subtract from this number */
2004
while((first = *line) == '+' || first == '-') {
2007
if(isdigitByte(*line))
2008
add = strtol(line, (char **)&line, 10);
2009
ln += (first == '+' ? add : -add);
2013
setError(MSG_LineHigh);
2017
setError(MSG_LineLow);
2024
} /* getRangePart */
2026
/* Apply a regular expression to each line, and then execute
2027
* a command for each matching, or nonmatching, line.
2028
* This is the global feature, g/re/p, which gives us the word grep. */
2030
doGlobal(const char *line)
2032
int gcnt = 0; /* global count */
2033
bool ci = caseInsensitive;
2037
char *re; /* regular expression */
2038
int i, origdot, yesdot, nodot;
2041
setError(MSG_RexpMissing, icmd);
2045
if(!regexpCheck(line, true, true, &re, &line))
2047
if(*line != delim) {
2048
setError(MSG_NoDelimit);
2056
/* clean up any previous stars */
2057
for(t = cw->map + LNWIDTH; *t; t += LNWIDTH)
2060
/* Find the lines that match the pattern. */
2061
regexpCompile(re, ci);
2064
for(i = startRange; i <= endRange; ++i) {
2065
char *subject = (char *)fetchLine(i, 1);
2067
pcre_exec(re_cc, 0, subject, pstLength((pst) subject), 0, 0,
2072
setError(MSG_RexpError2, i);
2075
if(re_count < 0 && cmd == 'v' || re_count >= 0 && cmd == 'g') {
2077
cw->map[i * LNWIDTH + 6] = '*';
2079
} /* loop over line */
2083
setError((cmd == 'v') + MSG_NoMatchG);
2087
/* apply the subcommand to every line with a star */
2095
while(gcnt && change) {
2096
change = false; /* kinda like bubble sort */
2097
for(i = 1; i <= cw->dol; ++i) {
2098
t = cw->map + i * LNWIDTH + 6;
2103
change = true, --gcnt;
2105
cw->dot = i; /* so we can run the command at this line */
2106
if(runCommand(line)) {
2108
/* try this line again, in case we deleted or moved it somewhere else */
2112
/* error in subcommand might turn global flag off */
2114
nodot = i, yesdot = 0;
2116
} /* serious error */
2117
} /* subcommand succeeds or fails */
2118
} /* loop over lines */
2119
} /* loop making changes */
2123
/* yesdot could be 0, even on success, if all lines are deleted via g/re/d */
2124
if(yesdot || !cw->dol) {
2126
if((cmd == 's' || cmd == 'i') && subPrint == 1)
2133
setError(MSG_NotModifiedG);
2135
if(!errorMsg[0] && intFlag)
2136
setError(MSG_Interrupted);
2137
return (errorMsg[0] == 0);
2141
fieldNumProblem(int desc, char c, int n, int nt, int nrt)
2144
setError(MSG_NoInputFields + desc);
2148
setError(MSG_ManyInputFields + desc, c, c, nt);
2152
setError(MSG_InputRange, n, c, c, nt);
2154
setError(MSG_InputRange2, n, c, c);
2155
} /* fieldNumProblem */
2157
/* Perform a substitution on a given line.
2158
* The lhs has been compiled, and the rhs is passed in for replacement.
2159
* Refer to the static variable re_cc for the compiled lhs.
2160
* The replacement line is static, with a fixed length.
2161
* Return 0 for no match, 1 for a replacement, and -1 for a real problem. */
2163
char replaceLine[REPLACELINELEN];
2164
static char *replaceLineEnd;
2165
static int replaceLineLen;
2167
replaceText(const char *line, int len, const char *rhs,
2168
bool ebmuck, int nth, bool global, int ln)
2170
int offset = 0, lastoffset, instance = 0;
2172
char *r = replaceLine;
2173
char *r_end = replaceLine + REPLACELINELEN - 8;
2174
const char *s = line, *s_end, *t;
2178
/* find the next match */
2179
re_count = pcre_exec(re_cc, 0, line, len, offset, 0, re_vector, 33);
2181
setError(MSG_RexpError2, ln);
2186
++instance; /* found another match */
2187
lastoffset = offset;
2188
offset = re_vector[1]; /* ready for next iteration */
2189
if(offset == lastoffset && (nth > 1 || global)) {
2190
setError(MSG_ManyEmptyStrings);
2193
if(!global &&instance != nth)
2196
/* copy up to the match point */
2197
s_end = line + re_vector[0];
2199
if(r + span >= r_end)
2205
/* Now copy over the rhs */
2206
/* Special case lc mc uc */
2207
if(ebmuck && (rhs[0] == 'l' || rhs[0] == 'm' || rhs[0] == 'u') &&
2208
rhs[1] == 'c' && rhs[2] == 0) {
2209
span = re_vector[1] - re_vector[0];
2210
if(r + span + 1 > r_end)
2212
memcpy(r, line + re_vector[0], span);
2214
i_caseShift((unsigned char *)r, rhs[0]);
2222
/* copy rhs, watching for $n */
2258
if(d >= '0' && d <= '7') {
2259
int octal = d - '0';
2261
if(d >= '0' && d <= '7') {
2263
octal = 8 * octal + d - '0';
2265
if(d >= '0' && d <= '7') {
2267
octal = 8 * octal + d - '0';
2278
if(c == '$' && isdigitByte(d)) {
2284
y = re_vector[2 * d];
2285
z = re_vector[2 * d + 1];
2289
if(r + span >= r_end)
2291
memcpy(r, line + y, span);
2301
} /* loop matching the regular expression */
2305
if(!global &&instance < nth)
2308
/* We got a match, copy the last span. */
2311
if(r + span >= r_end)
2316
replaceLineLen = r - replaceLine;
2320
setError(MSG_SubLong, REPLACELINELEN);
2324
/* Substitute text on the lines in startRange through endRange.
2325
* We could be changing the text in an input field.
2326
* If so, we'll call infReplace().
2327
* Also, we might be indirectory mode, whence we must rename the file.
2328
* This is a complicated function!
2329
* The return can be true or false, with the usual meaning,
2330
* but also a return of -1, which is failure,
2331
* and an indication that we need to abort any g// in progress.
2332
* It's a serious problem. */
2335
substituteText(const char *line)
2338
bool bl_mode = false; /* running the bl command */
2339
bool g_mode = false; /* s/x/y/g */
2340
bool ci = caseInsensitive;
2343
int nth = 0; /* s/x/y/7 */
2344
int lastSubst = 0; /* last successful substitution */
2345
char *re; /* the parsed regular expression */
2346
int ln; /* line number */
2347
int j, linecount, slashcount, nullcount, tagno, total, realtotal;
2348
char lhs[MAXRE], rhs[MAXRE];
2350
subPrint = 1; /* default is to print the last line substituted */
2352
if(stringEqual(line, "`bl"))
2353
bl_mode = true, breakLineSetup();
2356
/* watch for s2/x/y/ for the second input field */
2357
if(isdigitByte(*line))
2358
whichField = strtol(line, (char **)&line, 10);
2360
setError(MSG_RexpMissing2, icmd);
2364
if(cw->dirMode && !dirWrite) {
2365
setError(MSG_DirNoWrite);
2369
if(!regexpCheck(line, true, true, &re, &line))
2373
setError(MSG_NoDelimit);
2376
if(!regexpCheck(line, false, true, &re, &line))
2380
if(*line) { /* third delimiter */
2399
if(isdigitByte(c)) {
2401
setError(MSG_SubNumbersMany);
2404
nth = strtol(line, (char **)&line, 10);
2407
setError(MSG_SubSuffixBad);
2409
} /* loop gathering suffix flags */
2411
setError(MSG_SubNumberG);
2414
} /* closing delimiter */
2415
if(nth == 0 && !g_mode)
2418
regexpCompile(lhs, ci);
2424
} /* bl_mode or not */
2429
for(ln = startRange; ln <= endRange && !intFlag; ++ln) {
2430
char *p = (char *)fetchLine(ln, -1);
2431
int len = pstLength((pst) p);
2435
if(!breakLine(p, len, &newlen)) {
2436
setError(MSG_BreakLong, REPLACELINELEN);
2439
/* empty line is not allowed */
2441
replaceLine[newlen++] = '\n';
2442
/* perhaps no changes were made */
2443
if(newlen == len && !memcmp(p, replaceLine, len))
2445
replaceLineLen = newlen;
2446
replaceLineEnd = replaceLine + newlen;
2447
/* But the regular substitute doesn't have the \n on the end.
2448
* We need to make this one conform. */
2449
--replaceLineEnd, --replaceLineLen;
2452
if(cw->browseMode) {
2455
findInputField(p, 1, whichField, &total, &realtotal, &tagno);
2457
fieldNumProblem(0, 'i', whichField, total, realtotal);
2460
sprintf(search, "%c%d<", InternalCodeChar, tagno);
2461
sprintf(searchend, "%c0>", InternalCodeChar);
2462
/* Ok, if the line contains a null, this ain't gonna work. */
2463
s = strstr(p, search);
2466
s = strchr(s, '<') + 1;
2467
t = strstr(s, searchend);
2470
j = replaceText(s, t - s, rhs, true, nth, g_mode, ln);
2472
j = replaceText(p, len - 1, rhs, true, nth, g_mode, ln);
2480
/* Did we split this line into many lines? */
2481
linecount = slashcount = nullcount = 0;
2482
for(t = replaceLine; t < replaceLineEnd; ++t) {
2491
if(!linesComing(linecount + 1))
2496
setError(MSG_ReplaceNewline);
2500
setError(MSG_ReplaceNull);
2503
*replaceLineEnd = '\n';
2504
if(!sqlUpdateRow((pst) p, len - 1, (pst) replaceLine,
2505
replaceLineEnd - replaceLine))
2510
/* move the file, then update the text */
2511
char src[ABSPATH], *dest;
2512
if(slashcount + nullcount + linecount) {
2513
setError(MSG_DirNameBad);
2516
p[len - 1] = 0; /* temporary */
2522
*replaceLineEnd = 0;
2523
dest = makeAbsPath(replaceLine);
2526
if(!stringEqual(src, dest)) {
2527
if(fileTypeByName(dest, true)) {
2528
setError(MSG_DestFileExists);
2531
if(rename(src, dest)) {
2532
setError(MSG_NoRename, dest);
2535
} /* source and dest are different */
2538
if(cw->browseMode) {
2540
setError(MSG_InputNull2);
2544
setError(MSG_InputNewline2);
2547
replaceLine[replaceLineLen] = 0;
2548
/* We're managing our own printing, so leave notify = 0 */
2549
if(!infReplace(tagno, replaceLine, 0))
2553
replaceLine[replaceLineLen] = '\n';
2555
/* normal substitute */
2556
char newnum[LNWIDTH];
2557
textLines[textLinesCount] = allocMem(replaceLineLen + 1);
2558
memcpy(textLines[textLinesCount], replaceLine,
2559
replaceLineLen + 1);
2560
sprintf(newnum, "%06d", textLinesCount);
2561
memcpy(cw->map + ln * LNWIDTH, newnum, 6);
2564
/* Becomes many lines, this is the tricky case. */
2565
save_nlMode = cw->nlMode;
2567
addTextToBuffer((pst) replaceLine, replaceLineLen + 1, ln - 1);
2568
cw->nlMode = save_nlMode;
2569
endRange += linecount;
2571
/* There's a quirk when adding newline to the end of a buffer
2572
* that had no newline at the end before. */
2574
ln == cw->dol && replaceLine[replaceLineLen - 1] == '\n') {
2579
} /* browse or not */
2584
cw->firstOpMode = undoable = true;
2586
cw->changeMode = true;
2587
} /* loop over lines in the range */
2592
setError(MSG_Interrupted);
2599
setError(bl_mode + MSG_NoMatch);
2603
cw->dot = lastSubst;
2604
if(subPrint == 1 && !globSub)
2612
} /* substituteText */
2614
/*********************************************************************
2615
Implement various two letter commands.
2616
Most of these set and clear modes.
2617
Return 1 or 0 for success or failure as usual.
2618
But return 2 if there is a new command to run.
2619
The second parameter is a result parameter, the new command.
2620
*********************************************************************/
2623
twoLetter(const char *line, const char **runThis)
2625
static char newline[MAXTTYLINE];
2632
if(stringEqual(line, "qt"))
2635
if(line[0] == 'd' && line[1] == 'b' && isdigitByte(line[2]) && !line[3]) {
2636
debugLevel = line[2] - '0';
2640
if(line[0] == 'u' && line[1] == 'a' && isdigitByte(line[2]) && !line[3]) {
2641
char *t = userAgents[line[2] - '0'];
2644
setError(MSG_NoAgent, line[2]);
2648
if(helpMessagesOn || debugLevel >= 1)
2653
if(stringEqual(line, "re") || stringEqual(line, "rea")) {
2654
bool wasbrowse = cw->browseMode;
2655
freeUndoLines(cw->map);
2657
nzFree(preWindow.map);
2659
cw->firstOpMode = undoable = false;
2660
cmd = 'e'; /* so error messages are printed */
2661
rc = setupReply(line[2] == 'a');
2662
cw->firstOpMode = undoable = false;
2663
if(wasbrowse && cw->browseMode) {
2671
/* ^^^^ is the same as ^4 */
2672
if(line[0] == '^' && line[1] == '^') {
2673
const char *t = line + 2;
2677
sprintf(newline, "^%d", t - line);
2682
if(line[0] == 'm' && (i = stringIsNum(line + 1)) >= 0) {
2683
sprintf(newline, "^M%d", i);
2687
if(line[0] == 'c' && line[1] == 'd') {
2689
if(!c || isspaceByte(c)) {
2690
const char *t = line + 2;
2693
cmd = 'e'; /* so error messages are printed */
2695
char cwdbuf[ABSPATH];
2697
if(!getcwd(cwdbuf, sizeof (cwdbuf))) {
2698
setError(c ? MSG_CDGetError : MSG_CDSetError);
2708
setError(MSG_CDInvalid);
2713
if(line[0] == 'p' && line[1] == 'b') {
2715
if(!c || c == '.') {
2716
const struct MIMETYPE *mt;
2718
const char *suffix = 0;
2719
bool trailPercent = false;
2721
setError(MSG_AudioEmpty);
2724
if(cw->browseMode) {
2725
setError(MSG_AudioBrowse);
2729
setError(MSG_AudioDir);
2733
setError(MSG_AudioDB);
2740
suffix = strrchr(cw->fileName, '.');
2742
setError(MSG_NoSuffix);
2747
if(strlen(suffix) > 5) {
2748
setError(MSG_SuffixLong);
2751
mt = findMimeBySuffix(suffix);
2753
setError(MSG_SuffixBad, suffix);
2756
if(mt->program[strlen(mt->program) - 1] == '%')
2757
trailPercent = true;
2758
cmd = pluginCommand(mt, 0, suffix);
2759
rc = bufferToProgram(cmd, suffix, trailPercent);
2765
if(stringEqual(line, "rf")) {
2768
setError(MSG_NoRefresh);
2774
sprintf(newline, "%c %s", cmd, cw->fileName);
2775
debrowseSuffix(newline);
2779
if(stringEqual(line, "sc")) {
2788
if(stringEqual(line, "ub") || stringEqual(line, "et")) {
2789
ub = (line[0] == 'u');
2792
if(!cw->browseMode) {
2793
setError(MSG_NoBrowse);
2796
freeUndoLines(cw->map);
2798
nzFree(preWindow.map);
2800
cw->firstOpMode = undoable = false;
2801
cw->browseMode = false;
2804
debrowseSuffix(cw->fileName);
2805
cw->nlMode = cw->rnlMode;
2806
cw->dot = cw->r_dot, cw->dol = cw->r_dol;
2807
memcpy(cw->labels, cw->r_labels, sizeof (cw->labels));
2808
freeWindowLines(cw->map);
2809
cw->map = cw->r_map;
2812
for(i = 1; i <= cw->dol; ++i) {
2813
int ln = atoi(cw->map + i * LNWIDTH);
2814
removeHiddenNumbers(textLines[ln]);
2816
freeWindowLines(cw->r_map);
2821
freeJavaContext(cw->jsc);
2831
nzFree(cw->mailInfo);
2834
fileSize = apparentSize(context, false);
2838
if(stringEqual(line, "ip")) {
2843
if(!cw->iplist || cw->iplist[0] == -1) {
2847
for(i = 0; (ip = cw->iplist[i]) != NULL_IP; ++i) {
2848
puts(tcp_ip_dots(ip));
2854
if(stringEqual(line, "f/") || stringEqual(line, "w/")) {
2858
setError(MSG_NoRefresh);
2861
t = strrchr(cw->fileName, '/');
2863
setError(MSG_NoSlash);
2868
setError(MSG_YesSlash);
2871
sprintf(newline, "%c `%s", cmd, t);
2875
if(line[0] == 'f' && line[2] == 0 &&
2876
(line[1] == 'd' || line[1] == 'k' || line[1] == 't')) {
2880
if(!cw->browseMode) {
2881
setError(MSG_NoBrowse);
2885
s = cw->ft, t = MSG_NoTitle;
2887
s = cw->fd, t = MSG_NoDesc;
2889
s = cw->fk, t = MSG_NoKeywords;
2897
if(line[0] == 's' && line[1] == 'm') {
2898
const char *t = line + 2;
2907
account = strtol(t, (char **)&t, 10);
2910
return sendMailCurrent(account, dosig);
2912
setError(MSG_SMBadChar);
2917
if(stringEqual(line, "sg")) {
2918
searchStringsAll = true;
2920
i_puts(MSG_SubGlobal);
2924
if(stringEqual(line, "sl")) {
2925
searchStringsAll = false;
2927
i_puts(MSG_SubLocal);
2931
if(stringEqual(line, "ci")) {
2932
caseInsensitive = true;
2934
i_puts(MSG_CaseIns);
2938
if(stringEqual(line, "cs")) {
2939
caseInsensitive = false;
2941
i_puts(MSG_CaseSen);
2945
if(stringEqual(line, "dr")) {
2948
i_puts(MSG_DirReadonly);
2952
if(stringEqual(line, "dw")) {
2955
i_puts(MSG_DirWritable);
2959
if(stringEqual(line, "dx")) {
2966
if(stringEqual(line, "hr")) {
2967
allowRedirection ^= 1;
2968
if(helpMessagesOn || debugLevel >= 1)
2969
i_puts(allowRedirection + MSG_RedirectionOff);
2973
if(stringEqual(line, "iu")) {
2975
if(helpMessagesOn || debugLevel >= 1)
2976
i_puts(iuConvert + MSG_IUConvertOff);
2980
if(stringEqual(line, "sr")) {
2982
if(helpMessagesOn || debugLevel >= 1)
2983
i_puts(sendReferrer + MSG_RefererOff);
2987
if(stringEqual(line, "js")) {
2989
if(helpMessagesOn || debugLevel >= 1)
2990
i_puts(allowJS + MSG_JavaOff);
2994
if(stringEqual(line, "bd")) {
2996
if(helpMessagesOn || debugLevel >= 1)
2997
i_puts(binaryDetect + MSG_BinaryIgnore);
3001
if(stringEqual(line, "lna")) {
3003
if(helpMessagesOn || debugLevel >= 1)
3004
i_puts(listNA + MSG_ListControl);
3008
if(line[0] == 'f' && line[1] == 'm' &&
3009
line[2] && strchr("pad", line[2]) && !line[3]) {
3015
if(helpMessagesOn || debugLevel >= 1) {
3017
i_puts(MSG_PassiveMode);
3019
i_puts(MSG_ActiveMode);
3021
i_puts(MSG_PassActMode);
3026
if(stringEqual(line, "vs")) {
3027
verifyCertificates ^= 1;
3028
if(helpMessagesOn || debugLevel >= 1)
3029
i_puts(verifyCertificates + MSG_CertifyOff);
3030
ssl_verify_setting();
3034
if(stringEqual(line, "hf")) {
3035
showHiddenFiles ^= 1;
3036
if(helpMessagesOn || debugLevel >= 1)
3037
i_puts(showHiddenFiles + MSG_HiddenOff);
3041
if(stringEqual(line, "tn")) {
3042
textAreaDosNewlines ^= 1;
3043
if(helpMessagesOn || debugLevel >= 1)
3044
i_puts(textAreaDosNewlines + MSG_AreaUnix);
3048
if(stringEqual(line, "eo")) {
3051
i_puts(MSG_MarkOff);
3055
if(stringEqual(line, "el")) {
3058
i_puts(MSG_MarkList);
3062
if(stringEqual(line, "ep")) {
3070
return 2; /* no change */
3073
/* Return the number of unbalanced punctuation marks.
3074
* This is used by the next routine. */
3076
unbalanced(char c, char d, int ln, int *back_p, int *for_p)
3077
{ /* result parameters */
3079
char *p = (char *)fetchLine(ln, 1);
3081
int backward, forward;
3087
for(t = p; *t != '\n'; ++t) {
3090
if(*t == d && open) {
3099
backward = forward = 0;
3100
for(t = p; *t != '\n'; ++t) {
3112
/* Find the line that balances the unbalanced punctuation. */
3114
balanceLine(const char *line)
3116
char c, d; /* open and close */
3118
static char openlist[] = "{([<`";
3119
static char closelist[] = "})]>'";
3120
static const char alllist[] = "{}()[]<>`'";
3123
int i, direction, forward, backward;
3126
if(!strchr(alllist, c) || line[1]) {
3127
setError(MSG_BalanceChar, alllist);
3130
if(t = strchr(openlist, c)) {
3131
d = closelist[t - openlist];
3135
t = strchr(closelist, d);
3136
c = openlist[t - closelist];
3139
unbalanced(c, d, endRange, &backward, &forward);
3141
if((level = forward) == 0) {
3142
setError(MSG_BalanceNoOpen, c);
3146
if((level = backward) == 0) {
3147
setError(MSG_BalanceNoOpen, d);
3153
/* Look for anything unbalanced, probably a brace. */
3154
for(i = 0; i <= 2; ++i) {
3157
unbalanced(c, d, endRange, &backward, &forward);
3158
if(backward && forward) {
3159
setError(MSG_BalanceAmbig, c, d, c, d);
3162
level = backward + forward;
3171
setError(MSG_BalanceNothing);
3174
} /* explicit character passed in, or look for one */
3176
selected = (direction > 0 ? c : d);
3178
/* search for the balancing line */
3180
while((i += direction) > 0 && i <= cw->dol) {
3181
unbalanced(c, d, i, &backward, &forward);
3182
if(direction > 0 && backward >= level ||
3183
direction < 0 && forward >= level) {
3188
level += (forward - backward) * direction;
3189
} /* loop over lines */
3191
setError(MSG_Unbalanced, selected);
3195
/* Unfold the buffer into one long, allocated string. */
3197
unfoldBuffer(int cx, bool cr, char **data, int *len)
3202
int size = apparentSize(cx, false);
3205
w = sessionList[cx].lw;
3207
setError(MSG_SessionBrowse, cx);
3211
setError(MSG_SessionDir, cx);
3216
/* a few bytes more, just for safety */
3217
buf = allocMem(size + 4);
3219
for(ln = 1; ln <= w->dol; ++ln) {
3220
pst line = fetchLineContext(ln, -1, cx);
3221
l = pstLength(line) - 1;
3223
memcpy(buf, line, l);
3228
if(l && buf[-2] == '\r')
3232
} /* loop over lines */
3233
if(w->dol && w->nlMode) {
3240
} /* unfoldBuffer */
3246
char *a = initString(&a_l);
3248
char c, *p, *s, *t, *q, *line, *h;
3249
int j, k = 0, tagno;
3252
if(cw->browseMode && endRange) {
3254
line = (char *)fetchLine(endRange, -1);
3255
for(p = line; (c = *p) != '\n'; ++p) {
3256
if(c != InternalCodeChar)
3258
if(!isdigitByte(p[1]))
3260
j = strtol(p + 1, &s, 10);
3265
findField(line, 0, k, 0, 0, &tagno, &h, &ev);
3267
continue; /* should never happen */
3269
click = tagHandler(tagno, "onclick");
3270
dclick = tagHandler(tagno, "ondblclick");
3272
/* find the closing brace */
3273
/* It might not be there, could be on the next line. */
3274
for(s = p + 1; (c = *s) != '\n'; ++s)
3275
if(c == InternalCodeChar && s[1] == '0' && s[2] == '}')
3277
/* Ok, everything between p and s exclusive is the description */
3280
if(stringEqual(h, "#")) {
3285
if(memEqualCI(h, "mailto:", 7)) {
3286
stringAndBytes(&a, &a_l, p + 1, s - p - 1);
3287
stringAndChar(&a, &a_l, ':');
3289
t = s + strcspn(s, "?");
3290
stringAndBytes(&a, &a_l, s, t - s);
3291
stringAndChar(&a, &a_l, '\n');
3296
stringAndString(&a, &a_l, "<a href=");
3298
if(memEqualCI(h, "javascript:", 11)) {
3299
stringAndString(&a, &a_l, "javascript:>\n");
3300
} else if(!*h && (click | dclick)) {
3302
sprintf(buf, "%s>\n", click ? "onclick" : "ondblclick");
3303
stringAndString(&a, &a_l, buf);
3306
stringAndString(&a, &a_l, h);
3307
stringAndString(&a, &a_l, ">\n");
3311
/* next line is the description of the bookmark */
3312
stringAndBytes(&a, &a_l, p + 1, s - p - 1);
3313
stringAndString(&a, &a_l, "\n</a>\n");
3314
} /* loop looking for hyperlinks */
3317
if(!a_l) { /* nothing found yet */
3319
setError(MSG_NoFileName);
3322
h = cloneString(cw->fileName);
3324
stringAndString(&a, &a_l, "<a href=");
3325
stringAndString(&a, &a_l, h);
3326
stringAndString(&a, &a_l, ">\n");
3327
s = (char *)getDataURL(h);
3330
t = s + strcspn(s, "\1?#");
3331
if(t > s && t[-1] == '/')
3334
q = strrchr(s, '/');
3337
stringAndBytes(&a, &a_l, s, t - s);
3338
stringAndString(&a, &a_l, "\n</a>\n");
3341
/* using the filename */
3348
struct ebWindow *pw = &preWindow;
3353
memcpy(pw->labels, cw->labels, 26 * sizeof (int));
3354
pw->binMode = cw->binMode;
3355
pw->nlMode = cw->nlMode;
3356
pw->dirMode = cw->dirMode;
3358
if(!cw->map || !stringEqual(pw->map, cw->map)) {
3363
if(cw->map && !pw->map)
3364
pw->map = cloneString(cw->map);
3367
/* Run the entered edbrowse command.
3368
* This is indirectly recursive, as in g/x/d
3369
* Pass in the ed command, and return success or failure.
3370
* We assume it has been turned into a C string.
3371
* This means no embeded nulls.
3372
* If you want to use null in a search or substitute, use \0. */
3374
runCommand(const char *line)
3376
int i, j, n, writeMode;
3378
void *ev; /* event variables */
3380
bool postSpace = false, didRange = false;
3382
int cx = 0; /* numeric suffix as in s/x/y/3 or w2 */
3385
static char newline[MAXTTYLINE];
3386
static char *allocatedLine = 0;
3389
nzFree(allocatedLine);
3392
nzFree(currentReferrer);
3393
currentReferrer = cloneString(cw->fileName);
3394
js_redirects = false;
3401
/* Allow things like comment, or shell escape, but not if we're
3402
* in the midst of a global substitute, as in g/x/ !echo hello world */
3406
return shellEscape(line + 1);
3408
/* Watch for successive q commands. */
3409
lastq = lastqq, lastqq = 0;
3412
/* special 2 letter commands - most of these change operational modes */
3413
j = twoLetter(line, &line);
3418
startRange = endRange = cw->dot; /* default range */
3419
/* Just hit return to read the next line. */
3423
++startRange, ++endRange;
3424
if(endRange > cw->dol) {
3425
setError(MSG_EndBuffer);
3440
startRange = cw->dot;
3443
if(first == 'j' || first == 'J') {
3445
endRange = startRange + 1;
3446
if(endRange > cw->dol) {
3447
setError(MSG_EndJoin);
3453
startRange = endRange = cw->dol;
3455
if(first == 'w' || first == 'v' || first == 'g' &&
3456
line[1] && strchr(valid_delim, line[1])) {
3465
if(!getRangePart(line, &startRange, &line))
3466
return (globSub = false);
3467
endRange = startRange;
3468
if(line[0] == ',') {
3470
endRange = cw->dol; /* new default */
3472
if(first && strchr(valid_laddr, first)) {
3473
if(!getRangePart(line, &endRange, &line))
3474
return (globSub = false);
3478
if(endRange < startRange) {
3479
setError(MSG_BadRange);
3483
/* change uc into a substitute command, converting the whole line */
3486
if((first == 'u' || first == 'l' || first == 'm') && line[1] == 'c' &&
3488
sprintf(newline, "s/.*/%cc/", first);
3491
/* Breakline is actually a substitution of lines. */
3492
if(stringEqual(line, "bl")) {
3494
setError(MSG_BreakDir);
3498
setError(MSG_BreakDB);
3501
if(cw->browseMode) {
3502
setError(MSG_BreakBrowse);
3508
/* get the command */
3516
if(!strchr(valid_cmd, cmd)) {
3517
setError(MSG_UnknownCommand, cmd);
3518
return (globSub = false);
3522
writeMode = O_TRUNC;
3523
if(cmd == 'w' && first == '+')
3524
writeMode = O_APPEND, first = *++line;
3526
if(cw->dirMode && !strchr(dir_cmd, cmd)) {
3527
setError(MSG_DirCommand, icmd);
3528
return (globSub = false);
3530
if(cw->sqlMode && !strchr(sql_cmd, cmd)) {
3531
setError(MSG_BadRange, icmd);
3532
return (globSub = false);
3534
if(cw->browseMode && !strchr(browse_cmd, cmd)) {
3535
setError(MSG_BrowseCommand, icmd);
3536
return (globSub = false);
3538
if(startRange == 0 && !strchr(zero_cmd, cmd)) {
3539
setError(MSG_AtLine0);
3540
return (globSub = false);
3542
while(isspaceByte(first))
3543
postSpace = true, first = *++line;
3544
if(strchr(spaceplus_cmd, cmd) && !postSpace && first) {
3546
while(isdigitByte(*s))
3549
setError(MSG_NoSpaceAfter);
3550
return (globSub = false);
3553
if(globSub && !strchr(global_cmd, cmd)) {
3554
setError(MSG_GlobalCommand, icmd);
3555
return (globSub = false);
3558
/* move/copy destination, the third address */
3559
if(cmd == 't' || cmd == 'm') {
3563
if(!strchr(valid_laddr, first)) {
3564
setError(MSG_BadDest);
3565
return (globSub = false);
3567
if(!getRangePart(line, &destLine, &line))
3568
return (globSub = false);
3570
} /* was there something after m or t */
3574
/* Any command other than a lone b resets history */
3575
if(cmd != 'b' || first) {
3576
fetchHistory(0, 0); /* reset history */
3579
/* env variable and wild card expansion */
3580
if(strchr("brewf", cmd) && first && !isURL(line) && !isSQL(line)) {
3581
if(cmd != 'r' || !cw->sqlMode) {
3582
if(!envFile(line, &line))
3589
if(isdigitByte(first)) {
3590
last_z = strtol(line, (char **)&line, 10);
3595
startRange = endRange + 1;
3596
endRange = startRange;
3597
if(startRange > cw->dol) {
3598
setError(MSG_LineHigh);
3602
endRange += last_z - 1;
3603
if(endRange > cw->dol)
3607
/* the a+ feature, when you thought you were in append mode */
3609
if(stringEqual(line, "+"))
3616
if(first && strchr(nofollow_cmd, cmd)) {
3617
setError(MSG_TextAfter, icmd);
3618
return (globSub = false);
3627
if(helpMessagesOn ^= 1)
3638
if(strchr("Llpn", cmd)) {
3639
for(i = startRange; i <= endRange; ++i) {
3649
printf("%d\n", endRange);
3654
return balanceLine(line);
3658
struct ebWindow *uw = &undoWindow;
3660
if(!cw->firstOpMode) {
3661
setError(MSG_NoUndo);
3664
/* swap, so we can undo our undo, if need be */
3665
i = uw->dot, uw->dot = cw->dot, cw->dot = i;
3666
i = uw->dol, uw->dol = cw->dol, cw->dol = i;
3667
for(j = 0; j < 26; ++j) {
3668
i = uw->labels[j], uw->labels[j] = cw->labels[j], cw->labels[j] = i;
3670
swapmap = uw->map, uw->map = cw->map, cw->map = swapmap;
3675
if(!islowerByte(first) || line[1]) {
3676
setError(MSG_EnterKAZ);
3679
if(startRange < endRange) {
3680
setError(MSG_RangeLabel);
3683
cw->labels[first - 'a'] = endRange;
3688
/* Find suffix, as in 27,59w2 */
3690
cx = stringIsNum(line);
3692
setError((cmd == '^') ? MSG_Backup0 : MSG_Session0);
3708
setError(MSG_QAfter);
3712
saveSubstitutionStrings();
3717
/* look around for another active session */
3719
if(++cx == MAXSESSION)
3723
if(!sessionList[cx].lw)
3727
} /* loop over sessions */
3736
s = sessionList[cx].lw->fileName;
3740
i_printf(MSG_NoFile);
3741
if(sessionList[cx].lw->binMode)
3742
i_printf(MSG_BinaryBrackets);
3745
} /* another session */
3748
setError(MSG_DirRename);
3752
setError(MSG_TableRename);
3755
nzFree(cw->fileName);
3756
cw->fileName = cloneString(line);
3762
i_printf(MSG_NoFile);
3764
i_printf(MSG_BinaryBrackets);
3770
if(cx) { /* write to another buffer */
3771
if(writeMode == O_APPEND) {
3772
setError(MSG_BufferAppend);
3775
return writeContext(cx);
3778
line = cw->fileName;
3780
setError(MSG_NoFileSpecified);
3783
if(cw->dirMode && stringEqual(line, cw->fileName)) {
3784
setError(MSG_NoDirWrite);
3787
if(cw->sqlMode && stringEqual(line, cw->fileName)) {
3788
setError(MSG_NoDBWrite);
3791
return writeFile(line, writeMode);
3794
if(cmd == '^') { /* back key, pop the stack */
3796
setError(MSG_ArrowAfter);
3802
struct ebWindow *prev = cw->prev;
3804
setError(MSG_NoPrevious);
3807
saveSubstitutionStrings();
3808
if(!cxQuit(context, 1))
3810
sessionList[context].lw = cw = prev;
3811
restoreSubstitutionStrings(cw);
3818
if(cmd == 'M') { /* move this to another session */
3820
setError(MSG_MAfter);
3824
setError(MSG_NoDestSession);
3828
setError(MSG_NoBackup);
3833
if(cxActive(cx) && !cxQuit(cx, 2))
3835
/* Magic with pointers, hang on to your hat. */
3836
sessionList[cx].fw = sessionList[cx].lw = cw;
3846
if(!cxQuit(context, 0))
3848
if(!(a = showLinks()))
3850
freeUndoLines(cw->map);
3852
nzFree(preWindow.map);
3854
cw->firstOpMode = cw->changeMode = false;
3859
rc = addTextToBuffer((pst) a, strlen(a), 0);
3861
undoable = cw->changeMode = false;
3862
fileSize = apparentSize(context, false);
3866
if(cmd == '<') { /* run a function */
3867
return runEbFunction(line);
3871
/* go to a file in a directory listing */
3872
if(cmd == 'g' && cw->dirMode && !first) {
3873
char *p, *dirline, *endline;
3874
if(endRange > startRange) {
3875
setError(MSG_RangeG);
3878
p = (char *)fetchLine(endRange, -1);
3879
j = pstLength((pst) p);
3881
p[j] = 0; /* temporary */
3882
dirline = makeAbsPath(p);
3887
/* I don't think we need to make a copy here. */
3891
/* g in directory mode */
3900
i_printf(MSG_SessionX, context);
3903
/* more e to come */
3906
if(cmd == 'g') { /* see if it's a go command */
3909
bool click, dclick, over;
3910
bool jsh, jsgo, jsdead;
3914
j = strtol(line, (char **)&s, 10);
3920
jsh = jsgo = nogo = false;
3921
jsdead = cw->jsdead;
3924
click = dclick = over = false;
3926
if(endRange > startRange) {
3927
setError(MSG_RangeG);
3930
p = (char *)fetchLine(endRange, -1);
3932
findField(p, 0, j, &n, 0, &tagno, &h, &ev);
3933
debugPrint(5, "findField returns %d, %s", tagno, h);
3935
fieldNumProblem(1, 'g', j, n, n);
3938
jsh = memEqualCI(h, "javascript:", 11);
3940
over = tagHandler(tagno, "onmouseover");
3941
click = tagHandler(tagno, "onclick");
3942
dclick = tagHandler(tagno, "ondblclick");
3947
nogo = stringEqual(h, "#");
3949
debugPrint(5, "go%d nogo%d jsh%d dead%d", jsgo, nogo, jsh, jsdead);
3950
debugPrint(5, "click %d dclick %d over %d", click, dclick, over);
3953
i_puts(MSG_NJNoAction);
3955
i_puts(MSG_NJGoing);
3958
line = allocatedLine = h;
3964
/* The program might depend on the mouseover code running first */
3966
rc = handlerGo(ev, "onmouseover");
3972
/* This is the only handler where false tells the browser to do something else. */
3974
set_property_string(jwin, "status", h);
3976
rc = handlerGo(ev, "onclick");
3984
rc = javaParseExecute(jwin, h, 0, 0);
3996
/* Some shorthand, like s,2 to split the line at the second comma */
3998
strcpy(newline, "//%");
4000
} else if(strchr(",.;:!?)-\"", first) &&
4001
(!line[1] || isdigitByte(line[1]) && !line[2])) {
4003
esc[0] = esc[1] = 0;
4004
if(first == '.' || first == '?')
4006
sprintf(newline, "/%s%c +/%c\\n%s%s",
4007
esc, first, first, (line[1] ? "/" : ""), line + 1);
4008
debugPrint(7, "shorthand regexp %s", newline);
4015
if((cmd == 'i' || cmd == 's') && first) {
4019
cx = strtol(s, (char **)&s, 10);
4021
if(c && (strchr(valid_delim, c) || cmd == 'i' && strchr("*<?=", c))) {
4022
if(!cw->browseMode && (cmd == 'i' || cx)) {
4023
setError(MSG_NoBrowse);
4026
if(endRange > startRange && cmd == 'i') {
4027
setError(MSG_RangeI, c);
4030
if(cmd == 'i' && strchr("?=<*", c)) {
4036
debugPrint(5, "scmd = %c", scmd);
4038
p = (char *)fetchLine(cw->dot, -1);
4044
findInputField(p, j, cx, &n, &realtotal, &tagno);
4045
debugPrint(5, "findField returns %d.%d", n, tagno);
4047
fieldNumProblem((c == '*' ? 2 : 0), 'i', cx, n, realtotal);
4051
infShow(tagno, line);
4055
bool fromfile = false;
4058
return (globSub = false);
4062
setError(MSG_NoFileSpecified);
4065
n = stringIsNum(line);
4069
if(!cxCompare(n) || !cxActive(n))
4071
dol = sessionList[n].lw->dol;
4073
setError(MSG_BufferXEmpty, n);
4077
setError(MSG_BufferXLines, n);
4080
p = (char *)fetchLineContext(1, 1, n);
4081
plen = pstLength((pst) p);
4082
if(plen > sizeof (newline))
4083
plen = sizeof (newline);
4084
memcpy(newline, p, plen);
4090
if(!envFile(line, &line))
4092
fd = open(line, O_RDONLY | O_TEXT);
4094
setError(MSG_NoOpen, line);
4097
n = read(fd, newline, sizeof (newline));
4100
setError(MSG_NoRead, line);
4104
for(j = 0; j < n; ++j) {
4105
if(newline[j] == 0) {
4106
setError(MSG_InputNull, line);
4109
if(newline[j] == '\r' && !fromfile &&
4110
j < n - 1 && newline[j + 1] != '\n') {
4111
setError(MSG_InputCR);
4114
if(newline[j] == '\r' || newline[j] == '\n')
4117
if(j == sizeof (newline)) {
4118
setError(MSG_FirstLineLong, line);
4128
if(!infPush(tagno, &allocatedLine))
4132
/* No url means it was a reset button */
4135
line = allocatedLine;
4142
setError(MSG_TextAfter, icmd);
4148
if(cmd == 'e' || cmd == 'b' && first && first != '#') {
4149
if(cw->fileName && !noStack && sameURL(line, cw->fileName)) {
4150
if(stringEqual(line, cw->fileName)) {
4151
setError(MSG_AlreadyInBuffer);
4154
/* Same url, but a different #section */
4155
s = strchr(line, '#');
4156
if(!s) { /* no section specified */
4168
/* Different URL, go get it. */
4169
/* did you make changes that you didn't write? */
4170
if(!cxQuit(context, 0))
4172
freeUndoLines(cw->map);
4174
nzFree(preWindow.map);
4176
cw->firstOpMode = cw->changeMode = false;
4177
startRange = endRange = 0;
4178
changeFileName = 0; /* should already be zero */
4180
cw = w; /* we might wind up putting this back */
4181
/* Check for sendmail link */
4182
if(cmd == 'b' && memEqualCI(line, "mailto:", 7)) {
4183
char *addr, *subj, *body;
4186
decodeMailURL(line, &addr, &subj, &body);
4188
ql += 4; /* to:\n */
4189
ql += subj ? strlen(subj) : 5;
4190
ql += 9; /* subject:\n */
4193
q = allocMem(ql + 1);
4194
sprintf(q, "to:%s\nSubject:%s\n%s", addr, subj ? subj : "Hello",
4196
j = addTextToBuffer((pst) q, ql, 0);
4202
i_puts(MSG_MailHowto);
4204
cw->fileName = cloneString(line);
4207
if(icmd == 'g' && !nogo && isURL(line))
4208
debugPrint(2, "*%s", line);
4209
j = readFile(line, "");
4211
w->firstOpMode = w->changeMode = false;
4214
/* Don't push a new session if we were trying to read a url,
4215
* and didn't get anything. This is a feature that I'm
4216
* not sure if I really like. */
4217
if(!serverData && (isURL(line) || isSQL(line))) {
4220
if(noStack && cw->prev) {
4239
if(changeFileName) {
4240
nzFree(w->fileName);
4241
w->fileName = changeFileName;
4244
/* Some files we just can't browse */
4245
if(!cw->dol || cw->dirMode)
4247
if(cw->binMode && !stringIsPDF(cw->fileName))
4255
if(!cw->browseMode) {
4256
if(cw->binMode && !stringIsPDF(cw->fileName)) {
4257
setError(MSG_BrowseBinary);
4261
setError(MSG_BrowseEmpty);
4265
debugPrint(1, "%d", fileSize);
4268
if(!browseCurrentBuffer()) {
4274
setError(MSG_BrowseAlready);
4279
if(!refreshDelay(newloc_d, newlocation)) {
4280
nzFree(newlocation);
4282
} else if(fetchHistory(cw->fileName, newlocation) < 0) {
4283
nzFree(newlocation);
4288
noStack = newloc_rf;
4289
nzFree(allocatedLine);
4290
line = allocatedLine = newlocation;
4291
debugPrint(2, "redirect %s", line);
4296
i_puts(MSG_RedirectionInterrupted);
4303
/* Jump to the #section, if specified in the url */
4304
s = strchr(line, '#');
4308
/* Sometimes there's a 3 in the midst of a long url,
4309
* probably with post data. It really screws things up.
4310
* Here is a kludge to avoid this problem.
4311
* Some day I need to figure this out. */
4314
/* Print the file size before we print the line. */
4316
debugPrint(1, "%d", fileSize);
4319
for(i = 1; i <= cw->dol; ++i) {
4320
char *p = (char *)fetchLine(i, -1);
4321
if(lineHasTag(p, s)) {
4327
setError(MSG_NoLable2, s);
4333
if(cmd == 'g' || cmd == 'v') {
4334
return doGlobal(line);
4337
if(cmd == 'm' || cmd == 't') {
4343
rc = infReplace(tagno, line, 1);
4348
if(cw->browseMode) {
4349
setError(MSG_BrowseI);
4353
--startRange, --endRange;
4357
delText(startRange, endRange);
4358
endRange = --startRange;
4364
setError(MSG_InsertFunction);
4369
rc = sqlAddRows(endRange);
4373
cw->dot = endRange + j;
4374
else if(!endRange && cw->dol)
4380
return inputLinesIntoBuffer();
4383
if(cmd == 'd' || cmd == 'D') {
4389
j = sqlDelRows(startRange, endRange);
4392
delText(startRange, endRange);
4402
if(cmd == 'j' || cmd == 'J') {
4408
return readContext(cx);
4410
if(cw->sqlMode && !isSQL(line)) {
4411
j = 0; /* count the dashes */
4412
s = strchr(line, '=');
4415
for(q = line; q < s; ++q)
4416
if(!isalnumByte(*q))
4422
if(stringEqual(s, "*"))
4425
if(*s == '-' && s > line && s[1]) {
4437
setError(MSG_readText);
4441
strcpy(newline, cw->fileName);
4442
strcpy(strchr(newline, ']') + 1, line);
4445
j = readFile(line, "");
4450
setError(MSG_NoFileSpecified);
4455
j = substituteText(line);
4465
setError(MSG_CNYI, icmd);
4466
return (globSub = false);
4470
edbrowseCommand(const char *line, bool script)
4473
globSub = intFlag = false;
4477
rc = runCommand(line);
4479
debugPrint(1, "%d", fileSize);
4483
showErrorConditional(cmd);
4487
struct ebWindow *pw = &preWindow;
4488
struct ebWindow *uw = &undoWindow;
4489
debugPrint(6, "undoable");
4492
if(uw->map && pw->map && stringEqual(uw->map, pw->map)) {
4495
debugPrint(6, "success freeUndo");
4496
freeUndoLines(pw->map);
4500
memcpy(uw->labels, pw->labels, 26 * sizeof (int));
4501
uw->binMode = pw->binMode;
4502
uw->nlMode = pw->nlMode;
4503
uw->dirMode = pw->dirMode;
4507
} /* edbrowseCommand */
4509
/* Take some text, usually empty, and put it in a side buffer. */
4511
sideBuffer(int cx, const char *text, int textlen,
4512
const char *bufname, bool autobrowse)
4519
for(cx = 1; cx < MAXSESSION; ++cx)
4520
if(!sessionList[cx].lw)
4522
if(cx == MAXSESSION) {
4523
i_puts(MSG_NoBufferExtraWindow);
4527
cxSwitch(cx, false);
4529
cw->fileName = cloneString(bufname);
4530
debrowseSuffix(cw->fileName);
4533
textlen = strlen(text);
4535
cw->binMode = looksBinary(text, textlen);
4538
rc = addTextToBuffer((pst) text, textlen, 0);
4540
i_printf(MSG_BufferPreload, cx);
4542
/* This is html; we need to render it.
4543
* I'm disabling javascript in this window.
4545
* Because this window is being created by javascript,
4546
* and if we call more javascript, well, I don't think
4547
* any of that code is reentrant.
4548
* Smells like a disaster in the making. */
4550
browseCurrentBuffer();
4552
} /* browse the side window */
4554
/* back to original context */
4555
cxSwitch(svcx, false);
4559
/* Bailing wire and duct tape, here it comes.
4560
* You'd never know I was a professional programmer. :-)
4561
* Ok, the text buffer, that you edit, is the final arbiter on most
4562
* (but not all) input fields. And when javascript changes
4563
* one of these values, it is copied back to the text buffer,
4564
* where you can see it, and modify it as you like.
4565
* But what happens if that javascript is running while the html is parsed,
4566
* and before the page even exists?
4567
* I hadn't thought of that.
4568
* The following is a cache of input fields that have been changed,
4569
* before we even had a chance to render the page. */
4571
struct inputChange {
4572
struct inputChange *next, *prev;
4576
static struct listHead inputChangesPending = {
4577
&inputChangesPending, &inputChangesPending
4579
static struct inputChange *ic;
4582
browseCurrentBuffer(void)
4584
char *rawbuf, *newbuf;
4586
bool rc, remote = false, do_ip = false, ispdf = false;
4587
bool save_ch = cw->changeMode;
4591
remote = isURL(cw->fileName);
4592
ispdf = stringIsPDF(cw->fileName);
4595
/* I'm trusting the pdf suffix, and not looking inside. */
4598
/* A mail message often contains lots of html tags,
4599
* so we need to check for email headers first. */
4600
else if(!remote && emailTest())
4605
setError(MSG_Unbrowsable);
4609
if(!unfoldBuffer(context, false, &rawbuf, &rawsize))
4610
return false; /* should never happen */
4612
/* expand pdf using pdftohtml */
4613
/* http://rpmfind.net/linux/RPM/suse/updates/10.0/i386/rpm/i586/pdftohtml-0.36-130.9.i586.html */
4618
if(!memoryOutToFile(edbrowseTempPDF, rawbuf, rawsize,
4619
MSG_TempNoCreate2, MSG_TempNoWrite)) {
4624
unlink(edbrowseTempHTML);
4625
j = strlen(edbrowseTempPDF);
4626
cmd = allocMem(50 + j);
4627
sprintf(cmd, "pdftohtml -i -noframes %s >/dev/null 2>&1",
4631
if(fileSizeByName(edbrowseTempHTML) <= 0) {
4632
setError(MSG_NoPDF, edbrowseTempPDF);
4635
rc = fileIntoMemory(edbrowseTempHTML, &rawbuf, &rawsize);
4638
iuReformat(rawbuf, rawsize, &tbuf, &tlen);
4647
prepareForBrowse(rawbuf, rawsize);
4649
/* No harm in running this code in mail client, but no help either,
4650
* and it begs for bugs, so leave it out. */
4652
freeUndoLines(cw->map);
4654
nzFree(preWindow.map);
4656
cw->firstOpMode = false;
4658
/* There shouldn't be anything in the input pending list, but clear
4659
* it out, just to be safe. */
4660
freeList(&inputChangesPending);
4664
newbuf = emailParse(rawbuf);
4670
if(memEqualCI(newbuf, "<html>\n", 7) && allowRedirection) {
4671
/* double browse, mail then html */
4674
rawsize = strlen(rawbuf);
4675
prepareForBrowse(rawbuf, rawsize);
4680
cw->jsdead = !javaOK(cw->fileName);
4682
cw->jsc = createJavaContext();
4683
nzFree(newlocation); /* should already be 0 */
4685
newbuf = htmlParse(rawbuf, remote);
4688
cw->browseMode = true;
4689
cw->rnlMode = cw->nlMode;
4691
cw->r_dot = cw->dot, cw->r_dol = cw->dol;
4692
cw->dot = cw->dol = 0;
4693
cw->r_map = cw->map;
4695
memcpy(cw->r_labels, cw->labels, sizeof (cw->labels));
4696
memset(cw->labels, 0, sizeof (cw->labels));
4698
rc = addTextToBuffer((pst) newbuf, j, 0);
4700
cw->firstOpMode = undoable = false;
4701
cw->changeMode = save_ch;
4705
j = strlen(cw->fileName);
4706
cw->fileName = reallocMem(cw->fileName, j + 8);
4707
strcat(cw->fileName, ".browse");
4712
} /* should never happen */
4713
fileSize = apparentSize(context, true);
4716
/* apply any input changes pending */
4717
foreach(ic, inputChangesPending)
4718
updateFieldInBuffer(ic->tagno, ic->value, 0, false);
4719
freeList(&inputChangesPending);
4725
} /* browseCurrentBuffer */
4728
locateTagInBuffer(int tagno, int *ln_p, char **p_p, char **s_p, char **t_p)
4736
foreachback(ic, inputChangesPending) {
4737
if(ic->tagno != tagno)
4740
*t_p = ic->value + strlen(ic->value);
4741
/* we don't need to set the others in this special case */
4746
/* still rendering the page */
4747
sprintf(search, "%c%d<", InternalCodeChar, tagno);
4748
sprintf(searchend, "%c0>", InternalCodeChar);
4750
for(ln = 1; ln <= cw->dol; ++ln) {
4751
p = (char *)fetchLine(ln, -1);
4752
for(s = p; (c = *s) != '\n'; ++s) {
4753
if(c != InternalCodeChar)
4755
if(!memcmp(s, search, n))
4759
continue; /* not here, try next line */
4760
s = strchr(s, '<') + 1;
4761
t = strstr(s, searchend);
4763
i_printfExit(MSG_NoClosingLine, ln);
4772
} /* locateTagInBuffer */
4774
/* Update an input field in the current buffer.
4775
* The input field may not be here, if you've deleted some lines. */
4777
updateFieldInBuffer(int tagno, const char *newtext, int notify, bool required)
4779
int ln, idx, n, plen;
4780
char *p, *s, *t, *new;
4781
char newidx[LNWIDTH + 1];
4783
if(parsePage) { /* we don't even have the buffer yet */
4784
ic = allocMem(sizeof (struct inputChange) + strlen(newtext));
4786
strcpy(ic->value, newtext);
4787
addToListBack(&inputChangesPending, ic);
4791
if(locateTagInBuffer(tagno, &ln, &p, &s, &t)) {
4792
n = (plen = pstLength((pst) p)) + strlen(newtext) - (t - s);
4794
memcpy(new, p, s - p);
4795
strcpy(new + (s - p), newtext);
4796
memcpy(new + strlen(new), t, plen - (t - p));
4797
idx = textLinesCount++;
4798
sprintf(newidx, LNFORMAT, idx);
4799
memcpy(cw->map + ln * LNWIDTH, newidx, LNWIDTH);
4800
textLines[idx] = (pst) new;
4801
/* In case a javascript routine updates a field that you weren't expecting */
4805
i_printf(MSG_LineUpdated, ln);
4806
cw->firstOpMode = true;
4812
i_printfExit(MSG_NoTagFound, tagno, newtext);
4813
} /* updateFieldInBuffer */
4815
/* This is the inverse of the above function, fetch instead of update. */
4817
getFieldFromBuffer(int tagno)
4821
if(locateTagInBuffer(tagno, &ln, &p, &s, &t)) {
4822
return pullString1(s, t);
4825
/* line has been deleted, revert to the reset value */
4827
} /* getFieldFromBuffer */
4830
fieldIsChecked(int tagno)
4833
char *p, *s, *t, *new;
4834
if(locateTagInBuffer(tagno, &ln, &p, &s, &t))
4837
} /* fieldIsChecked */