Vim Scripting - вызывать функцию один раз для всех выделенных строк - PullRequest
14 голосов
/ 22 декабря 2011

Итак, у меня есть такой текстовый файл:

Item a: <total>
  Subitem: 10 min
  Subitem 2: 20 min

Я хотел бы заменить <total> на 10 и 20. Сейчас я делаю это со следующими функциями:

let g:S = 0  "result in global variable S
function! Sum(number)
  let g:S = g:S + a:number
  return a:number
endfunction

function! SumSelection()
  let g:S=0
  '<,'>s/\d\+/\=Sum(submatch(0))/g
  echo g:S
endfunction

vnoremap <s-e> call SumSelection()<cr>

Sum получает сумму переданных чисел, SumSelection вызывает сумму по всем числам в выбранных строках, и (предположительно) Shift + e вызывает SumSelection в визуальном режиме (или как вы выберете)чтобы вызвать его.)

Проблема в том, что когда я нажимаю Shift + e, когда у меня выбрано несколько строк, вместо :call SumSelection() я действительно получаю :'<,'>call SumSelection(), что означает, что функция вызывается один раз для выбранной строки.Нет хорошо, правда?Так что, насколько я могу судить, нет никакого способа обойти это.Что я могу сделать, чтобы функция:

  1. вызывалась только один раз
  2. , возможно, суммируйте их более эффективным способом

Ответы [ 2 ]

19 голосов
/ 22 декабря 2011

Чтобы функция выполнялась всего один раз в диапазоне, добавьте ключевое слово range.Например,

fun Foo() range
    ...
endfun

Тогда ваша функция может позаботиться о самом диапазоне с помощью специальных параметров a: firstline и a: lastline.(Подробнее см. :help a:firstline.)

Однако я думаю, что в этом случае ваши требования достаточно просты, чтобы их можно было выполнить при помощи однострочника с использованием :global.

Мы могли быделать с лучше определенными входами и выходами действительно.Но если предположить, что файл содержит

Item a: <total>
  Subitem: 10 min
  Subitem 2: 20 min
Item b: <total>
  Subitem: 10 min
  Subitem 2: 23 min
Item c: <total>
  Subitem: 10 min
  Subitem 2: 23 min
  Subitem 3: 43 min

и функцию, определенную как

fun! Sum(n)
    let g:s += a:n
    return a:n
endfun

, то это будет суммировать подэлементы (все в одну строку)

:g/^Item/ let g:s = 0 | mark a | +,-/\nItem\|\%$/ s/\v(\d+)\ze\s+min$/\=Sum(submatch(0))/g | 'a s/<total>/\=(g:s . ' min')/

и выдаем этот вывод

Item a: 30 min
  Subitem: 10 min
  Subitem 2: 20 min
Item b: 33 min
  Subitem: 10 min
  Subitem 2: 23 min
Item c: 76 min
  Subitem: 10 min
  Subitem 2: 23 min
  Subitem 3: 43 min

Кстати, вышеприведенная команда действует на весь буфер.Если вы сначала выделите диапазон, а затем нажмете клавишу :, vim автоматически добавит к вашей команде '<,'>, что означает, что следующая команда будет действовать от начала до конца выделенного диапазона.

Напримервыделение строк с 1 по 5 и последующее выполнение команды (:'<,'> g/...) приводит к выводу

Item a: 30 min
  Subitem: 10 min
  Subitem 2: 20 min
Item b: 33 min
  Subitem: 10 min
  Subitem 2: 23 min
Item c: <total>
  Subitem: 10 min
  Subitem 2: 23 min
  Subitem 3: 43 min

Одна заключительная нота.Если в одной из групп нет подэлементов, например,

Item a: <total>
  Subitem: 10 min
  Subitem 2: 20 min
Item b: <total>
Item c: <total>
  Subitem: 10 min
  Subitem 2: 23 min
  Subitem 3: 43 min

, то команда будет прервана с ошибкой «Неверный диапазон» при достижении второго элемента.Вы можете заставить Vim игнорировать это и продолжать работу независимо от того, поставив перед всей командой префикс :silent!.

EDIT

Просто краткое объяснение моего рабочего процесса и почемуlong one-liner.

  1. Мне нравится :g и много команд ex.
  2. Я собрал команду в интерактивном режиме, используя историю командной строки и окно командной строки дляправка.
  3. Когда я закончу, я вставляю одноразовые команды, подобные этой, в буфер: у меня есть отображение, чтобы выполнить текущую строку как команду ex.Таким образом, команда всегда легко доступна, когда она мне нужна.Для более регулярного использования вам может потребоваться команда, функция или ftplugin.
1 голос
/ 22 декабря 2011

Я знаю, что мой vim7.3-script намного длиннее adscriven.
Но он может выполнить работу.

fun! SubTotal()
    fun! Collect()
        " define some global vars
        if line('.') == 1
            let g:dict = {}
            let g:key = ''
        endif

        " set current key && add key to dict
        fun! AddKey(k)
            let g:key = a:k
            let g:dict[a:k]= []
        endfun

        " add value to dict[key]
        fun! AddValue(v)
            if g:key != ''
                call add(g:dict[g:key], a:v)
            endif
        endfun

        " scan every line
        let line = getline('.')
        if line =~ '^Item'
            call AddKey(matchstr(line, '^Item\s\+\zs\w\+\ze\s*:'))
        elseif line =~ '^\s*Subitem'
            call AddValue(str2nr(matchstr(line, '\d\+\ze\s*min$')))
        endif
        return line
    endfun

    " collect data line by line
    silent %s/.*/\=Collect()/
    " replace <total> with sum
    silent g/^Item/s/^Item\s\+\(\w\+\)\s*:\s*\zs.*/\=eval(join(g:dict[submatch(1)], '+'))/
endfun

Item a: 30
  Subitem: 10 min
  Subitem 2: 20 min
Item b: 33
  Subitem: 10 min
  Subitem 2: 23 min
Item c: 76
  Subitem: 10 min
  Subitem 2: 23 min
  Subitem 3: 43 min

:echo dict
{'a': [10, 20], 'b': [10, 23], 'c': [10, 23, 43]}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...