Mercurial > hg > xemacs-beta
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 } |