Discussion continue sur l'écriture de Java avec Emacs @ 2018

Cet article est l'article du 24ème jour de Emacs Advent Calandar 2018.

Salut, c'est moi. Cette fois, j'aimerais écrire après l'histoire de l'écriture de Java avec Emacs que j'ai écrite plus tôt. Voir ci-dessous pour l'histoire de l'écriture de Java dans le précédent Emacs. http://qiita.com/mopemope/items/d1658a4ac72d85db9ccf Aussi, j'aimerais écrire quelques parties que je ne pouvais pas écrire auparavant.

Pour écrire Java dans Emacs, il faut traverser la rivière Akerone avec Karon et parcourir plusieurs hiérarchies infernales. Et avant cela, j'aimerais écrire un peu sur lsp-mode.

lsp-mode et lsp-java

Récemment, il y a eu une tendance à utiliser LSP dans Emacs également. LSP, Language Server Protocol est indépendant de l'éditeur C'est comme normaliser un ensemble d'API qui connectent le serveur principal et l'éditeur frontal. En d'autres termes, vous pourrez convertir votre éditeur préféré en un IDE. Vous pouvez utiliser lsp-mode ou egolot avec Emacs. Parmi eux, lsp-mode est fourni par des packages plus personnalisés pour chaque langue. Java fournit un package appelé lsp-java. Auparavant, l'installation du serveur principal n'était pas automatisée et le coût d'installation était élevé, mais il s'agit maintenant d'une installation automatique. Est également pris en charge et l'installation est devenue plus facile. Et le backend de lsp-java est Eclipse. Puisque la partie qui parle LSP est ajoutée à Eclipse, les fonctions et réalisations d'Eclipse peuvent être utilisées telles quelles. C'est très puissant. Par conséquent, lors de l'écriture de Java dans Emacs, je pense que le mode lsp est souvent utilisé. Cependant, ce n'est pas le cas que nous développons notamment le back-end. Et le mode lsp est très influencé par VS Code, y compris l'interface utilisateur, et il ne semble pas suivre la voie Emacs. Le mode meghanada que je développe est développé de manière à suivre autant que possible la voie Emacs. Par conséquent, il peut être utilisé avec Emacs sans aucune gêne. Étant donné que le back-end est également développé par nous-mêmes, nous pouvons répondre de manière flexible.

Parlons de l'enfer.

L'enfer de l'analyse de type

À propos, dans la version introduite précédemment, toutes les analyses de type ont été implémentées par moi-même. Quelle classe est le type de symbole sur la source? J'analysais juste les informations, mais c'était difficile à cause des problèmes de vitesse et du mur de Generisc. Non seulement les symboles simples, mais également la valeur de retour de chaque appel de méthode doivent être analysés pour que l'achèvement fonctionne. Plusieurs des causes de difficulté sont la confusion de l'API «lambda» et «stream» «collect».

La définition de «collect» est la suivante.

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

Un modèle courant consiste à appliquer la méthode de la classe Collectors à cela, mais le côté gauche ne peut être connu que si le type de la méthode à l'intérieur est résolu en premier. Cela peut être simple, mais dans certains cas, il y a aussi des choses comme groupingBy qui prennent lambda comme argument, ce qui ouvre facilement la porte de l'enfer.

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

Lorsque quatre paramètres de type apparaissent, le piquant augmente. De plus, le modèle suivant est un cas difficile avec «lambda».


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

Ici, si «a» et «b» sont de types différents, la valeur de retour de «map» doit dériver une classe qui est un terme commun de «a» et «b». Vous devez également tenir compte de la présence ou de l'absence de «blocage» et de «référence de méthode». Pour être honnête, c'est une histoire difficile à analyser.

Qui connaît le type?

Alors, quelle est la meilleure façon de résoudre ce problème? La réponse est simple. Le compilateur doit connaître tous les types, donc tout ce que vous avez à faire est d'obtenir le résultat de l'analyse de type du compilateur. meghanada laisse le compilateur faire l'analyse de type. Accédez à l''AST 'craché par le compilateur, lisez le' symbole de type 'qui y est suspendu, et obtenez le type de chaque symbole, le type de la valeur de retour de la méthode, et ainsi de suite. Dans de nombreux environnements de développement, lors de l'enregistrement d'un fichier, il effectue deux processus, l'analyse et la compilation, mais en faisant cela en même temps, il est plus efficace et plus rapide. (Bien que la vitesse de compilation soit réduite par la quantité d'analyse, elle est extrêmement plus rapide que l'analyse individuelle) Cela permet d'effectuer en douceur la vérification de type et l'affichage des erreurs de compilation même avec la vérification flycheck. Le temps d'analyse prend environ 200 ms, mais comme il est exécuté de manière asynchrone, cela ne me dérange pas beaucoup. En connaissant le type, si le type sur le côté gauche peut être lu, les candidats de complétion qui correspondent au type sur le côté gauche sont affichés préférentiellement.

L'enfer de la vitesse de démarrage

Dans le cas du projet gradle, le modèle de gradle est obtenu à partir de l''API d'outillage gradle, et la dépendance, le chemin source, etc. sont obtenus à partir de celui-ci. Vous devez lancer . Cela ne vous dérangera peut-être pas dans un petit projet, mais dans un grand projet tel que ʻelasticsearch`, cela prend un temps considérable juste pour charger et évaluer le fichier de construction lors du lancement de ce démon. De plus, il est nécessaire d'analyser et de considérer l'ordre de construction en tenant compte de la dépendance entre les sous-projets. Puisqu'il s'agit d'un démon, il semble que cela ne prenne du temps qu'au premier démarrage, mais si cela prend du temps à chaque démarrage, c'est assez sévère. «meghanada» contient les dépendances de projet comme des données intermédiaires indépendantes. Le résultat du fichier de construction une fois analysé est converti dans cette classe de données et mis en cache. (C'est aussi parce qu'il prend en charge plusieurs outils de construction) Après analyse au premier démarrage, le cache analysé sera utilisé au prochain démarrage. L'API `` gradle tooling '' n'est pas utilisée tant qu'elle n'est pas nécessaire, elle peut donc être démarrée rapidement. Grâce à l'adoption de données intermédiaires, il est désormais possible de soutenir divers projets tels que Maven, Gradle, Eclipse, et des projets originaux. Comme il n'est pas basé sur Eclipse, meghanada prend également en charge les projets Android.

Format enfer

Le formatage est également un problème ennuyeux. Auparavant, il ne prenait en charge que le format Google Java, mais maintenant il prend également en charge le format Eclipse. Comme pour le type de formateur, le plus important est de savoir comment formater le tampon que vous éditez sans aucune gêne lors de son enregistrement. est. À ce propos, «go-mode» a été très utile. Nous avons imaginé un moyen d'appliquer le patch afin de ne pas déplacer le curseur du patch généré par diff autant que possible. J'écrivais l'application Google Assistant sur Android Things, qui était également écrite sur Emacs.


(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")))))))))

Enfer de processus réseau

C'est le plus gros problème. Lors de la conversion d'Emacs en IDE, ce qui prend en charge tout, c'est comment gérer l'interaction avec le back-end. Vim prend désormais également en charge la communication asynchrone, mais Emacs prend depuis longtemps en charge la communication asynchrone. C'est ce qui me fait utiliser Emacs. En utilisant cette communication asynchrone, il est possible d'interagir en douceur avec le serveur principal et d'effectuer un traitement d'édition puissant sans interférer avec le processus d'édition. Cette partie est toute inspirée de l'ironie. Sans le mode ironique, je n'aurais pas non plus créé ce package. Le flux général est le suivant.

  1. Connexion socket avec le serveur backend
  2. Émettez une commande
  3. Faites une demande au serveur backend
  4. Numérotez la demande
  5. Mettez en file d'attente le processus de rappel demandé au serveur principal
  6. Envoyez une demande au serveur principal
  7. Traitez sur le serveur principal et renvoyez une réponse
  8. Faites correspondre la réponse renvoyée avec le rappel
  9. Effectuer le traitement avec rappel

2-9 s'exécutent de manière asynchrone sur le backend. La numérotation est effectuée au moment de la demande. Il s'agit d'un processus asynchrone pour gérer le cas où une demande dépasse la demande précédente et renvoie une réponse rapidement. (Pour le moment, meghanada synchronise également le traitement asynchrone afin qu'il puisse être traité simplement) Les commandes sont exécutées de manière séquentielle de manière asynchrone afin de ne pas interférer avec l'édition sur l'éditeur. Dans certains cas, il est préférable de fonctionner de manière synchrone, nous prenons donc en charge les deux.

Résumé

Eh bien, nous le développons depuis 2016, mais la vitesse de traitement et la précision se sont considérablement améliorées par rapport à avant. La facilité d'installation et de configuration a longtemps été évaluée. Que diriez-vous de l'utiliser pour ceux qui pensent que lsp-java ne convient pas?

Recommended Posts

Discussion continue sur l'écriture de Java avec Emacs @ 2018
L'histoire de l'écriture de Java dans Emacs
À propos de la classe abstraite Java
L'histoire de l'utilisation de Java Input Wait (Scanner) avec VSCode
A propos du traitement de la copie de fichiers en Java
A propos du renvoi d'une référence dans un Java Getter
[Création] Un mémorandum sur le codage en Java
À propos des enregistrements ajoutés pour l'aperçu dans Java JDK 14
Lire et écrire des fichiers gzip en Java
À propos de l'interface Java
[Java] À propos des fonctionnalités de Java 12
À propos de la confusion observée dans les serveurs Java de démarrage
À propos de l'idée des classes anonymes en Java
Une histoire sur le JDK à l'ère de Java 11
Partition en Java
[Java] À propos des tableaux
Essayez de gratter environ 30 lignes en Java (sortie CSV)
Changements dans Java 11
Janken à Java
Différences dans l'écriture des classes Java, C # et Javascript
Où est Java
À propos des fonctionnalités Java
À propos des threads Java
Interface [Java]
À propos de la classe Java
À propos des tableaux Java
À propos de l'héritage Java
À propos de l'interface, interface java
Taux circonférentiel à Java
FizzBuzz en Java
À propos de Java Var
À propos de Java Literal
À propos des commandes Java
J'ai écrit sur Java downcast d'une manière facile à comprendre
[Java] Points à noter sur l'inférence de type étendue dans Java 10
Pensez au problème JAVA = JAVAscript (nécessaire à l'avenir)
À propos de la conversion pleine largeur ⇔ demi-largeur des chaînes de caractères en Java
À propos de la sortie du journal Java
À propos de l'interface fonctionnelle Java
Lire JSON en Java
Implémentation de l'interpréteur par Java
Faites un blackjack avec Java
Java, à propos d'un tableau à deux dimensions
Application Janken en Java
Programmation par contraintes en Java
À propos de la division de classe (Java)
Mettez java8 dans centos7
NVL-ish guy en Java
Joindre des tableaux en Java
À propos de [Java] [StreamAPI] allMatch ()
À propos de la classe Java StringBuilder
"Hello World" en Java
À propos de Ruby Hash (suite)
[Java] À propos de la classe Singleton
Commentaires dans la source Java
Fonctions Azure en Java
À propos de la liaison de méthode Java