-
Notifications
You must be signed in to change notification settings - Fork 7.6k
Brackets CodeMirror v4 Migration Guide
In Sprint 38, Brackets will migrate to CodeMirror v4, which primarily adds support for multiple selections. This document briefly describes what extension authors should be aware of when updating extensions to work with Sprint 38.
In general, most extensions should continue to work as-is, but extensions that have not been upgraded to work with the new multiple selection APIs will only operate on the "primary" selection (the last selection made by the user) if multiple selections are active. Also, other changes might affect the functioning of your extension.
Here are the main changes to be aware of:
-
CodeMirror is no longer global. In Brackets, we used to provide CodeMirror as a global. CodeMirror now supports
require()
, so we now include CodeMirror as a module instead. This means you should addvar CodeMirror = brackets.getModule("thirdparty/CodeMirror2/lib/CodeMirror");
at the top of any module that accesses the CodeMirror object. Sprint 38 will still provide the global CodeMirror (which is the same as the module-loaded CodeMirror), but access to it is deprecated and it will be removed in a future release. (Note that this doesn't affect accesses toeditor._codeMirror
, which should work as before.) -
General CodeMirror v4 changes. In general, aside from multiple selections, CodeMirror's APIs haven't changed much. However, you should read the CodeMirror v4 migration guide to see if any of the changes might affect you. In general, if you're just calling Brackets APIs on Editor or Document, rather than calling CodeMirror functions directly (e.g. on
editor._codeMirror
), these shouldn't affect you. -
Edit merging. If you're using the
origin
property ofDocument.replaceRange()
in order to merge edits that are nearby in time, and you're also manually setting the selection after each edit, you'll need to group each edit together with the new selection inside aDocument.batchOperation()
in order for the edits to merge together. Otherwise the selection change will force the edits not to merge. - Multiple selections. If your extension looks at or performs edits on the user's current selection, you will want to consider making your edits work with multiple selections by using the new selection APIs on Editor. For more information on this, see Changes to Editor selection APIs and Performing edits on multiple selections below.
- New keyboard shortcuts. A few new keyboard shortcuts have been added to CodeMirror and Brackets in order to provide functionality related to multiple selections. If you use any of these shortcuts in your extension, you should consider changing them to something that doesn't conflict.
Shortcut | Function |
---|---|
Alt-Click-Drag | Create a vertical or rectangular selection |
Ctrl/Cmd-Click / Ctrl/Cmd-Click-Drag | Add a cursor or range to the selection |
Shift-Alt-Up/Down | Add Line to Selection - adds a cursor on the previous/next line |
Ctrl/Cmd-Alt-L | Split Selection into Lines - takes a contiguous selection and converts it to a multiple selection, one range for each line |
Ctrl/Cmd-B | Add Next Match to Selection - adds the next match for the current range or word to the selection |
Ctrl/Cmd-Shift-B | Skip and Add Next Match - removes the current range from the selection and adds the next match |
Alt-F3 / Cmd-Ctrl-G | Find All and Select - adds all matches for the current range or word to the selection |
Ctrl/Cmd-U | Undo Selection - undoes the last selection change or edit |
Ctrl/Cmd-Shift-U | Redo Selection - redoes the last selection change or edit |
Esc | Switch to Single Selection - changes the selection to the primary selection |
If you make any changes that require Sprint 38 or CodeMirror v4 (e.g. using the new selection APIs in Editor or using require to load CodeMirror), make sure to set Sprint 38 as the minimum Brackets version for your updated extension by setting "engines": { "brackets" : ">=0.38.0" }
to your extension's package.json
. Users of older Brackets versions will still be able to install older versions of your extension.
The existing single-selection APIs in Editor (getSelection()
, getCursorPos()
, setSelection()
, and so on) are still available, but if a multiple selection is active, they only return information about the primary selection - that is, the last selection the user added to the current multiple selection. To access the current multiple selection, use getSelections()
, which returns an array of selection objects, each of which contains these fields:
-
start
: a{line, ch}
object for the beginning of the selected range -
end
: a{line, ch}
object for the end of the selected range - will be equal tostart
if it's a cursor -
reversed
: true if the head of the range isstart
, false if the head isend
. (The "head" is the end of the range that would move if the user extends the range via the keyboard or mouse. Most edit operations probably won't care too much about this.) -
primary
: whether this is the primary selection; exactly one selection will have this set totrue
.
The selections are guaranteed to be sorted in document order and to be non-overlapping.
(Note that this format is different from the native CodeMirror format, which uses anchor
and head
. In most cases, edit ops probably care more about the actual ranges involved and want them to be in start/end order, and don't really care which end of a given range is the anchor and which is the head, so we thought it better to optimize for that case. You can still figure out which end is the head by looking at the reversed
flag.)
setSelections()
takes exactly the same format that getSelections()
returns. However, you can omit the primary
flag; if you leave it off, the last selection in the array will be the primary selection.
Another enhancement to selections is the ability to undo/redo selections using Ctrl/Cmd-U and Ctrl/Cmd-Shift-U. This is handy even without multiple selections; it provides an easy way to move backwards through your selection history. Like edits, you can specify that nearby selections of a similar type should be merged by specifying an origin
. As mentioned above, however, selection and edit origins are separate; if you want to merge together an edit and an associated selection, you should wrap them in an operation.
For more detailed information on the changes to the selection APIs on Editor, see the table in Research: Multiple cursors and selections.
(Once the multiple selection functionality is released in Brackets, this will move to the Brackets Development How-Tos page.)
In most cases, your edits will be based on the user's current selection. As of Sprint 38, Brackets supports multiple selections, so in most cases you will want to switch to use the new getSelections()
and setSelections()
APIs, and determine how your edits will operate on multiple selections.
In the simplest case, where your edits operate on the range of each selection and they're performed in such a way that the selection is properly fixed up automatically by CodeMirror, you can just iterate over all selections and edit each one. However, you must be careful to re-get the list of selections after each individual edit, because later selections may shift as a result of your edit. So, your code should look like this:
editor.document.batchOperation(function () {
var sels = editor.getSelections(), i, sel;
for (i = 0; i < sels.length; i++) {
sel = sels[i];
// ... do various edits with editor.document.replaceRange() ...
sels = editor.getSelections(); // refresh all selections that might have changed
}
});
If you additionally need to adjust the selections that should result from the edit, you can build up a list of ranges to select at the end. You should try to preserve the primary
and reversed
attributes from each original selection range.
editor.document.batchOperation(function () {
var sels = editor.getSelections(),
newSels = [],
i, sel;
for (i = 0; i < sels.length; i++) {
sel = sels[i];
// ... do various edits with editor.document.replaceRange() ...
// ... set start/end of sel to the selection range that should be set after this edit
// (reusing sel makes it so you can easily preserve primary/reversed flags) ...
newSels.push(sel); // have to put this in a new array since we're re-getting selections
sels = editor.getSelections();
}
editor.setSelections(newSels);
});
Here's a complete example of a function that adds numbers before each selected range and selects the added numbers (but not the spaces inserted afterwards):
function addNumbers() {
var editor = EditorManager.getActiveEditor();
if (!editor) {
return;
}
editor.document.batchOperation(function () {
var sels = editor.getSelections(),
newSels = [],
i, sel, prefix;
for (i = 0; i < sels.length; i++) {
sel = sels[i];
prefix = String(i + 1) + ".";
editor.document.replaceRange(prefix + " ", sel.start);
sel.end = {line: sel.start.line, ch: sel.start.ch + prefix.length};
newSels.push(sel);
sels = editor.getSelections();
}
editor.setSelections(newSels);
});
}
Note that it works properly even if multiple selections are on the same line because we re-get the selections each time.
In more complicated cases, you might need to do some more complex processing that isn't strictly one-edit-per-selection. A typical case of this is in line-oriented edits, such as Move Line Up/Down, where there might be multiple selections within a given line, but you only want to process each line once, while still tracking the positions of the various selections that intersect that line. In cases like these, you can use two helper functions:
-
Editor.convertToLineSelections()
takes a multiple selection and converts it to a set of whole-line selections, each of which is paired with the original selection(s) that overlap the range of the whole-line selection. -
Document.doMultipleEdits()
takes an array of edit descriptors, each of which describes a particularreplaceRange()
-style edit (or set of edits) to do at once, and a set of selections that should be tracked through those edits. The advantage of using this over just directly usingreplaceRange()
and fixing up selections yourself is that it allows you to act as if each edit is separate from all the others (so you don't have to worry about fixing up selections for any edit other than the current one), and it handles tracking multiple selections that are related to each edit automatically.
For examples of how to use these APIs, you can look at the line-oriented edits in EditorCommandHandlers, such as moveLine()
or openLine()
.
One other technical note: if you depend on Editor.getFirstVisibleLine()
or Editor.getLastVisibleLine()
(because your edit has special requirements when performed at the boundaries of an inline editor), then you'll need to either use Document.doMultipleEdits()
, or keep track of your edits and figure out how they will affect the number of visible lines. This is because the visible line information isn't updated till the end of a batch operation. In the future, we're planning to get rid of these entirely.
For a more detailed spec on the new multiple selection functionality, see Research: Multiple cursors and selections.