hook.c 30 KB

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