12 KiB
title |
---|
2. Editor Coordinate Systems - Positions and Offsets |
The previous tutorial Working with Text discussed extending the AnAction.java class, and using an AnActionEvent object. The event object provides access to Project, Document, and Editor objects.
Every caret has a set of properties describing its position in one of several coordinate systems. This tutorial describes how to access information about the caret(s) in the editor.
2.1. Pre-requirements
In this tutorial the editor_basics code sample is used to explore caret positions.
In particular, the Caret Position action added by editor_basics
to the editor context menu is used to retrieve information about the current caret position.
The source code for the Java class behind the menu action is EditorAreaIllustration.java.
The focus of discussion will be the EditorAreaIllustration.actionPerformed()
method.
2.2. Accessing Caret Positions from the CaretModel Object
The properties of a caret can be accessed by obtaining an instance of the CaretModel object for a caret.
As in the Working with Text tutorial, the AnActionEvent
is used to get the Editor
object.
The Editor
object provides access to the CaretModel
object, as shown below:
public class EditorAreaIllustration extends AnAction {
@Override
public void actionPerformed(AnActionEvent anActionEvent) {
final Editor editor = anActionEvent.getRequiredData(CommonDataKeys.EDITOR);
CaretModel caretModel = editor.getCaretModel();
}
@Override
public void update(AnActionEvent e) { /* ... */ }
}
2.3. Caret Position
When a Document is opened the editor assigns an internal, zero-based coordinate system to lines and columns in the Document. The first line in a Document and the first character in each line are assigned the zero position. Note that the editor coordinate system is different from what is shown in the editor UI, which is one-based rather than zero-based.
2.3.1. Caret Logical Position
The caret Logical Position is a zero-based, (line and column) position of the caret in the Editor Tool Window. Line values are based on the corresponding lines in the underlying Document being edited. Logical Position information is obtained from the LogicalPosition object for that caret.
The Logical Position line number of a caret ignores the effects of settings that change the presentation of a Document within the Editor Tool Window. Examples of these settings are Code (Line) Folding and Soft Line Wrap. This means regardless of whether one or more lines in an Editor Tool Window are folded or soft-wrapped, the caret Logical Position line number will not change.
The image below shows the simplest case of reporting Logical Position using the caret position functionality of the editor_basics
plugin.
No Soft Wrap or Code Folding is applied.
Each line has a comment showing the Logical Position line number.
The caret - a blue block - is placed on the letter "p" in "public".
The caret is reported to be at Logical Position (5,0) - which is Logical (Position) line 5, character 0 - the first character in the line.
Caret Visual Position, caret leaning,, and caret offset are discussed in later sections.
If Logical Position line numbers 1-3 are folded into line 0, the caret is still reported as Logical Position (5,0). This means that caret Logical Position is not changed by Code Folding:
However, note that applying Code Folding does change the reported Visual Position of the caret even if the Logical Position stays constant.
More about Visual Position is discussed below, but it's clear combinations of Code Folding and Soft Wrap can mean that one Logical Position of a caret could map to multiple Visual Positions.
It is for this reason the Editor interface provides a number of methods to work with a caret Logical and Visual Position, such as the method Editor.logicalToVisualPosition()
.
The LogicalPosition
object for a caret is obtained from the caret's CaretModel
object, as shown in the code snippet below.
public class EditorAreaIllustration extends AnAction {
@Override
public void actionPerformed(AnActionEvent anActionEvent) {
final Editor editor = anActionEvent.getRequiredData(CommonDataKeys.EDITOR);
CaretModel caretModel = editor.getCaretModel();
LogicalPosition logicalPosition = caretModel.getLogicalPosition();
}
@Override
public void update(AnActionEvent e) { /* ... */ }
}
2.3.2. Caret Visual Position
A caret's Visual Position differs from Logical Position in that it takes into account editor presentation settings such as Code Folding and Soft Line Wrap.
In doing so, VisualPosition counts - zero-based - the lines of a Document that are displayed in an editor Tool Window.
Consequently, Visual Position lines are not uniquely mapped to corresponding lines in the underlying Document being edited.
For example, when Soft Line Wrap is applied to a line displayed in an Editor Tool Window it affects the Visual Position. In the image below, Soft Line Wrap has been applied to Logical line three. With the caret placed at the same location as in previous tests, it is evident the Logical Position has not changed. However, the Visual Position line number has increased by 1! The comments on each line illustrate how the Soft Wrap portion of Editor Line three is considered Visual Position line four, as though it was a separate line.
The Editor interface provides a number of methods to work with a caret Logical and Visual Position, such as the method Editor.visualToLogicalPosition()
.
The Visual Position object for a caret is obtained from the caret's CaretModel
object, as shown in the code snippet below.
public class EditorAreaIllustration extends AnAction {
@Override
public void actionPerformed(AnActionEvent anActionEvent) {
final Editor editor = anActionEvent.getRequiredData(CommonDataKeys.EDITOR);
CaretModel caretModel = editor.getCaretModel();
LogicalPosition logicalPosition = caretModel.getLogicalPosition();
VisualPosition visualPosition = caretModel.getVisualPosition();
}
@Override
public void update(AnActionEvent e) { /* ... */ }
}
2.3.3. Caret Column Position
The Column Position is a count of characters from the beginning of a Logical (Position) line to the current caret position in that line. Characters are counted using a zero-based numbering system, so the first character of a line is numbered zero. Note that Column Position is different from what is shown in the editor UI, which uses a one-based numbering scheme.
Column Position includes:
- The first character in a Logical line.
- Whitespace, such as tabs. Tabs can occupy multiple columns, up to the tab size set for the editor.
- The character selected by the caret.
More specifically, the Logical Position of a caret represents the boundary between two characters. As defined in the LogicalPosition class, if a caret position is associated with a succeeding character it is said to Lean Forward.
In the example below, placing a (red) line caret on the first visible character in Logical line three produces a complete lack of lean forward?! Only with caret color #FF0000 ?! The same color as for 'Unknown Symbol' in my Preferences | Editor | Color Scheme | General
In the example below, placing a (blue) block caret on the first visible character in Logical line three produces a column position of 0 for both Visual and Logical Positions. In both Visual and Logical Positions the character leans forward, meaning it is associated with the succeeding character in the Logical line.
2.3.4. Caret Offset
The Offset of a caret is a character count from the beginning of a Document to the caret position. Caret offsets are always calculated in terms of Logical Position. The caret Offset includes:
- The first (0th) character in a document.
- Whitespace characters, including end-of-line and tabs.
- Any characters after end-of-line if the IDE settings permit them. (Preferences | Editor | General | Virtual Space)
- The character selected by the caret.
The example below demonstrates the Offset of a caret placed at the first character of Logical line one. Note the Offset is 22, which is 1 greater than the number of visible characters on line one, and the first character on line two. This is because the Offset includes the EOL character for the first line.
The Offset for a given caret position is accessible through CaretModel
as well:
public class EditorAreaIllustration extends AnAction {
@Override
public void actionPerformed(AnActionEvent anActionEvent) {
final Editor editor = anActionEvent.getRequiredData(CommonDataKeys.EDITOR);
CaretModel caretModel = editor.getCaretModel();
LogicalPosition logicalPosition = caretModel.getLogicalPosition();
VisualPosition visualPosition = caretModel.getVisualPosition();
int offset = caretModel.getOffset();
}
@Override
public void update(AnActionEvent e) { /* ... */ }
}
2.4. Displaying position values
To display the actual values of logical and visual positions an
Messages.showInfoMessage()
call shows them in form of notification after the action is performed.
public class EditorAreaIllustration extends AnAction {
@Override
public void actionPerformed(AnActionEvent anActionEvent) {
final Editor editor = anActionEvent.getRequiredData(CommonDataKeys.EDITOR);
CaretModel caretModel = editor.getCaretModel();
LogicalPosition logicalPosition = caretModel.getLogicalPosition();
VisualPosition visualPosition = caretModel.getVisualPosition();
int offset = caretModel.getOffset();
Messages.showInfoMessage(logicalPosition.toString() + "\n" +
visualPosition.toString() + "\n" +
"Offset: " + offset, "Caret Parameters Inside The Editor");
}
@Override
public void update(AnActionEvent e) { /* ... */ }
}