1 
2 /******************************************************************************
3 * MODULE     : cursor.cpp
4 * DESCRIPTION: cursor handling
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_cursor.hpp"
13 #include "iterator.hpp"
14 #include "tm_buffer.hpp"
15 #include "tree_traverse.hpp"
16 #include "drd_mode.hpp"
17 #include "analyze.hpp"
18 
19 /******************************************************************************
20 * Constructor and destructor
21 ******************************************************************************/
22 
edit_cursor_rep()23 edit_cursor_rep::edit_cursor_rep ():
24   cu (0, 0), mv (0, 0), mv_status (0) {}
~edit_cursor_rep()25 edit_cursor_rep::~edit_cursor_rep () {}
the_cursor()26 cursor& edit_cursor_rep::the_cursor () { return cu; }
the_ghost_cursor()27 cursor& edit_cursor_rep::the_ghost_cursor () { return mv; }
28 
29 /******************************************************************************
30 * Cursor movement
31 ******************************************************************************/
32 
33 #define DELTA (1<<23)
34 
35 static bool searching_forwards;
36 
37 path
make_cursor_accessible(path p,bool forwards)38 edit_cursor_rep::make_cursor_accessible (path p, bool forwards) {
39   //time_t t1= texmacs_time ();
40   path start_p= p;
41   bool inverse= false;
42   int old_mode= get_access_mode ();
43   if (get_init_string (MODE) == "src")
44     set_access_mode (DRD_ACCESS_SOURCE);
45   while (!is_accessible_cursor (et, p) && !in_source ()) {
46     path pp;
47     ASSERT (rp <= p, "path outside document");
48     p= rp * closest_inside (subtree (et, rp), p / rp);
49     if (forwards ^ inverse)
50       pp= rp * next_valid (subtree (et, rp), p / rp);
51     else
52       pp= rp * previous_valid (subtree (et, rp), p / rp);
53     if (pp == p) {
54       if (inverse) break;
55       else { p= start_p; inverse= true; }
56     }
57     else p= pp;
58   }
59   set_access_mode (old_mode);
60   //time_t t2= texmacs_time ();
61   //if (t2-t1 >= 1) cout << "made_cursor_accessible took " << t2-t1 << "ms\n";
62   return p;
63 }
64 
65 path
tree_path(path sp,SI x,SI y,SI delta)66 edit_cursor_rep::tree_path (path sp, SI x, SI y, SI delta) {
67   path stp= find_scrolled_tree_path (eb, sp, x, y, delta);
68   path p= correct_cursor (et, stp /*, searching_forwards */);
69   return make_cursor_accessible (p, searching_forwards);
70 }
71 
72 bool
cursor_move_sub(SI & x0,SI & y0,SI & d0,SI dx,SI dy)73 edit_cursor_rep::cursor_move_sub (SI& x0, SI& y0, SI& d0, SI dx, SI dy) {
74   path sp= find_innermost_scroll (eb, tp);
75   searching_forwards= dx == 1 || dy == -1;
76 
77   int i,d;
78   path ref_p= tree_path (sp, x0, y0, d0);
79   if (ref_p != tp) {
80     tp= ref_p;
81     return true;
82   }
83 
84   // cout << "ref_p = " << ref_p << "\n";
85   if (ref_p == tree_path (sp, x0, y0, d0+ dx*DELTA)) {
86     for (i=1; i<DELTA; i=i<<1)
87       if (ref_p != tree_path (sp, x0+ dx*i, y0+ dy*i, d0+ dx*DELTA))
88 	break;
89     if (i>=DELTA) return false;
90     for (d=i>>2; d>=1; d=d>>1)
91       if (ref_p != tree_path (sp, x0+ dx*(i-d), y0+ dy*(i-d), d0+ dx*DELTA))
92 	i-=d;
93 
94     x0 += dx*i;
95     y0 += dy*i;
96   }
97 
98   // cout << "path  = " << tree_path (sp, x0, y0, d0) << "\n";
99   if (dx!=0) {
100     if (ref_p == tree_path (sp, x0, y0, d0)) {
101       for (i=1; i<DELTA; i=i<<1)
102 	if (ref_p != tree_path (sp, x0, y0, d0+ dx*i)) break;
103       if (i>=DELTA)
104 	FAILED ("inconsistent cursor handling");
105       for (d=i>>2; d>=1; d=d>>1)
106 	if (ref_p != tree_path (sp, x0, y0, d0+ dx*(i-d))) i-=d;
107       d0 += dx*i;
108     }
109     else {
110       for (i=1; i<DELTA; i=i<<1)
111 	if (ref_p == tree_path (sp, x0, y0, d0- dx*i)) break;
112       if (i<DELTA) {
113 	for (d=i>>2; d>=1; d=d>>1)
114 	  if (ref_p == tree_path (sp, x0, y0, d0- dx*(i-d))) i-=d;
115 	i--;
116 	d0 -= dx*i;
117       }
118       else {  // exceptional case
119 	ref_p= tree_path (sp, x0, y0, d0- dx*DELTA);
120 	for (i=1; i<DELTA; i=i<<1)
121 	  if (ref_p == tree_path (sp, x0, y0, d0- dx*i)) break;
122 	for (d=i>>2; d>=1; d=d>>1)
123 	  if (ref_p == tree_path (sp, x0, y0, d0- dx*(i-d))) i-=d;
124 	d0 -= dx*i;
125       }
126     }
127   }
128 
129   tp= tree_path (sp, x0, y0, d0);
130   return true;
131 }
132 
133 void
cursor_move(SI dx,SI dy)134 edit_cursor_rep::cursor_move (SI dx, SI dy) {
135   //time_t t1= texmacs_time ();
136   //stretched_print ((tree) eb, false);
137   cursor_move_sub (mv->ox, mv->oy, mv->delta, dx, dy);
138   //time_t t2= texmacs_time ();
139   //if (t2 - t1 >= 10) cout << "cursor_move took " << t2-t1 << "ms\n";
140 }
141 
142 /******************************************************************************
143 * Routines affecting both the cursor and the ghost cursor
144 ******************************************************************************/
145 
146 void
adjust_ghost_cursor(int status)147 edit_cursor_rep::adjust_ghost_cursor (int status) {
148   if (status==mv_status) {
149     if (status!=HORIZONTAL) {
150       mv->ox   = cu->ox;
151       mv->delta= cu->delta;
152     }
153     if (status!=VERTICAL)
154       mv->oy= cu->oy;
155   }
156 }
157 
158 void
notify_cursor_moved(int status)159 edit_cursor_rep::notify_cursor_moved (int status) {
160   mv_status= status;
161   cu= eb->find_check_cursor (tp);
162   notify_change (THE_CURSOR);
163   if (cu->valid) call ("notify-cursor-moved", object (status));
164 }
165 
166 void
go_to(SI x,SI y,bool absolute)167 edit_cursor_rep::go_to (SI x, SI y, bool absolute) {
168   if (has_changed (THE_TREE+THE_ENVIRONMENT)) return;
169   tp= tree_path (absolute? path (): find_innermost_scroll (eb, tp), x, y, 0);
170   notify_cursor_moved (CENTER);
171   mv->ox   = x;
172   mv->oy   = y;
173   mv->delta= 0;
174 }
175 
176 void
go_left_physical()177 edit_cursor_rep::go_left_physical () {
178   if (has_changed (THE_TREE+THE_ENVIRONMENT)) return;
179   adjust_ghost_cursor (VERTICAL);
180   cursor_move (-1, 0);
181   notify_cursor_moved (HORIZONTAL);
182   select_from_cursor_if_active ();
183 }
184 
185 void
go_right_physical()186 edit_cursor_rep::go_right_physical () {
187   if (has_changed (THE_TREE+THE_ENVIRONMENT)) return;
188   adjust_ghost_cursor (VERTICAL);
189   cursor_move (1, 0);
190   notify_cursor_moved (HORIZONTAL);
191   select_from_cursor_if_active ();
192 }
193 
194 void
go_up()195 edit_cursor_rep::go_up () {
196   if (has_changed (THE_TREE+THE_ENVIRONMENT)) return;
197   path scroll_p= find_innermost_scroll (eb, tp);
198   path start_p= tree_path (scroll_p, -(1 << 30), 1 << 30, 0);
199   if (tp == start_p) return;
200   path old_p= tp;
201   adjust_ghost_cursor (HORIZONTAL);
202   cursor_move (0, 1);
203   notify_cursor_moved (VERTICAL);
204   if (tp == old_p) tp= start_p;
205   select_from_cursor_if_active ();
206 }
207 
208 void
go_down()209 edit_cursor_rep::go_down () {
210   if (has_changed (THE_TREE+THE_ENVIRONMENT)) return;
211   path scroll_p= find_innermost_scroll (eb, tp);
212   path end_p= tree_path (scroll_p, 1 << 30, -(1 << 30), 0);
213   if (tp == end_p) return;
214   path old_p= tp;
215   adjust_ghost_cursor (HORIZONTAL);
216   cursor_move (0, -1);
217   notify_cursor_moved (VERTICAL);
218   if (tp == old_p) tp= end_p;
219   select_from_cursor_if_active ();
220 }
221 
222 void
go_page_up()223 edit_cursor_rep::go_page_up () {
224   if (has_changed (THE_TREE+THE_ENVIRONMENT)) return;
225   path sp= find_innermost_scroll (eb, tp);
226   if (is_nil (sp)) go_to (mv->ox, min (mv->oy + get_visible_height (), eb->y2));
227   else {
228     SI x, y, sx, sy;
229     rectangle outer, inner;
230     box b= eb[path_up (sp)];
231     find_canvas_info (eb, sp, x, y, sx, sy, outer, inner);
232     go_to (mv->ox, min (mv->oy + b->h (), y + sy + inner->y2), false);
233   }
234   select_from_cursor_if_active ();
235 }
236 
237 void
go_page_down()238 edit_cursor_rep::go_page_down () {
239   if (has_changed (THE_TREE+THE_ENVIRONMENT)) return;
240   path sp= find_innermost_scroll (eb, tp);
241   if (is_nil (sp)) go_to (mv->ox, max (mv->oy - get_visible_height (), eb->y1));
242   else {
243     SI x, y, sx, sy;
244     rectangle outer, inner;
245     box b= eb[path_up (sp)];
246     find_canvas_info (eb, sp, x, y, sx, sy, outer, inner);
247     go_to (mv->ox, max (mv->oy - b->h (), y + sy + inner->y1), false);
248   }
249   select_from_cursor_if_active ();
250 }
251 
252 /******************************************************************************
253 * Adapt physical horizontal cursor movement to line breaking
254 ******************************************************************************/
255 
256 void
go_left()257 edit_cursor_rep::go_left () {
258   if (has_changed (THE_TREE+THE_ENVIRONMENT)) return;
259   path old_tp= copy (tp);
260   go_left_physical ();
261   if (tp != old_tp && var_inside_same (et, old_tp, tp, DOCUMENT)) return;
262   path p= previous_valid (et, old_tp);
263   if (rp < p) go_to (p);
264   select_from_cursor_if_active ();
265 }
266 
267 void
go_right()268 edit_cursor_rep::go_right () {
269   if (has_changed (THE_TREE+THE_ENVIRONMENT)) return;
270   path old_tp= copy (tp);
271   go_right_physical ();
272   if (tp != old_tp && var_inside_same (et, old_tp, tp, DOCUMENT)) return;
273   path p= next_valid (et, old_tp);
274   if (rp < p) go_to (p);
275   select_from_cursor_if_active ();
276 }
277 
278 void
go_start_line()279 edit_cursor_rep::go_start_line () {
280   if (has_changed (THE_TREE+THE_ENVIRONMENT)) return;
281   while (true) {
282     cursor old_cu= copy (cu);
283     cursor old_mv= copy (mv);
284     path   old_tp= copy (tp);
285     go_left_physical ();
286     if (tp == old_tp || !more_inside (et, tp, old_tp, DOCUMENT)) {
287       notify_cursor_moved (HORIZONTAL);
288       cu= old_cu;
289       mv= old_mv;
290       tp= old_tp;
291       select_from_cursor_if_active ();
292       return;
293     }
294   }
295 }
296 
297 void
go_end_line()298 edit_cursor_rep::go_end_line () {
299   if (has_changed (THE_TREE+THE_ENVIRONMENT)) return;
300   while (true) {
301     cursor old_cu= copy (cu);
302     cursor old_mv= copy (mv);
303     path   old_tp= copy (tp);
304     go_right_physical ();
305     if (tp == old_tp || !more_inside (et, tp, old_tp, DOCUMENT)) {
306       notify_cursor_moved (HORIZONTAL);
307       cu= old_cu;
308       mv= old_mv;
309       tp= old_tp;
310       select_from_cursor_if_active ();
311       return;
312     }
313   }
314 }
315 
316 /******************************************************************************
317 * Logical cursor changes
318 ******************************************************************************/
319 
320 void
adjust_cursor()321 edit_cursor_rep::adjust_cursor () {
322   path sp= find_innermost_scroll (eb, tp);
323   cursor mv= copy (cu);
324   SI dx= PIXEL << 8, ddelta= 0;
325   path p= tree_path (sp, mv->ox, mv->oy, mv->delta);
326   if (p != tp) {
327     // cout << "Cursors don't match\n";
328     while (dx != 0 || ddelta != 0) {
329       // cout << "  " << tp << ", " << p << "\n";
330       p= tree_path (sp, mv->ox, mv->oy, mv->delta);
331       int eps= (path_inf (p, tp)? 1: -1);
332       if (p == tp) eps= (mv->ox < cu->ox? 1: -1);
333       if (p == tp && mv->ox == cu->ox) eps= (mv->delta < cu->delta? 1: -1);
334       if (dx > 0) {
335 	if (p != tp ||
336 	    tree_path (sp, mv->ox + eps * dx, mv->oy, mv->delta) == tp)
337 	  mv->ox += eps * dx;
338 	dx >>= 1;
339 	if (dx == 0) ddelta= DELTA;
340       }
341       else if (ddelta > 0) {
342         if (p != tp ||
343             tree_path (sp, mv->ox, mv->oy, mv->delta + eps * ddelta) == tp)
344           mv->delta += eps * ddelta;
345         ddelta >>= 1;
346       }
347     }
348   }
349   if (p == tp) cu= mv;
350 }
351 
352 void
go_to_here()353 edit_cursor_rep::go_to_here () {
354   cu= eb->find_check_cursor (tp);
355   if (!cu->valid || !valid_cursor (et, tp)) {
356     tp= super_correct (et, tp);
357     cu= eb->find_check_cursor (tp);
358   }
359   if (!cu->valid || !valid_cursor (et, tp)) {
360     tp= make_cursor_accessible (tp, false);
361     cu= eb->find_check_cursor (tp);
362   }
363   if (cu->valid) adjust_cursor ();
364   if (mv_status == DIRECT) mv= copy (cu);
365   notify_change (THE_CURSOR);
366   if (cu->valid) call ("notify-cursor-moved", object (DIRECT));
367 }
368 
369 void
go_to(path p)370 edit_cursor_rep::go_to (path p) {
371   if (rp <= p) {
372     //if (tp != p) cout << "Go to " << p << "\n";
373     tp= p;
374     mv_status= DIRECT;
375     if (!has_changed (THE_TREE+THE_ENVIRONMENT)) {
376       cu= eb->find_check_cursor (tp);
377       if (cu->valid) adjust_cursor ();
378       mv= copy (cu);
379     }
380     notify_change (THE_CURSOR);
381     if (cu->valid) call ("notify-cursor-moved", object (DIRECT));
382   }
383 }
384 
385 void
go_to_correct(path p)386 edit_cursor_rep::go_to_correct (path p) {
387   p= correct_cursor (et, p);
388   go_to (p);
389 }
390 
391 void
go_to_start(path p)392 edit_cursor_rep::go_to_start (path p) {
393   go_to (start (et, p));
394 }
395 
396 void
go_to_end(path p)397 edit_cursor_rep::go_to_end (path p) {
398   go_to (end (et, p));
399 }
400 
401 void
go_to_border(path p,bool at_start)402 edit_cursor_rep::go_to_border (path p, bool at_start) {
403   if (at_start) go_to_start (p);
404   else go_to_end (p);
405 }
406 
407 void
go_start()408 edit_cursor_rep::go_start () {
409   go_to (correct_cursor (et, rp * 0));
410   select_from_cursor_if_active ();
411 }
412 
413 void
go_end()414 edit_cursor_rep::go_end () {
415   go_to (correct_cursor (et, rp * 1));
416   select_from_cursor_if_active ();
417 }
418 
419 void
go_start_paragraph()420 edit_cursor_rep::go_start_paragraph () {
421   path p= search_parent_upwards (DOCUMENT);
422   go_to (start (et, p));
423   select_from_cursor_if_active ();
424 }
425 
426 void
go_end_paragraph()427 edit_cursor_rep::go_end_paragraph () {
428   path p= search_parent_upwards (DOCUMENT);
429   go_to (end (et, p));
430   select_from_cursor_if_active ();
431 }
432 
433 void
go_start_of(tree_label what)434 edit_cursor_rep::go_start_of (tree_label what) {
435   path p= search_upwards (what);
436   if (!is_nil (p)) go_to (start (et, p));
437 }
438 
439 void
go_end_of(tree_label what)440 edit_cursor_rep::go_end_of (tree_label what) {
441   path p= search_upwards (what);
442   if (!is_nil (p)) go_to (end (et, p));
443 }
444 
445 void
go_start_with(string var,string val)446 edit_cursor_rep::go_start_with (string var, string val) {
447   path p= search_upwards_with (var, val);
448   if (!is_nil (p)) go_to (start (et, p));
449 }
450 
451 void
go_end_with(string var,string val)452 edit_cursor_rep::go_end_with (string var, string val) {
453   path p= search_upwards_with (var, val);
454   if (!is_nil (p)) go_to (end (et, p));
455 }
456 
457 /******************************************************************************
458 * Jumping to a label
459 ******************************************************************************/
460 
461 tree
get_labels()462 edit_cursor_rep::get_labels () {
463   tree r (TUPLE);
464   hashmap<string,tree> h= buf->data->ref;
465   if (buf->prj != NULL) {
466     h= copy (buf->prj->data->ref);
467     h->join (buf->data->ref);
468   }
469   iterator<string> it= iterate (h);
470   while (it->busy ()) {
471     string ref= it->next ();
472     r << ref;
473   }
474   return r;
475 }
476 
477 static path
search_label(tree t,string which)478 search_label (tree t, string which) {
479   if (is_atomic (t)) return path ();
480   else if (t == tree (LABEL, which)) return path (1);
481   else if (is_compound (t, "tag", 2) && t[0] == which)
482     return path (1, start (t[1]));
483   else {
484     int i, n=N(t);
485     for (i=0; i<n; i++) {
486       path q= search_label (t[i], which);
487       if (!is_nil (q)) return path (i, q);
488     }
489     return path ();
490   }
491 }
492 
493 bool
cursor_is_accessible()494 edit_cursor_rep::cursor_is_accessible () {
495   return is_accessible_cursor (et, tp);
496 }
497 
498 void
show_cursor_if_hidden()499 edit_cursor_rep::show_cursor_if_hidden () {
500   if (!is_accessible_cursor (et, tp) && !in_source ()) {
501     eval ("(use-modules (utils edit variants))");
502     eval ("(cursor-show-hidden)");
503   }
504 }
505 
506 void
go_to_label(string s)507 edit_cursor_rep::go_to_label (string s) {
508   path p= search_label (et, s);
509   if (!is_nil (p)) {
510     go_to (p);
511     show_cursor_if_hidden ();
512     return;
513   }
514   if (!is_nil (eb)) {
515     p= eb->find_tag (s);
516     if (!is_nil (p)) {
517       go_to (p);
518       show_cursor_if_hidden ();
519       return;
520     }
521   }
522   tree val= (buf->prj==NULL? buf->data->ref[s]: buf->prj->data->ref[s]);
523   if (is_func (val, TUPLE, 3) && is_atomic (val[2])) {
524     string extra= val[2]->label;
525     if (starts (extra, "#")) {
526       string part= extra (1, N (extra));
527       int i= search_forwards (".", part);
528       if (i >= 0) part= part (0, i);
529       string show= "(show-hidden-part " * scm_quote (part) * ")";
530       string jump= "(go-to-label " * scm_quote (s) * ")";
531       exec_delayed (scheme_cmd ("(if " * show * " (delayed " * jump * "))"));
532     }
533     else {
534       url u= relative (buf->buf->name, url (extra));
535       if (u != buf->buf->name) {
536 	string new_buf = scm_quote (as_string (u));
537 	string load_buf= "(load-buffer (system->url " * new_buf * "))";
538 	string jump_to = "(go-to-label " * scm_quote (s) * ")";
539 	exec_delayed (scheme_cmd ("(begin " * load_buf * " " * jump_to * ")"));
540       }
541     }
542   }
543 }
544