Home | History | Annotate | Line # | Download | only in dist
      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 Csar 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  */
     50 typedef struct _ReplaceEntry {
     51     hash_key *word;
     52     struct _ReplaceEntry *next;
     53     char *replace;
     54 } ReplaceEntry;
     55 
     56 typedef enum {
     57     SubstituteDisabled,
     58     SubstituteAsk,
     59     SubstituteNo,
     60     SubstituteYes
     61 } SubstitutionState;
     62 
     63 typedef 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  */
    100 static void ActionHook(Widget, XtPointer, String, XEvent*, String*, Cardinal*);
    101 static void AutoReplaceHook(Widget, String, XEvent*);
    102 static Bool StartAutoReplace(void);
    103 static char *ReplacedWord(char*, char*);
    104 static void AutoReplace(Widget, XEvent*);
    105 static void AutoReplaceCallback(Widget, XtPointer, XtPointer);
    106 
    107 static void SubstituteHook(Widget w, String action, XEvent *event);
    108 static void SubstituteCallback(Widget, XtPointer, XtPointer);
    109 
    110 /*
    111  * Initialization
    112  */
    113 #define STRTBLSZ	11
    114 static hash_table *replace_hash;
    115 static EditInfo einfo;
    116 extern Widget scratch;
    117 
    118 /*
    119  * Implementation
    120  */
    121 Bool
    122 StartHooks(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*/
    137 static void
    138 ActionHook(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 ***/
    146 struct {
    147     Widget widget;
    148     String text;
    149     Cardinal length;
    150     XawTextPosition left, right;
    151     Bool replace;
    152     Bool enabled;
    153 } auto_replace;
    154 
    155 static void
    156 AutoReplaceHook(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 
    187 static Bool
    188 StartAutoReplace(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 
    252 static char *
    253 ReplacedWord(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 
    277 static void
    278 AutoReplace(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*/
    335 static void
    336 AutoReplaceCallback(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*/
    383 void
    384 LineEditAction(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 
    405 void
    406 LineEdit(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 
    899 confirm_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 
    983 substitute_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 
   1050 no_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 
   1127 print:
   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 
   1143 fail:
   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 
   1206 static void
   1207 SubstituteHook(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*/
   1248 static void
   1249 SubstituteCallback(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