Building a hackable editor - in your browser

Benedikt Jenik - July 14, 2019

Warning: All of this doesn’t really follow any best practices for working with html, css or javascript - on the contrary: what we will be doing may look horrible to some people, and rightfully so - so you may not want to try those things at work just yet.

Editors are those magical huge pieces of software that are supposed to help you working on your code or data and usually have thousands of features you don’t know how to use, while at the same time lacking the one thing you actually need, or being really inconvenient to use. Some even claim to be extendible, but only as the author sees fit, making it possible to add a few tiny features, but you generally can’t trivially change things in a significant way, or make the ui look completely different, like doing tiling instead of tabs or even have a full “window manager”. You could do all of that, if you wrote your own - so let’s do that.

Our first goal for the editor is to be really helpful for working on the editor itself - especially a short feedback loop would be great to see if we did something useful. So let’s have that - namely let’s have live refresh for ui changes and hot reloading for all changes in functionality.

We will get text rendering and keyboard input from the browser but have to do everything else ourselves. How? By abusing two html kinks you probably didn’t know about:

1. Making html content editable

The first version of our editor will just consist of a single body tag - that’s all we need for now - just paste the following code <body contenteditable="true"></body> into an empty .html file and open it in your browser - you should be able to type text (this should work in most browsers - maybe even your favorite version of Internet Explorer). As you probably have guessed the trick is the contenteditable part - with it you can tell your browser that the user is allowed to edit the content of whatever html tag you put it on - it’s that easy to build a text editor in html - we even get undo and redo from the browser. Let’s now get to the “hackable” part - namely live ui refresh and hot reloading.

2. Making script and style visible

Did you ever wonder why you can see text you put inside <div></div> or <p></p> tags but somehow not the code you put inside <script></script> or <style></style> tags? You probably didn’t, because it doesn’t make any sense to see that code on a website - so obviously the browser knows not to render it . But it’s not hardcoded in the rendering engine. It’s actually just hidden with a tiny bit of css in most browser’s standard stylesheet, and we can override that. Just putting script, style { display: block; } into a <style></style> tag will do that. Minimal example:

<body> <style> script, style { display: block; } </style> <script> console.log("Hello World!"); </script> </body>

Feel free to try that by pasting it into a .html file and opening it in a browser.

This whole thing is crazy right? But it will get even crazier: we can make the whole thing contenteditable as well. Everything still works with that, but it gets even better: most browsers actually watch the content of the <style></style> tags for changes and update the page accordingly, meaning we just got ui live refresh for free.

Minimal example here - feel free to play with the font size:

<body contenteditable="true"> <style> script, style { display: block; font-size: 20pt; } </style> <script> console.log("Hello World!"); </script> </body>

Everything may disappear while you are typing (because if the content of your <style></style> tag is invalid it will also not override the invisible setting anymore) - just keep typing.

Will that also work for refreshing our javascript code? Unfortunately not, because only gets evaluated once when the page loads. Your edits still change the content of the actual <script></script> tag (feel free to pull up the web inspector to confirm) but nobody runs it after - so we have to do that ourselves.

3. Hot reloading javascript

To reload the javascript code we can for example listen for a certain shortcut and then make the browser evaluate it again - let’s use shift+enter - enter is keyCode 13 in javascript - so our code looks like this now:

<body contenteditable="true"> <style> script, style { display: block; white-space: pre-wrap; font-size: 20pt; } </style> <script> //alert("Hello World!"); document.onkeydown = function(e) { var key = window.event ? event : e; if (key.shiftKey && key.keyCode == 13) { eval(window.getSelection().focusNode.parentNode.innerHTML); } }; </script> </body>

After putting that into a html file again try uncommenting the line with the alert in the browser and hit enter while holding shift - you should get a popup window with that message. Watch out: if your javascript is bad you may break the hot reload functionality and have to reopen the page.

The additional white-space: pre-wrap; makes the rendering a little nicer (we wouldn’t have line breaks otherwise).

A bit of polish

The above code is already complete, but the following fixes a few weird issues and also adds a bit of styling and a bit of color to show if the javascript code worked or not.

You can try the full version here and the full code is also below:

<body contenteditable="true" spellcheck="false"> <title>editor</title> <style>script, style { display: block; white-space: pre-wrap; background-color: #eeeeee; border: solid; border-radius: 10px; padding: 20px; } body { font-family: Menlo, Monaco, monospace; font-size: 12pt; tab-size: 4; } script.success { background-color: #ccffcc; border: solid, #00cc00; } script.error { background-color: #ffcccc; border: solid, #ff1111; } </style> <script type="text/javascript" class="success"> //alert("Hello World!"); document.onkeydown = function(e) { var key = window.event ? event : e; var node = window.getSelection().focusNode; if (key.shiftKey) { if (key.keyCode == 13) { node.parentNode.classList.remove("error"); node.parentNode.classList.remove("success"); try { eval(node.parentNode.innerHTML); node.parentNode.classList.add("success"); } catch (e) { node.parentNode.classList.add("error"); } return false; } } else { if (key.keyCode == 13) { document.execCommand("insertHTML", false, "

"); return false; } if (key.keyCode == 9) { document.execCommand("insertHTML", false, "\t"); return false; } } } </script> </body>

How to work on it?

Let’s say you added a few of your own changes - how do you save these? That’s pretty simple - just make the browser save the html to file ( cmd+s or ctrl+s as expected, on most browsers) and then open that file again to continue working - most browsers add a tiny bit of junk, but that doesn’t matter too much.

Next steps

Adding features

The editor is very simple right now - some features like syntax highlighting and autocomplete could be nice - but those may also add a lot of bloat. Those are both nontrivial, but you can potentially outsource the work, e.g. autocomplete can be had if you make the editor speak Language Server Protocol , which is still hard, but potentially easier than writing your own autocomplete (even though that may be fun, too).

As for syntax highlighting the obvious answer is to stuff everything into tags and color those (most other html based editors do that), which may break the simplicity of our auto ui refresh and hot loading of functionality. Another alternative, that is potentially way harder to do, but keeps the editor itself simple, is doing syntax highlighting like this with font ligatures.

Adding content

Instead of just working on the editor itself, you probably want to also work on actual content. That is really easy - just add another element inside the contenteditable body - you can either do this with the editor’s javascript field - and maybe also make it generate more elements like have a button for it in the future - or just quick and dirty by modifying the html somewhere else. If you want to work on multiple things just add more of those elements - with css you can even make them behave like tabs, or place and overlay them however you like.

Editor and content in one

You obviously want to keep the codebase of your editor separate from the actual code you’re working on. The same is true for code and data. But is it actually?

Microsoft Excel proves quite successfully that this doesn’t have to be the case - at least for code and data. Of course it seems ugly and may lead to horrible outcomes, but within its focus area nothing beats it in the speed you can hack something together to try something out. What if you could do that for everything and also customize it with additional functionality and ui for the task at hand?

There is no reason you can’t build a version of this editor to be your email client - or another version being your personal data-/knowledge-base that, in addition to the data itself, has all kinds of functionality to automate things and the ability to edit those automations. Maybe you shouldn’t think of it as an editor - or even one single version of an editor than can do everything, and more as a living document that, per use case, has all kinds of features to update content automatically and the ability to add more of those features as needed.

I definitely agree that this idea may look super weird living in a world where most tools are massive and have their own particular way they can be used in, which also can’t be changed and you’re just supposed to work on the content and never on the tool itself - but maybe the other way could be better?

Modular vs minimal?

Looking at it from a software engineering perspective there often is the idea that you want to make your code as modular as possible, and separate concerns as much as possible as well, with the assumption that this makes it easier to modify and extend in the future. This seems especially true within large teams, where nobody can know all the code, but instead just focus on their current area.

While the argument about big teams may be true (with the part of software ever being good if nobody knows everything at least being questionable), you are definitely not a large team working on your own personal editor that’s just for you.

Modularity making future changes easier is a more difficult assertion. I would rather phrase it as discarding some part of the option space (changes that would need work on all modules) in favor of making some expected changes (within a single module) easier. But looking at it this way, if you already know what you will need in the future you could also just add it, and if you don’t you potentially just made a horrible tradeoff. On the other hand when making the code as minimal and integrated as possible to just have what is needed now, you don’t trade away parts of the option space just yet. You will definitely have to refactor and rewrite parts in the future (which is true for the modular option as well), but I’d rather do that on a minimal than a huge modular codebase.

This whole argument may even hold true regarding modularizing between your data, your code and the code of the editor that you work in versus just saving everything together in that one html file. Some things will certainly be easier if those borders are chosen well and you have to only work on one part at a time, but if you instead go for the entangled, but minimal approach, a whole new area of the option space may suddenly open up.

Been thinking about something similar and want to share your ideas? Or want to discuss something completely different? Feel free to reach out and send an email to [email protected]