РУКОВОДСТВО ПОЛЬЗОВАТЕЛЯ VIM - Брам Мооленаар

Перемещение в программном коде

Создатель Vim -- программист. Не удивительно, что в Vim так много свойств, полезных при написании компьютерных программ: это и прыжки к месту определения тех или иных имён, и предварительный просмотр определений в отдельном окне, и многое другое, чему посвящена эта и следующая глава руководства.

29.1    Использование меток
29.2    Окно предварительного просмотра
29.3    Перемещение в программном коде
29.4    Поиск глобальных имён
29.5    Поиск местных имён

Следующая глава: Редактирование программ
Предыдущая глава: Складки
Содержание: Руководство пользователя Vim


29.1 Использование меток

Что такое метка? Это позиция в тексте, где определено имя. Например, определение функции в программе на языке C или C++. Список меток хранится в специальном файле меток и используется Vim для того, чтобы совершать прыжки к позиции метки, т.е. туда, где определено имя, из любого места.

Для создания списка меток для всех файлов С в текущем каталоге используйте команду:

ctags *.c

"ctags" это отдельная программа. Она включена в состав системы на многих Unix-машинах. Если у вас нет этой программы, то вы можете загрузить Exuberant ctags отсюда: http://ctags.sf.net

Находясь в редакторе Vim и имея созданный файл меток вы можете прыгнуть к любому определению функции по команде:

:tag startlist

Эта команда найдёт функцию "startlist", даже если та находится в другом файле.

Команда CTRL-] прыгает к метке, обозначенной словом, расположенным в данный момент под курсором. Это позволяет легко перемещаться по клубку кода на C. Например, вы находитесь в функции "write_block" и видите, что она вызывает функцию "write_line". Но что делает эта функция "write_line"? Поместив курсор на вызов функции "write_line" и нажав CTRL-], вы можете перепрыгнуть к определению этой функции.

Функция "write_line", в свою очередь, вызывает функцию "write_char". Вам теперь хочется выяснить, что делает эта функция. Нет проблем! Вы размещаете курсор на вызове функции "write_char" и нажимаете CTRL-]. Теперь перед вами определение функции "write_char".

+-------------------------------------+
|void write_block(char **s; int cnt)  |
|{                      |
|   int i;                  |
|   for (i = 0; i < cnt; ++i)         |
|      write_line(s[i]);           |
|}        |                   |
+-----------|-------------------------+
        |
     CTRL-] |
         |    +----------------------------+
        +--> |void write_line(char *s)    |
         |{                  |
         |   while (*s != 0)          |
             |     write_char(*s++);     |
             |}      |              |
             +--------|-------------------+
                  |
               CTRL-] |
                   |    +------------------------------------+
                   +--> |void write_char(char c)            |
                        |{                      |
                       |    putchar((int)(unsigned char)c); |
                       |}                    |
                        +------------------------------------+

Команда ":tags" показывает список меток, по которым вы перемещались:

:tags
  # К  метка       ОТ   стр.  в файле или тексте ~
  1  1 write_line       8  write_block.c ~
  2  1 write_char       7  write_line.c ~
> ~

Теперь вернёмся назад. Команда CTRL-T прыгает к предыдущей метке. В нашем примере вы вернётесь к функции "write_line", к месту вызова "write_char".

Эта команда принимает числовую приставку-аргумент, указывающую на сколько прыжков надо вернуться назад. Давайте теперь опять перепрыгнем в направлении "вперёд". Команда

:tag

переходит к метке наверху списка. Эта команда также может сопровождаться числовым аргументом приставкой, указывающим количество прыжков вперёд, которое надо выполнить. Например, ":3tag". Как уже говорилось, то же касается и команды CTRL-T.

Таким образом, эти команды позволяют перемещаться вниз по дереву меток с помощью CTRL-], и вверх с помощью CTRL-T. Команда ":tags" позволяет выяснить, где вы находитесь в данный момент.

РАЗДЕЛЕНИЕ ОКОН

Команда ":tag" заменяет файл, открытый в текущем окне на другой, тот, в котором находится метка. Но что, если вы хотите видеть не только определение старой функции, но и новую функцию, ту, из которой вы делаете прыжок к метке? Для этого можно разделить окно по команде ":split" с последующим выполнением команды ":tag". В Vim даже имеется сокращенная команда для такой операции:

:stag имя_метки

Для того, чтобы разделить текущее окно и прыгнуть к метке, расположенной под курсором, пользуйтесь командой

CTRL-W ]

Если эта команда отдаётся с числовым аргументом-приставкой, то новое окно будет высотой в указанное количество строк.

НЕСКОЛЬКО ФАЙЛОВ МЕТОК

Если ваши файлы находятся в различных каталогах, то вы можете создать файлы меток в каждом из них. Vim будет способен перемещаться по меткам в текущем каталоге.

Чтобы Vim мог найти и использовать несколько файлов меток, настройте опцию 'tags' так, чтобы Vim мог найти все необходимые файлы. Пример:

:set tags=./tags,./../tags,./*/tags

Это позволяет использовать файлы меток в том же каталоге, что и редактируемый файл, а также в каталоге, расположенном на один уровень выше и во всех  подкаталогах.

Если вам не хватает даже этого, то Vim позволяет также искать файлы по всему дереву файловой системы. Например, если вы редактируете файл в "~/proj/src", а вам надо найти файл меток в "~/proj/sub/tags", то можно выполнить команду:

:set tags=~/proj/**/tags
ОДИН ФАЙЛ МЕТОК

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

Вам потребуется программа Exuberant ctags. Она позволяет задать аргумент для рекурсивного поиска определений:

cd ~/proj
ctags -R .

Приятной особенностью Exuberant ctags является и то, что она распознаёт множество различных типов файлов. Для более тонкой настройки смотрите документацию к ctags.

Теперь от вас требуется только подсказать редактору, где находится ваш файл меток:

:set tags=~/proj/tags
МНОЖЕСТВЕННЫЕ СООТВЕТСТВИЯ

Если функция определена несколько раз (или метод определён в нескольких классах), то команда ":tag" прыгнет к первому в списке. Если такая метка имеется в текущем файле, то переход произойдёт прежде всего к ней.

Для перехода к остальным меткам, которые соответствуют тому же самому слову, следует пользоваться командой

:tnext

Для поиска дальнейших соответствий просто повторяйте эту команду. Если список вариантов достаточно большой, то можно использовать команду

:tselect имя_метки

В этом случае Vim предложит вам список для выбора:

  # при тип  метка              файл ~
  1 F   f    mch_init           os_amiga.c ~
           mch_init() ~
  2 F   f    mch_init           os_mac.c ~
          mch_init() ~
  3 F   f    mch_init           os_msdos.c ~
          mch_init(void) ~
  4 F   f    mch_init           os_riscos.c ~
          mch_init() ~
Введите номер для выбора (или <CR> чтобы отказаться):  ~

Теперь вы можете ввести номер (указан в первой колонке) метки, к которой вам бы хотелось прыгнуть. Информация в других колонках поможет вам получить представление о том, где находится эта метка.

Для перемещения между метками с одинаковыми именами можно использовать такие команды:

:tfirst прыжок к первому соответствию
:[число]tprevious прыжок к [число] предыдущему соответствию
:[число]tnext прыжок к [число] следующему соответствию
:tlast прыжок к последнему соответствию

Если [число] не указано, то считается, что оно равно 1.

ДОПОЛНЕНИЕ ИМЕНИ МЕТКИ

Чтобы не печатать длинное имя метки, можно воспользоваться средствами автодополнения командной строки. Просто введите несколько символов имени метки и нажмите <Tab>:

:tag write_<Tab>

Вы увидите первое из возможных соответствий. Если это окажется не совсем та метка, которая вам необходима, то можно продолжать нажимать <Tab> пока не появится то, что нужно.

Иногда вы помните только часть имени функции. Или у вас может быть множество меток, начало имён которых совпадает. В таких случаях вы можете заставить Vim использовать шаблон для поиска нужной метки.

Предположим, например, что вам нужно прыгнуть к метке, имя которой содержит слово "block". Для начала попробуйте набрать

:tag /block

Теперь воспользуйтесь дополнением командной строки: нажмите <Tab>. Vim  найдёт все метки, соответствующие шаблону "block" и покажет первое соответствие.

"/" перед именем метки указывает редактору, что далее следует не буквальное имя метки, а шаблон для поиска. Вы можете пользоваться любыми шаблонами поиска. Например, если вам хочется найти метку, которая начинается со слова "write_":

:tselect /^write_

"^" указывает, что метка начинается с "write_". Без этого символа в шаблоне были бы также найдены метки, где это слово встречается в середине имени метки. Аналогично, "$" в конце шаблона позволяет гарантировать, что шаблон соответствует концу имени метки.

ПРОСМОТР МЕТОК

Поскольку CTRL-] переносит вас к определению имени, находящегося под курсором, то вы можете использовать список имён подобно содержанию в книге. Приведём пример.

Прежде всего, создайте список имён (вам потребуется Exuberant ctags):

ctags --c-types=f -f functions *.c

Теперь запустите Vim без аргументов командной строки и откройте файл со списком в вертикально разделённом окне:

vim
:vsplit functions

Теперь окно содержит список всех функций, а также несколько дополнительных элементов, которые вы можете игнорировать. Чтобы немного подчистить список, введите команду ":setlocal ts=99".

В этом окне следует определить привязку:

:nnoremap <buffer> <CR> 0ye<C-W>w:tag <C-R>"<CR>

Подведите курсор к имени функции, которую вам бы хотелось посмотреть и нажмите <Enter>. Vim покажет определение выбранной функции в другом окне.

ДОПОЛНИТЕЛЬНЫЕ СВЕДЕНИЯ

Чтобы не обращать внимания на регистр в именах меток, включите опцию 'ignorecase'.

Опция 'tagbsearch' сообщает редактору, является ли список меток отсортированным по алфавиту, или же нет. По умолчанию предполагается, что список отсортирован, что позволяет искать необходимую метку намного быстрее, но в том случае, если список меток не отсортирован, прыжки не будут работать правильно.

Опция 'taglength' сообщает редактору число символов, которые имеют значение для распознавания метки.

Если вы пользуетесь программой SNiFF+, то в Vim имеется интерфейс к этой программе, см. |sniff|. SNiFF+ это коммерческий продукт.

Cscope это бесплатная программа, которая позволяет обнаружить не только место, где определяется то или иное имя, но и все места, где оно используется.  См. |cscope|.


29.2 Окно предварительного просмотра

При использовании вызова функций нужно следить за тем, чтобы использовать правильные аргументы. Чтобы узнать, какие аргументы требует функция, необходимо посмотреть на определение функции. Механизм меток очень хорошо подходит для этой цели. Лучше всего, если определение функции при этом показывается в отдельном окне. Как нельзя лучше для этого подходит окно предварительного просмотра.

Чтобы открыть окно предварительного просмотра с определением функции "write_char":

:ptag write_char

Vim откроет окно и прыгнет к метке "write_char". Самое приятное, что курсор вернётся в предыдущее окно и вы можете продолжать набор без необходимости переключения между окнами по команде CTRL-W.

Если имя функции встречается в тексте, то посмотреть её определение в окне предпросмотра можно по команде:

CTRL-W }

Существует сценарий, который автоматически показывает текст определения, если слово под курсором является именем метки. См. |CursorHold-пример|.

Чтобы закрыть окно предпросмотра, используйте команду:

:pclose

Для редактирования того или иного файла в окне предпросмотра, используйте команду ":pedit". Она может быть полезной при редактировании файла с заголовками, например:

:pedit defs.h

Кроме того, вы можете пользоваться командой ":psearch" для поиска слова в текущем файле и любом другом включённом файле, и показать соответствие в окне предпросмотра. Это особенно полезно при использовании библиотечных функций, для которых у вас может и не быть файла меток.  Например:

:psearch popen

Эта команда покажет в окне предпросмотра файл "stdio.h" с прототипом функции popen():

FILE    *popen __P((const char *, const char *)); ~

Вы можете настроить высоту окна предпросмотра при его открытии. Для этого существует специальная опция 'previewheight'.


29.3 Перемещение в программном коде

Поскольку исходные тексты программ хорошо структурированы, то Vim может распознавать их отдельные элементы. Для перемещения внутри программного кода можно использовать специальные команды.

Программы на языке C часто содержат конструкции наподобие следующей:

#ifdef USE_POPEN ~
    fd = popen("ls", "r") ~
#else ~
    fd = fopen("tmp", "w") ~
#endif ~

Зачастую такого рода конструкции намного длиннее показанной; кроме того, такие конструкции могут быть вложены одна в другую. Поместите курсор на "#ifdef" и нажмите %. Vim перейдёт к строке с "#else". Повторное нажатие на % позволяет перейти к "#endif". Следующее нажатие на % возвращает курсор на строку с "#ifdef".

Если конструкция будет вложена в другую похожую конструкцию, то Vim найдёт правильное соответствие. Этот способ хорошо годится для проверки незакрытых "#if".

Если вы находитесь где-то внутри "#if" - "#endif", то вы можете прыгнуть к началу этого блока с помощью команды

[#

Если позиция курсора не находится где-либо после "#if" или "#ifdef", то Vim подаст звуковой сигнал. Для перехода к следующему "#else" или "#endif" пользуйтесь

]#

Эти две команды пропускают любые встреченные по дороге блоки "#if" - "#endif". Например:

#if defined(HAS_INC_H) ~
    a = a + inc(); ~
# ifdef USE_THEME ~
    a += 3; ~
# endif ~
    set_width(a); ~

Если курсор расположен на последней строке в примере, то "[#" переместит курсор на первую строку. Блок "#ifdef" - "#endif" внутри внешнего блока будет пропущен.

ПЕРЕМЕЩЕНИЕ ВНУТРИ БЛОКОВ В КОДЕ

В языке C блоки заключаются в фигурные скобки. Такие гирлянды блоков могут быть достаточно длинными. Для перемещения к началу внешнего блока используйте команду "[[". Команда "][" поможет найти конец блока. При этом предполагается, что "{" и "}" находятся в первой колонке.

Команда "[{" перемещает курсор в начало текущего блока. Она будет пропускать пары {}, расположенные на уровне ниже. "]}" позволяет перейти в конец блока.

Вот схема, которая поможет разобраться в этих командах:

                    function(int a)
       +->         {
       |            if (a)
       |       +->      {
    [[ |       |         for (;;)           --+
       |       |      +->   {             |
       |    [{ |      |        foo(32);         |     --+
       |       |   [{ |        if (bar(a))  --+     | ]}    |
       +--     |      +--        break;     | ]}  |     |
           |         }         <-+     |     | ][
           +--         foobar(a)         |     |
                }                   <-+     |
             }                       <-+

При написании программ на C++ или Java, внешний блок {} соответствует классу. Следующий уровень блока {} относится к методу. Внутри класса для перехода к предыдущему началу метода можно использовать команду "[m", а команда "]m" находит следующий конец метода.

Кроме того, "[]" перемещает курсор назад в конец функции, а "]]" перемещает курсор вперёд к началу следующей функции. Конец функции определяется по скобке "}" в первой колонке.

                         int func1(void)
                 {
                     return 1;
           +---------->  }
          |
      []  |         int func2(void)
           |       +->   {
          |   [[  |        if (flag)
start      +--     +--            return flag;
           |   ][  |        return 2;
          |       +->   }
       ]]  |
           |         int func3(void)
          +---------->  {
                     return 3;
                 }

Не забывайте, что вы также можете пользоваться и командой "%" для перемещения между соответствующими парами (), {} и []. Эта команда работает даже тогда, когда соответствующие скобки разнесены друг от друга на много строк.

ПЕРЕМЕЩЕНИЕ МЕЖДУ ЭЛЕМЕНТАМИ В СКОБКАХ

Команды "[(" и "])" работают так же, как и "[{" и "]}", с тем исключением, что они позволяют перемещаться по парам простых скобок () вместо фигурных {}.

                 [(
<   <--------------------------------
              <-------
if (a == b && (c == d || (e > f)) && x > y) ~
                  -------------->
          --------------------------------> >
                       ])
ПЕРЕМЕЩЕНИЕ ВНУТРИ КОММЕНТАРИЕВ

Для перемещения назад к началу комментария используйте команду "[/". Для перемещения вперёд в конец комментария используйте команду "]/". Эти команды работают для комментариев в стиле /* - */.

   +->    +-> /*
   |   [/ |    * Комментарий о             --+
[/ |      +--  * прекрасной жизни.          | ]/
   |           */                   <-+
   |
   +--         foo = bar * 3;              --+
                             | ]/
               /* короткий комментарий */  <-+

29.4 Поиск глобальных имён

Представьте, что вы редактируете программу на C и хотите выяснить как объявлена та или иная переменная. "int" или "unsigned"? Быстрый способ выяснить это -- использовать команду "[I".

Предположим, курсор находится на слове "column". Введите:

[I

Vim покажет все строки с этим именем, которые сможет найти, причём не только в текущем файле, но и в файлах, которые включаются в этот файл (а также в тех, которые включаются во включённые файлы, и т.д.). Результат выглядит примерно так:

structs.h ~
 1:   29     unsigned     column;    /* column number */ ~

Преимущество этого метода перед использованием меток или окна предварительного просмотра состоит в том, что поиск также ведётся во включённых файлах. В большинстве случаев это приводит к тому, что вы сразу видите как была объявлена та или иная переменная. Кроме того, этот способ помогает в тех случаях, когда файл меток не соответствует действительности, или если у вас нет файла меток для включённых файлов.

Однако, чтобы эта команда работала, должны быть правильно настроены некоторые вещи. Прежде всего, опция 'include' должна указывать, каким способом включаются файлы. Значение по умолчанию подходит для языков C и C++. Для других языков, возможно, придётся изменить значение этой опции.

ГДЕ НАХОДЯТСЯ ВКЛЮЧЁННЫЕ ФАЙЛЫ?

Vim будет искать включённые файлы в каталогах, указанных в опции 'path'. Если тот или иной каталог не указан в этой опции, то некоторые включённые файлы могут быть не обнаружены. Проще всего проверить, как обстоят дела, при помощи команды

:checkpath

Вам будет представлен список включённых файлов, которые не обнаружены, включая файлы, которые включены в обнаруженные файлы. Пример вывода команды:

--- Включённые файлы не обнаруженные по пути --- ~
<io.h> ~
vim.h --> ~
  <functions.h> ~
  <clib/exec_protos.h> ~

Файл "io.h" включён в текущий файл, но не найден. "vim.h" обнаружен, поэтому команда ":checkpath" проверяет файлы, включённые в этом файле, и отмечает, что среди них не найдены "functions.h" и "clib/exec_protos.h".

Замечание: Vim это не компилятор. Он не распознаёт "#ifdef" при поиске включённых файлов, а использует все выражения "#include", которые может найти, даже если они находятся после выражения вроде "#if NEVER".

Для того, чтобы исправить ситуацию, добавьте необходимый каталог в опцию 'path'. Посмотрите в своём Makefile, скорее всего там уже перечислены все необходимые пути, которые обычно бывают указаны с помощью "-I", например "-I/usr/local/X11". Чтобы добавить этот каталог, введите команду:

:set path+=/usr/local/X11

Если подкаталогов несколько, то можно использовать символ маски "*". Пример:

:set path+=/usr/*/include

Эта команда позволяет искать как в "/usr/local/include", так и в "/usr/X11/include".

При работе над проектом, в котором есть множество подкаталогов с включаемыми файлами, полезна также маска "**". Она позволяет устроить поиск во всех подкаталогах. Пример:

:set path+=/projects/invent/**/include

Эта настройка позволит искать в таких каталогах, как:

/projects/invent/include ~
/projects/invent/main/include ~
/projects/invent/main/os/include ~

и т.д.

Существует также и еще несколько возможностей. Дополнительную информацию смотрите в справке по опции 'path'.

Если вы хотите посмотреть, какие именно файлы были найдены, то используйте команду:

:checkpath!

Вы увидите список включённых файлов, который, возможно, будет длинным. Чтобы немного сократить список, Vim будет отмечать файлы, которые уже были показаны как "(уже показано в списке)".

ПРЫЖОК К МЕСТУ НАЙДЕННОГО СООТВЕТСТВИЯ

"[I" показывает список, состоящий из единственной строки текста. Чтобы посмотреть на первую найденную строку поближе, можно перейти к ней по команде

[<Tab>

Вы также можете пользоваться командой "[ CTRL-I", поскольку CTRL-I и <Tab> это одно и то же.

Список, выдаваемый по команде "[I", включает номер, который выводится в начале каждой строки. Если вы хотите перейти к строке, имеющей отличный от единицы номер в списке, то укажите этот номер перед вводом команды:

3[<Tab>

Эта команда позволит вам перейти к третьей строке. Не забывайте, что для возвращения назад можно пользоваться командой CTRL-O.


ДОПОЛНИТЕЛЬНЫЕ КОМАНДЫ
[i показывает только первое соответствие
]I показывает только совпадения, расположенные по тексту ниже позиции курсора
]i показывает только первое соответствие ниже по тексту
ПОИСК ОПРЕДЕЛЕНИЙ #define

Команда "[I" ищет любое имя. Для поиска только определений макросов "#define", используйте команду

[D

Поиск будет происходить также и во всех включённых файлах. Настроить синтаксис макроопределений можно с помощью опции 'define', которая по умолчанию настроена на синтаксис в стиле C/C++.

Команды, связанные с "[D":

[d показывает только первое соответствие
]D показывает только совпадения, расположенные по тексту ниже позиции курсора
]d показывает только первое соответствие ниже по тексту

29.5 Поиск местных имён

Команда "[I" ищет во всех включённых файлах. Для поиска только в тексте текущего файла и перехода к первой позиции, где было использовано слово, находящееся под курсором, используйте команду:

gD

Эта команда перехода к определению ("Goto Definition"). Она очень полезна для поиска местных переменных и функций, говоря языком C - "статических". Приведём пример (курсор находится на слове "counter"):

   +->   static int counter = 0;
   |
   |     int get_counter(void)
gD |     {
   |         ++counter;
   +--         return counter;
         }

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

gd

Эта команда позволяет перейти к началу текущей функции и затем пытается найти первое появление имени ниже позиции курсора. На самом деле, эта команда переносит курсор назад к первой пустой строке перед символом "{" в первой колонке, и уже с этой позиции выполняет поиск имени. Приведём пример (курсор на "idx"):

        int find_entry(char *name)
        {
   +->      int idx;
   |
gd |        for (idx = 0; idx < table_len; ++idx)
   |        if (strcmp(table[idx].name, name) == 0)
   +--          return idx;
        }

Следующая глава: Редактирование программ
Авторские права: см. Авторские права