Edit

IABSD.fr/xenocara/app/xedit/hook.c

Branch :

  • Show log

    Commit

  • Author : matthieu
    Date : 2015-05-10 10:07:47
    Hash : 99a72825
    Message : Update to xedit 1.2.2

  • app/xedit/hook.c
  • /*
     * Copyright (c) 1999 by The XFree86 Project, Inc.
     *
     * Permission is hereby granted, free of charge, to any person obtaining a
     * copy of this software and associated documentation files (the "Software"),
     * to deal in the Software without restriction, including without limitation
     * the rights to use, copy, modify, merge, publish, distribute, sublicense,
     * and/or sell copies of the Software, and to permit persons to whom the
     * Software is furnished to do so, subject to the following conditions:
     *
     * The above copyright notice and this permission notice shall be included in
     * all copies or substantial portions of the Software.
     *  
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
     * THE XFREE86 PROJECT BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
     * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
     * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
     * SOFTWARE.
     *
     * Except as contained in this notice, the name of the XFree86 Project shall
     * not be used in advertising or otherwise to promote the sale, use or other
     * dealings in this Software without prior written authorization from the
     * XFree86 Project.
     *
     * Author: Paulo César Pereira de Andrade
     */
    
    /* $XFree86: xc/programs/xedit/hook.c,v 1.9 2003/01/08 05:07:40 paulo Exp $ */
    
    /*
     * This file is intended to be used to add all the necessary hooks to xedit
     * emulate certain features of emacs (and other text editors) that are better
     * kept only in xedit, to avoid unnecessary code in the Text Widget.
     *
     * The code here is not finished, and will probably be changed frequently.
     */
    
    #include "xedit.h"
    #include "re.h"
    #include "util.h"
    #include <stdlib.h>
    #include <string.h>
    #include <ctype.h>
    
    /*
     * Types
     */
    typedef struct _ReplaceEntry {
        hash_key *word;
        struct _ReplaceEntry *next;
        char *replace;
    } ReplaceEntry;
    
    typedef enum {
        SubstituteDisabled,
        SubstituteAsk,
        SubstituteNo,
        SubstituteYes
    } SubstitutionState;
    
    typedef struct _EditInfo {
        /* Xedit regex data */
        re_cod regex;
        re_mat mats[10];
    
        /* Last command entered */
        char command[128];
    
        /* String and flags used to compile regex */
        char pattern[64];
        char subst_pattern[64];
        int pat_length;
        int flags;
    
        /* Substitution buffer */
        char subst[64];
        int soff, slen, sref;
    
        /* For interactive substitution */
        int callback;
        Widget widget;
        char *text_line;
        SubstitutionState state;
        XawTextPosition from, to, start, end, first, last;
    
        /* Use if need to allocate a buffer to pass the entire line to reexec */
        char *line;
        long lsize;
    
        /* Buffer to prepare replacement, if needs to expand backreferences */
        char *buffer;
        long bsize;
    } EditInfo;
    
    /*
     * Prototypes
     */
    static void ActionHook(Widget, XtPointer, String, XEvent*, String*, Cardinal*);
    static void AutoReplaceHook(Widget, String, XEvent*);
    static Bool StartAutoReplace(void);
    static char *ReplacedWord(char*, char*);
    static void AutoReplace(Widget, XEvent*);
    static void AutoReplaceCallback(Widget, XtPointer, XtPointer);
    
    static void SubstituteHook(Widget w, String action, XEvent *event);
    static void SubstituteCallback(Widget, XtPointer, XtPointer);
    
    /*
     * Initialization
     */
    #define STRTBLSZ	11
    static hash_table *replace_hash;
    static EditInfo einfo;
    extern Widget scratch;
    
    /*
     * Implementation
     */
    Bool
    StartHooks(XtAppContext app)
    {
        static Bool first_time = True;
    
        if (first_time) {
    	StartAutoReplace();
    	(void)XtAppAddActionHook(app, ActionHook, NULL);
    	first_time = False;
    
    	return (True);
        }
        return (False);
    }
    
    /*ARGSUSED*/
    static void
    ActionHook(Widget w, XtPointer client_data, String action, XEvent *event,
    	   String *params, Cardinal *num_params)
    {
        AutoReplaceHook(w, action, event);
        SubstituteHook(w, action, event);
    }
    
    /*** auto replace ***/
    struct {
        Widget widget;
        String text;
        Cardinal length;
        XawTextPosition left, right;
        Bool replace;
        Bool enabled;
    } auto_replace;
    
    static void
    AutoReplaceHook(Widget w, String action, XEvent *event)
    {
        static Bool multiply;
    
        if (w != textwindow || !auto_replace.enabled)
    	return;
    
        if (auto_replace.widget != textwindow) {
    	if (auto_replace.replace) {
    	    auto_replace.replace = False;
    	    XtRemoveCallback(auto_replace.widget, XtNpositionCallback,
    			     AutoReplaceCallback, NULL);
    	}
        }
        else if (strcmp(action, "multiply") == 0) {
    	multiply = True;
    	return;
        }
        else if (strcmp(action, "numeric") == 0) {
    	if (multiply)
    	    return;
        }
        else if (strcmp(action, "insert-char") && strcmp(action, "newline") &&
    	strcmp(action, "newline-and-indent")) {
    	return;
        }
        multiply = False;
    
        AutoReplace(w, event);
    }
    
    static Bool
    StartAutoReplace(void)
    {
        Bool esc;
        int len, llen, rlen, count = 0;
        char ch, *tmp, *left, *right, *replace = app_resources.auto_replace;
    
        if (!replace || !*replace)
    	return (False);
    
        replace_hash = hash_new(STRTBLSZ, NULL);
    
        left = XtMalloc(llen = 256);
        right = XtMalloc(rlen = 256);
        while (*replace) {
    	/* skip white spaces */
    	while (*replace && isspace(*replace))
    	    ++replace;
    	if (!*replace)
    	    break;
    
    	/* read left */
    	tmp = replace;
    	while (*replace && !isspace(*replace))
    	    ++replace;
    	len = replace - tmp;
    	if (len >= llen)
    	    left = XtRealloc(left, llen = len + 1);
    	strncpy(left, tmp, len);
    	left[len] = '\0';
    
    	/* skip white spaces */
    	while (*replace && isspace(*replace))
    	    ++replace;
    
    	/* read right */
    	len = 0;
    	esc = False;
    	while ((ch = *replace) != '\0') {
    	    ++replace;
    	    if (len + 2 >= rlen)
    		right = XtRealloc(right, rlen += 256);
    	    if (ch == '\\') {
    		if (esc)
    		    right[len++] = '\\';
    		esc = !esc;
    		continue;
    	    }
    	    else if (ch == '\n' && !esc)
    		break;
    	    else
    		right[len++] = ch;
    	    esc = False;
    	}
    	right[len] = '\0';
    
    	(void)ReplacedWord(left, right);
    	++count;
        }
        XtFree(left);
        XtFree(right);
    
        return (auto_replace.enabled = count > 0);
    }
    
    static char *
    ReplacedWord(char *word, char *replace)
    {
        int length;
        ReplaceEntry *entry;
    
        length = strlen(word);
        entry = (ReplaceEntry *)hash_check(replace_hash, word, length);
        if (entry == NULL && replace != NULL) {
    	entry = XtNew(ReplaceEntry);
    	entry->word = XtNew(hash_key);
    	entry->word->value = XtNewString(word);
    	entry->word->length = length;
    	entry->next = NULL;
    	entry->replace = XtNewString(replace);
    	hash_put(replace_hash, (hash_entry *)entry);
        }
        else if (replace) {
    	XtFree(entry->replace);
    	entry->replace = XtNewString(replace);
        }
    
        return (entry ? entry->replace : NULL);
    }
    
    static void
    AutoReplace(Widget w, XEvent *event)
    {
        static XComposeStatus compose = {NULL, 0};
        KeySym keysym;
        XawTextBlock block;
        XawTextPosition left, right, pos;
        Widget source;
        int i, len, size;
        char *str, buf[32], mb[sizeof(wchar_t)];
    
        size = XLookupString((XKeyEvent*)event, mb, sizeof(mb), &keysym, &compose);
    
        if (size != 1 || isalnum(*mb))
    	return;
    
        source = XawTextGetSource(w);
        right = XawTextGetInsertionPoint(w);
        left = XawTextSourceScan(source, right, XawstWhiteSpace,
    			     XawsdLeft, 1, False);
    
        if (left < 0 || left == right)
    	return;
    
        len = 0;
        str = buf;
        size = sizeof(buf);
        pos = left;
        while (pos < right) {
    	pos = XawTextSourceRead(source, pos, &block, right - pos);
    	for (i = 0; i < block.length; i++) {
    	    if (block.format == FMT8BIT)
    		*mb = block.ptr[i];
    	    else
    		wctomb(mb, ((wchar_t*)block.ptr)[i]);
    	    str[len++] = *mb;
    	    if (len + 2 >= size) {
    		if (str == buf)
    		    str = XtMalloc(size += sizeof(buf));
    		else
    		    str = XtRealloc(str, size += sizeof(buf));
    	    }
    	}
        }
        str[len] = '\0';
        if ((auto_replace.text = ReplacedWord(str, NULL)) != NULL) {
    	auto_replace.length = strlen(auto_replace.text);
    	auto_replace.left = left;
    	auto_replace.right = right;
    	auto_replace.replace = True;
    	XtAddCallback(auto_replace.widget = w, XtNpositionCallback,
    		      AutoReplaceCallback, NULL);
        }
        if (str != buf)
    	XtFree(str);
    }
    
    /*ARGSUSED*/
    static void
    AutoReplaceCallback(Widget w, XtPointer client_data, XtPointer call_data)
    {
        int i, inc;
        XawTextBlock block, text;
        char buffer[1024], mb[sizeof(wchar_t)];
        XawTextPosition left, right, pos;
    
        if (!auto_replace.replace || w != auto_replace.widget)
    	return;
    
        XtRemoveCallback(auto_replace.widget, XtNpositionCallback,
    		     AutoReplaceCallback, NULL);
        auto_replace.replace = False;
    
        inc = XawTextGetInsertionPoint(w) - auto_replace.right;
        if (auto_replace.length + inc > sizeof(buffer))
    	block.ptr = XtMalloc(auto_replace.length + inc);
        else
    	block.ptr = buffer;
        memcpy(block.ptr, auto_replace.text, auto_replace.length);
    
        block.length = auto_replace.length;
        pos = left = auto_replace.right;
        right = left + inc;
        while (pos < right) {
    	pos = XawTextSourceRead(XawTextGetSource(w), pos, &text, inc);
    	for (i = 0; i < text.length; i++) {
    	    if (text.format == FMT8BIT)
    		*mb = text.ptr[i];
    	    else
    		wctomb(mb, ((wchar_t*)text.ptr)[i]);
    	    block.ptr[block.length++] = *mb;
    	}
        }
    
        block.firstPos = 0;
        block.format = FMT8BIT;
    
        if (XawTextReplace(w, auto_replace.left, auto_replace.right + inc,
    		       &block) == XawEditDone)
    	XawTextSetInsertionPoint(w, auto_replace.left + block.length);
    
        if (block.ptr != buffer)
    	XtFree(block.ptr);
    }
    
    /*ARGUSED*/
    void
    LineEditAction(Widget w, XEvent *event, String *params, Cardinal *num_params)
    {
        XawTextBlock block;
    
        if (international) {
    	/* XXX FIXME */
            fprintf(stderr, "LineEditAction: Not working in international mode.\n");
    	return;
        }
    
        block.firstPos = 0;
        block.format = FMT8BIT;
        block.ptr = einfo.command;
        block.length = strlen(einfo.command);
    
        XawTextReplace(filenamewindow, 0,
    		   XawTextLastPosition(filenamewindow), &block);
        XtSetKeyboardFocus(topwindow, filenamewindow);
        line_edit = True;
    }
    
    void
    LineEdit(Widget w)
    {
        /* Global usage variables */
        XawTextPosition from, to, first, last, position, length, redisplay;
        int replace, compile, ecode, nth, flags, count, etype;
        char *command, *line, buffer[128];
        XawTextBlock block;
        Widget source;
        XawTextScanDirection direction;
        xedit_flist_item *item;
    
        /* Variables used while parsing command */
        int state, action, offset, icase, confirm;
        long lfrom, lto, lfinc, ltinc, number;
        char *ptr, *pstart, *pend, *rstart, *rend, *tmp;
    
        /* Variables used in the search/replace loop */
        int len;
        XawTextPosition adjust = 0;
    
        command = GetString(filenamewindow);
        length = strlen(command);
        if (length >= sizeof(einfo.command)) {
    	Feep();
    	return;
        }
    
        item = FindTextSource(XawTextGetSource(w), NULL);
        source = item->source;
        position = XawTextGetInsertionPoint(w);
        first = XawTextSourceScan(source, 0, XawstAll, XawsdLeft, 1, True);
        last = XawTextSourceScan(source, 0, XawstAll, XawsdRight, 1, True);
        compile = redisplay = nth = count = confirm = 0;
        direction = XawsdRight;
        flags = RE_STARTEND;
    
    	/* Error types */
    #define T_NONE		0
    #define T_OPTION	1
    #define T_ICASE		2
    #define T_COMMAND	3
    #define T_REPLACE	4
    #define T_SEARCH	5
    #define T_BACKSLASH	6
    #define T_DIRECTION	7
    #define T_COMMA		8
    #define T_OFFSET	9
    #define T_INCREMENT	10
    #define T_NUMBER	11
    #define T_UNFINISHED	12
    #define T_RANGE		13
    #define T_BACKREF	14
    #define T_EDIT		15
        etype = T_NONE;
    
    #define FAIL(code)	{ etype = code; goto fail; }
    
    	/* Value for the line value, anything else is the line number */
    #define L_FIRST		-1
    #define L_CURRENT	-2
    #define L_LAST		-3
        lfrom = L_FIRST;
        lto = L_LAST;
    
    	/* Parsing states */
    #define E_FINC		0
    #define E_FROM		1
    #define E_COMMA		2
    #define E_TINC		3
    #define E_TO		4
    #define E_COMMAND	5
    #define E_REGEX		6
    #define E_SUBST		7
    #define E_OPTIONS	8
        state = E_FROM;	    /* Beginning interpretation */
    
    	/* Known commands */
    #define	A_SEARCH	0
    #define	A_REPLACE	1
        action = A_SEARCH;
    
    	/* Flag to replace all occurrences */
    #define	O_ALL		-1
    
        number = 1;
        lfinc = ltinc = 0;
        icase = offset = 0;
        pstart = pend = rstart = rend = NULL;
    
        if (einfo.state != SubstituteDisabled) {
    	if (einfo.widget != w || strcmp(einfo.command, command)) {
    	    einfo.widget = w;
    	    einfo.state = SubstituteAsk;
    	}
    	else {
    	    XawTextPosition s_start, s_end;
    
    	    XawTextGetSelectionPos(w, &s_start, &s_end);
    	    if (s_start != einfo.start || s_end != einfo.end)
    		einfo.state = SubstituteAsk;
    	    confirm = replace = 1;
    	    from = einfo.from;
    	    to = einfo.to;
    	    first = einfo.first;
    	    last = einfo.last;
    	    goto confirm_label;
    	}
        }
    
        /* Remember last command */
        strcpy(einfo.command, command); 
    
        /* Loop parsing command */
        for (ptr = einfo.command; *ptr;) {
    	switch (*ptr++) {
    	    case 'c':
    		if (state != E_OPTIONS &&
    		    state != E_COMMAND &&
    		    state != E_REGEX)
    		    FAIL(T_OPTION)
    		confirm = 1;
    		break;
    	    case 'g':
    		if (state != E_OPTIONS &&
    		    state != E_COMMAND &&
    		    state != E_REGEX)
    		    FAIL(T_OPTION)
    		offset = O_ALL;
    		break;
    	    case 'i':
    		if (state != E_OPTIONS &&
    		    state != E_COMMAND &&
    		    state != E_REGEX &&
    		    state != E_FROM)
    		    FAIL(T_ICASE)
    		icase = 1;
    		break;
    	    case 's':
    		if (state == E_FROM)
    		    lfrom = lto = L_CURRENT;
    		else if (state == E_COMMA) {
    		    lto = L_CURRENT;
    		    ltinc = lfinc;
    		}
    		else if (state == E_TO)
    		    lto = L_LAST;
    		else if (state == E_FINC) {
    		    ltinc = lfinc;
    		    lto = L_CURRENT;
    		}
    		else if (state != E_COMMAND && state != E_TINC)
    		    FAIL(T_COMMAND)
    		action = A_REPLACE;
    		state = E_REGEX;
    		break;
    	    case '?':
    		if (action == A_REPLACE)
    		    FAIL(T_REPLACE)
    	    case '/':
    		if (state == E_TINC)
    		    state = action == A_REPLACE ? E_REGEX : E_FROM;
    		else if (state == E_COMMA || state == E_FINC) {
    		    lto = L_LAST;
    		    state = E_FROM;
    		}
    		else if (state == E_TO) {
    		    if (ltinc == 0)
    			lto = L_LAST;
    		    state = E_FROM;
    		}
    		else if (state == E_COMMAND)
    		    state = E_FROM;
    		else if (state != E_REGEX &&
    			 state != E_SUBST &&
    			 state != E_FROM)
    		    FAIL(T_SEARCH)
    		if (state != E_SUBST)
    		    direction = ptr[-1] == '/' ? XawsdRight : XawsdLeft;
    		for (tmp = ptr; *tmp; tmp++) {
    		    if (*tmp == '\\') {
    			if (*++tmp == '\0')
    			    FAIL(T_BACKSLASH)
    		    }
    		    else if (*tmp == ptr[-1])
    			break;
    		}
    		if (state == E_REGEX) {
    		    if (*tmp != ptr[-1])
    			FAIL(T_DIRECTION)
    		    pstart = ptr;
    		    pend = ptr = tmp;
    		    state = E_SUBST;
    		}
    		else if (state == E_FROM) {
    		    pstart = ptr;
    		    pend = ptr = tmp;
    		    state = E_OPTIONS;
    		    if (*ptr)
    			++ptr;
    		}
    		else { /* E_SUBST */
    		    rstart = ptr;
    		    rend = tmp;
    		    state = E_OPTIONS;
    		    ptr = tmp;
    		    if (*ptr)
    			++ptr;
    		}
    		break;
    	    case ',':
    		if (state == E_FROM)
    		    lfrom = L_FIRST;
    		else if (state == E_FINC)
    		    lfrom = L_CURRENT;
    		else if (state != E_COMMA)
    		    FAIL(T_COMMA)
    		state = E_TO;
    		break;
    	    case '%':
    		if (state == E_FROM) {
    		    lfrom = L_FIRST;
    		    lto = L_LAST;
    		    state = E_COMMAND;
    		}
    		else
    		    FAIL(T_OFFSET)
    		break;
    	    case '$':
    		if (state != E_TO)
    		    FAIL(T_OFFSET)
    		lto = L_LAST;
    		state = E_COMMAND;
    		break;
    	    case '.':
    		if (state == E_FROM) {
    		    lfrom = L_CURRENT;
    		    state = E_COMMA;
    		}
    		else if (state == E_TO) {
    		    lto = L_CURRENT;
    		    state = E_COMMAND;
    		}
    		else
    		    FAIL(T_OFFSET)
    		break;
    	    case '+':
    		if (state == E_FROM) {
    		    lfinc = 1;
    		    lfrom = L_CURRENT;
    		    state = E_FINC;
    		}
    		else if (state == E_TO) {
    		    ltinc = 1;
    		    lto = L_CURRENT;
    		    state = E_TINC;
    		}
    		else
    		    FAIL(T_INCREMENT)
    		break;
    	    case '-':	    case '^':
    		if (state == E_FROM) {
    		    lfinc = -1;
    		    lfrom = L_CURRENT;
    		    state = E_FINC;
    		}
    		else if (state == E_TO) {
    		    ltinc = -1;
    		    lto = L_CURRENT;
    		    state = E_TINC;
    		}
    		else
    		    FAIL(T_INCREMENT)
    		number = -1;
    		break;
    	    case ';':
    		if (state != E_FROM)
    		    FAIL(T_OFFSET)
    		lfrom = L_CURRENT;
    		lto = L_LAST;
    		state = E_COMMAND;
    		break;
    	    case '1':	    case '2':	    case '3':
    	    case '4':	    case '5':	    case '6':
    	    case '7':	    case '8':	    case '9':
    		number = number * (ptr[-1] - '0');
    		while (isdigit(*ptr))
    		    number = number * 10 + (*ptr++ - '0');
    		if (state == E_FROM) {
    		    lfrom = number;
    		    state = E_COMMA;
    		}
    		else if (state == E_FINC) {
    		    lfinc = number;
    		    state = E_COMMA;
    		}
    		else if (state == E_TO) {
    		    lto = number;
    		    state = E_COMMAND;
    		}
    		else if (state == E_TINC) {
    		    ltinc = number;
    		    state = E_COMMAND;
    		}
    		else if (state == E_OPTIONS && action == A_REPLACE)
    		    offset = number - 1;
    		else
    		    FAIL(T_NUMBER)
    		number = 1;
    		break;
    	    case '\0':
    		if (state == E_OPTIONS)
    		    break;
    	    default:
    		FAIL(T_UNFINISHED)
    	}
        }
    
        replace = action == A_REPLACE;
    
        switch (lfrom) {
    	case L_FIRST:
    	    from = first;
    	    break;
    	case L_LAST:
    	    from = LSCAN(last, 1, False);
    	    break;
    	case L_CURRENT:
    	    if (lfinc <= 0)
    		from = LSCAN(position, -lfinc + 1, False);
    	    else {
    		from = RSCAN(position, lfinc + 1, False);
    		from = LSCAN(from, 1, False);
    	    }
    	    break;
    	default:
    	    from = RSCAN(first, lfrom, False);
    	    from = LSCAN(from, 1, False);
    	    break;
        }
        /* Just requesting to go to the numbered line */
        if (state == E_COMMA || state == E_FINC) {
    	XawTextSetInsertionPoint(w, from);
    	return;
        }
    
        length = pend - pstart;
        if (pstart == NULL || (replace && rstart == NULL) ||
    	length >= sizeof(einfo.pattern) - 1)
    	FAIL(T_UNFINISHED)
    
        /* Need to (re)compile regular expression pattern? */
        if ((!!(einfo.flags & RE_ICASE) ^ icase) ||
    	einfo.pat_length != length ||
    	memcmp(pstart, einfo.pattern,
    	       length > einfo.pat_length ? einfo.pat_length : length)) {
    	compile = 1;
    	memcpy(einfo.pattern, pstart, length);
    	einfo.pattern[length] = '\0';
    	einfo.flags = icase ? RE_ICASE : 0;
        }
    
        /* Check range of lines to operate on */
        switch (lto) {
    	case L_FIRST:
    	    to = RSCAN(first, 1, True);
    	    break;
    	case L_LAST:
    	    to = last;
    	    break;
    	case L_CURRENT:
    	    if (ltinc < 0) {
    		to = LSCAN(position, -ltinc + 1, True);
    		to = RSCAN(to, 2, True);
    	    }
    	    else
    		to = RSCAN(position, ltinc + 1, True);
    	    break;
    	default:
    	    to = RSCAN(first, lto, True);
    	    break;
        }
        if (from >= to)
    	FAIL(T_RANGE)
    
        /* Set first and last position allowed to search/replace */
        first = from;
        last = to;
    
        /* Check bounds to work on */
        if (replace) {
    	int i, j, ch;
    
    	/* Check number of required match results and remove/parse backslashes */
    	einfo.slen = rend - rstart;
    	einfo.sref = 0;
    	einfo.soff = offset;
    	for (i = j = 0; i < einfo.slen; i++) {
    	    ch = rstart[i];
    	    if (ch == '\\') {
    		++i;
    		switch (rstart[i]) {
    		    case '0':	ch = '\0';	break;
    		    case 'a':	ch = '\a';	break;
    		    case 'b':	ch = '\b';	break;
    		    case 'f':	ch = '\f';	break;
    		    case 'n':	ch = '\n';	break;
    		    case 'r':	ch = '\r';	break;
    		    case 't':	ch = '\t';	break;
    		    case 'v':	ch = '\v';	break;
    		    case '1':	case '2':	case '3':
    		    case '4':	case '5':	case '6':
    		    case '7':	case '8':	case '9':
    			einfo.subst[j++] = '\\';
    			if (rstart[i] - '0' > einfo.sref)
    			    einfo.sref = rstart[i] - '0';
    			/* FALLTHROUGH */
    		    default:
    			ch = rstart[i];
    			break;
    		}
    	    }
    	    einfo.subst[j++] = ch;
    	}
    	einfo.slen = j;
        }
        else if (einfo.widget != w) {
    	/* Just a flag for backward search */
    	einfo.from = last;
    	einfo.widget = w;
        }
    
        /* Compile pattern if required */
        if (compile) {
    	int ch;
    	char *eptr, *pptr;
    
    	/* Parse backslashes */
    	pptr = einfo.subst_pattern;
    	for (eptr = einfo.pattern, ch = *eptr++; ch; ch = *eptr++) {
    	    if (ch == '\\') {
    		switch (*eptr) {
    		    case '0':	ch = '\0';  einfo.flags |= RE_PEND; break;
    		    case 'a':	ch = '\a';	break;
    		    case 'b':	ch = '\b';	break;
    		    case 'f':	ch = '\f';	break;
    		    case 'n':	ch = '\n';	break;
    		    case 'r':	ch = '\r';	break;
    		    case 't':	ch = '\t';	break;
    		    case 'v':	ch = '\v';	break;
    		    default:			break;
    		}
    		if (ch != '\\')
    		    ++eptr;
    	    }
    	    *pptr++ = ch;
    	}
    	*pptr = '\0';
    
    	refree(&einfo.regex);
    	/* Allow nuls in search regex */
    	einfo.regex.re_endp = pptr;
    	ecode = recomp(&einfo.regex, einfo.subst_pattern, einfo.flags);
    	if (ecode)
    	    goto print;
        }
    
        if (!replace && position >= first && position <= last) {
    	from = position;
    	/* The backwards repetition currently is only backwards when
    	 * changing lines, so remember from where started, to also
    	 * search in the first line. */
    	if (LSCAN(from, 1, False) == from) {
    	    if (direction == XawsdLeft)
    		einfo.from = from;
    	}
    	else
    	    flags |= RE_NOTBOL;
        }
        to = RSCAN(from, 1, True);
    
        if (confirm) {
    	if (!replace)
    	    FAIL(T_UNFINISHED)
    	einfo.widget = w;
    	einfo.state = SubstituteAsk;
    	einfo.from = from;
    	einfo.to = to;
    	einfo.first = first;
    	einfo.last = last;
        }
        else
    	einfo.state = SubstituteDisabled;
    
    confirm_label:
        if (replace) {
    	redisplay = 1;
    	XawTextDisableRedisplay(w);
        }
    
        for (;;) {
    	if (confirm && einfo.state != SubstituteAsk) {
    	    /* Restore state from previous call */
    	    ecode = 0;
    	    nth = einfo.soff;
    	    /* einfo.mats should not have changed */
    	    if (einfo.state == SubstituteYes) {
    		einfo.state = SubstituteAsk;
    		line = einfo.text_line;
    		goto substitute_label;
    	    }
    	    else {
    		++nth;
    		einfo.state = SubstituteAsk;
    		from = einfo.from = einfo.end;
    		goto no_substitute_label;
    	    }
    	}
    
    	/* Read or use a line of text inplace */
    	position = from;
    	length = to - from;
    	XawTextSourceRead(source, position, &block, to - position);
    	if (block.length >= length)
    	    line = block.ptr;
    	else {
    	    if (length > einfo.lsize) {
    		einfo.line = XtRealloc(einfo.line, to - from);
    		einfo.lsize = to - from;
    	    }
    	    memcpy(einfo.line, block.ptr, block.length);
    	    length = block.length;
    	    for (position += length;
    		 position < to && block.length;
    		 position += block.length) {
    		XawTextSourceRead(source, position, &block, to - position);
    		memcpy(einfo.line + length, block.ptr, block.length);
    		length += block.length;
    	    }
    	    line = einfo.line;
    	}
    
    	/* Execute expression */
    	einfo.mats[0].rm_so = 0;
    	einfo.mats[0].rm_eo = to - from;
    
    	/* If not last line or if it ends in a newline */
    	if (to != from) {
    	    if (to < last || (to > from && line[einfo.mats[0].rm_eo - 1] == '\n'))
    		--einfo.mats[0].rm_eo;
    
    	    ecode = reexec(&einfo.regex, line,
    			   einfo.sref + 1, &einfo.mats[0], flags);
    
    	    if (replace && einfo.mats[0].rm_so == einfo.mats[0].rm_eo)
    		/* Ignore empty matches */
    		ecode = RE_NOMATCH;
    
    	    if (ecode == 0 && confirm &&
    		(einfo.soff == O_ALL || nth == einfo.soff)) {
    		einfo.end = from + einfo.mats[0].rm_eo;
    		einfo.start = from + einfo.mats[0].rm_so;
    		XawTextSetInsertionPoint(w, einfo.end);
    		XawTextSetSelection(w, einfo.start, einfo.end);
    
    		einfo.state = SubstituteAsk;
    		einfo.from = from;
    		einfo.to = to;
    		einfo.first = first;
    		einfo.last = last;
    		einfo.text_line = line;
    		break;
    	    }
    	}
    	else
    	    /* Check bellow will update offsets */
    	    ecode = RE_NOMATCH;
    
    substitute_label:
    	if (ecode == 0) {
    	    from += einfo.mats[0].rm_so;
    	    len = einfo.mats[0].rm_eo - einfo.mats[0].rm_so;
    
    	    /* Found match */
    	    if (replace) {
    		/* If not replacing all ocurrences, or if not
    		 * at the correct offset */
    		if (einfo.soff != O_ALL && nth < einfo.soff) {
    		    from += len;
    		    ++nth;
    		    continue;
    		}
    
    		/* Do the substitution */
    		block.firstPos = 0;
    		block.format = FMT8BIT;
    		if (einfo.sref) {
    		    /* Hard way */
    		    int i, ref, xlen;
    
    		    for (i = length = 0; i < einfo.slen; i++) {
    			if (length + 2 >= einfo.bsize) {
    			    einfo.bsize = einfo.bsize + 1024;
    			    einfo.buffer = XtRealloc(einfo.buffer, einfo.bsize);
    			}
    			if (einfo.subst[i] == '\\') {
    			    ++i;
    			    if (einfo.subst[i] >= '1' && einfo.subst[i] <= '9') {
    				ref = einfo.subst[i] - '0';
    				xlen = einfo.mats[ref].rm_eo -
    				       einfo.mats[ref].rm_so;
    				if (xlen < 0)
    				    /* Oops, something went wrong... */
    				    FAIL(T_BACKREF)
    				if (length + xlen >= einfo.bsize) {
    				    einfo.bsize += xlen + 1024 - (xlen % 1024);
    				    einfo.buffer = XtRealloc(einfo.buffer,
    							     einfo.bsize);
    				}
    				memcpy(einfo.buffer + length,
    				      line + einfo.mats[ref].rm_so, xlen);
    				length += xlen;
    			    }
    			    else {
    				einfo.buffer[length++] = einfo.subst[i - 1];
    				einfo.buffer[length++] = einfo.subst[i];
    			    }
    			}
    			else
    			    einfo.buffer[length++] = einfo.subst[i];
    		    }
    		    block.ptr = einfo.buffer;
    		    block.length = length;
    		}
    		else {
    		    block.ptr = einfo.subst;
    		    block.length = length = einfo.slen;
    		}
    		adjust = length - len;
    		if (XawTextReplace(w, from, from + len, &block) != XawEditDone)
    		    FAIL(T_EDIT)
    		last += adjust;
    		to += adjust;
    		from += length;
    
    no_substitute_label:
    		if (einfo.soff != O_ALL) {
    		    nth = 0;
    		    to = RSCAN(from, 1, True);
    		    from = LSCAN(to, 1, False);
    		    if (to == last) {
    			XawTextSetInsertionPoint(w, from);
    			break;
    		    }
    		}
    		else
    		    flags |= RE_NOTBOL;
    	    }
    	    else {
    		XawTextSetInsertionPoint(w, from + len);
    		XawTextSetSelection(w, from, from + len);
    		break;
    	    }
    	}
    	else if (ecode == RE_NOMATCH) {
    	    nth = 0;
    
    	    /* Try again in the next/previous line */
    	    if (direction == XawsdLeft) {
    		from = LSCAN(to - 1, 1 + (from != to), False);
    		if (einfo.from <= first) {
    		    Feep();
    		    if (++count > 1) {
    			XawTextSetInsertionPoint(w, position);
    			XawTextUnsetSelection(w);
    			break;
    		    }
    		    from = LSCAN(last, 1, False);
    		}
    		to = RSCAN(from, 1, True);
    		/* Can use einfo.from because replace is only done forward */
    		einfo.from = from;
    	    }
    	    else {
    		if (to >= last) {
    		    Feep();
    		    if (replace || ++count > 1) {
    			XawTextSetInsertionPoint(w, position);
    			XawTextUnsetSelection(w);
    			einfo.state = SubstituteDisabled;
    			confirm = 0;
    			break;
    		    }
    		    to = first;
    		}
    		from = LSCAN(to + 1, 1, False);
    		to = RSCAN(from, 1, True);
    	    }
    
    	    /* Reset flags now */
    	    flags = RE_STARTEND;
    	}
    	else
    	    goto print;
        }
    
        if (redisplay)
    	XawTextEnableRedisplay(w);
        /* If replacing not interatively return to the edit window after finished */
        if (replace && !confirm) {
    	Arg args[1];
    
    	XtSetKeyboardFocus(topwindow, textwindow);
    	if (item->source != scratch)
    	    XtSetArg(args[0], XtNstring, item->name);
    	else
    	    XtSetArg(args[0], XtNstring, NULL);
    	XtSetValues(filenamewindow, args, 1);
    	line_edit = False;
        }
        return;
    
    print:
        if (redisplay)
    	XawTextEnableRedisplay(w);
    
        strcpy(buffer, "Regex error: ");
        length = 13;
        reerror(ecode, &einfo.regex,
    	     buffer + length, sizeof(buffer) - length - 2);
        strcat(buffer, "\n");
        XeditPrintf("%s", buffer);
        refree(&einfo.regex);
        einfo.state = SubstituteDisabled;
        Feep();
        return;
    
    
    fail:
        if (etype != T_NONE) {
    	const char *errptr;
    	switch (etype) {
    	    case T_OPTION:
    		errptr = "Option needs a command";
    		break;
    	    case T_ICASE:
    		errptr = "Icase needs an command defined or none for search";
    		break;
    	    case T_COMMAND:
    		errptr = "Command incorrectly specified";
    		break;
    	    case T_REPLACE:
    		errptr = "Can only search backwards";
    		break;
    	    case T_SEARCH:
    		errptr = "Badly placed search/replace specifier";
    		break;
    	    case T_BACKSLASH:
    		errptr = "A single backslash cannot be the last command character";
    		break;
    	    case T_DIRECTION:
    		errptr = "Regular expression must be separeted by / or ? not both";
    		break;
    	    case T_COMMA:
    		errptr = "Badly placed comma";
    		break;
    	    case T_OFFSET:
    		errptr = "Badly placed line offset specifier";
    		break;
    	    case T_INCREMENT:
    		errptr = "Badly placed line offset increment specifier";
    		break;
    	    case T_NUMBER:
    		errptr = "Numeric argument not expected";
    		break;
    	    case T_UNFINISHED:
    		errptr = "Unfinished command";
    		break;
    	    case T_RANGE:
    		errptr = "Bad line range";
    		break;
    	    case T_BACKREF:
    		/* This may be an internal re error, but most likely the
    		 * user asked for something like "s/re0(re1)re2/\2/" */
    		errptr = "Bad backreference";
    		break;
    	    case T_EDIT:
    		errptr = "Failed to replace text";
    		break;
    	    default:
    		errptr = "Unknown error";
    		break;
    	}
    	XeditPrintf("Error: %s.\n", errptr);
        }
        if (redisplay)
    	XawTextEnableRedisplay(w);
        einfo.state = SubstituteDisabled;
        Feep();
    }
    
    static void
    SubstituteHook(Widget w, String action, XEvent *event)
    {
        if (w != filenamewindow)
    	return;
    
        if (line_edit && einfo.state == SubstituteAsk) {
    	if (strcmp(action, "newline") == 0 ||
    	    strcmp(action, "load-file") == 0)
    	    einfo.state = SubstituteAsk;
    	else if (strcmp(action, "insert-char") == 0) {
    	    static XComposeStatus compose = {NULL, 0};
    	    KeySym keysym;
    	    char mb[sizeof(wchar_t)];
    
    	    if (XLookupString((XKeyEvent*)event, mb, sizeof(mb),
    			      &keysym, &compose) == 1) {
    		if (*mb == 'y' || *mb == 'Y')
    		    einfo.state = SubstituteYes;
    		else if (*mb == 'n' || *mb == 'N')
    		    einfo.state = SubstituteNo;
    		else
    		    einfo.state = SubstituteDisabled;
    
    		if (einfo.state != SubstituteDisabled) {
    		    einfo.callback = 1;
    		    XtAddCallback(filenamewindow, XtNpositionCallback,
    				  SubstituteCallback, NULL);
    		}
    	    }
    	}
    	else if (strcmp(action, "cancel-find-file") == 0)
    	    einfo.state = SubstituteDisabled;
        }
        if (einfo.state == SubstituteDisabled && einfo.callback) {
    	einfo.callback = 0;
    	XtRemoveCallback(filenamewindow, XtNpositionCallback,
    			 SubstituteCallback, NULL);
        }
    }
    
    /*ARGSUSED*/
    static void
    SubstituteCallback(Widget w, XtPointer client_data, XtPointer call_data)
    {
        XawTextBlock block;
    
        einfo.callback = 0;
        XtRemoveCallback(filenamewindow, XtNpositionCallback,
    		     SubstituteCallback, NULL);
    
        block.firstPos = 0;
        block.format = FMT8BIT;
        block.ptr = einfo.command;
        block.length = strlen(einfo.command);
    
        XawTextReplace(filenamewindow, 0,
    		   XawTextLastPosition(filenamewindow), &block);
    
        LineEdit(einfo.widget);
    }