Хабрахабр

Consulo UI API от идеи до прототипа

Всем привет, давно я не писал статьи о жизни проекта на хабре, решил исправиться и начну пожалуй с того над чем сейчас работаю а именно Consulo UI API.

NET(C#), Java Consulo — это форк IntelliJ IDEA Community Edition, который имеет поддержку .

Начнем

Q: Consulo UI API — что это такое?

На деле — простой набор интерфейсов, который повторяет разные компоненты — Button, RadionButton, Label, etc. A: Это набор API для создания UI.

Q: Какова цель создания, еще одного набора UI API, если уже был Swing (так как IDEA UI использует Swing для отображения интерфейса)

Так как я основной и практически единственный контрибьютор в проект Consulo, со временем мне стало трудно поддерживать то количество проектов которое сейчас (порядка 156 репозиториев). A: Для этого давайте углубимся в идею которую я последовал работая над Consulo UI API. Встал вопрос о массовом анализе кода, но это нереально сделать в рамках одного инстанса IDE на Desktop платформе, а использовать опыт JetBrains где один проект idea-ultimate держит все плагины я не хотел практиковать по ряду причин.

"Обычный анализ" меня не сильно устраивал на веб-сервере, захотелось сделать Web IDE (хотя бы readonly на старте) — при этом иметь полностью весь тот же функционал что и на Desktop. Зародилась мысль об анализе на веб-сервере.

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

За плечами был опыт использования GWT, Vaadin фреймворков — другие не Java фреймворки для генерации JS(ну или plain js) я не хотел использовать. И вот пришел тот момент — когда идея была, но не было известно как это сделать.

Это был тест моих возможностей в этой части. Я уделил себе месяц на это исследования. Сначала я использовал только GWT — для получения информации я использовал встроенный RPC.

При этом все должно было похожим на Desktop версию. Была простая цель — проект открыт уже, нужно было только отобразить Project Tree + Editor Tabs.

Например использования EventQueue для внутренних действий Сразу появились проблемы с новоиспеченным бэкэндом.

EventQueue если коротко это UI(AWT, Swing) поток в нем происходит почти все что связано с UI — отрисовка, обработка нажатия на кнопку и так далее.
Исторически сложилось в IDEA то что write действия должны всегда выполняться в UI потоке.
Write действие — это запись в файл, либо изменения какого то сервиса (например переименования модуля)

Например банальные иконки. Поначалу проблему с EventQueue можно было игнорировать — но потом появились другие проблемы. Представим у нас есть дерево проекта

  • [ ] project-name
    • [ ] src
      • [ ] Main.java
    • [ ] test
    • [ ] build.gradle

Так как мы работаем внутри Swing кода — мы используем класс javax.swing. И для каждого файла нам нужно загрузить и показать картинку. Проблема в том что это просто интерфейс — который имеет очень много разных реализаций Icon.

  • image icon это иконка которая просто оборачивает Image(то есть обычную картинку с файловой системы)
  • layred icon слоеная иконка которая состоит из двух или более иконок наложенных друг на друга
  • disabled icon — иконка с наложенным серым фильтром
  • transparent icon — иконка с заданной прозрачностью
  • и много других

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

Банально проверять через instanceof на нужный нам тип — а все другие игнорировать. Методом костыля (ну куда же без них) — было сделано решение.

По сути Editor был очень похож на Desktop платформу. Спустя время была сделана поддержка навигации по файловой системе, открытия файла, подсветка синтаксиса, семантический анализ, quick doc info, навигация по code references(поддерживалась комбинация например Ctrl + B, или Ctrl + MouseClick1).

Как это выглядило:

Но это была очень грубая работа — которую нужно было переделать. Итак — сделать Web интерфейс было возможно моими силами. И тут пришёл на помощь Vaadin.

Этот тест получился очень плохим (очень сильно деградировал перфоманс) — тут больше сказался мой опыт использования Vaadin, и я забраковал этот вариант(я даже сделал hard-reset на текущем бранче, чтобы забыть об этом :D). Я решил переделать мою GWT реализацию на использования Vaadin фреймворка.

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

Пример такой проблемы — это две совсем разных реализации Tabs Еще одна причина унифицировать UI — это полный зоопарк Swing компонентов внутри IntelliJ платформы.

Разделяем UI логику:

  • frontend — набор интерфейсов для каждого элемента, например consulo.ui.Button#create()
  • backend — реализация в зависимости от платформы
    • Swing — desktop реализация
    • WGWT — web реализация

Сокращения от Wrapper GWT. Что такое WGWT? Он писался с оглядкой на Vaadin (да да — еще один костыль). Это самописный фреймворк — который хранил STATE компонента и отправлял его через WebSocket браузеру(который в свою очередь генерировал html).

Время шло — и уже я мог запустить тестовый UI, который работал одинаково на Desktop и в браузере

Я все больше и больше изучал Vaadin — и я решил опять переписать WGWT на Vaadin но с некоторыми правками. Так же паралельно я использовал Vaadin на работе, так как это один из самых дешёвых вариантов построить Web UI если использовать Java.

Каковы были правки:

  • отказ от использования практически всех компонентов Vaadin. Причин было несколько — одна из них, это слишком ограниченные компоненты (кастомизация была минимальная).
  • использования уже существующие компоненты из моего самописного фреймворка WGWT — то есть их GWT реализацию
  • так же был сделать patch который позволял писать аннотации Connect без прямой ссылки на серверный компонент (это было сделано больше для project structure, во избежания доступности серверных классов внутри клиент кода)

В итоге получилось вот так:

  • frontend — набор интерфейсов для каждого элемента, например consulo.ui.Button#create()
  • backend — текущая реализация в зависимости от платформы
    • Swing — desktop реализация
    • Vaadin — web реализация
    • Android? — ради того чтобы телефон сгорел на старте приложения 😀 Пока только на уровне идеи о том что можно будет использовать уже существующий код для переноса на Android (ибо не будет привязки к Swing)

И так родилась текущая использоваться Consulo UI API.

Где будет использоватся Consulo UI API?

  • Во всех плагинах. AWT/Swing будет "заблочен"(никаких больше java.awt.Color) во время компиляции(будет сделан javac processor — позднее, возможно вообще не нужно будет с приходом java 9). Свой набор компонентов — это не панацея, это я понимаю. На текущий момент — можно сделать свой кастомный UI компонент, пока только на Swing стороне (и в таких случаях нужно будет добавлять зависимость в плагин consulo.destop во избежания проблем на веб сервере). Создания Vaadin компонентов на стороне плагинов пока нет — будет, это минорная задача.
  • На стороне платформы — это Settings/Preferences, Run Configurations, Editor — по сути весь интерфейс который идет до JFrame.

Какие есть проблемы?

  • Полностью не совместимость с AWT/Swing кодом (есть костыльный класс TargetAWT/TargetVaadin который имеет методы для конвертацию компонентов, но эти классы не доступы для плагинов).
    Все Swing компоненты не могут быть отображены в браузере — в итоге нужно переписывать весь этот код.
    Практически везде уже поддерживается Consulo UI API внутри платформы — это позволяет уже использовать новый UI фреймворк внутри плагинов и не только.
  • Очень сильная привязаность IntelliJ платформы к Swing, он зарыт так глубоко — что без "очередного" костыля его не выкопать ().

Спустя некоторое время

Вот этот код работает одинаково на обеих платформах.

Его работа на Desktop:

Его работа в браузере:

По поводу вышеописанных проблем:

  • Иконки. Был введен класс consulo.ui.image.Image который представляет собой картинку с файловой системы (и не только). Для загрузки картинки можно использовать метод consulo.ui.image.Image#create(java.net.URL).

Image назывался consulo.ui. На Desktop платформе — иконки грузятся как и грузились ранее, только теперь возвращаемый тип это SwingImageRef(legacy названия класса — раньше consulo.ui.image. Icon и consulo.ui.image. ImageRef) — интерфейс который наследует javax.swing. Позднее этот интерфейс будет удален (его существования обусловлено упрощенной миграцией на новый тип) 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 с браузера

  • Write Action in UI Thread. Ууууу. Пока решения этой проблемы нет. На текущий момент — есть прототип Write Action in Own Thread но пока что только на Web платформе, слишком много нужно поменять внутри платформы чтобы это "выкатить" на Desktop.
  • UI был унифицирован — никакого зоопарка для простых элементов

В итоге в IDEA любят писать код в таком виде: Также появилась новая проблема — Swing диалоги блокируют выполняющий поток во время показа.

DialogWrapper wrapper = ...; int value = wrapper.showAndGet(); if(value == DialogWrapper.OK) { ...
}

При этом в Vaadin показ диалогов — не блокирует выполняющий поток.

Во избежания путаницы с синхронным и асинхронным показом диалогов — был выбран асинхронный вариант (вышеописанный код придется переосмыслить и переделать).

Итог

Спустя время я имеею рабочий прототип Web приложения.

Пока это прототип, который двигается к своему релизу — но это будет не быстро (увы).

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»