Added empty bootstrap.c and its build target
[AROS.git] / workbench / tools / Edit / UndoRedo.c
blobcd38df58bd6be95b4e3300668cebe3b6cba64266
1 /**********************************************************
2 ** **
3 ** $VER: UndoRedo.c 1.2 (3.2.2001) **
4 ** Implementation of journalized buffer **
5 ** **
6 ** © T.Pierron, C.Guilaume. Free software under **
7 ** terms of GNU public license. **
8 ** **
9 **********************************************************/
11 #include <exec/types.h>
12 #include <exec/memory.h>
13 #include "Jed.h"
14 #include "Utility.h"
15 #include "ProtoTypes.h"
16 #define DEBUG_UNDO_STUFF /* Only activated if DEBUG macro is defined */
17 #include "Debug.h"
19 /** Sizeof each struct **/
20 UBYTE SizeOf[] = {0,
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)
47 RBOps new;
48 void *ret;
49 /* Enough place? */
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)))
54 return NULL;
56 new->prev = jb->ops;
57 new->size = jb->size; /* Save old usage */
58 jb->ops = new;
59 jb->size = size;
60 ret = new->data;
61 } else {
62 ret = jb->ops->data + jb->size;
63 jb->size += size;
65 IS_COMMIT((STRPTR)ret+size) = 0;
66 return ret;
69 /*** Alloc a new rollback segment (data from file) ***/
70 static char new_rbseg(JBuf jb, ULONG size)
72 RBSeg new;
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) ) )
80 new->max = size;
81 new->prev = jb->rbseg;
82 new->next = NULL;
83 if(jb->rbseg) jb->rbseg->next = new;
84 else jb->rbseg = new;
86 else return 0;
88 else if( jb->rbsz + size > jb->rbseg->max )
90 /* This time, fully use segment capacity */
91 size -= jb->rbseg->max - jb->rbsz;
92 goto alloc;
94 return 1;
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;
106 else
107 p->savepoint = NULL;
110 /*** Free memory used for one project's undo buf ***/
111 void flush_undo_buf( JBuf jb )
113 RBOps jd, prev;
114 UWORD size;
116 for(jd=jb->ops, size=jb->size; jd; size=jd->size, prev=jd->prev, FreeVec(jd), jd=prev)
118 register UBYTE type;
119 register STRPTR op;
120 /* Some operations contain non-freed data */
121 while(size != 0)
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;
128 switch ( type )
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)
144 AddChar buf;
146 PRE_CHECK( jb );
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)
155 /* Yes! */
156 jb->nbc = pos+1, buf->nbc += nb;
157 else
158 goto new_op;
160 else /* Register the new operation */
162 new_op:
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;
168 buf->nbc = nb;
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)
191 STRPTR dst, src;
192 RBSeg dmin, smin;
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;
201 } jb->rbseg = dmin;
203 /* Move size bytes from src to dst */
204 while( size-- )
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;
208 *dst-- = *src--;
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;
214 *dst-- = *src--;
218 /*** Register some characters removed from the buffer ***/
219 void reg_rem_chars(JBuf jb, LINE *ln, ULONG s, ULONG e)
221 RemChar buf;
223 PRE_CHECK( jb );
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)
232 if(buf->nbc == 1) {
233 if( pop_last_op(jb, ADD_CHAR, 0) == PRJ(jb)->savepoint )
234 unset_modif_mark(PRJ(jb), TRUE);
235 } else buf->nbc--;
236 return;
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)
245 goto new_op;
246 else
247 if( new_rbseg(jb, e-s+1) )
249 if(s < buf->pos) {
250 s = buf->pos-s; buf->pos -= s; e -= s;
251 move_rbseg(jb, ln->stream+buf->pos, buf->nbc, s);
252 buf->nbc += s;
253 } else s = 0;
255 if(e >= buf->pos)
256 copy_rbseg(jb, ln->stream+buf->pos+s, e-buf->pos+1),
257 buf->nbc += e-buf->pos+1;
258 jb->nbc = buf->pos;
261 else /* New operation */
263 new_op:
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)
278 RemLine buf;
280 PRE_CHECK( jb );
282 if( NULL != (buf = new_undo_buf(jb, sizeof(*buf))) )
284 buf->line = ln;
285 buf->type = jb->op = REM_LINE;
286 buf->after = (ln->prev ? ln->prev : NULL);
287 jb->line = (ln->next ? ln->next : ln);
288 jb->nbc = 0;
292 /*** Register two lines joined ***/
293 void reg_join_lines(JBuf jb, LINE *ln, LINE *ln2)
295 JoinLine buf;
297 PRE_CHECK( jb );
299 if( NULL != (buf = new_undo_buf(jb, sizeof(*buf))) )
301 /* Keep original pointers since further modif. may reference them */
302 buf->line = ln;
303 buf->old = ln2;
304 buf->type = jb->op = JOIN_LINE;
305 buf->pos = jb->nbc = ln->size;
306 jb->line = ln;
310 /*** Group of modifications ***/
311 void reg_group_by( JBuf jb )
313 GroupBy buf;
315 /* No PRE_CHECK since GROUP_BY type doesn't imply any modif. */
316 if( NULL != (buf = new_undo_buf(jb, sizeof(*buf))) )
318 jb->op = GROUP_BY;
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 */
328 register LINE *ln;
329 register ULONG nb;
331 for(ln=p->the_line,nb=0; ln && ln->flags == 0; ln=ln->next, nb++);
332 if(ln->next != NULL)
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;
338 register ULONG nbl;
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++);
358 /* if(ln == NULL) {
359 printf("Line not found! (0x%08x - 0x%08x)", jb->line, p->the_line);
360 print_n(jb->line->stream, jb->line->size);
361 return;
362 } */
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;
374 p->show = ln;
376 /* and edited to nbl field */
377 if( p->nbl >= p->max_lines ) p->nbl = p->max_lines-1;
378 if( p->nbl >= nbl )
380 while(nbl < p->nbl) nbl++, ln=ln->next;
381 p->edited = ln;
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 )
392 STRPTR ptr = NULL;
393 jb->size -= SizeOf[ (unsigned char) type ];
395 /* If chunk is empty, it can be freed now */
396 if( jb->size == 0 )
398 RBOps tmp = jb->ops;
400 jb->ops = tmp->prev;
401 if( jb->ops ) jb->size = tmp->size;
402 else jb->op = 0;
404 FreeVec( tmp );
406 /* Get previous operation */
407 if( jb->size != 0 )
409 ptr = jb->ops->data + jb->size;
410 if( isgrouped == 0 ) {
411 register STRPTR op = ptr - SizeOf[ jb->op = LAST_TYPE(ptr) ];
412 switch( jb->op )
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;
421 return ptr;
424 /*** Undo/redo the last operation ***/
425 void rollback( JBuf jb )
427 static LINE *lastline;
428 static ULONG lastpos;
429 STRPTR ptr;
430 char group = 0, dirty = LINE_DIRTY, commit;
431 JBuf redolog;
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);
437 jb->line = NULL;
438 PRJ(jb)->state |= DONT_FLUSH;
440 do {
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;
464 break;
465 } else
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);
485 } break;
486 case REM_CHAR: /* Characters remove from the buffer */
488 RemChar op = (RemChar) (ptr - sizeof(*op));
489 ULONG sz = op->nbc;
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;
499 for(;;) {
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);
503 jb->rbsz -= sz;
504 break;
505 } else {
506 insert_str(redolog, op->line, op->pos, jb->rbseg->data, jb->rbsz);
507 sz -= jb->rbsz;
508 jb->rbsz = (jb->rbseg = jb->rbseg->prev)->max;
509 FreeVec(jb->rbseg->next);
510 jb->rbseg->next = NULL;
513 } break;
514 case REM_LINE: /* Cancel line removed */
516 RemLine buf = (RemLine) (ptr - sizeof(*buf));
517 Project p = PRJ(jb);
518 lastpos = 0; lastline = buf->line;
520 dirty = LINES_DIRTY;
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;
530 } break;
531 case JOIN_LINE: /* Two lines joined */
533 JoinLine buf = (JoinLine) (ptr - sizeof(*buf));
535 buf->old->size = 0;
536 dirty = LINES_DIRTY;
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++;
547 } break;
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);
566 } while( group );
568 /* Transfer commit flag and savepoint into redolog */
569 if( commit )
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);