Custom tooltips in the Atom text editor

Recently, a colleague requested a new feature for a package I maintain for the Atom text editor, that gives automatic syntax highlighting to macro files written for Geant4. The Atom text editor is great, and lives up to its reputation of being hackable to the core, but at the same time, sometimes I wish things were a little more clearly explained when writing an addon, or there were a few more examples. So this post is here to serve that purpose, giving another perspective on how to add tooltips in an atom package.

The aim of the package I was writing was simple. Geant4 uses macro files as application inputs. It’s difficult to remember exactly what all the macro commands are, and what they  do. The aim of the tooltips was to facilitate this, like so:

49058054-7d27-11e6-820d-438f00f2a5fb
The final product. Tooltips for Geant4 macros.

Seems pretty easy. The thought I had was to track the mouse position, and when it entered a region that had the class tags saying it was over a function (blue text), I would display add a Tooltip to the region via Atom’s Tooltip manager. In the end, that’s what I did, but there is a bit of nuance to Atom’s Tooltip manager that let’s you do this in a few ways, some of which are quite clever.

Step 1: Understanding Tooltips

Tooltips in Atom are managed by the Tooltip Manager. The tooltip manager provides methods that lets you add tooltips to objects as if you were adding a Bootstrap tooltip to an object using jQuery. If you are familiar with JavaScript, you’ve probably seen something like this, if you haven’t, it’s worth visiting the Bootstrap tooltip page just to see how everything is meant to work.

Typically in JS, you place some extra code in an element to define a tooltip, and then initialise it using a jQuery selector:


$("#idOfTooltipElement").tooltip(options)

In Atom, it is handled a little differently, but follows the same principal

// create a tooltip
myTooltip = atom.tooltips.add(elementToRecieveTooltip, options)
// when the tooltip shouldn't exist any longer
myTooltip.dispose()

Options here is specified a JS object that contains the same option fields as a Bootstrap tooltip, with a few tiny changes. You can specify a title field, which is the text the tooltip will show. The trigger conditions are the same as Bootstrap’s ‘click | hover | focus | manual’ options, although manual will automatically trigger the tooltip to display.

In the Geant4-macro highlighting, I use some of these options to change the default template of the tooltip. Notably, I change the template of the tooltip so I can change the CSS style rules.

text = "Pre-generated text"
myTooltip = atom.tooltips.add(evt.path[0],
  {title: text,
   trigger: "manual",
   placement: "bottom",
   template: '<div class="tooltip" role="tooltip">
                <div class="tooltip-arrow"></div>
                <div class="tooltip-inner" style="max-width: 300px !important; white-space: normal !important; text-align: left"></div>
              </div>'
   })

 

What’s cool here is that if you set your code to update the tooltips based on what CSS elements are present, every time a new element you want to attach a Tooltip to is written, you can add the Tooltip just by creating it using “trigger: ‘hover'”.

As you can see above, I use a manual trigger, rather than a hover trigger, tracking the mouse and recognising when it is over appropriate text rather than assigning a new tooltip to each function instance that is created.

Step 2: Tracking the mouse in Atom using events

The other thing to worry about when making tooltips is what events are going to trigger the assignment of a tooltip object in the manager. You can use keyboard input events to see if a new element has been added by typing, or you can use the mousemove event to follow the mouse position. There are probably a few other ways to do it too (you can find a full list of events by going in to Atom in developer mode).

I handle this by tracking the mouse. When the tooltip class is loaded, an event listener is added as follows:

  constructor: ->
    atom.views.getView(atom.workspace).addEventListener 'mousemove', (evt) =>
      @mouseMove(evt)

  mouseMove: (evt) =>
    isScope = (evt.path[1].className == @scopeName)
    isFunction = (evt.path[0].className == "support function")
    if isScope && isFunction
      @tooltipCreate(evt) if !(@theTooltip?)
    else if @theTooltip?
      @tooltipDestroy()

When the mousemove event is triggered, a function is called that checks using the event occurence to see if the cursor is in the right part of the screen to display a tooltip. If it is, and there is no tooltip existing, a new tooltip is created, otherwise, if the mouse move event occurs and it isn’t in an area that should have a tooltip, any existing tooltips are destroyed.

The event object passed by the mouse is really good at giving you spatial information about where you are in the code. Event.path contains a tree of HTML elements which you can use to assign the tooltip to later on. When the tooltip is finally created, it is created using the element stored in ‘event.path[0]’.

Final Thoughts

The main point of all this is to really highlight that tooltips in Atom work almost exactly like Bootstrap tooltips, and as such are really versatile. This wasn’t all that clear to me at first, and I spent a lot of time reading about different event subscriptions, emitters, and the many other things Atom has tucked inside it. Reading through some other packages that provide tooltips I was a little confused by their complexity, and wanted to write about my own implementation. Once I got the hang of it though, it was reminded why I love Atom and its hackability. You can check out the language-geant4-macro package on Github.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s