URI: 
       add undo support for box, visual, cut/paste - gramscii - A simple editor for ASCII box-and-arrow charts
   DIR Log
   DIR Files
   DIR Refs
   DIR Tags
   DIR README
   DIR LICENSE
       ---
   DIR commit f3100ddd486d6f39b0c97c9c492bb6020bf3caf1
   DIR parent 526ce3a130732d4a2374a6e36a689d9e0cf5cc34
  HTML Author: KatolaZ <katolaz@freaknet.org>
       Date:   Wed, 31 Jul 2019 11:19:55 +0100
       
       add undo support for box, visual, cut/paste
       
       Diffstat:
         M TODO                                |       4 ++++
         M draw.c                              |      32 +++++++++++++++++--------------
         M gramscii.1                          |      24 ++++++++++++++++++++++--
         M gramscii.h                          |       4 ++--
         M lineset.c                           |      38 +++++++++++++++++--------------
         M screen.c                            |       4 ++--
       
       6 files changed, 69 insertions(+), 37 deletions(-)
       ---
   DIR diff --git a/TODO b/TODO
       @@ -1,5 +1,9 @@
        + optimize redraws (redraw only the modified rectangle)
        + undo (by storing lines changed across insert/remove operations)
       +  * re-organise undo-ring management
       +  - add undo for arrow mode
       +  - add undo for text mode
       +  - add undo for erase mode
        - fix bug with 'g' commands in arrow mode
        - add screen geometry option (-g 25x80?)
        - read file at point
   DIR diff --git a/draw.c b/draw.c
       @@ -112,7 +112,7 @@ void draw_box(int x1, int y1, int fix){
        
                if (fix == FIX){
                        f = set_xy;
       -                copy_lines_to_ring(ymin, ymax, CUR);
       +                copy_lines_to_ring(ymin, ymax, PRV_STATE);
                }
                else
                        f = draw_xy;
       @@ -130,7 +130,7 @@ void draw_box(int x1, int y1, int fix){
                f(xmax, ymin, corner);
                f(xmax, ymax, corner);
                if (fix == FIX)
       -                copy_lines_to_ring(ymin, ymax, LST);
       +                copy_lines_to_ring(ymin, ymax, NEW_STATE);
                show_cursor();
        }
        
       @@ -316,10 +316,10 @@ void visual_box(FILE *fc){
                                case 'x':/* erase */
                                        if (c == 'x')
                                                yank_region(MIN(orig_x,x), MIN(orig_y,y), MAX(orig_x, x), MAX(orig_y, y));
       -                                copy_lines_to_ring(MIN(orig_y, y), MAX(orig_y, y), CUR);
       +                                copy_lines_to_ring(MIN(orig_y, y), MAX(orig_y, y), PRV_STATE);
                                        erase_box(orig_x, orig_y, f);
                                        erase_blank_lines(MIN(y,orig_y), MAX(y, orig_y));
       -                                copy_lines_to_ring(MIN(orig_y, y), MAX(orig_y, y), LST);
       +                                copy_lines_to_ring(MIN(orig_y, y), MAX(orig_y, y), NEW_STATE);
                        
                                        modified = 1;
                                        goto vis_exit;
       @@ -345,9 +345,9 @@ void paste(){
                int y2;
        
                y2 = y + cutbuf.num - 1;
       -        copy_lines_to_ring(y, y2, CUR);
       +        copy_lines_to_ring(y, y2, PRV_STATE);
                paste_region(x, y);
       -        copy_lines_to_ring(y, y2, LST);
       +        copy_lines_to_ring(y, y2, NEW_STATE);
                redraw();
        }
        
       @@ -356,28 +356,32 @@ void put_lines(lineset_t *u){
                
                for (i=0; i< u->num; i++){
                        n = u->l[i].n;
       -                ensure_line_length(&(screen.l[i]), u->l[i].lst);
       +                ensure_line_length(&(screen.l[i]), strlen(u->l[i].s));
                        strcpy(screen.l[n].s, u->l[i].s);
       -                screen.l[n].lst = u->l[i].lst;
       +                screen.l[n].lst = strlen(u->l[i].s)-1;
                }
        }
        
        
        void undo_change(){
                if (undo_cur >= 0){
       +                if (undo_cur > undo_lst)
       +                        undo_cur = undo_lst;
                        put_lines(& (undo[undo_cur]));
       -                undo_cur --;
       +                undo_cur -= 2;
       +                modified = 1;
                }
                redraw();
       -        modified = 1;
        }
        
        void redo_change(){
       -        if (undo_cur < undo_lst){
       -                undo_cur ++;
       -                put_lines(& (undo[undo_cur]));
       +        if (undo_cur <= undo_lst-2){
       +                if (undo_cur > 0)
       +                        put_lines(& (undo[undo_cur+1]));
       +                undo_cur +=2;
       +                put_lines(& (undo[undo_cur+1]));
       +                modified = 1;
                }
                redraw();
       -        modified = 1;
        }
        
   DIR diff --git a/gramscii.1 b/gramscii.1
       @@ -63,6 +63,26 @@ buffer contains the rectangle yanked/cut in
        .B visual
        mode. 
        .TP 5m
       +.BI u
       +Undo the last change. gramscii supports an undo history of indefinite
       +length. The command 
       +.BI u
       +gets the last change from the history, and moves the history pointer
       +back by one change. See the related command
       +.BI U
       +below.
       +.TP 5m
       +.BI U
       +Redo, i.e., cancel a previous
       +.BI u
       +command. gramscii supports an undo history of indefinite length. The
       +command
       +.BI U
       +moves to the following change, if possible. For instance, the sequence 
       +.BI uuU
       +will go back two changes, and then forward one, effectively resetting
       +the state of the screen to what it was before the last change occurred.
       +.TP 5m
        .BI q
        Quit gramscii, and prompt for a filename if the current screen contains
        unsaved changes.
       @@ -516,8 +536,8 @@ support scrolling and "virtual" screens of any (reasonable) size.
        .PP
        gramscii currently does
        .B not
       -provide an "undo" command. This requires a restructuring of buffer
       -management, and will most probably be implemented in a future release.
       +support "undo" commands for arrow, text, and erase mode. This will be
       +added soon.
        .SH AUTHORS
        gramscii is written and maintained by Vincenzo "KatolaZ" Nicosia
        <katolaz@freaknet.org>. You can use, copy, modify, and redistribute
   DIR diff --git a/gramscii.h b/gramscii.h
       @@ -44,8 +44,8 @@
        #define VIDEO_NRM 0
        #define VIDEO_REV 7 
        
       -#define CUR 0x01
       -#define LST 0x02
       +#define PRV_STATE 0x01
       +#define NEW_STATE 0x02
        
        /** types **/
        
   DIR diff --git a/lineset.c b/lineset.c
       @@ -127,19 +127,24 @@ void paste_region(int x1, int y1){
        }
        
        void copy_lines_to_ring(int y1, int y2, int which){
       -        lineset_t *tmp;
       -        int i, len, *idx;
       +        int i, len, idx;
       +        int offset;
       +        lineset_t *tmp;        
        
                if (y1 > y2){
                        y1 ^= y2;
                        y2 ^= y1;
                        y1 ^= y2;
                }
       -        if (which == CUR)
       -                idx = &undo_cur;
       +        if (undo_cur > undo_lst)
       +                undo_cur = undo_lst;
       +        if (which == PRV_STATE){ /* adding a new previous state */
       +                undo_cur += 2;
       +                idx = undo_cur;
       +        }
                else
       -                idx = &undo_lst;
       -        if (*idx == undo_sz - 1){
       +                idx = undo_cur + 1;
       +        if (idx >= undo_sz - 1){
                        undo_sz += 10;
                        tmp = realloc(undo, undo_sz * sizeof(lineset_t));
                        if (tmp == NULL){
       @@ -148,22 +153,21 @@ void copy_lines_to_ring(int y1, int y2, int which){
                        }
                        undo = tmp;
                }
       -        (*idx) ++;
       -        ensure_num_lines(&(undo[*idx]), y2 - y1 + 1);
       +        ensure_num_lines(&(undo[idx]), y2 - y1 + 1);
                for(i=y1; i<=y2; i++){
                        len = strlen(screen.l[i].s);
       -                ensure_line_length(&(undo[*idx].l[i-y1]), len);
       -                strcpy(undo[*idx].l[i-y1].s, screen.l[i].s);
       -                undo[*idx].l[i-y1].n = i;
       -                undo[*idx].l[i-y1].lst = screen.l[i].lst;
       +                ensure_line_length(&(undo[idx].l[i-y1]), len);
       +                strcpy(undo[idx].l[i-y1].s, screen.l[i].s);
       +                undo[idx].l[i-y1].n = i;
       +                undo[idx].l[i-y1].lst = screen.l[i].lst;
                }
       -        undo[*idx].num = y2 - y1 + 1;
       -        if (which == CUR)
       +        undo[idx].num = y2 - y1 + 1;
       +        if (which == PRV_STATE)
                        undo_lst = undo_cur;
        #ifdef DEBUG
       -        fprintf(stderr, "undo_ring: y1: %d y2: %d idx: %d\n", y1, y2, *idx);
       -        for(i=0; i<undo[undo_cur].num; i++){
       -                fprintf(stderr, "UU: %d| %s\n", undo[*idx].l[i].n, undo[*idx].l[i].s);
       +        fprintf(stderr, "undo_ring: y1: %d y2: %d idx: %d\n", y1, y2, idx);
       +        for(i=0; i<undo[idx].num; i++){
       +                fprintf(stderr, "UU: %d| %s\n", undo[idx].l[i].n, undo[idx].l[i].s);
                }
        #endif        
        }
   DIR diff --git a/screen.c b/screen.c
       @@ -410,8 +410,8 @@ void init_screen(){
                cutbuf.num = 0;
                
                undo_sz = 0;
       -        undo_cur = -1;
       -        undo_lst = -1;
       +        undo_cur = -2;
       +        undo_lst = -2;
        }
        
        void find_nonblank_rect(int *x1, int *y1, int *x2, int *y2){