Consulo UI API von der Idee bis zum Prototyp

Hallo allerseits, ich habe lange Zeit keinen Artikel über das Leben des Projekts auf dem Hub geschrieben


Consulo ist eine Abzweigung der IntelliJ IDEA Community Edition, die .NET (C #), Java unterstützt

Fangen wir an


F: Consulo UI API - was ist das?


A: Dies ist eine Reihe von APIs zum Erstellen einer Benutzeroberfläche. In der Tat ein einfacher Satz von Schnittstellen, die verschiedene Komponenten wiederholen - Button, RadionButton, Label usw.


F: Was ist der Zweck des Erstellens eines weiteren Satzes von UI-APIs, wenn bereits Swing vorhanden war (da IDEA UI Swing zum Anzeigen der Benutzeroberfläche verwendet)?


A: Lassen Sie uns dazu auf die Idee eingehen, die ich bei der Arbeit an der Consulo UI-API verfolgt habe. Da ich der Haupt- und fast der einzige Mitwirkende am Consulo-Projekt bin, wurde es im Laufe der Zeit für mich schwierig, die Anzahl der derzeit vorhandenen Projekte (ca. 156 Repositories) aufrechtzuerhalten. Es gab eine Frage zur Massencode-Analyse, aber es ist unmöglich, dies im Rahmen einer IDE-Instanz auf der Desktop- Plattform zu tun, und ich wollte nicht üben, Plug-Ins von JetBrains zu verwenden, bei denen ein ideenschonendes Projekt aus mehreren Gründen alle Plugins enthält.


Die Idee entstand der Analyse auf einem Webserver. Die "übliche Analyse" passte nicht viel zu mir auf dem Webserver. Ich wollte eine Web-IDE erstellen (zumindest zu Beginn schreibgeschützt) - und dabei immer noch die gleichen Funktionen wie auf dem Desktop haben.


Man kann sagen, dass dies ein bisschen Upsource wiederholt, die Idee selbst ist ähnlich - aber der Ansatz ist völlig anders.


Und dann kam der Moment - als die Idee war, aber es war nicht bekannt, wie es geht. Ich hatte Erfahrung mit GWT, Vaadin-Frameworks - ich wollte keine anderen Nicht-Java-Frameworks zum Generieren von JS (naja oder einfach js) verwenden.


Ich habe einen Monat gebraucht, um das zu erforschen . Es war ein Test meiner Fähigkeiten in diesem Teil. Zuerst habe ich nur GWT verwendet - zur Information habe ich den eingebauten RPC verwendet.


Es gab ein einfaches Ziel: Das Projekt war bereits geöffnet, es mussten nur die Registerkarten Projektbaum + Editor angezeigt werden . In diesem Fall sollte alles der Desktop-Version ähnlich sein.


Sofort gab es Probleme mit dem neu erstellten Backend. Verwenden Sie beispielsweise EventQueue für interne Aktionen


EventQueue, kurz, es ist ein UI-Stream (AWT, Swing), in dem fast alles, was mit der UI zu tun hat, passiert - Rendern, Verarbeiten eines Schaltflächenklicks und so weiter.
In IDEA sollten Schreibaktionen in der Vergangenheit immer in einem UI-Thread ausgeführt werden.
Eine Schreibaktion ist ein Datensatz in eine Datei oder eine Änderung an einem Dienst (z. B. Umbenennen eines Moduls).

Zuerst konnte das Problem mit EventQueue ignoriert werden - dann traten andere Probleme auf. Zum Beispiel banale Symbole . Stellen Sie sich vor, wir haben einen Projektbaum


  • [] Projektname
    • [] src
      • [] Main.java
    • [] Test
    • [] build.gradle

Und für jede Datei müssen wir ein Bild hochladen und anzeigen. Da wir im Swing-Code arbeiten, verwenden wir die Klasse javax.swing.Icon . Das Problem ist, dass es nur eine Schnittstelle ist - die so viele verschiedene Implementierungen hat


  • Das Bildsymbol ist ein Symbol, das das Bild einfach umschließt (dh ein normales Bild aus dem Dateisystem).
  • Layred-Symbol Ein Layered-Symbol, das aus zwei oder mehr übereinander gestapelten Symbolen besteht
  • deaktiviertes Symbol - Symbol mit angewendetem Graufilter
  • transparentes Symbol - Symbol mit der angegebenen Transparenz
  • und viele andere

Um das Symbol im Browser anzuzeigen, müssen Sie daher den gesamten Zoo (und fast alle gleichzeitig) unterstützen. Eines der damit verbundenen Probleme besteht darin, dass ein Symbol, das Ihnen für eine Datei völlig unbekannt ist, möglicherweise angezeigt wird (z. B. wird ein Symbol Pixel für Pixel in einem Plugin gezeichnet) - und so weiter müssen ignoriert werden.


Mit einer Krückenmethode ( na ja, wo ohne sie) wurde eine Entscheidung getroffen. Es ist einfach, die Instanz nach dem Typ zu durchsuchen, den wir benötigen - und alle anderen zu ignorieren.


Nach einer Weile wurde die Navigation im Dateisystem, das Öffnen einer Datei, das Hervorheben der Syntax, die semantische Analyse, schnelle Dokumentinformationen und die Navigation nach Code-Referenzen unterstützt (eine Kombination wie Strg + B oder Strg + MouseClick1 wurde unterstützt). Im Wesentlichen war der Editor der Desktop-Plattform sehr ähnlich.


Wie es aussah:



Also - das Webinterface mit meiner Kraft möglich zu machen. Aber es war ein sehr harter Job - der wiederholt werden musste. Und dann kam Vaadin zur Rettung.


Ich habe beschlossen, meine GWT-Implementierung zu wiederholen, um das Vaadin-Framework zu verwenden. Dieser Test erwies sich als sehr schlecht (die Leistung war sehr beeinträchtigt) - meine Erfahrung mit Vaadin wirkte sich stärker auf ihn aus und ich lehnte diese Option ab (ich habe sogar einen Hard-Reset für den aktuellen Brunch durchgeführt, um ihn zu vergessen: D).


Die Erfahrung mit Vaadin war für mich jedoch immer nützlich. Es entstand die Idee, die Benutzeroberfläche so zu vereinheitlichen, dass Sie einen Code schreiben können, aber je nach Plattform unterschiedliche Ergebnisse bei der Ausgabe erzielen.


Ein weiterer Grund für die Vereinheitlichung der Benutzeroberfläche ist der vollständige Zoo der Swing-Komponenten innerhalb der IntelliJ-Plattform. Ein Beispiel für ein solches Problem sind zwei völlig unterschiedliche Tabs- Implementierungen.




Trennen Sie die UI-Logik:


  • Frontend - eine Reihe von Schnittstellen für jedes Element, zum Beispiel consulo.ui.Button # create ()
  • Backend - plattformabhängige Implementierung
    • Swing - Desktop-Implementierung
    • WGWT - Webimplementierung

Was ist WGWT ? Akronyme für Wrapper GWT. Dies ist ein selbstgeschriebenes Framework, das den STATE der Komponente speichert und über das WebSocket an den Browser sendet (der wiederum HTML generiert). Er schrieb mit Blick auf Vaadin (ja ja - noch eine Krücke).


Die Zeit verging - und ich konnte bereits eine Test-Benutzeroberfläche starten, die auf dem Desktop und im Browser gleich funktionierte



Parallel dazu habe ich Vaadin bei der Arbeit verwendet, da dies eine der billigsten Optionen zum Erstellen einer Web-Benutzeroberfläche ist, wenn Sie Java verwenden. Ich habe Vaadin immer mehr studiert - und ich habe beschlossen, WGWT wieder in Vaadin zu schreiben, aber mit einigen Korrekturen.


Was waren die Änderungen:


  • Weigerung, fast alle Vaadin-Komponenten zu verwenden. Es gab mehrere Gründe - einer davon waren zu begrenzte Komponenten (die Anpassung war minimal).
  • Verwenden vorhandener Komponenten aus meinem WGWT-Framework, d. h. deren GWT-Implementierung
  • Es gab auch einen Patch, mit dem Sie Connect- Annotationen ohne direkten Link zur Serverkomponente schreiben konnten (dies wurde mehr für die Projektstruktur getan, um die Verfügbarkeit von Serverklassen im Clientcode zu vermeiden).

Infolgedessen stellte sich Folgendes heraus:


  • Frontend - eine Reihe von Schnittstellen für jedes Element, zum Beispiel consulo.ui.Button # create ()
  • Backend - aktuelle Implementierung je nach Plattform
    • Swing - Desktop-Implementierung
    • Vaadin - Webimplementierung
    • Android? - Damit das Telefon zu Beginn der Anwendung ausbrennt: D Bisher nur auf der Ebene der Idee, dass es möglich sein wird, einen vorhandenen Code für die Übertragung auf Android zu verwenden (da keine Bindung zu Swing besteht)

Und so wurde die aktuelle Consulo UI API geboren.


Wo wird die Consulo UI API verwendet?


  • In allen Plugins. AWT / Swing wird während der Kompilierung "blockiert" (nicht mehr java.awt.Color ) (ein Javac-Prozessor wird erstellt - später ist dies mit der Ankunft von Java 9 möglicherweise überhaupt nicht mehr erforderlich). Ihr Satz von Komponenten ist kein Allheilmittel, das verstehe ich. Im Moment können Sie Ihre eigene benutzerdefinierte UI-Komponente erstellen, bisher nur auf der Swing-Seite (und in solchen Fällen muss dem consulo.destop-Plugin eine Abhängigkeit hinzugefügt werden, um Probleme auf dem Webserver zu vermeiden). Es gibt noch keine Erstellung von Vaadin-Komponenten auf der Plugin-Seite - dies ist eine geringfügige Aufgabe.
  • Auf der Plattformseite sind dies Einstellungen / Einstellungen, Ausführungskonfigurationen, Editor - im Wesentlichen die gesamte Schnittstelle, die zum JFrame führt.

Was sind die Probleme?


  • Völlig inkompatibel mit AWT / Swing-Code (es gibt eine Krückenklasse TargetAWT / TargetVaadin, die Methoden zum Konvertieren von Komponenten enthält, auf die jedoch Plugins nicht zugreifen können).
    Alle Swing-Komponenten können nicht im Browser angezeigt werden. Daher müssen Sie den gesamten Code neu schreiben.
    Fast überall wird die Consulo-UI-API bereits innerhalb der Plattform unterstützt. Auf diese Weise können Sie das neue UI-Framework bereits in Plugins verwenden und nicht nur.
  • Die sehr starke Bindung der IntelliJ-Plattform an Swing ist so tief vergraben, dass Sie sie ohne die „nächste“ Krücke nicht ausgraben können ( )

Nach einiger Zeit


Dieser Code funktioniert auf beiden Plattformen gleich.


Seine Arbeit am Desktop:



Seine Arbeit im Browser:



In Bezug auf die oben genannten Probleme:


  • Symbole. Die Klasse consulo.ui.image.Image wurde eingeführt. Dies ist ein Bild aus dem Dateisystem (und nicht nur). Sie können die Methode consulo.ui.image.Image # create (java.net.URL) verwenden, um ein Bild hochzuladen.

Auf der Desktop-Plattform werden Symbole so geladen, wie sie zuvor geladen wurden. Erst jetzt lautet der Rückgabetyp SwingImageRef (Name der alten Klasse - früher consulo.ui.image.Image hieß consulo.ui.ImageRef) - eine Schnittstelle, die javax.swing.Icon und consulo.ui erbt .image.Image. Später wird diese Schnittstelle entfernt (ihre Existenz ist auf die vereinfachte Migration auf einen neuen Typ zurückzuführen).


Auf der Webplattform wird die URL im Objekt gespeichert und ist die Kennung für die Anzeige in der Benutzeroberfläche (über URL - / app / uiImage = URLhashCode ).


Die ImageEffects-Klasse wurde eingeführt. Es verfügt über die Methoden, die zum Erstellen abgeleiteter Symbole erforderlich sind. Zum Beispiel gibt #grayed (Bild) ein Symbol mit einem Graufilter zurück, #transparent (Bild) ein durchscheinendes Symbol.


Das heißt, der gesamte oben beschriebene Zoo wurde in enge Rahmen gefahren.


Unterstützung für das manuelle Rendern von Elementen (na ja, wo ohne dies) wird ebenfalls eingeführt. Die Methode ImageEffects # canvas (int height, int width, Consumer <Canvas2D> painterConsumer) gibt ein Symbol zurück, das über Canvas2D gezeichnet wird


On Desktop - Wrapper wird zusätzlich zu regulärem Graphics2D von Swing verwendet


Im Web - Jeder Aufruf von Canvas2D-Methoden wird gespeichert und anschließend an den Browser übertragen, in dem der interne Canvas-Code des Browsers verwendet wird


  • Aktion in UI-Thread schreiben. Oooo Es gibt noch keine Lösung für dieses Problem. Im Moment gibt es einen Prototyp einer Schreibaktion im eigenen Thread, aber bisher nur auf der Webplattform muss zu viel innerhalb der Plattform geändert werden, um sie auf dem Desktop "auszurollen".
  • Die Benutzeroberfläche wurde vereinheitlicht - kein Zoo für einfache Elemente

Ein neues Problem trat ebenfalls auf: Swing-Dialoge blockieren den Ausführungsthread während der Show. Aus diesem Grund schreibt IDEA gerne Code in folgender Form:


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

Gleichzeitig blockiert das Anzeigen von Dialogen in Vaadin den Ausführungsthread nicht.


Um Verwechslungen mit der synchronen und asynchronen Anzeige von Dialogen zu vermeiden, wurde eine asynchrone Option ausgewählt (der obige Code muss überdacht und überarbeitet werden).


Zusammenfassung


Nach einer Weile habe ich einen funktionierenden Prototyp einer Webanwendung.



Bisher ist dies ein Prototyp, der sich seiner Veröffentlichung nähert - aber er wird (leider) nicht schnell sein.

Source: https://habr.com/ru/post/de414497/


All Articles