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.
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
;
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
);
77 public void release() {
82 protected void changedUpdateImpl(DocumentEvent e
) {
83 if (myIsReleased
) return;
84 int startBefore
= getStartOffset();
85 int endBefore
= getEndOffset();
86 super.changedUpdateImpl(e
);
89 myLastSelectionStart
= myEditor
.getCaretModel().getOffset();
91 mySelectionMarker
= null;
92 fireSelectionChanged(startBefore
, endBefore
, myLastSelectionStart
, myLastSelectionStart
);
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
) {
112 if (mySelectionMarker
!= null && mySelectionMarker
.isValid()) {
113 mySelectionMarker
.documentChanged(event
);
118 public int getPriority() {
122 public SelectionModelImpl(EditorImpl 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
) {
134 ApplicationManager
.getApplication().assertIsDispatchThread();
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()) {
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
) {
179 /* Normalize selection */
180 if (startOffset
> endOffset
) {
181 int tmp
= startOffset
;
182 startOffset
= endOffset
;
186 if (!GraphicsEnvironment
.isHeadless()) {
187 final Toolkit toolkit
= myEditor
.getComponent().getToolkit();
188 Clipboard clip
= toolkit
.getSystemSelection();
190 clip
.setContents(new StringSelection(getSelectedText()), EmptyClipboardOwner
.INSTANCE
);
194 int oldSelectionStart
;
197 if (hasSelection()) {
198 oldSelectionStart
= mySelectionMarker
.getStartOffset();
199 oldSelectionEnd
= mySelectionMarker
.getEndOffset();
200 if (oldSelectionStart
== startOffset
&& oldSelectionEnd
== endOffset
) return;
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
) {
224 listener
.selectionChanged(event
);
226 catch (Exception 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
) {
240 int oldStartLine
= 0;
243 if (hasBlockSelection()) {
244 oldStartLine
= myBlockStart
.line
;
245 oldEndLine
= myBlockEnd
.line
;
246 if (oldStartLine
> oldEndLine
) {
247 int t
= oldStartLine
;
248 oldStartLine
= oldEndLine
;
253 int newStartLine
= blockStart
.line
;
254 int newEndLine
= blockEnd
.line
;
256 if (newStartLine
> newEndLine
) {
257 int t
= newStartLine
;
258 newStartLine
= newEndLine
;
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());
288 public boolean hasBlockSelection() {
289 return myBlockStart
!= null;
292 public LogicalPosition
getBlockStart() {
296 public LogicalPosition
getBlockEnd() {
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
];
308 if (start
== end
&& doc
.getOffsetGuard(start
) != null || start
!= end
&& doc
.getRangeGuard(start
, end
) != null) {
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
];
325 RangeMarker guard
= doc
.getOffsetGuard(start
);
326 if (guard
!= null) return guard
;
329 RangeMarker guard
= doc
.getRangeGuard(start
, end
);
330 if (guard
!= null) return guard
;
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
));
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
));
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());
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
;
440 public void selectLineAtCaret() {
441 validateContext(true);
442 int lineNumber
= myEditor
.getCaretModel().getLogicalPosition().line
;
443 Document document
= myEditor
.getDocument();
444 if (lineNumber
>= document
.getLineCount()) {
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);
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;
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;
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
);
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;