comparison src/undo.c @ 0:376386a54a3c r19-14

Import from CVS: tag r19-14
author cvs
date Mon, 13 Aug 2007 08:45:50 +0200
parents
children 0293115a14e9
comparison
equal deleted inserted replaced
-1:000000000000 0:376386a54a3c
1 /* undo handling for XEmacs.
2 Copyright (C) 1990, 1992, 1993, 1994 Free Software Foundation, Inc.
3
4 This file is part of XEmacs.
5
6 XEmacs is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
9 later version.
10
11 XEmacs is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with XEmacs; see the file COPYING. If not, write to
18 the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 Boston, MA 02111-1307, USA. */
20
21 /* Synched up with: FSF 19.28. */
22
23 /* This file has been Mule-ized. */
24
25 #include <config.h>
26 #include "lisp.h"
27 #include "buffer.h"
28 #include "extents.h"
29
30 /* Maintained in event-stream.c */
31 extern Bufpos last_point_position;
32 extern Lisp_Object last_point_position_buffer;
33
34 /* Extent code needs to know about undo because the behavior of insert()
35 with regard to extents varies depending on whether we are inside
36 an undo or not. */
37 int inside_undo;
38
39 /* Last buffer for which undo information was recorded. */
40 static Lisp_Object last_undo_buffer;
41
42 Lisp_Object Qinhibit_read_only;
43
44 /* The first time a command records something for undo.
45 it also allocates the undo-boundary object
46 which will be added to the list at the end of the command.
47 This ensures we can't run out of space while trying to make
48 an undo-boundary. */
49 Lisp_Object pending_boundary;
50
51 static void
52 undo_boundary (struct buffer *b)
53 {
54 Lisp_Object tem = Fcar (b->undo_list);
55 if (!NILP (tem))
56 {
57 /* One way or another, cons nil onto the front of the undo list. */
58 if (CONSP (pending_boundary))
59 {
60 /* If we have preallocated the cons cell to use here,
61 use that one. */
62 XCDR (pending_boundary) = b->undo_list;
63 b->undo_list = pending_boundary;
64 pending_boundary = Qnil;
65 }
66 else
67 b->undo_list = Fcons (Qnil, b->undo_list);
68 }
69 }
70
71
72 static int
73 undo_prelude (struct buffer *b, int hack_pending_boundary)
74 {
75 if (EQ (b->undo_list, Qt))
76 return (0);
77
78 if (NILP (last_undo_buffer) || b != XBUFFER (last_undo_buffer))
79 {
80 undo_boundary (b);
81 XSETBUFFER (last_undo_buffer, b);
82 }
83
84 /* Allocate a cons cell to be the undo boundary after this command. */
85 if (hack_pending_boundary && NILP (pending_boundary))
86 pending_boundary = Fcons (Qnil, Qnil);
87
88 if (BUF_MODIFF (b) <= BUF_SAVE_MODIFF (b))
89 {
90 /* Record that an unmodified buffer is about to be changed.
91 Record the file modification date so that when undoing this entry
92 we can tell whether it is obsolete because the file was saved again. */
93 b->undo_list
94 = Fcons (Fcons (Qt,
95 Fcons (make_int ((b->modtime >> 16) & 0xffff),
96 make_int (b->modtime & 0xffff))),
97 b->undo_list);
98 }
99 return (1);
100 }
101
102
103
104 static Lisp_Object
105 restore_inside_undo (Lisp_Object val)
106 {
107 inside_undo = XINT (val);
108 return val;
109 }
110
111
112 /* Record an insertion that just happened or is about to happen,
113 for LENGTH characters at position BEG.
114 (It is possible to record an insertion before or after the fact
115 because we don't need to record the contents.) */
116
117 void
118 record_insert (struct buffer *b, Bufpos beg, Charcount length)
119 {
120 if (!undo_prelude (b, 1))
121 return;
122
123 /* If this is following another insertion and consecutive with it
124 in the buffer, combine the two. */
125 if (CONSP (b->undo_list))
126 {
127 Lisp_Object elt;
128 elt = XCAR (b->undo_list);
129 if (CONSP (elt)
130 && INTP (XCAR (elt))
131 && INTP (XCDR (elt))
132 && XINT (XCDR (elt)) == beg)
133 {
134 XCDR (elt) = make_int (beg + length);
135 return;
136 }
137 }
138
139 b->undo_list = Fcons (Fcons (make_int (beg),
140 make_int (beg + length)),
141 b->undo_list);
142 }
143
144 /* Record that a deletion is about to take place,
145 for LENGTH characters at location BEG. */
146
147 #ifdef ENERGIZE
148 extern int inside_parse_buffer; /* total kludge */
149 #endif
150
151 void
152 record_delete (struct buffer *b, Bufpos beg, Charcount length)
153 {
154 /* This function can GC */
155 Lisp_Object sbeg;
156 int at_boundary;
157
158 #ifdef ENERGIZE
159 /* #### why is this necessary? */
160 if (inside_parse_buffer)
161 return;
162 #endif
163
164 if (!undo_prelude (b, 1))
165 return;
166
167 at_boundary = (CONSP (b->undo_list)
168 && NILP (XCAR (b->undo_list)));
169
170 if (BUF_PT (b) == beg + length)
171 sbeg = make_int (-beg);
172 else
173 sbeg = make_int (beg);
174
175 /* If we are just after an undo boundary, and
176 point wasn't at start of deleted range, record where it was. */
177 if (at_boundary
178 && BUFFERP (last_point_position_buffer)
179 && b == XBUFFER (last_point_position_buffer)
180 && last_point_position != XINT (sbeg))
181 b->undo_list = Fcons (make_int (last_point_position), b->undo_list);
182
183 b->undo_list = Fcons (Fcons (make_string_from_buffer (b, beg,
184 length),
185 sbeg),
186 b->undo_list);
187 }
188
189 /* Record that a replacement is about to take place,
190 for LENGTH characters at location BEG.
191 The replacement does not change the number of characters. */
192
193 void
194 record_change (struct buffer *b, Bufpos beg, Charcount length)
195 {
196 record_delete (b, beg, length);
197 record_insert (b, beg, length);
198 }
199
200 /* Record that an EXTENT is about to be attached or detached in its buffer.
201 This works much like a deletion or insertion, except that there's no string.
202 The tricky part is that the buffer we operate on comes from EXTENT.
203 Most extent changes happen as a side effect of string insertion and
204 deletion; this call is solely for Fdetach_extent() and Finsert_extent().
205 */
206 void
207 record_extent (Lisp_Object extent, int attached)
208 {
209 Lisp_Object buffer = Fextent_object (extent);
210 struct buffer *b = XBUFFER (buffer); /* !!#### */
211 Lisp_Object token;
212
213 if (!undo_prelude (b, 1))
214 return;
215
216 if (attached)
217 token = extent;
218 else
219 token = list3 (extent, Fextent_start_position (extent),
220 Fextent_end_position (extent));
221 b->undo_list = Fcons (token, b->undo_list);
222 }
223
224 #if 0 /* FSFmacs */
225 /* Record a change in property PROP (whose old value was VAL)
226 for LENGTH characters starting at position BEG in BUFFER. */
227
228 record_property_change (Bufpos beg, Charcount length,
229 Lisp_Object prop, Lisp_Object value,
230 Lisp_Object buffer)
231 {
232 Lisp_Object lbeg, lend, entry;
233 struct buffer *b = XBUFFER (buffer);
234
235 if (!undo_prelude (b, 1))
236 return;
237
238 lbeg = make_int (beg);
239 lend = make_int (beg + length);
240 entry = Fcons (Qnil, Fcons (prop, Fcons (value, Fcons (lbeg, lend))));
241 b->undo_list = Fcons (entry, b->undo_list);
242 }
243 #endif /* FSFmacs */
244
245
246 DEFUN ("undo-boundary", Fundo_boundary, Sundo_boundary, 0, 0, 0 /*
247 Mark a boundary between units of undo.
248 An undo command will stop at this point,
249 but another undo command will undo to the previous boundary.
250 */ )
251 ()
252 {
253 if (EQ (current_buffer->undo_list, Qt))
254 return Qnil;
255 undo_boundary (current_buffer);
256 return Qnil;
257 }
258
259 /* At garbage collection time, make an undo list shorter at the end,
260 returning the truncated list.
261 MINSIZE and MAXSIZE are the limits on size allowed, as described below.
262 In practice, these are the values of undo-threshold and
263 undo-high-threshold. */
264
265 Lisp_Object
266 truncate_undo_list (Lisp_Object list, int minsize, int maxsize)
267 {
268 Lisp_Object prev, next, last_boundary;
269 int size_so_far = 0;
270
271 if (!(minsize > 0 || maxsize > 0))
272 return list;
273
274 prev = Qnil;
275 next = list;
276 last_boundary = Qnil;
277
278 if (!CONSP (list))
279 return (list);
280
281 /* Always preserve at least the most recent undo record.
282 If the first element is an undo boundary, skip past it. */
283 if (CONSP (next)
284 && NILP (XCAR (next)))
285 {
286 /* Add in the space occupied by this element and its chain link. */
287 size_so_far += sizeof (struct Lisp_Cons);
288
289 /* Advance to next element. */
290 prev = next;
291 next = XCDR (next);
292 }
293 while (CONSP (next)
294 && !NILP (XCAR (next)))
295 {
296 Lisp_Object elt;
297 elt = XCAR (next);
298
299 /* Add in the space occupied by this element and its chain link. */
300 size_so_far += sizeof (struct Lisp_Cons);
301 if (CONSP (elt))
302 {
303 size_so_far += sizeof (struct Lisp_Cons);
304 if (STRINGP (XCAR (elt)))
305 size_so_far += (sizeof (struct Lisp_String) - 1
306 + string_length (XSTRING (XCAR (elt))));
307 }
308
309 /* Advance to next element. */
310 prev = next;
311 next = XCDR (next);
312 }
313 if (CONSP (next))
314 last_boundary = prev;
315
316 while (CONSP (next))
317 {
318 Lisp_Object elt;
319 elt = XCAR (next);
320
321 /* When we get to a boundary, decide whether to truncate
322 either before or after it. The lower threshold, MINSIZE,
323 tells us to truncate after it. If its size pushes past
324 the higher threshold MAXSIZE as well, we truncate before it. */
325 if (NILP (elt))
326 {
327 if (size_so_far > maxsize && maxsize > 0)
328 break;
329 last_boundary = prev;
330 if (size_so_far > minsize && minsize > 0)
331 break;
332 }
333
334 /* Add in the space occupied by this element and its chain link. */
335 size_so_far += sizeof (struct Lisp_Cons);
336 if (CONSP (elt))
337 {
338 size_so_far += sizeof (struct Lisp_Cons);
339 if (STRINGP (XCAR (elt)))
340 size_so_far += (sizeof (struct Lisp_String) - 1
341 + string_length (XSTRING (XCAR (elt))));
342 }
343
344 /* Advance to next element. */
345 prev = next;
346 next = XCDR (next);
347 }
348
349 /* If we scanned the whole list, it is short enough; don't change it. */
350 if (NILP (next))
351 return list;
352
353 /* Truncate at the boundary where we decided to truncate. */
354 if (!NILP (last_boundary))
355 {
356 XCDR (last_boundary) = Qnil;
357 return list;
358 }
359 else
360 return Qnil;
361 }
362
363 DEFUN ("primitive-undo", Fprimitive_undo, Sprimitive_undo, 2, 2, 0 /*
364 Undo COUNT records from the front of the list LIST.
365 Return what remains of the list.
366 */ )
367 (count, list)
368 Lisp_Object count, list;
369 {
370 struct gcpro gcpro1, gcpro2;
371 Lisp_Object next = Qnil;
372 /* This function can GC */
373 int arg;
374 int speccount = specpdl_depth ();
375
376 record_unwind_protect (restore_inside_undo, make_int (inside_undo));
377 inside_undo = 1;
378
379 #if 0 /* This is a good feature, but would make undo-start
380 unable to do what is expected. */
381 Lisp_Object tem;
382
383 /* If the head of the list is a boundary, it is the boundary
384 preceding this command. Get rid of it and don't count it. */
385 tem = Fcar (list);
386 if (NILP (tem))
387 list = Fcdr (list);
388 #endif
389
390 CHECK_INT (count);
391 arg = XINT (count);
392 next = Qnil;
393 GCPRO2 (next, list);
394
395 /* Don't let read-only properties interfere with undo. */
396 if (NILP (current_buffer->read_only))
397 specbind (Qinhibit_read_only, Qt);
398
399 while (arg > 0)
400 {
401 while (1)
402 {
403 if (NILP (list))
404 break;
405 else if (!CONSP (list))
406 goto rotten;
407 next = XCAR (list);
408 list = XCDR (list);
409 /* Exit inner loop at undo boundary. */
410 if (NILP (next))
411 break;
412 /* Handle an integer by setting point to that value. */
413 else if (INTP (next))
414 BUF_SET_PT (current_buffer,
415 bufpos_clip_to_bounds (BUF_BEGV (current_buffer),
416 XINT (next),
417 BUF_ZV (current_buffer)));
418 else if (CONSP (next))
419 {
420 Lisp_Object car = XCAR (next);
421 Lisp_Object cdr = XCDR (next);
422
423 if (EQ (car, Qt))
424 {
425 /* Element (t high . low) records previous modtime. */
426 Lisp_Object high, low;
427 int mod_time;
428 if (!CONSP (cdr)) goto rotten;
429 high = XCAR (cdr);
430 low = XCDR (cdr);
431 if (!INTP (high) || !INTP (low)) goto rotten;
432 mod_time = (XINT (high) << 16) + XINT (low);
433 /* If this records an obsolete save
434 (not matching the actual disk file)
435 then don't mark unmodified. */
436 if (mod_time != current_buffer->modtime)
437 break;
438 #ifdef CLASH_DETECTION
439 Funlock_buffer ();
440 #endif /* CLASH_DETECTION */
441 Fset_buffer_modified_p (Qnil, Fcurrent_buffer ()); /* may GC under ENERGIZE */
442 }
443 else if (EXTENTP (car))
444 {
445 /* Element (extent start end) means that EXTENT was
446 detached, and we need to reattach it. */
447 Lisp_Object extent_obj, start, end;
448
449 extent_obj = car;
450 start = Fcar (cdr);
451 end = Fcar (Fcdr (cdr));
452
453 if (!INTP (start) || !INTP (end))
454 goto rotten;
455 Fset_extent_endpoints (extent_obj, start, end,
456 Fcurrent_buffer ());
457 }
458 #if 0 /* FSFmacs */
459 else if (EQ (car, Qnil))
460 {
461 /* Element (nil prop val beg . end) is property change. */
462 Lisp_Object beg, end, prop, val;
463
464 prop = Fcar (cdr);
465 cdr = Fcdr (cdr);
466 val = Fcar (cdr);
467 cdr = Fcdr (cdr);
468 beg = Fcar (cdr);
469 end = Fcdr (cdr);
470
471 Fput_text_property (beg, end, prop, val, Qnil);
472 }
473 #endif /* FSFmacs */
474 else if (INTP (car) && INTP (cdr))
475 {
476 /* Element (BEG . END) means range was inserted. */
477
478 if (XINT (car) < BUF_BEGV (current_buffer)
479 || XINT (cdr) > BUF_ZV (current_buffer))
480 error ("Changes to be undone are outside visible portion of buffer");
481 /* Set point first thing, so that undoing this undo
482 does not send point back to where it is now. */
483 Fgoto_char (car, Fcurrent_buffer ());
484 Fdelete_region (car, cdr, Fcurrent_buffer ());
485 }
486 else if (STRINGP (car) && INTP (cdr))
487 {
488 /* Element (STRING . POS) means STRING was deleted. */
489 Lisp_Object membuf = car;
490 int pos = XINT (cdr);
491
492 if (pos < 0)
493 {
494 if (-pos < BUF_BEGV (current_buffer) || -pos > BUF_ZV (current_buffer))
495 error ("Changes to be undone are outside visible portion of buffer");
496 BUF_SET_PT (current_buffer, -pos);
497 Finsert (1, &membuf);
498 }
499 else
500 {
501 if (pos < BUF_BEGV (current_buffer) || pos > BUF_ZV (current_buffer))
502 error ("Changes to be undone are outside visible portion of buffer");
503 BUF_SET_PT (current_buffer, pos);
504
505 /* Insert before markers so that if the mark is
506 currently on the boundary of this deletion, it
507 ends up on the other side of the now-undeleted
508 text from point. Since undo doesn't even keep
509 track of the mark, this isn't really necessary,
510 but it may lead to better behavior in certain
511 situations.
512
513 I'm doubtful that this is safe; you could mess
514 up the process-output mark in shell buffers, so
515 until I hear a compelling reason for this change,
516 I'm leaving it out. -jwz
517 */
518 /* Finsert_before_markers (1, &membuf); */
519 Finsert (1, &membuf);
520 BUF_SET_PT (current_buffer, pos);
521 }
522 }
523 else
524 {
525 goto rotten;
526 }
527 }
528 else if (EXTENTP (next))
529 Fdetach_extent (next);
530 else
531 {
532 rotten:
533 signal_simple_continuable_error
534 ("Something rotten in the state of undo:", next);
535 }
536 }
537 arg--;
538 }
539
540 UNGCPRO;
541 return unbind_to (speccount, list);
542 }
543
544 void
545 syms_of_undo (void)
546 {
547 defsubr (&Sprimitive_undo);
548 defsubr (&Sundo_boundary);
549 defsymbol (&Qinhibit_read_only, "inhibit-read-only");
550 }
551
552 void
553 vars_of_undo (void)
554 {
555 inside_undo = 0;
556 pending_boundary = Qnil;
557 staticpro (&pending_boundary);
558 last_undo_buffer = Qnil;
559 staticpro (&last_undo_buffer);
560 }