A Methodical Migration: Replicating Modern IDE Features for Scala and Kotlin in Emacs

By systematically configuring the Metals and Kotlin Language Servers with Eglot, Emacs can be transformed into a surprisingly powerful and lightweight environment for JVM development. This approach, centered on a standardized protocol, allows developers to unbundle the features of a monolithic Integrated Development Environment (IDE) and reconstruct them within a text editor renowned for its extensibility and efficiency. The result is a consistent, resource-conscious workspace that handles disparate languages with uniform grace.

First Principles: The Language Server Protocol as a Universal Translator

Before any configuration can be undertaken, one must first understand the foundational technology that makes it possible: the Language Server Protocol (LSP). Initially developed by Microsoft for Visual Studio Code, LSP is an open standard that defines a common protocol for communication between code editors and language-specific "servers." These servers are standalone executables that provide code intelligence features.

Historically, adding rich support for a new programming language to a new editor was a task of significant redundant effort. Each editor implemented its own APIs for features like code completion, syntax checking, and definition lookups. Supporting N languages across M editors resulted in a combinatorial explosion of N-times-M implementation projects. LSP elegantly solves this problem by introducing a layer of abstraction.

The protocol decouples the editor front-end from the language analysis back-end. A language community now needs to build only one language server. Any editor that implements an LSP client can then communicate with that server using a standardized set of JSON-RPC messages to provide a rich editing experience. The editor is responsible for the user interface, while the server handles the complex, language-specific logic of parsing and analyzing code. This architecture allows a single server, like the Metals server for Scala, to service clients in Emacs, VS Code, Vim, and others with no modification.

The Emacs Client: An Introduction to Eglot

For Emacs users, several LSP clients are available, but Eglot (an acronym for "Emacs Polyglot") stands out for its minimalist philosophy. Since its inclusion as a standard package in Emacs 29, Eglot has become a popular choice for developers seeking powerful features without intrusive changes to their workflow.

Eglot’s design principle is to act as a lightweight bridge, translating LSP server messages into actions handled by existing, battle-tested Emacs subsystems. Instead of building its own user interface for displaying errors, for example, Eglot pipes diagnostics to Emacs's built-in Flymake framework. Instead of creating a custom pop-up for function signatures, it populates the standard Eldoc buffer. This approach contrasts with more feature-heavy clients that tend to create their own, often-opinionated, UI components.

The mechanism is straightforward. When a user opens a file associated with a known language, Eglot searches for a project root (e.g., a build.sbt or pom.xml file). Upon finding one, it consults a registry to determine the correct language server to launch. It then starts the server as a background process and manages the asynchronous communication, seamlessly integrating server-provided data into the native Emacs experience.

Case Study 1: Integrating the Metals Scala Language Server

Scala, a powerful but notoriously complex language for compilers to parse, is an excellent test case for LSP's utility. The official language server, Metals, is a sophisticated tool that leverages a separate build server daemon, Bloop, to provide fast, incremental compilation and diagnostic feedback.

Configuring Emacs to use Metals via Eglot is a matter of ensuring the prerequisite modes are installed and associated with Eglot. A typical configuration using the use-package macro might look like this:

(use-package scala-mode
  :ensure t
  :interpreter
  ("scala" . scala-mode))

(use-package eglot
  :ensure t
  :config
  (add-to-list 'eglot-server-programs '(scala-mode . ("metals"))))

With this in place, opening a Scala file within a project managed by a supported build tool (like sbt or Mill) prompts Eglot to start Metals. The first time this happens in a new project, Metals performs a one-time indexing step. Subsequently, the developer gains access to a suite of features that rival a dedicated IDE, including:

  • Real-time error highlighting and diagnostics.
  • Code completion with type-aware suggestions.
  • On-hover display of inferred types and documentation.
  • Go-to-definition, which navigates directly to the source of a class, method, or value.
  • Find-references to locate all usages of a symbol across the project.

Herein lies the beauty of the LSP model: its universality. A developer invests in learning one set of editor-agnostic concepts—go-to-definition, find-references—and these skills translate across languages. The cognitive load of working in a polyglot environment is dramatically reduced.

Case Study 2: Configuring the Kotlin Language Server

A similar process enables support for Kotlin, another prominent JVM language. The Kotlin Language Server is a separate project, typically installed as a standalone binary made available on the system's PATH. Once the server executable is installed and kotlin-mode is configured in Emacs, Eglot can be instructed to use it.

The configuration mirrors the one for Scala, associating kotlin-mode with the appropriate server command:

(use-package kotlin-mode
  :ensure t)

(use-package eglot
  ;; Assuming Eglot is already configured from the Scala example
  :config
  (add-to-list 'eglot-server-programs '(kotlin-mode . ("kotlin-language-server"))))

When a Kotlin file within a Gradle or Maven project is opened, Eglot invokes the server. The resulting feature set is largely parallel to that provided by Metals for Scala, offering robust completion, navigation, and diagnostics. The user experience remains consistent, with the same keybindings and UI conventions applying to both languages. Performance is generally excellent, though some users report minor differences in the comprehensiveness of certain features. (The Kotlin server, while functional, occasionally reminds the user of its comparative youth next to the more mature Metals project.)

An Assessment of the Unbundled IDE

The outcome of this methodical configuration is a single, coherent development environment capable of handling two distinct and complex JVM languages. By delegating language intelligence to specialized, out-of-process servers, Emacs remains lean and responsive. The editor itself is not burdened with the overhead of parsing Scala's implicit-heavy syntax or Kotlin's extension functions; it simply renders the results provided by the servers.

This approach is not without trade-offs. While core editing and navigation are superb, functionality that is deeply integrated in a monolithic IDE—such as graphical debuggers, advanced profiling tools, or complex, language-aware refactoring wizards—may be less seamless or require separate configuration. The unbundled model prioritizes control, performance, and cross-language consistency over the all-in-one convenience of a product like IntelliJ IDEA.

This approach is part of a clear trend toward protocol-driven tooling, where developers build bespoke, composite environments from best-in-class components. LSP acted as the catalyst for this movement in the local editing space, proving that a powerful, unified experience is possible without being locked into a single vendor's ecosystem.

Ultimately, the viability of an LSP-based setup depends on the developer's specific needs. For a significant portion of coding, refactoring, and debugging tasks, the combination of Emacs, Eglot, and high-quality language servers presents a compellingly powerful and efficient alternative to the traditional IDE. This migration from monolithic applications to modular, protocol-interconnected systems is not an isolated phenomenon; it reflects a broader architectural shift in the design of developer tools, one that will likely continue to reshape how software is built for years to come.