Continued Talk about writing Java in Emacs @ 2018

This article is the 24th day article of Emacs Advent Calandar 2018.

Hi, it ’s me. This time, I would like to write after the story of writing Java in Emacs that I wrote earlier. See below for the story of writing Java in previous Emacs. http://qiita.com/mopemope/items/d1658a4ac72d85db9ccf Also, I would like to write a little about the parts that I couldn't write before.

To write Java in Emacs, you have to cross the Acheron River with Karon and travel through several hellish hierarchies. And before that, I would like to write a little about lsp-mode.

lsp-mode and lsp-java

Recently, there is a trend to use LSP in Emacs as well. LSP, Language Server Protocol is editor independent It's like standardizing the APIs that connect the back-end server and the front-end editor. In other words, you will be able to convert your favorite editor into an IDE. You can use lsp-mode or egolot in Emacs. Among them, lsp-mode is provided by a more customized package for each language. Java provides a package called lsp-java. Previously, back-end server installation was not automated and the installation cost was high, but now it is automatic installation. Is also supported and installation has become easier. And the backend of lsp-java is Eclipse. Since the part that speaks LSP is added to Eclipse, the functions and achievements of Eclipse can be used as it is. It's very powerful. Therefore, when writing Java in Emacs, I think that lsp-mode is often used. However, it is not the case that we are developing including the back end. And lsp-mode is very much influenced by VS Code, including the UI, and it doesn't seem to follow the Emacs Way. The meghanada-mode I am developing is developed in a way that follows the Emacs Way as much as possible. Therefore, it can be used with Emacs without any discomfort. Since the back end is also developed by ourselves, we can respond flexibly.

Let's talk about hell.

Type analysis hell

By the way, in the version introduced before, all type analysis was implemented by myself. What class is the symbol type on the source? I was just analyzing the information, but it was difficult because of speed problems and the wall of Generisc. Not only simple symbols, but also the return value of each method call must be parsed for completion to work. Many of the causes of difficulty are the lambda and Stream API collect being confusing.

The definition of collect is as follows.

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

A common pattern is to apply the method of the Collectors class to this, but the left side cannot be known unless the type of the method inside is resolved first. It may be simple, but in some cases, there are also things such as groupingBy that take lambda as an argument, which easily opens the hell gate.

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

When four Type Parameters appear, the spiciness goes up. Also, the following pattern is a painful case with lambda.


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

Here, if ʻaandb are of different types, the return value of map must derive a class that is a common term of ʻa and b. You also have to consider the presence or absence of block and method refernce. To be honest, it's a difficult story to analyze this.

Who knows the type?

So what's the best way to solve this problem? The answer is simple. The compiler should know all the types, so all you have to do is get the type analysis results from the compiler. meghanada lets the compiler do the type parsing. Access the ʻASTspit out by the compiler, read thetype symbolhanging from it, and get the type of each symbol, the return type of the method, and so on. In many development environments, when saving a file, analysis and compilation are performed, but by doing this at the same time, efficiency and speed are increased. (Although the compilation speed is reduced by the amount of analysis, it is overwhelmingly faster than individual analysis) As a result, even with theflycheck` check, type checking and compilation errors can be displayed smoothly. The analysis time takes about 200ms, but since it is executed asynchronously, it doesn't bother me much. By knowing the type, if the type on the left side can be read, the completion candidates that match the type on the left side are preferentially displayed.

Startup speed hell

In the case of gradle project, I get the model of gradle from gradle tooling API and get the dependency, source path, etc. from it. To use gradle tooling API, gradle daemon You need to launch . It may not bother you in a small project, but in a large project such as ʻelasticsearch, it takes a considerable amount of time just to load and evaluate the build file when launching this daemon. In addition, it is necessary to analyze and consider the build order in consideration of the dependency between subprojects. Since it is a daemon, it seems that it takes time only at the first startup, but if it takes time for each startup, it is quite severe. meghanadaholds project dependencies etc. as independent intermediate data. The result of the build file once analyzed is converted to this data class and cached. (This is also because it supports multiple build tools) After analyzing at the first startup, the analyzed cache will be used at the next startup. Thegradle tooling API` is not used until it is needed, so it can be started quickly. Thanks to the adoption of intermediate data, it is now possible to support various projects such as Maven, Gradle, Eclipse, and original projects. Since it is not based on Eclipse, meghanada actually supports Android projects as well.

Format hell

Formatting is also an annoying problem. Previously it only supported Google Java Format, but now it also supports Eclipse Format. As with the formatter type, more importantly, how can the buffer you're editing be formatted comfortably when you save it? is. Regarding this, go-mode was very helpful. I am trying to apply the patch so as not to shift the cursor from the patch generated by diff as much as possible. I was writing the Google Assistant app on Android Things, which was also written on 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")))))))))

Network process hell

This is the biggest problem. When converting Emacs to an IDE, what supports everything is how to handle the interaction with the back end. Vim now supports asynchronous communication, but Emacs has long supported asynchronous communication. This is what makes me use Emacs. By utilizing this asynchronous communication, it is possible to smoothly interact with the back-end server and perform powerful editing processing without interfering with the editing process. This part is all inspired by irony. Without irony-mode I wouldn't have created this package either. The general flow is as follows.

  1. Socket connection with backend server
  2. Issue a command
  3. Make a request to the backend server
  4. Number the request
  5. Queue the callback process requested to the backend server
  6. Send a request to the backend server
  7. Process on the backend server and return a response
  8. Match the returned response with the callback
  9. Perform processing with callback

2-9 run asynchronously on the backend. Numbering is done at the time of request. This is asynchronous processing, and it corresponds to the case when there is a request that overtakes the previous request and returns a response quickly. (For the time being, meghanada also synchronizes asynchronous processing so that it can be processed simply) The commands are executed asynchronously sequentially so that they do not interfere with editing in the editor. In some cases it is better to run synchronously, so we support both.

Summary

Well, we have been developing it since 2016, but the processing speed and accuracy have improved significantly compared to before. The ease of installation and configuration has long been evaluated. How about using it if you think that lsp-java does not fit?

Recommended Posts

Continued Talk about writing Java in Emacs @ 2018
The story of writing Java in Emacs
About abstract classes in java
Talk about using Java input wait (Scanner) in VS Code
About file copy processing in Java
About returning a reference in a Java Getter
[Creating] A memorandum about coding in Java
About Records preview added in Java JDK 14
Reading and writing gzip files in Java
About Java interface
[Java] About Java 12 features
About the confusion seen in startup Java servers
About the idea of anonymous classes in Java
A story about the JDK in the Java 11 era
Partization in Java
[Java] About arrays
Try scraping about 30 lines in Java (CSV output)
Changes in Java 11
Rock-paper-scissors in Java
Differences in writing Java, C # and Javascript classes
Where about java
About Java features
About Java threads
[Java] About interface
About Java class
About Java arrays
About java inheritance
About interface, java interface
Pi in Java
FizzBuzz in Java
About List [Java]
About java var
About Java literals
About Java commands
I wrote about Java downcast in an easy-to-understand manner
[Java] Things to note about type inference extended in Java 10
Think about the JAVA = JAVAscript problem (needed in the future)
About full-width ⇔ half-width conversion of character strings in Java
About Java log output
About Java functional interface
[java] sort in list
Read JSON in Java
Interpreter implementation in Java
Make Blackjack in Java
Java, about 2D arrays
Rock-paper-scissors app in Java
Constraint programming in Java
About class division (Java)
Put java8 in centos7
NVL-ish guy in Java
Combine arrays in Java
About [Java] [StreamAPI] allMatch ()
About Java StringBuilder class
"Hello World" in Java
About Ruby Hashes (continued)
About eval in Ruby
[Java] About Singleton Class
About emacs, qiita, git
Comments in Java source
Azure functions in java
About Java method binding