Показаны сообщения с ярлыком ack. Показать все сообщения
Показаны сообщения с ярлыком ack. Показать все сообщения

среда, 31 августа 2016 г.

Бенчмаркинг cgrep, ack и hlgrep

Когда-то я сравнивал cgrep и ack с точки зрения функциональных возможностей, пришла пора сравнить их скорости выполнения. Кроме того, недавно мне удалось серьезно оптимизировать программу hl, да так, что при рекурсивном грепе она стала во многих случаях быстрее, чем ack. Сильной оптимизации было подвергнуто время старта программы, поэтому преимущество в скорости особенно заметно при небольшом объеме входных данных. Как и ack, hl написана на perl, но реализует другую функциональность, а именно богатую подсветку входных данных, в том числе на основе сниппетов. Таким образом, рекурсивный греп в hl — возможность чисто факультативная, без большого количества опций, доступных в cgrep и ack. Тем интересней добавить его для сравнения к двум последним. Я не стал добавлять для сравнения стандартный grep. Ему по плечу такие простые задачи, как рекурсивный поиск символов, однако он настолько быстр, что с легкостью опережает на порядок все три узкоспециализированные программы (в частности, на моей машине скорость поиска символов ARGS программой grep внутри директории /usr/include/boost в четыре раза превосходит скорость cgrep). Для бенчмаркинга я использовал замечательную программу bench, которая по сути является простым объединением библиотек criterion (я писал о criterion здесь) и turtle. Поскольку я хочу погонять шелл-функциию cgr, представленную в статье, ссылка на которую приводится в самом начале данной статьи, мне придется обернуть команду для bench в bash -ci — ведь turtle в лице bench не является полноценной bash-совместимой оболочкой и не способна загружать функции bash. Для hl я тоже буду использовать шелл-функцию hlgrep, которая определена так:
function hlgrep
{
    `env which hl` -t -r -n "$@" |
            sed 's/:\(\x1b\[22;38;5;224;49m\)\([[:digit:]]\+\)'`
               `'\(\x1b\[22;38;5;248;49m\)\(\x1b\[0m\): / ⎜ \1\2\1\3 ⎜ /' |
            column -t -s-o|
            sed 's/\(\s\+\)\(\(\x1b\[22;38;5;224;49m\)[[:digit:]]\+\3'`
               `'\x1b\[22;38;5;248;49m\)\(\s\+\)/\4\2\1/' |
            hl -u -216 '\x{239C}'
}
Но даже чистую cgrep нужно будет обернуть в bash. Проблема в том, что bench подменяет канал stdin в дочерних процессах, запущенных для тестирования, на новый канал, а cgrep, определив подмену, считает, что последний аргумент его командной строки, который обычно указывает на директорию для чтения входных файлов, должен быть просто еще одним элементом для поиска. Таким образом, нам придется сначала выполнить cd, а уже затем запускать cgrep, но уже без ссылки на директорию. Проще всего это сделать в отдельной оболочке. По той же самой причине подмены stdin, в ack нужно будет добавить специальную опцию --nofilter, которая, впрочем, не должна повлиять на его скорость. Есть еще один тонкий момент, связанный с bench: он аварийно завершается в случае, если тестируемая программа возвращает не нулевое значение. Именно это делает ack, если не находит ни одного совпадения. Поэтому перед запуском bench следует проверить, что совпадения имеются для всех тестов с ack (и, автоматически, для других тестируемых программ), при этом желательно, чтобы совпадений было больше — в конце концов мы тестируем полезную нагрузку. В качестве сценариев для тестирования я выбрал поиск символов ARGS в директории /usr/include/boost на моей машине и поиск символов parse внутри относительно небольшого проекта cexmc. Во втором случае я проверил также чистые cgrep, hl и ack. Каждый тест длился по 60 секунд. И вот что из этого вышло.
bench -v2 -L60 \
'bash -ci "(cd /usr/include/boost; cgr ARGS)"' \
'bash -ci "(cd /usr/include/boost; hlgrep ARGS)"' \
'bash -ci "(cd /usr/include/boost; ack --nofilter ARGS)"' \
'bash -ci "(cd ~/devel/cexmc; cgr parse)"' \
'bash -ci "(cd ~/devel/cexmc; hlgrep parse)"' \
'bash -ci "(cd ~/devel/cexmc; ack --nofilter parse)"' \
'bash -ci "(cd ~/devel/cexmc; cgrep -r parse)"' \
'bash -ci "(cd ~/devel/cexmc; hl -r parse)"'  \
'bash -ci "(cd ~/devel/cexmc; ack --nofilter --noenv parse)"' \
-o ~/bench-cgrep-hlgrep-ack-60s.html
benchmarking bench/bash -ci "(cd /usr/include/boost; cgr ARGS)"
analysing with 1000 resamples
measurement overhead 624.0 ns
bootstrapping with 11 of 12 samples (91%)
time                 694.1 ms   (644.7 ms .. 738.5 ms)
                     0.992 R²   (0.982 R² .. 0.999 R²)
mean                 667.6 ms   (653.5 ms .. 687.1 ms)
std dev              29.20 ms   (19.88 ms .. 38.25 ms)
variance introduced by outliers: 8% (slightly inflated)

benchmarking bench/bash -ci "(cd /usr/include/boost; hlgrep ARGS)"
analysing with 1000 resamples
bootstrapping with 5 of 6 samples (83%)
time                 2.129 s    (2.004 s .. 2.287 s)
                     0.998 R²   (0.992 R² .. 1.000 R²)
mean                 2.197 s    (2.141 s .. 2.249 s)
std dev              68.12 ms   (47.76 ms .. 82.33 ms)
variance introduced by outliers: 14% (moderately inflated)

benchmarking bench/bash -ci "(cd /usr/include/boost; ack --nofilter ARGS)"
analysing with 1000 resamples
bootstrapping with 5 of 6 samples (83%)
time                 2.250 s    (2.211 s .. 2.360 s)
                     0.999 R²   (0.995 R² .. 1.000 R²)
mean                 2.239 s    (2.219 s .. 2.279 s)
std dev              32.61 ms   (5.023 ms .. 43.91 ms)
found 1 outliers among 5 samples (20.0%)
  1 (20.0%) high mild
variance introduced by outliers: 14% (moderately inflated)

benchmarking bench/bash -ci "(cd ~/devel/cexmc; cgr parse)"
analysing with 1000 resamples
bootstrapping with 36 of 37 samples (97%)
time                 77.04 ms   (76.21 ms .. 78.01 ms)
                     0.999 R²   (0.999 R² .. 1.000 R²)
mean                 76.23 ms   (75.70 ms .. 76.84 ms)
std dev              1.794 ms   (1.429 ms .. 2.352 ms)
found 2 outliers among 36 samples (5.6%)
  1 (2.8%) low mild
  1 (2.8%) high mild
variance introduced by outliers: 8% (slightly inflated)

benchmarking bench/bash -ci "(cd ~/devel/cexmc; hlgrep parse)"
analysing with 1000 resamples
bootstrapping with 33 of 34 samples (97%)
time                 94.47 ms   (93.40 ms .. 95.40 ms)
                     1.000 R²   (0.999 R² .. 1.000 R²)
mean                 93.74 ms   (93.27 ms .. 94.17 ms)
std dev              1.325 ms   (1.054 ms .. 1.740 ms)
variance introduced by outliers: 3% (slightly inflated)

benchmarking bench/bash -ci "(cd ~/devel/cexmc; ack --nofilter parse)"
analysing with 1000 resamples
bootstrapping with 29 of 30 samples (96%)
time                 121.5 ms   (121.0 ms .. 122.0 ms)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 121.6 ms   (121.1 ms .. 122.2 ms)
std dev              1.449 ms   (1.174 ms .. 1.830 ms)
variance introduced by outliers: 3% (slightly inflated)

benchmarking bench/bash -ci "(cd ~/devel/cexmc; cgrep -r parse)"
analysing with 1000 resamples
bootstrapping with 38 of 39 samples (97%)
time                 68.35 ms   (66.80 ms .. 70.55 ms)
                     0.996 R²   (0.994 R² .. 0.999 R²)
mean                 68.03 ms   (67.52 ms .. 68.83 ms)
std dev              2.001 ms   (1.543 ms .. 2.696 ms)
found 5 outliers among 38 samples (13.2%)
  4 (10.5%) high mild
  1 (2.6%) high severe
variance introduced by outliers: 12% (moderately inflated)

benchmarking bench/bash -ci "(cd ~/devel/cexmc; hl -r parse)"
analysing with 1000 resamples
bootstrapping with 34 of 35 samples (97%)
time                 87.98 ms   (87.55 ms .. 88.39 ms)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 88.04 ms   (87.77 ms .. 88.32 ms)
std dev              836.7 μs   (676.7 μs .. 1.051 ms)
variance introduced by outliers: 3% (slightly inflated)

benchmarking bench/bash -ci "(cd ~/devel/cexmc; ack --nofilter --noenv parse)"
analysing with 1000 resamples
bootstrapping with 29 of 30 samples (96%)
time                 120.8 ms   (119.5 ms .. 122.5 ms)
                     0.999 R²   (0.999 R² .. 1.000 R²)
mean                 119.7 ms   (119.3 ms .. 120.3 ms)
std dev              1.456 ms   (1.006 ms .. 2.055 ms)
found 3 outliers among 29 samples (10.3%)
  3 (10.3%) high mild
variance introduced by outliers: 3% (slightly inflated)
Графический отчет здесь. Выводы следующие: cgrep быстрее всех, особенно при большом объеме входных данных (в директории /usr/include/boost) — 0.668 против 2.20 и 2.24 сек у hlgrep и ack. При небольших объемах данных (в директории cexmc) cgrep по-прежнему лидирует, хоть и с небольшим отрывом (0.076 сек), hlgrep значительно опережает ack (0.094 против 0.122 сек) за счет стартовой скорости. Чистые варианты cgrep, hl и ack в директории cexmc незначительно быстрее шелл-функций (0.068 против 0.076 сек в случае cgrep, 0.088 против 0.094 сек в случае hl и 0.120 против 0.122 сек в случае ack) — это говорит о том, что конвейеры, состоящие из программ sed, column и hl, из которых собраны эти функции, достаточно быстры.

среда, 11 ноября 2015 г.

cgrep vs ack

Cgrep и ack — это две grep-подобные программы, приспособленные для поиска внутри исходных текстов на разных языках программирования. Ack написана на perl и существует довольно давно, в то время как cgrep — относительно новая программа, написанная на haskell и обладающая большим количеством экспериментальных возможностей. Я не буду подробно сравнивать всевозможные аспекты использования этих программ, но отмечу их наиболее яркие положительные качества и недостатки и в конце приведу мои собственные настройки для декорирования их вывода на терминал. Итак, начнем с положительных качеств ack.
  • Возможность настроить рекурсивный поиск только внутри исходников указанного языка программирования (или нескольких языков). Соответствие файла определенному языку программирования определяется несколькими способами: расширением имени файла, точным именем файла (например, Makefile для make) и соответствием имени файла или первой строки в файле заданному шаблону регулярного выражения. Настройки соответствий языков по умолчанию можно вывести с помощью опции ack --dump: список языков находится внутри опций --type-add. Собственно включение в поиск определенного языка задается синтетической опцией --язык, где язык соответствует одному из значений в выведенном списке, стоящих сразу после символа =. Например, чтобы включить поиск внутри исходников C++, нужно указать опцию --cpp.
  • Возможность гибкой настройки вывода контекста вокруг строк с найденными совпадениями: для этого имеются целых три опции -A, -B и -C.
  • Гибкая настройка формата вывода, включая цветовую подсветку найденных совпадений и возможность перенаправления во внешнюю программу или скрипт с опцией --pager.
  • Задание настроек по умолчанию в файле .ackrc в домашней директории и поддиректориях.
  • Поиск файлов по имени или регулярному выражению (опция -g). Это просто киллер-фича! Теперь от громоздкой и негибкой команды find . -name '*pattern*' можно отказаться. Или почти отказаться… Есть два тонких момента, даже три. Во-первых, имеются опции по умолчанию --ignore-file, которые можно вывести с помощью опции --dump: в них указано, какие файлы будут игнорироваться при поиске. В частности, туда должны входить файлы jpg, png и тому подобные. Чтобы отменить все настройки по умолчанию, в том числе настройки игнорирования файлов, нужно просто добавить опцию --ignore-ack-defaults. Но… Вряд ли после этого ack начнет находить jpg и png файлы! Потому что они бинарные! И это было во-вторых. Чтобы преодолеть это ограничение, нужно поступить так, как сказано здесь, то есть добавить еще две опции --type-set='all:match:.*' -k (см. также определение алиаса ackf в конце этой статьи). Ну а в-третьих, пустые директории, а также всякого рода потоки и другие не-файлы, искусственно привязанные к файловой системе (сокеты, FIFO и т.п.), ack будет игнорировать в любом случае.
Прежде чем перейти к плюсам cgrep, я отмечу ее серьезный недостаток по сравнению с ack. Это невозможность вывода контекста вокруг строк с найденными совпадениями. А теперь плюсы.
  • Скорость и возможность параллельного исполнения. Это все-таки haskell!
  • Поддержка разных типов поиска. Кроме дословного поиска и поиска совпадения с регулярным выражением (включая POSIX и PCRE), сюда входят префиксный и суффиксный поиски, а также поиск с учетом расстояния Левенштейна. Последний тип поиска позволяет находить похожие слова или слова с возможными ошибками.
  • Поддержка семантического поиска позволяет искать совпадения в коде, комментариях или строковых литералах отдельно (опции -c, -m и -l), ограничивать поиск специфическими типами идентификаторов, такими как ключевые слова, литералы, директивы препроцессора и операторы (наилучшим образом поддерживаются языки C и C++). Опция -S позволяет настроить семантический поиск с помощью специального семантического языка (простейшие примеры можно увидеть, запустив cgrep --help).
  • Как и в ack, можно настроить поиск только внутри исходников определенного языка (или языков). Список поддерживаемых языков и соответствующие им расширения имен файлов можно вывести с помощью опции --lang-maps. Чтобы включить в поиск определенный язык, нужно указать его в опции --lang, например, --lang=Cpp.
  • Как и в ack, можно гибко настроить формат вывода с помощью опции --format. Вывод подсветки задается, но сами цвета не настраиваются (в функции cgr, которую я приведу ниже, изменение цветов подсветки достигается с помощью обработки в sed).
  • Некоторые настройки по умолчанию можно записать в файле .cgreprc в домашней директории.
Нужно отметить, что поиск на основе регулярных выражений в cgrep отличается от того, как это реализовано в ack или в обычном grep. Если последние ищут совпадения в каждой отдельной строке ввода, то cgrep применяет шаблон ко всему входному тексту. Это может приводить к тонким семантическим отличиям для некоторых шаблонов. Например, класс символов \s включает переносы строк, а значит найденное совпадение после применения шаблона с \s может быть многострочным. Ситуация осложняется тем, что cgrep выведет только первую строку совпадения (а это уже баг, на мой взгляд), которая может не содержать значимой информации, тем самым дезориентируя пользователя. Поэтому я советую вместо \s использовать класс горизонтальных пробелов \h (правда, он доступен только в PCRE) — это будет гарантировать отсутствие переносов строк в найденном совпадении. Ниже я привожу картинку с выводом ack и cgrep на моем терминале, а также настройки и скрипты, которые позволяют добиться этого результата. Файл .ackrc.
--color-match=rgb551
--color-filename=rgb342
--color-lineno=rgb233

--noheading
--pager=sed -e 's/\(.\+\)\([:-]\)\(\x1b\[38;5;109m[[:digit:]]\+\x1b\[0m\)\2/\1 ⎜ \3 ⎜ /'
            -e 's/\x1b/@eCHr@/g' | column -t -s-o⎜ |
        sed -e 's/@eCHr@/\x1b/g'
            -e 's/\(\s\+\)\(\x1b\[38;5;109m[[:digit:]]\+\x1b\[0m\)\(\s\+\)/\3\2\1/' |
        hl -u -255 -b '^--$' -rb -216 '\x{239C}'
Обратите внимание, я разбил последнюю строку с опцией --pager на пять отдельных строк для удобочитаемости. На самом деле это должна быть одна строка и разбивать ее на части нельзя! А это функция-обертка cgr (ее можно поместить в .bashrc).
function cgr
{
    `env which cgrep` --color --format='#f ⎜ #n ⎜ #l' -r "$@" |
            sed -e 's/\x1b\[1m\x1b\[94m/@eCHrF@/' \
                -e 's/\x1b\[1m/@eCHrB@/g' \
                -e 's/\x1b\[m/@eCHrE@/g' |
            column -t -s-o|
            sed -e 's/@eCHrF@/\x1b\[38;5;150m/' \
                -e 's/@eCHrB@/\x1b\[38;5;227m/g' \
                -e 's/@eCHrE@/\x1b\[m/g' \
                -e 's/\(\s\+\)\([[:digit:]]\+\)\(\s\+\)/\3\2\1/' |
            hl -u -73 '\h+\d+\h+(?=\x{239C})' -216 '\x{239C}'
}
(Здесь переносы допустимы). Программа hl доступна отсюда — она нужна для дополнительной подсветки колонки с номерами строк, я писал о ней здесь и здесь. Ну и алиас для поиска файлов с помощью ack.
alias ackf='ack --ignore-ack-defaults --type-set=all:match:. -k --color --nopager -g'
Update. Начиная с версии util-linux 2.28 программа column научилась игнорировать управляющие последовательности ANSI, поэтому ужасные вре́менные замены текста типа @eCHr@ в опции --pager из .ackrc и в функции cgr больше не нужны. Соответственно, теперь они будут выглядеть так (содержимое опции --pager должно по-прежнему находиться в одной строке).
--pager=sed 's/\(.\+\)\([:-]\)\(\x1b\[38;5;109m[[:digit:]]\+\x1b\[0m\)\2/\1 ⎜ \3 ⎜ /' |
        column -t -s-o⎜ |
        sed 's/\(\s\+\)\(\x1b\[38;5;109m[[:digit:]]\+\x1b\[0m\)\(\s\+\)/\3\2\1/' |
        hl -u -255 -b '^--$' -rb -216 '\x{239C}'

function cgr
{
    `env which cgrep` --color --format='#f ⎜ #n ⎜ #l' --no-column -r "$@" |
            column -t -s-o|
            sed -e 's/\x1b\[1;94m/\x1b\[38;5;150m/' \
                -e 's/\x1b\[1m/\x1b\[38;5;227m/g' \
                -e 's/\(\s\+\)\([[:digit:]]\+\)\(\s\+\)/\3\2\1/' |
            hl -u -73 '\h+\d+\h+(?=\x{239C})' -216 '\x{239C}'
}
Также обратите внимание на некоторые изменения в функции cgr, связанные с обновленным cgrep, в частности на новую опцию --no-column.

воскресенье, 26 июня 2011 г.

vim: практические шаги для комфортного использования

Ниже приводятся четыре практических совета, которые являются в большей степени методами устранения недостатков, присущих vim и его известным плагинам, нежели самостоятельными решениями. Поскольку в трех советах речь пойдет о модификации плагинов, будут представлены патчи, соответствующие текущим версиям этих плагинов. Патчи можно накладывать автоматически или вручную. В первом случае достаточно сохранить патч в файле, скажем c.vim.patch, затем перейти в директорию, в которой находится файл c.vim (в данном случае это $HOME/.vim/plugin/) и выполнить команду:
patch -p0 < /path/to/c.vim.patch
1. Поддержка директорий проектов в c.vim. Под поддержкой проекта будем понимать возможность использования различных шаблонов c.vim в зависимости от того, какому проекту принадлежит редактируемый файл. Как правило, файлы проекта расположены в отдельной директории, а сами проекты могут предъявлять разные требования к оформлению элементов, таких как заголовки файлов. В c.vim имеется поддержка отдельных проектов с помощью макроса STYLE (см. документацию c.vim), однако ее реализация имеет недостатки. В частности, этот подход подразумевает централизованное хранение всех шаблонов в директории ~/.vim/c-support/templates/. Нормальная поддержка проектов должна предполагать возможность хранения независимого набора шаблонов в директории проекта - это более гибкий и естественный подход.

Следующий патч (относительно c.vim версии 5.13) реализует возможность хранения шаблонов c.vim в директории проекта:
--- c.vim 2011-01-26 00:12:31.000000000 +0300
+++ c.vim.new 2011-06-26 13:07:16.000000000 +0400
@@ -327,6 +327,10 @@
 
 let s:C_SourceCodeExtensionsList = split( s:C_SourceCodeExtensions, '\s\+' )
 
+if exists( 'g:C_ProjectDirs' )
+ let s:C_ProjectList   = split( g:C_ProjectDirs, ',' )
+endif
+
 "------------------------------------------------------------------------------
 
 "------------------------------------------------------------------------------
@@ -2702,6 +2706,16 @@
     let s:C_FileVisited   = []
   let messsage     = ''
   "
+
+  for proj in s:C_ProjectList
+   if stridx(getcwd(), proj) != -1
+    if filereadable( proj."/.cvim.templates/Templates" )
+     call C_ReadTemplates( proj."/.cvim.templates/Templates" )
+     return
+    endif
+   endif
+  endfor
+
   if s:installation == 'system'
    "
    if filereadable( s:C_GlobalTemplateFile )
Здесь вводится новая глобальная переменная C_ProjectDirs, которая представляет собой список проектов, разделенных запятыми, для которых могут быть предложены отдельные наборы шаблонов. Например, в C_ProjectDirs добавим проект /path/to/proj1, далее в указанной директории создадим директорию .cvim.templates/, скопируем туда шаблоны из ~/.vim/c-support/templates/ и отредактируем по вкусу. Теперь при редактировании файлов в директории /path/to/proj1 и любой ее поддиректории будут использоваться новые шаблоны. При редактировании файла, не входящего ни в один из указанных в C_ProjectDirs проектов, будут использоваться шаблоны по умолчанию.

Переменную C_ProjectDirs можно определить в ~/.vimrc, например так:
let g:C_ProjectDirs = "/path/to/proj1,".$PROJ2WORKDIR
Здесь указаны 2 проекта: /path/to/proj1 и проект, определенный через переменную среды PROJ2WORKDIR, проекты разделены запятой.

2. Проблема взаимодействия fuzzyfinder и taglist. Taglist - самый популярный плагин vim. Он предоставляет отдельное окно для навигации по тегам исходного кода в открытых файлах. Это очень старый плагин и, к сожалению, давно не обновлялся, поэтому ему присущи недостатки, в частности, его сложно использовать при наличии нескольких табов (которые стали доступны в vim 7.0). Fuzzyfinder - очень интересный плагин, который легко использовать для навигации по файловой системе, тегам, закладкам и др. Особенностью данного плагина является то, что он не использует отдельное окно, а открывает временное окно поиска в левом верхнем углу терминала по запросу. К сожалению, при определенных настройках taglist, эти два плагина не дружат, что выражается в выводе нового буфера в окно taglist после закрытия окна поиска fuzzyfinder (см. здесь). Починить это поведение можно с помощью простого изменения в файле ~/.vim/autoload/l9/tempbuffer.vim (библиотека l9 поставляется вместе с fuzzyfinder, патч приведен относительно версии l9 1.1):
--- tempbuffer.vim 2010-09-28 23:55:28.000000000 +0400
+++ tempbuffer.vim.new 2011-06-26 13:13:54.000000000 +0400
@@ -22,7 +22,9 @@
   endif
   if bufnr('%') == s:dataMap[a:bufname].bufNr && winnr('#') != 0
     " if winnr('#') returns 0, "wincmd p" causes ringing the bell.
-    wincmd p
+    " my cmt: 'wincmd p' works badly with taglist - using 'wincmd j' instead
+    "wincmd p
+    wincmd j
   endif
 endfunction
 
@@ -43,7 +45,8 @@
 " a:listener:
 "   a:listener.onClose(written)
 function l9#tempbuffer#openScratch(bufname, filetype, lines, topleft, vertical, height, listener)
-  let openCmdPrefix = (a:topleft ? 'topleft ' : '')
+  " my cmt: open fuf buffer above current window ('leftabove' instead 'topleft')
+  let openCmdPrefix = (a:topleft ? 'leftabove ' : '')
         \           . (a:vertical ? 'vertical ' : '')
         \           . (a:height > 0 ? a:height : '')
   if !exists('s:dataMap[a:bufname]') || !bufexists(s:dataMap[a:bufname].bufNr)
Данный патч делает еще одно, на мой взгляд, полезное изменение в поведении fuzzyfinder: теперь окно поиска будет открываться не в левом верхнем угла терминала, а вверху окна, в котором осуществляется редактирование.

3. Поддержка ack в grep.vim. ack - это grep для пограммистов. Это очень удобная утилита для поиска текста в директории исходников, она знает о служебных директориях CVS, Subversion и др. и игнорирует их, поддерживает регулярные выражения perl и обладает другими полезными свойствами. В vim существует плагин, который поддерживает ack, однако он реализует простейшую обертку встроенного в vim grep и "мусорит" в stdout. Плагин grep.vim реализован лучше, однако не обладает поддержкой ack. Следующий патч реализует это поддержку в grep.vim (относительно версии grep.vim 1.9):
--- grep.vim 2011-06-26 13:19:02.000000000 +0400
+++ grep.vim.new 2011-06-26 13:22:22.000000000 +0400
@@ -317,6 +317,10 @@
     let Agrep_Path = 'agrep'
 endif
 
+if !exists("Ack_Path")
+    let Ack_Path = 'ack'
+endif
+
 " Location of the find utility
 if !exists("Grep_Find_Path")
     let Grep_Find_Path = 'find'
@@ -731,7 +735,7 @@
         let grep_opt = g:Grep_Default_Options
     endif
 
-    if a:grep_cmd != 'agrep'
+    if a:grep_cmd != 'agrep' && a:grep_cmd != 'ack'
         " Don't display messages about non-existent files
         " Agrep doesn't support the -s option
         let grep_opt = grep_opt . " -s"
@@ -749,6 +753,9 @@
     elseif a:grep_cmd == 'agrep'
         let grep_path = g:Agrep_Path
         let grep_expr_option = ''
+    elseif a:grep_cmd == 'ack'
+        let grep_path = g:Ack_Path
+        let grep_expr_option = ' -H --nogroup --nocolor'
     else
         return
     endif
@@ -763,7 +770,7 @@
                         \ g:Grep_Shell_Quote_Char
     endif
 
-    if filenames == ""
+    if filenames == "" && a:grep_cmd != 'ack'
         if v:version >= 700
             let filenames = input("Search in files: ", g:Grep_Default_Filelist,
                         \ "file")
@@ -777,8 +784,12 @@
 
     " Add /dev/null to the list of filenames, so that grep print the
     " filename and linenumber when grepping in a single file
-    let filenames = filenames . " " . g:Grep_Null_Device
-    let cmd = grep_path . " " . grep_opt . " -n "
+    if a:grep_cmd != 'ack'
+        let filenames = filenames . " " . g:Grep_Null_Device
+        let cmd = grep_path . " " . grep_opt . " -n "
+    else
+        let cmd = grep_path . " " . grep_opt
+    endif
     let cmd = cmd . grep_expr_option . " " . pattern
     let cmd = cmd . " " . filenames
 
@@ -810,6 +821,9 @@
 command! -nargs=* -complete=file Ragrep
             \ call s:RunGrepRecursive('Ragrep', 'agrep', 'set', <f-args>)
 
+command! -nargs=* -complete=file Ack
+            \ call s:RunGrep('Ack', 'ack', 'set', <f-args>)
+
 if v:version >= 700
 command! -nargs=* -complete=file GrepAdd
             \ call s:RunGrep('GrepAdd', 'grep', 'add', <f-args>)
Теперь, для того чтобы выполнить рекурсивный поиск вхождения sample в текущей директории из vim, достаточно ввести команду
:Ack sample
Тот же поиск, но во всех файлах с расширением .cc в текущей директории:
:Ack sample *.cc
4. Навигация между окнами с помощью комбинаций клавиш Ctrl-Arrow. Это очень полезный совет, который можно найти на многих ресурсах в сети. Суть идеи в том, чтобы при наличии нескольких открытых окон переключаться между ними простой комбинацией клавиш Ctrl-Up и Ctrl-Down. Такое поведение требует некоторой настройки, в частности, значение bufhidden должно быть равным delete, чтобы при навигации скрытые буферы (т.е. те файлы, которые ранее редактировались в открытых окнах, но были в дальнейшем скрыты новыми файлами) не открывались в отдельных окнах. Итак, настройки для данного поведения, которые следует поместить в ~/.vimrc выглядят так:
set switchbuf=usetab
autocmd BufEnter * setlocal bufhidden=delete
nmap <C-up>     :sbn<CR>
nmap <C-down>   :sbp<CR>
nmap <C-left>   :tabp<CR>
nmap <C-right>  :tabn<CR>
Последние два маппинга добавляют возможность навигации между табами по комбинациям клавиш Ctrl-Left и Ctrl-Right и не имеют отношения к рассматриваемой далее проблеме.

А проблема заключается в том, что если некоторый файл попал по той или иной причине в список bufhidden, то при его повторном открытии попасть в его окно с помощью комбинаций Ctrl-Up или Ctrl-Down становится невозможно. Это означает, что, скорее всего, vim не удаляет файл из списка bufhidden при его повторном открытии. Лечится это добавлением еще одной авто-команды в ~/.vimrc:
autocmd BufEnter * if empty(&buftype) | setlocal buflisted | endif
Update: Кажется редактор blogger превратил символы табуляции в патчах в пробелы :( Так что для автоматического наложения патчей теперь придется использовать опцию -l, например так:
patch -l -p0 < /path/to/c.vim.patch