IDEA-51739
[fedora-idea.git] / platform / platform-impl / src / com / intellij / openapi / editor / impl / SelectionModelImpl.java
blob37e05a4fa308bdbbd760d12000a23ec3ea010d27
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * Created by IntelliJ IDEA.
19 * User: max
20 * Date: Apr 19, 2002
21 * Time: 1:51:41 PM
22 * To change template for new class use
23 * Code Style | Class Templates options (Tools | IDE Options).
25 package com.intellij.openapi.editor.impl;
27 import com.intellij.ide.DataManager;
28 import com.intellij.openapi.actionSystem.IdeActions;
29 import com.intellij.openapi.actionSystem.PlatformDataKeys;
30 import com.intellij.openapi.application.ApplicationManager;
31 import com.intellij.openapi.diagnostic.Logger;
32 import com.intellij.openapi.editor.*;
33 import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
34 import com.intellij.openapi.editor.actionSystem.EditorActionManager;
35 import com.intellij.openapi.editor.actions.EditorActionUtil;
36 import com.intellij.openapi.editor.colors.EditorColors;
37 import com.intellij.openapi.editor.colors.EditorColorsScheme;
38 import com.intellij.openapi.editor.event.DocumentEvent;
39 import com.intellij.openapi.editor.event.SelectionEvent;
40 import com.intellij.openapi.editor.event.SelectionListener;
41 import com.intellij.openapi.editor.ex.DocumentEx;
42 import com.intellij.openapi.editor.ex.PrioritizedDocumentListener;
43 import com.intellij.openapi.editor.markup.TextAttributes;
44 import com.intellij.openapi.ide.CopyPasteManager;
45 import com.intellij.openapi.project.Project;
46 import com.intellij.openapi.util.text.StringUtil;
47 import com.intellij.util.ArrayUtil;
48 import com.intellij.util.containers.ContainerUtil;
49 import com.intellij.util.ui.EmptyClipboardOwner;
50 import org.jetbrains.annotations.NotNull;
52 import java.awt.*;
53 import java.awt.datatransfer.Clipboard;
54 import java.awt.datatransfer.StringSelection;
55 import java.util.concurrent.CopyOnWriteArrayList;
57 public class SelectionModelImpl implements SelectionModel, PrioritizedDocumentListener {
58 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.SelectionModelImpl");
60 private final CopyOnWriteArrayList<SelectionListener> mySelectionListeners = ContainerUtil.createEmptyCOWList();
61 private MyRangeMarker mySelectionMarker = null;
62 private final EditorImpl myEditor;
63 private int myLastSelectionStart;
64 private LogicalPosition myBlockStart;
65 private LogicalPosition myBlockEnd;
66 private TextAttributes myTextAttributes;
67 private DocumentEvent myIsInUpdate;
69 private class MyRangeMarker extends RangeMarkerImpl {
70 private boolean myIsReleased;
72 private MyRangeMarker(DocumentEx document, int start, int end) {
73 super(document, start, end);
74 myIsReleased = false;
77 public void release() {
78 myIsReleased = true;
81 @Override
82 protected void changedUpdateImpl(DocumentEvent e) {
83 if (myIsReleased) return;
84 int startBefore = getStartOffset();
85 int endBefore = getEndOffset();
86 super.changedUpdateImpl(e);
88 if (!isValid()) {
89 myLastSelectionStart = myEditor.getCaretModel().getOffset();
90 release();
91 mySelectionMarker = null;
92 fireSelectionChanged(startBefore, endBefore, myLastSelectionStart, myLastSelectionStart);
93 return;
96 if (startBefore != getStartOffset() || endBefore != getStartOffset()) {
97 fireSelectionChanged(startBefore, endBefore, getStartOffset(), getEndOffset());
101 protected void registerInDocument() {
105 public void beforeDocumentChange(DocumentEvent event) {
106 myIsInUpdate = event;
109 public void documentChanged(DocumentEvent event) {
110 if (myIsInUpdate == event) {
111 myIsInUpdate = null;
112 if (mySelectionMarker != null && mySelectionMarker.isValid()) {
113 mySelectionMarker.documentChanged(event);
118 public int getPriority() {
119 return 4;
122 public SelectionModelImpl(EditorImpl editor) {
123 myEditor = editor;
126 public int getSelectionStart() {
127 validateContext(false);
128 if (!hasSelection()) return myEditor.getCaretModel().getOffset();
129 return mySelectionMarker.getStartOffset();
132 private void validateContext(boolean isWrite) {
133 if (isWrite) {
134 ApplicationManager.getApplication().assertIsDispatchThread();
136 else {
137 ApplicationManager.getApplication().assertReadAccessAllowed();
140 if (myIsInUpdate != null) {
141 documentChanged(myIsInUpdate);
145 public int getSelectionEnd() {
146 validateContext(false);
147 if (!hasSelection()) return myEditor.getCaretModel().getOffset();
148 return mySelectionMarker.getEndOffset();
151 public boolean hasSelection() {
152 validateContext(false);
153 if (mySelectionMarker != null && !mySelectionMarker.isValid()) {
154 removeSelection();
157 return mySelectionMarker != null;
160 public void setSelection(int startOffset, int endOffset) {
161 validateContext(true);
163 removeBlockSelection();
164 Document doc = myEditor.getDocument();
166 if (startOffset < 0 || startOffset > doc.getTextLength()) {
167 LOG.error("Wrong startOffset: " + startOffset);
169 if (endOffset < 0 || endOffset > doc.getTextLength()) {
170 LOG.error("Wrong endOffset: " + endOffset);
173 myLastSelectionStart = startOffset;
174 if (startOffset == endOffset) {
175 removeSelection();
176 return;
179 /* Normalize selection */
180 if (startOffset > endOffset) {
181 int tmp = startOffset;
182 startOffset = endOffset;
183 endOffset = tmp;
186 if (!GraphicsEnvironment.isHeadless()) {
187 final Toolkit toolkit = myEditor.getComponent().getToolkit();
188 Clipboard clip = toolkit.getSystemSelection();
189 if (clip != null) {
190 clip.setContents(new StringSelection(getSelectedText()), EmptyClipboardOwner.INSTANCE);
194 int oldSelectionStart;
195 int oldSelectionEnd;
197 if (hasSelection()) {
198 oldSelectionStart = mySelectionMarker.getStartOffset();
199 oldSelectionEnd = mySelectionMarker.getEndOffset();
200 if (oldSelectionStart == startOffset && oldSelectionEnd == endOffset) return;
202 else {
203 oldSelectionStart = oldSelectionEnd = myEditor.getCaretModel().getOffset();
206 if (mySelectionMarker != null) {
207 mySelectionMarker.release();
210 mySelectionMarker = new MyRangeMarker((DocumentEx)doc, startOffset, endOffset);
212 fireSelectionChanged(oldSelectionStart, oldSelectionEnd, startOffset, endOffset);
215 private void fireSelectionChanged(int oldSelectionStart, int oldSelectionEnd, int startOffset, int endOffset) {
216 repaintBySelectionChange(oldSelectionStart, startOffset, oldSelectionEnd, endOffset);
218 SelectionEvent event = new SelectionEvent(myEditor,
219 oldSelectionStart, oldSelectionEnd,
220 startOffset, endOffset);
222 for (SelectionListener listener : mySelectionListeners) {
223 try {
224 listener.selectionChanged(event);
226 catch (Exception e) {
227 LOG.error(e);
232 private void repaintBySelectionChange(int oldSelectionStart, int startOffset, int oldSelectionEnd, int endOffset) {
233 myEditor.repaint(Math.min(oldSelectionStart, startOffset), Math.max(oldSelectionStart, startOffset));
234 myEditor.repaint(Math.min(oldSelectionEnd, endOffset), Math.max(oldSelectionEnd, endOffset));
237 public void setBlockSelection(LogicalPosition blockStart, LogicalPosition blockEnd) {
238 removeSelection();
240 int oldStartLine = 0;
241 int oldEndLine = 0;
243 if (hasBlockSelection()) {
244 oldStartLine = myBlockStart.line;
245 oldEndLine = myBlockEnd.line;
246 if (oldStartLine > oldEndLine) {
247 int t = oldStartLine;
248 oldStartLine = oldEndLine;
249 oldEndLine = t;
253 int newStartLine = blockStart.line;
254 int newEndLine = blockEnd.line;
256 if (newStartLine > newEndLine) {
257 int t = newStartLine;
258 newStartLine = newEndLine;
259 newEndLine = t;
262 myEditor.repaintLines(Math.min(oldStartLine, newStartLine), Math.max(newEndLine, oldEndLine));
263 myBlockStart = blockStart;
264 myBlockEnd = blockEnd;
267 public void removeSelection() {
268 validateContext(true);
269 removeBlockSelection();
270 myLastSelectionStart = myEditor.getCaretModel().getOffset();
271 if (mySelectionMarker != null) {
272 int startOffset = mySelectionMarker.getStartOffset();
273 int endOffset = mySelectionMarker.getEndOffset();
274 mySelectionMarker.release();
275 mySelectionMarker = null;
276 fireSelectionChanged(startOffset, endOffset, myLastSelectionStart, myLastSelectionStart);
280 public void removeBlockSelection() {
281 if (hasBlockSelection()) {
282 myEditor.repaint(0, myEditor.getDocument().getTextLength());
283 myBlockStart = null;
284 myBlockEnd = null;
288 public boolean hasBlockSelection() {
289 return myBlockStart != null;
292 public LogicalPosition getBlockStart() {
293 return myBlockStart;
296 public LogicalPosition getBlockEnd() {
297 return myBlockEnd;
300 public boolean isBlockSelectionGuarded() {
301 if (!hasBlockSelection()) return false;
302 int[] starts = getBlockSelectionStarts();
303 int[] ends = getBlockSelectionEnds();
304 Document doc = myEditor.getDocument();
305 for (int i = 0; i < starts.length; i++) {
306 int start = starts[i];
307 int end = ends[i];
308 if (start == end && doc.getOffsetGuard(start) != null || start != end && doc.getRangeGuard(start, end) != null) {
309 return true;
312 return false;
315 public RangeMarker getBlockSelectionGuard() {
316 if (!hasBlockSelection()) return null;
318 int[] starts = getBlockSelectionStarts();
319 int[] ends = getBlockSelectionEnds();
320 Document doc = myEditor.getDocument();
321 for (int i = 0; i < starts.length; i++) {
322 int start = starts[i];
323 int end = ends[i];
324 if (start == end) {
325 RangeMarker guard = doc.getOffsetGuard(start);
326 if (guard != null) return guard;
328 if (start != end) {
329 RangeMarker guard = doc.getRangeGuard(start, end);
330 if (guard != null) return guard;
334 return null;
337 @NotNull
338 public int[] getBlockSelectionStarts() {
339 if (hasSelection()) {
340 return new int[]{getSelectionStart()};
342 if (!hasBlockSelection()) {
343 return ArrayUtil.EMPTY_INT_ARRAY;
346 int lineCount = Math.abs(myBlockEnd.line - myBlockStart.line) + 1;
347 int startLine = Math.min(myBlockStart.line, myBlockEnd.line);
349 int startColumn = Math.min(myBlockStart.column, myBlockEnd.column);
351 int[] res = new int[lineCount];
352 for (int i = startLine; i < startLine + lineCount; i++) {
353 res[i - startLine] = myEditor.logicalPositionToOffset(new LogicalPosition(i, startColumn));
356 return res;
359 @NotNull
360 public int[] getBlockSelectionEnds() {
361 if (hasSelection()) {
362 return new int[]{getSelectionEnd()};
365 if (!hasBlockSelection()) {
366 return ArrayUtil.EMPTY_INT_ARRAY;
369 int lineCount = Math.abs(myBlockEnd.line - myBlockStart.line) + 1;
370 int startLine = Math.min(myBlockStart.line, myBlockEnd.line);
372 int startColumn = Math.min(myBlockStart.column, myBlockEnd.column);
373 int columnCount = Math.abs(myBlockEnd.column - myBlockStart.column);
375 int[] res = new int[lineCount];
376 for (int i = startLine; i < startLine + lineCount; i++) {
377 res[i - startLine] = myEditor.logicalPositionToOffset(new LogicalPosition(i, startColumn + columnCount));
380 return res;
383 public void addSelectionListener(SelectionListener listener) {
384 mySelectionListeners.add(listener);
387 public void removeSelectionListener(SelectionListener listener) {
388 boolean success = mySelectionListeners.remove(listener);
389 LOG.assertTrue(success);
392 public String getSelectedText() {
393 validateContext(false);
394 if (!hasSelection() && !hasBlockSelection()) return null;
396 CharSequence text = myEditor.getDocument().getCharsSequence();
397 if (hasBlockSelection()) {
398 int[] starts = getBlockSelectionStarts();
399 int[] ends = getBlockSelectionEnds();
400 int width = Math.abs(myBlockEnd.column - myBlockStart.column);
401 final StringBuffer buf = new StringBuffer();
402 for (int i = 0; i < starts.length; i++) {
403 if (i > 0) buf.append('\n');
404 final int len = ends[i] - starts[i];
405 appendCharSequence(buf, text, starts[i], len);
406 for (int j = len; j < width; j++) buf.append(' ');
408 return buf.toString();
411 int selectionStart = getSelectionStart();
412 int selectionEnd = getSelectionEnd();
413 return text.subSequence(selectionStart, selectionEnd).toString();
416 private static void appendCharSequence(@NotNull StringBuffer buf, @NotNull CharSequence s, int srcOffset, int len) {
417 if (srcOffset < 0 || len < 0 || srcOffset > s.length() - len) {
418 throw new IndexOutOfBoundsException("srcOffset " + srcOffset + ", len " + len + ", s.length() " + s.length());
420 if (len == 0) {
421 return;
423 final int limit = srcOffset + len;
424 for (int i = srcOffset; i < limit; i++){
425 buf.append(s.charAt(i));
430 public int getLeadSelectionOffset() {
431 validateContext(false);
432 int caretOffset = myEditor.getCaretModel().getOffset();
433 if (!hasSelection()) return caretOffset;
434 int startOffset = mySelectionMarker.getStartOffset();
435 int endOffset = mySelectionMarker.getEndOffset();
436 if (caretOffset == endOffset) return startOffset;
437 return endOffset;
440 public void selectLineAtCaret() {
441 validateContext(true);
442 int lineNumber = myEditor.getCaretModel().getLogicalPosition().line;
443 Document document = myEditor.getDocument();
444 if (lineNumber >= document.getLineCount()) {
445 return;
448 VisualPosition caret = myEditor.getCaretModel().getVisualPosition();
449 LogicalPosition lineStart = myEditor.visualToLogicalPosition(new VisualPosition(caret.line, 0));
450 LogicalPosition nextLineStart = myEditor.visualToLogicalPosition(new VisualPosition(caret.line + 1, 0));
452 int start = myEditor.logicalPositionToOffset(lineStart);
453 int end = myEditor.logicalPositionToOffset(nextLineStart);
455 //myEditor.getCaretModel().moveToOffset(start);
456 myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
457 myEditor.getSelectionModel().removeSelection();
458 myEditor.getSelectionModel().setSelection(start, end);
461 public void selectWordAtCaret(boolean honorCamelWordsSettings) {
462 validateContext(true);
463 removeSelection();
464 final EditorSettings settings = myEditor.getSettings();
465 boolean camelTemp = settings.isCamelWords();
467 final boolean needOverrideSetting = camelTemp && !honorCamelWordsSettings;
468 if (needOverrideSetting) {
469 settings.setCamelWords(false);
472 EditorActionHandler handler = EditorActionManager.getInstance().getActionHandler(
473 IdeActions.ACTION_EDITOR_SELECT_WORD_AT_CARET);
474 handler.execute(myEditor, myEditor.getDataContext());
476 if (needOverrideSetting) {
477 settings.setCamelWords(camelTemp);
481 int getWordAtCaretStart() {
482 Document document = myEditor.getDocument();
483 int offset = myEditor.getCaretModel().getOffset();
484 if (offset == 0) return 0;
485 int lineNumber = myEditor.getCaretModel().getLogicalPosition().line;
486 CharSequence text = document.getCharsSequence();
487 int newOffset = offset - 1;
488 int minOffset = lineNumber > 0 ? document.getLineEndOffset(lineNumber - 1) : 0;
489 boolean camel = myEditor.getSettings().isCamelWords();
490 for (; newOffset > minOffset; newOffset--) {
491 if (EditorActionUtil.isWordStart(text, newOffset, camel)) break;
494 return newOffset;
497 int getWordAtCaretEnd() {
498 Document document = myEditor.getDocument();
499 int offset = myEditor.getCaretModel().getOffset();
501 CharSequence text = document.getCharsSequence();
502 if (offset >= document.getTextLength() - 1 || document.getLineCount() == 0) return offset;
504 int newOffset = offset + 1;
506 int lineNumber = myEditor.getCaretModel().getLogicalPosition().line;
507 int maxOffset = document.getLineEndOffset(lineNumber);
508 if (newOffset > maxOffset) {
509 if (lineNumber + 1 >= document.getLineCount()) return offset;
510 maxOffset = document.getLineEndOffset(lineNumber + 1);
512 boolean camel = myEditor.getSettings().isCamelWords();
513 for (; newOffset < maxOffset; newOffset++) {
514 if (EditorActionUtil.isWordEnd(text, newOffset, camel)) break;
517 return newOffset;
520 public void copySelectionToClipboard() {
521 validateContext(true);
522 String s = myEditor.getSelectionModel().getSelectedText();
523 if (s == null) return;
525 s = StringUtil.convertLineSeparators(s);
526 StringSelection contents = new StringSelection(s);
528 Project project = PlatformDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(myEditor.getContentComponent()));
529 if (project == null) {
530 Clipboard clipboard = myEditor.getComponent().getToolkit().getSystemClipboard();
531 clipboard.setContents(contents, EmptyClipboardOwner.INSTANCE);
533 else {
534 CopyPasteManager.getInstance().setContents(contents);
538 public TextAttributes getTextAttributes() {
539 if (myTextAttributes == null) {
540 TextAttributes textAttributes = new TextAttributes();
541 EditorColorsScheme scheme = myEditor.getColorsScheme();
542 textAttributes.setForegroundColor(scheme.getColor(EditorColors.SELECTION_FOREGROUND_COLOR));
543 textAttributes.setBackgroundColor(scheme.getColor(EditorColors.SELECTION_BACKGROUND_COLOR));
544 myTextAttributes = textAttributes;
547 return myTextAttributes;
550 public void reinitSettings() {
551 myTextAttributes = null;