Эта статья продолжает небольшую серию «Создаём ваш первый плагин для…», в которую уже вошли статьи про написания плагина для Grunt и Gulp.
Дисклеймер
Я люблю JavaScript. Мне довольно приятно наблюдать за тем, что этот прекрасный язык программирования вышел за пределы браузера и собирает всё больше и больше областей применения. Так, например, благодаря Electron от GitHub у меня появилось сразу несколько приложений, которые я использую в повседневной жизни. К таким приложениям относится Hain, 1Clipboard, Wagon, Gitify и, конечно же, Visual Studio Code.
Теперь поговорим о приятном для некоторых людей и противном для других. У меня нет симпатий к TypeScript. На то есть свои причины, в основном, связанные с типизацией — не люблю я её, что тут поделать. При этом я не буду отрицать, что когда-нибудь начну использовать TypeScript или подобный язык, компилируемый в JavaScript — всё бывает, мнение может меняться со временем. Однако, в связи с этим, в статье все примеры будут написаны на JavaScript, хотя сам редактор и вся документация к нему написана с применением TypeScript в примерах.
Кстати, я недавно узнал, что создателем TypeScript был Андерс Хейлсберг, который, оказывается, приложил руку к Turbo Pascal, Delphi и C#.
Что-то вместо введения
Герой нашего дня (VS Code) построен на Electron, который подробно рассматривался в статье «Построение Electron приложения. Введение». На момент написания статьи (июнь 2016) в основе редактора лежит Electron версии 0.37.6, что подразумевает под собой Chromium 49-ой ветки и Node.js версии 5.10.0. В репозитории на GitHub уже думают над переходом на новую версию Electron, где версия Chromium поднимется минимум до 51-ой ветки, а Node.js до версии 6.1.0 или выше. Всё это означает, что вы можете писать плагины, используя синтаксис ES2015 без Babel и его альтернатив, а также применяя любой API Node.js.
Предупреждение
Не стоит читать эту статью дальше введения, если вы не понимаете фишки, введённые в ES2015. Если говорить конкретно, то от вас требуется понимание деструктуризации, обещаний, стрелочных функций, const и let, а также понимание основ Node.js.
Итак, пожалуй, начнём с того, что плагины в VS Code изолированы от самого редактора и запускаются в отдельном хост-процессе (extension host process), который представляет собой процесс Node.js с возможностью использования VS Code API. Такой подход не позволяет плагинам влиять на производительность редактора при его запуске или в процессе его работы. Для пользователя это означает, что редактор не зависнет на время выполнения задач каким-либо плагином или, если плагин выдаст фатальную ошибку.
Для экономии расхода памяти разработчики также добавили ленивую загрузку плагинов. Это означает, что плагины активируются лишь в тот момент, когда они нужны. Например, если пользователь открывает Markdown-файл, то плагины, работающие с Markdown, будут загружены только в момент открытия файла. Разумеется, мы должны сообщить редактору о том, когда именно он должен активировать какой-либо плагин. О настройке ленивой загрузки мы поговорим позднее в разделе про файл манифеста.
Помимо всего прочего, плагины делятся на три вида в зависимости от функционала:
К первому виду относятся стандартные плагины, которые запускаются в хост-процессе, активируются в нужный момент и не требуют серьёзных вычислений.
Ко второму виду относятся так называемые «языковые серверы». Клиентская часть плагина запускается в хост-процессе, а серверная часть создаёт дополнительный процесс, в котором производятся все сложные вычисления. К такому виду плагинов относятся линтеры.
К третьему виду относят «службы отладки», которые пишутся в виде отдельной программы и взаимодействуют с VS Code по специальному протоколу CDP (VS Code Debug Protocol).
В этой статье будет рассматриваться лишь первый вид плагинов на примере vscode-lebab. Во второй статье разбирается процесс построения второго вида плагинов на примере vscode-puglint.
Манифест плагина
Написание плагина начинается не с кода, а с файла манифеста, которым в мире Node.js является файл package.json
. VS Code дополняет стандартный файл манифеста своими полями. Ниже будут рассмотрены самые основные из них.
publisher [string]
Имя пользователя, под которым вы зарегистрировались в vsce.
icon [string]
Путь до иконки, которая будет отображаться в магазине расширений. Размер иконки 128x128 пикселей. Также поддерживается SVG формат.
displayName [string]
Название плагина, которое будет отображаться в магазине расширений.
categories [array]
Массив, содержащий имена категорий, к которым относится плагин. Доступны следующие категории: [Languages, Snippets, Linters, Themes, Debuggers, Other]
. Пожалуйста, указывайте категорию или категории обдуманно. Например, если ваше расширение включает в себя подсветку синтаксиса языка и сниппеты, то указывайте только эти две категории.
galleryBanner [object]
Настройки оформления страницы расширения в магазине. Используется для того, чтобы иконка расширения и фон подложки были контрастны. Свойство color
отвечает за цвет фона, свойство theme
за цвет шрифта: dark
— белый, light
— чёрный.
"galleryBanner": {
"color": "#0000FF",
"theme": "dark"
}
preview [boolean]
Флаг, позволяющий пометить плагин сообщением о том, что он доступен в режиме предварительного просмотра.
activationEvents [array]
Массив событий, в случае наступления которых плагин будет активирован редактором. Поддерживаются следующие события, активирующие плагин в том случае, если будет:
onLanguage
— открыт файл указанного языка (не расширения).onCommand
— вызвана указанная команда.onDebug
— запущен сеанс отладки указанного типа.workspaceContains
— найден указанный файл в корневой папке проекта.
Отдельно стоит отметить событие *
, которое активирует плагин при загрузке редактора. Однако, использовать это событие нужно крайне редко и в том случае, если комбинации других событий не могут решить сложившуюся проблему.
contributes [object]
С помощью этого поля в package.json
разработчик описывает своё расширение. Доступно довольно большое количество всевозможных полей, описывающих:
configuration
— поля, доступные пользователю в настройках редактора.commands
— команды, доступные пользователю в палитре командF1
.keybindings
— сочетания клавиш для вызова команд.languages
— языки.debuggers
— отладочный адаптер.grammars
— TextMate-грамматику, необходимую для подсветки синтаксиса.themes
— темы.snippets
— сниппеты.jsonValidation
— схемы проверки определённых JSON-файлов.
Несложно догадаться, что поля необходимо указывать в зависимости от типа вашего расширения. Если это плагин, сортирующий строки, то, вероятнее всего, вы будете использовать поля: configuration
, commands
и keybindings
.
extensionDependencies [array]
Массив идентификаторов расширений, которые требуются для работы плагина. Например, если ваше расширение требует поддержки синтаксиса C#, то необходимо будет добавить в массив строку vscode.csharp
, где vscode
— ник опубликовавшего расширение, а csharp
— имя расширения.
Немного про VS Code API
Как и полагается любому крупному проекту, VS Code имеет довольно обширный API, доступный разработчикам плагинов. Рассмотрим лишь так называемые пространства имён:
- commands — это пространство имён для работы с командами. С помощью доступных методов разработчик может регистрировать, получать и выполнять команды.
- env — пространство имён, содержащее описание переменных окружения редактора при запуске. С помощью соответствующих методов можно получить имя окна редактора, его язык, идентификатор редактора в системе и идентификатора сессии редактора, которые устанавливается при запуске.
- extensions — пространство имён для работы с установленными расширениями. С помощью этого API можно получить все или конкретные расширения, известные редактору.
- languages — пространство имён, позволяющее получить доступ к языковым возможностям редактора, например, к IntelliSense, подсказкам, а также функциям диагностики кода для линтеров.
- window — пространство имён для работы с текущим окном редактора. Доступно API для работы с видимыми и активными окнами редактора, а также элементами пользовательского интерфейса. Последнее подразумевает под собой возможность отображения различных сообщений, ввода текста или выбора каких-либо вариантов.
- workspace — пространство имён для работы с текущей рабочей областью, включая открытую директорию и файлы. С помощью этого API осуществляется вся работа с содержимым открытого файла.
Пишем стандартный плагин
Постановка задачи
В этой части статьи я буду описывать процесс написания плагина для VS Code на основе lebab, который автоматически конвертирует JavaScript-код, написанный по стандарту ES5 в ES2015. Это проект является альтернативой проекту Babel, но в обратную сторону.
Манифест
И снова скажу, что написание плагина начинается не с кода на JavaScript, а с манифеста. Первым делом создаём файл package.json
и пишем туда пару десятков строк, описывающих плагин. Полный листинг манифеста вы сможете найти в репозитории плагина vscode-lebab. Остановимся именно на тех моментах, которые касаются работы с VS Code.
Во-первых, укажем информацию, которая будет отображаться в маркете:
{
"displayName": "lebab",
"publisher": "mrmlnc",
"icon": "icon.png",
"homepage": "https://github.com/mrmlnc/vscode-lebab/blob/master/README.md",
"categories": [
"Other"
]
}
Во-вторых, укажем массив событий, на которые наш плагин должен откликаться. Про команду lebab.convert
я расскажу немного позднее.
{
"activationEvents": [
"onCommand:lebab.convert"
]
}
В-третьих, опишем плагин. Предполагается, что пользователю будет доступна лишь одна команда, по вызову которой он получит сконвертированный ES5-код в синтаксис ES2015. Также предполагается, что в настройках редактора пользователь сможет указать, что именно он хочет конвертировать с помощью lebab. Для этого я определил опцию lebab.transforms
, содержащую объект ключей, с которыми будет работать конвертер.
{
"contributes": {
"commands": [{
"command": "lebab.convert",
"title": "Lebab: convert JavaScript code from ES5 to ES2015"
}],
"configuration": {
"type": "object",
"title": "Lebab configuration",
"properties": {
"lebab.transforms": {
"type": "object",
"default": {},
"description": "Convert your old-fashioned code with a specific transformation."
}
}
}
}
}
Убираем из маркета лишнее
Сколько раз не говори, но я всё равно встречаю модули в npm, у которых вместе с кодом я получаю файлы тестов, изображений и прочей лабуды. К счастью, теперь я могу ссылаться на эту статью в отношении npm. В случае VS Code, необходимо создать файл .vscodeignore
, который действует так же, как и файл .gitignore
, но в отношении маркета расширений.
У меня файл .vscodeignore
имеет следующее содержимое:
.vscode/**
typings/**
test/**
.editorconfig
.gitignore
.travis.yml
jsconfig.json
Я очень прошу, убирайте всё лишнее из плагина, иначе я вас вычислю по IP и накажу.
Базовый код
В мире Node.js принято писать код модуля в файле index.js
, а приложения — app.js
. Мир VS Code тоже имеет традиции, и код плагина пишется в файле extension.js
.
Внимание
Если очень хочется писать код в файле с именем, отличным от
extension.js
, то, как и в мире Node.js, вам придётся указать имя файла в полеmain
в манифесте.
Для начала определим две функции. Первая функция будет иметь имя activate
и вызываться в том случае, если плагин был активирован событием, указанным в манифесте. Вторая функция имеет имя deactivate
и вызывается в том случае, если плагин был деактивирован. Под деактивацией следует понимать последействие команды, а не удаление плагина. Её предназначение в большинстве плагинов излишне, поэтому она не обязательна. Далее в статье я не буду упоминать функцию деактивации плагина.
const vscode = require('vscode');
function activate(context) {
// Code...
}
exports.activate = activate;
function deactivate() {
// Code...
}
exports.deactivate = deactivate;
Напомню, что в файле манифеста была указана команда lebab.convert
— самое время её зарегистрировать. Для регистрации команд существует два метода:
registerTextEditorCommand
— регистрирует команду в контексте текстового редактора или файла.registerCommand
— регистрирует команду в глобальном контексте, то есть вне зависимости от наличия открытого редатора с текстом.
Второй метод используется крайне редко, вследствие того, что, в основном, плагины нацелены на работу с содержимом текстового редактора.
В конце все объявленные команды должны быть добавлены в массив subscriptions
.
'use strict';
const vscode = require('vscode');
function activate(context) {
const convert = vscode.commands.registerTextEditorCommand('lebab.convert', (textEditor) => {
// ...
});
context.subscriptions.push(convert);
}
exports.activate = activate;
При регистрации команды необходимо передать идентификатор команды, указанный в файле манифеста и функцию обратного вызова, в которую передаётся объект textEditor
, содержащий всю информацию о текущем редакторе, такую как:
- Файл на диске или новый файл (untitled)
- Путь до файла и его имя
- Идентификатор языка
- Наличие EOL
- Если было выделение текста, то параметры этого выделения
- Статистика текста (количество символов в строке и прочее)
- Версия файла (проще говоря, номер сохранения в истории файла)
- Строки файла
- и т.д.
Чисто практически, вы можете обращаться к свойствам объекта и работать с полученными из него данными. Но, разумеется, лучше всего обращаться к этому объекту с помощью определённых методов. На данном этапе нам нужно получить текст файла, чтобы в дальнейшем обработать его, и настройки, определённые в редакторе для нашего плагина.
Получить текст открытого документа можно, используя метод getText
, а настройки, используя метод getConfiguration
у workspace
API:
const convert = vscode.commands.registerTextEditorCommand('lebab.convert', (textEditor) => {
// Обычный объект, где имена свойств совпадают с теми, что были обозначены в манифесте.
const options = vscode.workspace.getConfiguration('lebab');
// Текст открытого файла.
const text = textEditor.document.getText();
});
Внимание
Настройки редактора нужно получать в момент вызова команды, иначе, если получить их в момент активации плагина, то при обновлении настроек редактора, объект в переменной не обновится.
Далее я не буду рассматривать процесс вызова Lebab, потому что это элементарное действие, не относящееся к VS Code. Покажу лишь тот участок, что отвечает за вставку обработанного текста обратно в окно редактора. Для этого мы обратимся к объекту textEditor
и вызовем метод edit
с коллбэком, представленным ниже. Конечно же, код должен располагаться после получения текста документа и настроек редактора в функции обратного вызова регистрации команды.
textEditor.edit((editBuilder) => {
// Получаем текущий документ.
const document = textEditor.document;
// Получаем последнюю строку документа.
const lastLine = document.lineAt(document.lineCount - 1);
// Создаём нулевую позицию, то есть начало документа, где первый ноль — номер
// строки, а второй ноль — номер символа в строке.
const start = new vscode.Position(0, 0);
// Создаём завершающую позицию, где первое число — последняя строка документа,
// а второе — номер последнего символа в строке.
const end = new vscode.Position(document.lineCount - 1, lastLine.text.length);
// Создаём диапазон, используя специальное API.
const range = new vscode.Range(start, end);
// Заменяем текст в обозначенном диапазоне на что-либо.
editBuilder.replace(range, text);
});
Собственно, это всё, что требуется сделать в обычном плагине для VS Code: получить текст, обработать его и вернуть обратно. Полный листинг кода содержит 52 строки и размещён на GitHub в репозитории vscode-lebab.
Как можно заметить, ничего сложно — простейший JavaScript-код, который разбавлен вызовами необходимых API редактора. В этой статье был рассмотрен простейший случай, когда у вас есть готовое решение и его нужно подружить с редактором. В случае, если готового решения нет, то лучше оформить его в виде npm-пакета по всем канонам (тесты, документация), а уже потом подружить его с редактором. В качестве примеров, если решите писать плагин на JavaScript, вы можете посмотреть код следующих плагинов:
Что-то вместо вывода
В этой статье я помог вам начать писать плагины для VS Code. Многое осталось за кулисами и не рассматривалось, однако вам в любом случае придётся обращаться к документации. Считайте, что эта статья преследует цель показать, что писать плагин для VS Code довольно просто, причём необязательно делать это на TypeScript. Хотя, при этом не стоит забывать, что TypeScript — это всё тот же JavaScript.
Также, советую посмотреть на код, представленный в репозитории VSCode-Sample. Здесь автор собрал примеры взаимодействия с UI редактора, которые, возможно, помогут вам освоиться.
Если вам интересна тема разработки плагинов для VS Code, то добро пожаловать во вторую часть этой статьи, в которой рассматривается процесс написания плагина, использующего языковой сервер.