Подтвердить что ты не робот

Vim Scripting - вызов функции один раз для всех выбранных строк

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

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(), что означает, что функция вызывается один раз для выбранной строки. Нет, хорошо, правда? Так что, насколько я могу судить об этом. Что я могу сделать, чтобы получить функцию:

  • будет вызываться только один раз
  • возможно, это более эффективный способ
4b9b3361

Ответ 1

Ну, чтобы функция выполнялась только один раз над диапазоном, добавьте ключевое слово 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!.

ИЗМЕНИТЬ

Просто краткое объяснение моего рабочего процесса и почему длинный однострочный.

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

Ответ 2

Я знаю, что мой 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]}