Пользователь
0,0
рейтинг
21 июля 2013 в 09:32

Разработка → Пока Vim пишет код за Вас…

VIM*, Python*


Не буду долго распинаться на тему того, насколько велик и могуч Vim — это уже давно и достоверно известно. Кто-то даже утверждает, что «Практически любая строка латинских букв является синтаксически верной командой для vi».

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



Действующие лица



Собственно, их всего два — Vim и Python. Но взаимодействие этих компонент приводит к тысяче изменений и десяти тысячам превращений. И вот однажды, когда меня в очередной раз посетило желание перенести часть моей повседневной активности в Vim, я взялся за написание плагина для чтения постов с главной страницы reddit. Идея предельно проста — вытягивать список постов в json формате и отображать их в форматированном виде в буффере редактора. Ну и, конечно, эта задумка носила частично образовательный характер.

В качестве доступных вариантов для написания плагина существуют Vimscript, Python, Ruby. Не имею возможности сравнить их достоинства и недостатки, скажу лишь, что Python был выбран просто потому, что я с ним хорошо знаком.

Плагин: Начало



Самый простой плагин для Vim представляет собой файл с расширением *.vim — но лучше так не делать) Создадим директорию с плагином, которую разместим в .vim/bundle/ (используем vundle). Внутри заведем директорию plugin с исходниками и добавим строку Bundle 'vim-reddit'. После этих телодвижений плагин будет запускаться при старте Vim.

Основой плагина будет являться все же код, написанный на Vimscript (reddit.vim). В нем будут содержаться некоторые настройки, подсветка отображаемых постов, а также обертка для вызова Python кода. Вкратце расскажу об основных моментах и «правилах хорошего тона» (хотя я и сам их порой игнорирую).

Т.к. в коде мы будем использовать Python, необходимо проверить, что Vim собран с поддержкой этого языка:

if !has('python')
    echo "Error: Required vim compiled with +python"
    finish
endif


Затем необходимо проверить, не был ли загружен плагин повторно. Если нет — открыто заявляем миру о своем присутствии:

if exists('g:vim_reddit_module')
    finish
endif
let g:vim_reddit_module = 1


Python модуль


Затем начинается основное действо. Для начала, раскрою страшный секрет запуска кода на Python из Vimscript:

function! Reddit()

python << EOF
# some function
main()
EOF

endfunction


Внутри блока можно расслабиться, и вызвать пару питоновских библиотек) Создадим модуль reddit.py (__init__.py подразумевается), который будет выполнять всю работу по загрузке json. Но просто так подключить его не получится, т.к. питон не сможет найти этот модуль. Для выхода из этой трагичной ситуации можно добавить путь к модулю в sys.path:

import sys, vim
sys.path.append(vim.eval("expand('<sfile>:p:h')"))


Полагаю, вы уже догадались, что метод eval производит вычисление команды Vim и возвращает результат) После этого можно со спокойной душой импортировать модуль:

from reddit import main
main()


Теперь плавно перейдем к тому безобразию, которое происходит в reddit.main(). Опять же, пропущу никому неинтересные подробности загрузки json и сразу пройдусь по особенностям. А из особенностей здесь, пожалуй, только работа с буффером. Дело в том, что текущий буффер, в который будем добавлять загруженные посты, представляет собой простой массив строк. Поэтому, очистка выглядит вот так:

del vim.current.buffer[:]


а добавление строки вот так:

vim.current.buffer[0] = "Reddit front page "
vim.current.buffer.append(20*"=" + " ")


Более ничего специфического в модуле нет. Отмечу только свое острое желание сделать плагин гибким и настраиваемым. Это привело к появлению конфигурационного файла default.json, который помимо источника json также содержит указания, какие именно данные из него выбирать (простым маппингом). Поэтому, в теории, возможно приспособить данный плагин для чтения любой новостной ленты в json формате с похожей структурой (т.е. набор постов с определенными атрибутами).

Немного о мелочах



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

setlocal nomodifiable
setlocal buftype=nofile
call s:reddit_syntax()


В качестве результата имеем нередактируемый буффер. Функция reddit_syntax раскрашивает содержимое во все цвета радуги. Она была подсмотрена вот здесь, за исключением одной группы, которую я добавил исключительно для себя:

syntax match title /^.\{-1,} /
highlight title gui=bold guifg=yellowgreen


Группа называется title и описывается соответствующей регуляркой, а затем выделяется bold шрифтом.

Заключение



Плагин прост и малофункционален как (подобрать соответсвующее сравнение), но тем не менее основные цели он достигает — я (и, надеюсь, некоторые из читателей) прозрел относительно изменений и допиливаний Vim под свои скромные нужды. Ну и к тому же теперь я могу читать reddit из Vim)

Код плагина выложен в моем репозитории, его чрезвычайно просто установить с помощью vundle.

P.S.: Оказывается, утренние посты даются гораздо сложнее)
erthalion @erthalion
карма
25,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (28)

  • +3
    Да уж… Vim был бы отличной операционной системой, если бы в нем был текстовый редактор
    • +35
      Путаете с емаксом.
      • –3
        Ну, судя по таким постам, Вим скоро его догонит.
        • +1
          Не раньше, чем кто‐то научит его работать с несколькими нитями и получит нормальное API для различных вещей. Что будет очень и очень нескоро.

          Сейчас его при некоторой удаче можно уронить стандартной функциональностью: изменением размера окна (gvim, не vim) и клиент‐серверным взаимодействием. Как это нормально исправить непонятно: слишком много глобальных переменных.
          • 0
            Нити? Последний раз я видел эту терминологию в известном трактате Терренса Чана, где он еще сокеты гнёздами называл. Я не имею желания придраться, просто хочется узнать, почему именно нити, а не потоки?
            • 0
              Думаю, Терренс Чан всё же называл сокеты sockets, а потоки — threads. А дальше уже на вкус переводчика.
              Можно потоки и фибры, а можно — нити и волокна. И в последнем варианте сразу ясно, что из чего состоит.
              • 0
                Прошу прощения. Конечно я имел в виду перевод. И все же, на мой взгляд, потоки и волокна звучат традиционнее нитей и фибр. Но это моё мнение, никому не навязываю.
            • 0
              Термин «нити» обычно используют люди, которым приходится работать одновременно и с нитями и с потоками: потоки это streams, например fstream, stringstream и т.д., а нити это threads.
  • +1
    if exists('vim_reddit_module')
    finish
    endif

    Переменная должна быть глобальной — g:vim_reddit_module
    Иначе это не страхует от повторной загрузки плагина.
    • 0
      да, опечатка — благодарю за находку)
      • 0
        Ошибки не было — нечего исправлять, см. коммент ниже. Я, кстати, использую для страховки локальные для скрипта переменные (s:): если кому‐то надо отключить конкретное дополнение, то для этого есть менеджеры путей (pathogen) или дополнений (VAM, Vundle, …), а засорять глобальную область видимости тем, что не является абсолютно необходимым я не люблю. Тем более, что отключение загрузки можно легко добавить в мой framework в виде одной глобальной переменной на все дополнения со списком отключённых, а не пачки по одной переменной на дополнение, если кому‐то потребуется.

        Кстати, в vim-7.4a можно не трогать sys.path. Только модуль надо класть не в plugin/, а в python2/, python3/ или pythonx/ (для второй, третьей и обеих версий Python соответственно).
        • 0
          s: использовать точно плохо, т.к. кто-то может по ошибке положить скрипт в два места (в plugins/ и bundle/.../ например)
          • 0
            У меня при этом будет куча ошибок и кому‐то придётся удалить скрипт из одного места: framework предполагает регистрацию всех внешних интерфейсов, а также принципиальное неиспользование function! и command! (именно с восклицательными знаками, а не неиспользование вообще). Такое поведение гораздо лучше, чем притворяться, что ошибок нет, а потом узнавать, что дополнение было обновлено в одном месте, а загружается из другого.

            При использовании только событий кучи ошибок может и не быть, но необходимо будет хотя бы одно предупреждение о том, что файл с таким именем уже загружен. (Под именем следует полагать часть полного пути файла без runtimepath и .vim и с компонентами пути, разделёнными /, независимо от используемой ОС. Например, plugin/aurum.)
            • 0
              По опыту сидения в issue tracker’е powerline, который может быть установлен (и зачастую устанавливается) в два места: в систему с помощью pip и в .vim/bundle, могу сказать, что замалчивание такой ошибки (которое здесь (в powerline) происходит), приводит к появлению кучи народа именно с проблемой обновлённого только в одном месте powerline. Если вы ожидаете, что ваше дополнение будет положено в два места, ни в коем случае не используйте такой простой guard, по крайней мере, в одиночку. Ни s:, ни g:. У меня вылезают ошибки, но за счёт frawor — то есть guard уже не используется в одиночку.

              С g:, если вы заботитесь о такой ситуации, надо сохранять expand('<sfile>') в переменную и проверять соответствие при запуске (то есть, сначала if exists(), внутри if expand('sfile') isnot# g:… с сообщением об ошибке).

              Впрочем, я не видел реальных пользователей с такой проблемой.
    • +2
      Она и так глобальная. Локальные для файла переменные начинаются с s:. И страхуют от загрузки файла, поскольку эта область видимости не очищается. Без префикса переменные локальны только внутри функции, а finish внутри функции не работает.

      There are several name spaces for variables. Which one is to be used is
      specified by what is prepended:

      (nothing) In a function: local to a function; otherwise: global


      vimhelp.appspot.com/eval.txt.html#internal-variables
      • 0
        Ок, согласен.
  • +8
    «Vim пишет код за Вас…» — это действительно так? как с ним работать?
    в остальном статья осталась непонятной. что же делает ваш плагин? зачем он нужен?
    • –3
      Возможно, статья получилась немного сумубрной, но в ней описано все, что я хотел сказать. Плагин в данном случае не есть самоцель — через него я просто демонстрирую (и разбираюсь сам), как реализовать в vim нужную фичу на python.
      Если же вам непонятно в буквальном смысле, что делает этот плагин — он отображает посты из reddit на странице редактора. Это маленькая приятность, которую я давно хотел для себя сделать. Собственно, поэтому он и нужен — в первую очередь, мне)
      • +5
        Спасибо, что вы выкладываете в свободный доступ все, что вам удалось реализовать и рассказываете об этом сообществу.
        Сочтите дальнейшее как конструктивную критику и желание получить качественно поданный материал.

        1.Вместо кричащих заголовков «Vim пишет код за Вас…» читателю/пользователю было бы интереснее узнать, что же реально делает ваш плагин.
        2.Вместо картинки из мультфильма стоило бы скриншотами показать, как было и как стало. Или, что лучше, сделать видео на ютьюбе.
        3.Установка и мелочи — под кат. (Не всем же это надо, и не все будут это устанавливать. Пожалейте чужое время)
        4.Поменьше метафор и аллегорий. Приятнее читать, когда все просто и ясно. Мы же не художественную литературу читаем.

        Спасибо.
        • –1
          5.Если это нужно только и в первую очередь вам, то может не стоит лишний раз хвастаться и выкладывать «очередной суперполезный плагин» на хабр? или, что лучше, сделать «сей скромный трактат» читаемым и полезным?

          Спасибо.
          • 0
            Если вам не интересно как работать с буферами в виме то это всем неинтересно?
        • 0
          Видео о том как в буфере появляется список новых постов из reddit'a? Вы серъеёзно?
  • 0
    >"(подобрать соответсвующее сравнение)"

    Штопор?
  • +5
    А я наивный… думал что тут реально что-то крутое будет, что перевернет мое представление о текстовом редакторе и покажет как он за меня пишет нужный мне код. Эх
    • 0
      Я думал о каком‐то очередном конкуренте snipmate/XPtemplate/… Тысячи их, но остальные далеко не так известны.
  • 0
    Кстати, если я не ошибаюсь, у вас каждый раз при вызове :Reddit в sys.path добавляется путь. Зачем?

    И ещё: я не вижу, где вы создаёте новый буфер.

    Вообще, если требуется показать какие‐либо данные я всегда использую не команду, а событие BufReadCmd и псевдо‐протокол (к примеру,
    augroup Reddit
        autocmd! BufReadCmd reddit:// :call Reddit()
    augroup END
    ) + ещё, возможно, FileReadCmd: на случай, если хочется использовать :read reddit://. Теоретически это даст возможность восстановления таких буферов при загрузке сессий. Правда, я не использую последние.

    А всю подсветку правильно было бы убрать в syntax/reddit.vim и использовать set ft=reddit. Не надо писать велосипеды для обработки выключенного синтаксиса. И не надо бояться множества мелких файлов.
    • 0
      Кстати, если я не ошибаюсь, у вас каждый раз при вызове :Reddit в sys.path добавляется путь. Зачем?

      Это недосмотр — конечно, надо добавлять только при запуске плагина.

      И ещё: я не вижу, где вы создаёте новый буфер.

      Я использую текущий буфер — просто очищаю его и заполняю заново. Возможно это не самый лучший путь, скорее это — первое, что пришло в голову и было реализовано.

      А всю подсветку правильно было бы убрать в syntax/reddit.vim и использовать set ft=reddit. Не надо писать велосипеды для обработки выключенного синтаксиса. И не надо бояться множества мелких файлов.

      Согласен. Судя по всему, мне придется внести еще кучу мелких и не очень исправлений, т.к. в создании плагинов к Vim я очевидный новичок. Благодарю за замечания — они чрезвычайно полезны для меня)
      • 0
        Я использую текущий буфер — просто очищаю его и заполняю заново. Возможно это не самый лучший путь, скорее это — первое, что пришло в голову и было реализовано.
        Использование текущего буфера — это то, что вы должны делать с BufReadCmd. При использовании команды надо его всё же создавать, иначе область применения команды ограничиться либо командой‐обвязкой, которая его‐таки создаст (и добавит ненужный уровень вложенности), либо запуском через vim +Reddit. То есть можно просто взять вашу функцию и мой код с BufReadCmd и оставить так.

        Кстати: у BufReadCmd есть ещё преимущества: можно взять и написать tabedit reddit:// и не париться созданием кучи команд на все случаи: когда пользователь захочет видеть посты из reddit в новой вкладке, новом окне сверху/слева/справа/снизу, в текущем окне и т.д.

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