Requirements

Prerequisite knowledge

Readers should know about Brackets or Edge Code and have a good knowledge of JavaScript.

 

Required Adobe products

  • Brackets (An open source code editor for the web, written in JavaScript, HTML and CSS.)

User level

Intermediate

Brackets, the new open source code editor for the web, takes extensibility very seriously. In fact, some of its core features are implemented as extensions, such as Quick Open, Quick Edit, and HTML Code Hinting.

Brackets has a highly modular architecture that enforces separation of concerns by keeping the Brackets main code as focused as possible, while it is still deeply customizable—an absolute requirement to fit modern web developers’ needs.

You may notice, however, that while Brackets comes with some default extensions, out-of-box it still might seem a bit limited. For that reason, the first thing new users can do is to install extra extensions, as described in my blog post: Brackets/Edge Code extensions you should install right now. By doing so, you will have access to extensions, such as string manipulation or code snippets.

That said, as a user, you'll probably quickly find yourself thinking "if only Brackets could X... how hard can it be?" Well, since Brackets is built using web standards, as described in the article, Brackets: The Open Source Code Editor for the Web, by Adam Lehman, customizing it by yourself is far easier than you might imagine. It's also a very rewarding experience—one you can quickly get hooked on.

In this article, we'll take a look at how Brackets extensions work, how you can build one using a template or starting from scratch, and how to best set up your development environment.

Note: While Edge Code is a Brackets distribution that you can use to potentially build extensions too, it does not come with a Debug menu, making it less convenient than Brackets for the job of writing extensions. You can, however, use any extension you create with Brackets within Edge Code. This article was written using Brackets Sprint 16.

Extensions location

Technically speaking, an extension is nothing more than a folder containing a JavaScript file named main.js, which is executed by Brackets at startup.

When it finished loading and executing the rest of its code, the application will look for those extensions in the following folders:

  • The default extensions folder (src/extensions/default). This folder contains default extensions that the Brackets team has chosen. Users should not put anything in this folder.
  • The user extensions folder (/Users/<user>/Library/Application Support/Brackets/extensions/user on OSX or C:\Users\<user>\AppData\Roaming\Brackets\extensions\user on Windows 7). Brackets users put downloaded third-party extensions from Github within this folder. For convenience, Brackets automatically opens this folder when you select Help > Show Extensions Folder.
  • The extension development folder (src/extensions/dev). As an extension developer, you place your own extensions within this folder. This is a more convenient place for them than the user extension folder, which ties into the user system.

Note that Brackets provides a place for disabled extensions, at src/extensions/disabled, which makes it easy to temporarily disable extensions without having to delete them altogether.

Setting up Brackets for development

To debug an extension within Brackets, use the following steps.

  1.  Within Brackets, select Debug > Show Developer Tools to open the Chrome developer tools. Note that, in the current version (sprint 16), Brackets opens the Chrome developer tools in a Chrome browser window (or opens Chrome if it was closed). On Macintosh, if you use Spaces, ensure that you have a Chrome window open alongside Brackets.
  1. I strongly recommend that you disable the cache in the developer tools tab opened from Brackets, otherwise you won’t be able to test your changes, even after reloading the application. You can do that from the developer tools option panel.
  1. You'll quickly realize, however, that writing code and testing the result within the same editor window is just not the way to go. This is exactly why it is preferable that you open a New Brackets Window under the Debug menu, but selecting Debug > New Brackets Window (Figure 4).

I also strongly recommend testing from this second window. To do so correctly, you must set up the developer tools from this window, as explained in step 1. My typical setup looks like the following:

Working from a Brackets repository clone

Another good practice is to develop your code from a separate copy of the Brackets source. The advantage is that you can work with the latest version of the application code, ensuring your extension is up-to-date. You also avoid having to edit the original application source, which could have some undesirable effects. A simple way to work from a Brackets repository clone would be to just fork or clone the Brackets repository from GitHub as described in the following steps.

  1. Go to the Brackets repository from GitHub.
  2. If you have the GitHub client for Mac or GitHub client for Windows, simply click the “Clone in Mac” or “Clone in Windows” button (depending on your platform) from this repo homepage, and choose and a local destination on your disk.
  1. Specify that Brackets should run this code instead of the default application contents by pressing shift while you launch the app.

To better understand what I mean by this, it is important to understand that the Brackets application is actually made up of two parts:

  • On the one hand, you have the Brackets shell built with the Chromium Embedded Framework, which executes local web content within a native desktop application shell.
  • On the other hand, you have local web content such as HTML, CSS, and JavaScript files, which are the Brackets editor source code.

By default, the Brackets shell executes the web files stored within the application content folder, which was created when the application was installed. It is not recommended that you  edit code directly from this folder, however. As a result, the native application has the capacity of running from within a different source folder by pressing and holding the Shift key while launching the app. Alternatively, you can use the setup_for_hacking script included under the tools folder of the application source code. You can get this script in from the article, How to Hack on Brackets, on GitHub.

Using an extension template

While you can of course write an extension from scratch, it's preferable to start with a template. Several options are available for you to choose.
A typical way to begin with extension development is to copy and paste an existing extension relatively close to what you want to achieve. This is a valid way to get started, but keep in mind that not all extensions are up to date, and that not all necessarily showcase the latest best practices. Also, some extensions can be really hard to read, especially if you're new to extension development.

Alternatively, you can use the Brackets Extension Toolkit, which provides you with a dedicated template, which you can just drag and drop to the src/extensions/dev folder. The toolkit also comes with  heavily commented code to guide you through your first development experience of extensions, and the toolkit also provides related helper tools.

Whatever option you choose, once you're relatively comfortable, I recommend storing your own custom template in the src/extensions/disabled folder to access it quickly later. Simply copy it to the user extensions folder.

Introduction to the Brackets extension API

If you look at an existing extension for the first time, chances are that you'll feel a bit lost. Brackets extensions are JavaScript modules as specified by RequireJS' Simplified CommonJS Wrapper. Understanding JavaScript modules is outside the scope of this tutorial, however, for more information, see Modern Modular JavaScript Design Patterns, by Addy Osmani. For demonstration purposes, let's assume that in its simplest form, an extension would look like the following:

define(function (require, exports, module) { 'use strict'; // Extension code goes here console.log("Extension initialized"); });

All the code within the body of the anonymous function inside the define call executes at application startup.
Of course, most of the time, what you want to build is an extension that makes it possible to execute some code when another part of the application invokes the extension. For example, when a user selects a menu item, this causes your application to execute an action.

You can code this kind of mechanism through commands. You register a command ID with a function so that when the app invokes the ID, a function executes. This is the job of the CommandManager. As with pretty much everything in Brackets, the CommandManager itself is a module. To access other modules from an extension, simply call the dedicated brackets.getModule() method, passing to the method the path to the module relative to the src folder. Notice how the code below does not specify the JS file extension.

var CommandManager = brackets.getModule("command/CommandManager");

To specify to the CommandManager which function to execute, and when, specify the register(name, command_id, function) method. The parameters within this method are as follows:

  • The name parameter is a readable user-friendly name, which is also used as a menu label in the user interface.
  • The command_id parameter is a string to uniquely identify this command. Format it as follows: [author].[my_extension].[menu_name].[my_command_name].
  • The last parameter, function, is the function the method calls when a user selects a menu item in the UI. When a user selects a menu item in the UI, the app automatically calls execute(command_id) method with the corresponding ID, as shown in the following code example.
var COMMAND_ID = "dderaedt.tutorialExt.logHelloWorld.logHelloWorld"; var COMMAND_NAME = "Log Hello World"; function sayHello() { console.log("Hello World"); } CommandManager.register(COMMAND_NAME, COMMAND_ID, sayHello);

At this stage, the extension is functional but will never be called since something must invoke the command ID. To do so, we add a menu item through the Menus module to invoke  the corresponding command ID, resulting in the execution of the function. To do so, we'll first access the Menus module, as follows:

var Menus = brackets.getModule("command/Menus");

Then, we'll get a handle to the menu we want to add our item to (the File menu, in this example).

var fileMenu = Menus.getMenu(Menus.AppMenuBar.FILE_MENU);

Finally, we simply add an item corresponding to the command that we wish to trigger, as follows:

fileMenu.addMenuItem(COMMAND_ID);

The resulting code for this Hello World extension is as follows:

define(function (require, exports, module) { 'use strict'; var CommandManager = brackets.getModule("command/CommandManager"); var Menus = brackets.getModule("command/Menus"); var COMMAND_ID = "dderaedt.tutorialExt.LogHelloWorld"; var COMMAND_NAME = "Log Hello World"; function sayHello() { console.log("Hello World"); } CommandManager.register(COMMAND_NAME, COMMAND_ID, sayHello); var fileMenu = Menus.getMenu(Menus.AppMenuBar.FILE_MENU); fileMenu.addMenuItem(COMMAND_ID); });

Editing the current document

Now, to do something slightly more useful, let's pretend we want our extension to generate some code inside the current document.

In Brackets, files opened in the tool are represented as instances of the Document class. To get a reference to the current document (in other words, the file the user currently has open and editing), we need to use the DocumentManager module. First, we must get a reference to this module, which is inside the document folder.

var DocumentManager = brackets.getModule("document/DocumentManager");

Now, starting from the code in the previous section, rename the sayHello() function to addSomeText().

The first thing we'll need to do inside this function is get a reference to the current document.

var currentDoc = DocumentManager.getCurrentDocument();

Now you can use the Document API, which can help you work with the text content. Here are some of the available methods:

  • getText() returns the whole text content
  • setText() set the whole text content
  • getRange(start, end) returns part of the text content
  • replaceRange(text, start, end) replaces text in a given position

Note that the position in the text (such as the start and end parameters above) are expressed through a position object, which contains two properties: line, the line number, and ch (the character number).

In the following scenario, pretend that we want to generate a one-line comment at the location of the current cursor position. Since the Editor object manages code editing, you must get access to the current editor instance as returned by the EditorManager.getCurrentFullEditor() method. So after you make sure to import the corresponding module, as follows:

var EditorManager = brackets.getModule("editor/EditorManager");

You can access the editor for the current document, as follows:

var editor = EditorManager.getCurrentFullEditor();

Finally, you can then use the editor to do all sorts of operations related to text selection, such as:

  • getSelectedText()
  • setSelection(start, end)
  • selectWordAt(position)
  • getCursorPos()

Now that you have access to all you need, you can rewrite the addSomeText() function, as follows:

function addSomeText() { var currentDoc = DocumentManager.getCurrentDocument(); var editor = EditorManager.getCurrentFullEditor(); var pos = editor.getCursorPos(); currentDoc.replaceRange("//Black magic. Do not modify EVER", pos); }

Where to go from here

Of course, such simple examples will only get you so far. You'll quickly need to learn more about the APIs exposed by other modules. The following are some useful resources to learn more about it:

  • Brackets' source code itself is, of course, the ultimate and only 100% reliable source of information. If you installed the Brackets extension toolkit, simply select Help > Open Brackets src. Start by taking a look at Document, Editor, EditorManager, FileUtils and of course all the default extensions.
  • The Brackets wiki is full of useful information. In particular, the How to write extensions page covers the basics you should know about. The Brackets extension toolkit includes a shortcut to this wiki under the Help menu.
  • If you intend to make an extension that edits the code for the current document, it's a good idea to get familiar with CodeMirror, the project responsible for the underlying code editing logic.
  • To understand how Brackets works at a higher level, learn more about the Brackets general architecture in the article, An overview of Brackets code architecture.
  • Finally, if you plan to include some UI in your extension, be sure to check the Extension UI Guidelines draft on the Brackets wiki.