вторник, 27 января 2009 г.

Пару слов о vim и его фолдинге.

История эта началась с небольшого поста уважаемого tengu-crow, в котором я откментировал, что тоже есть такие идеи и методы использования. Собственно для того, что бы понять о чем идет дальше речь, желательно прочесть то, о чем шла речь в том посте + коментарии мои и tengu-crow + последующие его заметки и наши общие коментарии под общим названием в его ЖЖ "Афигено большой файл" тут, тут, и тут. :) И так речь идет о фолдинге в vim по просьбе вышеупомянутого френда. :)

Я не являюсь сторонником использования русских терминов и абревиатур, как например НЖМД - накопитель на жестких магнитных дисках, вместо хард драйв или просто хард. И поэтому люблю использовать устоявшиеся английские словоформы, порой даже записанные русскими буквами. Они, как правило более лаконичны, емки и облегчают понимание с иностранными коллегами. Так что уж простите, но я буду применять в тексте не "складки", а английское "фолдинг".

Небольшое лирическое отступление.

Когда программисты пишут программы, то довольно удобно, что бы все функции были "свернуты", то есть мы видели только их заголовки, а внутрь попадали только тогда, когда нам это действительно надо. Это напоминает лист бумаги, часть которого сложена и как бы скрыта от глаз, но в любой момент ее можно развернуть и глянуть что внутри. Данный подход называется фолдингом. Фолднг в vim бывает 3-х видов:
  • manual - ручной
  • indent - большая величина отступа означает большую глубину складки
  • expr - для определения складок используется выражение
  • syntax - складки определяются в соответствии с правилами подсветки синтаксиса
  • diff - специальный режим складок для отражения различий в файлах
  • marker - складки определяются с помощью специальных маркеров в тексте
В нашем случае я попробовал использовать режим expr, так как из всех методов более всего подходили 3: expr, syntax и marker. С маркерным методом я пободался, но так и не смог заставить его работать так, как я хочу, поэтому я его забросил, во всяком случае пока. С синтаксисом я вообще пока не разбирался, хотя и надо бы. ;) Поэтому остался метод "регулярных выражений", которые я очень люблю еще с первого знакомства с perl. :)

Далее лирика, но уже без отступлений. ;)

И так, в нашем афигено большом файле есть темы и заметки. И очень не плохо было бы, сворачивать все наши записи, оставляя только заголовки от них. С методом мы уже определились. Так что поехали. Далее привожу не весь, но часть конфига, дабы не загружать повествование лишними деталями. Для тех, кто не в курсе, коментарии в конфигах vim начинаются со знака ковычек (").
vim ~/.vimrc
;)

Собственно исторически сложилось, что файл со всякими записями у меня назывался *.clipboard, поэтому для его открытия я и сделал функцию.

"вызываем функцию при открытии файла
autocmd BufEnter *.clipboard silent exe LoadClipboardFile()
"
сама функция
function! LoadClipboardFile()
"
покажем номера строк, люблю я так ;)
set number
"
выберем метод фолдинга
set foldmethod=expr
"
а тут самое интересное: вычисление фолдинга по маскам, но об этом позже
set foldexpr=getline(v:lnum)=~'=end=---$'?'s1':getline(v:lnum)=~'^---=====\\s'?'a1':-1
"
заголовок, тоже опишу ниже
set foldtext=v:folddashes.substitute(getline(v:foldstart),'^---=====\\s\\\|\\s=====---$','','g')
"отступ от края, в котором будут показываться уровни фолдинга
set foldcolumn=3
"
ну это чисто для вставки даты в текст по нажатию F2
map <F2> :r!date '+\%F \%H:\%M \%S s'<CR>
imap <F2> <Esc><F2>
"
а это для создания новой записи по нажатию F4
map <F4> o---===== <Esc>zo<F2>kJA =====---<CR>---===== <F2>kJA =end=---<Esc>k^/=====-<CR>h
endfunction


И так каждая запись у меня имела вид:

---===== <дата опционально> тема =====---

текст
---===== <дата опционально> тема =end=---

Соотеветственно строка

foldexpr=getline(v:lnum)=~'=end=---$'?'s1':getline(v:lnum)=~'^---=====\\s'?'a1':-1


делает следующее:
  1. берем текущую строку (getline)
  2. если она оканчивается на '=end=---$', то возвращаем s1, что означает конец текущего фолда (знак =~ - означает если содержит)
  3. если нет, то оцениваем ее начало на наличие '^---=====\\s', и если так, то возвращаем начало нового фолда (a1)
  4. если же нет, то возвращаем -1, что значит, что уровень фолда не поменялся
Комбинация: <условие>?<результат если верно>:<результат если ложно> я думаю известно многим программистам. ;) Так что тут все довольно понятно.

Далее делаем еще одну косметическую операцию, а именно надпись на фолде:

set foldtext=v:folddashes.substitute(getline(v:foldstart),'^---=====\\s\\\|\\s=====---$','','g')


Тут вообще практически все понятно: устанавливаем заголовок фолда как строку, взятую из начала фолда и удалив из нее "---===== " - вначале и " =====---" - в конце, что бы не мешали. ;)

Ну можно еще немного объяснить, как работает вставка новой записи, хотя там все просто - чисто последовательность команд vim-а:

map <F4> o---===== <Esc>zo<F2>kJA =====---<CR>---===== <F2>kJA =end=---<Esc>k^/=====-<CR>h


  1. o---===== - вводимновую строку, печатаем начало заголовка и выходим в командный режим
  2. zo - раскрываем фолд, ибо обнаружив начало, наш фолд схлопывается
  3. F2 - на ней у нас висит мап ввода даты. ;)
  4. Но дата вбилась строкой ниже! Поэтому поднимаемся обратно и присоединяем (J) следующую строку к нашей.
  5. A - начинаем в конце ее добивать окончание заголовка (=====---)
  6. - переход на новую строку
  7. Далее все повторяется для закрывающего заголовка (=end=---)
  8. k - поднимаемся вверх
  9. ^ - переходим в начало строки
  10. /=====- - ищем точку после даты на первом знаке равно
  11. И делаем шаг влево, что бы оказаться сразу же за введенной датой и быть готовыми вводить новый заголовок. :)
Если лень - то нажмем просто "o" и будем в новой строке вводить текст, в заголовке останется только дата. Если нет - то напишем текст, который и будет отображаться в заголовке фолдинга.


Свернутый файл выглядит так:



Развернутый:



И далее: