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 <Enter> 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. Скорее всего вы увидите следующее:

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

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

Теперь познакомимся поближе с этим кодом (в 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 — как, например, на картинке:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

и getTreeItem:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

$ npm install -g /vsce

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

$ vsce package

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

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

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

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

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

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

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

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

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

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

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

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

В заключение

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

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


  1. ris58h
    20.06.2024 07:32

    Расширения в VS Code пишутся на Typescript

    JavaScript уже запретили?


    1. roboboroed316 Автор
      20.06.2024 07:32
      +1

      нет, не запретили, не стал сильно вдаваться в этот вопрос, так-то, если заморочиться, то можно на чём угодно, по идее, писать, но Typescript будет поудобнее, разве нет?


      1. ris58h
        20.06.2024 07:32

        то можно на чём угодно, по идее, писать

        Даже на C++? Расскажите как - мне интересно.


        1. roboboroed316 Автор
          20.06.2024 07:32

          Ну, например, есть такая разновидность расширений - сервер поддержки языка, они работают по определённому протоколу и поэтому абсолютно неважно, на чём они написанны, такая же история и с расширениями типа адаптер отладки.
          Опять же, если очень хочется, то можно что-то в ноду завернуть, но это потребует усилий для поддержки на разных платформах. Но, тем не менее, чем-то таким занимается Микрософт в своих расширениях для cmake и c++, если ничего не путаю. В принципе, этому можно посвятить отдельную серию статей, если будет запрос от трудящихся :)


  1. semenInRussia
    20.06.2024 07:32

    Ой, как же хорош Emacs, когда читаешь как писать плагины для других редакторов)

    Но пост был познавательным, Я охренел


    1. roboboroed316 Автор
      20.06.2024 07:32

      спасибо, а что именно удивило, если не секрет?


      1. semenInRussia
        20.06.2024 07:32

        Ну.. Я не думал до этого как работает система плагинов в современных редакторах, а это ну довольно легко, точно легче, чем я(пользователь Emacs) думал — просто регистрируешь команды в json, встроенные UI компоненты легко расширяются. Но в Emacs ещё легче)

        Ответ на сообщение ниже: Это не похоже на разработку пакетов в Emacs. Emacs это по сути просто REPL, где ты выполняешь код, например, определяет функцию и можешь моментально юзать, никаких стандартов только фантазия


        1. roboboroed316 Автор
          20.06.2024 07:32

          про REPL надо подумать...но чисто теоретически не вижу проблем пока
          с точки зрения структурирования: ну, хорошо, мы можем там запускать свои программы на Лиспе, которые имеют доступ ко всем ресурсам Emacs, я правильно понимаю?


    1. roboboroed316 Автор
      20.06.2024 07:32

      откомментировал ниже, может это похоже на Emacs?


  1. Padrino
    20.06.2024 07:32

    спасибо большое за статью. Как раз задумывался над какими-то механизмами, которые бы облегчали запоминание всевозможных команд, которых ныне огромное количество. Имею в виду в основном фронтовые фреймворки, где у каждого своя специфика и документация. Было бы здорово через подобные плагины сделать простой способ по выполнению частых команд. подскажите, а для именно для VSCode какие-то ещё есть варианты языков для написания расширений? или в основном Typescript как некий бестпрактис?


    1. roboboroed316 Автор
      20.06.2024 07:32

      Есть такая вещь как Задания (Tasks) , на уровне конфигурационных файлов можно всё сделать, в описании есть опять про TypeScript, но его совершенно необязательно использовать. Или можно взять расширение, которое помогает с ними работать, например, TaskList, описание есть здесь, поставить можно прямо из Кода. Может допишу тут про это подробнее, если есть запрос.