How to Hack on Brackets

Narciso (NJ) Jaramillo
@notwebsafe

What To Hack On

What Should I Work On?

Or whatever you want!

Signing Up for a Bug

  • Check the labels
  • Comment on the bug to claim it
  • Submit a pull request
  • After it's merged, add a comment asking filer to close

Signing Up for a Feature

  • Mention what you're working on on the Google Group
  • Describe your design/approach and get feedback
  • Or post a prototype (as a branch or an extension)

Hacking on Core

Get Set Up

  • Install a build of Brackets to get the shell
  • Fork the Brackets repo
  • Run the setup script in your fork of the repo
    Mac
    tools/setup_for_hacking.sh "/Applications/Brackets Sprint 14.app"
    Win
    tools\setup_for_hacking.bat "C:\Program Files (x86)\Brackets Sprint 14"

Development Workflow

  • Launch Brackets
  • Debug > New Brackets Window
  • Edit / save in first window, reload / test in second window

Trouble?

  • Debug > Show Developer Tools brings up Web Inspector
  • Delete your prefs/cache
    Mac
    ~/Library/Application Support/Brackets
    Win
    {USER}\AppData\Roaming\Brackets
  • Revert to installed Brackets source
    Mac
    tools/restore_installed_build.sh "/Applications/Brackets Sprint 14.app"
    Win
    tools\restore_installed_build.bat "C:\Program Files (x86)\Brackets Sprint 14"

Practice Good Hygiene

Unit Testing, Jasmine-style

describe("PreferenceStorage", function () {

    it("should read initial preferences from JSON", function () {

        var store = new PreferenceStorage(CLIENT_ID, 
            {"foo": "bar", hello: "world"});
        expect(store.getValue("foo")).toBe("bar");
        expect(store.getValue("hello")).toBe("world");

    });

});

Testing Asynchronous Sequences

it("should remove a list item when a file is closed", function () {
    DocumentManager.getCurrentDocument()._markClean();
           
    // close the document
    var didClose = false, gotError = false;
    runs(function () {
        CommandManager.execute(Commands.FILE_CLOSE)
            .done(function () { didClose = true; })
            .fail(function () { gotError = true; });
    });
    waitsFor(function () { return didClose && !gotError; }, 
        "FILE_OPEN on file timeout", 1000);
            
    // check there are no list items
    runs(function () {
        var listItems = 
            testWindow.$("#open-files-container > ul").children();
        expect(listItems.length).toBe(1);
    });
});

Unit testing in Brackets

  • Core tests are in brackets/test/spec
  • Add new test files to brackets/test/UnitTestSuite.js
  • Test contexts:
    • True unit tests: run in test runner window
      Use require() for modules (private to test window)
    • Integration tests: (slower)
      SpecRunnerUtils.createTestWindowAndRun()
      testWindow.brackets.test.ModuleName

Code Complete! What Now?

  • Fill out the Contributor License Agreement (once)
  • Submit a pull request from your fork
  • Small changes are reviewed within a couple of days
  • Larger requests will be reviewed in a future sprint
  • Core team sprints are 2.5 weeks long

Writing Extensions

What You Can Build With Extensions

Try to build new features as extensions first

Minimize core code changes (and discuss with community)

How to Build an Extension

  • Create a folder or repo in extensions/user
  • Create a main.js file containing your main module
  • Use brackets.getModule() for core modules
  • Use require() for your own modules and strings
  • Use ExtensionUtils.loadStyleSheet() for styles
  • Create unittests.js for unit tests

Example: HelloWorld

define(function (require, exports, module) {
    "use strict";

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

    // Function to run when the menu item is clicked
    function doHelloWorld() {
        window.alert("Hello, world!");
    }
    
    // Register command--can be called from menus, kbd shortcuts, etc.
    var MY_COMMAND_ID = "helloworld.sayhello";
    CommandManager.register("Hello World", MY_COMMAND_ID, doHelloWorld);

    // Create a menu item and key binding bound to the command
    var menu = Menus.getMenu(Menus.AppMenuBar.FILE_MENU);
    menu.addMenuItem(MY_COMMAND_ID, "Ctrl-Alt-H");
});
                        

Code Hinting Extensions

Register with CodeHintManager.registerHintProvider()

{    
    shouldShowHintsOnKey: function (string) {
        // quick shortcut check
        // return true if you want to pop up hints on this key
    },

    getQueryInfo: function (editor, cursor) {
        // return the query object to be used for hinting
        // must have queryStr property--set to null if no hints
        // query object can have other properties
    },
    
    search: function (query) {
        // takes the query object and performs the actual search
        // returns an array of matching strings
    },
    
    handleSelect: function (string, editor, cursor) {
        // performs the edit based on the selected string
    }
}

Quick Open Extensions

Register with QuickOpen.addQuickOpenPlugin()

{
    name: /* plugin name */,
    fileTypes: /* array of applicable types, or [] for all */,
    match: function (queryStr) {
        // returns true if this provider wants to handle the query
    },
    search: function (queryStr) {
        // returns list of applicable items
        // items can be strings or objects with a "label" property
    },
    itemFocus: function (item) {
        // called when user highlights an item
    },
    itemSelect: function (item) { 
        // called when user chooses an item
    },
    resultFormatter: function (queryStr, item) {
        // returns an <li> to display the given query and item object
    },
    done: function () { 
        // called when quick open is closed
    }
}

Quick Edit Extensions

Register with EditorManager.registerInlineEditProvider()

function myProvider(editor, pos) {
    // returns either a Promise that will be resolved with an inline widget
    // or null if we don't want to handle it
}

// Standard prototype chain hookup--do it however you like
function MyInlineWidget() {
    InlineWidget.call(this);
    // construct your widget's contents inside the $htmlContent node
}
MyInlineWidget.prototype = new InlineWidget();
MyInlineWidget.prototype.constructor = MyInlineWidget;
MyInlineWidget.prototype.parentClass = InlineWidget.prototype;

// can also implement onAdded, onClosed

Code Complete! What Now?

More Resources

If something's missing, please help us improve the docs!

GO
#brackets on freenode
Permalink: is.gd/hackbrackets