In this blog post, we bring you the last part of the series of blogs about the development of Fleet, a next-generation IDE from JetBrains. When you need to view and update your code quickly, Fleet becomes a speedy and lightweight text editor. It instantly launches, allowing you to get to work right away. Then it is simple to convert into an integrated development environment (IDE), with the IntelliJ code-processing engine running independently of the editor.
Here is an outline of the previous parts in this series if you care to learn more about this Fleet story:
- Part I – Architecture Overview
- Part II – Breaking Down the Editor
- Part III – State Management
- Part IV – Distributed Transactions
- Part V – The Story of Code Completion
Parts III and IV covered complex abstract architectural ideas entailed in state management and how they are synchronized across the Fleet’s distributed components. Now we’re moving to a feature you’re more familiar with — Code completion—and see how Fleet uses it.
Code Completion as seen by end users
Let’s say that we begin by taking some text notes. This is how the code completion we have so far looks:
The Fleet editor is designed in such a way as to scan the text to determine which words are likely candidates for code completion. This seems relatively logical, though you might assume that, based on the information in your other previously collected notes, some machine learning magic is at work here.
Let’s get to some Kotlin programming now.
This behavior is not especially noteworthy in terms of completion. Essentially, the algorithm is the same as it was for text notes. Fleet suggests turning on smart mode to help us in this situation.
It’s better now: Based on the code analysis, we got some precise recommendations. What is supplying us with these completing pieces, exactly? We require a lot of details about the code itself and the libraries it might perhaps utilize, so it’s not the editor.
Keep in mind that completion does not always mean appending code. It’s changing the code. The dot character is eliminated, and enclosing braces are added because of code completion in the following example:
Code completion can also be used to create common code fragments from predefined snippets. The sample that follows is written in Rust with smart mode turned on. When we type for and press Ctrl-Space, the snippet below is added:
The finished product is a template text with some caret-indicated placeholders. With the TAB key, we can go to the following one. Snippet insertion is a laborious process. Fleet can accomplish it without needing to analyze the code. The editor itself can quickly integrate this capability. By looking at these cases, we might discover that smart mode is crucial for completing results, but even without it, we have something to work on. We may also observe that we might need to append some basic code, restructure some existing code, or introduce sophisticated code fragments with placeholders. It is time to check out what’s happening below deck now.
What’s going on inside
The functionality of code completion in Fleet is depicted in the following diagram.
Fleet’s front end may occasionally be able to offer code completion on its own. There are various standard JSON schemes, for instance, that can be used to complete settings. json file that contains Fleet’s settings. Enabling smart mode will allow us to observe all completion engines, whether they are driven by IntelliJ IDEA, ReSharper, or a variety of LSP (language server protocol) servers. The first LSP server supported by Fleet was Rust-analyzer. There will be more projects in the future because the Fleet project is going quite well.
Code completion is a feature offered by the LSP servers and IntelliJ IDEA backend servers. They also offer a variety of other services, such as flagging programming problems in your code and maintaining indentation conventions. Multiple backend engines work fine for various programming languages. Users of Fleet will soon be able to select the engine of their choice.
Code completion consists of the following elements:
- Completion elements that are suitable and accessible for a specific source position.
- A completion service acting as the engine in charge of supplying completion items.
- A completion session that is in charge of delivering completion items to users and implementing the one that is selected.
These call for a more in-depth discussion.
A completion item refers to one available option for proceeding with the current source position.
There are three kinds of completion items in Fleet:
- A basic completion item is simply some text that should be appended to the current cursor position.
- A snippet is a template with placeholders. Once inserted into the source file, snippets support navigation between placeholders and dynamic suggestions of completion items based on what was entered in the other placeholders. These snippets provide the same functionality as the Live templates in IntelliJ IDEA.
- Declarative Insert is a collection of editing instructions (like insert, remove, replace) that should be applied to code to finalize a code completion action.
An option for carrying on with the current source position is referred to as a completion item.
In Fleet, there are three different types of completion items:
- A simple completion item is just some text that should be added to where the cursor is currently located.
- A snippet is a placeholder-filled template. Snippets enable navigation between placeholders once they have been added to the source file and dynamic completion item suggestions based on what has already been entered in the other placeholders. The functionality offered by these snippets is identical to that of IntelliJ IDEA’s Live templates.
- Declarative Insert is a group of editing instructions that should be applied to code to complete a code completion action, such as insert, remove, and replace.
The completion item specifies some things, including what is displayed in the list of completion items, what happens to the code once it is applied, what the priority of a particular code recommendation is, and so forth. Depending on the type of completed item, applying it may be simple. Fleet must request the backend to perform completion on the backend’s side, not in the Fleet editor if completion necessitates complex code modification that cannot be expressed declaratively.
The procedure of applying a completion item doesn’t result in changing document content. Instead, it produces a set of operations that should be applied to that content. This is where we arrive at the first bridge to Fleet’s state management. These text manipulating operations will then be applied with Fleet’s distributed nature in mind.
Completion items don’t just appear. There must be a service that creates them based on the location of the current source code and the version of the current document. We require an API to have uniform access to all these services because there may be more than one of them.
Completion API and Completion Services
Fleet’s completion API lets us:
- Acquire completion items for the specified source code location.
- If there is only one logical way to finish a current statement, then that way.
- To apply a completion for us if there is a difficult task to do.
- To declutter the completion engine’s resource usage.
How does Fleet know which completion services are offered? Are services for discovery used? We can really use our state management engine for that, as those of you who have read the earlier blog postings may be aware. Any completion service is simply a loaded object into the Fleet’s state. In reality, Fleet frequently presents any functionality in the form of an entity.
These service entities know which document types they support. Fleet searches for the state and chooses the first one responsible for the current document type. As shown in the diagram above, Fleet falls back on predetermined snippets or document words lists if nothing is detected. End users have control over which plugins are used to execute their code by loading and unloading the corresponding entities (i.e., turning on and off the corresponding plugins).
Currently, Fleet offers two main completion services:
- Obtaining completion items from the IntelliJ IDEA backend is the responsibility of one of them.
- The other receives completion items from the LSP link.
They both implement the Completion API for Fleet to use them consistently. Since Fleet has no interest in how these services really provide completion goods, we won’t get into that discussion here. After all, Fleet is just a straightforward text editor!
Code completion is an efficient procedure. Once the user has started it, Fleet must locate and launch the proper finishing service. The received objects must be presented in a popup window. The user must apply their chosen item everywhere once they’ve made their decision (in the workspace and all the frontends).
This technique is typically significantly more difficult. Firstly, it would be foolish to wait until all the high-quality completion goods are complete because it takes time to create them. They are instead offered as a flow of objects. When Fleet receives a fresh batch of products, the component in charge of showing them must be updated.
Second, after asking code completion, the user may carry on typing. Starting over after completion would be too inefficient. The cost of calling a backend for each character entered would likewise be high. By filtering and reranking the completed items, Fleet can refresh the list of already received completion items. Reranking is a tricky process. Several additional priority features that can reflect prefix-matching are used to implement it.
In the end, if the user is dissatisfied with the suggestions, they can cancel the completion.
Another entity in Fleet’s state that is in charge of overseeing everything mentioned above and more is the completion session. For instance, given that other users might be updating the same document simultaneously using another Fleet frontend, it deals with source code positions that may very well be outdated!
Sometimes it’s difficult to comprehend how everything works. Examine the first section’s screenshots once more, paying close attention to the UI specifics that are shown in conjunction with the code completion session. As we proceed through code completion, alter templates, and fill placeholders, errors and warnings pop up and disappear. Between the workspace and frontends, large amounts of data are moving through the network simultaneously. Underneath, distributed transactions are being implemented. It’s magical.
Code completion only makes up a small portion of Fleet’s features. These days, it is hard to picture life without this function. Even though some people find it handy to develop programs in Google Docs, especially those who feel, for some reason, that it’s a reliable way to evaluate coding skills, the rest of us are content to rely on code completion.
Fleet frequently is not smart enough to know what to recommend to its user. It also makes use of LSP servers and IntelliJ Backend. Instead, Fleet concentrates on providing the best source code editing experience across languages. It uses all its state management tools to facilitate users’ work.
Reach out to us for any information!