2015-07-21 21 views
9

правок:VIM undo: Почему курсор переходит в неправильное положение при отмене `undojoin`?


Почему курсор позиционируется по-разному в двух следующих примерах :

  1. [ПРАВИЛЬНАЯ позиции курсора] Следующий тест производит ожидаемое изменение результата замещения присоединилось к предыдущему изменению в буфере (добавление строки 3), позиция курсора находится правильно восстановлен во второй строке в буфере ,

    normal ggiline one is full of aaaa 
    set undolevels=10 " splits the change into separate undo blocks 
    
    normal Goline two is full of bbbb 
    set undolevels=10 
    
    normal Goline three is full of cccc 
    set undolevels=10 
    
    undojoin 
    keepjumps %s/aaaa/zzzz/ 
    normal u 
    
  2. [НЕПРАВИЛЬНЫЕ позиции курсора] Следующий тест дает неожиданный результат: изменение замены присоединяются к предыдущему изменению в буфере (добавление линии 4), позиция курсора находится неправильно восстановлена ​​первым строка в буфере (должна быть строка 3).

    normal ggiline one is bull of aaaa 
    set undolevels=10 " splits the change into separate undo blocks 
    
    normal Goline two is full of bbbb 
    set undolevels=10 
    
    normal Goline three is full of cccc   
    set undolevels=10 
    
    normal Goline four is full of aaaa's again 
    set undolevels=10 
    
    undojoin 
    keepjumps %s/aaaa/zzzz/ 
    normal u 
    

Оригинал Вопрос

Путь мой VIM настроен, сохранение буфера в файл запускает функцию (прилагается в конце вопроса) обычай StripTrailingSpaces():

autocmd BufWritePre,FileWritePre,FileAppendPre,FilterWritePre <buffer> 
     \ :keepjumps call UmkaDK#StripTrailingSpaces(0) 

Осмотрев Restore the cursor position after undoing text change made by a script, я получил представление о том, чтобы исключить изменения, сделанные мой StripTrailingSpaces() из истории отмены, объединив отмененную запись, созданную функцией, в конце предыдущего изменения в буфере.

Таким образом, при отмене изменений, казалось бы, функция не создала собственную отменную запись.

Чтобы проверить свою идею, я использовал простой тестовый пример: создать чистый буфер и введите следующие команды вручную или сохранить следующий блок в виде файла и источником его через:

vim +"source <saved-filename-here>"

normal ggiline one is full of aaaa 
set undolevels=10 " splits the change into separate undo blocks 

normal Goline two is full of bbbb 
set undolevels=10 

normal Goline three is full of cccc 
set undolevels=10 

undojoin 
keepjumps %s/aaaa/zzzz/ 
normal u 

Как вы можете видеть, после отмены последнего изменения в буфере, который создает третью строку, курсор правильно возвращается ко второй строке файла.

С момента моего тестирования я реализовал почти идентичный undojoin в своем StripTrailingSpaces(). Однако, когда я отменю последнее изменение после запуска функции, курсор возвращается к самому большому изменению файла. Это часто разделенное пространство и составляет не позиция изменения I undojoin -ed to.

Может кто-нибудь подумать, почему это было бы? Еще лучше, может ли кто-нибудь предложить исправить?

function! UmkaDK#StripTrailingSpaces(number_of_allowed_spaces) 
    " Match all trailing spaces in a file 
    let l:regex = [ 
       \ '\^\zs\s\{1,\}\$', 
       \ '\S\s\{' . a:number_of_allowed_spaces . '\}\zs\s\{1,\}\$', 
       \ ] 

    " Join trailing spaces regex into a single, non-magic string 
    let l:regex_str = '\V\(' . join(l:regex, '\|') . '\)' 

    " Save current window state 
    let l:[email protected]/ 
    let l:winview = winsaveview() 

    try 
     " Append the comming change onto the end of the previous change 
     " NOTE: Fails if previous change doesn't exist 
     undojoin 
    catch 
    endtry 

    " Substitute all 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 
+0

Извините, но какая разница между этими 75 строками и ':% s/\ s * $ /'? – steffen

+0

@steffen: Ну ... его 74 строки и 2866 символов длиннее ... он также содержит описательные комментарии, сохраняет историю поиска и последнюю строку поиска, не изменяет ваши метки '' '', ''. 'И' '^', не добавляет новую запись 'jumplist' и' changelist', сохраняет ваш взгляд и позицию курсора, а * должен * создавать более гладкий случай отмены. (Хотя последний пункт субъективен и является причиной этого вопроса.) – UmkaDK

+0

Позиция курсора запоминается перед внесением изменений, а затем восстанавливается после отмены изменений. –

ответ

3

Это определенно похоже на ошибку с заменой команды для меня. Из того, что я могу сказать, команда substitute будет периодически вмешиваться в место изменения, чтобы перейти к блоку отмены, когда он включен. Я не могу изолировать шаблон - иногда он будет делать это, когда замена происходит несколько раз. В других случаях местоположение замещения, похоже, влияет, когда это происходит. Это кажется очень ненадежным. Я не думаю, что это действительно имеет отношение к команде отмены, так как я смог воспроизвести этот эффект для других функций, которые не используют это. Если вы заинтересованы, попробуйте следующее:

function! Test() 
    normal ciwfoo 
    normal ciwbar 
    %s/one/two/ 
endfunction 

Попробуйте на некоторых различных текстов с различным числом «единиц» включены и размещены в разных местах. Вы заметите, что впоследствии иногда отмена будет переходить к строке, где произошла первая замена, а в других случаях она переместится туда, где первая нормальная команда делает свое изменение.

Я думаю, что решение здесь для вас будет делать что-то вроде этого:

undo 
normal ma 
redo 

в верхней части вашей функции, а затем связать U-то вроде u'a в функции так, что после отмена его вернется к месту, где произошло фактическое первое изменение, в отличие от любой случайности: s силы на вас. Конечно, это не может быть так просто, потому что вам придется отключить u после того, как вы совершили прыжок и т. Д. И т. Д., Но этот шаблон в целом должен дать вам способ сохранить правильное местоположение, а затем отскочить назад к нему. Конечно, вы, вероятно, захотите сделать все это с помощью некоторой глобальной переменной вместо захвата меток, но вы получите эту идею.

EDIT: Пробыв некоторое время копаться в исходном коде, это на самом деле выглядит как поведение, которое вы после это ошибка. Это кусок кода, который определяет, где курсор должен быть помещен после отмены:

if (top < newlnum) 
{ 
    /* If the saved cursor is somewhere in this undo block, move it to 
    * the remembered position. Makes "gwap" put the cursor back 
    * where it 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 the first line that actually changed. Avoids that 
    * undoing auto-formatting puts the cursor in the previous 
    * line. */ 
    for (i = 0; i < newsize && i < 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; 
    } 
    } 
} 

Это достаточно сложное, но в основном то, что это делает, это проверить, где курсор был, когда было принято изменение, а затем, если это внутри измените блок для отмены, затем переместите курсор в эту позицию для команды gw. В противном случае он пропускает верхнюю часть самой измененной строки и помещает вас туда. Что происходит с заменой, так это то, что она активирует эту логику для каждой заменяемой строки, и поэтому, если одна из этих подстановок находится в блоке отмены, то она переходит в позицию курсора перед отменой (желаемое поведение). В других случаях ни один из изменений не будет в этом блоке, поэтому он перейдет к самой верхней измененной строке (вероятно, что она должна делать). Поэтому я думаю, что ответ на ваш вопрос заключается в том, что ваше желаемое поведение (внести изменения, но объединить его с предыдущим изменением, за исключением определения места размещения курсора при отмене изменения) в настоящее время не поддерживается vim.

EDIT: Этот конкретный фрагмент кода находится в undo.c в строке 2711 внутри функции отмены.Внутри u_savecommon все происходит до того, как на самом деле вызывается отмена, и именно там сохраняется позиция курсора, которая заканчивается тем, что используется для исключения команды gw (строка 385 undo.c и сохраняется в строке 548 при вызове в синхронизированном буфере). Логика команды подстановки находится в ex_cmds.c в строке 4268, которая вызывает u_savecommon косвенно на линии 5208 (вызывает u_savesub, который вызывает u_savecommon).

+0

Спасибо @doliver, похоже, вы потратили довольно много времени на отладку этого. Очень признателен!! Однако решение, которое вы предлагаете (то есть: переназначение 'u' на пользовательское действие), требует изменения одной из основных функциональных возможностей VIM, и я бы предпочел избежать этого. Кроме того, если это поведение является результатом ошибки, то, что вы предлагаете, решит проблему только для меня, а не для сообщества в целом. Позвольте мне посмотреть, могу ли я получить здесь #vim (IRC) и vim_dev (список рассылки). Может быть, они смогут нам помочь. – UmkaDK

+0

Этот вопрос теперь перекрестно помещен в список рассылки vim_dev: https://goo.gl/HpW4NX – UmkaDK

+1

Да, я думаю, в идеале это было бы исправлено внутри ядра. Я не думаю, что вы получите главный приоритет с такой проблемой. Если вам будет удобно с C, я бы не удивился, если бы это было довольно легко зафиксировано в ядре. Помимо этого или переназначения ключа u, вероятно, наилучшим обходным путем является просто не отменять отмену, которая потребует дополнительного нажатия клавиши, но сохранит эту позицию. Я уже пробрался в ядро ​​vim, поэтому я могу взглянуть на какой-то момент сегодня и посмотреть, будет ли это простое исправление. – doliver