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