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

понедельник, 28 ноября 2011 г.

vim: TagHighlight и tagbar - немного магии цвета

Про плагин TagHighlight (который раньше назывался ctags_highlighting) я уже писал (см. здесь). C тех пор в TagHighlight многое изменилось, прежде всего сам движок, но кроме этого было добавлено большое число новых фич. Самая главная фича - это возможность использовать разные подсветки для разных проектов. Однако для ранжира подсветок по проектам я по-прежнему использую скрипт MakeVimHlTags - так уж у меня сложилось - который, естественно, тоже обновился (новая версия доступна для скачивания здесь). В данном посте я хочу продемонстрировать другую возможность нового TagHighlight - подсветку буферов вспомогательного типа (FileType) тегами из некоторого проекта. Такими буферами могут быть, например, поля редактирования при коммите svn (в этом случае буфер имеет тип svn). Чтобы понять, что это такое и для чего это нужно представьте, что вы делаете svn commit, открывается ваш любимый редактор vim, вы вводите сообщение об изменениях, и названия объектов тут же подсвечиваются в соответствии с тегами для данного проекта - на мой взгляд это очень удобно.

Другой вспомогательный тип буфера - и тут я плавно перехожу ко второму герою повествования - это тип tagbar. Плагин tagbar - это современная замена taglist. Когда я решил посмотреть, что он из себя представляет, он сразу стал моим фаворитом. Это действительно лучший вариант taglist. Во-первых, у него нет глюков, связанных с переходом между табами. Во-вторых, он представляет информацию о тегах в более систематизированном виде. В-третьих, имеет очень приличный look-n-feel. Ну и то, что он предоставляет собственный FileType, можно использовать для подсветки TagHighlight!

Кстати, для того, чтобы tagbar не сортировал теги по алфавиту, а показывал их в той последовательности, в которой они объявлены в исходном файле, добавьте в .vimrc строку:
let g:tagbar_sort = 0
Итак, вот картинка (кликните для увеличения):


На этой картинке представлено два обычных буфера - исходники на C++, и буфер tagbar (самое правое окно). Все три буфера расцвечены с помощью TagHighlight. Для создания тегов в MakeVimHlTags использовался пропатченный ctags (см. здесь), который я настоятельно рекомендую, так как за время его использования c августа этого года никаких ошибок я не обнаружил, а вот проблемы с пропуском тегов исчезли.

Теперь о том, как добиться подсветки tagbar c помощью TagHighlight. Я по-прежнему предполагаю (по крайней мере так сделано у меня), что мы используем понятие проектов, которые находятся в директории, на которую ссылается переменная окружения $TAGSDIR (лично я установил ее в файле .bash_profile в значение $HOME/opt/tags), и состоят из файлов с тегами ctags и cscope (для TagHighlight эти файлы не нужны - просто они упоминаются в коде, который я приведу ниже), и файла с подсветкой для TagHighlight, который был создан с помощью MakeVimHlTags и имеет имя типа projectname_c.vim, где projectname - имя конкретного проекта. Итак, вот участок кода, который отвечает за запуск подсветок для проектов (вы можете вставить его в ваш .vimrc, лично я положил его в отдельный файл $HOME/.vim-myprojects.vim, который сорсится в конце .vimrc):

" ---- Setting tags and tag highlights
" ----
fun<SID>SetTags(ctags_db, cscope_db)
    if filereadable(a:ctags_db)
        execute 'set tags+=' . a:ctags_db
    endif
    if filereadable(a:cscope_db)
        execute 'cscope add ' . a:cscope_db
    endif
endfun

fun<SID>SetCTagsHl(ctagshl_db, skipft)
    if !exists('g:loaded_TagHighlight')
        return
    endif
    if index(split(a:skipft), &ft) < 0
        "let g:TagHighlightSettings['DebugLevel'] = 'Information'
        let g:TagHighlightSettings['UserLibraries'= [a:ctagshl_db]
        " add 'FileType' to LanguageDetectionMethods to see highlights when
        " committed to svn (to achieve this, this function must be triggered
        " with BufReadPre event), tagbar is also to be highlighted
        let g:TagHighlightSettings['LanguageDetectionMethods'=
                                                \ ['Extension''FileType']
        let g:TagHighlightSettings['FileTypeLanguageOverrides'=
                                                \ {'svn''c''tagbar''c'}
    endif
endfun

fun<SID>SkipCTagsHlForFt(skipft)
    if !exists('g:loaded_TagHighlight')
        return
    endif
    if index(split(a:skipft), &ft) >= 0
        let g:TagHighlightSettings['UserLibraries'= []
        " trigger FileType event forcibly
        exe "set ft=" . &ft
        "ReadTypes
    endif
endfun

let mpr_def_skipft = 'perl sh'

let mpr_tags =
    \ [
        \ ['*/geant4/pnpi/*''pnpi', mpr_def_skipft],
        \ ['*/geant4/src/geant4*''geant4', mpr_def_skipft],
    \ ]

for item in mpr_tags
    let mpr_tagname = item[1]
    let mpr_hltags  = $TAGSDIR."/".mpr_tagname."_c.vim"
    let mpr_ctags   = $TAGSDIR."/".mpr_tagname.".ctags"
    let mpr_cstags  = $TAGSDIR."/".mpr_tagname.".cscope_out"
    if filereadable(mpr_hltags)
        exe "autocmd BufReadPre,BufNewFile ".item[0].
                    \ " call <SID>SetCTagsHl('".mpr_hltags."', '".item[2]."')"
        exe "autocmd BufRead ".item[0].
                    \ " call <SID>SkipCTagsHlForFt('".item[2]."')"
    endif
    exe "autocmd VimEnter ".item[0].
                \ " silent call <SID>SetTags('".mpr_ctags."', '".mpr_cstags."')"
endfor

Функция <SID>SetTags() к нашему разговору не относится - она нужна для загрузки тех самых ctags и cscope файлов проекта, о которых я упомянул выше, а они отвечают не за подсветку тегов, а за навигацию по ним. Интерес представляют функция <SID>SetCTagsHl(), объекты mpr_def_skipft, mpr_tags и цикл for по mpr_tags. Объект-строка mpr_def_skipft определяет, файлы каких типов (в данном случае perl и sh) не должны подсвечиваться TagHighlight. В объекте-коллекции mpr_tags указан полный список всех проектов, которые могут быть загружены из директории $TAGSDIR. В данном случае я утверждаю, что в директории $TAGSDIR (привязка к которой осуществляется ниже - в цикле for) находятся два проекта с именами pnpi и geant4, рабочие директории проектов (которые указаны в списках autocmd BufReadPre, BufNewFile, BufRead и VimEnter цикла for в качестве первого аргумента - через item[0] - и являются триггером для чтения тегов и запуска подсветки TagHighlight) - это */geant4/pnpi/* и */geant4/src/geant4/* соответственно. В качестве третьего параметра в элементах коллекции mpr_tags указан mpr_def_skipft - он будет использован в цикле for для отключения подсветки TagHighlight для файлов типа perl и sh (путем вызова функции <SID>SkipCTagsHlForFt() из autocmd BufRead).

Вызов подсветки tagbar происходит внутри функции <SID>SetCTagsHl() с помощью всего двух объявлений:
        let g:TagHighlightSettings['LanguageDetectionMethods'=
                                                \ ['Extension''FileType']
        let g:TagHighlightSettings['FileTypeLanguageOverrides'=
                                                \ {'svn''c''tagbar''c'}
В первой строке мы говорим, что будем определять нужна ли подсветка TagHighlight как по расширению читаемого файла, так и по заданному для него типу FileType, который устанавливается в недрах движка vim. Во второй строке мы проассоциировали FileType tagbar с FileType c, и это значит, что в окне tagbar появится подсветка, определенная для C-проектов, а это то, чего мы и добивались. Кроме того, мы проассоциировали FileType svn с FileType c - это нужно для подсветки коммитов svn, о чем я говорил раньше.

понедельник, 19 сентября 2011 г.

Цветной ngrep (в продолжение предпоследнего поста)

ngrep - это что-то среднее между tcpdump и wireshark. Он может показаться не таким удобным как wireshark, зато превосходит по удобству использования tcpdump, поскольку основной особенностью ngrep является вывод на экран полезного содержимого пакетов (то, что обычно называют payload), а не всевозможных сетевых деталей. Кроме того, ngrep работает в терминале, а это, на мой взгляд, огромный плюс.

Итак, сначала картинка:


А теперь настройки .hlrc:
snippet ngrep   -191 '^\s*(?:POST|GET)\s+' '^[ITU]\s+' -211 '\[[A-Z]+\]' \
                -120 '^[A-Z]\S+: ' -130 '^#+' \
                -210 '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+' \
                -140 '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}' -190 '^HTTP.*'
и .hl_functions:
function ngrep()
{
    sudo `env which ngrep` $@ | hl -sngrep
}
Чтобы этот пост не получился совсем уж скучным и банальным, ниже я покажу как предоставить права на запуск ngrep обычному пользователю (заметили sudo в функции ngrep?).

Итак, в обычной ситуации обычный пользователь не имеет права на запуск ngrep, поскольку может получить доступ к информации, для него не предназначенной. Но что если нам часто требуется получать доступ к содержимому сетевых пакетов (например, при тестировании какого-нибудь веб-приложения), или мы работаем на домашнем компьютере и доступ к информации о том, что происходит в нашей сетке не только не ослабляет безопасность системы, а наоборот повышает ее? В этом случае нужно добавить себя в список sudoers. Я не стану добавлять записи в файл /etc/sudoers, а создам новый файл (назовем его users) в директории /etc/sudoers.d/. Итак, от имени суперпользователя открываем файл /etc/sudoers.d/users и записываем в него следующие строки:
Cmnd_Alias  NETTASKS = /usr/sbin/ngrep, /usr/sbin/wireshark, /usr/sbin/tcpdump

user1      ALL = NOPASSWD: NETTASKS
Вместо user1 нужно поставить ваше настоящее регистрационное имя. После завершения редактирования файла необходимо изменить его атрибуты доступа:
chmod 0440 /etc/sudoers.d/users
Если этого не сделать, sudo откажется выполнять инструкции данного файла с соответствующей диагностикой. Ввиду данного изменения, при дальнейшем редактировании /etc/sudoers.d/users придется подтверждать внесение изменений при записи на диск (в vim это делается командой :w!), но лучше всего воспользоваться командой visudo -f /etc/sudoers.d/users, которая знает, как работать с файлами sudoers

Кроме права на запуск ngrep я наделил пользователя user1 правами на запуск wireshark и tcpdump. Теперь для запуска этих двух команд данный пользователь может ввести sudo wireshark и sudo tcpdump, а для запуска ngrep достаточно просто ввести ngrep, поскольку в интерактивной среде ngrep - это функция, записанная нами в файл .hl_functions, которая через sudo вызывает настоящий ngrep.

Заметим также, что ввод пароля пользователем при запуске этих команд не требуется, так как в файле /etc/sudoers.d/users перед задачами NETTASKS  для пользователя user1 указана опция NOPASSWD. Кроме того, слово ALL указывает на то, что пользователь user1 имеет право запускать команды с любого хоста. В целях безопасности, наверное имеет смысл заменить ALL на localhost, но в этом случае user1 не сможет запускать команды из NETTASKS удаленно.

Update. Не сразу заметил, что функция ngrep() в приведенном выше виде,  работает неверно для многих достаточно типичных случаев, в частности, когда паттерн для поиска пуст (например, в случае ngrep '' 'tcp port 80'). Проблема в том, что переменная $@ внутри ngrep() удаляет все кавычки, которые в случае пустого паттерна (или паттернов, содержащих пробелы) имеют значение. К счастью, если переменную $@ взять в двойные кавычки, то информация о пустых аргументах и аргументах, содержащих пробелы, не теряется, и ее можно восстановить. Я не знаю, существует ли в sh или bash штатный способ вернуть кавычки в этом случае на место, скорее всего нет, поэтому я написал отдельную функцию quote_args_if_needed() и поместил ее в начале файла .hl_functions.
function quote_args_if_needed()
{
    local result=''
    for i in "$@" ; do
        if [[ -z "$i" || "$i" =~ ' ' ]] ; then
            result=$result" '"$i"'"
            continue
        fi
        result=$result' '$i
    done
    echo $result
}
Функция проходит по переданным ей аргументам и добавляет их (через пробел) к изначально пустой переменной result. Если очередной аргумент пуст, либо содержит пробелы, то он обертывается одинарными кавычками. Мы будем передавать в quote_args_if_needed() значение "$@". Переменная $@, взятая в двойные кавычки представляет собой правильный массив переданных аргументов, в котором те аргументы, которые были заключены в кавычки, представляют единое целое (в отличие от бескавычечной версии, в которой все содержимое переданных аргументов разбивается по пробельным символам). Теперь нужно использовать новую функцию в ngrep():
function ngrep()
{
    eval sudo `env which ngrep` `quote_args_if_needed "$@"` | hl -sngrep
}
Как видите, мне пришлось обернуть все в eval. Это потому, что кавычки для аргументов, которые нам удалось вернуть с помощью quote_args_if_needed(), будут переданы без eval в `env which ngrep` дословно, и он будет ругаться на syntax error. Команда eval очистит нашу строку от кавычек, и при этом сохранит пустые аргументы и аргументы, содержащие пробелы.

суббота, 5 февраля 2011 г.

Подсветка в vim на основе тегов ctags (perl скрипт прилагается)

Из серии было и стало (картинки кликабельны):


Итак, наша задача - обеспечить подсветку в vim не только ключевых слов, но, по возможности, всего исходного кода: собственных функций, классов, пространств имен, переменных и т.д. Эта задача хорошо решается в vim-скрипте ctags_highlighting. Как ясно из названия, этот скрипт использует базу данных ctags (строит сам или берет уже готовую). В состав ctags_highlighting входит питоновский скрипт mktypes.py, который проделывает основную работу, главным результатом которой является vim-скрипт со списком групп подсветки, в случае языков C и C++ этот файл будет называться types_c.vim. Группы подсветки в этом файле не являются стандартными, соответственно используемая цветовая схема должна их поддерживать. Автор ctags_highlighting предлагает собственную цветовую схему bandit. При использовании другой цветовой схемы, ей нужно "сообщить" о новых группах подсветки. В разных схемах это делается по-разному. Я использую цветовую схему xterm16. Для того, чтобы xterm16 знала о новых группах подсветки, я добавил в нее следующие определения:
    "my highlight groups for ctags_highlight
    call s:hi( 'Namespace'   , 'none', 'cyan'      , 'none'      )
    call s:hi( 'Function'    , 'none', 'cyan'      , 'none'      )
    call s:hi( 'DefinedName' , 'none', 'cyan'      , 'none'      )
    call s:hi( 'EnumerationValue'  , 'none', 'cyan'      , 'none'      )
    call s:hi( 'EnumeratorName', 'none', 'cyan'      , 'none'      )
    call s:hi( 'Member'      , 'none', 'cyan'      , 'none'      )
    call s:hi( 'Union'       , 'none', 'cyan'      , 'none'      )
    call s:hi( 'GlobalVariable', 'none', 'cyan'      , 'none'      )
    call s:hi( 'LocalVariable', 'none', 'cyan'      , 'none'      )
    call s:hi( 'GlobalConstant', 'none', 'cyan'      , 'none'      )
    call s:hi( 'Conditional' , 'none', 'cyan'      , 'none'      )
    call s:hi( 'Repeat'      , 'none', 'cyan'      , 'none'      )
    call s:hi( 'Label'       , 'none', 'cyan'      , 'none'      )
    call s:hi( 'Exception'   , 'none', 'cyan'      , 'none'      )
    call s:hi( 'Operator'    , 'none', 'cyan'      , 'none'      )
    call s:hi( 'PreCondit'   , 'none', 'cyan'      , 'none'      )
    call s:hi( 'Include'     , 'none', 'cyan'      , 'none'      )
    call s:hi( 'Macro'       , 'none', 'cyan'      , 'none'      )
    call s:hi( 'StorageClass', 'none', 'green'     , 'none'      )
    call s:hi( 'Class'       , 'none', 'green'      , 'none'      )
    call s:hi( 'Structure'   , 'none', 'green'      , 'none'      )
    "my highlight groups from after/syntax/c.vim
    call s:hi( 'Delimiter'   , 'none', 'green'      , 'none'      )
    call s:hi( 'Boolean'     , 'none', 'green'      , 'none'      )
    "my ColorColumn highlight
    call s:hi( 'ColorColumn' , 'none', 'none'      , 'cyan'      )
Эти объявления следует разместить где-нибудь среди похожих объявлений в ~/.vim/colors/xterm16.vim. Кроме объявлений, связанных с ctags_highlighting, здесь добавлены группы подсветки для скрипта after/syntax/c.vim, а также подсветка для вертикальной колонки, которая появилась с выходом в vim 7.3 и в xterm16 пока отсутствует. Конкретные цвета (cyan, green и т.д.) здесь не важны, так как я переопределяю их в файле .vimrc:
syntax on
let xterm16_brightness = 'high'
let xterm16_colormap = 'soft'
let xterm16fg_Comment = 'grey'
let xterm16fg_Identifier = '#87ffaf'
let xterm16fg_Namespace = '#df875f'
let xterm16fg_Function = '#87ffaf'
let xterm16fg_Statement = '#87afdf'
let xterm16fg_EnumerationValue = '#5fafdf'
let xterm16fg_EnumeratorName = '#00afdf'
let xterm16fg_GlobalConstant = '#87875f'
let xterm16fg_GlobalVariable = '#87875f'
let xterm16fg_DefinedName = 'purple'
let xterm16fg_LocalVariable = '#00df87'
let xterm16fg_Class = '#afff87'
let xterm16fg_Structure = '#dfffaf'
let xterm16fg_Member = '#00af87'
let xterm16fg_Conditional = '#5fafff'
let xterm16fg_Repeat = '#5fafff'
let xterm16fg_Label = '#5fdfff'
let xterm16fg_Exception = '#ff5f87'
let xterm16fg_PreCondit = '#af5faf'
let xterm16fg_Include = '#df5f00'
let xterm16fg_Macro = 'purple'
let xterm16fg_StorageClass = '#5faf5f'
let xterm16fg_Operator = '#ffafaf'
let xterm16fg_Delimiter = '#5fffdf'
let xterm16fg_Boolean = '#87afdf'
let xterm16bg_ColorColumn = '#949494'
if $DISPLAY != '' && !has('gui_running')
    let xterm16bg_Normal = 'none'
endif
colo xterm16
Теперь для обновления подсветки тегов можно воспользоваться командами, предоставляемыми плагином ctags_highlighting. К сожалению, на мой взгляд, это не самое лучшее решение. И причин тому несколько. Прежде всего, я не хочу хранить файлы типа tags и types_c.vim в рабочей директории. Во-вторых, в ctags_highlighting нет выраженной модульности, в частности, если я работаю с некой библиотекой (или несколькими библиотеками), то хочу, чтобы теги используемой библиотекой и моего рабочего кода были четко разделены. Это позволит быстро перестраивать теги для рабочего кода, не изменяя, а только добавляя в файл подсветки теги из библиотеки. В-третьих, ctags часто ошибается, что приводит к появлению псевдотегов типа const, а также переопределению объявлений используемой библиотеки как локальных переменных рабочего кода (если установлено использование локальных переменных).
Представленный здесь скрипт основан на использовании mktypes.py и призван обойти указанные недостатки. Для просмотра опций достаточно набрать имя скрипта MakeVimHlTags без аргументов. В скрипте используется понятие проекта; проекты расположены в директории ~/opt/tags/. В сущности, проект напрямую соответствует модулям в том смысле, о котором говорилось выше. То есть, если в собственном исходном коде используются библиотеки lib1 и lib2, и есть желание подсветить теги из этих библиотек, то можно создать два проекта lib1 и lib2. Для этого следует перейти в каталоги с исходным кодом этих библиотек и выполнить команды:
MakeVimHlTags -r -e const,true,false -p lib1
и
MakeVimHlTags -r -e const,true,false -p lib2
Скорее всего библиотеки находятся в системных директориях и запускать скрипт придется от рута, поскольку mktypes.py создает временные файлы tags и types_c.vim, которые будут удалены по окончании работы скрипта. К счастью, обновлять теги системных библиотек достаточно только при их обновлении. Опция -r означает, что теги будут искаться во всех поддиректориях, начиная с указанных (или как здесь - с текущей). В опции -e перечислены через запятую теги, которые следует игнорировать. В моем случае ctags пытался переопределить слова const, false и true, поэтому я указал, что их следует игнорировать. Опция -e поддерживает простейшие шаблоны, в которых символ "точка" представляет собой последовательность любых символов кроме пробельных; точка может находиться в начале или конце шаблона. Например шаблон "Lib1." обозначает все теги, которые начинаются с "Lib1". Опция -p указывает название проекта.
Теперь следует создать теги для собственного исходного кода. Пусть наш проект называется Proj и использует библиотеки lib1 и lib2. Для того, чтобы добавить в проект теги из другого проекта используется опция -a:
MakeVimHlTags -r -l -a lib1,lib2 -e Lib1.,Lib2. -p Proj
Опция -l нужна для того, чтобы ctags сгенерировал теги для локальных переменных. Это хорошая идея для собственного кода, но плохая - для кода библиотек, поэтому мы не использовали ее для построения тегов проектов lib1 и lib2. В принципе, в случае с C++, теги для локальных переменных не всегда хороши (в частности, если в коде часто используется инициализация в стиле конструктора), поэтому использовать опцию -l следует с осторожностью. С помощью опции -e мы удаляем все теги, которые начинаются с "Lib1" и "Lib2": поскольку в нашем проекте используются имена из lib1 и lib2, то, особенно если включена опция -l, возможны ложные срабатывания ctags для таких имен.
После выполнения трех команд в директории ~/opt/tags/ появятся 3 новых файла: lib1_c.vim, lib2_c.vim и Proj_c.vim. Эти файлы содержат команды для vim, которые необходимы для подсветки тегов, при этом файл Proj_c.vim будет содержать теги из lib1_c.vim и lib2_c.vim.
Осталось самое простое: сделать так, чтобы при открытии файла из рабочей директории проекта Proj скрипт Proj_c.vim выполнялся автоматически. Для этого в файле .vimrc можно ввести следующую команду:
if (filereadable($HOME . "/opt/tags/Proj_c.vim"))
    autocmd BufRead,BufNewFile */part/of/path/to/Proj/* 
        \ execute 'source' . $HOME . "/opt/tags/Proj_c.vim"
endif
где /part/of/path/to/Proj/ - часть относительного пути к рабочей директории проекта Proj.
Для обновления тегов подсветки достаточно выполнить последнюю приведенную команду MakeVimHlTags, при этом теги проектов lib1 и lib2 не перестраиваются, а просто включаются из готовых файлов.

Скрипт MakeVimHlTags можно взять здесь.