How to write extensions

Peter Flynn edited this page Oct 24, 2015 · 63 revisions
Clone this wiki locally

If you'd like to develop a theme, take a look at Creating Themes

There are three stages to developing an extension:

  1. Set up the basic scaffolding.
  2. Develop your extension and debug it.
  3. Package and publish your extension for others to use.

Follow these links to the sections below for details!

Looking for inspiration? Check out the Extension Ideas list.

Creating an Extension

  • Open your extensions folder by selecting "Help > Show Extensions Folder" in Brackets
  • Inside the user folder(*), create a new "yourExtensionName" folder, and inside that create a main.js file.
  • For a quick start, you can paste in the Simple "Hello World" extension or the code from an existing extension that is similar to what you want to do.
  • If you're working on anything big we recommend you post to the brackets-dev Google group or the #brackets IRC channel on freenode early on so you can get feedback (there may be others working on similar ideas!).

* Note: Because Extension Manager lets you delete extensions from this location, in the long run it's safer to develop inside the src/extensions/dev folder. The best way to do that is to clone the Brackets source and run from that copy. This also makes it easy to test your extensions with upcoming Brackets changes before they're released.

Testing/Debugging Workflow

  • Edit your main.js file.
  • Save the file and restart Brackets via "Debug > Reload With Extensions" to see your changes.
  • To debug problems, use "Debug > Show Developer Tools". You can use console.log(), set breakpoints, etc.
    • The first time you open Developer Tools, you must disable caching - otherwise using Reload while dev tools are open will not reflect changes to your extension.

See Debugging Brackets for a more robust two-window workflow.

You can also write unit tests for your extension.

Publishing Extensions

  1. Add a package.json file next to your main.js
  2. ZIP up your entire extension folder (the GitHub "Download ZIP" button is handy for this) or use the command git archive --format zip -o master to generate a zip file.
    • Note: we've had difficulty with ZIP files created from Finder on the Mac. If you get an error when uploading your ZIP file, try creating it from the command line instead.
    • If your extension utilizes git submodules, they must be wrapped in the ZIP. For a solution, refer to this blog and use git-archive-all.
  3. Publish your extension by uploading the ZIP to the Brackets Extension Registry

For more, see Extension Registry Help.

Common How-Tos

API docs are available online or as JSDoc comments inline in the Brackets source code.

Using modules

  • To load modules from your extension's folder tree, use require() with a path relative to your extension's root folder.
  • To load modules from Brackets core, use brackets.getModule() with a path relative to the Brackets src root.
  • You cannot load modules from other extensions (yet).

You can also use other files packaged inside your extension - for example, see "Load a CSS file" below.

Adding menu items & keyboard shortcuts

See Simple "Hello World" extension for a code sample.

For any new behavior, first register a Command that implements your behavior, via CommandManager.register(). This just maps a Command id (string) to your handler function. Use package-style naming for your Command id (e.g. "myorg.myextension.mycommand") to avoid collisions with other extensions. (See also: higher-level overview of command architecture).

Add a menu item: Get a top-level menu by calling Menus.getMenu() with one of the AppMenuBar constants (currently FILE_MENU, EDIT_MENU, VIEW_MENU, NAVIGATE_MENU or HELP_MENU). Then add a menu item via theMenu.addMenuItem(), linking it to your Command id. The menu item's label will be the string name you gave the Command when it was created.

As a convenience, addMenuItem() also lets you create a keyboard shortcut for your Command at the same time.

Add a context menu item: Get a context menu by calling Menus.getContextMenu() with one of the ContextMenuIds constants (currently EDITOR_MENU, INLINE_EDITOR_MENU, PROJECT_MENU or WORKING_SET_MENU). Then add a menu item via theContextMenu.addMenuItem(), linking it to your Command id exactly like a top level menu item.

Add a menu divider Get a top level or context menu as explained above. Then add a menu dividers via theMenu.addMenuDivider(). It will default to the last position currently in the menu. You have the option of placing it with the position parameter first and last, which will place the divider accordingly. Additionally, you can set position parameter to before and after, pass in a Command ID, and place the divider accordingly.

Add a keyboard shortcut: To add a keyboard shortcut without any related menu item, call KeyBindingManager.addBinding() directly, linking a shortcut to your Command id. Be sure to use the Brackets Shortcuts page to see which shortcuts are available and to add the shortcuts that you use to the list.

To decline a keyboard event and allow other parts of Brackets to handle it, make your Command handler return a $.Promise that is already rejected at the time you return it. (This is useful if you want to override editing keys like Enter only when the cursor lies in certain places, and allow the default behavior in other cases; or always override a key in the code editor but allow default behavior in simpler textfields). (Note: requires Sprint 18 or later)

Adding new UI elements

Add a panel below the editor: Use the CSS class .bottom-panel; see the JSLint bottom-panel.html for an example. Add your panel above the status bar using PanelManager.createBottomPanel("", $(panelHtml)). You may see Resizer.makeResizable() and manual DOM insertion of panels in some extensions but this practice is being phased out since the introduction of PanelManager.

Unofficial techniques - adding UI elements directly through the DOM works, but puts you on shaky ground. Code that does this will break as Brackets updates evolve the UI. Use these code snippets as best practices that behave as nicely as possible given the risks:

Add a toolbar icon: (unofficial) Use $myIcon.appendTo($("#main-toolbar .buttons")).

Add a top panel/toolbar: (unofficial) Use $myPanel.insertBefore("#editor-holder").

UI design: Be sure to follow the Extension UI Guidelines.

Load a CSS file: use ExtensionUtils.loadStyleSheet(). It returns a Promise you can use to track when the CSS is done loading.
To avoid accidentally breaking core Brackets UI, place a CSS class on the root of your UI and make sure all your CSS rules include a descendant selector. E.g. instead of li { ... } use .myExtension li { ... }.

Extending specific Brackets features

For each API listed, see documentation for more details.

Quick Edit (inline editors): To create an extension that responds on Ctrl+E (like the inline color picker), use EditorManager.registerInlineEditProvider(). If multiple "providers" all want to respond in a given context, however, the first one wins - there's no notion of priority or cycling through providers yet.

Quick Docs: Similar to Quick Edit, but register your provider with EditorManager.registerInlineDocsProvider() instead.

Quick Find Definition: To provide quick symbol navigation for a new language, use QuickOpen.addQuickOpenPlugin(). Register for a specific language id and only return true from match() when an "@" prefix is present (see CSS support for a simple example).

Quick Open: To add a new global search feature (like Quick Open), use QuickOpen.addQuickOpenPlugin() with an empty languageIds array. Pick a new, unique prefix for match() to respond to, and register a new command that invokes QuickOpen.beginSearch() with your custom prefix. (See the File Navigation Shortcuts extension for a simple example).

Code Hints: To create an extension that shows a code hint popup, use CodeHintManager.registerHintProvider(). Unlike Quick Edit, these providers can have varying priority to resolve conflicts; more specific providers take precedence - but similar to Quick Edit, only the "winning" provider is shown.

Jump to Definition: To create an extension that responds on Ctrl-J (Jump to Definition), use EditorManager.registerJumpToDefProvider(). Similar to Quick Edit, the first provider to respond for a given cursor position wins.

Syntax Coloring: Extensions can add new code-coloring "modes" via LanguageManager.defineLanguage(). See Language Support for details.

Linting: Use CodeInspection.register() to provide linting/inspection for a given Language. Just like the built-in JSLint functionality, the provider is invoked whenever a file is opened or saved, and its results are displayed in a panel below the editor (providers may be run more frequently in the future, however). Currently, only one provider is accepted per language, although extensions can replace the default JSLint provider for JavaScript.

File Tree: Take a look at the documentation for the project/ProjectManager module. Starting with Brackets 0.44, there are functions you can call (addIconProvider and addClassesProvider) to decorate the tree.

Accessing resources (e.g. images) in your extension

Extensions can get installed at (semi-)arbitrary paths. For example, you might develop your extension in the brackets/src/extensions/dev/foo directory, but a user might install it in /Users/<user>/Library/Application Settings/Brackets/extensions/user/bar.

Thankfully, the require context that's passed in to your extension's main.js file can help you resolve paths. Just call require.toUrl with the relative (to your module) path you'd like to make relative to the site root. IMPORTANT: Make sure you're using the require object that was passed to your module, not the global require object.

For example, if you have awesome.jpg in your extension's top-level foo folder, you can do require.toUrl('./awesome.jpg'), and it will return something like /extensions/dev/foo/awesome.jpg when you call it and /Users/<user>/Library/Application Settings/Brackets/extensions/user/bar/awesome.jpg when your user calls it. The path you give toUrl should be relative to your extension's top-level folder (yes, subdirectories work), and the URL you get back will be relative to the site root (i.e. it will begin with "/").

Working with Preferences

Your extension can access Brackets' preferences and define preferences of its own. For preferences specific to your extension, you should make sure that all of the preferences have a prefix so that they don't clobber any other preferences.

For details, see the full Preferences System documentation. But here's an example that covers the main parts of the API you'll need to know:

var PreferencesManager = brackets.getModule("preferences/PreferencesManager"),
    prefs = PreferencesManager.getExtensionPrefs("myextensionname");

// First, we define our preference so that Brackets knows about it.
// Eventually there may be some automatic UI for this.
// Name of preference, type and the default value are the main things to define.
// This is actually going to create a preference called "myextensionname.enabled".
prefs.definePreference("enabled", "boolean", true);

// Set up a listener that is called whenever the preference changes
// You don't need to listen for changes if you can just look up the current value of
// the pref when you're performing some operation.
prefs.on("change", function () {
    // This gets the current value of "enabled" where current means for the
    // file being edited right now.

// This will set the "enabled" pref in the same spot in which the user has set it.
// Generally, this will be in the user's brackets.json file in their app info directory.

prefs.set("enabled", false);

// Then save the change;

Accessing Node APIs

Brackets includes a built-in Node.js server that runs as a side process. Your extension can include code that runs in Node – accessing useful Node APIs and pulling in helpful NPM libraries. Read more on running code in Brackets' Node instance...

Further reading

For more on Brackets APIs and architecture, see Brackets Development How Tos.

If you're interested in contributing to the core Brackets codebase, see How to Hack on Brackets.