Всем привет, давно я не писал статьи о жизни проекта на хабре, решил исправиться и начну пожалуй с того над чем сейчас работаю а именно Consulo UI API.
Consulo — это форк IntelliJ IDEA Community Edition, который имеет поддержку .NET(C#), Java
Q: Consulo UI API — что это такое?
A: Это набор API для создания UI. На деле — простой набор интерфейсов, который повторяет разные компоненты — Button, RadionButton, Label, etc.
Q: Какова цель создания, еще одного набора UI API, если уже был Swing (так как IDEA UI использует Swing для отображения интерфейса)
A: Для этого давайте углубимся в идею которую я последовал работая над Consulo UI API. Так как я основной и практически единственный контрибьютор в проект Consulo, со временем мне стало трудно поддерживать то количество проектов которое сейчас (порядка 156 репозиториев). Встал вопрос о массовом анализе кода, но это нереально сделать в рамках одного инстанса IDE на Desktop платформе, а использовать опыт JetBrains где один проект idea-ultimate держит все плагины я не хотел практиковать по ряду причин.
Зародилась мысль об анализе на веб-сервере. "Обычный анализ" меня не сильно устраивал на веб-сервере, захотелось сделать Web IDE (хотя бы readonly на старте) — при этом иметь полностью весь тот же функционал что и на Desktop.
Вы можете сказать что это повторяет немного Upsource, сама идея подобная — но подход совсем другой.
И вот пришел тот момент — когда идея была, но не было известно как это сделать. За плечами был опыт использования GWT, Vaadin фреймворков — другие не Java фреймворки для генерации JS(ну или plain js) я не хотел использовать.
Я уделил себе месяц на это исследования. Это был тест моих возможностей в этой части. Сначала я использовал только GWT — для получения информации я использовал встроенный RPC.
Была простая цель — проект открыт уже, нужно было только отобразить Project Tree + Editor Tabs. При этом все должно было похожим на Desktop версию.
Сразу появились проблемы с новоиспеченным бэкэндом. Например использования EventQueue для внутренних действий
EventQueue если коротко это UI(AWT, Swing) поток в нем происходит почти все что связано с UI — отрисовка, обработка нажатия на кнопку и так далее.
Исторически сложилось в IDEA то что write действия должны всегда выполняться в UI потоке.
Write действие — это запись в файл, либо изменения какого то сервиса (например переименования модуля)
Поначалу проблему с EventQueue можно было игнорировать — но потом появились другие проблемы. Например банальные иконки. Представим у нас есть дерево проекта
И для каждого файла нам нужно загрузить и показать картинку. Так как мы работаем внутри Swing кода — мы используем класс javax.swing.Icon. Проблема в том что это просто интерфейс — который имеет очень много разных реализаций
В итоге чтобы показать иконку в браузере нужно поддерживать весь зоопарк(и почти все сразу).Одна из сопуствующих проблем состоит в том, что может прийти совсем неизвестная тебе иконка для файла (например внутри какого то плагина отрисован попиксельно какой то символ) — и такое нужно игнорировать.
Методом костыля (ну куда же без них) — было сделано решение. Банально проверять через instanceof на нужный нам тип — а все другие игнорировать.
Спустя время была сделана поддержка навигации по файловой системе, открытия файла, подсветка синтаксиса, семантический анализ, quick doc info, навигация по code references(поддерживалась комбинация например Ctrl + B, или Ctrl + MouseClick1). По сути Editor был очень похож на Desktop платформу.
Как это выглядило:
Итак — сделать Web интерфейс было возможно моими силами. Но это была очень грубая работа — которую нужно было переделать. И тут пришёл на помощь Vaadin.
Я решил переделать мою GWT реализацию на использования Vaadin фреймворка. Этот тест получился очень плохим (очень сильно деградировал перфоманс) — тут больше сказался мой опыт использования Vaadin, и я забраковал этот вариант(я даже сделал hard-reset на текущем бранче, чтобы забыть об этом :D).
Но опыт использования Vaadin мне всеодно пригодился, зародилась мысль — унифицировать UI чтобы можно было писать один код, но на выходе получать различный результат в зависимости от платформы.
Еще одна причина унифицировать UI — это полный зоопарк Swing компонентов внутри IntelliJ платформы. Пример такой проблемы — это две совсем разных реализации Tabs
Разделяем UI логику:
Что такое WGWT? Сокращения от Wrapper GWT. Это самописный фреймворк — который хранил STATE компонента и отправлял его через WebSocket браузеру(который в свою очередь генерировал html). Он писался с оглядкой на Vaadin (да да — еще один костыль).
Время шло — и уже я мог запустить тестовый UI, который работал одинаково на Desktop и в браузере
Так же паралельно я использовал Vaadin на работе, так как это один из самых дешёвых вариантов построить Web UI если использовать Java. Я все больше и больше изучал Vaadin — и я решил опять переписать WGWT на Vaadin но с некоторыми правками.
Каковы были правки:
В итоге получилось вот так:
И так родилась текущая использоваться Consulo UI API.
Где будет использоватся Consulo UI API?
Какие есть проблемы?
Вот этот код работает одинаково на обеих платформах.
Его работа на Desktop:
Его работа в браузере:
По поводу вышеописанных проблем:
На Desktop платформе — иконки грузятся как и грузились ранее, только теперь возвращаемый тип это SwingImageRef(legacy названия класса — раньше consulo.ui.image.Image назывался consulo.ui.ImageRef) — интерфейс который наследует javax.swing.Icon и consulo.ui.image.Image. Позднее этот интерфейс будет удален (его существования обусловлено упрощенной миграцией на новый тип)
На Web платформе — URL сохраняется внутри объекта, и является идентификатором для отображения в интерфейсе (через URL — /app/uiImage=URLhashCode)
Был введен класс ImageEffects. Он имеет внутри себя методы которые нужны для создания производных иконок. Например #grayed(Image) вернет иконку с серым фильтром, #transparent(Image) полупрозрачную иконку.
То есть — весь весь вышеописанный зоопарк был загнан в узкие рамки.
Также будет введена поддержка ручной отрисовки элементов (ну куда без этого). Метод ImageEffects#canvas(int height, int width, Consumer<Canvas2D> painterConsumer) вернет иконку которая будет отрисована через Canvas2D
На Desktop — будет использован wrapper поверх обычного Graphics2D из Swing
На Web — каждый вызов Canvas2D методов будет сохранен, и потом будет передан в браузер где будет использован внутренний Canvas с браузера
Также появилась новая проблема — Swing диалоги блокируют выполняющий поток во время показа. В итоге в IDEA любят писать код в таком виде:
DialogWrapper wrapper = ...;
int value = wrapper.showAndGet();
if(value == DialogWrapper.OK) {
...
}
При этом в Vaadin показ диалогов — не блокирует выполняющий поток.
Во избежания путаницы с синхронным и асинхронным показом диалогов — был выбран асинхронный вариант (вышеописанный код придется переосмыслить и переделать).
Спустя время я имеею рабочий прототип Web приложения.
Пока это прототип, который двигается к своему релизу — но это будет не быстро (увы).
К сожалению, не доступен сервер mySQL