1 /**********************************************************
3 ** $VER: UndoRedo.c 1.2 (3.2.2001) **
4 ** Implementation of journalized buffer **
6 ** © T.Pierron, C.Guilaume. Free software under **
7 ** terms of GNU public license. **
9 **********************************************************/
11 #include <exec/types.h>
12 #include <exec/memory.h>
15 #include "ProtoTypes.h"
16 #define DEBUG_UNDO_STUFF /* Only activated if DEBUG macro is defined */
19 /** Sizeof each struct **/
21 sizeof(struct _AddChar
),
22 sizeof(struct _AddChar
),
23 sizeof(struct _RemLine
),
24 sizeof(struct _JoinLine
),
25 sizeof(struct _GroupBy
)
28 #define PRE_CHECK(jbuf) { \
29 register Project prj = PRJ(jbuf); \
30 if((prj->state & DONT_FLUSH) == 0) { \
31 flush_undo_buf(&prj->redo); \
32 if((prj->state & MODIFIED) == 0) set_modif_mark( prj ); \
36 /*** Get last operation done ***/
37 static void *last_operation( JBuf jb
)
39 STRPTR ptr
= jb
->ops
->data
+ jb
->size
;
41 return ptr
- SizeOf
[ LAST_TYPE(ptr
) ];
44 /*** Alloc a new buffer for operations only ***/
45 static void *new_undo_buf(JBuf jb
, UWORD size
)
50 if(jb
->ops
==NULL
|| jb
->size
+ size
>= UNDO_CHUNK
)
52 /* It may have some bytes left, but don't care splitting op over 2 buf */
53 if(NULL
== (new = (RBOps
) AllocVec(sizeof(*new), MEMF_PUBLIC
)))
57 new->size
= jb
->size
; /* Save old usage */
62 ret
= jb
->ops
->data
+ jb
->size
;
65 IS_COMMIT((STRPTR
)ret
+size
) = 0;
69 /*** Alloc a new rollback segment (data from file) ***/
70 static char new_rbseg(JBuf jb
, ULONG size
)
73 if( jb
->rbseg
== NULL
)
75 alloc
: /* Adjust size */
76 size
&= ~(UNDO_CHUNK
-1); size
+= UNDO_CHUNK
;
78 if( ( new = (RBSeg
) AllocVec(size
+sizeof(*new)-1, MEMF_PUBLIC
) ) )
81 new->prev
= jb
->rbseg
;
83 if(jb
->rbseg
) jb
->rbseg
->next
= new;
88 else if( jb
->rbsz
+ size
> jb
->rbseg
->max
)
90 /* This time, fully use segment capacity */
91 size
-= jb
->rbseg
->max
- jb
->rbsz
;
97 /*** Do a savepoint ***/
98 void commit_work( Project p
)
100 /* Remove last savepoint, if any */
101 if(p
->savepoint
!= NULL
&& p
->savepoint
!= OUT_OF_DATE
)
102 IS_COMMIT(p
->savepoint
) = 0;
103 /* Commit last operation done */
104 if(p
->undo
.ops
!= NULL
)
105 IS_COMMIT(p
->savepoint
= p
->undo
.ops
->data
+ p
->undo
.size
) = 1;
110 /*** Free memory used for one project's undo buf ***/
111 void flush_undo_buf( JBuf jb
)
116 for(jd
=jb
->ops
, size
=jb
->size
; jd
; size
=jd
->size
, prev
=jd
->prev
, FreeVec(jd
), jd
=prev
)
120 /* Some operations contain non-freed data */
123 op
= jd
->data
+ size
;
124 type
= LAST_TYPE(op
);
125 size
-= SizeOf
[ type
];
126 if( IS_COMMIT(op
) ) PRJ(jb
)->savepoint
= OUT_OF_DATE
;
127 op
= jd
->data
+ size
;
130 case REM_LINE
: free_line( ((RemLine
)op
)->line
); break;
131 case JOIN_LINE
: free_line( ((JoinLine
)op
)->old
); break;
135 /* Free the rollback segments */
136 for(jd
= (RBOps
)jb
->rbseg
; jd
; prev
= jd
->prev
, FreeVec(jd
), jd
= prev
);
137 /* Reset struct fields to 0 */
138 memset(jb
, 0, (IPTR
)&((JBuf
)0L)->rbtype
);
141 /*** Register a character added in the buffer ***/
142 void reg_add_chars(JBuf jb
, LINE
*ln
, ULONG pos
, UWORD nb
)
148 /* Reuse old registered operation? */
149 if(jb
->op
== ADD_CHAR
&& jb
->line
== ln
)
151 buf
= (AddChar
) last_operation( jb
);
152 /* If character position is just after the previous one && **
153 ** If it wasn't a committed operation (must be unchanged) */
154 if(pos
== buf
->pos
+ buf
->nbc
&& !buf
->commit
)
156 jb
->nbc
= pos
+1, buf
->nbc
+= nb
;
160 else /* Register the new operation */
163 if((buf
= new_undo_buf(jb
, sizeof(*buf
))))
165 jb
->nbc
= (buf
->pos
= pos
)+1;
166 buf
->line
= jb
->line
= ln
;
167 buf
->type
= jb
->op
= ADD_CHAR
;
173 /*** Copy a string into rollback segment ***/
174 void copy_rbseg(JBuf jb
, STRPTR str
, ULONG size
)
176 RBSeg rbs
= jb
->rbseg
;
177 STRPTR dst
= rbs
->data
+ jb
->rbsz
;
179 if(jb
->rbsz
+ size
> rbs
->max
)
181 /* The string can never be spread over more than 2 buffers */
182 CopyMem(str
, dst
, jb
->rbsz
= rbs
->max
-jb
->rbsz
); str
+= jb
->rbsz
;
183 CopyMem(str
, (jb
->rbseg
= rbs
->next
)->data
, jb
->rbsz
= size
-jb
->rbsz
);
185 else CopyMem(str
, dst
, size
), jb
->rbsz
+=size
;
188 /*** Move a string inside rollback segment ***/
189 void move_rbseg(JBuf jb
, STRPTR ins
, UWORD size
, ULONG off
)
194 src
= (smin
= dmin
= jb
->rbseg
)->data
+ jb
->rbsz
- 1;
195 { /* Compute search final position: current + off bytes in rb segment */
196 register ULONG nb
= jb
->rbsz
+ off
;
197 while( nb
> dmin
->max
)
198 nb
-= dmin
->max
, dmin
= dmin
->next
;
200 dst
= dmin
->data
+ (jb
->rbsz
= nb
) - 1;
203 /* Move size bytes from src to dst */
206 if(src
< smin
->data
) smin
=smin
->prev
, src
=smin
->data
+smin
->max
-1;
207 if(dst
< dmin
->data
) dmin
=dmin
->prev
, dst
=dmin
->data
+dmin
->max
-1;
210 /* Then insert string "ins" of "off" bytes */
211 for(src
=ins
+off
-1; off
--; )
213 if(dst
< dmin
->data
) dmin
=dmin
->prev
, dst
=dmin
->data
+dmin
->max
-1;
218 /*** Register some characters removed from the buffer ***/
219 void reg_rem_chars(JBuf jb
, LINE
*ln
, ULONG s
, ULONG e
)
225 /* Previous operation was the converse one */
226 if(jb
->op
== ADD_CHAR
&& jb
->line
== ln
&& s
== e
)
228 AddChar buf
= (AddChar
)last_operation( jb
);
229 /* Remove the last enterred char is very frequent */
230 if(buf
->pos
<= e
&& e
< buf
->pos
+buf
->nbc
&& !buf
->commit
)
233 if( pop_last_op(jb
, ADD_CHAR
, 0) == PRJ(jb
)->savepoint
)
234 unset_modif_mark(PRJ(jb
), TRUE
);
239 /* Optimize if previous operation was the same */
240 if(jb
->op
== REM_CHAR
&& jb
->line
== ln
)
242 buf
= (RemChar
) last_operation( jb
);
243 /* Ranges don't match */
244 if((LONG
)e
< (LONG
)buf
->pos
-1 || s
> buf
->pos
|| buf
->commit
)
247 if( new_rbseg(jb
, e
-s
+1) )
250 s
= buf
->pos
-s
; buf
->pos
-= s
; e
-= s
;
251 move_rbseg(jb
, ln
->stream
+buf
->pos
, buf
->nbc
, s
);
256 copy_rbseg(jb
, ln
->stream
+buf
->pos
+s
, e
-buf
->pos
+1),
257 buf
->nbc
+= e
-buf
->pos
+1;
261 else /* New operation */
264 if(NULL
!= (buf
= new_undo_buf(jb
, sizeof(*buf
))) &&
265 0 != new_rbseg(jb
, buf
->nbc
= e
-s
+1))
267 copy_rbseg(jb
, ln
->stream
+s
, buf
->nbc
);
268 buf
->line
= jb
->line
= ln
;
269 buf
->pos
= jb
->nbc
= s
;
270 buf
->type
= jb
->op
= REM_CHAR
;
275 /*** Register a entire line removed/added ***/
276 void reg_rem_line(JBuf jb
, LINE
*ln
)
282 if( NULL
!= (buf
= new_undo_buf(jb
, sizeof(*buf
))) )
285 buf
->type
= jb
->op
= REM_LINE
;
286 buf
->after
= (ln
->prev
? ln
->prev
: NULL
);
287 jb
->line
= (ln
->next
? ln
->next
: ln
);
292 /*** Register two lines joined ***/
293 void reg_join_lines(JBuf jb
, LINE
*ln
, LINE
*ln2
)
299 if( NULL
!= (buf
= new_undo_buf(jb
, sizeof(*buf
))) )
301 /* Keep original pointers since further modif. may reference them */
304 buf
->type
= jb
->op
= JOIN_LINE
;
305 buf
->pos
= jb
->nbc
= ln
->size
;
310 /*** Group of modifications ***/
311 void reg_group_by( JBuf jb
)
315 /* No PRE_CHECK since GROUP_BY type doesn't imply any modif. */
316 if( NULL
!= (buf
= new_undo_buf(jb
, sizeof(*buf
))) )
319 buf
->type
= GROUP_BY
;
323 /*** References may not be synchronized anymore ***/
324 void adjust_ccp( Project p
)
326 /* The problem when user undoes or redoes something is that **
327 ** line references may not be valid anymore, so readjust */
331 for(ln
=p
->the_line
,nb
=0; ln
&& ln
->flags
== 0; ln
=ln
->next
, nb
++);
334 char YPinfYC
= p
->ccp
.yp
< p
->ccp
.yc
;
335 if(YPinfYC
) p
->ccp
.yp
=nb
, p
->ccp
.line
=ln
;
336 else p
->ccp
.yc
=nb
, p
->ccp
.cline
=ln
;
337 { register LINE
*tmp
;
339 for(tmp
=ln
,nbl
=nb
; tmp
; tmp
=tmp
->next
,nbl
++)
340 if( tmp
->flags
!= 0 ) ln
= tmp
, nb
= nbl
;
342 if(YPinfYC
) p
->ccp
.yc
=nb
, p
->ccp
.cline
=ln
;
343 else p
->ccp
.yp
=nb
, p
->ccp
.line
=ln
;
345 else p
->ccp
.select
= 0;
348 /*** Jump to the last modified line ***/
349 void last_modif( JBuf jb
, char dirtiness
)
351 LINE
*ln
; ULONG nb
; Project p
;
353 if(jb
->line
== NULL
) return;
355 /* Get line number of the last modified line */
356 for(nb
=0, p
=PRJ(jb
), ln
=p
->the_line
; ln
&& ln
!=jb
->line
; ln
=ln
->next
, nb
++);
359 printf("Line not found! (0x%08x - 0x%08x)", jb->line, p->the_line);
360 print_n(jb->line->stream, jb->line->size);
364 p
->nbrwc
= x2pos(ln
, jb
->nbc
);
366 if(dirtiness
== LINES_DIRTY
)
368 register ULONG nbl
= nb
;
369 /* Be sure "show" is the right pointer to the top_line */
370 if( p
->top_line
>= p
->max_lines
) p
->top_line
= p
->max_lines
-1;
371 if( p
->top_line
>= nbl
)
373 while(nbl
< p
->top_line
) nbl
++, ln
=ln
->next
;
376 /* and edited to nbl field */
377 if( p
->nbl
>= p
->max_lines
) p
->nbl
= p
->max_lines
-1;
380 while(nbl
< p
->nbl
) nbl
++, ln
=ln
->next
;
383 /* References may not be valid */
384 if(p
->ccp
.select
) adjust_ccp(p
);
386 move_to_line(p
, nb
, dirtiness
);
389 /*** Free last operation ***/
390 STRPTR
pop_last_op( JBuf jb
, char type
, char isgrouped
)
393 jb
->size
-= SizeOf
[ (unsigned char) type
];
395 /* If chunk is empty, it can be freed now */
401 if( jb
->ops
) jb
->size
= tmp
->size
;
406 /* Get previous operation */
409 ptr
= jb
->ops
->data
+ jb
->size
;
410 if( isgrouped
== 0 ) {
411 register STRPTR op
= ptr
- SizeOf
[ jb
->op
= LAST_TYPE(ptr
) ];
414 case ADD_CHAR
: jb
->nbc
= ((AddChar
) op
)->pos
+ ((AddChar
)op
)->nbc
; goto after
;
415 case REM_CHAR
: jb
->nbc
= ((RemChar
) op
)->pos
; after
:
416 case JOIN_LINE
: jb
->line
= ((JoinLine
)op
)->line
; break;
417 case REM_LINE
: jb
->line
= ((RemLine
) op
)->after
; break;
424 /*** Undo/redo the last operation ***/
425 void rollback( JBuf jb
)
427 static LINE
*lastline
;
428 static ULONG lastpos
;
430 char group
= 0, dirty
= LINE_DIRTY
, commit
;
433 if(jb
->ops
== NULL
) return;
434 ptr
= jb
->ops
->data
+ jb
->size
;
435 redolog
= jb
->rbtype
== 0 ? &PRJ(jb
)->redo
: &PRJ(jb
)->undo
;
436 commit
= IS_COMMIT(ptr
);
438 PRJ(jb
)->state
|= DONT_FLUSH
;
441 /* Cancel last operation */
442 switch( LAST_TYPE(ptr
) )
444 case ADD_CHAR
: /* Characters added to the buffer */
446 AddChar buf
= (AddChar
) (ptr
- sizeof(*buf
));
448 /* Operations spread over several lines must be refreshed */
449 if(group
&& jb
->line
!= buf
->line
) dirty
= LINES_DIRTY
;
451 /* Committed op must not be joined with the others */
452 if(commit
) redolog
->op
= 0;
454 lastpos
= buf
->pos
; lastline
= buf
->line
;
456 if((buf
->nbc
+= buf
->pos
) > buf
->line
->size
)
458 PRJ(jb
)->max_lines
--; dirty
= LINES_DIRTY
;
459 if(buf
->pos
== 0 && jb
->rbtype
!= 0) {
460 /* Whole line added => Remove it entirely */
461 if( lastline
== jb
->line
) jb
->line
= NULL
;
462 if( del_line(redolog
, &PRJ(jb
)->the_line
, buf
->line
) )
463 lastline
= lastline
->next
;
466 /* The last character keyed in this line was a '\n' */
467 buf
->nbc
--, join_lines(redolog
, buf
->line
, buf
->line
->next
);
470 /* Remove the last enterred word if it's a standalone modif */
471 if( buf
->nbc
> buf
->pos
)
473 if( group
== 0 && buf
->nbc
> 1 && jb
->rbtype
== 0 )
475 register ULONG pos
= backward_word(buf
->line
, buf
->nbc
-2);
477 if(pos
< buf
->pos
) pos
= buf
->pos
;
478 rem_chars(redolog
, buf
->line
, pos
, buf
->nbc
-1);
479 /* If the operation is non-empty, do not pops it */
480 if( ( buf
->nbc
= pos
- buf
->pos
) )
481 IS_COMMIT(ptr
) = 0, ptr
= NO_MEANING_VAL
, lastpos
+= buf
->nbc
;
483 else rem_chars(redolog
, buf
->line
, buf
->pos
, buf
->nbc
-1);
486 case REM_CHAR
: /* Characters remove from the buffer */
488 RemChar op
= (RemChar
) (ptr
- sizeof(*op
));
491 /* Operations spread over several lines must be refreshed */
492 if(group
&& jb
->line
!= op
->line
) dirty
= LINES_DIRTY
;
494 /* Committed op must not be joined with the others */
495 if(commit
) redolog
->op
= 0;
497 lastpos
= op
->pos
; lastline
= op
->line
;
500 /* The line in the rollback segment may be spread over several chunk */
501 if( jb
->rbsz
>= sz
) {
502 insert_str(redolog
, op
->line
, op
->pos
, jb
->rbseg
->data
+ jb
->rbsz
- sz
, sz
);
506 insert_str(redolog
, op
->line
, op
->pos
, jb
->rbseg
->data
, jb
->rbsz
);
508 jb
->rbsz
= (jb
->rbseg
= jb
->rbseg
->prev
)->max
;
509 FreeVec(jb
->rbseg
->next
);
510 jb
->rbseg
->next
= NULL
;
514 case REM_LINE
: /* Cancel line removed */
516 RemLine buf
= (RemLine
) (ptr
- sizeof(*buf
));
518 lastpos
= 0; lastline
= buf
->line
;
521 /* Restore line pointer */
522 InsertAfter(buf
->after
, buf
->line
); p
->max_lines
++;
523 reg_add_chars(redolog
, buf
->line
, 0, buf
->line
->size
+1);
525 if( buf
->after
== NULL
) /* :-/ */
526 buf
->line
->next
= p
->the_line
,
527 p
->the_line
->prev
= buf
->line
,
528 p
->the_line
= buf
->line
;
531 case JOIN_LINE
: /* Two lines joined */
533 JoinLine buf
= (JoinLine
) (ptr
- sizeof(*buf
));
538 /* Insert characters added at the end of the first line into the second */
539 insert_str(NULL
, buf
->old
, 0, buf
->line
->stream
+buf
->pos
, buf
->line
->size
-buf
->pos
);
541 if(buf
->pos
< buf
->line
->size
)
542 rem_chars(NULL
, buf
->line
, buf
->pos
+1, buf
->line
->size
);
543 reg_add_chars(redolog
, buf
->line
, buf
->line
->size
, 1);
544 lastpos
= (lastline
= buf
->line
)->size
;
546 InsertAfter(buf
->line
, buf
->old
); PRJ(jb
)->max_lines
++;
548 case GROUP_BY
: /* Group of operations */
549 reg_group_by(redolog
);
550 if( ( group
= !group
) ) goto pop_it
;
552 /* Get the first modified line */
553 if(jb
->line
== NULL
|| (jb
->rbtype
== 0 ? lastline
->prev
!= jb
->line
:
554 lastline
->next
== jb
->line
))
555 jb
->line
= lastline
, jb
->nbc
= lastpos
;
557 /* Move cursor to the last modification */
558 if(group
== 0) last_modif( jb
, dirty
);
560 /* Partial op cancelled : do not remove from rollback seg now */
561 if( ptr
== NO_MEANING_VAL
) break;
563 /* Remove op from memory */
564 pop_it
: ptr
= pop_last_op(jb
, LAST_TYPE(ptr
), group
);
568 /* Transfer commit flag and savepoint into redolog */
570 IS_COMMIT(PRJ(jb
)->savepoint
= redolog
->ops
->data
+ redolog
->size
) = 1;
572 PRJ(jb
)->state
&= ~DONT_FLUSH
;
574 /* Last savepoint reached or left? */
575 if((PRJ(jb
)->state
& MODIFIED
) == 0)
576 set_modif_mark(PRJ(jb
));
577 else if(jb
->rbtype
? commit
: PRJ(jb
)->savepoint
== ptr
)
578 unset_modif_mark(PRJ(jb
), TRUE
);