Пишем простые расширения для VS Code, автоматизируя задачи командной строки

740dad3ed88bedcbef392543eddb48e9.jpg

VS Code — популярный редактор исходного кода. Его используют разработчики многих компаний, в том числе и мы в МойОфис. Мы пользуемся им для написания кода (включая сборку, тестирование и отладку), но при этом часто упускаем из виду, что благодаря встроенным возможностям по разработке расширений, VS Code можно легко превратить в средство автоматизации практически любых повседневных задач в нашей работе. Например, тех, которые мы привыкли рутинно делать в командной строке.

Расширения в VS Code пишутся на Typescript, который достаточно просто освоить. Однако существенным препятствием является то, что в документации часто нет ответов на вопросы, которые возникают при реальной разработке.

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

Привет, Хабр! Меня зовут Эдуард Боровицкий, я ведущий разработчик в МойОфис. Сегодня мы создадим простое расширение в VS Code и дополним его возможностью показывать иерархическую структуру пакетов для вашего проекта, если он использует conan. Для подобного в VS Code обычно применяют древовидные представления (treeview). Их, как и некоторые другие средства, например, веб-панели и панели вывода, легко использовать для упрощения взаимодействия разных частей расширений и повышения наглядности.

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

Пояснение

Этот пример — лишь часть одного из расширений, которое я написал для облегчения работы с некоторыми специфическими проектами, использующими в том числе conan, С++ и другие средства, которые мы разработали во внутренних проектах.

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

К началу разработки мне было известно несколько расширений для работы с conan в VS Code. К сожалению, они нам не подошли по разным причинам, да и скорее были похожи на прототипы расширений, чем на что-то пригодное для использования.

А теперь, пройдём все основные шаги по созданию расширения.

Создание каркаса расширения

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

Согласно официальному руководству по разработке, в начале нам нужно установить генератор кода (с использованием Yeoman). Для этого воспользуемся командой:

$ npm install -g yo generator-code

Теперь мы можем запустить процесс создания базового каркаса:

# ? What type of extension do you want to create? New Extension (TypeScript)
# ? What's the name of your extension?conan-pkg-treeview  
### Press  to choose default for all options below ###

# ? What's the identifier of your extension? helloworld
# ? What's the description of your extension? 
# ? Initialize a git repository? Yes     (или No – на ваше усмотрение)
# ? Bundle the source code with webpack? No
# ? Which package manager to use? npm

# ? Do you want to open the new folder with Visual Studio Code? Open with `code`

Как видите, заполнить поля можно сразу, либо потом исправить в коде и настроечных файлах — об этом расскажу подробнее далее.

Откроем только что созданный проект в VS Code. Скорее всего вы увидите следующее:

d4ed9eeed30fe625169d476d24cdd291.png

Вы можете удалить различные файлы с расширениями ».md», убрать подкаталог с тестами или добавить что-то своё. Самое главное, на что стоит здесь обратить внимание — файлы в каталоге src/, а именно extension.ts.

46f423a02063e4d3bfe10d7bd19b6216.png

Давайте откроем его для редактирования — в файле будет много комментариев, но довольно мало кода.

Теперь познакомимся поближе с этим кодом (в extension.ts), который был создан автоматически. Первая строчка кода (не комментария) — это импорт символов из модуля vscode. В этом модуле мы можем найти почти всё, что нужно для создания расширений в VS Code.

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

Далее мы видим, что с помощью vscode.commands.registerCommand создаётся первая простая команда, которая позволит нам показывать информационное сообщение Hello World from conan-pkg-treeview!

На что еще стоит обратить внимание? Созданная команда должна быть передана в подписки — команда context.subscriptions.push (disposable). В самом конце можно заметить функцию deactivate, в ней как раз можно воспользоваться сохранёнными в context.subscriptions сущностями. Как вы, наверное, догадываетесь, она будет вызвана в процессе остановки нашего расширения.

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

$ npm i

Важной частью проекта является файл package.json. В нём можно описать команды (и не только) в разделе contributes, через добавление в подраздел commands элемента command — как, например, на картинке:

1402ae82ce3b4da8d976418bf4b8db9f.png

Чтобы увидеть результат, запустим проект через главное меню Run→Start Debugging или нажатием F5.

После этого, при появлении нового окна VS Code, используем комбинацию клавиш Ctrl-Shift-P для вызова нашей команды. Мы видим выпадающий список из доступных команд. Выбираем HellWorld (достаточно начать набирать лишь часть название команды, смотрите выше о добавлении команды в package.json):

f71e18dd1570b23d9b2e6c374e640d2f.png

И видим результат:

769caceeb984c69223e7485b5f49c56a.png

Поздравляем! Вы смогли написать и запустить своё первое расширение для VS Code!

Пара вещей на которые вы, возможно, обратите внимание. После первого запуска с отладкой вы, скорее всего, увидите что-то, похожее на следующую картинку:

ba1b225c5f112f4eb1856d272b241a50.png

Да-да, как и было сказано, вы можете легко (как правило) отлаживать ваши расширения в VS Code. Например, ставить точки останова, видеть содержимое переменных и стек вызовов. Кроме этого, в отладочной консоли (смотрите картинку ниже) вы будете видеть диагностику, которая будет выводится с помощью директив console.log ().

4d0bd27781f00a98bd5a48cf0c0f609e.png

Попробуем сделать наше первое расширение немного интереснее и добавим ещё одну команду. Сначала вставим ещё одну подгруппу для этой команды (назовём её conan-version) в файл package.json:

44c3be961d55df41ebcc28439cfbd1bc.png

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

5ac37ea13d7956f8c4f41324209e2e16.png

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

Дополним функцию activate регистрацией команды:

7d3477d9539ba3750f77d2ebdacfaae3.png

После этого можем снова запустить расширение с отладкой (F5, или без — Ctrl+F5) и запросить вызов нашей новой команды через панель команд (начните набирать conan-version, если её не будет видно в списке).

d1b06e42e29c65cda19eaeb9c2674b88.png

Результат на моей системе выглядит так:

c01c48c504891915c803af661d549df5.png

Treeview или древовидные (иерархические) представления

Теперь перейдём к наиболее интересной части — созданию древовидного представления. Для этого нужно добавить ещё немного кода в нашу функцию activate:

1832735835d5a1513a66062cd7877c66.png

Про ProjectDeps мы ещё поговорим отдельно. Пока просто отметим объявление этих двух переменных и перейдём к их использованию в связке. А именно — создадим представление и используем ProjectDeps как поставщик данных (treeDataProvider).

5ef2a368f7c8955113e3dbf3ce04af46.png

Само собой, нам нужно получить данные для представления. Например, вот так:

a9cb675e935dcaed68aac7e4d163db2b.png

Конечно, в случае вашего собственного расширения, тут нужно будет подставить другой код. Кстати, следует обратить внимание на пути для работы с файлами. Корневой каталог вашего проекта доступен через свойство модуля vscode, которое называется workspace.workspaceFolders (содержит массив с объектами класса WorkspaceFolder).

Класс-поставщик данных (treeDataProvider)

Cоздадим класс ProjectDeps как реализацию шаблонного интерфейса treeDataProvider.

de0a470627f6e4439fff17d210af9f12.png

В качестве параметра шаблона будем использовать произвольный тип данных, в данном случае структуру DepNode.

35c0e59ec556e5b66c60e2561d7c9b67.png

В предыдущем разделе мы вызывали заполнение данных для дерева. Для этого нам понадобятся два относительно несложных метода:

2d83d6bd0504beb57218192880fa83f3.png

Здесь мы создаём два корневых узла (их может быть произвольное количество), так как у нас есть два вида зависимостей: один набор для сборки, другой для работы. Второй метод вызывается из первого и создаёт самые верхние узлы зависимостей:

ea86f8510e9aee44ffa768e9ec4cbcfc.png

Кроме того, если взглянуть на спецификацию TreeDataProvider, то можно заметить, что нам необходимо определить два метода — getChildren:

d37fc3dc5d044f89cec84c296faec7c0.png

и getTreeItem:

0c8c7156fd67c37550da609c27e0c795.png

Важный момент: обратите внимание на contextValue при создании элементов (treeItem), так как это будет использовано в дальнейшем.

Прежде чем перейти к самому интересному — запуску нашего расширения, где мы наконец увидим то самое древовидное представление пакетов, необходимо добавить дополнительные описания в файле »package.json».

Возвращаемся в раздел contributes, где добавляем подраздел viewsContainers с элементом activitybar. Там будет указан идентификатор project-manager (мы используем его чуть ниже), название, которое будет всплывать при наведении мышки на иконку и картинка для иконки (в виде лампочки):

313fc941f77f42a2f62c06732261b576.png

Далее используем project-manager в создании нашего древовидного представления.

eaf7949b57533f6512becdc5977c078a.png

Тут стоит обратить внимание на значение атрибута id — mo.projectDep. Оно понадобится при описании контекстных меню или панели инструментов, которые мы можем создать для работы с элементами дерева. Теперь можно снова запустить и посмотреть, что у нас получилось. Если мы всё сделали правильно, то увидим новую иконку на полосе Выбор активности в виде зажжённой лампочки.

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

В качестве обозначений (иконок) были использованы встраиваемые значения. Майкрософт называет их icons-in-labels. Помимо простоты в использовании, они позволяют не думать о последствиях переключения между темами (см. картинку ниже).

6b94fa72d5354a0eb03c4e1aefa5a7cb.png

Выбор активности

Полоска слева с иконками называется ActivityBar и служит для быстрого переключения между разными режимами работы (видами активности). Например (смотрите картинку ниже), можно переключиться между файлами проекта (Explorer, иконка «две страницы»), поиском (иконка в виде лупы), системой контроля версий, управлением расширениями и многим другим (зависит от установленных расширений), в том числе и нашим расширением, обозначенным иконкой в виде горящей лампочки. 

При щелчке мыши по той или иной иконке мы можем видеть дополнительные представления, которые могут быть, например, древовидными (иерархическими), а могут — так называемыми веб-представлениями. Последние имеют произвольное наполнение в формате HTML и могут, хотя и не без ограничений, поддерживать обработку JavaScript.

7d7f19f26b3f343dfd8f77136741d529.png

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

Создание установщика

Этот процесс описан в документации Майкрософт, но для полноты картины приведу и его.

Для распространения готовое расширение нужно упаковать утилитой vsce в файл типа vsix. Для этого устанавливаем пакет vsce:

$ npm install -g @vscode/vsce

После этого пакуем расширение в его каталоге:

$ vsce package

f9ba3f6365467593de74111ea8d169f6.png

Узнать больше об этой команде можно, используя ключ — $ vsce package -h.

В результате, получаем файл с расширением vsix, например, conan-pkg-0.0.1.vsix.
Далее, этот файл может быть использован для установки на других компьютерах и/или другими пользователями. Теперь заходим в активность Extensions (Ctrl+Shift+X).

dd5c34b97ea1772ea8c306906a21ea88.png

При вызове меню (троеточие над полем поиска в расширениях) мы можем увидеть пункт «Inslall from VSIX». Появится диалог выбора файла, а дальше вы знаете, что нужно делать.

Либо мы можем в командной строке набрать:
code --install-extension conan-pkg-0.0.1.vsix (да, у VS Code есть разные ключи для запуска, я и сам удивлён).

Если после этого мы наберём в строке поиска @installed, то, например, в моём случае (с добавлением слова myoffice)

1c7cd55dde4cb17b2c73d4411fed95f9.png

мы можем увидеть что-то вроде изображённого на картинке:

21f6dec50329885de1f435c3ae06e20e.png

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

Дальнейшие улучшения

В файл package.json добавим ещё одну команду:

37fb665b3fd022fe6c07027b9991eaa2.png

А так же добавим возможность вызова контекстной команды (view/item/context) в зависимости от выбранного в данный момент элемента:

7231d59ee339dcea7daeda4a72a76e92.png

Следует обратить внимание на элемент when. Он описывает контекст, в котором мы хотим увидеть элемент меню в виде иконки (лампочка).

Если мы всё сделали правильно и создали наше представление с идентификатором mo.projectDeps, и элемент дерева, выбранный в данный момент, имеет значение контекста package (смотрите класс ProjectDeps, а именно метод getTreeItem), то результат будет соответствовать нашим ожиданиям.

8752e1ff3dcaa82e27b33ef6b801daec.png

В заключение

Итак, подытожим: мы прошли все шаги создания своего расширения для VS Code и даже смогли увидеть работающее древовидное представление пакетов conan для реального проекта (про его автоматизацию мы поговорим в будущем). В следующей статье мы рассмотрим: как добавить панели для редактирования параметров, которые вы наверняка захотите иметь в своём распоряжении, дополнительные области вывода, куда вы сможете выводить диагностику (например, так называемые логи), асинхронный запуск ваших процессов (откуда скорее всего вывод будет попадать в эти дополнительные области). Полностью код расширения, разработанного нами можно посмотреть здесь.

© Habrahabr.ru