Web
Article

How To Develop a Package for GitHub’s Atom Code Editor

By Jérémy Heleine

Atom logo

Atom is a fantastic editor and comes by default with all you need to develop your project … except maybe for one thing – that one detail that you’d love to see in Atom. That one thing could be anything: a keyboard shortcut to write faster; an important feature; or even syntax highlighting for a language you use but that isn’t supported by default.

The good news is that Atom is ready to welcome a lot of packages. You can extend its default features with these packages, written by the community. But what if you don’t find the package you’re searching for?

Writing your own package is not so complicated, so why not? In this tutorial we’ll see how to create our own package for Atom, by taking the example of a syntax highlighting package.

What We’ll Build

Recently I wanted to develop some programs in the Scilab language. As it’s a language used in maths, it’s not really the type of language we find by default in Atom, and there was no package for its syntax. That’s why I decided to write my own package: language-scilab.

Here we’ll write a similar package, for the language you want. We’ll see first how to initialize a brand new package with a valid package.json file. Then we’ll write some rules to highlight our language. Finally, we’ll see how to publish our package – so that any user of Atom will be able to use it.

Initializing a New Atom Package

Atom uses a configuration folder to store all your personal options, but also the packages you installed. This folder is named .atom, and is located in your personal folder (/home/user/.atom for instance).

Viewing the packages folder

The packages you install are all located in the packages subfolder of this folder. Every package has its own folder. So the first step to create your package is to create the your folder, named after your package. In our example, I create the folder language-mylanguage: it’s a sort of naming convention when we want to add the support for a language.

For now, your package is invalid. To be recognized by Atom, it needs a package.json file at the root of the folder you just created.

The package.json file

This file contains some information, like the name of your package or the repository where we can find it. Below is the package.json file of our language-mylanguage package (explanations follow):

{
  "name": "language-mylanguage",
  "version": "0.0.0",
  "description": "Mylanguage language support in Atom",
  "engines": {
    "atom": "*"
  },
  "dependencies": {},
  "repository": {
    "type": "git",
    "url": "https://github.com/JeremyHeleine/language-mylanguage.git"
  },
  "bugs": {
    "url": "https://github.com/JeremyHeleine/language-mylanguage/issues"
  },
  "license": "MIT"
}

We find several entries in this file. First, the name one: as you can guess, it contains the name of your package. You can (and you should) add a description with the description entry, basically to let the other users know about what your package does.

The version entry is filled with a version number, which must respect the following convention: major.minor.bug. Here we indicate 0.0.0. Even if you know that you’re developing the version 1.0.0 or 0.1.0 or your package, indicate 0.0.0. We’ll see why when we publish our package.

The engines entry can be used to indicate the minimal required version of Atom for your package to work. In the same vein, we find the dependencies entry to indicate other packages needed by your package. It must be used if you create a plugin for another package.

Then we find the repository entry. It’s the URL indicating where the public repository of your package is located. If you want to publish your package, you need this entry. You can leave it empty for now if you don’t want to create your public repository right now, but think about filling it before publishing.

The bugs entry is the URL where we can report issues affecting your package. Here we use the default page GitHub offers for every repository. Finally, a license name can be indicated with the license entry.

Other entries can be filled if you need them. They’re not mandatory. All available entries can be found on Atom documentation.

Now that your package has a valid package.json file, Atom can recognize it and load it. However, it’s totally useless right now, so it’s time to make it useful by giving it some features.

Note that Atom won’t load your package now: it loads all the installed packages at start. You can, however, force Atom to reload the packages with View/Reload. It’s useful for seeing the changes you just did on your package.

Creating a Syntax Highlighting Package

To add new syntax highlighting rules, you need to create a subfolder named grammars. In this folder, create a new CSON file named after the language you want to support (e.g. mylanguage.cson). This file will contain all your syntax highlighting rules.

Basic Information

Before identifying the syntax of our language, we need to tell Atom how to identify this language. This can be achieved thanks to three entries in your newly created CSON file:

'scopeName': 'source.mylanguage'
'name': 'Mylanguage'
'fileTypes': ['ext1', 'ext2']

The scopeName entry is a way to identify your language for themes, other packages or even configuration files. Generally we follow the convention used above.

The name entry contains the name of the language that will be displayed at the bottom right corner of Atom, when you are editing a file using your language.

Finally, the fileTypes entry contains an array listing all the file extensions used by your language. Each time you open a file using one of these extensions, Atom will automatically choose your syntax highlighting.

Syntax Rules

All the rules for your language must be declared in a fourth entry: patterns. We’ll see here two types of rules we can add. However, any added rule must be declared between {} in the patterns array:

'scopeName': 'source.mylanguage'
'name': 'Mylanguage'
'fileTypes': ['ext1', 'ext2']

'patterns': [
    {
        # First rule
    },
    {
        # Second rule
    },
    # …
    {
        # Last rule
    }
]

Matching a Syntax Element

Let’s build our first rule with match, the first type we’ll see. This rule will detect any number like 4, 7.2 or even .568, for instance:

{
    'match': '\\b(([0-9]+\\.?[0-9]*)|(\\.[0-9]+))\\b'
    'name': 'constant.numeric.mylanguage'
}

The match entry must be filled with a valid regex. Then, each time Atom sees text matching this regex, it will encapsulate it in a span element with the class names indicated in name. You can add several class names, each one separated by a dot.

You should notice that the regex contains more backslashes than usual. In fact, we can use every special character like \b to detect a word break. However, as your regex is nothing but a string, if you just type \b, the b will be escaped and nothing will happen. We want the \b string in our final regex, after the string being parsed. That’s why we need to escape the backslash. In practice, that means you need to double each backslash you type and \b becomes \\b.

You can add whatever class names you want in the name entry. However, there are some conventions to follow. Generally, indicate the type of element you want to highlight and finish with the name of the language. There are a lot of different types that are all shared by different editors. TextMate lists them in its documentation on naming conventions.

If you use these conventions, other developers can better understand what you’re doing. But there’s another reason why it’s a good idea to respect these conventions: themes.

In Atom, it’s the theme that decides which style will be displayed for element you want to highlight. These themes try to be compatible with the more languages possible, so they use these conventions.

That way, in the example above, we don’t have to add any CSS to stylize our numbers. As we use the convention, our theme automatically detects it. Better: as it’s the same convention used for every language, numbers in our language will be displayed the same way numbers are displayed in other languages.

Matching Several Elements in the Same Rule

The name entry should always be present. It’s here to highlight the entire text verifying the regex. But what if you want to highlight several elements in the same rule?

Let’s take an example with a function declaration like the following:

function nameOfTheFunction() {
}

We want to highlight the word function as a keyword, along with the name of the function. If we can match the function keyword to highlight it without any problem, the name of the function can’t be retrieved without this keyword. That’s why we need to highlight these two elements in the same rule. This can be achieved with the following rule:

{
    'match': '\\b(function)\\s+(\\w+)\\s*\\(.*\\)'
    'captures':
        '1':
            'name': 'keyword.control.mylanguage'
        '2':
            'name': 'entity.name.function.mylanguage'
    'name': 'meta.function.mylanguage'
}

The name entry still contains the class names that will encapsulate the whole text. However, we now find the captures entry, which needs some explanation.

Our regex contains capturing parentheses around function and the name of the function. As in other languages, these captures can be retrieved thanks to the captures entry. The 1 then refers to the first capture (the function keyword) and the 2 refers to the second (the name of the function). We apply the right class names to them with name.

With the function declaration above we obtain the following in Atom:

<span class="meta function mylanguage">
    <span class="keyword control mylanguage">function</span>
    <span class="entity name function mylanguage">nameOfTheFunction</span>
    …
</span>

Matching the Beginning and the End of a Rule

Sometimes it’s easier to detect the beginning and the end of an element than the element itself. It’s true for strings, for instance:

{
    'begin': '\''
    'end': '\''
    'name': 'string.quoted.single.mylanguage'
}

With this rule, Atom will search for a first quote to begin the string. Then, the next quote it finds will be the end of the string, as expected (the search is ungreedy). You can indicate the regexes you want as begin and end delimiters. As always, the name entry must be filled with the class names you want for the whole retrieved element.

For example, with this:

var best_site = 'SitePoint';

… you get this:

var best_site = <span class="string quoted single mylanguage">'SitePoint'</span>;

Capturing elements is also possible. Highlighting the quotes is, for example, a common task. To achieve this, you can use beginCaptures and endCaptures. The way they work is exactly the same as captures works for the match rule. The only difference is that with beginCaptures you can capture parentheses in the begin regex and, with endCaptures, you can capture elements in the end regex:

{
    'begin': '\''
    'beginCaptures':
        '0':
            'name': 'punctuation.definition.string.begin.mylanguage'
    'end': '\''
    'endCaptures':
        '0':
            'name': 'punctuation.definition.string.end.mylanguage'
    'name': 'string.quoted.single.mylanguage'
}

With the same JavaScript line as above, you’ll now get this:

var best_site =
    <span class="string quoted single mylanguage">
        <span class="punctuation definition string begin mylanguage">'</span>
        SitePoint
        <span class="punctuation definition string end mylanguage">'</span>
    </span>;

Finally, you can also capture elements placed between your delimiters. As match is not used, you can’t use captures, but you can use patterns instead.

This new entry is an array listing the elements you want to highlight inside the current highlight. It’s a regex to execute only between your delimiters. In the example below, we escape \' to highlight the fact that it’s a special character in our string:

{
    'begin': '\''
    'beginCaptures':
        '0':
            'name': 'punctuation.definition.string.begin.mylanguage'
    'end': '\''
    'endCaptures':
        '0':
            'name': 'punctuation.definition.string.end.mylanguage'
    'name': 'string.quoted.single.mylanguage'
    'patterns': [
        {
            'match': '\\''
            'name': 'constant.character.escape.mylanguage'
        }
    ]
}

You can add as many patterns as you want. With the rule above, if we want to highlight the following JavaScript code:

var best_site = 'It\'s SitePoint';

… we’ll get this:

var best_site =
    <span class="string quoted single mylanguage">
        <span class="punctuation definition string begin mylanguage">'</span>
        It<span class="constant character escape mylanguage">\'</span>s SitePoint
        <span class="punctuation definition string end mylanguage">'</span>
    </span>;

Publishing an Atom Package

So now our package works great … for us. If you needed your package, chances are that other developers also need it. In this case, why not share your work, so that everyone can enjoy it?

The very first thing to do before publishing your package is to check if a package with the same name as yours already exists. To do that, you can search for this name in Atom itself (in the options, where you can install packages). You can also check the URL https://atom.io/packages/name-of-your-package: if you get an error, the package doesn’t exist yet, and you can publish yours.

To publish your package, you’ll need a public repository, like the ones you can create on GitHub. Refer to the tips above on how to indicate your repository’s URL in the package.json file of your package.

Now you’re ready to publish your package. In a terminal, go to your package’s folder and type apm publish minor to publish your first minor version. You can also publish a major or very minor version by replacing minor with major or patch.

This command will do several things. If it’s the first time you’ve used it, it will begin by registering your package’s name. Then, it will retrieve the current version number and increase it. That’s why we indicated 0.0.0 as a version number for our package: by using apm-publish, it will automatically become 0.1.0 (with minor). Then, the command creates the corresponding Git tag and pushes these changes. For this step, your credentials will be required.

Finally, your package is published, without you having to have anything else.

apm publish

You can unpublish one of your packages with apm unpublish name-of-the-package. Be careful if you also want to delete your GitHub repository: you have to unpublish your package before deleting your repository. If you delete your repository first, you won’t be able to unpublish your package.

Closing Words

Atom is a very powerful editor. With packages, you can enhance it and add the features you need. Now you know how to create yours and how to publish them.

As Atom is very flexible, you can do a lot of different things with packages. That also means that we can’t describe in one article how to write any type of package. That’s why we only saw here how to create a syntax highlighting package. However, initializing and publishing your package will always be done in the same way.

If you’ve got any questions or comments, please reply below. Have you created a package of your own? Tell us about it!

Free Guide:

7 Habits of Successful CTOs

"What makes a great CTO?" Engineering skills? Business savvy? An innate tendency to channel a mythical creature (ahem, unicorn)? All of the above? Discover the top traits of the most successful CTOs in this free guide.

  • Per Pedersen

    great tutorial….what if I write one for python, where’s the original syntax and can you choose between them? I’m newbie…

  • Puranjay Jain

    Great tutorial @Jérémy Heleine:disqus! Can you write something like that for autocomplete-providers as well It’s kind of tricky for newbies and harder to explain to people

    • http://tassedecafe.org Jérémy Heleine

      Why not, I’ll think about it :)

  • Alpyre

    I think I know what he wants to do.
    This is something I really couldn’t find anywhere on the web.

    Let’s say we want to create a package for an API. For example lets say AmigaOS API.
    For AmigaOS you code in C or C++. So language-c package would be enough for highlighting the language syntax.

    But you would also want the types, macros and functions of the API to be highlighted as well.

    So how do you do that?

    Is there a way to create a package that extends the (i.e)language-c package? Like including that package and just adding on the API’s symbols.

    …or do we have to make a complete copy of the current language-c package with a new name, and add our additional syntax there?

Recommended
Sponsors
Because We Like You
Free Ebooks!

Grab SitePoint's top 10 web dev and design ebooks, completely free!

Get the latest in Front-end, once a week, for free.