0
0
mirror of https://github.com/vim/vim.git synced 2025-07-04 23:07:33 -04:00

Added ":earlier 1f" and ":later 1f".

This commit is contained in:
Bram Moolenaar 2010-06-27 05:18:54 +02:00
parent a800b42975
commit 730cde924c
14 changed files with 216 additions and 40 deletions

View File

@ -5797,6 +5797,8 @@ undotree() *undotree()*
something readable. something readable.
"save_last" Number of the last file write. Zero when no "save_last" Number of the last file write. Zero when no
write yet. write yet.
"save_cur" Number of the current position in the undo
tree.
"synced" Non-zero when the last undo block was synced. "synced" Non-zero when the last undo block was synced.
This happens when waiting from input from the This happens when waiting from input from the
user. See |undo-blocks|. user. See |undo-blocks|.

View File

@ -145,6 +145,16 @@ g- Go to older text state. With a count repeat that many
:earlier {N}s Go to older text state about {N} seconds before. :earlier {N}s Go to older text state about {N} seconds before.
:earlier {N}m Go to older text state about {N} minutes before. :earlier {N}m Go to older text state about {N} minutes before.
:earlier {N}h Go to older text state about {N} hours before. :earlier {N}h Go to older text state about {N} hours before.
:earlier {N}d Go to older text state about {N} days before.
:earlier {N}f Go to older text state {N} file writes before.
When changes were made since the laste write
":earlier 1f" will revert the text to the state when
it was written. Otherwise it will go to the write
before that.
When at the state of the first file write, or when
the file was not written, ":earlier 1f" will go to
before the first change.
*g+* *g+*
g+ Go to newer text state. With a count repeat that many g+ Go to newer text state. With a count repeat that many
@ -154,6 +164,11 @@ g+ Go to newer text state. With a count repeat that many
:later {N}s Go to newer text state about {N} seconds later. :later {N}s Go to newer text state about {N} seconds later.
:later {N}m Go to newer text state about {N} minutes later. :later {N}m Go to newer text state about {N} minutes later.
:later {N}h Go to newer text state about {N} hours later. :later {N}h Go to newer text state about {N} hours later.
:later {N}d Go to newer text state about {N} days later.
:later {N}f Go to newer text state {N} file writes later.
When at the state of the last file write, ":later 1f"
will go to the newest text state.
Note that text states will become unreachable when undo information is cleared Note that text states will become unreachable when undo information is cleared

View File

@ -302,7 +302,7 @@ edited. Typing this command twice cancels the preceding "U".
The "U" command is a change by itself, which the "u" command undoes and CTRL-R The "U" command is a change by itself, which the "u" command undoes and CTRL-R
redoes. This might be a bit confusing. Don't worry, with "u" and CTRL-R you redoes. This might be a bit confusing. Don't worry, with "u" and CTRL-R you
can go to any of the situations you had. More about that in section |32.1|. can go to any of the situations you had. More about that in section |32.2|.
============================================================================== ==============================================================================
*02.6* Other editing commands *02.6* Other editing commands

View File

@ -9,16 +9,40 @@ Vim provides multi-level undo. If you undo a few changes and then make a new
change you create a branch in the undo tree. This text is about moving change you create a branch in the undo tree. This text is about moving
through the branches. through the branches.
|32.1| Numbering changes |32.1| Undo up to a file write
|32.2| Jumping around the tree |32.2| Numbering changes
|32.3| Time travelling |32.3| Jumping around the tree
|32.4| Time travelling
Next chapter: |usr_40.txt| Make new commands Next chapter: |usr_40.txt| Make new commands
Previous chapter: |usr_31.txt| Exploiting the GUI Previous chapter: |usr_31.txt| Exploiting the GUI
Table of contents: |usr_toc.txt| Table of contents: |usr_toc.txt|
============================================================================== ==============================================================================
*32.1* Numbering changes *32.1* Undo up to a file write
Sometimes you make several changes, and then discover you want to go back to
when you have last written the file. You can do that with this command: >
:earlier 1f
The "f" stands for "file" here.
You can repeat this command to go further back in the past. Or use a count
diferent from 1 to go back faster.
If you go back too far, go forward again with: >
:later 1f
Note that these commands really work in time sequence. This matters if you
made changes after undoing some changes. It's explained in the next section.
Also note that we are talking about text writes here. For writing the undo
information in a file see |undo-persistence|.
==============================================================================
*32.2* Numbering changes
In section |02.5| we only discussed one line of undo/redo. But it is also In section |02.5| we only discussed one line of undo/redo. But it is also
possible to branch off. This happens when you undo a few changes and then possible to branch off. This happens when you undo a few changes and then
@ -66,7 +90,7 @@ it. But sometimes by the number of one of the changes below it, especially
when moving up in the tree, so that you know which change was just undone. when moving up in the tree, so that you know which change was just undone.
============================================================================== ==============================================================================
*32.2* Jumping around the tree *32.3* Jumping around the tree
So how do you get to "one two" now? You can use this command: > So how do you get to "one two" now? You can use this command: >
@ -114,7 +138,7 @@ Using |:undo| is useful if you know what change you want to jump to. |g-| and
You can type a count before |g-| and |g+| to repeat them. You can type a count before |g-| and |g+| to repeat them.
============================================================================== ==============================================================================
*32.3* Time travelling *32.4* Time travelling
When you have been working on text for a while the tree grows to become big. When you have been working on text for a while the tree grows to become big.
Then you may want to go to the text of some minutes ago. Then you may want to go to the text of some minutes ago.
@ -133,10 +157,10 @@ seconds with this command: >
:earlier 10s :earlier 10s
Depending on how much time you took for the changes you end up at a certain Depending on how much time you took for the changes you end up at a certain
position in the tree. The |:earlier| command argument can be "m" for minutes position in the tree. The |:earlier| command argument can be "m" for minutes,
and "h" for hours. To go all the way back use a big number: > "h" for hours and "d" for days. To go all the way back use a big number: >
:earlier 10h :earlier 100d
To travel forward in time again use the |:later| command: > To travel forward in time again use the |:later| command: >
@ -144,6 +168,11 @@ To travel forward in time again use the |:later| command: >
The arguments are "s", "m" and "h", just like with |:earlier|. The arguments are "s", "m" and "h", just like with |:earlier|.
If you want even more details, or want to manipulate the information, you can
use the |undotree()| function. To see what it returns: >
:echo undotree()
============================================================================== ==============================================================================
Next chapter: |usr_40.txt| Make new commands Next chapter: |usr_40.txt| Make new commands

View File

@ -273,9 +273,10 @@ Subjects that can be read independently.
|31.5| Various |31.5| Various
|usr_32.txt| The undo tree |usr_32.txt| The undo tree
|32.1| Numbering changes |32.1| Undo up to a file write
|32.2| Jumping around the tree |32.2| Numbering changes
|32.3| Time travelling |32.3| Jumping around the tree
|32.4| Time travelling
============================================================================== ==============================================================================
Tuning Vim ~ Tuning Vim ~

View File

@ -17735,12 +17735,13 @@ f_undotree(argvars, rettv)
dict_T *dict = rettv->vval.v_dict; dict_T *dict = rettv->vval.v_dict;
list_T *list; list_T *list;
dict_add_nr_str(dict, "synced", (long)curbuf->b_u_synced, NULL);
dict_add_nr_str(dict, "seq_last", curbuf->b_u_seq_last, NULL); dict_add_nr_str(dict, "seq_last", curbuf->b_u_seq_last, NULL);
dict_add_nr_str(dict, "save_last",
(long)curbuf->b_u_save_nr_last, NULL);
dict_add_nr_str(dict, "seq_cur", curbuf->b_u_seq_cur, NULL); dict_add_nr_str(dict, "seq_cur", curbuf->b_u_seq_cur, NULL);
dict_add_nr_str(dict, "time_cur", (long)curbuf->b_u_time_cur, NULL); dict_add_nr_str(dict, "time_cur", (long)curbuf->b_u_time_cur, NULL);
dict_add_nr_str(dict, "save_last", dict_add_nr_str(dict, "save_cur", (long)curbuf->b_u_save_nr_cur, NULL);
(long)curbuf->b_u_last_save_nr, NULL);
dict_add_nr_str(dict, "synced", (long)curbuf->b_u_synced, NULL);
list = list_alloc(); list = list_alloc();
if (list != NULL) if (list != NULL)

View File

@ -8461,7 +8461,7 @@ ex_undo(eap)
exarg_T *eap UNUSED; exarg_T *eap UNUSED;
{ {
if (eap->addr_count == 1) /* :undo 123 */ if (eap->addr_count == 1) /* :undo 123 */
undo_time(eap->line2, FALSE, TRUE); undo_time(eap->line2, FALSE, FALSE, TRUE);
else else
u_undo(1); u_undo(1);
} }
@ -8507,6 +8507,7 @@ ex_later(eap)
{ {
long count = 0; long count = 0;
int sec = FALSE; int sec = FALSE;
int file = FALSE;
char_u *p = eap->arg; char_u *p = eap->arg;
if (*p == NUL) if (*p == NUL)
@ -8519,13 +8520,16 @@ ex_later(eap)
case 's': ++p; sec = TRUE; break; case 's': ++p; sec = TRUE; break;
case 'm': ++p; sec = TRUE; count *= 60; break; case 'm': ++p; sec = TRUE; count *= 60; break;
case 'h': ++p; sec = TRUE; count *= 60 * 60; break; case 'h': ++p; sec = TRUE; count *= 60 * 60; break;
case 'd': ++p; sec = TRUE; count *= 24 * 60 * 60; break;
case 'f': ++p; file = TRUE; break;
} }
} }
if (*p != NUL) if (*p != NUL)
EMSG2(_(e_invarg2), eap->arg); EMSG2(_(e_invarg2), eap->arg);
else else
undo_time(eap->cmdidx == CMD_earlier ? -count : count, sec, FALSE); undo_time(eap->cmdidx == CMD_earlier ? -count : count,
sec, file, FALSE);
} }
/* /*

View File

@ -4879,6 +4879,7 @@ restore_backup:
{ {
unchanged(buf, TRUE); unchanged(buf, TRUE);
u_unchanged(buf); u_unchanged(buf);
u_update_save_nr(buf);
} }
/* /*

View File

@ -8294,7 +8294,7 @@ nv_g_cmd(cap)
case '-': /* "g+" and "g-": undo or redo along the timeline */ case '-': /* "g+" and "g-": undo or redo along the timeline */
if (!checkclearopq(oap)) if (!checkclearopq(oap))
undo_time(cap->nchar == '-' ? -cap->count1 : cap->count1, undo_time(cap->nchar == '-' ? -cap->count1 : cap->count1,
FALSE, FALSE); FALSE, FALSE, FALSE);
break; break;
default: default:

View File

@ -12,11 +12,12 @@ void u_write_undo __ARGS((char_u *name, int forceit, buf_T *buf, char_u *hash));
void u_read_undo __ARGS((char_u *name, char_u *hash, char_u *orig_name)); void u_read_undo __ARGS((char_u *name, char_u *hash, char_u *orig_name));
void u_undo __ARGS((int count)); void u_undo __ARGS((int count));
void u_redo __ARGS((int count)); void u_redo __ARGS((int count));
void undo_time __ARGS((long step, int sec, int absolute)); void undo_time __ARGS((long step, int sec, int file, int absolute));
void u_sync __ARGS((int force)); void u_sync __ARGS((int force));
void ex_undolist __ARGS((exarg_T *eap)); void ex_undolist __ARGS((exarg_T *eap));
void ex_undojoin __ARGS((exarg_T *eap)); void ex_undojoin __ARGS((exarg_T *eap));
void u_unchanged __ARGS((buf_T *buf)); void u_unchanged __ARGS((buf_T *buf));
void u_update_save_nr __ARGS((buf_T *buf));
void u_clearall __ARGS((buf_T *buf)); void u_clearall __ARGS((buf_T *buf));
void u_saveline __ARGS((linenr_T lnum)); void u_saveline __ARGS((linenr_T lnum));
void u_clearline __ARGS((void)); void u_clearline __ARGS((void));

View File

@ -327,7 +327,8 @@ struct u_header
visualinfo_T uh_visual; /* Visual areas before undo/after redo */ visualinfo_T uh_visual; /* Visual areas before undo/after redo */
#endif #endif
time_t uh_time; /* timestamp when the change was made */ time_t uh_time; /* timestamp when the change was made */
long_u uh_save_nr; /* counter for last time saved */ long uh_save_nr; /* set when the file was saved after the
changes in this block */
#ifdef U_DEBUG #ifdef U_DEBUG
int uh_magic; /* magic number to check allocation */ int uh_magic; /* magic number to check allocation */
#endif #endif
@ -1371,9 +1372,10 @@ struct file_buffer
int b_u_numhead; /* current number of headers */ int b_u_numhead; /* current number of headers */
int b_u_synced; /* entry lists are synced */ int b_u_synced; /* entry lists are synced */
long b_u_seq_last; /* last used undo sequence number */ long b_u_seq_last; /* last used undo sequence number */
long b_u_save_nr_last; /* counter for last file write */
long b_u_seq_cur; /* hu_seq of header below which we are now */ long b_u_seq_cur; /* hu_seq of header below which we are now */
time_t b_u_time_cur; /* uh_time of header below which we are now */ time_t b_u_time_cur; /* uh_time of header below which we are now */
long_u b_u_last_save_nr; /* counter for last file write */ long b_u_save_nr_cur; /* file write nr after which we are now */
/* /*
* variables for "U" command in undo.c * variables for "U" command in undo.c

View File

@ -1,6 +1,7 @@
Tests for undo tree. Tests for undo tree.
Since this script is sourced we need to explicitly break changes up in Since this script is sourced we need to explicitly break changes up in
undo-able pieces. Do that by setting 'undolevels'. undo-able pieces. Do that by setting 'undolevels'.
Also tests :earlier and :later.
STARTTEST STARTTEST
:" Delete three characters and undo :" Delete three characters and undo
@ -50,6 +51,35 @@ obbbbu:.w >>test.out
obbbb:set ul=100 obbbb:set ul=100
:undojoin :undojoin
occccu:.w >>test.out occccu:.w >>test.out
:e! Xtest
ione one one:set ul=100
:w!
otwo:set ul=100
otwo:set ul=100
:w
othree:earlier 1f
:" expect "one one one\ntwo\ntwo"
:%yank a
:earlier 1f
:" expect "one one one"
:%yank b
:earlier 1f
:" expect empty line
:%yank c
:later 1f
:" expect "one one one"
:%yank d
:later 1f
:" expect "one one one\ntwo\ntwo"
:%yank e
:later 1f
:" expect "one one one\ntwo\ntwo\nthree"
ggO---:0put e
ggO---:0put d
ggO---:0put c
ggO---:0put b
ggO---:0put a
ggO---:w >>test.out
:qa! :qa!
ENDTEST ENDTEST

View File

@ -22,3 +22,22 @@
123456abc 123456abc
aaaa aaaa
aaaa aaaa
---
one one one
two
two
---
one one one
---
---
one one one
---
one one one
two
two
---
one one one
two
two
three

View File

@ -119,6 +119,7 @@ static void put_header_ptr __ARGS((FILE *fp, u_header_T *uhp));
#define U_ALLOC_LINE(size) lalloc((long_u)(size), FALSE) #define U_ALLOC_LINE(size) lalloc((long_u)(size), FALSE)
static char_u *u_save_line __ARGS((linenr_T)); static char_u *u_save_line __ARGS((linenr_T));
/* used in undo_end() to report number of added and deleted lines */
static long u_newcount, u_oldcount; static long u_newcount, u_oldcount;
/* /*
@ -932,7 +933,7 @@ serialize_header(fp, buf, hash)
/* Optional fields. */ /* Optional fields. */
putc(4, fp); putc(4, fp);
putc(UF_LAST_SAVE_NR, fp); putc(UF_LAST_SAVE_NR, fp);
put_bytes(fp, (long_u)buf->b_u_last_save_nr, 4); put_bytes(fp, (long_u)buf->b_u_save_nr_last, 4);
putc(0, fp); /* end marker */ putc(0, fp); /* end marker */
@ -1444,17 +1445,6 @@ u_write_undo(name, forceit, buf, hash)
/* Undo must be synced. */ /* Undo must be synced. */
u_sync(TRUE); u_sync(TRUE);
/* Increase the write count, store it in the last undo header, what would
* be used for "u". */
++buf->b_u_last_save_nr;
uhp = buf->b_u_curhead;
if (uhp != NULL)
uhp = uhp->uh_next.ptr;
else
uhp = buf->b_u_newhead;
if (uhp != NULL)
uhp->uh_save_nr = buf->b_u_last_save_nr;
/* /*
* Write the header. * Write the header.
*/ */
@ -1849,7 +1839,7 @@ u_read_undo(name, hash, orig_name)
curbuf->b_u_seq_last = seq_last; curbuf->b_u_seq_last = seq_last;
curbuf->b_u_seq_cur = seq_cur; curbuf->b_u_seq_cur = seq_cur;
curbuf->b_u_time_cur = seq_time; curbuf->b_u_time_cur = seq_time;
curbuf->b_u_last_save_nr = last_save_nr; curbuf->b_u_save_nr_last = last_save_nr;
curbuf->b_u_synced = TRUE; curbuf->b_u_synced = TRUE;
vim_free(uhp_table); vim_free(uhp_table);
@ -2001,13 +1991,15 @@ u_doit(startcount)
* When "step" is negative go back in time, otherwise goes forward in time. * When "step" is negative go back in time, otherwise goes forward in time.
* When "sec" is FALSE make "step" steps, when "sec" is TRUE use "step" as * When "sec" is FALSE make "step" steps, when "sec" is TRUE use "step" as
* seconds. * seconds.
* When "file" is TRUE use "step" as a number of file writes.
* When "absolute" is TRUE use "step" as the sequence number to jump to. * When "absolute" is TRUE use "step" as the sequence number to jump to.
* "sec" must be FALSE then. * "sec" must be FALSE then.
*/ */
void void
undo_time(step, sec, absolute) undo_time(step, sec, file, absolute)
long step; long step;
int sec; int sec;
int file;
int absolute; int absolute;
{ {
long target; long target;
@ -2021,6 +2013,7 @@ undo_time(step, sec, absolute)
int nomark; int nomark;
int round; int round;
int dosec = sec; int dosec = sec;
int dofile = file;
int above = FALSE; int above = FALSE;
int did_undo = TRUE; int did_undo = TRUE;
@ -2044,8 +2037,45 @@ undo_time(step, sec, absolute)
{ {
/* When doing computations with time_t subtract starttime, because /* When doing computations with time_t subtract starttime, because
* time_t converted to a long may result in a wrong number. */ * time_t converted to a long may result in a wrong number. */
if (sec) if (dosec)
target = (long)(curbuf->b_u_time_cur - starttime) + step; target = (long)(curbuf->b_u_time_cur - starttime) + step;
else if (dofile)
{
if (step < 0)
{
/* Going back to a previous write. If there were changes after
* the last write, count that as moving one file-write, so
* that ":earlier 1f" undoes all changes since the last save. */
uhp = curbuf->b_u_curhead;
if (uhp != NULL)
uhp = uhp->uh_next.ptr;
else
uhp = curbuf->b_u_newhead;
if (uhp != NULL && uhp->uh_save_nr != 0)
/* "uh_save_nr" was set in the last block, that means
* there were no changes since the last write */
target = curbuf->b_u_save_nr_cur + step;
else
/* count the changes since the last write as one step */
target = curbuf->b_u_save_nr_cur + step + 1;
if (target <= 0)
/* Go to before first write: before the oldest change. Use
* the sequence number for that. */
dofile = FALSE;
}
else
{
/* Moving forward to a newer write. */
target = curbuf->b_u_save_nr_cur + step;
if (target > curbuf->b_u_save_nr_last)
{
/* Go to after last write: after the latest change. Use
* the sequence number for that. */
target = curbuf->b_u_seq_last + 1;
dofile = FALSE;
}
}
}
else else
target = curbuf->b_u_seq_cur + step; target = curbuf->b_u_seq_cur + step;
if (step < 0) if (step < 0)
@ -2056,8 +2086,10 @@ undo_time(step, sec, absolute)
} }
else else
{ {
if (sec) if (dosec)
closest = (long)(time(NULL) - starttime + 1); closest = (long)(time(NULL) - starttime + 1);
else if (dofile)
closest = curbuf->b_u_save_nr_last + 2;
else else
closest = curbuf->b_u_seq_last + 2; closest = curbuf->b_u_seq_last + 2;
if (target >= closest) if (target >= closest)
@ -2092,9 +2124,14 @@ undo_time(step, sec, absolute)
while (uhp != NULL) while (uhp != NULL)
{ {
uhp->uh_walk = mark; uhp->uh_walk = mark;
val = (long)(dosec ? (uhp->uh_time - starttime) : uhp->uh_seq); if (dosec)
val = (long)(uhp->uh_time - starttime);
else if (dofile)
val = uhp->uh_save_nr;
else
val = uhp->uh_seq;
if (round == 1) if (round == 1 && !(dofile && val == 0))
{ {
/* Remember the header that is closest to the target. /* Remember the header that is closest to the target.
* It must be at least in the right direction (checked with * It must be at least in the right direction (checked with
@ -2123,7 +2160,10 @@ undo_time(step, sec, absolute)
/* Quit searching when we found a match. But when searching for a /* Quit searching when we found a match. But when searching for a
* time we need to continue looking for the best uh_seq. */ * time we need to continue looking for the best uh_seq. */
if (target == val && !dosec) if (target == val && !dosec)
{
target = uhp->uh_seq;
break; break;
}
/* go down in the tree if we haven't been there */ /* go down in the tree if we haven't been there */
if (uhp->uh_prev.ptr != NULL && uhp->uh_prev.ptr->uh_walk != nomark if (uhp->uh_prev.ptr != NULL && uhp->uh_prev.ptr->uh_walk != nomark
@ -2179,6 +2219,7 @@ undo_time(step, sec, absolute)
target = closest_seq; target = closest_seq;
dosec = FALSE; dosec = FALSE;
dofile = FALSE;
if (step < 0) if (step < 0)
above = TRUE; /* stop above the header */ above = TRUE; /* stop above the header */
} }
@ -2539,6 +2580,15 @@ u_undoredo(undo)
* work we compute this as being just above the just undone change. */ * work we compute this as being just above the just undone change. */
--curbuf->b_u_seq_cur; --curbuf->b_u_seq_cur;
/* Remember where we are for ":earlier 1f" and ":later 1f". */
if (curhead->uh_save_nr != 0)
{
if (undo)
curbuf->b_u_save_nr_cur = curhead->uh_save_nr - 1;
else
curbuf->b_u_save_nr_cur = curhead->uh_save_nr;
}
/* The timestamp can be the same for multiple changes, just use the one of /* The timestamp can be the same for multiple changes, just use the one of
* the undone/redone change. */ * the undone/redone change. */
curbuf->b_u_time_cur = curhead->uh_time; curbuf->b_u_time_cur = curhead->uh_time;
@ -2811,6 +2861,27 @@ u_unchanged(buf)
buf->b_did_warn = FALSE; buf->b_did_warn = FALSE;
} }
/*
* Increase the write count, store it in the last undo header, what would be
* used for "u".
*/
void
u_update_save_nr(buf)
buf_T *buf;
{
u_header_T *uhp;
++buf->b_u_save_nr_last;
buf->b_u_save_nr_cur = buf->b_u_save_nr_last;
uhp = buf->b_u_curhead;
if (uhp != NULL)
uhp = uhp->uh_next.ptr;
else
uhp = buf->b_u_newhead;
if (uhp != NULL)
uhp->uh_save_nr = buf->b_u_save_nr_last;
}
static void static void
u_unch_branch(uhp) u_unch_branch(uhp)
u_header_T *uhp; u_header_T *uhp;