VIM undo: Why does the cursor jump to the wrong position when undoing `undojoin`? -
edits:
i've simplified function , clarified question.
original question still available further down page.crossposted onto vim_dev mailing list: https://groups.google.com/forum/#!topic/vim_dev/_rz3uvxbwsq
reported bug neovim:
https://github.com/neovim/neovim/issues/6276
why cursor positioned differently in following 2 examples:
[correct cursor position] following test produces expected result substitution change joined previous change in buffer (addition of line 3), cursor position correctly restored second line in buffer.
normal ggiline 1 full of aaaa set undolevels=10 " splits change separate undo blocks normal goline 2 full of bbbb set undolevels=10 normal goline 3 full of cccc set undolevels=10 undojoin keepjumps %s/aaaa/zzzz/ normal u
[incorrect cursor position] following test produces unexpected result: substitution change joined previous change in buffer (addition of line 4), cursor position incorrectly restored first line in buffer (should line 3).
normal ggiline 1 bull of aaaa set undolevels=10 " splits change separate undo blocks normal goline 2 full of bbbb set undolevels=10 normal goline 3 full of cccc set undolevels=10 normal goline 4 full of aaaa's again set undolevels=10 undojoin keepjumps %s/aaaa/zzzz/ normal u
original question
the way vim set up, saving buffer file triggers custom striptrailingspaces() function (attached @ end of question):
autocmd bufwritepre,filewritepre,fileappendpre,filterwritepre <buffer> \ :keepjumps call umkadk#striptrailingspaces(0)
after seeing restore cursor position after undoing text change made script, got idea exclude changes made striptrailingspaces() function undo history merging undo record created function onto end of previous change in buffer.
this way, when undoing changes, appear function didn't create it's own undo record @ all.
to validate idea i've used simple test case: create clean buffer , enter following commands manually, or save following block file , source via:
vim +"source <saved-filename-here>"
normal ggiline 1 full of aaaa set undolevels=10 " splits change separate undo blocks normal goline 2 full of bbbb set undolevels=10 normal goline 3 full of cccc set undolevels=10 undojoin keepjumps %s/aaaa/zzzz/ normal u
as can see, after undoing last change in buffer, creating third line, cursor correctly returned second line in file.
since test worked, implemented identical undojoin
in striptrailingspaces(). however, when undo last change after function has run, cursor returned top change in file. stripped space , not position of change undojoin
-ed to.
can think of why be? better yet, can suggest fix?
function! umkadk#striptrailingspaces(number_of_allowed_spaces) " match trailing spaces in file let l:regex = [ \ '\^\zs\s\{1,\}\$', \ '\s\s\{' . a:number_of_allowed_spaces . '\}\zs\s\{1,\}\$', \ ] " join trailing spaces regex single, non-magic string let l:regex_str = '\v\(' . join(l:regex, '\|') . '\)' " save current window state let l:last_search=@/ let l:winview = winsaveview() try " append comming change onto end of previous change " note: fails if previous change doesn't exist undojoin catch endtry " substitute trailing spaces if v:version > 704 || v:version == 704 && has('patch155') execute 'keepjumps keeppatterns %s/' . l:regex_str . '//e' else execute 'keepjumps %s/' . l:regex_str . '//e' call histdel('search', -1) endif " restore current window state call winrestview(l:winview) let @/=l:last_search endfunction
this looks bug substitute command me. can tell substitute command sporadically take on change location jump undo block when included. can't isolate pattern - when substitution happens > number of times. other times, location of substitution seems affect when happens. seems unreliable. don't think has undojoin command either have been able reproduce effect other functions don't make use of that. if you're interested, try following:
function! test() normal ciwfoo normal ciwbar %s/one/two/ endfunction
try on different texts different numbers of "ones" included , placed @ different locations. you'll notice afterwards undo jump line first substitution occurred , other times jump place first normal command makes change.
i think solution here going to this:
undo normal ma redo
at top of function , bind u u'a in function after undo jump place actual first change occurred opposed whatever randomness :s forces on you. of course, can't quite simple because have unmap u once you've done jump, etc., etc. pattern in general should give way save correct location , jump it. of course, you'll want of global variable instead of hijacking marks idea.
edit: after spending time digging through source code, looks behavior you're after bug. chunk of code determines cursor should placed after undo:
if (top < newlnum) { /* if saved cursor somewhere in undo block, move * remembered position. makes "gwap" put cursor * was. */ lnum = curhead->uh_cursor.lnum; if (lnum >= top && lnum <= top + newsize + 1) { msg("remembered position.\n"); curwin->w_cursor = curhead->uh_cursor; newlnum = curwin->w_cursor.lnum - 1; } else { char msg_buf[1000]; msg("first change\n"); sprintf(msg_buf, "lnum: %d, top: %d, newsize: %d", lnum, top, newsize); msg(msg_buf); /* use first line changed. avoids * undoing auto-formatting puts cursor in previous * line. */ (i = 0; < newsize && < oldsize; ++i) if (strcmp(uep->ue_array[i], ml_get(top + 1 + i)) != 0) break; if (i == newsize && newlnum == maxlnum && uep->ue_next == null) { newlnum = top; curwin->w_cursor.lnum = newlnum + 1; } else if (i < newsize) { newlnum = top + i; curwin->w_cursor.lnum = newlnum + 1; } } }
it's rather involved check cursor when change made , if it's inside change block undo reset cursor position gw command. otherwise, skips top changed line , puts there. happens substitute activates logic each line substituted , if 1 of substitutions in undo block jumps position of cursor before undo (your desired behavior). other times, none of changes in block jump topmost changed line (probably should do). therefore, think answer question desired behavior (make change merge previous change except in determining place cursor when change undone) not supported vim.
edit: particular chunk of code resides in undo.c on line 2711 inside undoredo function. inside of u_savecommon whole thing gets setup before undo called , that's cursor position ends being used gw command exception saved (undo.c line 385 , saved on line 548 when called on synced buffer). logic substitution command located in ex_cmds.c on line 4268 calls u_savecommon indirectly on line 5208 (calls u_savesub calls u_savecommon).
Comments
Post a Comment