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