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