How to embed a Monaco Editor in a browser as a part of my first task at TypeFox
Hi there, this is Akos. I am the new one at TypeFox, and within this post, I would like to describe you what was my first task after joining TypeFox. Namely, how to embed the Monaco Editor in the web browser and how to support a simple expression language from the browser using the Language Server Protocol (LSP).
I came from the Java world; I did Eclipse plug-in development in the last couple of years. Although I also worked with Java EE technologies and created rich web based applications with various frameworks such as JBoss Seam and Vaadin I never really had to deal with the JavaScript part because the currently used technology somehow magically took care of them under the hood and I had to deal rather with the Java code. Embedding the Monaco Editor was a bit more complicated task and required some additional JavaScript and TypeScript knowledge. Besides that, I also used Gradle, Webpack, and npm.
First and foremost, what is the Monaco Editor? The Monaco Editor is a browser-based code editor that powers VS Code. It supports cool features such as syntax and semantic validation, content assist, syntax coloring, parameter hints, hover, and much more out of the box. It is well-documented and relatively easy to connect with a language server and integrate into your projects.
The first thing I needed for this task is the Xtext implementation of the language server. This server is available from the 2.11.0.beta1 milestone version of Xtext and depends on a lightweight library: ls-api. This library is a simple Java binding for the LSP and is going to be replaced by the LSP4J Eclipse project in the future. By default, the Xtext language server supports various features, such as content proposals, hover, mark occurrences, and find references, which one can use for almost any kind of DSLs without any further customizations. Besides that, there are a couple of additional features that require custom implementation; for instance the signature helper which provides parameter hints. Usually, one single language is supported by one server; however, the Xtext server is capable of supporting multiple Xtext languages at the same time. The only requirement is that the actual implementation of the language should be available from the classpath of the server as a bundled jar.
I had to prepare some generic glue code that acts as a web socket server endpoint and handles the lifecycle of the Xtext language server instance associated with the web socket session. On session-open event, it creates a new server instance and caches it and indeed on the web socket session-close event it shuts down the server and removes it from the cache. Besides that, to be able to support Guice based dependency injection in RESTful web services I used the jersey2-guice library which supports DI within the Jersey 2.x implementation of the JAX-RS/JSR-311 specification.
Once the server-side was ready, and our DSL was available on the classpath, I had to implement a language-specific signature helper. I added some tests and switched to the client-side code. On the client-side, I used mostly TypeScript with some additional JavaScript code and invoked Webpack to compile TypeScript to JavaScript and to build the dependency graph with all of my static assets to create one single uglified JavaScript file for the browser. The client code is responsible for creating a web socket and connecting to the Xtext language server. Once the connection is successfully established between the client and server, the language gets registered into a new Monaco Editor instance. Right after the editor instantiation, both the syntax coloring and the auto-bracket insertion was configured in the client code. Currently, the LSP does not support syntax coloring, so this had to be added to the JavaScript code.
The last remaining part of this task was to build a web-archive file and deploy it. Since not all environments have installed Node.js on it, an individual Node.js task was added to the Gradle configuration to install Node.js with npm. Npm can install Webpack and Webpack can gather all modules and their direct and transitive dependencies reading the content of the package.json of our module. Finally, Gradle creates a war file which can be optionally deployed on a Tomcat server using the Gretty Gradle plug-in.
This example web-based Monaco Editor, which was presented at EclipseCon Europe last week, is available here. We are planning to make both the generic glue code (used for the server) and the Monaco Editor code accessible in the future. Once the code is available under the EPL 1.0 license, we’ll come back to you with another blog post with all the technical details and pitfalls. If you cannot wait, feel free to drop me a mail.