At CodeSignal we’ve built an IDE that runs in the cloud to provide a seamless live interview experience. Our goal was to include all the familiar features of the most popular desktop IDEs, so candidates wouldn’t be missing their favorite code-editing tools on top of trying to perform well in an interview. One of these features is autocompletion (AKA code completion), which provides smart suggestions to fill in what you’ve just started typing.
Autocompletion has to understand the syntax of a programming language to know how to provide smart suggestions. A handful of other important IDE features, such as finding references or jumping to a definition, are also language-dependent. These capabilities need to be blazing fast in CodeSignal’s IDE, because the last thing you want in an interview is to get tripped up by a laggy experience. In this article, we’ll explain how we achieve fast, reliable autocompletion and other language-specific features with the help of language servers.
Problem: Autocompletion in the cloud
How does autocompletion work in a standard IDE? Well, to make sure that this function can change based on the language and doesn’t interfere with the main UI thread, autocompletion runs in a separate background process. This process has special logic and indexing to understand the language syntax and retrieve the right results immediately. When you’re developing in your IDE on your local machine, each language-specific process is running locally and usually communicates with the UI through standard input/output.
But what happens for a cloud IDE? It’s a bit more complicated. Everything has to run on a server, and we need a different way for processes like autocompletion and jump-to-definition to communicate with the frontend, since we can’t use standard input/output.
Solution: Language servers and the Language Server Protocol
Enter the idea of language servers. Microsoft developed the concept and released an open-source Language Server Protocol (LSP) in 2016, in collaboration with several other organizations. The core insight here is: people who develop a language should be able to build a single source of truth for their language’s smarts to support features like autocomplete, jump-to-definition, and find references (a language server). By the same logic, people who develop an IDE should only have to implement one communication protocol (the language server protocol) to enable those smart features across many different languages.
The concept took off, and today there are over 100 different language servers (some languages have multiple language server implementations to choose from). Language servers can vary in their capabilities—most, but not all, support code completion, for example. You can find a list of open-source language servers with the features they support at langserver.org.
Implementing autocompletion in a cloud IDE
Language servers are now used by most of the popular desktop IDEs. For example, VSCode supports autocompletion and other smart language features by running language servers locally. In our opinion, the great thing about the LSP is that it’s a JSON-RPC protocol—which is transport agnostic. This means that language servers work equally well for our purposes: building an IDE with language smarts in the cloud.
Here’s how it works:
- Our frontend is based on Monaco and uses the monaco-languageclient package to connect with language servers and support functions like autocomplete.
- When you start an interview or test session in CodeSignal’s IDE, you select a language and we start a language server that runs as a separate process.
- We open a WebSocket to connect our IDE frontend to the language server process input/output. Using WebSockets, we can achieve very fast two-way communication.
- When a candidate or interviewer enters texts in CodeSignal’s IDE, it gets sent to the language server, which responds with results (if it has any). This all happens using the Language Server Protocol, which works the same way for any language.
Handling multiple users
There’s an added layer here—we need to be able to handle multiple users during live interviews, where the interviewer and candidate collaborate in a shared CodeSignal IDE. This means that for any given session, we might need to run multiple instances of the same language server. We have to keep these processes independent so that you don’t get autocompletion results for what someone else just typed. To achieve this, we spin up a Docker container for each user; a single server can have many Docker containers. Each Docker container gets its own language server so that we can have separate processes for separate users.
The biggest challenge: setting up the language servers
Many browser-based IDEs don’t support autocompletion—or if they do, they only support a few popular languages. We suspect that this is because of the cost of setting up many different language servers. Although language servers are a great innovation to cut down on complexity and save repeat work, you still need to install and build each one separately and set them up to communicate with the client. Here’s where the one-size-fits-all solution breaks down a bit. Different language servers use different build tools and tend to be written in their developer’s favorite language. For each language server that we rely on to support autocompletion (in over 25 languages), we’ve needed to dive into the code to understand how it was built, how we can build it locally, and how to set up the process to connect with a WebSocket.
If you’re drawn to IDEs and language capabilities, we work on problems in this space every day at CodeSignal, with an exciting roadmap ahead. You can help us build the #1 remote interview tool that unicorn companies trust with their technical hiring—check out our jobs page.
Albert Sahakyan is a Senior Software Engineer at CodeSignal. His primary focus is making CodeSignal’s IDE great—which includes not only the editor but also components like our terminal and filesystem.