Hello everyone, for a long time I did not write an article about the life of the project on Habré, I decided to improve and perhaps begin with what I’m working on now, namely the Consulo UI API.
Consulo is a fork of IntelliJ IDEA Community Edition that has support for .NET (C #), Java
Let's start
Q: Consulo UI API - what is it?
A: This is a set of API for creating UI. In fact - a simple set of interfaces that repeats different components - Button, RadionButton, Label, etc.
Q: What is the purpose of creating another set of UI APIs if Swing was already there (since IDEA UI uses Swing to display the interface)
A: To do this, let's delve into the idea that I followed while working on the Consulo UI API. Since I am the main and almost the only contributor to the Consulo project, over time it became difficult for me to support the number of projects that are now (about 156 repositories). There was a question about the mass analysis of the code, but it is impossible to do it within the framework of one IDE instance on the Desktop platform, and using the experience of JetBrains where one project idea-ultimate holds all the plugins I didn’t want to practice for several reasons.
The thought of analyzing the web server was born. "Normal analysis" did not suit me much on the web server, I wanted to make a Web IDE (at least readonly at the start) - while having all the same functionality as on the Desktop.
You can say that this repeats a bit of Upsource , the very idea is similar - but the approach is completely different.
And then came the moment - when the idea was, but it was not known how to do it. Behind him was the experience of using GWT, Vaadin frameworks - other non-Java frameworks for generating JS (or plain js) I did not want to use.
I spent a month on this research . It was a test of my capabilities in this part. At first, I only used GWT — I used the built-in RPC for information.
There was a simple goal - the project was already open, it was only necessary to display Project Tree + Editor Tabs . At the same time, everything should be similar to the Desktop version.
Immediately there were problems with the new backend. For example, using EventQueue for internal actions.
EventQueue if it is short for UI (AWT, Swing) a thread in it almost everything is connected with UI - drawing, handling of pressing a button, and so on.
Historically, in IDEA, write actions should always be performed in a UI stream.
Write action is an entry to a file, or a change to a service (for example, renaming a module)
At first, the problem with EventQueue could be ignored - but then other problems appeared. For example, banal icons . Imagine we have a project tree
- [] project-name
- [] src
- [] test
- [] build.gradle
And for each file we need to upload and display a picture. As we work inside the Swing code, we use the javax.swing.Icon class. The problem is that this is just an interface - which has a lot of different implementations.
- image icon is an icon that simply wraps the Image (that is, a regular image from the file system)
- layred icon a layered icon which consists of two or more icons superimposed on each other
- disabled icon - icon with a gray filter overlaid
- transparent icon - an icon with the specified transparency
- and many others
As a result, to show the icon in the browser you need to support the entire zoo (and almost all at once). One of the problems is that a completely unknown icon for a file can come (for example, inside some plug-in some character is drawn pixel-by-pixel) need to ignore.
The crutch method (well, where without them) - the decision was made. It is trite to check through instanceof for the type we need - and ignore all the others.
After a while, support was made for navigating through the file system, opening a file, syntax highlighting, semantic analysis, quick doc info, navigation through code references (a combination such as Ctrl + B, or Ctrl + MouseClick1 was supported). In essence, the Editor was very similar to the Desktop platform.
What it looked like:

So - to make the Web interface was possible by my own. But it was a very rough job - that needed to be redone. And then Vaadin came to the rescue.
I decided to remake my GWT implementation using the Vaadin framework. This test turned out to be very bad (the performance was very degraded) - my experience with Vaadin affected me more, and I rejected this option (I even did a hard-reset on the current brunch to forget about it: D).
But the experience of using Vaadin came in handy for me all the time, the thought arose - to unify the UI so that you could write one code, but at the output get a different result depending on the platform.
Another reason to unify the UI is the complete zoo of Swing components inside the IntelliJ platform. An example of such a problem is two completely different implementations of Tabs.


Separate the UI logic:
- frontend - a set of interfaces for each element, for example, consulo.ui.Button # create ()
- backend - implementation depending on the platform
- Swing - desktop implementation
- WGWT - web implementation
What is a WGWT ? Abbreviations from Wrapper GWT. This is a self-written framework - which stored the STATE of the component and sent it via the WebSocket to the browser (which in turn generated html). He wrote with an eye to Vaadin (yes, yes - another crutch).
As time went on - and I could already run a test UI that worked equally well on Desktop and in the browser

I also used Vaadin at work in parallel, as this is one of the cheapest options to build a Web UI if you use Java. I studied Vaadin more and more - and I decided to rewrite WGWT to Vaadin again but with some edits.
What were the edits:
- avoiding the use of virtually all Vaadin components. There were several reasons - one of them is too limited components (customization was minimal).
- use existing components from my self-written WGWT framework - i.e. their GWT implementation
- there was also a patch which made it possible to write Connect annotations without a direct link to the server component (this was done more for the project structure, in order to avoid the availability of server classes within the client code)
The result was this:
- frontend - a set of interfaces for each element, for example, consulo.ui.Button # create ()
- backend - the current implementation depending on the platform
- Swing - desktop implementation
- Vaadin - web implementation
- Android? - in order for the phone to burn at the start of the application: D So far only at the level of the idea that it will be possible to use the already existing code for transfer to Android (for there will be no binding to Swing)
And so was born the current used Consulo UI API.
Where will the Consulo UI API be used?
- In all plugins. AWT / Swing will be "blocked" (no more java.awt.Color ) during compilation (the javac processor will be made - later, perhaps it will not be necessary with the arrival of java 9). My own set of components is not a panacea, I understand that. At the moment - you can make your custom UI component, while only on the Swing side (and in such cases you will need to add a dependency to the consulo.destop plugin to avoid problems on the web server). The creation of Vaadin components on the plug-in side is not there yet - it will be, this is a minor task.
- On the platform side, these are Settings / Preferences, Run Configurations, Editor - essentially the entire interface that goes to JFrame.
What are the problems?
- Completely incompatible with AWT / Swing code (there is a TargetAWT / TargetVaadin crutch class that has methods for converting components, but these classes are not available for plugins).
All Swing components can not be displayed in the browser - as a result, you need to rewrite all this code.
Almost everywhere, the Consulo UI API is already supported inside the platform - this allows you to use the new UI framework inside plug-ins and not only. - The IntelliJ platform’s very strong attachment to Swing, it is buried so deeply - that without a “next” crutch you can't dig it up (
).
Some time later
This code works the same on both platforms.
His work on Desktop:

His work in the browser:

Concerning the above problems:
- Icons. The class consulo.ui.image.Image was introduced, which is a picture of the file system (and not only). To upload a picture, you can use the method consulo.ui.image.Image # create (java.net.URL).
On the Desktop platform, the icons are loaded as they were loaded earlier, only now the return type is SwingImageRef (the legacy of the class name is formerly consulo.ui.image.Image was called consulo.ui.ImageRef) —an interface that inherits javax.swing.Icon and consulo.ui .image.Image. Later this interface will be removed (its existence is due to simplified migration to a new type)
On the Web platform, the URL is stored inside the object, and is an identifier for displaying in the interface (via the URL - / app / uiImage = URLhashCode )
The ImageEffects class was introduced. He has inside the methods that are needed to create derived icons. For example, #grayed (Image) will return an icon with a gray filter, #transparent (Image) a translucent icon.
That is - all of the above zoo was driven into a narrow frame.
There will also be introduced support for manual drawing of elements (well, where without it). The ImageEffects # canvas method (int height, int width, Consumer <Canvas2D> painterConsumer) will return the icon to be drawn through Canvas2D
On the Desktop, a wrapper will be used over the usual Graphics2D from Swing
On the Web - every call to Canvas2D methods will be saved, and then transferred to the browser where the internal Canvas will be used from the browser
- Write Action in UI Thread. Oooo. While there is no solution to this problem. At the moment - there is a prototype of Write Action in Own Thread, but for now only on the Web platform, too much needs to be changed inside the platform to "roll out" it on the Desktop.
- UI was unified - no zoo for simple items
There is also a new problem - Swing dialogs block the executing thread during the show. As a result, IDEA like to write code in this form:
DialogWrapper wrapper = ...; int value = wrapper.showAndGet(); if(value == DialogWrapper.OK) { ... }
In this case, the display of dialogs in Vaadin does not block the executing thread.
In order to avoid confusion with the synchronous and asynchronous display of dialogs - the asynchronous option was chosen (the code described above will have to be rethought and redone).
Total
After a while, I have a working prototype of a Web application.

So far this is a prototype that is moving towards its release - but it will not be fast (alas).