See: Description
Interface | Description |
---|---|
EditorCaretListener |
Listener for changes in editor caret.
|
Class | Description |
---|---|
CaretInfo |
Immutable info about a single caret used in caret API - see
EditorCaret . |
CaretMoveContext |
Context for carets moving within
CaretMoveHandler . |
EditorCaret |
Extension to standard Swing caret used by all NetBeans editors.
|
EditorCaretEvent |
Notification event about changes in editor caret.
|
MoveCaretsOrigin |
Describes the operation which initiated the caret navigation.
|
The Editor Caret API opens up the editor to give information about its carets and to manipulate them.
The whole API is organized around the
EditorCaret
class, an implementation of the javax.swing.text.Caret
managing
all the carets in a single document. Information about carets maintained by
EditorCaret
is stored in the immutable class
CaretInfo
.
Once a caret gets mutated its corresponding caret info becomes obsolete
and new caret info instance gets created lazily.
EditorCaretListener
can be registered to an EditorCaret
to be informed about caret
addition/removal or movement with an
EditorCaretEvent
.
The EditorCaret
mutates its carets in a single transaction.
CaretMoveContext
allows clients implementing
CaretMoveHandler
to manipulate carets. The following code shows how all carets are moved to the
end of the word they are currently on.
editorCaret.moveCarets((CaretMoveContext context) -> {
for (CaretInfo ci : context.getOriginalCarets()) {
Position pos = target.getDocument().createPosition(Utilities.getWordEnd(target, ci.getDot()));
context.setDot(ci, pos);
}
});
Document
changesThe basics of the locking and events model of Swing documents is described in the javadoc of the javax.swing.text.AbstractDocument class. Netbeans documents use the same patterns and so does the Caret API, because of its tight relation to documents. The fundamentals of the Swing documents locking model are that any changes to a document are done under the document's write lock, the document's listeners are notified synchronously on the mutating thread and have full read access to the document, but can't modify it.
EditorCaret
to query or
mutate its carets, should be called with document's read-lock acquired which
will guarantee stability of CaretInfo#getDot()
and
CaretInfo#getMark()
and prevent caret merging as a possible
effect of document modifications.
EditorCaret
needs to notify its listeners that some
of its carets have changed, all the events have to be fired under the
layer's document's read lock. Obviously, the listeners are not allowed to
modify the document from the event notification methods.
The Caret API does not use any special threads and any processing it does is always done on the caller's thread. This means that the above described constraints hardly cause any limitation for practical use.
The Caret API is generally thread-safe meaning that it can be used simultaneously from multiple threads if not stated otherwise. This doesn't change in any way the rule of acquiring a read lock before calling the API. Swing documents generally allow access for multiple readers that can run concurrently.
It may be desirable (especially for NavigationListeners, but possibly for EditorCaretListeners too) to determine what was the reason leading to caret movement. The caller of Caret API which intends to position the caret may provide an optional MoveCaretsOrigin instance to Caret API methods to indicate type of action leading to the movement. This description is used to select NavigationFilters which receive and can modify the movement, and is available to all invoked NavigationFilters or EditorCaretListeners contacted by the Caret API operation.
Only one action type is currently defined by the API - DIRECT_NAVIGATION. This type identifies operations actions, whose only task is to reposition the caret (i.e. left/right, page-up, document-begin) from actions, which position caret as a part of larger task (i.e. search, goto type, ...).
The Caret API implements and extends the concept of swing
NavigationFilters. The Filter
gets notified on CaretInfo
movement; so if more Carets are present,
a NavigationFilter
will see all their moves. Navigation Filters are
called with a special instance of
FilterBypass extending
NavigationFilterBypass. The Filter can downcast the FilterBypass to access extended information
about the current caret's movement:
public void setDot(FilterBypass fb, int dot, Position.Bias bias) {
if (fb instanceof NavigationFilterBypass) {
NavigationFilterBypass nfb = (NavigationFilterBypass)fb;
// get the Origin object created by the caret-moving operation, can query the details
MoveCaretsOrigin origin = nfb.getOrigin();
// get the individual caret in multi-caret scenario
CaretInfo info = nfb.getCaretInfo();
// get the whole EditorCaret
EditorCaret eCaret = nfb.getEditorCaret();
}
}
Navigation filters can be also selectively registered for only certain type of actions described by MoveCaretsOrigin instance.
EditorCaret eCaret = .... ; // obtain EditorCaret
eCaret.setNavigationFilter(
new NavigationFilter() {
// navigation filter implementation, not important for the example
},
new MoveCaretsOrigin(MoveCaretsOrigin.DIRECT_NAVIGATION)
);
Such filters are only called if the caller of Caret API provides a suitable MoveCaretsOrigin description of the move operatoion.
// Action perform method
editorCaret.moveCarets(new CaretMoveHandler() {
@Override
public void moveCarets(CaretMoveContext context) {
...
}
}, new MoveCaretsOrigin(
// The action is a raw movement command
MoveCaretsOrigin.DIRECT_NAVIGATION,
// The approximate direction of the movement; can be 0.
SwingConstants.NORTH)
);
Abstract boilerplate for NavigationFilters
is created,
CascadingNavigationFilter
which allows to chain individual filters. Implementors are encouraged to use it, or otherwise
pass the control to previously registered NavigationFilter
in case the movement
event is not handled by the custom implementation.
As a technical limitation, EditorCaret
has to implement Caret
to be able to work with Swings Text API. The Caret
interface is not
aware of multiple carets and a call to setDot(int)
will only retain
a single caret. For multiple carets a call to moveDot(int)
will move the last
caret only (but it retains other existing carets).+
editorTextComponent.getCaret()
will now always return EditorCaret
instance instead of the original caret implementation org.netbeans.editor.BaseCaret
.
Since clients were expected to only rely on javax.swing.text.Caret
returned type
(except internal code in editor modules) they should not be affected by the change
since EditorCaret
implements Caret
interface.
With official Caret API the clients can start to cast
editorTextComponent.getCaret()
to EditorCaret
type
and use its extended functionality.
Use cases are shown in javadoc documentation of particular methods.