summaryrefslogtreecommitdiff
path: root/platform/editor-ui-api/src/com/intellij/openapi/editor/CaretModel.java
blob: df247d5be4019dd44c7df2b2f5df32a7aa4908a2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
/*
 * Copyright 2000-2014 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.intellij.openapi.editor;

import com.intellij.openapi.editor.event.CaretListener;
import com.intellij.openapi.editor.markup.TextAttributes;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.List;

/**
 * Provides services for moving the caret and retrieving information about caret position.
 *
 * May support several carets existing simultaneously in a document. {@link #supportsMultipleCarets()} method can be used to find out
 * whether particular instance of CaretModel does it. If it does, query and update methods for caret position operate on a certain 'primary'
 * caret. There exists a way to perform the same operation(s) on each caret - see
 * {@link #runForEachCaret(com.intellij.openapi.editor.CaretAction)} method. Within its context, query and update methods operate on the
 * current caret in that iteration. This behaviour can change in future though, so using caret and selection query and update methods in
 * actions that need to operate on multiple carets is discouraged - methods on {@link com.intellij.openapi.editor.Caret} instances obtained
 * via {@link #getAllCarets()} or {@link #runForEachCaret(CaretAction)} should be used instead.
 * <p>
 * How 'primary' caret is determined by the model is not defined (currently it's the most recently added caret, but that can change).
 * <p>
 * At all times at least one caret will exist in a document.
 * <p>
 * Update methods, {@link #runBatchCaretOperation(Runnable)} and {@link #runForEachCaret(com.intellij.openapi.editor.CaretAction)} methods
 * should only be run from EDT. Query methods can be run from any thread, when called not from EDT, those methods are 'not aware' of
 * 'runForEachCaret' scope - they will always return information about primary caret.
 *
 * @see Editor#getCaretModel()
 */
public interface CaretModel {
  /**
   * Moves the caret by the specified number of lines and/or columns.
   *
   * @param columnShift    the number of columns to move the caret by.
   * @param lineShift      the number of lines to move the caret by.
   * @param withSelection  if true, the caret move should extend the range or block selection in the document.
   * @param blockSelection if true and <code>withSelection</code> is true, the caret move should extend
   *                       the block selection in the document. This parameter is ignored when multiple carets are supported by the model.
   * @param scrollToCaret  if true, the document should be scrolled so that the caret is visible after the move.
   */
  void moveCaretRelatively(int columnShift,
                           int lineShift,
                           boolean withSelection,
                           boolean blockSelection,
                           boolean scrollToCaret);

  /**
   * Moves the caret to the specified logical position.
   * If corresponding position is in the folded region currently, the region will be expanded.
   *
   * @param pos the position to move to.
   */
  void moveToLogicalPosition(@NotNull LogicalPosition pos);

  /**
   * Moves the caret to the specified visual position.
   *
   * @param pos the position to move to.
   */
  void moveToVisualPosition(@NotNull VisualPosition pos);

  /**
   * Short hand for calling {@link #moveToOffset(int, boolean)} with <code>'false'</code> as a second argument.
   *
   * @param offset      the offset to move to
   */
  void moveToOffset(int offset);

  /**
   * Moves the caret to the specified offset in the document.
   * If corresponding position is in the folded region currently, the region will be expanded.
   *
   * @param offset                  the offset to move to.
   * @param locateBeforeSoftWrap    there is a possible case that there is a soft wrap at the given offset, hence, the same offset
   *                                corresponds to two different visual positions - just before soft wrap and just after soft wrap.
   *                                We may want to clearly indicate where to put the caret then. Given parameter allows to do that.
   *                                <b>Note:</b> it's ignored if there is no soft wrap at the given offset
   */
  void moveToOffset(int offset, boolean locateBeforeSoftWrap);

  /**
   * Caret position may be updated on document change (e.g. consider that user updates from VCS that causes addition of text
   * before caret. Caret offset, visual and logical positions should be updated then). So, there is a possible case
   * that caret model in in the process of caret position update now.
   * <p/>
   * Current method allows to check that.
   *
   * @return    <code>true</code> if caret position is up-to-date for now; <code>false</code> otherwise
   */
  boolean isUpToDate();

  /**
   * Returns the logical position of the caret.
   *
   * @return the caret position.
   */
  @NotNull
  LogicalPosition getLogicalPosition();

  /**
   * Returns the visual position of the caret.
   *
   * @return the caret position.
   */
  @NotNull
  VisualPosition getVisualPosition();

  /**
   * Returns the offset of the caret in the document.
   *
   * @return the caret offset.
   */
  int getOffset();

  /**
   * Adds a listener for receiving notifications about caret movement and caret addition/removal
   *
   * @param listener the listener instance.
   */
  void addCaretListener(@NotNull CaretListener listener);

  /**
   * Removes a listener for receiving notifications about caret movement and caret addition/removal
   *
   * @param listener the listener instance.
   */
  void removeCaretListener(@NotNull CaretListener listener);

  /**
   * @return    document offset for the start of the logical line where caret is located
   */
  int getVisualLineStart();

  /**
   * @return    document offset that points to the first symbol shown at the next visual line after the one with caret on it
   */
  int getVisualLineEnd();

  /**
   * Returns visual representation of caret (e.g. background color).
   *
   * @return Caret attributes.
   */
  TextAttributes getTextAttributes();

  /**
   * Tells whether multiple coexisting carets are supported by this CaretModel instance.
   */
  boolean supportsMultipleCarets();

  /**
   * Returns current caret - the one, query and update methods in the model operate at the moment. In the current implementation this is
   * either an iteration-current caret within the context of {@link #runForEachCaret(CaretAction)} method, or the 'primary' caret without that
   * context. Users {@link #runForEachCaret(CaretAction)} method should use caret parameter passed to
   * {@link com.intellij.openapi.editor.CaretAction#perform(Caret)} method instead of this method, as the definition of current caret (as
   * well as caret instance operated on by model methods) can potentially change.
   */
  @NotNull
  Caret getCurrentCaret();

  /**
   * Returns the 'primary' caret.
   */
  @NotNull
  Caret getPrimaryCaret();

  /**
   * Returns number of carets currently existing in the document
   */
  int getCaretCount();

  /**
   * Returns all carets currently existing in the document, ordered by their position in the document.
   */
  @NotNull
  List<Caret> getAllCarets();

  /**
   * Returns a caret at the given position in the document, or <code>null</code>, if there's no caret there.
   */
  @Nullable
  Caret getCaretAt(@NotNull VisualPosition pos);

  /**
   * Adds a new caret at the given position, and returns corresponding Caret instance. Locations outside of possible values for the given
   * document are trimmed automatically.
   * Does nothing if multiple carets are not supporeted, a caret already exists at specified location or selection of existing caret
   * includes the specified location, <code>null</code> is returned in this case.
   */
  @Nullable
  Caret addCaret(@NotNull VisualPosition pos);

  /**
   * Removes a given caret if it's recognized by the model and is not the only existing caret in the document, returning <code>true</code>.
   * <code>false</code> is returned if any of the above condition doesn't hold, and the removal cannot happen.
   */
  boolean removeCaret(@NotNull Caret caret);

  /**
   * Removes all carets except the 'primary' one from the document.
   */
  void removeSecondaryCarets();

  /**
   * Sets the number of carets, their positions and selection ranges according to the provided data. Null values for caret position or
   * selection boundaries will mean that corresponding caret's position and/or selection won't be changed.
   * <p>
   * If multiple carets are not supported, the behaviour is unspecified.
   *
   * @see #supportsMultipleCarets()
   * @see #getCaretsAndSelections()
   */
  void setCaretsAndSelections(@NotNull List<CaretState> caretStates);

  /**
   * Returns the current positions of all carets and their selections. The order of entries in the returned list does not necessarily
   * correspond to the order of {@link #getAllCarets()} method results. Passing the result of this method to
   * {@link #setCaretsAndSelections(java.util.List)} will restore the state of carets, including the internal caret order, in particular,
   * the caret, that was primary when this method was called, will be the primary one after corresponding
   * {@link #setCaretsAndSelections(java.util.List)} invocation.
   * <p>
   * If multiple carets are not supported, the behaviour is unspecified.
   *
   * @see #supportsMultipleCarets()
   * @see #setCaretsAndSelections(java.util.List)
   */
  @NotNull
  List<CaretState> getCaretsAndSelections();

  /**
   * Same as {@link #runForEachCaret(CaretAction, boolean)} with <code>reverseOrder</code> set to <code>false</code>
   */
  void runForEachCaret(@NotNull CaretAction action);

  /**
   * Executes the given task for each existing caret. Set of carets to iterate over is
   * determined in the beginning and is not affected by the potential carets addition or removal by the task being executed.
   * At the end, merging of carets and selections is performed, so that no two carets will occur at the same logical position and
   * no two selection will overlap after this method is finished.
   * <p>
   * Carets are iterated in position order (top-to-bottom) if <code>reverseOrder</code> is <code>false</code>, and in reverse order
   * if it's <code>true</code>.
   */
  void runForEachCaret(@NotNull CaretAction action, boolean reverseOrder);

  /**
   * Executes the given task, performing caret merging afterwards. Caret merging will not happen until the operation is finished.
   */
  void runBatchCaretOperation(@NotNull Runnable runnable);
}