-
Notifications
You must be signed in to change notification settings - Fork 279
Brackets Development How Tos
Warning: because Brackets is still undergoing a lot of change, many of the APIs and techniques below will change in the future. Breaking API changes will be listed in the [release notes](Release Notes).
There are 2 primary milestones you can wait for during startup, defined in utils/AppInit
:
-
htmlReady
signals when the initial DOM content of Brackets is loaded (a template that is rendered by Mustache to include translated strings). Once this event fires, it is safe to query for static DOM elements. Extensions do not need to wait for this event - they are always loaded after it. But core modules must wait for this event before querying the DOM. -
appReady
signals when extensions have finished loading, the initially open project is loaded, and the last-open editor/document is loaded. Waiting for this event is not usually needed for extensions or core.
Do not rely on other events such as $(document).ready
or window.onload
.
The overall Brackets startup sequence unfolds as follows:
- Load most third-party libraries (jQuery, LESS, CodeMirror, etc.)
- Load core Brackets modules
- Main static HTML structure created
- Application-wide DOM event handlers are installed
-
htmlReady
event fires - Extensions are loaded
- Initial project is loaded (file tree is populated, working set is restored then active document is opened)
- After the project is fully opened,
appReady
fires
If a new version of Brackets is available, the update notification dialog may appear at any time after step 6.
Many operations in Brackets return their results asynchronously -- for example, because they involve file I/O. These APIs return a jQuery Promise object that you can use to listen for success/failure and retrieve the result.
For working with a sequence of asynchronous operations (in parallel or in serial), the Async utils module may be helpful.
When dealing with files the user is editing, there are three important classes to understand:
-
Editor
represents the view (it wraps a CodeMirror widget) -- either a full-size editor or an inline editor. An Editor can have focus. Use an Editor object to get/set the cursor position, selection, or scroll position. Every Editor is attached to a Document (accessible viaeditor.document
). -
Document
represents the model (the text content of the file). Use the Document object to get or modify the text, or to listen for changes to the text. There may be multiple Editors attached to a single Document (for example, a full-size editor plus an inline editor). Every Document is associated with a file on disk (accessible viadocument.file
). -
File
represents a file on disk. It's almost as lightweight as a plain string path: you can get aFile
object synchronously viaFileSystem.getFileForPath()
, without having to actually read or locate the file on disk yet.
Document
is an object that represents an editable file on disk. Documents perform several important functions: they are the backing model for Editors; they provide APIs for reading and modifying the text content; and they emit events whenever the text is edited.
- If you're operating on an Editor, use
Editor.document
.
How to get an Editor:-
EditorManager.getFocusedEditor()
returns the editor that has focus; may be an inline editor. Will be null if focus is elsewhere, such as the search bar. -
EditorManager.getActiveEditor()
is similar, but if focus is somewhere other than an editor it returns whichever Editor last had focus (i.e. the Editor that focus will return to when the search bar / dialog / etc. closes). -
EditorManager.getCurrentFullEditor()
returns the currently visible full-size editor (even focus currently lies in an inline editor within it, or if focus lies in other UI such as the search bar). Synonymous with usingDocumentManager.getCurrentDocument()
.
-
- To get a Document for any file, use
DocumentManager.getDocumentForPath()
. This returns asynchronously because it may need to read the file's content from disk. - If you're sure a file is already open, you can use the synchronous
DocumentManager.getOpenDocumentForPath()
instead -- but it will return null if you're wrong.
Documents are globally tracked, and thus must be ref counted under certain circumstances.
Aside: Why is ref counting needed? We want all parts of Brackets to have a consistent view of the (potentially unsaved) contents of a file being edited. Yet we also want to throw out that state when all parts of Brackets stop caring about a file, instead of keeping it in memory forever. If JavaScript supported weak references they would be an ideal solution to these twin constraints, but alas -- we're stuck with ref counting instead.
If you're only holding onto a Document for the duration of a function call then you're in the clear and don't have to worry about this. The text editing commands in EditorCommandHandlers are good examples of this.
If, on the other hand, you're...
- Storing a Document object in a property that you'll access later
- Getting a Document, doing something asynchronous, and then accessing the same Document pointer again when it's done
- Attaching event listeners to a Document
...then you'll need to be more careful. In all these cases, you must call addRef()
on the Document after you fetch it, and then later call releaseRef()
when you're done using it (e.g. when you null out / overwrite the property pointing to it, or detach your listeners).
The Document and its full text content will be kept in memory until you call releaseRef()
, so it's better to avoid holding onto Documents if possible. In many cases, you can just store the file's path and re-fetch the Document each time you need it.
If you're attaching event listeners to Document:
- The Document "change" event fires on virtually every keystroke. For performance reasons, consider deferring your processing until later (for example, using the DocumentManager "documentSaved" event instead).
- If you're listening for Document changes, you probably also care about Document deletion -- so be sure to listen for the Document "deleted" event as well.
To modify a Document's text content, use Document.replaceRange()
. If you're going to call it multiple times as the result of a single user action, wrap all your calls in Document.batchOperation()
to ensure they're all batched into a single Undo/Redo entry.
In many cases, if you're implementing a new kind of edit, you'll want to handle multiple selections. See Changes to Editor selection APIs and Performing edits on multiple selections for more information.
Every menu item and shortcut invokes a Command
-- basically just an object containing a handler function, a label string, and some state such as enabled/disabled. CommandManager
has a mapping from string "command ID" to Command object. Menus
has a mapping from menu item to command ID, and KeyBindingManager
has a mapping from key binding to command ID. All command IDs in core Brackets are listed as constants in Commands
.
To add a menu item or keyboard shortcut, see [How to write extensions](How to write extensions#wiki-uihooks).
See [How to write extensions](How to write extensions#wiki-featurehooks).
Normally, you'll want to use Document (see above) to read the contents of a file that the user might edit. If you modify a file via Document, it will automatically be recorded as an unsaved change for the user to track.
There are some cases where you may want to simply load a configuration file without treating it as a user document. For this, Brackets provides a basic [FileSystem API](File System) for direct access to local files. Here's how to simply read a file:
// On Windows, paths look like "C:/foo/bar.txt"
// On Mac, paths look like "/foo/bar.txt"
var file = FileSystem.getFileForPath(localPath);
var promise = FileUtils.readAsText(file); // completes asynchronously
promise.done(function (text) {
console.log("The contents of the file are:\n" + text);
})
.fail(function (errorCode) {
console.log("Error #" + errorCode); // one of the FileSystemError constants
});