1
2 /******************************************************************************
3 * MODULE : edit_search.cpp
4 * DESCRIPTION: search and query replace
5 * COPYRIGHT : (C) 1999 Joris van der Hoeven
6 *******************************************************************************
7 * This software falls under the GNU general public license version 3 or later.
8 * It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE
9 * in the root directory or <http://www.gnu.org/licenses/gpl-3.0.html>.
10 ******************************************************************************/
11
12 #include "Replace/edit_replace.hpp"
13 #include "Interface/edit_interface.hpp"
14 #include "drd_std.hpp"
15 #include "drd_mode.hpp"
16 #include "analyze.hpp"
17
18 /******************************************************************************
19 * Constructor and destructor
20 ******************************************************************************/
21
edit_replace_rep()22 edit_replace_rep::edit_replace_rep () {}
~edit_replace_rep()23 edit_replace_rep::~edit_replace_rep () {}
24
25 /******************************************************************************
26 * Structural search routines
27 ******************************************************************************/
28
29 bool
inside(string what)30 edit_replace_rep::inside (string what) {
31 return inside (make_tree_label (what));
32 }
33
34 path
search_upwards(string what)35 edit_replace_rep::search_upwards (string what) {
36 return search_upwards (make_tree_label (what));
37 }
38
39 bool
inside(tree_label l)40 edit_replace_rep::inside (tree_label l) {
41 return !is_nil (search_upwards (l));
42 }
43
44 path
search_upwards(tree_label l)45 edit_replace_rep::search_upwards (tree_label l) {
46 path p= path_up (tp, 2);
47 while (!is_func (subtree (et, p), l)) {
48 if (p == rp) return path ();
49 p= path_up (p);
50 }
51 return p;
52 }
53
54 path
search_parent_upwards(tree_label l,int & last)55 edit_replace_rep::search_parent_upwards (tree_label l, int& last) {
56 path p= path_up (tp);
57 while (!is_func (subtree (et, path_up (p)), l)) {
58 p= path_up (p);
59 if (p == rp) {
60 last= -1;
61 return path ();
62 }
63 }
64 last= last_item (p);
65 return path_up (p);
66 }
67
68 path
search_parent_upwards(tree_label l)69 edit_replace_rep::search_parent_upwards (tree_label l) {
70 int last;
71 path p= search_parent_upwards (l, last);
72 return p * last;
73 }
74
75 bool
inside_with(string var,string val)76 edit_replace_rep::inside_with (string var, string val) {
77 return !is_nil (search_upwards_with (var, val));
78 }
79
80 path
search_upwards_with(string var,string val)81 edit_replace_rep::search_upwards_with (string var, string val) {
82 path p= path_up (tp);
83 while (true) {
84 p= path_up (p);
85 if (p == rp) return path ();
86 tree st= subtree (et, p);
87 if (is_func (st, WITH) && (st[0] == var) && (st[1] == val)) return p;
88 }
89 }
90
91 string
inside_which(tree t)92 edit_replace_rep::inside_which (tree t) {
93 path p= search_upwards_in_set (t);
94 if ((p == rp) || is_nil (p)) return "";
95 tree st= subtree (et, p);
96 if (is_func (st, COMPOUND)) return as_string (st[0]);
97 else return as_string (L(st));
98 }
99
100 path
search_upwards_in_set(tree t)101 edit_replace_rep::search_upwards_in_set (tree t) {
102 if (!is_tuple (t)) return path ();
103 int i, n=N(t);
104 path p= path_up (tp);
105 while (true) {
106 p= path_up (p);
107 if (p == rp) return path ();
108 tree st= subtree (et, p);
109 for (i=0; i<n; i++) {
110 if (is_atomic (t[i])) {
111 string s= t[i]->label;
112 if (is_quoted (s)) s= raw_unquote (s);
113 if (std_contains (s)) {
114 tree_label l= as_tree_label (s);
115 if (is_func (st, l)) return p;
116 }
117 else if (is_compound (st, s)) return p;
118 }
119 else if (is_func (st, L(t))) return p;
120 }
121 }
122 }
123
124 path
search_previous_compound(path init,string which)125 edit_replace_rep::search_previous_compound (path init, string which) {
126 path p= init;
127 while (true) {
128 if (p == rp) return init;
129 if (last_item (p) == 0) p= path_up (p);
130 else {
131 p= path_dec (p);
132 while (true) {
133 tree st= subtree (et, p);
134 if (arity (st) == 0) break;
135 p= p * (N(st)-1);
136 }
137 }
138 if (drd->is_accessible_path (et, p) &&
139 is_compound (subtree (et, p), which))
140 return p;
141 }
142 }
143
144 path
search_next_compound(path init,string which)145 edit_replace_rep::search_next_compound (path init, string which) {
146 path p= init;
147 while (true) {
148 if (p == rp) return init;
149 if (last_item (p) == (N (subtree (et, path_up (p))) - 1)) p= path_up (p);
150 else {
151 p= path_inc (p);
152 while (true) {
153 tree st= subtree (et, p);
154 if (arity (st) == 0) break;
155 p= p * 0;
156 }
157 }
158 if (drd->is_accessible_path (et, p) &&
159 is_compound (subtree (et, p), which))
160 return p;
161 }
162 }
163
164 /******************************************************************************
165 * Test whether we found a match
166 ******************************************************************************/
167
168 static bool
test_match(tree t,tree pat)169 test_match (tree t, tree pat) {
170 // FIXME: we use empty strings for wildcards now
171 // we should support real wildcards and regular expressions later
172 if (pat == "") return true;
173 else if (is_atomic (t) || is_atomic (pat)) return t == pat;
174 else if ((L(t) != L(pat)) || (N(t) != N(pat))) return false;
175 else {
176 int i, n= N(t);
177 for (i=0; i<n; i++)
178 if (!test_match (t[i], pat[i]))
179 return false;
180 return true;
181 }
182 }
183
184 path
test_sub(path p,tree t)185 edit_replace_rep::test_sub (path p, tree t) {
186 // cout << "Test " << subtree (et, path_up (p))
187 // << " :: " << t << " at " << p << "\n";
188 if (is_concat (t) && (N(t) > 1)) {
189 if (N(p) <= 1) return p;
190 tree st= subtree (et, path_up (p, 2));
191 if (!is_concat (st)) return p;
192 int i, l= last_item (path_up (p)), n= N(t);
193 if (N(st) < (n + l)) return p;
194 if ((t[0] != "") && (p == end (et, path_up (p)))) return p;
195 if (test_sub (p, t[0]) != end (et, path_up (p))) return p;
196 for (i=1; i<n-1; i++)
197 if (!test_match (st[l+i], t[i]))
198 return p;
199 path r= path_up (p, 2) * path (l+n-1, start (st[l+n-1], path ()));
200 path q= test_sub (r, t[n-1]);
201 if (q == r) return p;
202 return q;
203 }
204 else {
205 tree st= subtree (et, path_up (p));
206 if (is_compound (t)) {
207 if (!is_compound (st)) return p;
208 // cout << "Test with " << st << "\n";
209 if (last_item (p) != 0) return p;
210 if (!test_match (st, t)) return p;
211 return path_inc (p);
212 }
213 else {
214 if (is_compound (st)) return p;
215 int l= last_item (p);
216 // cout << "Test with " << st->label << " at " << l << "\n";
217 if (N(st->label) < (N(t->label) + l)) return p;
218 if (st->label (l, l + N(t->label)) != t->label) return p;
219 return path_add (p, N (t->label));
220 }
221 }
222 }
223
224 path
test(path p,tree t)225 edit_replace_rep::test (path p, tree t) {
226 path q= test_sub (p, t);
227 if (q == p) return p;
228 string mode= as_string (get_env_value (MODE, p));
229 string lan = as_string (get_env_value (MODE_LANGUAGE (mode), p));
230 if (search_mode != mode) return p;
231 if (search_lan != lan) return p;
232 return q;
233 }
234
235 /******************************************************************************
236 * Traversal of the edit tree
237 ******************************************************************************/
238
239 void
step_ascend(bool forward)240 edit_replace_rep::step_ascend (bool forward) {
241 // cout << "Step ascend at " << search_at << "\n";
242 search_at= path_add (path_up (search_at), forward? 1: -1);
243 tree st;
244 int l;
245 while (true) {
246 st= subtree (et, path_up (search_at));
247 l = last_item (search_at);
248 // cout << " st= " << st << "\n";
249 // cout << " l = " << l << "\n";
250 if ((l<0) || (l>=N(st)) || drd->is_accessible_child (st, l)) break;
251 search_at= path_add (search_at, forward? 1: -1);
252 }
253
254 if (forward) {
255 if (l == N(st)) {
256 if (is_atom (search_at / rp)) search_at= rp;
257 else step_ascend (forward);
258 }
259 else step_descend (forward);
260 }
261 else {
262 if (l == -1) {
263 if (is_atom (search_at / rp)) search_at= rp;
264 else step_ascend (forward);
265 }
266 else step_descend (forward);
267 }
268 }
269
270 void
step_descend(bool forward)271 edit_replace_rep::step_descend (bool forward) {
272 // cout << "Step descend at " << search_at << "\n";
273 tree st= subtree (et, search_at);
274 // cout << " st= " << st << "\n";
275 int last= (is_atomic (st)? N(st->label): N(st)-1);
276 search_at= search_at * (forward? 0: last);
277 if (is_format (st))
278 step_descend (forward);
279 }
280
281 void
step_horizontal(bool forward)282 edit_replace_rep::step_horizontal (bool forward) {
283 // cout << "Step horizontal at " << search_at << "\n";
284 if (search_at == rp) step_descend (forward);
285 else {
286 tree st = subtree (et, path_up (search_at));
287 int l = last_item (search_at);
288
289 // cout << " st= " << st << "\n";
290 if (forward) {
291 if (l == right_index (st)) step_ascend (forward);
292 else {
293 if (is_atomic (st)) {
294 if (st->label[l]=='<') {
295 string s= st->label;
296 while ((l<N(s)) && (s[l]!='>')) l++;
297 if (l<N(s)) l++;
298 search_at= path_up (search_at) * l;
299 }
300 else search_at= path_inc (search_at);
301 }
302 else {
303 int i;
304 for (i=l; i<N(st); i++)
305 if (drd->is_accessible_child (st, i)) {
306 search_at= path_up (search_at) * i;
307 step_descend (forward);
308 return;
309 }
310 step_ascend (forward);
311 }
312 }
313 }
314 else {
315 if (l == 0) step_ascend (forward);
316 else {
317 if (is_atomic (st)) {
318 if (st->label[l-1]=='>') {
319 string s= st->label;
320 l--;
321 while ((l>0) && (s[l]!='<')) l--;
322 search_at= path_up (search_at) * l;
323 }
324 else search_at= path_dec (search_at);
325 }
326 else {
327 int i;
328 for (i=l; i>=0; i--)
329 if (drd->is_accessible_child (st, i)) {
330 search_at= path_up (search_at) * i;
331 step_descend (forward);
332 return;
333 }
334 step_ascend (forward);
335 }
336 }
337 }
338 }
339 }
340
341 void
next_match(bool forward)342 edit_replace_rep::next_match (bool forward) {
343 // cout << "Next match at " << search_at << "\n";
344 while (true) {
345 if (search_at == rp) {
346 set_selection (tp, tp);
347 notify_change (THE_SELECTION);
348 return;
349 }
350 search_end= test (search_at, search_what);
351 if (search_end != search_at) {
352 go_to (copy (search_end));
353 show_cursor_if_hidden ();
354 set_selection (search_at, search_end);
355 notify_change (THE_SELECTION);
356 return;
357 }
358 int new_mode= DRD_ACCESS_HIDDEN;
359 if (get_init_string (MODE) == "src" || inside ("show-preamble"))
360 new_mode= DRD_ACCESS_SOURCE;
361 int old_mode= set_access_mode (new_mode);
362 step_horizontal (forward);
363 set_access_mode (old_mode);
364 }
365 }
366
367 /******************************************************************************
368 * Searching
369 ******************************************************************************/
370
371 void
search_start(bool flag)372 edit_replace_rep::search_start (bool flag) {
373 string r ("forward search");
374 if (!flag) r= "backward search";
375
376 forward = flag;
377 search_mode = copy (get_env_string (MODE));
378 search_lan = copy (get_env_string (MODE_LANGUAGE (search_mode)));
379 search_at = tp;
380 search_what = tree ("");
381 where_stack = list<path> ();
382 what_stack = tree ("");
383 set_input_mode (INPUT_SEARCH);
384 set_message ("Searching", r);
385 }
386
387 void
search_next(bool forward)388 edit_replace_rep::search_next (bool forward) {
389 string r ("forward search");
390 if (!forward) r= "backward search";
391 string w= as_string (search_what);
392 if (is_compound (search_what)) w= "compound expression";
393
394 next_match (forward);
395 if (search_at == rp) {
396 set_message (concat ("No more matches for ", verbatim (w)), r);
397 beep ();
398 }
399 else set_message (concat ("Searching ", verbatim (w)), r);
400 }
401
402 void
search_next(tree what,bool forward,bool step)403 edit_replace_rep::search_next (tree what, bool forward, bool step) {
404 where_stack= list<path> (copy (search_at), where_stack);
405 what_stack = tuple (copy (search_what), what_stack);
406 search_what= copy (what);
407 if (step) step_horizontal (forward);
408 search_next (forward);
409 }
410
411 void
search_stop()412 edit_replace_rep::search_stop () {
413 tree t= tuple ("texmacs", search_what, search_mode, search_lan);
414 set_input_normal ();
415 if (search_what != "")
416 selection_raw_set ("search", t);
417 }
418
419 void
search_button_next()420 edit_replace_rep::search_button_next () {
421 set_input_mode (INPUT_SEARCH);
422 search_next (search_what, forward, true);
423 }
424
425 bool
search_keypress(string s)426 edit_replace_rep::search_keypress (string s) {
427 set_message ("", "");
428 if (s == "space") s= " ";
429 if (N(s) == 1) {
430 if (is_atomic (search_what))
431 search_next (as_string (search_what) * s, forward, false);
432 }
433 else {
434 if ((s == "left") || (s == "right") ||
435 (s == "up") || (s == "down") ||
436 (s == "pageup") || (s == "pagedown") ||
437 (s == "begin") || (s == "end")) {
438 search_stop ();
439 return false;
440 }
441 else if ((s == "C-c") || (s == "C-g"))
442 search_stop ();
443 else if ((s == "next") || (s == "previous")) {
444 if (search_what == "") {
445 tree t= selection_raw_get ("search");
446 if (is_tuple (t, "texmacs", 3) &&
447 (t[1] != "") &&
448 (t[2] == search_mode) &&
449 (t[3] == search_lan))
450 search_next (t[1], s != "previous", true);
451 }
452 else search_next (search_what, s != "previous", true);
453 }
454 else if ((s == "delete") || (s == "backspace")) {
455 if (is_nil (where_stack))
456 search_stop ();
457 else if (is_atom (where_stack)) {
458 go_to (where_stack->item);
459 search_stop ();
460 }
461 else {
462 search_at = where_stack->item;
463 where_stack= where_stack->next;
464 search_what= what_stack[0];
465 what_stack = what_stack[1];
466 search_next (forward);
467 }
468 }
469 else if ((s == "C-left") || (s == "C-right")) {
470 // FIXME: integrate better with general searching mechanism
471 path p= path_up (search_at);
472 while ((p != rp) && (!is_extension (subtree (et, path_up (p)))))
473 p= path_up (p);
474 if (p == rp) return true;
475 path r= path_up (p);
476 string w= as_string (L (subtree (et, r)));
477 path q= (s == "C-right") ?
478 search_next_compound (r, w) :
479 search_previous_compound (r, w);
480 if (q == r) {
481 set_message ("No more matches", "search similar structure");
482 beep ();
483 }
484 else {
485 q= q * min (N (subtree (et, q)) - 1, last_item (p));
486 search_at= end (et, q);
487 go_to (copy (search_at));
488 }
489 }
490 }
491 return true;
492 }
493
494 /******************************************************************************
495 * Replacing
496 ******************************************************************************/
497
498 void
replace_start(tree what,tree by,bool flag)499 edit_replace_rep::replace_start (tree what, tree by, bool flag) {
500 forward = flag;
501 search_mode = copy (get_env_string (MODE));
502 search_lan = copy (get_env_string (MODE_LANGUAGE (search_mode)));
503 search_at = tp;
504 search_what = copy (what);
505 replace_by = copy (by);
506 nr_replaced = 0;
507 set_input_mode (INPUT_REPLACE);
508 if (search_what == "") {
509 tree t= selection_raw_get ("search");
510 if (is_tuple (t, "texmacs", 3) &&
511 (t[1] != "") &&
512 (t[2] == search_mode) &&
513 (t[3] == search_lan))
514 search_what= t[1];
515 t= selection_raw_get ("replace");
516 if (is_tuple (t, "texmacs", 3) &&
517 (t[1] != "") &&
518 (t[2] == search_mode) &&
519 (t[3] == search_lan))
520 replace_by= t[1];
521 }
522 replace_next ();
523 }
524
525 void
replace_next()526 edit_replace_rep::replace_next () {
527 string r ("forward replace");
528 if (!forward) r= "backward replace";
529
530 next_match (forward);
531 if (search_at == rp) {
532 tree l= concat ("Replaced ", as_string (nr_replaced), " occurrences");
533 if (nr_replaced == 0) l= "No matches found";
534 if (nr_replaced == 1) l= "Replaced one occurrence";
535 set_message (l, r);
536 beep ();
537 set_input_normal ();
538 }
539 else set_message ("Replace (y,n,a)?", r);
540 }
541
542 bool
replace_keypress(string s)543 edit_replace_rep::replace_keypress (string s) {
544 set_message ("", "");
545 if (s == "space") s= " ";
546 if ((s == "C-c") || (s == "C-g") || (s == "escape"))
547 set_input_normal ();
548 else if (s == "y") {
549 nr_replaced++;
550 go_to (copy (search_end));
551 cut (search_at, search_end);
552 insert_tree (copy (replace_by));
553 search_at= copy (tp);
554 replace_next ();
555 }
556 else if (s == "n") {
557 step_horizontal (forward);
558 replace_next ();
559 }
560 else if (s == "a" || s == "!") {
561 while (search_at != rp) {
562 nr_replaced++;
563 go_to (copy (search_end));
564 cut (search_at, search_end);
565 insert_tree (copy (replace_by));
566 search_at= copy (tp);
567 replace_next ();
568 }
569 }
570 return true;
571 }
572