1
2 /******************************************************************************
3 * MODULE : edit_dynamic.cpp
4 * DESCRIPTION: editing dynamic content
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 "edit_dynamic.hpp"
13 #include "tree_analyze.hpp"
14
15 /******************************************************************************
16 * Constructors and destructors
17 ******************************************************************************/
18
edit_dynamic_rep()19 edit_dynamic_rep::edit_dynamic_rep () {}
~edit_dynamic_rep()20 edit_dynamic_rep::~edit_dynamic_rep () {}
21
22 /******************************************************************************
23 * Subroutines for inactive content
24 ******************************************************************************/
25
26 static bool env_locked = false;
27 static bool env_in_source= false;
28
29 bool
in_source()30 edit_dynamic_rep::in_source () {
31 // FIXME: we use a very dirty trick to "lock the environment",
32 // so that no new look-ups are made during modifications of et or tp.
33 // In fact, we would need to retypeset the document in order to
34 // get a correct value.
35 if (!env_locked)
36 env_in_source= get_env_string (MODE) == "src";
37 return env_in_source;
38 }
39
40 path
find_dynamic(path p)41 edit_dynamic_rep::find_dynamic (path p) {
42 path parent= path_up (p);
43 if (!(rp < parent)) return path ();
44 if (drd->is_dynamic (subtree (et, parent))) return p;
45 return find_dynamic (parent);
46 }
47
48 /******************************************************************************
49 * Making general compound objects
50 ******************************************************************************/
51
52 bool
is_multi_paragraph_macro(tree t)53 edit_dynamic_rep::is_multi_paragraph_macro (tree t) {
54 int n= arity (t);
55 if (is_document (t) || is_func (t, PARA) || is_func (t, SURROUND))
56 return true;
57 if (is_func (t, MACRO) || is_func (t, WITH) ||
58 is_func (t, LOCUS) ||
59 is_func (t, CANVAS) || is_func (t, ORNAMENT))
60 return is_multi_paragraph_macro (t [n-1]);
61 if (is_extension (t) && (!is_compound (t, "footnote"))) {
62 int i;
63 for (i=1; i<n; i++)
64 if (is_multi_paragraph_macro (t[i]))
65 return true;
66 tree f= get_env_value (t[0]->label);
67 return is_multi_paragraph_macro (f);
68 }
69 return false;
70 }
71
72 static bool
contains_table_format(tree t,tree var)73 contains_table_format (tree t, tree var) {
74 // FIXME: this should go into the DRD
75 if (is_atomic (t)) return false;
76 else {
77 int i, n= N(t);
78 for (i=0; i<n; i++)
79 if (contains_table_format (t[i], var))
80 return true;
81 return is_func (t, TFORMAT) && (t[N(t)-1] == tree (ARG, var));
82 }
83 }
84
85 void
make_compound(tree_label l,int n=-1)86 edit_dynamic_rep::make_compound (tree_label l, int n= -1) {
87 //cout << "Make compound " << as_string (l) << ", " << n << "\n";
88 eval ("(use-modules (generic generic-edit))");
89 if (n == -1) {
90 for (n=0; true; n++) {
91 if (drd->correct_arity (l, n) &&
92 ((n>0) || (drd->get_arity_mode (l) == ARITY_NORMAL))) break;
93 if (n == 100) return;
94 }
95 }
96
97 tree t (l, n);
98 path p (0, 0);
99 int acc=0;
100 for (; acc<n; acc++)
101 if (drd->is_accessible_child (t, acc))
102 break;
103 if (acc<n) p->item= acc;
104 if (n == 0) insert_tree (t, 1);
105 else if (is_with_like (t) && as_bool (call ("with-like-check-insert", t)));
106 else {
107 string s= as_string (l);
108 tree f= get_env_value (s);
109 bool block_macro= (N(f) == 2) && is_multi_paragraph_macro (f);
110 bool table_macro= (N(f) == 2) && contains_table_format (f[1], f[0]);
111 // FIXME: why do we take the precaution N(f) == 2 ?
112 if (s == "explain") block_macro= true;
113
114 tree sel= "";
115 if (selection_active_small () ||
116 (block_macro && selection_active_normal ()))
117 sel= selection_get_cut ();
118 if ((block_macro && (!table_macro)) ||
119 (l == make_tree_label ("footnote")))
120 {
121 t[0]= tree (DOCUMENT, "");
122 p = path (0, 0, 0);
123 }
124 if (!drd->all_accessible (l))
125 if (get_init_string (MODE) != "src" && !inside ("show-preamble")) {
126 t= tree (INACTIVE, t);
127 p= path (0, p);
128 }
129 insert_tree (t, p);
130 if (table_macro) make_table (1, 1);
131 if (sel != "") insert_tree (sel, end (sel));
132
133 tree mess= concat ();
134 if (drd->get_arity_mode (l) != ARITY_NORMAL)
135 mess= concat (kbd ("A-right"), ": insert argument");
136 if (!drd->all_accessible (l)) {
137 if (mess != "") mess << ", ";
138 mess << kbd ("return") << ": activate";
139 }
140 if (mess == concat ()) mess= "Move to the right when finished";
141 set_message (mess, drd->get_name (l));
142 }
143 }
144
145 void
activate()146 edit_dynamic_rep::activate () {
147 path p= search_upwards (INACTIVE);
148 if (is_nil (p)) return;
149 tree st= subtree (et, p * 0);
150
151 if (is_func (st, COMPOUND) && is_atomic (st[0])) {
152 tree u (make_tree_label (st[0]->label));
153 u << A (st (1, N(st)));
154 assign (p, u);
155 call ("notify-activated", object (subtree (et, p)));
156 go_to (end (et, p));
157 correct (path_up (p));
158 }
159 else {
160 bool acc= (p < path_up (tp) && drd->is_accessible_child (st, tp[N(p)]));
161 remove_node (p * 0);
162 call ("notify-activated", object (subtree (et, p)));
163 if (!acc) go_to (end (et, p));
164 correct (path_up (p));
165 }
166 }
167
168 /******************************************************************************
169 * Inserting and removing arguments
170 ******************************************************************************/
171
172 void
go_to_argument(path p,bool start_flag)173 edit_dynamic_rep::go_to_argument (path p, bool start_flag) {
174 tree t= subtree (et, path_up (p));
175 bool inactive= is_func (subtree (et, path_up (p, 2)), INACTIVE);
176 int i= last_item (p), n= N(t);
177 if (i < 0) go_to_start (path_up (p, inactive? 2: 1));
178 else if (i >= n) go_to_end (path_up (p, inactive? 2: 1));
179 else {
180 if ((!drd->is_accessible_child (t, i)) &&
181 (!inactive) && (!in_source ()))
182 {
183 insert_node (path_up (p) * 0, INACTIVE);
184 call ("notify-disactivated", object (subtree (et, path_up (p) * 0)));
185 p= path_up (p) * path (0, i);
186 }
187 if (start_flag) go_to_start (p);
188 else go_to_end (p);
189 }
190 }
191
192 void
insert_argument(path p,bool forward)193 edit_dynamic_rep::insert_argument (path p, bool forward) {
194 tree t= subtree (et, path_up (p));
195 int i= last_item (p), n= N(t), d= 1;
196 if (is_func (t, WITH) ||
197 is_func (t, STYLE_WITH) ||
198 is_func (t, VAR_STYLE_WITH))
199 if (i == n-1) i--;
200 if ((!in_source ()) || drd->contains (as_string (L(t)))) {
201 if (forward) do i++; while ((i<=n) && (!drd->insert_point (L(t), i, n)));
202 else while ((i>=0) && (!drd->insert_point (L(t), i, n))) i--;
203 if ((i<0) || (i>n)) return;
204 while (!drd->correct_arity (L(t), n+d)) d++;
205 }
206 else if (forward) i++;
207 path q= path_up (p) * i;
208 tree ins (L(t), d);
209 insert (q, ins);
210 go_to_argument (q, forward);
211 }
212
213 void
insert_argument(bool forward)214 edit_dynamic_rep::insert_argument (bool forward) {
215 path p= find_dynamic (tp);
216 if (is_nil (p)) return;
217 if (p == tp) p= find_dynamic (path_up (tp));
218 if (is_nil (p)) return;
219 insert_argument (p, forward);
220 }
221
222 void
remove_empty_argument(path p,bool forward)223 edit_dynamic_rep::remove_empty_argument (path p, bool forward) {
224 tree t= subtree (et, path_up (p));
225 int i= last_item (p), j, d, n= N(t);
226 bool src_flag= in_source () && (!drd->contains (as_string (L(t))));
227
228 for (d=1; d<=n-i; d++)
229 if ((src_flag && (d==1)) ||
230 ((!src_flag) &&
231 drd->correct_arity (L(t), n-d) &&
232 drd->insert_point (L(t), i, n-d)))
233 {
234 bool flag= true;
235 for (j=0; j<d; j++)
236 flag= flag && is_empty (t[i+j]);
237 if (flag) {
238 bool old_locked= env_locked; env_locked= true;
239 remove (p, d);
240 if ((d == n) && is_mod_active_once (subtree (et, path_up (p, 2)))) {
241 remove_node (path_up (p, 2) * 0);
242 go_to_border (path_up (p, 2), forward);
243 }
244 else if (forward) go_to_argument (path_up (p) * i, true);
245 else go_to_argument (path_up (p) * (i-1), false);
246 env_locked= old_locked;
247 return;
248 }
249 else break;
250 }
251
252 bool flag= true;
253 for (j=0; j<n; j++)
254 flag= flag && is_empty (t[j]);
255 if (flag) {
256 assign (path_up (p), "");
257 tree st= subtree (et, path_up (p, 2));
258 if ((is_mod_active_once (st) || is_compound (st, "doc-inactive")) &&
259 (st[0] == "")) {
260 assign (path_up (p, 2), "");
261 correct (path_up (p, 3));
262 }
263 // FIXME: temporary hack for doc-data and doc-author-data
264 else if (is_compound (st, "doc-data") ||
265 is_compound (st, "abstract-data") ||
266 is_compound (st, "doc-author") ||
267 is_compound (st, "author-data")) {
268 if (N(st)==1) {
269 assign (path_up (p, 2), "");
270 correct (path_up (p, 3));
271 }
272 else {
273 int i= last_item (path_up (p)) + (forward? 0: -1);
274 remove (path_up (p), 1);
275 if (i<0) go_to_start (path_up (p, 2));
276 else go_to_border (path_up (p, 2) * i, forward);
277 }
278 }
279 else correct (path_up (p, 2));
280 return;
281 }
282
283 if (forward) go_to_argument (path_up (p) * (i+1), true);
284 else go_to_argument (path_up (p) * (i-1), false);
285 }
286
287 void
remove_argument(path p,bool forward)288 edit_dynamic_rep::remove_argument (path p, bool forward) {
289 tree t= subtree (et, path_up (p));
290 int i= last_item (p), n= N(t), d= 1;
291 if ((!in_source ()) || drd->contains (as_string (L(t)))) {
292 if (forward) do i++; while (i<=n && !drd->insert_point (L(t), i, n));
293 else while (i>=0 && !drd->insert_point (L(t), i, n)) i--;
294 if ((i<0) || (i>n)) return;
295 while (i>=d && !drd->correct_arity (L(t), n-d)) d++;
296 if (i<d || n<=d || !drd->insert_point (L(t), i-d, n-d)) return;
297 }
298 else {
299 if (forward) i++;
300 if (i<d || n<=d) return;
301 }
302 path q= path_up (p) * (i-d);
303 remove (q, d);
304 go_to_argument (q, forward);
305 }
306
307 void
remove_argument(bool forward)308 edit_dynamic_rep::remove_argument (bool forward) {
309 path p= find_dynamic (tp);
310 if (is_nil (p)) return;
311 if (p == tp) p= find_dynamic (path_up (tp));
312 if (is_nil (p)) return;
313 remove_argument (p, forward);
314 }
315
316 /******************************************************************************
317 * Backspace and delete
318 ******************************************************************************/
319
320 void
back_monolithic(path p)321 edit_dynamic_rep::back_monolithic (path p) {
322 if (!is_concat (subtree (et, path_up (p)))) assign (p, "");
323 else remove (p, 1);
324 correct (path_up (p));
325 }
326
327 void
back_general(path p,bool forward)328 edit_dynamic_rep::back_general (path p, bool forward) {
329 tree st= subtree (et, p);
330 int n= N(st);
331 if ((L(st) >= START_EXTENSIONS) && in_source () && (forward || (n == 0))) {
332 tree u (COMPOUND, copy (as_string (L(st))));
333 u << copy (A (st));
334 assign (p, u);
335 go_to_border (p * 0, forward);
336 }
337 else if (n==0) back_monolithic (p);
338 else if ((n==1) && is_func (st[0], DOCUMENT, 1) &&
339 (is_func (st[0][0], TFORMAT) || is_func (st[0][0], TABLE)))
340 back_table (p * path (0, 0), forward);
341 else if ((n==1) && (is_func (st[0], TFORMAT) || is_func (st[0], TABLE)))
342 back_table (p * 0, forward);
343 else go_to_argument (p * (forward? 0: n-1), forward);
344 }
345
346 void
back_in_general(tree t,path p,bool forward)347 edit_dynamic_rep::back_in_general (tree t, path p, bool forward) {
348 if (is_func (subtree (et, path_up (p, 2)), INACTIVE) || in_source ())
349 if ((L(t) >= START_EXTENSIONS) && (last_item (p) == 0) && (!forward)) {
350 bool src_flag= in_source () && (!drd->contains (as_string (L(t))));
351 tree u (COMPOUND, copy (as_string (L(t))));
352 if (is_empty (t[0]) && src_flag) u << A (copy (t (1, N(t))));
353 else u << A (copy (t));
354 assign (path_up (p), u);
355 go_to_end (p);
356 return;
357 }
358 remove_empty_argument (p, forward);
359 }
360
361 /******************************************************************************
362 * The WITH tag
363 ******************************************************************************/
364
365 static tree
remove_changes_in(tree t,string var)366 remove_changes_in (tree t, string var) {
367 if (is_atomic (t)) return t;
368 else if (is_func (t, WITH)) {
369 int i, n=N(t), k=(n-1)>>1;
370 if (k==1) {
371 tree r= remove_changes_in (t[2], var);
372 if (t[0] != var) r= tree (WITH, t[0], t[1], r);
373 return simplify_correct (r);
374 }
375 tree r (WITH);
376 for (i=0; i<k; i++)
377 if (t[i<<1] != var) r << t[i<<1] << t[(i<<1)+1];
378 r << remove_changes_in (t[i<<1], var);
379 return simplify_correct (r);
380 }
381 else if (is_format (t) || is_func (t, SURROUND)) {
382 int i, n= N(t);
383 tree r (t, n);
384 for (i=0; i<n; i++)
385 r[i]= remove_changes_in (t[i], var);
386 return simplify_correct (r);
387 }
388 else return t;
389 }
390
391 void
make_with(string var,string val)392 edit_dynamic_rep::make_with (string var, string val) {
393 if (selection_active_normal ()) {
394 tree t= remove_changes_in (selection_get (), var);
395 selection_cut ();
396 insert_tree (tree (WITH, var, val, t), path (2, end (t)));
397 }
398 else insert_tree (tree (WITH, var, val, ""), path (2, 0));
399 }
400
401 void
insert_with(path p,string var,tree val)402 edit_dynamic_rep::insert_with (path p, string var, tree val) {
403 tree st= subtree (et, p);
404 if (is_func (st, WITH)) {
405 int i, n= N(st)-1;
406 for (i=0; i<n; i+=2)
407 if (st[i] == var) {
408 assign (p * (i+1), copy (val));
409 return;
410 }
411 insert (p * n, copy (tree (WITH, var, val)));
412 }
413 else if ((rp < p) && is_func (subtree (et, path_up (p)), WITH))
414 insert_with (path_up (p), var, val);
415 else insert_node (p * 2, copy (tree (WITH, var, val)));
416 }
417
418 void
remove_with(path p,string var)419 edit_dynamic_rep::remove_with (path p, string var) {
420 tree st= subtree (et, p);
421 if (is_func (st, WITH)) {
422 int i, n= N(st)-1;
423 for (i=0; i<n; i+=2)
424 if (st[i] == var) {
425 remove (p * i, 2);
426 if (n == 2) remove_node (p * 0);
427 return;
428 }
429 }
430 else if ((rp < p) && is_func (subtree (et, path_up (p)), WITH))
431 remove_with (path_up (p), var);
432 }
433
434 void
back_in_with(tree t,path p,bool forward)435 edit_dynamic_rep::back_in_with (tree t, path p, bool forward) {
436 if (is_func (subtree (et, path_up (p, 2)), INACTIVE) ||
437 ((is_func (t, WITH) || is_func (t, LOCUS)) && in_source ()))
438 back_in_general (t, p, forward);
439 else if (t[N(t)-1] == "") {
440 assign (path_up (p), "");
441 correct (path_up (p, 2));
442 }
443 else go_to_border (path_up (p), !forward);
444 }
445
446 /******************************************************************************
447 * Style file editing
448 ******************************************************************************/
449
450 void
make_mod_active(tree_label l)451 edit_dynamic_rep::make_mod_active (tree_label l) {
452 if (selection_active_normal ()) {
453 tree t= selection_get ();
454 selection_cut ();
455 insert_tree (tree (l, t), path (0, end (t)));
456 }
457 else if ((l == VAR_STYLE_ONLY) || (l == VAR_ACTIVE) || (l == VAR_INACTIVE))
458 insert_tree (tree (l, ""), path (0, 0));
459 else {
460 path p= path_up (tp);
461 if (is_atomic (subtree (et, p))) p= path_up (p);
462 if (rp < p) insert_node (p * 0, l);
463 }
464 }
465
466 void
insert_style_with(path p,string var,string val)467 edit_dynamic_rep::insert_style_with (path p, string var, string val) {
468 if (!(rp < p)) return;
469 tree st= subtree (et, path_up (p));
470 if (is_func (st, STYLE_WITH)) {
471 int i, n= N(st);
472 for (i=n-1; i>=0; i-=2)
473 if (st[i] == var) {
474 assign (path_up (p) * (i+1), copy (val));
475 return;
476 }
477 insert (path_up (p) * (n-1), tree (STYLE_WITH, copy (var), copy (val)));
478 }
479 else insert_node (p * 2, copy (tree (STYLE_WITH, var, val)));
480 }
481
482 void
make_style_with(string var,string val)483 edit_dynamic_rep::make_style_with (string var, string val) {
484 if (selection_active_normal ()) {
485 tree t= selection_get ();
486 selection_cut ();
487 if (subtree (et, path_up (tp)) == "") {
488 insert_style_with (path_up (tp), var, val);
489 insert_tree (t);
490 }
491 else insert_tree (tree (STYLE_WITH, var, val, t), path (2, end (t)));
492 }
493 else insert_style_with (path_up (tp), var, val);
494 }
495
496 /******************************************************************************
497 * The HYBRID and LATEX tags
498 ******************************************************************************/
499
500 void
make_hybrid()501 edit_dynamic_rep::make_hybrid () {
502 tree t (HYBRID, "");
503 if (selection_active_small ())
504 t[0]= selection_get_cut ();
505 if (is_func (t, HYBRID, 1) && (t[0] != "") &&
506 (!(is_atomic (t[0]) && drd->contains (t[0]->label))))
507 t= tree (HYBRID, "", t[0]);
508 path p= end (t, path (0));
509 if (in_source ()) insert_tree (t, p);
510 else insert_tree (tree (INACTIVE, t), path (0, p));
511 set_message (concat (kbd ("return"), ": activate symbol or macro"),
512 "hybrid");
513 }
514
515 bool
activate_latex()516 edit_dynamic_rep::activate_latex () {
517 path p= search_upwards (LATEX);
518 if (is_nil (p)) p= search_upwards (HYBRID);
519 if (is_nil (p)) return false;
520 tree st= subtree (et, p);
521 if (is_atomic (st[0])) {
522 if (is_func (subtree (et, path_up (p)), INACTIVE))
523 p= path_up (p);
524 string s= st[0]->label, help;
525 command cmd;
526 if (kbd_get_command (s, help, cmd)) {
527 cut (p * 0, p * 1);
528 cmd ();
529 if (N(st) == 2) insert_tree (copy (st[1]));
530 return true;
531 }
532 set_message ("Error: not a command name",
533 "activate latex command");
534 }
535 return false;
536 }
537
538 void
activate_hybrid(bool with_args_hint)539 edit_dynamic_rep::activate_hybrid (bool with_args_hint) {
540 // WARNING: update edit_interface_rep::set_hybrid_footer when updating this
541 if (activate_latex ()) return;
542 set_message ("", "");
543 path p= search_upwards (HYBRID);
544 if (is_nil (p)) return;
545 tree st= subtree (et, p);
546 if (is_compound (st[0])) return;
547 if (is_func (subtree (et, path_up (p)), INACTIVE))
548 p= path_up (p);
549
550 // activate macro argument
551 string name= st[0]->label;
552 path mp= search_upwards (MACRO);
553 if (is_nil (mp)) mp= search_upwards ("edit-macro");
554 if (!is_nil (mp)) {
555 tree mt= subtree (et, mp);
556 int i, n= N(mt)-1;
557 for (i=0; i<n; i++)
558 if (mt[i] == name) {
559 assign (p, tree (ARG, copy (name)));
560 go_to (end (et, p));
561 correct (path_up (p));
562 return;
563 }
564 }
565
566 // built-in primitives, macro applications and values
567 bool old_locked= env_locked; env_locked= true;
568 tree f= get_env_value (name);
569 if ((drd->contains (name) && (f == UNINIT)) ||
570 is_func (f, MACRO) || is_func (f, XMACRO)) {
571 assign (p, "");
572 correct (path_up (p));
573 make_compound (make_tree_label (name));
574 if (N(st) == 2) insert_tree (st[1]);
575 }
576 else if (f != UNINIT) {
577 assign (p, tree (VALUE, copy (name)));
578 go_to (end (et, p));
579 correct (path_up (p));
580 }
581 else if (in_source ()) {
582 assign (p, "");
583 correct (path_up (p));
584 make_compound (make_tree_label (name), with_args_hint? 1: 0);
585 if (N(st) == 2) insert_tree (st[1]);
586 }
587 else set_message ("Error: unknown command",
588 "activate hybrid command");
589 env_locked= old_locked;
590 }
591
592 /******************************************************************************
593 * Other special tags (SYMBOL and COMPOUND)
594 ******************************************************************************/
595
596 void
activate_symbol()597 edit_dynamic_rep::activate_symbol () {
598 path p= search_upwards (SYMBOL);
599 if (is_nil (p)) return;
600 tree st= subtree (et, p);
601 if (is_func (subtree (et, path_up (p)), INACTIVE))
602 p= path_up (p);
603 string s= st[0]->label;
604 if (is_int (s))
605 assign (p, string (((char) as_int (s))));
606 else {
607 int i, n= N(s);
608 for (i=0; i<n; i++)
609 if ((s[i]=='<') || (s[i]=='>'))
610 { s= ""; break; }
611 assign (p, "<" * s * ">");
612 }
613 go_to (end (et, p));
614 correct (path_up (p));
615 }
616
617 /******************************************************************************
618 * Temporary fixes for block structures
619 ******************************************************************************/
620
621 bool
make_return_before()622 edit_dynamic_rep::make_return_before () {
623 bool flag;
624 path q= tp;
625 while (!is_document (subtree (et, path_up (q)))) q= path_up (q);
626 flag= (N (subtree (et, path_up (q))) == (q->item+1)) || (tp != end (et, q));
627 if (flag) {
628 flag= insert_return ();
629 go_to (end (et, q));
630 }
631 return flag;
632 }
633
634 bool
make_return_after()635 edit_dynamic_rep::make_return_after () {
636 path q= tp;
637 while (!is_document (subtree (et, path_up (q)))) {
638 q= path_up (q);
639 if (!(rp < q)) return false;
640 }
641 if (tp == start (et, q)) return false;
642 return insert_return ();
643 }
644
645 void
temp_proof_fix()646 edit_dynamic_rep::temp_proof_fix () {
647 /* this routine should be removed as soon as possible */
648 path p = search_upwards ("proof");
649 if (is_nil (p) || (N(tp) < N(p)+2)) return;
650 path q = head (tp, N(p)+2);
651 tree st= subtree (et, path_up (q));
652 if ((!is_document (st)) || (last_item (q) != (N(st)-1))) return;
653 insert (path_inc (q), tree (DOCUMENT, ""));
654 }
655