The Busy RCP Developer's Guide to Eclipse Theia
Eclipse Theia is a cloud and desktop IDE framework that combines the best parts of the classic Eclipse IDE and the popular VS Code editor. It can be deployed as a native desktop application or as a browser-based IDE. The Theia project is the latest addition to the Eclipse Cloud Development (ECD) group and is entirely written in TypeScript.
At a first glance this architecture and technology change might look like a steep learning curve but on closer look, there are many similarities between the Eclipse Platform and its new web-based alternative. In the following, I will go through some of the more important building blocks known from Eclipse plug-in development and explain the equivalent concepts in Theia. There are some differences here and there, which I will point out but all in all the similarities are striking and will help you get a solid understand of how Theia works.
Application Shell → Eclipse Workbench Window
Let's start with obvious: The first thing you see when starting a Theia application is what Theia developers call the application shell. Similarly to the Eclipse Workbench, it provides means to layout the contained building blocks either programmatically or through user interaction, i.e. drag 'n drop.
The standard Theia shell is composed of a main area, which is similar to the editor area in Eclipse. Additionally, the shell has three collapsable panels: left, right and bottom. These kinds of panels can contain any number of widgets, ordered in tabs with support for splitting them similarly to Eclipse TabGroups.
Theia doesn't support the notion of perspectives. After all, they never really got adopted outside the Eclipse community and are disliked by many. But since Theia is an open platform one can add support for perspectives easily. As a developer, I would need to manage multiple application shell layouts and provide means to switch between and allow others to participate in contributing to them.
Theia stores the layout of the shell between sessions per workspace. If no previous layout exists extensions can contribute to the `initialLayout`.
Widget → Eclipse View / Editor
The elements contained in the panels and the main area are called Widgets. Theia doesn't treat editors differently than views from the application shell perspective. So you can freely arrange editors and views in the panels and main area.
Singleton Widgets → Views
Widgets that should only be opened at most once, like the file navigator or the git widget, are called singleton widgets or simply views as in Eclipse. Implementing such widgets is very easy and Theia provides a convenient base class for that called `AbstractViewContribution`.
There not many differences between Theia and Eclipse editors. Theia embeds VS Code's Monaco editor and wraps it in a thin API, so other web-based editor widgets could possibly be used as well. Language support is usually provided through the Language Server Protocol (LSP) but additional API is available. One can, for instance, decorate the editor with complex inline widgets. Below is a screenshot from gitpod.io, an online IDE for GitHub, that integrates code reviews and thus allows to have comments inlined in the editor.
A command in Theia is a means for users to run some code in the IDE. It is similar to Eclipse commands as it has a unique id and name. There are no things like categories also there is always only a single handler that implements a command. The rare cases (copy/paste) where multiple different implementations are needed for the same command depending on the context are solved through specific contribution points (see below) within those handlers.
Keybindings are very important, specifically for developer tools. Theia provides a keybinding registry, where keybindings for a command can be registered. Keybindings can be reconfigured by users per workspace or per user.
The KeybindingContext is the equivalent to what is a Context in the Eclipse IDE. It allows for enabling or disabling keybindings in certain situations. A KeybindingContext can be reused and consists of a single function implementing whether the context is active or not. Unlike the Eclipse IDE, there is no hierarchy of contexts in its declaration as this can easily be modeled by calling a more coarse-grained context from within another one.
While talking about commands and keybindings we need to cover menu contributions as well. Menus are registered in a central menu registry using paths. A path consists of a list of ids (strings), where the first segment indicates what menu the children are part of. For instance, the main menu bar is defined with the id 'menubar'. If you want to contribute a menu entry to e.g. the editor's context menu, you would start your menu contribution path with the id 'editor_context_menu'. Any view can add new top-level menu paths and thus allow other extensions to contribute to that menu.
The other segments are pointing to groups contained in those menus. The ordering is based on the textual order of the ids on the same level. Which is why group ids usually start with a number. For instance, if you wanted to contribute an item to the open section in the main menu's file menu, the path would be:
export const MY_PATH = ['menubar', '1_file', '3_close', 'my_item'];
But fear not, there are constants that you can and should use.
A menu item points to a command id and optional has callbacks functions for indicating its enablement status and whether it is visible at all.
Theia Extensions → Eclipse plug-ins
Another commonality between the Eclipse Platform and Eclipse Theia is the extension model. Just as in the classic Eclipse IDE, a Theia application is a composition of extensions. Where an Eclipse plug-in is packaged as an OSGi bundle, Theia extensions are provided through npm packages. This allows for reusing the eco-system and technology out there. A Theia extension can contribute to the full IDE experience and use any APIs. This allows for building custom white labeled products just as the Eclipse community did for years with the Eclipse Platform.
Theia applications are composed using inversify.js (http://inversify.io/) a popular dependency injection framework written in TypeScript. Every extension can provide contributions by listing so-called modules in the package.json. That is the only configuration file needed.
The plugin.xml in Eclipse was mainly invented to allow deferring loading plug-ins. The idea is that you could install a huge number of plug-ins and they would declaratively contribute to the UI and extension-points. Only if the user actually uses one of those declarative UI contributions the plug-in would be started. The programming model leads to huge XML files that are hard to develop and maintain. Cross-references are done through ids and there is even some sort of expression language to define contexts and other things through XML.
For Theia we found this to be a too big tradeoff, especially because in practice, most plug-ins are going to be loaded sooner than later anyway.
So everything that would be done in plugin.xml in a typical Eclipse Plug-in is done directly in code in the case of Theia, resulting in much less 'code' which is better integrated and is supported with much better tool support.
Correct plug-in resolution and providing understandable error messages to users in case they have conflicting plug-ins installed has caused a huge headache for many developers and users. Much effort has gone into the p2 algorithms and still in case of conflicting configuration users are often left with puzzling error messages.
As Theia extensions are following the very same ideas, the underlying problem (resolving a working set of extensions based on all semantic version constraints) is the same.
VS Code Extensions
VS Code improves the situation by running extensions in a constraint sandbox model and by providing one API to all extensions. So instead of a huge complex dependency graph as we have them in Eclipse or with the powerful Theia extensions, most extensions only have one dependency to the VS Code API. So in the VS Code case, the dependency graph mostly is a simple tree of depth one. The main VS Code API is kept backward compatible.
VS Code extensions are not executed in the same thread/process as the main application but in their own sandbox (worker). So if they blow up, they don't tear down the whole application with them as it can happen with Eclipse plug-ins or Theia extensions.
That sounds all great, but there is a significant downside: everything an extension shall be allowed to do needs to be explicitly exposed through that single API. This means lots of limitations in terms of power and flexibility. For instance, in VS Code you cannot contribute full new views; only trees and HTML is supported.
Theia Supporting VS Code Extensions
The single API has another advantage: it abstracts over the actual implementation. Theia already supports the LSP and the Debug Adapter Protocol (DAP), both are means to connect editor independent functionality through a protocol. Language servers and debug adapter can be implemented in any language as they run in their own process and only communicate through a JSON-RPC connection. VS Code's extension API is very similar, though much bigger.
Long story short: Theia will soon support two ways of being extended:
- Theia Extensions: For full blown flexibility and integration, allowing arbitrary changes and contributions.
- VS Code Extensions: Less powerful, but safer to install at runtime and simpler to implement.
I hope this article helped you understanding Theia's architecture and main concepts a bit better. If you have further questions don't hesitate to join the Theia community by filing an issue on GitHub (https://github.com/theia-ide/theia) or getting in touch through the Gitter channel (https://gitter.im/theia-ide/theia).