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