Fortsetzung Sprechen Sie über das Schreiben von Java mit Emacs @ 2018

Dieser Artikel ist der Artikel zum 24. Tag von Emacs Advent Calandar 2018.

Hallo, ich bin's. Dieses Mal möchte ich nach der Geschichte schreiben, Java mit Emacs zu schreiben, die ich zuvor geschrieben habe. Unten finden Sie die Geschichte des Schreibens von Java in den vorherigen Emacs. http://qiita.com/mopemope/items/d1658a4ac72d85db9ccf Außerdem möchte ich ein paar Teile schreiben, die ich vorher nicht schreiben konnte.

Um Java in Emacs zu schreiben, müssen Sie mit Karon den Akerone River überqueren und durch mehrere höllische Hierarchien reisen. Und vorher möchte ich ein wenig über "lsp-mode" schreiben.

lsp-mode und lsp-java

In letzter Zeit gibt es einen Trend, LSP auch in Emacs zu verwenden. LSP, Language Server Protocol ist vom Editor unabhängig Es ist wie das Standardisieren einer Reihe von APIs, die den Back-End-Server und den Front-End-Editor verbinden. Mit anderen Worten, Sie können Ihren Lieblingseditor in eine IDE konvertieren. Sie können lsp-mode oder egolot mit Emacs verwenden. Unter diesen wird der "lsp-Modus" durch individuellere Pakete für jede Sprache bereitgestellt. Java bietet ein Paket namens lsp-java. Früher war die Installation des Back-End-Servers nicht automatisiert und die Installationskosten hoch, jetzt erfolgt die automatische Installation. Wird auch unterstützt und die Installation ist einfacher geworden. Und das Backend von lsp-java ist Eclipse. Da der Teil, der LSP spricht, zu Eclipse hinzugefügt wird, können die Funktionen und Erfolge von Eclipse unverändert verwendet werden. Es ist sehr mächtig. Daher denke ich, dass beim Schreiben von Java in Emacs häufig der lsp-Modus verwendet wird. Es ist jedoch nicht der Fall, dass wir einschließlich des Backends entwickeln. Der lsp-Modus wird stark vom VS-Code beeinflusst, einschließlich der Benutzeroberfläche, und scheint nicht dem Emacs-Weg zu folgen. Der Meghanada-Modus, den ich entwickle, ist so entwickelt, dass er dem Emacs-Weg so weit wie möglich folgt. Daher kann es mit Emacs ohne Beschwerden verwendet werden. Da das Backend auch von uns selbst entwickelt wird, können wir flexibel reagieren.

Reden wir über die Hölle.

Typanalyse Hölle

Übrigens wurde in der zuvor vorgestellten Version die gesamte Typanalyse von mir selbst implementiert. Welche Klasse ist der Symboltyp in der Quelle? Ich habe nur die Informationen analysiert, aber es war schwierig wegen der Geschwindigkeitsprobleme und der Mauer von Generisc. Nicht nur einfache Symbole, sondern auch der Rückgabewert jedes Methodenaufrufs müssen analysiert werden, damit der Abschluss funktioniert. Viele der Ursachen für Schwierigkeiten sind die verwirrenden "Lambda" - und "Stream" -API "Collect".

Die Definition von "sammeln" ist wie folgt.

<R,A> R	collect(Collector<? super T,A,R> collector)

Ein übliches Muster besteht darin, die Methode der Klasse "Collectors" auf diese anzuwenden, aber die linke Seite kann nur bekannt sein, wenn der Typ der darin enthaltenen Methode zuerst aufgelöst wird. Es mag einfach sein, aber in einigen Fällen gibt es auch Dinge wie "groupingBy", die "Lambda" als Argument nehmen, was das Höllen-Tor leicht öffnet.

static <T,K,A,D> Collector<T,?,Map<K,D>> groupingBy(Function<? super T,? extends K> classifier, Collector<? super T,A,D> downstream)

Wenn vier Typparameter angezeigt werden, steigt die Schärfe. Darüber hinaus ist das folgende Muster ein schwieriger Fall mit "Lambda".


.map(m -> {
    if (m.isActive()) {
        return b;
    }
    return a;
})
.filter(s -> s.enable())

Wenn "a" und "b" unterschiedlichen Typs sind, muss der Rückgabewert von "map" eine Klasse ableiten, die ein gemeinsamer Begriff von "a" und "b" ist. Sie müssen auch das Vorhandensein oder Fehlen von "Block" und "Methodenreferenz" berücksichtigen. Um ehrlich zu sein, ist es eine schwierige Geschichte, dies zu analysieren.

Wer kennt den Typ?

Was ist der beste Weg, um dieses Problem zu lösen? Die Antwort ist einfach. Der Compiler sollte alle Typen kennen. Sie müssen also nur die Ergebnisse der Typanalyse vom Compiler abrufen. meghanada lässt den Compiler die Typanalyse durchführen. Es greift auf die vom Compiler ausgegebene AST-Spucke zu, liest das daran hängende Typensymbol und ermittelt den Typ jedes Symbols, den Rückgabetyp der Methode usw. In vielen Entwicklungsumgebungen werden beim Speichern einer Datei zwei Prozesse ausgeführt: Analyse und Kompilierung. Gleichzeitig ist dies jedoch effizienter und schneller. (Obwohl die Kompilierungsgeschwindigkeit durch den Analyseaufwand verringert wird, ist sie überwiegend schneller als die Einzelanalyse.) Dies ermöglicht eine reibungslose Typprüfung und Anzeige von Kompilierungsfehlern auch mit der "Flycheck" -Prüfung. Die Analysezeit beträgt ungefähr 200 ms, aber da sie asynchron ausgeführt wird, stört mich das nicht sonderlich. Wenn Sie den Typ kennen und den Typ auf der linken Seite lesen können, werden vorzugsweise die Abschlusskandidaten angezeigt, die dem Typ auf der linken Seite entsprechen.

Startgeschwindigkeit Hölle

Im Fall des "Gradle" -Projekts wird das Modell von "Gradle" von der "Gradle Tooling API" und die Abhängigkeit, der Quellpfad usw. von dieser erhalten. Um die "Gradle Tooling API" zu verwenden, wird der "Gradle Daemon" verwendet. Sie müssen starten. In einem kleinen Projekt stört es Sie vielleicht nicht, aber in einem großen Projekt wie "elasticsearch" dauert das Laden und Auswerten der Build-Datei beim Starten dieses Daemons viel Zeit. Darüber hinaus ist es notwendig, die Reihenfolge des Bauens unter Berücksichtigung der Abhängigkeit zwischen Teilprojekten zu analysieren und zu berücksichtigen. Da es sich um einen Daemon handelt, scheint es, dass es nur beim ersten Start Zeit braucht, aber wenn es Zeit für jeden Start braucht, ist es ziemlich schwerwiegend. meghanada` enthält Projektabhängigkeiten als unabhängige Zwischendaten. Das Ergebnis der einmal analysierten Build-Datei wird in diese Datenklasse konvertiert und zwischengespeichert. (Dies liegt auch daran, dass mehrere Build-Tools unterstützt werden.) Nach der Analyse beim ersten Start wird der analysierte Cache beim nächsten Start verwendet. Die "Gradle Tooling API" wird erst verwendet, wenn sie benötigt wird, sodass sie schnell gestartet werden kann. Dank der Übernahme von Zwischendaten ist es jetzt möglich, verschiedene Projekte wie Maven, Gradle, Eclipse und Originalprojekte zu unterstützen. Da es nicht auf Eclipse basiert, unterstützt Meghanada auch Android-Projekte.

Formatiere die Hölle

Die Formatierung ist ebenfalls ein ärgerliches Problem. Früher wurde nur das Google Java-Format unterstützt, jetzt wird auch das Eclipse-Format unterstützt. Wie beim Formatierertyp ist es wichtiger, den zu bearbeitenden Puffer ohne Probleme beim Speichern zu formatieren. ist. In dieser Hinsicht war der "Go-Modus" sehr hilfreich. Wir haben eine Möglichkeit entwickelt, das Patch anzuwenden, um den Cursor nicht so weit wie möglich von dem durch diff erzeugten Patch zu verschieben. Ich habe die Google Assistant-App für Android Things geschrieben, die auch für Emacs geschrieben wurde.


(defun meghanada--apply-rcs-patch (patch-buffer)
  "Apply an RCS-formatted diff from PATCH-BUFFER to the current buffer."
  (let ((target-buffer (current-buffer))
        ;; Relative offset between buffer line numbers and line numbers
        ;; in patch.
        ;;
        ;; Line numbers in the patch are based on the source file, so
        ;; we have to keep an offset when making changes to the
        ;; buffer.
        ;;
        ;; Appending lines decrements the offset (possibly making it
        ;; negative), deleting lines increments it. This order
        ;; simplifies the forward-line invocations.
        (line-offset 0))
    (save-excursion
      (with-current-buffer patch-buffer
        (goto-char (point-min))
        (while (not (eobp))
          (unless (looking-at "^\\([ad]\\)\\([0-9]+\\) \\([0-9]+\\)")
            (error "Invalid rcs patch or internal error in apply-rcs-patch"))
          (forward-line)
          (let ((action (match-string 1))
                (from (string-to-number (match-string 2)))
                (len  (string-to-number (match-string 3))))
            (cond
             ((equal action "a")
              (let ((start (point)))
                (forward-line len)
                (let ((text (buffer-substring start (point))))
                  (with-current-buffer target-buffer
                    (cl-decf line-offset len)
                    (goto-char (point-min))
                    (forward-line (- from len line-offset))
                    (insert text)))))
             ((equal action "d")
              (with-current-buffer target-buffer
                (meghanada--goto-line (- from line-offset))
                (cl-incf line-offset len)
                (meghanada--delete-whole-line len)))
             (t
              (error "Invalid rcs patch or internal error in apply-rcs-patch")))))))))

Netzwerkprozess Hölle

Dies ist das größte Problem. Bei der Konvertierung von Emacs in IDE wird nur die Handhabung der Interaktion mit dem Back-End unterstützt. Vim unterstützt jetzt auch die asynchrone Kommunikation, Emacs unterstützt jedoch seit langem die asynchrone Kommunikation. Deshalb benutze ich Emacs. Durch die Verwendung dieser asynchronen Kommunikation ist es möglich, reibungslos mit dem Back-End-Server zu interagieren und eine leistungsstarke Bearbeitungsverarbeitung durchzuführen, ohne den Bearbeitungsprozess zu beeinträchtigen. Dieser Teil ist alle von Ironie inspiriert. Ohne Ironie-Modus hätte ich dieses Paket auch nicht erstellt. Der allgemeine Fluss ist wie folgt.

  1. Socket-Verbindung mit dem Backend-Server
  2. Geben Sie einen Befehl aus
  3. Stellen Sie eine Anfrage an den Backend-Server
  4. Nummerieren Sie die Anfrage
  5. Stellen Sie den angeforderten Rückrufprozess in eine Warteschlange an den Back-End-Server
  6. Senden Sie eine Anfrage an den Backend-Server
  7. Verarbeiten Sie auf dem Backend-Server und geben Sie eine Antwort zurück
  8. Ordnen Sie die zurückgegebene Antwort dem Rückruf zu
  9. Führen Sie die Verarbeitung mit Rückruf durch

2-9 werden im Backend asynchron ausgeführt. Die Nummerierung erfolgt zum Zeitpunkt der Anfrage. Dies ist ein asynchroner Prozess, um den Fall zu behandeln, wenn eine Anforderung vorliegt, die die vorherige Anforderung überholt und schnell eine Antwort zurückgibt. (Derzeit synchronisiert Meghanada auch die asynchrone Verarbeitung, sodass sie einfach verarbeitet werden kann.) Die Befehle werden nacheinander asynchron ausgeführt, damit sie die Bearbeitung im Editor nicht beeinträchtigen. In einigen Fällen ist es besser, synchron zu laufen, daher unterstützen wir beide.

Zusammenfassung

Nun, wir entwickeln es seit 2016, aber die Verarbeitungsgeschwindigkeit und -genauigkeit haben sich im Vergleich zu zuvor erheblich verbessert. Die einfache Installation und Konfiguration wurde lange Zeit evaluiert. Wie wäre es damit für diejenigen, die denken, dass lsp-java nicht passt?

Recommended Posts

Fortsetzung Sprechen Sie über das Schreiben von Java mit Emacs @ 2018
Die Geschichte des Schreibens von Java in Emacs
Über Java Abstract Class
Die Geschichte der Verwendung von Java Input Waiting (Scanner) mit VSCode
Informationen zur Dateikopierverarbeitung in Java
Informationen zum Zurückgeben einer Referenz in einem Java Getter
[Erstellen] Ein Memorandum über das Codieren in Java
Informationen zu Datensätzen, die zur Vorschau in Java JDK 14 hinzugefügt wurden
Lesen und Schreiben von GZIP-Dateien in Java
Über die Java-Schnittstelle
[Java] Informationen zu Java 12-Funktionen
Über die Verwirrung beim Starten von Java-Servern
Über die Idee anonymer Klassen in Java
Eine Geschichte über das JDK in der Java 11-Ära
Partisierung in Java
[Java] Über Arrays
Versuchen Sie, etwa 30 Zeilen in Java zu kratzen (CSV-Ausgabe)
Änderungen in Java 11
Janken in Java
Unterschiede beim Schreiben von Java-, C # - und Javascript-Klassen
Wo ist mit Java?
Informationen zu Java-Funktionen
Über Java-Threads
[Java] -Schnittstelle
Über die Java-Klasse
Informationen zu Java-Arrays
Über Java-Vererbung
Über Schnittstelle, Java-Schnittstelle
Umfangsrate in Java
FizzBuzz in Java
Über Java Var
Über Java Literal
Informationen zu Java-Befehlen
Ich habe leicht verständlich über Java Downcast geschrieben
[Java] Hinweise zur Typinferenz in Java 10
Denken Sie an das JAVA = JAVAscript-Problem (wird in Zukunft benötigt).
Informationen zur Konvertierung von Zeichenfolgen in Java in voller Breite und halber Breite
Informationen zur Java-Protokollausgabe
Informationen zur Java-Funktionsschnittstelle
Lesen Sie JSON in Java
Interpreter-Implementierung durch Java
Machen Sie einen Blackjack mit Java
Java, über zweidimensionales Array
Janken App in Java
Einschränkungsprogrammierung in Java
Über die Klassenteilung (Java)
Setzen Sie Java8 in Centos7
NVL-artiger Typ in Java
Verbinden Sie Arrays in Java
Über [Java] [StreamAPI] allMatch ()
Informationen zur Java StringBuilder-Klasse
"Hallo Welt" in Java
Über Ruby Hash (Fortsetzung)
[Java] Über Singleton Class
Kommentare in der Java-Quelle
Azure funktioniert in Java
Informationen zur Bindung von Java-Methoden