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.
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.
Ü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.
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.
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.
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")))))))))
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.
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.
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