1/*
2 * Copyright (c) 1999 by The XFree86 Project, Inc.
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
17 * THE XFREE86 PROJECT BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
18 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
19 * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 * SOFTWARE.
21 *
22 * Except as contained in this notice, the name of the XFree86 Project shall
23 * not be used in advertising or otherwise to promote the sale, use or other
24 * dealings in this Software without prior written authorization from the
25 * XFree86 Project.
26 *
27 * Author: Paulo César Pereira de Andrade
28 */
29
30/* $XFree86: xc/programs/xedit/hook.c,v 1.9 2003/01/08 05:07:40 paulo Exp $ */
31
32/*
33 * This file is intended to be used to add all the necessary hooks to xedit
34 * emulate certain features of emacs (and other text editors) that are better
35 * kept only in xedit, to avoid unnecessary code in the Text Widget.
36 *
37 * The code here is not finished, and will probably be changed frequently.
38 */
39
40#include "xedit.h"
41#include "re.h"
42#include "util.h"
43#include <stdlib.h>
44#include <string.h>
45#include <ctype.h>
46
47/*
48 * Types
49 */
50typedef struct _ReplaceEntry {
51    hash_key *word;
52    struct _ReplaceEntry *next;
53    char *replace;
54} ReplaceEntry;
55
56typedef enum {
57    SubstituteDisabled,
58    SubstituteAsk,
59    SubstituteNo,
60    SubstituteYes
61} SubstitutionState;
62
63typedef struct _EditInfo {
64    /* Xedit regex data */
65    re_cod regex;
66    re_mat mats[10];
67
68    /* Last command entered */
69    char command[128];
70
71    /* String and flags used to compile regex */
72    char pattern[64];
73    char subst_pattern[64];
74    int pat_length;
75    int flags;
76
77    /* Substitution buffer */
78    char subst[64];
79    int soff, slen, sref;
80
81    /* For interactive substitution */
82    int callback;
83    Widget widget;
84    char *text_line;
85    SubstitutionState state;
86    XawTextPosition from, to, start, end, first, last;
87
88    /* Use if need to allocate a buffer to pass the entire line to reexec */
89    char *line;
90    long lsize;
91
92    /* Buffer to prepare replacement, if needs to expand backreferences */
93    char *buffer;
94    long bsize;
95} EditInfo;
96
97/*
98 * Prototypes
99 */
100static void ActionHook(Widget, XtPointer, String, XEvent*, String*, Cardinal*);
101static void AutoReplaceHook(Widget, String, XEvent*);
102static Bool StartAutoReplace(void);
103static char *ReplacedWord(char*, char*);
104static void AutoReplace(Widget, XEvent*);
105static void AutoReplaceCallback(Widget, XtPointer, XtPointer);
106
107static void SubstituteHook(Widget w, String action, XEvent *event);
108static void SubstituteCallback(Widget, XtPointer, XtPointer);
109
110/*
111 * Initialization
112 */
113#define STRTBLSZ	11
114static hash_table *replace_hash;
115static EditInfo einfo;
116extern Widget scratch;
117
118/*
119 * Implementation
120 */
121Bool
122StartHooks(XtAppContext app)
123{
124    static Bool first_time = True;
125
126    if (first_time) {
127	StartAutoReplace();
128	(void)XtAppAddActionHook(app, ActionHook, NULL);
129	first_time = False;
130
131	return (True);
132    }
133    return (False);
134}
135
136/*ARGSUSED*/
137static void
138ActionHook(Widget w, XtPointer client_data, String action, XEvent *event,
139	   String *params, Cardinal *num_params)
140{
141    AutoReplaceHook(w, action, event);
142    SubstituteHook(w, action, event);
143}
144
145/*** auto replace ***/
146struct {
147    Widget widget;
148    String text;
149    Cardinal length;
150    XawTextPosition left, right;
151    Bool replace;
152    Bool enabled;
153} auto_replace;
154
155static void
156AutoReplaceHook(Widget w, String action, XEvent *event)
157{
158    static Bool multiply;
159
160    if (w != textwindow || !auto_replace.enabled)
161	return;
162
163    if (auto_replace.widget != textwindow) {
164	if (auto_replace.replace) {
165	    auto_replace.replace = False;
166	    XtRemoveCallback(auto_replace.widget, XtNpositionCallback,
167			     AutoReplaceCallback, NULL);
168	}
169    }
170    else if (strcmp(action, "multiply") == 0) {
171	multiply = True;
172	return;
173    }
174    else if (strcmp(action, "numeric") == 0) {
175	if (multiply)
176	    return;
177    }
178    else if (strcmp(action, "insert-char") && strcmp(action, "newline") &&
179	strcmp(action, "newline-and-indent")) {
180	return;
181    }
182    multiply = False;
183
184    AutoReplace(w, event);
185}
186
187static Bool
188StartAutoReplace(void)
189{
190    Bool esc;
191    int len, llen, rlen, count = 0;
192    char ch, *tmp, *left, *right, *replace = app_resources.auto_replace;
193
194    if (!replace || !*replace)
195	return (False);
196
197    replace_hash = hash_new(STRTBLSZ, NULL);
198
199    left = XtMalloc(llen = 256);
200    right = XtMalloc(rlen = 256);
201    while (*replace) {
202	/* skip white spaces */
203	while (*replace && isspace(*replace))
204	    ++replace;
205	if (!*replace)
206	    break;
207
208	/* read left */
209	tmp = replace;
210	while (*replace && !isspace(*replace))
211	    ++replace;
212	len = replace - tmp;
213	if (len >= llen)
214	    left = XtRealloc(left, llen = len + 1);
215	strncpy(left, tmp, len);
216	left[len] = '\0';
217
218	/* skip white spaces */
219	while (*replace && isspace(*replace))
220	    ++replace;
221
222	/* read right */
223	len = 0;
224	esc = False;
225	while ((ch = *replace) != '\0') {
226	    ++replace;
227	    if (len + 2 >= rlen)
228		right = XtRealloc(right, rlen += 256);
229	    if (ch == '\\') {
230		if (esc)
231		    right[len++] = '\\';
232		esc = !esc;
233		continue;
234	    }
235	    else if (ch == '\n' && !esc)
236		break;
237	    else
238		right[len++] = ch;
239	    esc = False;
240	}
241	right[len] = '\0';
242
243	(void)ReplacedWord(left, right);
244	++count;
245    }
246    XtFree(left);
247    XtFree(right);
248
249    return (auto_replace.enabled = count > 0);
250}
251
252static char *
253ReplacedWord(char *word, char *replace)
254{
255    int length;
256    ReplaceEntry *entry;
257
258    length = strlen(word);
259    entry = (ReplaceEntry *)hash_check(replace_hash, word, length);
260    if (entry == NULL && replace != NULL) {
261	entry = XtNew(ReplaceEntry);
262	entry->word = XtNew(hash_key);
263	entry->word->value = XtNewString(word);
264	entry->word->length = length;
265	entry->next = NULL;
266	entry->replace = XtNewString(replace);
267	hash_put(replace_hash, (hash_entry *)entry);
268    }
269    else if (replace) {
270	XtFree(entry->replace);
271	entry->replace = XtNewString(replace);
272    }
273
274    return (entry ? entry->replace : NULL);
275}
276
277static void
278AutoReplace(Widget w, XEvent *event)
279{
280    static XComposeStatus compose = {NULL, 0};
281    KeySym keysym;
282    XawTextBlock block;
283    XawTextPosition left, right, pos;
284    Widget source;
285    int i, len, size;
286    char *str, buf[32], mb[sizeof(wchar_t)];
287
288    size = XLookupString((XKeyEvent*)event, mb, sizeof(mb), &keysym, &compose);
289
290    if (size != 1 || isalnum(*mb))
291	return;
292
293    source = XawTextGetSource(w);
294    right = XawTextGetInsertionPoint(w);
295    left = XawTextSourceScan(source, right, XawstWhiteSpace,
296			     XawsdLeft, 1, False);
297
298    if (left < 0 || left == right)
299	return;
300
301    len = 0;
302    str = buf;
303    size = sizeof(buf);
304    pos = left;
305    while (pos < right) {
306	pos = XawTextSourceRead(source, pos, &block, right - pos);
307	for (i = 0; i < block.length; i++) {
308	    if (block.format == FMT8BIT)
309		*mb = block.ptr[i];
310	    else
311		wctomb(mb, ((wchar_t*)block.ptr)[i]);
312	    str[len++] = *mb;
313	    if (len + 2 >= size) {
314		if (str == buf)
315		    str = XtMalloc(size += sizeof(buf));
316		else
317		    str = XtRealloc(str, size += sizeof(buf));
318	    }
319	}
320    }
321    str[len] = '\0';
322    if ((auto_replace.text = ReplacedWord(str, NULL)) != NULL) {
323	auto_replace.length = strlen(auto_replace.text);
324	auto_replace.left = left;
325	auto_replace.right = right;
326	auto_replace.replace = True;
327	XtAddCallback(auto_replace.widget = w, XtNpositionCallback,
328		      AutoReplaceCallback, NULL);
329    }
330    if (str != buf)
331	XtFree(str);
332}
333
334/*ARGSUSED*/
335static void
336AutoReplaceCallback(Widget w, XtPointer client_data, XtPointer call_data)
337{
338    int i, inc;
339    XawTextBlock block, text;
340    char buffer[1024], mb[sizeof(wchar_t)];
341    XawTextPosition left, right, pos;
342
343    if (!auto_replace.replace || w != auto_replace.widget)
344	return;
345
346    XtRemoveCallback(auto_replace.widget, XtNpositionCallback,
347		     AutoReplaceCallback, NULL);
348    auto_replace.replace = False;
349
350    inc = XawTextGetInsertionPoint(w) - auto_replace.right;
351    if (auto_replace.length + inc > sizeof(buffer))
352	block.ptr = XtMalloc(auto_replace.length + inc);
353    else
354	block.ptr = buffer;
355    memcpy(block.ptr, auto_replace.text, auto_replace.length);
356
357    block.length = auto_replace.length;
358    pos = left = auto_replace.right;
359    right = left + inc;
360    while (pos < right) {
361	pos = XawTextSourceRead(XawTextGetSource(w), pos, &text, inc);
362	for (i = 0; i < text.length; i++) {
363	    if (text.format == FMT8BIT)
364		*mb = text.ptr[i];
365	    else
366		wctomb(mb, ((wchar_t*)text.ptr)[i]);
367	    block.ptr[block.length++] = *mb;
368	}
369    }
370
371    block.firstPos = 0;
372    block.format = FMT8BIT;
373
374    if (XawTextReplace(w, auto_replace.left, auto_replace.right + inc,
375		       &block) == XawEditDone)
376	XawTextSetInsertionPoint(w, auto_replace.left + block.length);
377
378    if (block.ptr != buffer)
379	XtFree(block.ptr);
380}
381
382/*ARGUSED*/
383void
384LineEditAction(Widget w, XEvent *event, String *params, Cardinal *num_params)
385{
386    XawTextBlock block;
387
388    if (international) {
389	/* XXX FIXME */
390        fprintf(stderr, "LineEditAction: Not working in international mode.\n");
391	return;
392    }
393
394    block.firstPos = 0;
395    block.format = FMT8BIT;
396    block.ptr = einfo.command;
397    block.length = strlen(einfo.command);
398
399    XawTextReplace(filenamewindow, 0,
400		   XawTextLastPosition(filenamewindow), &block);
401    XtSetKeyboardFocus(topwindow, filenamewindow);
402    line_edit = True;
403}
404
405void
406LineEdit(Widget w)
407{
408    /* Global usage variables */
409    XawTextPosition from, to, first, last, position, length, redisplay;
410    int replace, compile, ecode, nth, flags, count, etype;
411    char *command, *line, buffer[128];
412    XawTextBlock block;
413    Widget source;
414    XawTextScanDirection direction;
415    xedit_flist_item *item;
416
417    /* Variables used while parsing command */
418    int state, action, offset, icase, confirm;
419    long lfrom, lto, lfinc, ltinc, number;
420    char *ptr, *pstart, *pend, *rstart, *rend, *tmp;
421
422    /* Variables used in the search/replace loop */
423    int len;
424    XawTextPosition adjust = 0;
425
426    command = GetString(filenamewindow);
427    length = strlen(command);
428    if (length >= sizeof(einfo.command)) {
429	Feep();
430	return;
431    }
432
433    item = FindTextSource(XawTextGetSource(w), NULL);
434    source = item->source;
435    position = XawTextGetInsertionPoint(w);
436    first = XawTextSourceScan(source, 0, XawstAll, XawsdLeft, 1, True);
437    last = XawTextSourceScan(source, 0, XawstAll, XawsdRight, 1, True);
438    compile = redisplay = nth = count = confirm = 0;
439    direction = XawsdRight;
440    flags = RE_STARTEND;
441
442	/* Error types */
443#define T_NONE		0
444#define T_OPTION	1
445#define T_ICASE		2
446#define T_COMMAND	3
447#define T_REPLACE	4
448#define T_SEARCH	5
449#define T_BACKSLASH	6
450#define T_DIRECTION	7
451#define T_COMMA		8
452#define T_OFFSET	9
453#define T_INCREMENT	10
454#define T_NUMBER	11
455#define T_UNFINISHED	12
456#define T_RANGE		13
457#define T_BACKREF	14
458#define T_EDIT		15
459    etype = T_NONE;
460
461#define FAIL(code)	{ etype = code; goto fail; }
462
463	/* Value for the line value, anything else is the line number */
464#define L_FIRST		-1
465#define L_CURRENT	-2
466#define L_LAST		-3
467    lfrom = L_FIRST;
468    lto = L_LAST;
469
470	/* Parsing states */
471#define E_FINC		0
472#define E_FROM		1
473#define E_COMMA		2
474#define E_TINC		3
475#define E_TO		4
476#define E_COMMAND	5
477#define E_REGEX		6
478#define E_SUBST		7
479#define E_OPTIONS	8
480    state = E_FROM;	    /* Beginning interpretation */
481
482	/* Known commands */
483#define	A_SEARCH	0
484#define	A_REPLACE	1
485    action = A_SEARCH;
486
487	/* Flag to replace all occurrences */
488#define	O_ALL		-1
489
490    number = 1;
491    lfinc = ltinc = 0;
492    icase = offset = 0;
493    pstart = pend = rstart = rend = NULL;
494
495    if (einfo.state != SubstituteDisabled) {
496	if (einfo.widget != w || strcmp(einfo.command, command)) {
497	    einfo.widget = w;
498	    einfo.state = SubstituteAsk;
499	}
500	else {
501	    XawTextPosition s_start, s_end;
502
503	    XawTextGetSelectionPos(w, &s_start, &s_end);
504	    if (s_start != einfo.start || s_end != einfo.end)
505		einfo.state = SubstituteAsk;
506	    confirm = replace = 1;
507	    from = einfo.from;
508	    to = einfo.to;
509	    first = einfo.first;
510	    last = einfo.last;
511	    goto confirm_label;
512	}
513    }
514
515    /* Remember last command */
516    strcpy(einfo.command, command);
517
518    /* Loop parsing command */
519    for (ptr = einfo.command; *ptr;) {
520	switch (*ptr++) {
521	    case 'c':
522		if (state != E_OPTIONS &&
523		    state != E_COMMAND &&
524		    state != E_REGEX)
525		    FAIL(T_OPTION)
526		confirm = 1;
527		break;
528	    case 'g':
529		if (state != E_OPTIONS &&
530		    state != E_COMMAND &&
531		    state != E_REGEX)
532		    FAIL(T_OPTION)
533		offset = O_ALL;
534		break;
535	    case 'i':
536		if (state != E_OPTIONS &&
537		    state != E_COMMAND &&
538		    state != E_REGEX &&
539		    state != E_FROM)
540		    FAIL(T_ICASE)
541		icase = 1;
542		break;
543	    case 's':
544		if (state == E_FROM)
545		    lfrom = lto = L_CURRENT;
546		else if (state == E_COMMA) {
547		    lto = L_CURRENT;
548		    ltinc = lfinc;
549		}
550		else if (state == E_TO)
551		    lto = L_LAST;
552		else if (state == E_FINC) {
553		    ltinc = lfinc;
554		    lto = L_CURRENT;
555		}
556		else if (state != E_COMMAND && state != E_TINC)
557		    FAIL(T_COMMAND)
558		action = A_REPLACE;
559		state = E_REGEX;
560		break;
561	    case '?':
562		if (action == A_REPLACE)
563		    FAIL(T_REPLACE)
564	    case '/':
565		if (state == E_TINC)
566		    state = action == A_REPLACE ? E_REGEX : E_FROM;
567		else if (state == E_COMMA || state == E_FINC) {
568		    lto = L_LAST;
569		    state = E_FROM;
570		}
571		else if (state == E_TO) {
572		    if (ltinc == 0)
573			lto = L_LAST;
574		    state = E_FROM;
575		}
576		else if (state == E_COMMAND)
577		    state = E_FROM;
578		else if (state != E_REGEX &&
579			 state != E_SUBST &&
580			 state != E_FROM)
581		    FAIL(T_SEARCH)
582		if (state != E_SUBST)
583		    direction = ptr[-1] == '/' ? XawsdRight : XawsdLeft;
584		for (tmp = ptr; *tmp; tmp++) {
585		    if (*tmp == '\\') {
586			if (*++tmp == '\0')
587			    FAIL(T_BACKSLASH)
588		    }
589		    else if (*tmp == ptr[-1])
590			break;
591		}
592		if (state == E_REGEX) {
593		    if (*tmp != ptr[-1])
594			FAIL(T_DIRECTION)
595		    pstart = ptr;
596		    pend = ptr = tmp;
597		    state = E_SUBST;
598		}
599		else if (state == E_FROM) {
600		    pstart = ptr;
601		    pend = ptr = tmp;
602		    state = E_OPTIONS;
603		    if (*ptr)
604			++ptr;
605		}
606		else { /* E_SUBST */
607		    rstart = ptr;
608		    rend = tmp;
609		    state = E_OPTIONS;
610		    ptr = tmp;
611		    if (*ptr)
612			++ptr;
613		}
614		break;
615	    case ',':
616		if (state == E_FROM)
617		    lfrom = L_FIRST;
618		else if (state == E_FINC)
619		    lfrom = L_CURRENT;
620		else if (state != E_COMMA)
621		    FAIL(T_COMMA)
622		state = E_TO;
623		break;
624	    case '%':
625		if (state == E_FROM) {
626		    lfrom = L_FIRST;
627		    lto = L_LAST;
628		    state = E_COMMAND;
629		}
630		else
631		    FAIL(T_OFFSET)
632		break;
633	    case '$':
634		if (state != E_TO)
635		    FAIL(T_OFFSET)
636		lto = L_LAST;
637		state = E_COMMAND;
638		break;
639	    case '.':
640		if (state == E_FROM) {
641		    lfrom = L_CURRENT;
642		    state = E_COMMA;
643		}
644		else if (state == E_TO) {
645		    lto = L_CURRENT;
646		    state = E_COMMAND;
647		}
648		else
649		    FAIL(T_OFFSET)
650		break;
651	    case '+':
652		if (state == E_FROM) {
653		    lfinc = 1;
654		    lfrom = L_CURRENT;
655		    state = E_FINC;
656		}
657		else if (state == E_TO) {
658		    ltinc = 1;
659		    lto = L_CURRENT;
660		    state = E_TINC;
661		}
662		else
663		    FAIL(T_INCREMENT)
664		break;
665	    case '-':	    case '^':
666		if (state == E_FROM) {
667		    lfinc = -1;
668		    lfrom = L_CURRENT;
669		    state = E_FINC;
670		}
671		else if (state == E_TO) {
672		    ltinc = -1;
673		    lto = L_CURRENT;
674		    state = E_TINC;
675		}
676		else
677		    FAIL(T_INCREMENT)
678		number = -1;
679		break;
680	    case ';':
681		if (state != E_FROM)
682		    FAIL(T_OFFSET)
683		lfrom = L_CURRENT;
684		lto = L_LAST;
685		state = E_COMMAND;
686		break;
687	    case '1':	    case '2':	    case '3':
688	    case '4':	    case '5':	    case '6':
689	    case '7':	    case '8':	    case '9':
690		number = number * (ptr[-1] - '0');
691		while (isdigit(*ptr))
692		    number = number * 10 + (*ptr++ - '0');
693		if (state == E_FROM) {
694		    lfrom = number;
695		    state = E_COMMA;
696		}
697		else if (state == E_FINC) {
698		    lfinc = number;
699		    state = E_COMMA;
700		}
701		else if (state == E_TO) {
702		    lto = number;
703		    state = E_COMMAND;
704		}
705		else if (state == E_TINC) {
706		    ltinc = number;
707		    state = E_COMMAND;
708		}
709		else if (state == E_OPTIONS && action == A_REPLACE)
710		    offset = number - 1;
711		else
712		    FAIL(T_NUMBER)
713		number = 1;
714		break;
715	    case '\0':
716		if (state == E_OPTIONS)
717		    break;
718	    default:
719		FAIL(T_UNFINISHED)
720	}
721    }
722
723    replace = action == A_REPLACE;
724
725    switch (lfrom) {
726	case L_FIRST:
727	    from = first;
728	    break;
729	case L_LAST:
730	    from = LSCAN(last, 1, False);
731	    break;
732	case L_CURRENT:
733	    if (lfinc <= 0)
734		from = LSCAN(position, -lfinc + 1, False);
735	    else {
736		from = RSCAN(position, lfinc + 1, False);
737		from = LSCAN(from, 1, False);
738	    }
739	    break;
740	default:
741	    from = RSCAN(first, lfrom, False);
742	    from = LSCAN(from, 1, False);
743	    break;
744    }
745    /* Just requesting to go to the numbered line */
746    if (state == E_COMMA || state == E_FINC) {
747	XawTextSetInsertionPoint(w, from);
748	return;
749    }
750
751    length = pend - pstart;
752    if (pstart == NULL || (replace && rstart == NULL) ||
753	length >= sizeof(einfo.pattern) - 1)
754	FAIL(T_UNFINISHED)
755
756    /* Need to (re)compile regular expression pattern? */
757    if ((!!(einfo.flags & RE_ICASE) ^ icase) ||
758	einfo.pat_length != length ||
759	memcmp(pstart, einfo.pattern,
760	       length > einfo.pat_length ? einfo.pat_length : length)) {
761	compile = 1;
762	memcpy(einfo.pattern, pstart, length);
763	einfo.pattern[length] = '\0';
764	einfo.flags = icase ? RE_ICASE : 0;
765    }
766
767    /* Check range of lines to operate on */
768    switch (lto) {
769	case L_FIRST:
770	    to = RSCAN(first, 1, True);
771	    break;
772	case L_LAST:
773	    to = last;
774	    break;
775	case L_CURRENT:
776	    if (ltinc < 0) {
777		to = LSCAN(position, -ltinc + 1, True);
778		to = RSCAN(to, 2, True);
779	    }
780	    else
781		to = RSCAN(position, ltinc + 1, True);
782	    break;
783	default:
784	    to = RSCAN(first, lto, True);
785	    break;
786    }
787    if (from >= to)
788	FAIL(T_RANGE)
789
790    /* Set first and last position allowed to search/replace */
791    first = from;
792    last = to;
793
794    /* Check bounds to work on */
795    if (replace) {
796	int i, j, ch;
797
798	/* Check number of required match results and remove/parse backslashes */
799	einfo.slen = rend - rstart;
800	einfo.sref = 0;
801	einfo.soff = offset;
802	for (i = j = 0; i < einfo.slen; i++) {
803	    ch = rstart[i];
804	    if (ch == '\\') {
805		++i;
806		switch (rstart[i]) {
807		    case '0':	ch = '\0';	break;
808		    case 'a':	ch = '\a';	break;
809		    case 'b':	ch = '\b';	break;
810		    case 'f':	ch = '\f';	break;
811		    case 'n':	ch = '\n';	break;
812		    case 'r':	ch = '\r';	break;
813		    case 't':	ch = '\t';	break;
814		    case 'v':	ch = '\v';	break;
815		    case '1':	case '2':	case '3':
816		    case '4':	case '5':	case '6':
817		    case '7':	case '8':	case '9':
818			einfo.subst[j++] = '\\';
819			if (rstart[i] - '0' > einfo.sref)
820			    einfo.sref = rstart[i] - '0';
821			/* FALLTHROUGH */
822		    default:
823			ch = rstart[i];
824			break;
825		}
826	    }
827	    einfo.subst[j++] = ch;
828	}
829	einfo.slen = j;
830    }
831    else if (einfo.widget != w) {
832	/* Just a flag for backward search */
833	einfo.from = last;
834	einfo.widget = w;
835    }
836
837    /* Compile pattern if required */
838    if (compile) {
839	int ch;
840	char *eptr, *pptr;
841
842	/* Parse backslashes */
843	pptr = einfo.subst_pattern;
844	for (eptr = einfo.pattern, ch = *eptr++; ch; ch = *eptr++) {
845	    if (ch == '\\') {
846		switch (*eptr) {
847		    case '0':	ch = '\0';  einfo.flags |= RE_PEND; break;
848		    case 'a':	ch = '\a';	break;
849		    case 'b':	ch = '\b';	break;
850		    case 'f':	ch = '\f';	break;
851		    case 'n':	ch = '\n';	break;
852		    case 'r':	ch = '\r';	break;
853		    case 't':	ch = '\t';	break;
854		    case 'v':	ch = '\v';	break;
855		    default:			break;
856		}
857		if (ch != '\\')
858		    ++eptr;
859	    }
860	    *pptr++ = ch;
861	}
862	*pptr = '\0';
863
864	refree(&einfo.regex);
865	/* Allow nuls in search regex */
866	einfo.regex.re_endp = pptr;
867	ecode = recomp(&einfo.regex, einfo.subst_pattern, einfo.flags);
868	if (ecode)
869	    goto print;
870    }
871
872    if (!replace && position >= first && position <= last) {
873	from = position;
874	/* The backwards repetition currently is only backwards when
875	 * changing lines, so remember from where started, to also
876	 * search in the first line. */
877	if (LSCAN(from, 1, False) == from) {
878	    if (direction == XawsdLeft)
879		einfo.from = from;
880	}
881	else
882	    flags |= RE_NOTBOL;
883    }
884    to = RSCAN(from, 1, True);
885
886    if (confirm) {
887	if (!replace)
888	    FAIL(T_UNFINISHED)
889	einfo.widget = w;
890	einfo.state = SubstituteAsk;
891	einfo.from = from;
892	einfo.to = to;
893	einfo.first = first;
894	einfo.last = last;
895    }
896    else
897	einfo.state = SubstituteDisabled;
898
899confirm_label:
900    if (replace) {
901	redisplay = 1;
902	XawTextDisableRedisplay(w);
903    }
904
905    for (;;) {
906	if (confirm && einfo.state != SubstituteAsk) {
907	    /* Restore state from previous call */
908	    ecode = 0;
909	    nth = einfo.soff;
910	    /* einfo.mats should not have changed */
911	    if (einfo.state == SubstituteYes) {
912		einfo.state = SubstituteAsk;
913		line = einfo.text_line;
914		goto substitute_label;
915	    }
916	    else {
917		++nth;
918		einfo.state = SubstituteAsk;
919		from = einfo.from = einfo.end;
920		goto no_substitute_label;
921	    }
922	}
923
924	/* Read or use a line of text inplace */
925	position = from;
926	length = to - from;
927	XawTextSourceRead(source, position, &block, to - position);
928	if (block.length >= length)
929	    line = block.ptr;
930	else {
931	    if (length > einfo.lsize) {
932		einfo.line = XtRealloc(einfo.line, to - from);
933		einfo.lsize = to - from;
934	    }
935	    memcpy(einfo.line, block.ptr, block.length);
936	    length = block.length;
937	    for (position += length;
938		 position < to && block.length;
939		 position += block.length) {
940		XawTextSourceRead(source, position, &block, to - position);
941		memcpy(einfo.line + length, block.ptr, block.length);
942		length += block.length;
943	    }
944	    line = einfo.line;
945	}
946
947	/* Execute expression */
948	einfo.mats[0].rm_so = 0;
949	einfo.mats[0].rm_eo = to - from;
950
951	/* If not last line or if it ends in a newline */
952	if (to != from) {
953	    if (to < last || (to > from && line[einfo.mats[0].rm_eo - 1] == '\n'))
954		--einfo.mats[0].rm_eo;
955
956	    ecode = reexec(&einfo.regex, line,
957			   einfo.sref + 1, &einfo.mats[0], flags);
958
959	    if (replace && einfo.mats[0].rm_so == einfo.mats[0].rm_eo)
960		/* Ignore empty matches */
961		ecode = RE_NOMATCH;
962
963	    if (ecode == 0 && confirm &&
964		(einfo.soff == O_ALL || nth == einfo.soff)) {
965		einfo.end = from + einfo.mats[0].rm_eo;
966		einfo.start = from + einfo.mats[0].rm_so;
967		XawTextSetInsertionPoint(w, einfo.end);
968		XawTextSetSelection(w, einfo.start, einfo.end);
969
970		einfo.state = SubstituteAsk;
971		einfo.from = from;
972		einfo.to = to;
973		einfo.first = first;
974		einfo.last = last;
975		einfo.text_line = line;
976		break;
977	    }
978	}
979	else
980	    /* Check bellow will update offsets */
981	    ecode = RE_NOMATCH;
982
983substitute_label:
984	if (ecode == 0) {
985	    from += einfo.mats[0].rm_so;
986	    len = einfo.mats[0].rm_eo - einfo.mats[0].rm_so;
987
988	    /* Found match */
989	    if (replace) {
990		/* If not replacing all ocurrences, or if not
991		 * at the correct offset */
992		if (einfo.soff != O_ALL && nth < einfo.soff) {
993		    from += len;
994		    ++nth;
995		    continue;
996		}
997
998		/* Do the substitution */
999		block.firstPos = 0;
1000		block.format = FMT8BIT;
1001		if (einfo.sref) {
1002		    /* Hard way */
1003		    int i, ref, xlen;
1004
1005		    for (i = length = 0; i < einfo.slen; i++) {
1006			if (length + 2 >= einfo.bsize) {
1007			    einfo.bsize = einfo.bsize + 1024;
1008			    einfo.buffer = XtRealloc(einfo.buffer, einfo.bsize);
1009			}
1010			if (einfo.subst[i] == '\\') {
1011			    ++i;
1012			    if (einfo.subst[i] >= '1' && einfo.subst[i] <= '9') {
1013				ref = einfo.subst[i] - '0';
1014				xlen = einfo.mats[ref].rm_eo -
1015				       einfo.mats[ref].rm_so;
1016				if (xlen < 0)
1017				    /* Oops, something went wrong... */
1018				    FAIL(T_BACKREF)
1019				if (length + xlen >= einfo.bsize) {
1020				    einfo.bsize += xlen + 1024 - (xlen % 1024);
1021				    einfo.buffer = XtRealloc(einfo.buffer,
1022							     einfo.bsize);
1023				}
1024				memcpy(einfo.buffer + length,
1025				      line + einfo.mats[ref].rm_so, xlen);
1026				length += xlen;
1027			    }
1028			    else {
1029				einfo.buffer[length++] = einfo.subst[i - 1];
1030				einfo.buffer[length++] = einfo.subst[i];
1031			    }
1032			}
1033			else
1034			    einfo.buffer[length++] = einfo.subst[i];
1035		    }
1036		    block.ptr = einfo.buffer;
1037		    block.length = length;
1038		}
1039		else {
1040		    block.ptr = einfo.subst;
1041		    block.length = length = einfo.slen;
1042		}
1043		adjust = length - len;
1044		if (XawTextReplace(w, from, from + len, &block) != XawEditDone)
1045		    FAIL(T_EDIT)
1046		last += adjust;
1047		to += adjust;
1048		from += length;
1049
1050no_substitute_label:
1051		if (einfo.soff != O_ALL) {
1052		    nth = 0;
1053		    to = RSCAN(from, 1, True);
1054		    from = LSCAN(to, 1, False);
1055		    if (to == last) {
1056			XawTextSetInsertionPoint(w, from);
1057			break;
1058		    }
1059		}
1060		else
1061		    flags |= RE_NOTBOL;
1062	    }
1063	    else {
1064		XawTextSetInsertionPoint(w, from + len);
1065		XawTextSetSelection(w, from, from + len);
1066		break;
1067	    }
1068	}
1069	else if (ecode == RE_NOMATCH) {
1070	    nth = 0;
1071
1072	    /* Try again in the next/previous line */
1073	    if (direction == XawsdLeft) {
1074		from = LSCAN(to - 1, 1 + (from != to), False);
1075		if (einfo.from <= first) {
1076		    Feep();
1077		    if (++count > 1) {
1078			XawTextSetInsertionPoint(w, position);
1079			XawTextUnsetSelection(w);
1080			break;
1081		    }
1082		    from = LSCAN(last, 1, False);
1083		}
1084		to = RSCAN(from, 1, True);
1085		/* Can use einfo.from because replace is only done forward */
1086		einfo.from = from;
1087	    }
1088	    else {
1089		if (to >= last) {
1090		    Feep();
1091		    if (replace || ++count > 1) {
1092			XawTextSetInsertionPoint(w, position);
1093			XawTextUnsetSelection(w);
1094			einfo.state = SubstituteDisabled;
1095			confirm = 0;
1096			break;
1097		    }
1098		    to = first;
1099		}
1100		from = LSCAN(to + 1, 1, False);
1101		to = RSCAN(from, 1, True);
1102	    }
1103
1104	    /* Reset flags now */
1105	    flags = RE_STARTEND;
1106	}
1107	else
1108	    goto print;
1109    }
1110
1111    if (redisplay)
1112	XawTextEnableRedisplay(w);
1113    /* If replacing not interatively return to the edit window after finished */
1114    if (replace && !confirm) {
1115	Arg args[1];
1116
1117	XtSetKeyboardFocus(topwindow, textwindow);
1118	if (item->source != scratch)
1119	    XtSetArg(args[0], XtNstring, item->name);
1120	else
1121	    XtSetArg(args[0], XtNstring, NULL);
1122	XtSetValues(filenamewindow, args, 1);
1123	line_edit = False;
1124    }
1125    return;
1126
1127print:
1128    if (redisplay)
1129	XawTextEnableRedisplay(w);
1130
1131    strcpy(buffer, "Regex error: ");
1132    length = 13;
1133    reerror(ecode, &einfo.regex,
1134	     buffer + length, sizeof(buffer) - length - 2);
1135    strcat(buffer, "\n");
1136    XeditPrintf("%s", buffer);
1137    refree(&einfo.regex);
1138    einfo.state = SubstituteDisabled;
1139    Feep();
1140    return;
1141
1142
1143fail:
1144    if (etype != T_NONE) {
1145	const char *errptr;
1146	switch (etype) {
1147	    case T_OPTION:
1148		errptr = "Option needs a command";
1149		break;
1150	    case T_ICASE:
1151		errptr = "Icase needs an command defined or none for search";
1152		break;
1153	    case T_COMMAND:
1154		errptr = "Command incorrectly specified";
1155		break;
1156	    case T_REPLACE:
1157		errptr = "Can only search backwards";
1158		break;
1159	    case T_SEARCH:
1160		errptr = "Badly placed search/replace specifier";
1161		break;
1162	    case T_BACKSLASH:
1163		errptr = "A single backslash cannot be the last command character";
1164		break;
1165	    case T_DIRECTION:
1166		errptr = "Regular expression must be separeted by / or ? not both";
1167		break;
1168	    case T_COMMA:
1169		errptr = "Badly placed comma";
1170		break;
1171	    case T_OFFSET:
1172		errptr = "Badly placed line offset specifier";
1173		break;
1174	    case T_INCREMENT:
1175		errptr = "Badly placed line offset increment specifier";
1176		break;
1177	    case T_NUMBER:
1178		errptr = "Numeric argument not expected";
1179		break;
1180	    case T_UNFINISHED:
1181		errptr = "Unfinished command";
1182		break;
1183	    case T_RANGE:
1184		errptr = "Bad line range";
1185		break;
1186	    case T_BACKREF:
1187		/* This may be an internal re error, but most likely the
1188		 * user asked for something like "s/re0(re1)re2/\2/" */
1189		errptr = "Bad backreference";
1190		break;
1191	    case T_EDIT:
1192		errptr = "Failed to replace text";
1193		break;
1194	    default:
1195		errptr = "Unknown error";
1196		break;
1197	}
1198	XeditPrintf("Error: %s.\n", errptr);
1199    }
1200    if (redisplay)
1201	XawTextEnableRedisplay(w);
1202    einfo.state = SubstituteDisabled;
1203    Feep();
1204}
1205
1206static void
1207SubstituteHook(Widget w, String action, XEvent *event)
1208{
1209    if (w != filenamewindow)
1210	return;
1211
1212    if (line_edit && einfo.state == SubstituteAsk) {
1213	if (strcmp(action, "newline") == 0 ||
1214	    strcmp(action, "load-file") == 0)
1215	    einfo.state = SubstituteAsk;
1216	else if (strcmp(action, "insert-char") == 0) {
1217	    static XComposeStatus compose = {NULL, 0};
1218	    KeySym keysym;
1219	    char mb[sizeof(wchar_t)];
1220
1221	    if (XLookupString((XKeyEvent*)event, mb, sizeof(mb),
1222			      &keysym, &compose) == 1) {
1223		if (*mb == 'y' || *mb == 'Y')
1224		    einfo.state = SubstituteYes;
1225		else if (*mb == 'n' || *mb == 'N')
1226		    einfo.state = SubstituteNo;
1227		else
1228		    einfo.state = SubstituteDisabled;
1229
1230		if (einfo.state != SubstituteDisabled) {
1231		    einfo.callback = 1;
1232		    XtAddCallback(filenamewindow, XtNpositionCallback,
1233				  SubstituteCallback, NULL);
1234		}
1235	    }
1236	}
1237	else if (strcmp(action, "cancel-find-file") == 0)
1238	    einfo.state = SubstituteDisabled;
1239    }
1240    if (einfo.state == SubstituteDisabled && einfo.callback) {
1241	einfo.callback = 0;
1242	XtRemoveCallback(filenamewindow, XtNpositionCallback,
1243			 SubstituteCallback, NULL);
1244    }
1245}
1246
1247/*ARGSUSED*/
1248static void
1249SubstituteCallback(Widget w, XtPointer client_data, XtPointer call_data)
1250{
1251    XawTextBlock block;
1252
1253    einfo.callback = 0;
1254    XtRemoveCallback(filenamewindow, XtNpositionCallback,
1255		     SubstituteCallback, NULL);
1256
1257    block.firstPos = 0;
1258    block.format = FMT8BIT;
1259    block.ptr = einfo.command;
1260    block.length = strlen(einfo.command);
1261
1262    XawTextReplace(filenamewindow, 0,
1263		   XawTextLastPosition(filenamewindow), &block);
1264
1265    LineEdit(einfo.widget);
1266}
1267