Some developers argue about the best text editors and IDEs, but what about editors for the web?
We recently migrated Monaco's Sourcegraph.com, the code editor component that powers VS Code, to CodeMirror. CodeMirror is one of the first web-based code editors that recently released a new version (CodeMirror 6) which is gaining popularity but still lags far behind Monaco for now.
Monaco is a popular choice when you need to embed a code editor in a browser. It worked well enough for most of our needs, and since it was maintained by Microsoft and built into VS Code, we knew we could rely on it for the foreseeable future, so why did we change it?
- Monaco offers many options out of the box, but it is very difficult to configure or reduce unneeded features.
- Monaco documentation isn't great, so we couldn't always find out if it was possible to do what we needed to do.
- Monaco has a global reference model, making it difficult to run multiple instances on the same page with minor configuration differences.
- In terms of code size, Monaco made up a whopping 40% of our external dependencies.
- CodeMirror has already been adopted by other big projects like developer tools Replit and Chrome, suggesting it's not going away anytime soon.
We're still learning about CodeMirror, but so far it's solved all of the above issues and it's been a pleasure to use. It's been the default component for our research input since May 2022 and we'll be using it a lot more in future versions of Sourcegraph!
An amazingly simple text input: our search field
As a research company, you might be wondering why we need a code editor. When you visit Google.com you will see the famous minimalism of an empty text entry on a blank page, and when you visit ithttps://sourcegraph.com/search(our public Sourcegraph instance that lets you search millions of open source repositories) probably looks more like the Google home page than a coding IDE that Monaco and CodeMirror were built for.
But this bar hides many functions that you are probably used to from your IDE.
Searches get tricky, so we provide syntax highlighting to help you navigate through the different components (e.g. "lang" is highlighted because it defines a filter, and escape brackets are highlighted to indicate that it is is a matched pair).
We also need tooltips so our users can hover over the options on the right or over the components of the query they are entering to get more information and suggestions.
Finally, we need auto-completion and error diagnosis to help developers write queries as quickly as possible and preemptively warn them before sending invalid queries.
We achieved most of that with Monaco for years, thoughmainlyIt worked, there were many small problems that we wanted to solve for years.
Defense of a paraphrase
Suggesting a paraphrase is never the easiest thing, as paraphrasing is often a tempting but wrong option. as engineer ofproduct research team, I suggested an experiment in which we should explore other options before investing more time and effort in adapting Monaco to our needs.
After following ourProcess RFCcreateRFC-637, I was able to complete a proof of concept and use CodeMirror to replace 90% of our current Monaco functionality in just two business days. I was immediately drawn to CodeMirror and knew it would be a much better option as I saw the potential to replace not only our search typing but also our dynamic notebooks and file reader.
Using CodeMirror was like playing with Lego - I had this set of bricks and felt like I could build anything I needed.
Although similar, CodeMirror turned out to be Monaco's polar opposite in many ways. Monaco offers a full IDE by default. These include a multi-line editor and a code "minimap" - an expanded view of the entire open file that allows you to more easily navigate to a specific location in the file. These are great features, but we didn't need them for our search input, and with Monaco it wasn't even obvious how to get a single-line input instead of a multi-line editor.
The issues we had with Monaco and how CodeMirror fixed them
Many of the issues we had with Monaco weren't inherently issues, which is why we've been using the component for so long. But together, the irritants added up. Some of the key issues we encountered with our implementation in Monaco included:
- one-line editor: Unlike most IDEs, our search input is a single line input... most of the time.
- global setting: It's cumbersome to render multiple instances of Monaco per page, each with slightly different settings.
- marker text: There is a long-standing issue with Monaco requesting a function to enable a placeholder or default value.
- code cleanup: We had some nasty tricks to get Monaco to do what we wanted.
If you are interested in the details, we will cover each point in detail below.
Our original plan was to use CodeMirror instead of Monaco for our search box, but we're so impressed that we're replacing all instances of Monaco with CodeMirror.
While there are ways to further optimize Monaco's packet size, we love the fact that we don't get anything we don't need with CodeMirror.Originally, so you don't have to spend time and effort extracting parts of the library to save valuable bytes.
Switch between single-line and multi-line editing
Monaco offers a full multi-line editor by default and it's not obvious how to turn it into a single-line editor that we need for our search input. Although we figured out how to do that, noforeverYou want a single line of input. On devices with a small screen, our search input is scaled to multiple lines. In this case, we always place the search controls (e.g. turning regexp mode on or off) as well as the actual search button in a row below the input, but more importantly, the search also intelligently groups the query in such a way that The input must transition seamlessly from one line to multiple lines.
In the image below you can see how our mobile search input grows as needed to serve a long query compared to the same query in a one-line editor on our desktop website.
That was hard to get right with Monaco. We had to add some custom configuration tricks, and even then it didn't work as well as we'd like. We expected this to be a challenge with CodeMirror as well, but to our surprise it worked by default.
Integration with our CSS framework
We have hundreds of websites and pages on Sourcegraph. Our CSS is organized in classes and variables, with all the colors we use defined in one place. This means our designers can easily make cosmetic tweaks or theme updates across our entire web presence with a single change.
The only exception? Monaco.
This process was error prone and on more than one occasion resulted in errors where the Monaco configuration was not updated or updated with different colors than our global CSS.
With CodeMirror we can easily integrate our color file and now all theme updates are automatically applied on our marketing pages and in our live editor.
CodeMirror allows us to eliminate a lot of code related to color settings and also simplify it. Below is an example of our Monaco color configuration code on the left (actually there is much more to seeon here) and how much could be simplified with CodeMirror on the right.
Global language setting x per instance
Monaco and CodeMirror handle configuration slightly differently. Monaco has a common global status for setting the language, and that language affects syntax highlighting, auto-completion, and other parts of the editor.
This is not a problem for our search page, where we only use a single instance of the editor. But in oursnotebooks, we have different blocks on the same page. However, our notebooks have different types of search blocks (e.g. we could have a single notebook with a regular search box and an icon search box).
These different blocks use slightly different configurations, but since the Monaco instances share a global state, we need to register each configuration separately with a unique language ID, even if the configuration differences are quite small.
With CodeMirror, each instance on a page is completely independent and only uses the local state for that instance, so configuring the different search blocks is much easier.
This also eliminates an entire potential class of bugs where adding a new block with slightly different settings could affect previously added blocks on the same page.
Add placeholder text
For Monaco, we were unable to display the placeholder text in our search input. It wasn't a big issue for us, but it's a great example of one of the many small, insignificant issues we had with Monaco that added up to making it finally make sense to move on. there wasan open problemabout it in the Monaco repository since 2017, and the hacky solution is to overlay elements to simulate placeholder text.
There are a number of minor workarounds that were required for our Monaco implementation, but they all add up. For example, we didn't want to use standard Monaco keyboard shortcuts, such as B. Using Ctrl + F to search in the editor. There's no point in using Ctrl+F to search within a single-line input like our search box, and we had to disable thata confusing solution.
CodeMirror still allows us to add the keyboard shortcuts we want, but we don't have to disable the ones we don't need, so workarounds like this can be removed.
We can do a lot more with CodeMirror
Although this started as a project to improve our search input, we love CodeMirror so much that we've moved everywhere we've used Monaco before.
As mentioned above, we've already replaced all of our laptops with CodeMirror, but we're also starting to find new uses for it.
Rewriting our file viewer with CodeMirror
After completing a Sourcegraph search, you can open the relevant files and view them directly. For historical reasons, we don't use a code editor for this file viewer. It's a read-only view, so loading an entire editor for each file was pointless. At the same time, however, we need many of the same features when viewing files, including syntax highlighting, line numbers, and code navigation.
Another benefit of using a code editor for a file viewer is when viewing large files. When opening a file with thousands or millions of lines, a code editor like CodeMirror does not load or process an entire file. Instead, it intelligently displays only what's on the user's screen.
One ofCodeMirror samplesIt shows how the editor can effortlessly instantly load a document with a few million lines and show the highlight only as far as the user scrolls.
We haven't released our new file viewer yet, but we hope to see it in a future release!
The open source software business model
Outside of tech circles, it's unclear how much software worldwide depends on open-source projects like CodeMirror, or how difficult it is to make these projects sustainable. Often these are passion projects that are mainly handled by a single person who has another full-time job.
If these caregivers step aside, the consequences could be enormous and difficult to predict. Large commercial projects may not even depend on them directly, but on another (commercial) project that depends on another, and so on, until you find the open-source dependency that precariously underpins everything, as illustrated inXKCD2347.
The MIT license used by many open source projects, including CodeMirror, is very permissive and allows open source code to be used in commercial projects as well. But CodeMirror is one of several projects actively positioning itself to remind companies to give back to the ecosystem they depend on and benefit from.
CodeMirror explains on its website:
If you use CodeMirror commercially, there is a social (but not legal) expectation that you help fund maintenance.
Sourcegraph has followed this societal expectation and has made monthly donations to CodeMirror since it began using CodeMirror. We also sponsor your facilityobrigado.dev.
Comment on this post on our Discord
Special thanks toTracy Johnson, miFabiana Castellanosfor the help with this post.
More posts like this
- Overhaul of the design approach of the Zig programming language
- A developer's view of developer productivity