From 60815461615e248978c8f59c08bc4937fecaf775 Mon Sep 17 00:00:00 2001 From: Benno Schulenberg Date: Thu, 15 May 2014 20:00:46 +0000 Subject: [PATCH] Going to the correct positions for undoing and redoing cuts and pastes. This fixes many undo problems and Savannah bug #25585. *Patch by Mark Majeres.* git-svn-id: svn://svn.savannah.gnu.org/nano/trunk/nano@4893 35c25a1d-7b9e-4130-9fde-d3aeb78583b8 --- ChangeLog | 5 ++ src/cut.c | 9 ++- src/move.c | 2 +- src/nano.c | 6 +- src/nano.h | 6 +- src/proto.h | 1 + src/search.c | 14 ++++ src/text.c | 211 +++++++++++++++++++++++++++++---------------------- src/utils.c | 5 +- 9 files changed, 158 insertions(+), 101 deletions(-) diff --git a/ChangeLog b/ChangeLog index 3a66e2e5..ab8baf4e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2014-05-15 Mark Majeres + * src/*, but mainly src/text.c (undo_cut, redo_cut, do_undo, do_redo): + Go to the correct positions for undoing/redoing the cuts and pastes. + This fixes several undo problems and Savannah bug #25585. + 2014-05-15 Benno Schulenberg * doc/syntax/c.nanorc: Improve the magic regex, plus tweaks. * src/color.c (color_update): Adjust a comment, and be clearer. diff --git a/src/cut.c b/src/cut.c index 4ec428bf..823ec6fb 100644 --- a/src/cut.c +++ b/src/cut.c @@ -44,6 +44,9 @@ void cutbuffer_reset(void) * current line. */ void cut_line(void) { + if(!openfile->mark_begin) + openfile->mark_begin = openfile->current; + if (openfile->current != openfile->filebot) move_to_filestruct(&cutbuffer, &cutbottom, openfile->current, 0, openfile->current->next, 0); @@ -204,7 +207,7 @@ void do_cut_text( if (!old_no_newlines) UNSET(NO_NEWLINES); } else if (!undoing) - update_undo(CUT); + update_undo(cut_till_end ? CUT_EOF : CUT); /* Leave the text in the cutbuffer, and mark the file as * modified. */ @@ -249,7 +252,7 @@ void do_copy_text(void) /* Cut from the current cursor position to the end of the file. */ void do_cut_till_end(void) { - add_undo(CUT); + add_undo(CUT_EOF); do_cut_text(FALSE, TRUE, FALSE); } #endif /* !NANO_TINY */ @@ -264,7 +267,7 @@ void do_uncut_text(void) return; #ifndef NANO_TINY - update_undo(UNCUT); + add_undo(PASTE); #endif /* Add a copy of the text in the cutbuffer to the current filestruct diff --git a/src/move.c b/src/move.c index d8d912a0..355fa027 100644 --- a/src/move.c +++ b/src/move.c @@ -570,7 +570,7 @@ void do_down( #endif /* If we're at the bottom of the file, get out. */ - if (openfile->current == openfile->filebot) + if (openfile->current == openfile->filebot || !openfile->current->next) return; assert(ISSET(SOFTWRAP) || openfile->current_y == openfile->current->lineno - openfile->edittop->lineno); diff --git a/src/nano.c b/src/nano.c index 9ca60e1d..bf2d17bf 100644 --- a/src/nano.c +++ b/src/nano.c @@ -2030,7 +2030,7 @@ void do_output(char *output, size_t output_len, bool allow_cntrls) set_modified(); #ifndef NANO_TINY - update_undo(ADD); + add_undo(ADD); /* Note that current_x has not yet been incremented. */ if (openfile->mark_set && openfile->current == @@ -2041,6 +2041,10 @@ void do_output(char *output, size_t output_len, bool allow_cntrls) openfile->current_x += char_buf_len; +#ifndef NANO_TINY + update_undo(ADD); +#endif + #ifndef DISABLE_WRAPPING /* If we're wrapping text, we need to call edit_refresh(). */ if (!ISSET(NO_WRAP)) diff --git a/src/nano.h b/src/nano.h index a1da639a..c97faad6 100644 --- a/src/nano.h +++ b/src/nano.h @@ -187,7 +187,7 @@ typedef enum { } function_type; typedef enum { - ADD, DEL, REPLACE, SPLIT, UNSPLIT, CUT, UNCUT, ENTER, INSERT, OTHER + ADD, DEL, REPLACE, SPLIT, UNSPLIT, CUT, CUT_EOF, PASTE, ENTER, INSERT, OTHER } undo_type; typedef struct color_pair { @@ -324,7 +324,7 @@ typedef struct undo { ssize_t lineno; undo_type type; /* What type of undo this was. */ - int begin; + size_t begin; /* Where did this action begin or end. */ char *strdata; /* String type data we will use for copying the affected line back. */ @@ -344,7 +344,7 @@ typedef struct undo { /* Was this a cut to end? */ ssize_t mark_begin_lineno; /* copy copy copy */ - ssize_t mark_begin_x; + size_t mark_begin_x; /* Another shadow variable. */ struct undo *next; } undo; diff --git a/src/proto.h b/src/proto.h index c8a4de7a..c1f17b9b 100644 --- a/src/proto.h +++ b/src/proto.h @@ -604,6 +604,7 @@ void do_gotolinecolumn_void(void); void do_gotopos(ssize_t pos_line, size_t pos_x, ssize_t pos_y, size_t pos_pww); #endif +void goto_line_posx(ssize_t line, size_t pos_x); #ifndef NANO_TINY bool find_bracket_match(bool reverse, const char *bracket_set); void do_find_bracket(void); diff --git a/src/search.c b/src/search.c index d841b463..2480bb9c 100644 --- a/src/search.c +++ b/src/search.c @@ -1008,6 +1008,20 @@ void do_replace(void) search_replace_abort(); } +/* Go to the specified line and x position. */ +void goto_line_posx(ssize_t line, size_t pos_x) +{ + for (openfile->current = openfile->fileage; openfile->current != openfile->filebot && + openfile->current->next != NULL && line > 1; line--) + openfile->current = openfile->current->next; + + openfile->current_x = pos_x; + openfile->placewewant = xplustabs(); + + edit_refresh_needed = TRUE; + edit_refresh(); +} + /* Go to the specified line and column, or ask for them if interactive * is TRUE. Save the x-coordinate and y-coordinate if save_pos is TRUE. * Update the screen afterwards if allow_update is TRUE. Note that both diff --git a/src/text.c b/src/text.c index 94696129..aee832e8 100644 --- a/src/text.c +++ b/src/text.c @@ -112,9 +112,8 @@ void do_delete(void) edit_refresh_needed = TRUE; openfile->current->data = charealloc(openfile->current->data, - openfile->current_x + strlen(foo->data) + 1); - strcpy(openfile->current->data + openfile->current_x, - foo->data); + strlen(openfile->current->data) + strlen(foo->data) + 1); + strcat(openfile->current->data, foo->data); #ifndef NANO_TINY if (openfile->mark_set && openfile->mark_begin == openfile->current->next) { @@ -359,6 +358,9 @@ void do_unindent(void) do_indent(-tabsize); } +#define redo_paste undo_cut +#define undo_paste redo_cut + /* Undo a cut, or redo an uncut. */ void undo_cut(undo *u) { @@ -373,10 +375,7 @@ void undo_cut(undo *u) ; /* Get to where we need to uncut from. */ - if (u->mark_set && u->mark_begin_lineno < u->lineno) - do_gotolinecolumn(u->mark_begin_lineno, u->mark_begin_x+1, FALSE, FALSE, FALSE, FALSE); - else - do_gotolinecolumn(u->lineno, u->begin+1, FALSE, FALSE, FALSE, FALSE); + goto_line_posx(u->mark_begin_lineno, u->mark_begin_x); copy_from_filestruct(cutbuffer); free_filestruct(cutbuffer); @@ -386,39 +385,26 @@ void undo_cut(undo *u) /* Redo a cut, or undo an uncut. */ void redo_cut(undo *u) { - int i; - filestruct *t, *c; - /* If we cut the magicline, we may as well not crash. :/ */ if (!u->cutbuffer) return; - do_gotolinecolumn(u->lineno, u->begin+1, FALSE, FALSE, FALSE, FALSE); + goto_line_posx(u->lineno, u->begin); + + if (ISSET(NO_NEWLINES) && openfile->current->lineno != u->lineno) { + openfile->current_x = strlen(openfile->current->data); + openfile->placewewant = xplustabs(); + } + openfile->mark_set = u->mark_set; if (cutbuffer) free(cutbuffer); cutbuffer = NULL; - /* Move ahead the same number of lines we had if a marked cut. */ - if (u->mark_set) { - for (i = 1, t = openfile->fileage; i != u->mark_begin_lineno; i++) - t = t->next; - openfile->mark_begin = t; - } else if (!u->to_end) { - /* Here we have a regular old potentially multi-line ^K cut. - * We'll need to trick nano into thinking it's a marked cut, - * to cut more than one line again. */ - for (c = u->cutbuffer, t = openfile->current; c->next != NULL && t->next != NULL; ) { -#ifdef DEBUG - fprintf(stderr, "Advancing, lineno = %lu, data = \"%s\"\n", (unsigned long) t->lineno, t->data); -#endif - c = c->next; - t = t->next; - } - openfile->mark_begin = t; - openfile->mark_begin_x = 0; + openfile->mark_begin = fsfromline(u->mark_begin_lineno); + + if (!ISSET(CUT_TO_END)) openfile->mark_set = TRUE; - } openfile->mark_begin_x = u->mark_begin_x; do_cut_text(FALSE, u->to_end, TRUE); @@ -431,8 +417,9 @@ void redo_cut(undo *u) /* Undo the last thing(s) we did. */ void do_undo(void) { + bool gotolinecolumn = FALSE; undo *u = openfile->current_undo; - filestruct *f = openfile->current, *t; + filestruct *t = 0; int len = 0; char *undidmsg, *data; filestruct *oldcutbuffer = cutbuffer, *oldcutbottom = cutbottom; @@ -442,14 +429,9 @@ void do_undo(void) return; } - if (u->lineno <= f->lineno) - for (; f->prev != NULL && f->lineno != u->lineno; f = f->prev) - ; - else - for (; f->next != NULL && f->lineno != u->lineno; f = f->next) - ; - if (f->lineno != u->lineno) { - statusbar(_("Internal error: can't match line %d. Please save your work."), u->lineno); + filestruct *f = fsfromline(u->mark_begin_lineno); + if (!f) { + statusbar(_("Internal error: can't match line %d. Please save your work."), u->mark_begin_lineno); return; } #ifdef DEBUG @@ -467,6 +449,7 @@ void do_undo(void) strcpy(&data[u->begin], &f->data[u->begin + strlen(u->strdata)]); free(f->data); f->data = data; + goto_line_posx(u->lineno, u->begin); break; case DEL: undidmsg = _("text delete"); @@ -480,6 +463,7 @@ void do_undo(void) f->data = data; if (u->xflags == UNdel_backspace) openfile->current_x += strlen(u->strdata); + goto_line_posx(u->mark_begin_lineno, u->mark_begin_x + 1); break; #ifndef DISABLE_WRAPPING case SPLIT: @@ -494,26 +478,28 @@ void do_undo(void) delete_node(foo); } renumber(f); + gotolinecolumn = TRUE; break; #endif /* !DISABLE_WRAPPING */ case UNSPLIT: undidmsg = _("line join"); t = make_new_node(f); t->data = mallocstrcpy(NULL, u->strdata); - data = mallocstrncpy(NULL, f->data, u->begin); + data = mallocstrncpy(NULL, f->data, u->begin + 1); data[u->begin] = '\0'; free(f->data); f->data = data; splice_node(f, t, f->next); - renumber(f); + gotolinecolumn = TRUE; break; + case CUT_EOF: case CUT: undidmsg = _("text cut"); undo_cut(u); break; - case UNCUT: + case PASTE: undidmsg = _("text uncut"); - redo_cut(u); + undo_paste(u); break; case ENTER: undidmsg = _("line break"); @@ -524,6 +510,7 @@ void do_undo(void) unlink_node(foo); delete_node(foo); } + goto_line_posx(u->lineno, u->begin); break; case INSERT: undidmsg = _("text insert"); @@ -535,7 +522,7 @@ void do_undo(void) openfile->mark_begin = fsfromline(u->lineno + u->mark_begin_lineno - 1); openfile->mark_begin_x = 0; openfile->mark_set = TRUE; - do_gotolinecolumn(u->lineno, u->begin+1, FALSE, FALSE, FALSE, FALSE); + goto_line_posx(u->lineno, u->begin); cut_marked(); u->cutbuffer = cutbuffer; u->cutbottom = cutbottom; @@ -545,6 +532,7 @@ void do_undo(void) break; case REPLACE: undidmsg = _("text replace"); + goto_line_posx(u->lineno, u->begin); data = u->strdata; u->strdata = f->data; f->data = data; @@ -555,17 +543,19 @@ void do_undo(void) } renumber(f); - do_gotolinecolumn(u->lineno, u->begin, FALSE, FALSE, FALSE, TRUE); + if (gotolinecolumn) + do_gotolinecolumn(u->lineno, u->begin, FALSE, FALSE, FALSE, TRUE); statusbar(_("Undid action (%s)"), undidmsg); openfile->current_undo = openfile->current_undo->next; openfile->last_action = OTHER; + set_modified(); } /* Redo the last thing(s) we undid. */ void do_redo(void) { + bool gotolinecolumn = FALSE; undo *u = openfile->undotop; - filestruct *f = openfile->current; int len = 0; char *undidmsg, *data; @@ -580,14 +570,9 @@ void do_redo(void) return; } - if (u->lineno <= f->lineno) - for (; f->prev != NULL && f->lineno != u->lineno; f = f->prev) - ; - else - for (; f->next != NULL && f->lineno != u->lineno; f = f->next) - ; - if (f->lineno != u->lineno) { - statusbar(_("Internal error: can't match line %d. Please save your work."), u->lineno); + filestruct *f = fsfromline(u->mark_begin_lineno); + if (!f) { + statusbar(_("Internal error: can't match line %d. Please save your work."), u->mark_begin_lineno); return; } #ifdef DEBUG @@ -605,6 +590,7 @@ void do_redo(void) strcpy(&data[u->begin + strlen(u->strdata)], &f->data[u->begin]); free(f->data); f->data = data; + goto_line_posx(u->mark_begin_lineno, u->mark_begin_x); break; case DEL: undidmsg = _("text delete"); @@ -614,10 +600,13 @@ void do_redo(void) strcpy(&data[u->begin], &f->data[u->begin + strlen(u->strdata)]); free(f->data); f->data = data; + openfile->current_x = u->begin; + openfile->placewewant = xplustabs(); + goto_line_posx(u->lineno, u->begin); break; case ENTER: undidmsg = _("line break"); - do_gotolinecolumn(u->lineno, u->begin+1, FALSE, FALSE, FALSE, FALSE); + goto_line_posx(u->lineno, u->begin); do_enter(TRUE); break; #ifndef DISABLE_WRAPPING @@ -627,53 +616,55 @@ void do_redo(void) prepend_wrap = TRUE; do_wrap(f, TRUE); renumber(f); + gotolinecolumn = TRUE; break; #endif /* !DISABLE_WRAPPING */ case UNSPLIT: undidmsg = _("line join"); - len = strlen(f->data) + strlen(u->strdata + 1); - data = charalloc(len); - strcpy(data, f->data); - strcat(data, u->strdata); - free(f->data); - f->data = data; + len = strlen(f->data) + strlen(u->strdata) + 1; + f->data = charealloc(f->data, len); + strcat(f->data, u->strdata); if (f->next != NULL) { filestruct *tmp = f->next; unlink_node(tmp); delete_node(tmp); } renumber(f); + gotolinecolumn = TRUE; break; + case CUT_EOF: case CUT: undidmsg = _("text cut"); redo_cut(u); break; - case UNCUT: + case PASTE: undidmsg = _("text uncut"); - undo_cut(u); + redo_paste(u); break; case REPLACE: undidmsg = _("text replace"); data = u->strdata; u->strdata = f->data; f->data = data; + goto_line_posx(u->lineno, u->begin); break; case INSERT: undidmsg = _("text insert"); - do_gotolinecolumn(u->lineno, u->begin+1, FALSE, FALSE, FALSE, FALSE); + goto_line_posx(u->lineno, u->begin); copy_from_filestruct(u->cutbuffer); - openfile->placewewant = xplustabs(); break; default: undidmsg = _("Internal error: unknown type. Please save your work."); break; } - do_gotolinecolumn(u->lineno, u->begin, FALSE, FALSE, FALSE, TRUE); + if (gotolinecolumn) + do_gotolinecolumn(u->lineno, u->begin, FALSE, FALSE, FALSE, TRUE); statusbar(_("Redid action (%s)"), undidmsg); openfile->current_undo = u; openfile->last_action = OTHER; + set_modified(); } #endif /* !NANO_TINY */ @@ -841,7 +832,6 @@ void add_undo(undo_type current_action) undo *u; char *data; openfilestruct *fs = openfile; - static undo *last_cutu = NULL; /* Last thing we cut to set up the undo for uncut. */ ssize_t wrap_loc; /* For calculating split beginning. */ @@ -852,8 +842,9 @@ void add_undo(undo_type current_action) /* Ugh, if we were called while cutting not-to-end, non-marked, and * on the same lineno, we need to abort here. */ u = fs->current_undo; - if (current_action == CUT && u && u->type == CUT - && !u->mark_set && u->lineno == fs->current->lineno) + if (u && u->mark_begin_lineno == fs->current->lineno && + ((current_action == CUT && u->type == CUT && !u->mark_set) || + (current_action == ADD && u->type == ADD && u->mark_begin_x == fs->current_x))) return; /* Blow away the old undo stack if we are starting from the middle. */ @@ -880,8 +871,8 @@ void add_undo(undo_type current_action) u->cutbuffer = NULL; u->cutbottom = NULL; u->mark_set = 0; - u->mark_begin_lineno = 0; - u->mark_begin_x = 0; + u->mark_begin_lineno = fs->current->lineno; + u->mark_begin_x = fs->current_x; u->xflags = 0; u->to_end = FALSE; @@ -890,8 +881,7 @@ void add_undo(undo_type current_action) * or we won't be able to restore it later. */ case ADD: data = charalloc(2); - data[0] = fs->current->data[fs->current_x]; - data[1] = '\0'; + data[0] = '\0'; u->strdata = data; break; case DEL: @@ -929,21 +919,43 @@ void add_undo(undo_type current_action) data = mallocstrcpy(NULL, fs->current->data); u->strdata = data; break; + case CUT_EOF: + u->to_end = TRUE; case CUT: u->mark_set = openfile->mark_set; if (u->mark_set) { u->mark_begin_lineno = openfile->mark_begin->lineno; u->mark_begin_x = openfile->mark_begin_x; } - u->to_end = (ISSET(CUT_TO_END)) ? TRUE : FALSE; - last_cutu = u; + else if (!ISSET(CUT_TO_END) && !u->to_end) { + /* The entire line is being cut regardless of the cursor position. */ + u->begin = 0; + u->mark_begin_x = 0; + } break; - case UNCUT: - if (!last_cutu) + case PASTE: + if (!cutbuffer) statusbar(_("Internal error: cannot set up uncut. Please save your work.")); - else if (last_cutu->type == CUT) { - u->cutbuffer = last_cutu->cutbuffer; - u->cutbottom = last_cutu->cutbottom; + else { + if (u->cutbuffer) + free(u->cutbuffer); + u->cutbuffer = copy_filestruct(cutbuffer); + u->mark_begin_lineno = fs->current->lineno; + u->mark_begin_x = fs->current_x; + u->lineno = fs->current->lineno + cutbottom->lineno - cutbuffer->lineno; + + filestruct *fs_buff = cutbuffer; + if (fs_buff->lineno == cutbottom->lineno) + u->begin = fs->current_x + get_totsize(fs_buff,cutbottom); + else { + /* Advance fs_buff to the last line in the cutbuffer. */ + while (fs_buff->lineno != cutbottom->lineno && fs_buff->next != NULL) + fs_buff = fs_buff->next; + assert(fs_buff->next != NULL); + u->begin = get_totsize(fs_buff,cutbottom); + } + + u->mark_set = TRUE; } break; case ENTER: @@ -1004,12 +1016,14 @@ void update_undo(undo_type action) #endif len = strlen(u->strdata) + 2; data = (char *) nrealloc((void *) u->strdata, len * sizeof(char *)); - data[len-2] = fs->current->data[fs->current_x]; + data[len-2] = fs->current->data[fs->current_x - 1]; data[len-1] = '\0'; u->strdata = (char *) data; #ifdef DEBUG fprintf(stderr, "current undo data now \"%s\"\n", u->strdata); #endif + u->mark_begin_lineno = fs->current->lineno; + u->mark_begin_x = fs->current_x; break; case DEL: len = strlen(u->strdata) + 2; @@ -1051,18 +1065,37 @@ void update_undo(undo_type action) fprintf(stderr, "current undo data now \"%s\"\nu->begin = %d\n", u->strdata, u->begin); #endif break; + case CUT_EOF: case CUT: if (!cutbuffer) break; if (u->cutbuffer) free(u->cutbuffer); u->cutbuffer = copy_filestruct(cutbuffer); - /* Compute cutbottom for the uncut using our copy. */ - for (u->cutbottom = u->cutbuffer; u->cutbottom->next != NULL; u->cutbottom = u->cutbottom->next) - ; + if (u->mark_set) { + /* If the "marking" operation was from right-->left or + * bottom-->top, then swap the mark points. */ + if ((u->lineno == u->mark_begin_lineno && u->begin < u->mark_begin_x) + || u->lineno < u->mark_begin_lineno) { + size_t x_loc = u->begin; + u->begin = u->mark_begin_x; + u->mark_begin_x = x_loc; + + ssize_t line = u->lineno; + u->lineno = u->mark_begin_lineno; + u->mark_begin_lineno = line; + } + } else if (!ISSET(CUT_TO_END)) { + /* Compute cutbottom for the uncut using our copy. */ + u->cutbottom = u->cutbuffer; + while (u->cutbottom->next != NULL) + u->cutbottom = u->cutbottom->next; + if (!u->to_end) + u->lineno++; + } break; case REPLACE: - case UNCUT: + case PASTE: add_undo(action); break; case INSERT: @@ -1085,14 +1118,8 @@ void update_undo(undo_type action) #ifdef DEBUG fprintf(stderr, "Done in update_undo (type was %d)\n", action); #endif - if (fs->last_action != action) { -#ifdef DEBUG - fprintf(stderr, "Starting add_undo for new action as it does not match last_action\n"); -#endif - add_undo(action); + } - fs->last_action = action; -} #endif /* !NANO_TINY */ diff --git a/src/utils.c b/src/utils.c index dda90c76..3aab3fc0 100644 --- a/src/utils.c +++ b/src/utils.c @@ -476,7 +476,10 @@ size_t get_page_start(size_t column) * current_x. */ size_t xplustabs(void) { - return strnlenpt(openfile->current->data, openfile->current_x); + if (openfile->current) + return strnlenpt(openfile->current->data, openfile->current_x); + else + return 0; } /* Return the index in s of the character displayed at the given column,