PageRenderTime 26ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/src/search.c

#
C | 467 lines | 322 code | 59 blank | 86 comment | 124 complexity | 51a0d6361aede66b60f864a1b91df9bf MD5 | raw file
  1. /* Search and replace functions
  2. Copyright (c) 1997-2014 Free Software Foundation, Inc.
  3. This file is part of GNU Zile.
  4. GNU Zile is free software; you can redistribute it and/or modify it
  5. under the terms of the GNU General Public License as published by
  6. the Free Software Foundation; either version 3, or (at your option)
  7. any later version.
  8. GNU Zile is distributed in the hope that it will be useful, but
  9. WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with GNU Zile; see the file COPYING. If not, write to the
  14. Free Software Foundation, Fifth Floor, 51 Franklin Street, Boston,
  15. MA 02111-1301, USA. */
  16. #include <config.h>
  17. #include <stdlib.h>
  18. #include <ctype.h>
  19. #include <regex.h>
  20. #include "main.h"
  21. #include "extern.h"
  22. /* Return true if there are no upper-case letters in the given string.
  23. If `regex' is true, ignore escaped characters. */
  24. static bool
  25. no_upper (const char *s, size_t len, int regex)
  26. {
  27. int quote_flag = 0;
  28. for (size_t i = 0; i < len; i++)
  29. {
  30. if (regex && s[i] == '\\')
  31. quote_flag = !quote_flag;
  32. else if (!quote_flag && isupper ((int) s[i]))
  33. return false;
  34. }
  35. return true;
  36. }
  37. static const char *re_find_err = NULL;
  38. static int
  39. find_substr (const_astr as, const char *n, size_t nsize,
  40. bool forward, bool notbol, bool noteol, bool regex, bool icase)
  41. {
  42. int ret = -1;
  43. struct re_pattern_buffer pattern;
  44. struct re_registers search_regs;
  45. reg_syntax_t syntax = RE_SYNTAX_EMACS;
  46. memset (&pattern, 0, sizeof (pattern));
  47. if (!regex)
  48. syntax |= RE_PLAIN;
  49. if (icase)
  50. syntax |= RE_ICASE;
  51. re_set_syntax (syntax);
  52. search_regs.num_regs = 1;
  53. re_find_err = re_compile_pattern (n, (int) nsize, &pattern);
  54. pattern.not_bol = notbol;
  55. pattern.not_eol = noteol;
  56. size_t len = astr_len (as);
  57. if (!re_find_err)
  58. ret = re_search (&pattern, astr_cstr (as), (int) len,
  59. forward ? 0 : len, forward ? len : -len, &search_regs);
  60. if (ret >= 0)
  61. ret = forward ? search_regs.end[0] : ret;
  62. return ret;
  63. }
  64. static bool
  65. search (const char *s, int forward, int regexp)
  66. {
  67. size_t ssize = strlen (s);
  68. if (ssize < 1)
  69. return false;
  70. /* Attempt match. */
  71. size_t o = get_buffer_pt (cur_bp);
  72. bool notbol = forward ? o > 0 : false;
  73. bool noteol = forward ? false : o < get_buffer_size (cur_bp);
  74. int pos = find_substr (forward ? get_buffer_post_point (cur_bp) : get_buffer_pre_point (cur_bp),
  75. s, ssize, forward, notbol, noteol, regexp,
  76. get_variable_bool ("case-fold-search") && no_upper (s, ssize, regexp));
  77. if (pos < 0)
  78. return false;
  79. goto_offset (pos + (forward ? get_buffer_pt (cur_bp) : 0));
  80. thisflag |= FLAG_NEED_RESYNC;
  81. return true;
  82. }
  83. static const_astr last_search = NULL;
  84. static le *
  85. do_search (bool forward, bool regexp, const_astr pattern)
  86. {
  87. le * ok = leNIL;
  88. if (pattern == NULL)
  89. pattern = minibuf_read ("%s%s: ", last_search ? astr_cstr (last_search) : NULL,
  90. regexp ? "RE search" : "Search", forward ? "" : " backward");
  91. if (pattern == NULL)
  92. return FUNCALL (keyboard_quit);
  93. if (astr_len (pattern) != 0)
  94. {
  95. last_search = pattern;
  96. if (!search (astr_cstr (pattern), forward, regexp))
  97. minibuf_error ("Search failed: \"%s\"", astr_cstr (pattern));
  98. else
  99. ok = leT;
  100. }
  101. return ok;
  102. }
  103. DEFUN_ARGS ("search-forward", search_forward,
  104. STR_ARG (pattern))
  105. /*+
  106. Search forward from point for the user specified text.
  107. +*/
  108. {
  109. STR_INIT (pattern);
  110. ok = do_search (true, false, pattern);
  111. }
  112. END_DEFUN
  113. DEFUN_ARGS ("search-backward", search_backward,
  114. STR_ARG (pattern))
  115. /*+
  116. Search backward from point for the user specified text.
  117. +*/
  118. {
  119. STR_INIT (pattern);
  120. ok = do_search (false, false, pattern);
  121. }
  122. END_DEFUN
  123. DEFUN_ARGS ("search-forward-regexp", search_forward_regexp,
  124. STR_ARG (pattern))
  125. /*+
  126. Search forward from point for regular expression REGEXP.
  127. +*/
  128. {
  129. STR_INIT (pattern);
  130. ok = do_search (true, true, pattern);
  131. }
  132. END_DEFUN
  133. DEFUN_ARGS ("search-backward-regexp", search_backward_regexp,
  134. STR_ARG (pattern))
  135. /*+
  136. Search backward from point for match for regular expression REGEXP.
  137. +*/
  138. {
  139. STR_INIT (pattern);
  140. ok = do_search (false, true, pattern);
  141. }
  142. END_DEFUN
  143. /*
  144. * Incremental search engine.
  145. */
  146. static le *
  147. isearch (int forward, int regexp)
  148. {
  149. Marker old_mark = copy_marker (get_buffer_mark (get_window_bp (cur_wp)));
  150. set_buffer_isearch (get_window_bp (cur_wp), true);
  151. int last = true;
  152. astr pattern = astr_new ();
  153. size_t start = get_buffer_pt (cur_bp), cur = start;
  154. for (;;)
  155. {
  156. /* Make the minibuf message. */
  157. astr buf = astr_fmt ("%sI-search%s: %s",
  158. (last ?
  159. (regexp ? "Regexp " : "") :
  160. (regexp ? "Failing regexp " : "Failing ")),
  161. forward ? "" : " backward",
  162. astr_cstr (pattern));
  163. /* Regex error. */
  164. if (re_find_err)
  165. {
  166. if ((strncmp (re_find_err, "Premature ", 10) == 0) ||
  167. (strncmp (re_find_err, "Unmatched ", 10) == 0) ||
  168. (strncmp (re_find_err, "Invalid ", 8) == 0))
  169. {
  170. re_find_err = "incomplete input";
  171. }
  172. astr_cat (buf, astr_fmt (" [%s]", re_find_err));
  173. re_find_err = NULL;
  174. }
  175. minibuf_write ("%s", astr_cstr (buf));
  176. int c = getkey (GETKEY_DEFAULT);
  177. if (c == KBD_CANCEL)
  178. {
  179. goto_offset (start);
  180. thisflag |= FLAG_NEED_RESYNC;
  181. /* Quit. */
  182. FUNCALL (keyboard_quit);
  183. /* Restore old mark position. */
  184. if (get_buffer_mark (cur_bp))
  185. unchain_marker (get_buffer_mark (cur_bp));
  186. set_buffer_mark (cur_bp, copy_marker (old_mark));
  187. break;
  188. }
  189. else if (c == KBD_BS)
  190. {
  191. if (astr_len (pattern) > 0)
  192. {
  193. astr_truncate (pattern, astr_len (pattern) - 1);
  194. cur = start;
  195. goto_offset (start);
  196. thisflag |= FLAG_NEED_RESYNC;
  197. }
  198. else
  199. ding ();
  200. }
  201. else if (c & KBD_CTRL && (c & 0xff) == 'q')
  202. {
  203. minibuf_write ("%s^Q-", astr_cstr (buf));
  204. astr_cat_char (pattern, getkey_unfiltered (GETKEY_DEFAULT));
  205. }
  206. else if (c & KBD_CTRL && ((c & 0xff) == 'r' || (c & 0xff) == 's'))
  207. {
  208. /* Invert direction. */
  209. if ((c & 0xff) == 'r')
  210. forward = false;
  211. else if ((c & 0xff) == 's')
  212. forward = true;
  213. if (astr_len (pattern) > 0)
  214. {
  215. /* Find next match. */
  216. cur = get_buffer_pt (cur_bp);
  217. /* Save search string. */
  218. last_search = astr_cpy (astr_new (), pattern);
  219. }
  220. else if (last_search != NULL)
  221. astr_cpy (pattern, last_search);
  222. }
  223. else if (c & KBD_META || c & KBD_CTRL || c > KBD_TAB)
  224. {
  225. if (c == KBD_RET && astr_len (pattern) == 0)
  226. do_search (forward, regexp, NULL);
  227. else
  228. {
  229. if (astr_len (pattern) > 0)
  230. {
  231. /* Save mark. */
  232. set_mark ();
  233. set_marker_o (get_buffer_mark (cur_bp), start);
  234. /* Save search string. */
  235. last_search = astr_cpy (astr_new (), pattern);
  236. minibuf_write ("Mark saved when search started");
  237. }
  238. else
  239. minibuf_clear ();
  240. if (c != KBD_RET)
  241. ungetkey (c);
  242. }
  243. break;
  244. }
  245. else
  246. astr_cat_char (pattern, c);
  247. if (astr_len (pattern) > 0)
  248. {
  249. goto_offset (cur);
  250. last = search (astr_cstr (pattern), forward, regexp);
  251. }
  252. else
  253. last = true;
  254. if (thisflag & FLAG_NEED_RESYNC)
  255. {
  256. window_resync (cur_wp);
  257. term_redisplay ();
  258. }
  259. }
  260. /* done */
  261. set_buffer_isearch (get_window_bp (cur_wp), false);
  262. if (old_mark)
  263. unchain_marker (old_mark);
  264. return leT;
  265. }
  266. DEFUN ("isearch-forward", isearch_forward)
  267. /*+
  268. Do incremental search forward.
  269. With a prefix argument, do an incremental regular expression search instead.
  270. As you type characters, they add to the search string and are found.
  271. Type return to exit, leaving point at location found.
  272. Type @kbd{C-s} to search again forward, @kbd{C-r} to search again backward.
  273. @kbd{C-g} when search is successful aborts and moves point to starting point.
  274. +*/
  275. {
  276. ok = isearch (true, lastflag & FLAG_SET_UNIARG);
  277. }
  278. END_DEFUN
  279. DEFUN ("isearch-backward", isearch_backward)
  280. /*+
  281. Do incremental search backward.
  282. With a prefix argument, do a regular expression search instead.
  283. As you type characters, they add to the search string and are found.
  284. Type return to exit, leaving point at location found.
  285. Type @kbd{C-r} to search again backward, @kbd{C-s} to search again forward.
  286. @kbd{C-g} when search is successful aborts and moves point to starting point.
  287. +*/
  288. {
  289. ok = isearch (false, lastflag & FLAG_SET_UNIARG);
  290. }
  291. END_DEFUN
  292. DEFUN ("isearch-forward-regexp", isearch_forward_regexp)
  293. /*+
  294. Do incremental search forward for regular expression.
  295. With a prefix argument, do a regular string search instead.
  296. Like ordinary incremental search except that your input
  297. is treated as a regexp. See @kbd{M-x isearch-forward} for more info.
  298. +*/
  299. {
  300. ok = isearch (true, !(lastflag & FLAG_SET_UNIARG));
  301. }
  302. END_DEFUN
  303. DEFUN ("isearch-backward-regexp", isearch_backward_regexp)
  304. /*+
  305. Do incremental search backward for regular expression.
  306. With a prefix argument, do a regular string search instead.
  307. Like ordinary incremental search except that your input
  308. is treated as a regexp. See @kbd{M-x isearch-backward} for more info.
  309. +*/
  310. {
  311. ok = isearch (false, !(lastflag & FLAG_SET_UNIARG));
  312. }
  313. END_DEFUN
  314. /*
  315. * Check the case of a string.
  316. * Returns 2 if it is all upper case, 1 if just the first letter is,
  317. * and 0 otherwise.
  318. */
  319. static _GL_ATTRIBUTE_PURE int
  320. check_case (astr as)
  321. {
  322. size_t i;
  323. for (i = 0; i < astr_len (as) && isupper ((int) astr_get (as, i)); i++)
  324. ;
  325. if (i == astr_len (as))
  326. return 2;
  327. else if (i == 1)
  328. for (; i < astr_len (as) && !isupper ((int) astr_get (as, i)); i++)
  329. ;
  330. return i == astr_len (as);
  331. }
  332. DEFUN ("query-replace", query_replace)
  333. /*+
  334. Replace occurrences of a string with other text.
  335. As each match is found, the user must type a character saying
  336. what to do with it.
  337. +*/
  338. {
  339. const_astr find = minibuf_read ("Query replace string: ", "");
  340. if (find == NULL)
  341. return FUNCALL (keyboard_quit);
  342. if (astr_len (find) == 0)
  343. return leNIL;
  344. bool find_no_upper = no_upper (astr_cstr (find), astr_len (find), false);
  345. const_astr repl = minibuf_read ("Query replace `%s' with: ", "", astr_cstr (find));
  346. if (repl == NULL)
  347. return FUNCALL (keyboard_quit);
  348. bool noask = false;
  349. size_t count = 0;
  350. while (search (astr_cstr (find), true, false))
  351. {
  352. int c = ' ';
  353. if (!noask)
  354. {
  355. if (thisflag & FLAG_NEED_RESYNC)
  356. window_resync (cur_wp);
  357. minibuf_write ("Query replacing `%s' with `%s' (y, n, !, ., q)? ", astr_cstr (find),
  358. astr_cstr (repl));
  359. c = getkey (GETKEY_DEFAULT);
  360. minibuf_clear ();
  361. if (c == 'q') /* Quit immediately. */
  362. break;
  363. else if (c == KBD_CANCEL)
  364. {
  365. ok = FUNCALL (keyboard_quit);
  366. break;
  367. }
  368. else if (c == '!')
  369. noask = true;
  370. }
  371. if (c == KBD_RET || c == ' ' || c == 'y' || c == 'Y' || c == '.' || c == '!')
  372. { /* Perform replacement. */
  373. ++count;
  374. const_astr case_repl = repl;
  375. Region r = region_new (get_buffer_pt (cur_bp) - astr_len (find), get_buffer_pt (cur_bp));
  376. if (find_no_upper && get_variable_bool ("case-replace"))
  377. {
  378. int case_type = check_case (estr_get_as (get_buffer_region (cur_bp, r)));
  379. if (case_type != 0)
  380. case_repl = astr_recase (astr_cpy (astr_new (), repl),
  381. case_type == 1 ? case_capitalized : case_upper);
  382. }
  383. Marker m = point_marker ();
  384. goto_offset (get_region_start (r));
  385. replace_estr (astr_len (find), estr_new_astr (case_repl));
  386. goto_offset (get_marker_o (m));
  387. unchain_marker (m);
  388. if (c == '.') /* Replace and quit. */
  389. break;
  390. }
  391. else if (!(c == KBD_RET || c == KBD_DEL || c == 'n' || c == 'N'))
  392. {
  393. ungetkey (c);
  394. ok = false;
  395. break;
  396. }
  397. }
  398. if (thisflag & FLAG_NEED_RESYNC)
  399. window_resync (cur_wp);
  400. if (ok)
  401. minibuf_write ("Replaced %zu occurrences", count);
  402. }
  403. END_DEFUN