My Week With GitHub’s Atom Editor
Atom is a very promising new editor written by GitHub. It’s very much in beta right now, so this post comes with some strong caveats that a lot of these facts may change in the future. That said, my motivation for posting this now is to provide early feedback about what works, and what doesn’t.
I’m writing this with mixed emotions. Atom is an editor that feels very much like Sublime Text in pretty much all respects. They even borrowed most of the default keyboard shortcuts, so if you’ve been using Sublime, you will likely be able to easily migrate to Atom. That said, the “feel” is where the similarities end. Instead of a Python core, Atom is based on Node.js and WebKit, using JS (CoffeeScript), HTML, and CSS to customize and implement most of the features. Generally speaking, this is a good foundation— the problem is, Atom seems to have gotten it all wrong when it comes to laying down a strong architecture for both the plugin API and core components. If I could summarize this editor in one sentence, it would be: “a promising and extensible editor sitting on a very shaky technical foundation”.
Because Atom is being marketed as a “hackable” editor, I want to focus most of this article on the API and actual functionality that can be customized. I spent my last week both using Atom and writing a handful of plugins, 3 of which I published (atom-runner, go-format, and rst-preview), so I would say I’ve had a good run-through of the current functionality of the editor and its API.
But before we get there, I do want to cover some of the basics.
Feature Check? Check.
First, let’s start with the easy stuff. On Atom’s landing page, GitHub bills this editor as “extensible”, “web native”, “modular”, and “full-featured”. It’s hard to argue with the first 3, because giving someone untethered access to a WebKit browser (it actually seems to be running node-webkit) and displaying the entire UI inside of a DOM implicitly gives you web nativity and pretty extensive control over the entire application. Extensibility, specifically extensibility provided through the HTML, CSS, and JS web technologies that most developers (and some non-developers) know and use every day, is basically the strongest selling point this editor has. Say what you want about using HTML in a desktop application, but exposing your plugin architecture as a bunch of HTML/CSS is something even some of my non-tech friends could pick up and hack on. Foundationally, GitHub made smart technology choices.
That leaves us with the last point: “full-featured”. This is the one I take issue with. Although Atom already has pretty decent parity with an editor like Sublime, it’s definitely missing quite a lot of the behavior you would expect from a polished code editor. And yes, I know this is beta, and I know things will change, but things only change if there is feedback, so here it is:
The Irony of Git Support
So let’s take a step back and remember that this is GitHub publishing this editor. GitHub, as in, creators of the canonical social Git hosting site that most of us use. And yet, clearly lacking in this editor is any sense of actual Git support. Sure, there are line diffs and some basic diff / branch status listings in the status bar, but come on, not even a built-in “Git Commit” command? For what it’s worth, there is a cool “view [history/blame] on GitHub” command, which is alright, but I imagine for those of us hacking on planes or on non-GitHub-backed projects, that’s not really going to… fly.
TextMate came with some pretty good script running support, a feature I’ve always liked more than Sublime’s variant of “Build Systems”. That said, both of these other editors have pretty good functionality for running code in the editor. I was shocked to find out that there was basically no such built-in support inside of Atom, which is what prompted me to write atom-runner. A final version of Atom must support some kind of equivalent, and it should be built-in.
Miscellaneous UI Fails
Call me spoiled, but these are features I expect every editor to have, if they claim even partial support for:
- Tab completion is almost useless in its current state. There is no intelligent memory of the last / most used completion in the document like Sublime manages.
- Ditto for file switching (Cmd+T in Sublime/TextMate). There is no memory of the last file, so you can’t hit the quick Cmd+T – Enter keystroke to switch between two files. You have to fuzzy-find from scratch each time— and the fuzzy-finder is extremely loose in its matching policy.
Middle-clicking does not close tabs.— Edit: This looks to have been added into a recent build (as of 03/11/2014). Nice! Unless I haven’t found it, no keyboard shortcuts to split windows or move windows between tab groups.— Edit: Cmd+K, ArrowKey is how you do it. Thanks @bryanl.
- Finally, this one is probably a bug, but history support is fairly weak and often times simply switching tabs or windows will completely clobber your history. History should be the most stable part of any text editor. If I can’t trust the history management, it’s kind of a deal breaker to adoption, for me.
I can’t stress this one enough. Atom is extremely slow, and this is extremely disappointing. Performance-wise, running Atom today feels a lot like running Eclipse back in 2006; slow startup, visible lag when switching tabs or loading files, scroll and typing lag. To quantify this with an example, starting up the editor from the command-line in a reasonably sized project (~8 root directories, ~200 files) can take a good 7-8 seconds before the main window comes into view. Similarly, switching between tabs when clicking in the file view or tabs at the top has a noticeable lag on larger files— I’m talking up to 500ms to 1sec. Scrolling those files shows similar lag. Granted, it’s not horribly bad, but it’s enough to feel the pain when switching between Sublime and Atom.
My biggest fear with Atom’s performance problem is that it does not seem conceivable to me that things can get better. Given that Atom is still not “complete”, the “1.0” build will likely only have more code and more DOM nodes to render; and when more plugins are published, you will only have people running more of them at a time hogging up all that processing power. Unless there is some seriously inefficient core code running under Atom, the performance problems really seem like a fundamental architectural limitation, not just a lack of optimization. Just like Eclipse learned, you can’t optimize yourself out of bad architecture, and Moore’s law no longer moves fast enough to be able to save you from scaling CPU intensive applications— especially when your architecture is threadless and single-core-bound.
The only real solution here is to go the route that Chrome went and use separate processes for each tab / component. This would give you the core-scalability that CPU intensive apps need. Unfortunately, shoehorning in such a fundamental piece of infrastructure into a sizeable codebase is not an easy task— if it’s even technically possible (since everything is displayed in a single DOM, I don’t know if it is). This is really something that has to be built from the ground up, but it wasn’t. This might be the most costly mistake GitHub has made with Atom.
Shaky Foundation: The API Is On One Too
A common theme running through my time with Atom is that much of the core infrastructure seems half-baked or completely lacking. It’s almost as if they decided they were going to write a text editor app half way through implementing the text editor component. Much like the lack of a strong performant foundation, there are parts of the API that seem inconsistent, unnecessarily evolutionary, and just generally under-engineered. Here are a handful of wat moments:
Documentation Where Art Thou?!
The documentation ranges from decent to misleading to lies to simply not there. It’s almost impossible to rely on the documentation as is, and I had to resort to simply reading other core Atom plugins instead of looking at docs. At the time of posting, the
Editor.getPath() method is still missing from the docs— arguably the most important method if you’re doing anything with the files, and necessary for all 3 of the plugins I published. Similar inconsistencies and notable missing things arise, like lack of event documentation (again, necessary for each of the 3 plugins I published), and lack of explanation for seemingly similar methods.
I’m a little biased when it comes to docs. I happen to believe that the best way to audit an API design is by writing good documentation around it, effectively dogfooding your documentation as the API contract. GitHub seems to have missed the mark here in thinking that docs are an optional artifact that you build up after you’ve finished coding. It doesn’t surprise me, at least, to see that attitude translate into weak APIs and inconsistent use of those APIs across different core plugins in the editor. When there is no document guiding usage, it’s not surprising that you end up with a smorgasbord of implementations.
There Are 4 Ways To Use Events
Searching for a way to register events in my plugins, I ended up reading a handful of different core Atom plugins that do event registration. A came across 4 distinct ways to actually register events:
- You can use
atom.subscribe(), which I found by accident. This was the easiest one, since it didn’t require any magic mixins.
- You can use the
Subscribermodule which you can use some fancy syntax to mix into your classes. This, of course, only works if you have a “class”, something Atom does not generate for you by default (it just throws you a bare Object returned in
- You can just do your event registration in the View class, if you have one, which has
Subscriberalready mixed in.
- Or, finally, you can use Node.js style events and just call
buffer.on 'event'instead of going through a Subscriber.
This begs the obvious question: why 4 separate ways? It also begs the more subtle but subversive question: why would you ever register an event from a view?
Again, the lack of documentation means everyone is going to find their own slightly different way to register events. After a while, you end up with a really messy API. If you missed it— events have no docs. I guess based on that fact, it’s not too surprising that the above happened.
The Plugin Architecture Is Not MVC, It’s Just “V”
Atom has a strong concept of a View, which is good and expected. Less expected, though, is the lack of any other kind of architectural components. Basically, the only thing you ever operate on/with are views— until you are not. There is a “View” superclass, but there is no “Controller” superclass. That’s why you have to use a View to subscribe to events, which Views shouldn’t even be doing.
Another great example is that in order to register a plugin command you actually have to call a method on
workspaceView. Not the Workspace object, but rather, its associated view. The weird part is that the commands are not registered from views, they are actually registered from the pseudo-controller thingies, but they are registered on views. Wat.
Other Various Mishaps And Crazies
- You can get from an
EditorViewto its associated editor (
getEditor()) but you cannot get back to the view. Generally in a DOM like arrangement, you should be able to get back to the parent of a child node (especially since these things actually are DOM nodes). Of course, this is absolutely necessary when some events are only emitted with an editor handle and not the view (like
editor-createdevent), because you need the view to actually do anything useful (remember, it’s all in the “V”).
workspaceViewdoes not have
getActiveEditorView(). Instead the latter only has
getActiveView()(which may not be an editor).
- There is a (undocumented, of course)
configDefaultshook and a
atom.config.setDefaultscommand. Why both?
- There are magical “attach” and “detach” methods associated with the main module but it’s unclear how they differ from “activate” and “destroy”.
- Serialization is apparently handled by the “controller”, but views can also serialize/deserialize themselves. It’s unclear which you should use and when.
- There is a gutter API (the git-diff package uses it) but it is completely unexposed and subject to churn. Again, dogfooding is the answer, here.
But I Want To Like It
I really tried hard to like Atom. I still really do like it. It’s beta, and I’m willing to bet that it will get (functionally) better. I am still, however, seriously concerned about performance, and although I thought I wouldn’t mind it too much, I found myself getting extremely frustrated with the lagginess by the end of the week, to the point where I launched Sublime a couple of times when I knew I was going to touch larger files.
Atom has a ton of potential. The power of using WebKit to render a UI opens a ton of doors, because it literally lets you customize every single pixel in the window. That’s power I don’t even think Vim or Emacs have. There are so many plugin ideas I have with that kind of power, including local Fiddle support, live updating game IDE features (you can use a Canvas to live-render your game) like Processing, live Brackets-style web editing, and more.
I want Atom to work, I really do, but it’s not quite working, yet. If GitHub can resolve performance problems, lay down a solid foundation for an API, document it, and fix some of the minor UI glitches, they would have something I’d be happy to switch to. Until then, I’m probably going to play around with it for a little longer and then inevitably switch back to Sublime.