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.
17 package com
.intellij
.execution
.impl
;
19 import com
.intellij
.codeInsight
.navigation
.IncrementalSearchHandler
;
20 import com
.intellij
.execution
.ExecutionBundle
;
21 import com
.intellij
.execution
.filters
.*;
22 import com
.intellij
.execution
.process
.ProcessHandler
;
23 import com
.intellij
.execution
.ui
.ConsoleView
;
24 import com
.intellij
.execution
.ui
.ConsoleViewContentType
;
25 import com
.intellij
.execution
.ui
.ObservableConsoleView
;
26 import com
.intellij
.ide
.CommonActionsManager
;
27 import com
.intellij
.ide
.DataAccessor
;
28 import com
.intellij
.ide
.DataAccessors
;
29 import com
.intellij
.ide
.OccurenceNavigator
;
30 import com
.intellij
.openapi
.Disposable
;
31 import com
.intellij
.openapi
.actionSystem
.*;
32 import com
.intellij
.openapi
.application
.ApplicationManager
;
33 import com
.intellij
.openapi
.application
.ModalityState
;
34 import com
.intellij
.openapi
.command
.CommandProcessor
;
35 import com
.intellij
.openapi
.diagnostic
.Logger
;
36 import com
.intellij
.openapi
.diff
.actions
.DiffActions
;
37 import com
.intellij
.openapi
.editor
.*;
38 import com
.intellij
.openapi
.editor
.actionSystem
.*;
39 import com
.intellij
.openapi
.editor
.colors
.CodeInsightColors
;
40 import com
.intellij
.openapi
.editor
.colors
.EditorColors
;
41 import com
.intellij
.openapi
.editor
.colors
.EditorColorsManager
;
42 import com
.intellij
.openapi
.editor
.colors
.EditorColorsScheme
;
43 import com
.intellij
.openapi
.editor
.event
.*;
44 import com
.intellij
.openapi
.editor
.ex
.EditorEx
;
45 import com
.intellij
.openapi
.editor
.ex
.MarkupModelEx
;
46 import com
.intellij
.openapi
.editor
.highlighter
.EditorHighlighter
;
47 import com
.intellij
.openapi
.editor
.highlighter
.HighlighterClient
;
48 import com
.intellij
.openapi
.editor
.highlighter
.HighlighterIterator
;
49 import com
.intellij
.openapi
.editor
.impl
.EditorFactoryImpl
;
50 import com
.intellij
.openapi
.editor
.markup
.HighlighterLayer
;
51 import com
.intellij
.openapi
.editor
.markup
.HighlighterTargetArea
;
52 import com
.intellij
.openapi
.editor
.markup
.RangeHighlighter
;
53 import com
.intellij
.openapi
.editor
.markup
.TextAttributes
;
54 import com
.intellij
.openapi
.extensions
.Extensions
;
55 import com
.intellij
.openapi
.fileEditor
.OpenFileDescriptor
;
56 import com
.intellij
.openapi
.fileTypes
.FileType
;
57 import com
.intellij
.openapi
.ide
.CopyPasteManager
;
58 import com
.intellij
.openapi
.keymap
.Keymap
;
59 import com
.intellij
.openapi
.keymap
.KeymapManager
;
60 import com
.intellij
.openapi
.project
.DumbAware
;
61 import com
.intellij
.openapi
.project
.Project
;
62 import com
.intellij
.openapi
.util
.Computable
;
63 import com
.intellij
.openapi
.util
.Condition
;
64 import com
.intellij
.openapi
.util
.Disposer
;
65 import com
.intellij
.openapi
.util
.Key
;
66 import com
.intellij
.openapi
.util
.text
.LineTokenizer
;
67 import com
.intellij
.openapi
.util
.text
.StringUtil
;
68 import com
.intellij
.pom
.Navigatable
;
69 import com
.intellij
.psi
.PsiDocumentManager
;
70 import com
.intellij
.psi
.PsiFile
;
71 import com
.intellij
.psi
.PsiFileFactory
;
72 import com
.intellij
.psi
.tree
.IElementType
;
73 import com
.intellij
.util
.Alarm
;
74 import com
.intellij
.util
.EditorPopupHandler
;
75 import com
.intellij
.util
.LocalTimeCounter
;
76 import com
.intellij
.util
.containers
.HashMap
;
77 import org
.jetbrains
.annotations
.NotNull
;
78 import org
.jetbrains
.annotations
.TestOnly
;
82 import java
.awt
.datatransfer
.DataFlavor
;
83 import java
.awt
.datatransfer
.Transferable
;
84 import java
.awt
.event
.KeyEvent
;
85 import java
.awt
.event
.KeyListener
;
86 import java
.awt
.event
.MouseEvent
;
87 import java
.awt
.event
.MouseMotionAdapter
;
88 import java
.io
.IOException
;
90 import java
.util
.List
;
91 import java
.util
.concurrent
.CopyOnWriteArraySet
;
93 public final class ConsoleViewImpl
extends JPanel
implements ConsoleView
, ObservableConsoleView
, DataProvider
, OccurenceNavigator
{
94 private static final Logger LOG
= Logger
.getInstance("#com.intellij.execution.impl.ConsoleViewImpl");
96 private static final int FLUSH_DELAY
= 200; //TODO : make it an option
98 private static final Key
<ConsoleViewImpl
> CONSOLE_VIEW_IN_EDITOR_VIEW
= Key
.create("CONSOLE_VIEW_IN_EDITOR_VIEW");
100 final EditorActionManager actionManager
= EditorActionManager
.getInstance();
101 final TypedAction typedAction
= actionManager
.getTypedAction();
102 typedAction
.setupHandler(new MyTypedHandler(typedAction
.getHandler()));
105 private final DisposedPsiManagerCheck myPsiDisposedCheck
;
106 private ConsoleState myState
= ConsoleState
.NOT_STARTED
;
107 private final int CYCLIC_BUFFER_SIZE
= getCycleBufferSize();
108 private final boolean isViewer
;
109 private Computable
<ModalityState
> myStateForUpdate
;
111 private static int getCycleBufferSize() {
112 final String cycleBufferSizeProperty
= System
.getProperty("idea.cycle.buffer.size");
113 if (cycleBufferSizeProperty
== null) return 1024 * 1024;
115 return Integer
.parseInt(cycleBufferSizeProperty
) * 1024;
117 catch (NumberFormatException e
) {
122 private final boolean USE_CYCLIC_BUFFER
= useCycleBuffer();
124 private static boolean useCycleBuffer() {
125 final String useCycleBufferProperty
= System
.getProperty("idea.cycle.buffer.size");
126 return useCycleBufferProperty
== null || !"disabled".equalsIgnoreCase(useCycleBufferProperty
);
129 private static final int HYPERLINK_LAYER
= HighlighterLayer
.SELECTION
- 123;
130 private final Alarm mySpareTimeAlarm
= new Alarm();
132 private final CopyOnWriteArraySet
<ChangeListener
> myListeners
= new CopyOnWriteArraySet
<ChangeListener
>();
133 private final Set
<ConsoleViewContentType
> myDeferredTypes
= new HashSet
<ConsoleViewContentType
>();
134 private final ArrayList
<AnAction
> customActions
= new ArrayList
<AnAction
>();
137 public Editor
getEditor() {
141 public void scrollToEnd() {
142 myEditor
.getCaretModel().moveToOffset(myEditor
.getDocument().getTextLength());
145 private static class TokenInfo
{
146 private final ConsoleViewContentType contentType
;
147 private int startOffset
;
148 private int endOffset
;
149 private final TextAttributes attributes
;
151 private TokenInfo(final ConsoleViewContentType contentType
, final int startOffset
, final int endOffset
) {
152 this.contentType
= contentType
;
153 this.startOffset
= startOffset
;
154 this.endOffset
= endOffset
;
155 attributes
= contentType
.getAttributes();
159 private final Project myProject
;
161 private boolean myOutputPaused
;
163 private Editor myEditor
;
165 private final Object LOCK
= new Object();
167 private int myContentSize
;
168 private StringBuffer myDeferredOutput
= new StringBuffer();
169 private StringBuffer myDeferredUserInput
= new StringBuffer();
171 private ArrayList
<TokenInfo
> myTokens
= new ArrayList
<TokenInfo
>();
172 private final Hyperlinks myHyperlinks
= new Hyperlinks();
174 private String myHelpId
;
176 private final Alarm myFlushAlarm
= new Alarm();
178 private final Runnable myFlushDeferredRunnable
= new Runnable() {
184 private final CompositeFilter myPredefinedMessageFilter
;
185 private final CompositeFilter myCustomFilter
;
187 private ArrayList
<String
> myHistory
= new ArrayList
<String
>();
188 private int myHistorySize
= 20;
190 private ArrayList
<ConsoleInputListener
> myConsoleInputListeners
= new ArrayList
<ConsoleInputListener
>();
192 public void addConsoleUserInputLestener(ConsoleInputListener consoleInputListener
) {
193 myConsoleInputListeners
.add(consoleInputListener
);
197 * By default history works for one session. If
198 * you want to import previous session, set it up here.
199 * @param history where you can save history
201 public void importHistory(Collection
<String
> history
) {
202 this.myHistory
.clear();
203 this.myHistory
.addAll(history
);
204 while (this.myHistory
.size() > myHistorySize
) {
205 this.myHistory
.remove(0);
209 public List
<String
> getHistory() {
210 return Collections
.unmodifiableList(myHistory
);
213 public void setHistorySize(int historySize
) {
214 this.myHistorySize
= historySize
;
217 public int getHistorySize() {
218 return myHistorySize
;
221 private FileType myFileType
;
224 * Use it for custom highlighting for user text.
225 * This will be highlighted as appropriate file to this file type.
226 * @param fileType according to which use highlighting
228 public void setFileType(FileType fileType
) {
229 myFileType
= fileType
;
232 public ConsoleViewImpl(final Project project
, boolean viewer
) {
233 this(project
, viewer
, null);
236 public ConsoleViewImpl(final Project project
, boolean viewer
, FileType fileType
) {
237 super(new BorderLayout());
239 myPsiDisposedCheck
= new DisposedPsiManagerCheck(project
);
241 myFileType
= fileType
;
243 myCustomFilter
= new CompositeFilter(project
);
244 myPredefinedMessageFilter
= new CompositeFilter(project
);
245 for (ConsoleFilterProvider filterProvider
: Extensions
.getExtensions(ConsoleFilterProvider
.FILTER_PROVIDERS
)) {
246 for (Filter filter
: filterProvider
.getDefaultFilters(project
)) {
247 myPredefinedMessageFilter
.addFilter(filter
);
251 Disposer
.register(project
, this);
254 public void attachToProcess(final ProcessHandler processHandler
){
255 myState
= myState
.attachTo(this, processHandler
);
258 public void clear() {
259 assertIsDispatchThread();
261 final Document document
;
264 if (USE_CYCLIC_BUFFER
) {
265 myDeferredOutput
= new StringBuffer(Math
.min(myDeferredOutput
.length(), CYCLIC_BUFFER_SIZE
));
268 myDeferredOutput
= new StringBuffer();
270 myDeferredTypes
.clear();
271 myDeferredUserInput
= new StringBuffer();
272 myHyperlinks
.clear();
274 if (myEditor
== null) return;
275 myEditor
.getMarkupModel().removeAllHighlighters();
276 document
= myEditor
.getDocument();
278 CommandProcessor
.getInstance().executeCommand(myProject
, new Runnable() {
280 document
.deleteString(0, document
.getTextLength());
282 }, null, DocCommandGroupId
.noneGroupId(document
));
285 public void scrollTo(final int offset
) {
286 assertIsDispatchThread();
288 if (myEditor
== null) return;
289 int moveOffset
= offset
;
290 if (USE_CYCLIC_BUFFER
&& moveOffset
>= myEditor
.getDocument().getTextLength()) {
293 myEditor
.getCaretModel().moveToOffset(moveOffset
);
294 myEditor
.getScrollingModel().scrollToCaret(ScrollType
.MAKE_VISIBLE
);
297 private static void assertIsDispatchThread() {
298 ApplicationManager
.getApplication().assertIsDispatchThread();
301 public void setOutputPaused(final boolean value
) {
302 myOutputPaused
= value
;
304 requestFlushImmediately();
308 public boolean isOutputPaused() {
309 return myOutputPaused
;
312 public boolean hasDeferredOutput() {
314 return myDeferredOutput
.length() > 0;
318 public void performWhenNoDeferredOutput(final Runnable runnable
) {
319 //Q: implement in another way without timer?
320 if (!hasDeferredOutput()){
324 mySpareTimeAlarm
.addRequest(
327 performWhenNoDeferredOutput(runnable
);
335 public JComponent
getComponent() {
336 if (myEditor
== null){
337 myEditor
= createEditor();
338 requestFlushImmediately();
339 add(myEditor
.getComponent(), BorderLayout
.CENTER
);
341 myEditor
.getDocument().addDocumentListener(new DocumentAdapter() {
342 public void documentChanged(DocumentEvent e
) {
343 if (e
.getNewLength() == 0 && e
.getOffset() == 0) {
344 // string has beeen removed from the beginning, move tokens down
345 synchronized (LOCK
) {
346 int toRemoveLen
= e
.getOldLength();
347 int tIndex
= findTokenInfoIndexByOffset(toRemoveLen
);
348 ArrayList
<TokenInfo
> newTokens
= new ArrayList
<TokenInfo
>(myTokens
.subList(tIndex
, myTokens
.size()));
349 for (TokenInfo token
: newTokens
) {
350 token
.startOffset
-= toRemoveLen
;
351 token
.endOffset
-= toRemoveLen
;
353 if (!newTokens
.isEmpty()) {
354 newTokens
.get(0).startOffset
= 0;
356 myContentSize
-= Math
.min(myContentSize
, toRemoveLen
);
357 myTokens
= newTokens
;
366 public void setModalityStateForUpdate(Computable
<ModalityState
> stateComputable
) {
367 myStateForUpdate
= stateComputable
;
372 public void dispose(){
373 myState
= myState
.dispose();
374 if (myEditor
!= null){
375 myFlushAlarm
.cancelAllRequests();
376 mySpareTimeAlarm
.cancelAllRequests();
377 if (!myEditor
.isDisposed()) {
378 EditorFactory
.getInstance().releaseEditor(myEditor
);
380 synchronized (LOCK
) {
381 myDeferredOutput
= new StringBuffer();
387 public void print(String s
, final ConsoleViewContentType contentType
) {
389 myDeferredTypes
.add(contentType
);
391 s
= StringUtil
.convertLineSeparators(s
);
392 myContentSize
+= s
.length();
393 myDeferredOutput
.append(s
);
394 if (contentType
== ConsoleViewContentType
.USER_INPUT
){
395 myDeferredUserInput
.append(s
);
398 boolean needNew
= true;
399 if (!myTokens
.isEmpty()){
400 final TokenInfo lastToken
= myTokens
.get(myTokens
.size() - 1);
401 if (lastToken
.contentType
== contentType
){
402 lastToken
.endOffset
= myContentSize
; // optimization
407 myTokens
.add(new TokenInfo(contentType
, myContentSize
- s
.length(), myContentSize
));
410 if (s
.indexOf('\n') >= 0 || s
.indexOf('\r') >= 0) {
411 if (contentType
== ConsoleViewContentType
.USER_INPUT
) {
412 flushDeferredUserInput();
415 if (myFlushAlarm
.getActiveRequestCount() == 0 && myEditor
!= null) {
416 final boolean shouldFlushNow
= USE_CYCLIC_BUFFER
&& myDeferredOutput
.length() > CYCLIC_BUFFER_SIZE
;
417 myFlushAlarm
.addRequest(myFlushDeferredRunnable
, shouldFlushNow?
0 : FLUSH_DELAY
, getStateForUpdate());
422 private ModalityState
getStateForUpdate() {
423 return myStateForUpdate
!= null ? myStateForUpdate
.compute() : ModalityState
.stateForComponent(myEditor
.getComponent());
426 private void requestFlushImmediately() {
427 if (myEditor
!= null) {
428 myFlushAlarm
.addRequest(myFlushDeferredRunnable
, 0, getStateForUpdate());
432 public int getContentSize() { return myContentSize
; }
434 public boolean canPause() {
438 private void flushDeferredText() {
439 ApplicationManager
.getApplication().assertIsDispatchThread();
440 if (myProject
.isDisposed()) {
445 synchronized (LOCK
) {
446 if (myOutputPaused
) return;
447 if (myDeferredOutput
.length() == 0) return;
448 if (myEditor
== null) return;
450 text
= myDeferredOutput
.substring(0, myDeferredOutput
.length());
451 if (USE_CYCLIC_BUFFER
) {
452 myDeferredOutput
= new StringBuffer(Math
.min(myDeferredOutput
.length(), CYCLIC_BUFFER_SIZE
));
455 myDeferredOutput
.setLength(0);
458 final Document document
= myEditor
.getDocument();
459 final int oldLineCount
= document
.getLineCount();
460 final boolean isAtEndOfDocument
= myEditor
.getCaretModel().getOffset() == document
.getTextLength();
461 boolean cycleUsed
= USE_CYCLIC_BUFFER
&& document
.getTextLength() + text
.length() > CYCLIC_BUFFER_SIZE
;
462 CommandProcessor
.getInstance().executeCommand(myProject
, new Runnable() {
464 document
.insertString(document
.getTextLength(), text
);
465 synchronized (LOCK
) {
469 }, null, DocCommandGroupId
.noneGroupId(document
));
470 myPsiDisposedCheck
.performCheck();
471 final int newLineCount
= document
.getLineCount();
473 final int lineCount
= LineTokenizer
.calcLineCount(text
, true);
474 for (Iterator
<RangeHighlighter
> it
= myHyperlinks
.getRanges().keySet().iterator(); it
.hasNext();) {
475 if (!it
.next().isValid()) {
479 highlightHyperlinks(newLineCount
>= lineCount
+ 1 ? newLineCount
- lineCount
- 1 : 0, newLineCount
- 1);
481 else if (oldLineCount
< newLineCount
) {
482 highlightHyperlinks(oldLineCount
- 1, newLineCount
- 2);
485 if (isAtEndOfDocument
) {
486 myEditor
.getCaretModel().moveToOffset(myEditor
.getDocument().getTextLength());
487 myEditor
.getSelectionModel().removeSelection();
488 myEditor
.getScrollingModel().scrollToCaret(ScrollType
.RELATIVE
);
492 private void flushDeferredUserInput() {
493 if (myState
.isRunning()){
494 final String text
= myDeferredUserInput
.substring(0, myDeferredUserInput
.length());
495 final int index
= Math
.max(text
.lastIndexOf('\n'), text
.lastIndexOf('\r'));
496 if (index
< 0) return;
498 myState
.sendUserInput(text
.substring(0, index
+ 1));
500 catch(IOException e
){
503 myDeferredUserInput
.setLength(0);
504 myDeferredUserInput
.append(text
.substring(index
+ 1));
508 public Object
getData(final String dataId
) {
509 if (DataConstants
.NAVIGATABLE
.equals(dataId
)){
510 if (myEditor
== null) {
513 final LogicalPosition pos
= myEditor
.getCaretModel().getLogicalPosition();
514 final HyperlinkInfo info
= getHyperlinkInfoByLineAndCol(pos
.line
, pos
.column
);
515 final OpenFileDescriptor openFileDescriptor
= info
instanceof FileHyperlinkInfo ?
((FileHyperlinkInfo
)info
).getDescriptor() : null;
516 if (openFileDescriptor
== null || !openFileDescriptor
.getFile().isValid()) {
519 return openFileDescriptor
;
522 if (DataConstants
.EDITOR
.equals(dataId
)) {
525 if (DataConstants
.HELP_ID
.equals(dataId
)) {
531 public void setHelpId(final String helpId
) {
535 public void addMessageFilter(final Filter filter
) {
536 myCustomFilter
.addFilter(filter
);
539 public void printHyperlink(final String hyperlinkText
, final HyperlinkInfo info
) {
540 if (myEditor
== null) return;
541 print(hyperlinkText
, ConsoleViewContentType
.NORMAL_OUTPUT
);
543 final int textLength
= myEditor
.getDocument().getTextLength();
544 addHyperlink(textLength
- hyperlinkText
.length(), textLength
, null, info
, getHyperlinkAttributes());
547 private static TextAttributes
getHyperlinkAttributes() {
548 return EditorColorsManager
.getInstance().getGlobalScheme().getAttributes(CodeInsightColors
.HYPERLINK_ATTRIBUTES
);
551 private static TextAttributes
getFollowedHyperlinkAttributes() {
552 return EditorColorsManager
.getInstance().getGlobalScheme().getAttributes(CodeInsightColors
.FOLLOWED_HYPERLINK_ATTRIBUTES
);
555 private Editor
createEditor() {
556 return ApplicationManager
.getApplication().runReadAction(new Computable
<Editor
>() {
557 public Editor
compute() {
558 return doCreateEditor();
563 private Editor
doCreateEditor() {
564 final EditorFactoryImpl editorFactory
= (EditorFactoryImpl
) EditorFactory
.getInstance();
565 final Document editorDocument
= editorFactory
.createDocument(true);
566 editorDocument
.addDocumentListener(new DocumentListener() {
567 public void beforeDocumentChange(DocumentEvent event
) {
570 public void documentChanged(DocumentEvent event
) {
571 if (myFileType
!= null) {
572 highlightUserTokens();
577 final int bufferSize
= USE_CYCLIC_BUFFER ? CYCLIC_BUFFER_SIZE
: 0;
578 editorDocument
.setCyclicBufferSize(bufferSize
);
580 final EditorEx editor
= (EditorEx
) editorFactory
.createViewer(editorDocument
,myProject
);
581 final EditorHighlighter highlighter
= new MyHighlighter();
582 editor
.setHighlighter(highlighter
);
583 editor
.putUserData(CONSOLE_VIEW_IN_EDITOR_VIEW
, this);
585 final EditorSettings editorSettings
= editor
.getSettings();
586 editorSettings
.setLineMarkerAreaShown(false);
587 editorSettings
.setLineNumbersShown(false);
588 editorSettings
.setFoldingOutlineShown(false);
589 editorSettings
.setAdditionalPageAtBottom(false);
590 editorSettings
.setAdditionalColumnsCount(0);
591 editorSettings
.setAdditionalLinesCount(0);
593 final EditorColorsScheme scheme
= editor
.getColorsScheme();
594 editor
.setBackgroundColor(scheme
.getColor(ConsoleViewContentType
.CONSOLE_BACKGROUND_KEY
));
595 scheme
.setColor(EditorColors
.CARET_ROW_COLOR
, null);
596 scheme
.setColor(EditorColors
.RIGHT_MARGIN_COLOR
, null);
598 editor
.addEditorMouseListener(new EditorPopupHandler(){
599 public void invokePopup(final EditorMouseEvent event
) {
600 final MouseEvent mouseEvent
= event
.getMouseEvent();
601 popupInvoked(mouseEvent
.getComponent(), mouseEvent
.getX(), mouseEvent
.getY());
605 editor
.addEditorMouseListener(
606 new EditorMouseAdapter(){
607 public void mouseReleased(final EditorMouseEvent e
){
608 final MouseEvent mouseEvent
= e
.getMouseEvent();
609 if (!mouseEvent
.isPopupTrigger()){
616 editor
.getContentComponent().addMouseMotionListener(
617 new MouseMotionAdapter(){
618 public void mouseMoved(final MouseEvent e
){
619 final HyperlinkInfo info
= getHyperlinkInfoByPoint(e
.getPoint());
621 editor
.getContentComponent().setCursor(Cursor
.getPredefinedCursor(Cursor
.HAND_CURSOR
));
624 editor
.getContentComponent().setCursor(Cursor
.getPredefinedCursor(Cursor
.TEXT_CURSOR
));
630 final ConsoleViewImpl consoleView
= this;
631 editor
.getContentComponent().addKeyListener(new KeyListener() {
632 private int historyPosition
= myHistory
.size();
634 public void keyTyped(KeyEvent e
) {
638 public void keyPressed(KeyEvent e
) {
641 public void keyReleased(KeyEvent e
) {
642 if (e
.isAltDown() && !e
.isControlDown() && !e
.isMetaDown() && !e
.isShiftDown()) {
643 if (e
.getKeyCode() == KeyEvent
.VK_UP
) {
645 if (historyPosition
< 0) historyPosition
= 0;
648 } else if (e
.getKeyCode() == KeyEvent
.VK_DOWN
) {
650 if (historyPosition
> myHistory
.size()) historyPosition
= myHistory
.size();
655 historyPosition
= myHistory
.size();
659 private void replaceString() {
662 if (myHistory
.size() == historyPosition
) str
= "";
663 else str
= myHistory
.get(historyPosition
);
664 synchronized (LOCK
) {
665 if (myTokens
.isEmpty()) return;
666 final TokenInfo info
= myTokens
.get(myTokens
.size() - 1);
667 if (info
.contentType
!= ConsoleViewContentType
.USER_INPUT
) {
668 consoleView
.insertUserText(str
, 0);
670 consoleView
.replaceUserText(str
, info
.startOffset
, info
.endOffset
);
676 setEditorUpActions(editor
);
681 private void highlightUserTokens() {
682 if (myTokens
.isEmpty()) return;
683 final TokenInfo token
= myTokens
.get(myTokens
.size() - 1);
684 if (token
.contentType
== ConsoleViewContentType
.USER_INPUT
) {
685 String text
= myEditor
.getDocument().getText().substring(token
.startOffset
, token
.endOffset
);
686 PsiFile file
= PsiFileFactory
.getInstance(myProject
).
687 createFileFromText("dummy", myFileType
, text
, LocalTimeCounter
.currentTime(), true);
688 Document document
= PsiDocumentManager
.getInstance(myProject
).getDocument(file
);
689 assert document
!= null;
690 Editor editor
= EditorFactory
.getInstance().createEditor(document
, myProject
, myFileType
, false);
692 RangeHighlighter
[] allHighlighters
= myEditor
.getMarkupModel().getAllHighlighters();
693 for (RangeHighlighter highlighter
: allHighlighters
) {
694 if (highlighter
.getStartOffset() >= token
.startOffset
) {
695 myEditor
.getMarkupModel().removeHighlighter(highlighter
);
698 HighlighterIterator iterator
= ((EditorEx
) editor
).getHighlighter().createIterator(0);
699 while (!iterator
.atEnd()) {
700 myEditor
.getMarkupModel().addRangeHighlighter(iterator
.getStart() + token
.startOffset
, iterator
.getEnd() + token
.startOffset
, HighlighterLayer
.SYNTAX
,
701 iterator
.getTextAttributes(),
702 HighlighterTargetArea
.EXACT_RANGE
);
707 EditorFactory
.getInstance().releaseEditor(editor
);
712 private static void setEditorUpActions(final Editor editor
) {
713 new EnterHandler().registerCustomShortcutSet(CommonShortcuts
.ENTER
, editor
.getContentComponent());
714 registerActionHandler(editor
, IdeActions
.ACTION_EDITOR_PASTE
, new PasteHandler());
715 registerActionHandler(editor
, IdeActions
.ACTION_EDITOR_BACKSPACE
, new BackSpaceHandler());
716 registerActionHandler(editor
, IdeActions
.ACTION_EDITOR_DELETE
, new DeleteHandler());
719 private static void registerActionHandler(final Editor editor
, final String actionId
, final AnAction action
) {
720 final Keymap keymap
=KeymapManager
.getInstance().getActiveKeymap();
721 final Shortcut
[] shortcuts
= keymap
.getShortcuts(actionId
);
722 action
.registerCustomShortcutSet(new CustomShortcutSet(shortcuts
), editor
.getContentComponent());
725 private void popupInvoked(final Component component
, final int x
, final int y
){
726 final DefaultActionGroup group
= new DefaultActionGroup();
727 group
.add(new ClearAllAction());
728 group
.add(new CopyAction());
729 group
.addSeparator();
730 final ActionManager actionManager
= ActionManager
.getInstance();
731 group
.add(actionManager
.getAction(DiffActions
.COMPARE_WITH_CLIPBOARD
));
732 final ActionPopupMenu menu
= actionManager
.createActionPopupMenu(ActionPlaces
.UNKNOWN
, group
);
733 menu
.getComponent().show(component
, x
, y
);
736 private void navigate(final EditorMouseEvent event
){
737 if (event
.getMouseEvent().isPopupTrigger()) return;
738 final Point p
= event
.getMouseEvent().getPoint();
739 final HyperlinkInfo info
= getHyperlinkInfoByPoint(p
);
741 info
.navigate(myProject
);
746 private static final Key
<TextAttributes
> OLD_HYPERLINK_TEXT_ATTRIBUTES
= Key
.create("OLD_HYPERLINK_TEXT_ATTRIBUTES");
747 private void linkFollowed(final HyperlinkInfo info
) {
748 MarkupModelEx markupModel
= (MarkupModelEx
)myEditor
.getMarkupModel();
749 for (Map
.Entry
<RangeHighlighter
,HyperlinkInfo
> entry
: myHyperlinks
.getRanges().entrySet()) {
750 RangeHighlighter range
= entry
.getKey();
751 TextAttributes oldAttr
= range
.getUserData(OLD_HYPERLINK_TEXT_ATTRIBUTES
);
752 if (oldAttr
!= null) {
753 markupModel
.setRangeHighlighterAttributes(range
, oldAttr
);
754 range
.putUserData(OLD_HYPERLINK_TEXT_ATTRIBUTES
, null);
756 if (entry
.getValue() == info
) {
757 TextAttributes oldAttributes
= range
.getTextAttributes();
758 range
.putUserData(OLD_HYPERLINK_TEXT_ATTRIBUTES
, oldAttributes
);
759 TextAttributes attributes
= getFollowedHyperlinkAttributes().clone();
760 assert oldAttributes
!= null;
761 attributes
.setFontType(oldAttributes
.getFontType());
762 attributes
.setEffectType(oldAttributes
.getEffectType());
763 attributes
.setEffectColor(oldAttributes
.getEffectColor());
764 attributes
.setForegroundColor(oldAttributes
.getForegroundColor());
765 markupModel
.setRangeHighlighterAttributes(range
, attributes
);
768 //refresh highlighter text attributes
769 RangeHighlighter dummy
= markupModel
.addRangeHighlighter(0, 0, HYPERLINK_LAYER
, getHyperlinkAttributes(), HighlighterTargetArea
.EXACT_RANGE
);
770 markupModel
.removeHighlighter(dummy
);
773 private HyperlinkInfo
getHyperlinkInfoByPoint(final Point p
){
774 if (myEditor
== null) return null;
775 final LogicalPosition pos
= myEditor
.xyToLogicalPosition(new Point(p
.x
, p
.y
));
776 return getHyperlinkInfoByLineAndCol(pos
.line
, pos
.column
);
779 private HyperlinkInfo
getHyperlinkInfoByLineAndCol(final int line
, final int col
) {
780 final int offset
= myEditor
.logicalPositionToOffset(new LogicalPosition(line
, col
));
781 return myHyperlinks
.getHyperlinkAt(offset
);
784 private void highlightHyperlinks(final int line1
, final int line2
){
785 ApplicationManager
.getApplication().assertIsDispatchThread();
786 PsiDocumentManager
.getInstance(myProject
).commitAllDocuments();
787 final Document document
= myEditor
.getDocument();
788 final CharSequence chars
= document
.getCharsSequence();
789 final TextAttributes hyperlinkAttributes
= getHyperlinkAttributes();
791 for(int line
= line1
; line
<= line2
; line
++) {
792 if (line
< 0) continue;
793 final int startOffset
= document
.getLineStartOffset(line
);
794 int endOffset
= document
.getLineEndOffset(line
);
795 if (endOffset
< document
.getTextLength()){
796 endOffset
++; // add '\n'
798 final String text
= chars
.subSequence(startOffset
, endOffset
).toString();
799 Filter
.Result result
= myCustomFilter
.applyFilter(text
, endOffset
);
800 if (result
== null) {
801 result
= myPredefinedMessageFilter
.applyFilter(text
, endOffset
);
804 final int highlightStartOffset
= result
.highlightStartOffset
;
805 final int highlightEndOffset
= result
.highlightEndOffset
;
806 final HyperlinkInfo hyperlinkInfo
= result
.hyperlinkInfo
;
807 addHyperlink(highlightStartOffset
, highlightEndOffset
, result
.highlightAttributes
, hyperlinkInfo
, hyperlinkAttributes
);
812 private void addHyperlink(final int highlightStartOffset
,
813 final int highlightEndOffset
,
814 final TextAttributes highlightAttributes
,
815 final HyperlinkInfo hyperlinkInfo
,
816 final TextAttributes hyperlinkAttributes
) {
817 TextAttributes textAttributes
= highlightAttributes
!= null ? highlightAttributes
: hyperlinkAttributes
;
818 final RangeHighlighter highlighter
= myEditor
.getMarkupModel().addRangeHighlighter(highlightStartOffset
,
822 HighlighterTargetArea
.EXACT_RANGE
);
823 myHyperlinks
.add(highlighter
, hyperlinkInfo
);
826 private class ClearAllAction
extends AnAction
implements DumbAware
{
827 private ClearAllAction(){
828 super(ExecutionBundle
.message("clear.all.from.console.action.name"));
831 public void actionPerformed(final AnActionEvent e
){
836 private class CopyAction
extends AnAction
implements DumbAware
{
837 private CopyAction(){
838 super(myEditor
!= null && myEditor
.getSelectionModel().hasSelection() ? ExecutionBundle
.message("copy.selected.content.action.name") : ExecutionBundle
.message("copy.content.action.name"));
841 public void actionPerformed(final AnActionEvent e
){
842 if (myEditor
== null) return;
843 if (myEditor
.getSelectionModel().hasSelection()){
844 myEditor
.getSelectionModel().copySelectionToClipboard();
847 myEditor
.getSelectionModel().setSelection(0, myEditor
.getDocument().getTextLength());
848 myEditor
.getSelectionModel().copySelectionToClipboard();
849 myEditor
.getSelectionModel().removeSelection();
854 private class MyHighlighter
extends DocumentAdapter
implements EditorHighlighter
{
855 private boolean myHasEditor
;
857 public HighlighterIterator
createIterator(final int startOffset
) {
858 final int startIndex
= findTokenInfoIndexByOffset(startOffset
);
860 return new HighlighterIterator(){
861 private int myIndex
= startIndex
;
863 public TextAttributes
getTextAttributes() {
864 if (myFileType
!= null && getTokenInfo().contentType
== ConsoleViewContentType
.USER_INPUT
) {
865 return ConsoleViewContentType
.NORMAL_OUTPUT
.getAttributes();
867 return getTokenInfo() == null ?
null : getTokenInfo().attributes
;
870 public int getStart() {
871 return getTokenInfo() == null ?
0 : getTokenInfo().startOffset
;
874 public int getEnd() {
875 return getTokenInfo() == null ?
0 : getTokenInfo().endOffset
;
878 public IElementType
getTokenType() {
882 public void advance() {
886 public void retreat() {
890 public boolean atEnd() {
891 return myIndex
< 0 || myIndex
>= myTokens
.size();
894 private TokenInfo
getTokenInfo() {
895 return myTokens
.get(myIndex
);
900 public void setText(final CharSequence text
) {
903 public void setEditor(final HighlighterClient editor
) {
904 LOG
.assertTrue(!myHasEditor
, "Highlighters cannot be reused with different editors");
908 public void setColorScheme(EditorColorsScheme scheme
) {
912 private int findTokenInfoIndexByOffset(final int offset
) {
914 int high
= myTokens
.size() - 1;
917 final int mid
= (low
+ high
) / 2;
918 final TokenInfo midVal
= myTokens
.get(mid
);
919 if (offset
< midVal
.startOffset
){
922 else if (offset
>= midVal
.endOffset
){
929 return myTokens
.size();
932 private static class MyTypedHandler
implements TypedActionHandler
{
933 private final TypedActionHandler myOriginalHandler
;
935 private MyTypedHandler(final TypedActionHandler originalAction
) {
936 myOriginalHandler
= originalAction
;
939 public void execute(@NotNull final Editor editor
, final char charTyped
, @NotNull final DataContext dataContext
) {
940 final ConsoleViewImpl consoleView
= editor
.getUserData(CONSOLE_VIEW_IN_EDITOR_VIEW
);
941 if (consoleView
== null || !consoleView
.myState
.isRunning() || consoleView
.isViewer
){
942 myOriginalHandler
.execute(editor
, charTyped
, dataContext
);
945 final String s
= String
.valueOf(charTyped
);
946 SelectionModel selectionModel
= editor
.getSelectionModel();
947 if (selectionModel
.hasSelection()) {
948 consoleView
.replaceUserText(s
, selectionModel
.getSelectionStart(), selectionModel
.getSelectionEnd());
950 consoleView
.insertUserText(s
, editor
.getCaretModel().getOffset());
956 private static final DataAccessor
<ConsoleViewImpl
> CONSOLE
= new DataAccessor
<ConsoleViewImpl
>() {
957 public ConsoleViewImpl
getImpl(final DataContext dataContext
) throws NoDataException
{
958 return DataAccessors
.EDITOR
.getNotNull(dataContext
).getUserData(CONSOLE_VIEW_IN_EDITOR_VIEW
);
962 private static final Condition
<ConsoleViewImpl
> CONSOLE_IS_RUNNING
= new Condition
<ConsoleViewImpl
>() {
963 public boolean value(final ConsoleViewImpl consoleView
) {
964 return consoleView
.myState
.isRunning();
968 private static final DataAccessor
<ConsoleViewImpl
> RUNNINT_CONSOLE
=DataAccessor
.createConditionalAccessor(CONSOLE
, CONSOLE_IS_RUNNING
);
970 private abstract static class ConsoleAction
extends AnAction
implements DumbAware
{
971 public void actionPerformed(final AnActionEvent e
) {
972 final DataContext context
= e
.getDataContext();
973 final ConsoleViewImpl console
= RUNNINT_CONSOLE
.from(context
);
974 execute(console
, context
);
977 protected abstract void execute(ConsoleViewImpl console
, final DataContext context
);
979 public void update(final AnActionEvent e
) {
980 final ConsoleViewImpl console
= RUNNINT_CONSOLE
.from(e
.getDataContext());
981 e
.getPresentation().setEnabled(console
!= null);
985 private static class EnterHandler
extends ConsoleAction
{
986 public void execute(final ConsoleViewImpl consoleView
, final DataContext context
) {
987 synchronized (consoleView
.LOCK
) {
988 String str
= consoleView
.myDeferredUserInput
.toString();
989 if (StringUtil
.isNotEmpty(str
)) {
990 consoleView
.myHistory
.remove(str
);
991 consoleView
.myHistory
.add(str
);
992 if (consoleView
.myHistory
.size() > consoleView
.myHistorySize
) consoleView
.myHistory
.remove(0);
994 for (ConsoleInputListener listener
: consoleView
.myConsoleInputListeners
) {
995 listener
.textEntered(str
);
998 consoleView
.print("\n", ConsoleViewContentType
.USER_INPUT
);
999 consoleView
.flushDeferredText();
1000 final Editor editor
= consoleView
.myEditor
;
1001 editor
.getCaretModel().moveToOffset(editor
.getDocument().getTextLength());
1002 editor
.getScrollingModel().scrollToCaret(ScrollType
.RELATIVE
);
1006 private static class PasteHandler
extends ConsoleAction
{
1007 public void execute(final ConsoleViewImpl consoleView
, final DataContext context
) {
1008 final Transferable content
= CopyPasteManager
.getInstance().getContents();
1009 if (content
== null) return;
1012 s
= (String
)content
.getTransferData(DataFlavor
.stringFlavor
);
1014 catch(Exception e
) {
1015 consoleView
.myEditor
.getComponent().getToolkit().beep();
1017 if (s
== null) return;
1018 Editor editor
= consoleView
.myEditor
;
1019 SelectionModel selectionModel
= editor
.getSelectionModel();
1020 if (selectionModel
.hasSelection()) {
1021 consoleView
.replaceUserText(s
, selectionModel
.getSelectionStart(), selectionModel
.getSelectionEnd());
1023 consoleView
.insertUserText(s
, editor
.getCaretModel().getOffset());
1028 private static class BackSpaceHandler
extends ConsoleAction
{
1029 public void execute(final ConsoleViewImpl consoleView
, final DataContext context
) {
1030 final Editor editor
= consoleView
.myEditor
;
1032 if (IncrementalSearchHandler
.isHintVisible(editor
)) {
1033 getDefaultActionHandler().execute(editor
, context
);
1037 final Document document
= editor
.getDocument();
1038 final int length
= document
.getTextLength();
1043 SelectionModel selectionModel
= editor
.getSelectionModel();
1044 if (selectionModel
.hasSelection()) {
1045 consoleView
.deleteUserText(selectionModel
.getSelectionStart(),
1046 selectionModel
.getSelectionEnd() - selectionModel
.getSelectionStart());
1047 } else if (editor
.getCaretModel().getOffset() > 0) {
1048 consoleView
.deleteUserText(editor
.getCaretModel().getOffset() - 1, 1);
1052 private static EditorActionHandler
getDefaultActionHandler() {
1053 return EditorActionManager
.getInstance().getActionHandler(IdeActions
.ACTION_EDITOR_BACKSPACE
);
1057 private static class DeleteHandler
extends ConsoleAction
{
1058 public void execute(final ConsoleViewImpl consoleView
, final DataContext context
) {
1059 final Editor editor
= consoleView
.myEditor
;
1061 if (IncrementalSearchHandler
.isHintVisible(editor
)) {
1062 getDefaultActionHandler().execute(editor
, context
);
1066 final Document document
= editor
.getDocument();
1067 final int length
= document
.getTextLength();
1072 SelectionModel selectionModel
= editor
.getSelectionModel();
1073 if (selectionModel
.hasSelection()) {
1074 consoleView
.deleteUserText(selectionModel
.getSelectionStart(),
1075 selectionModel
.getSelectionEnd() - selectionModel
.getSelectionStart());
1077 consoleView
.deleteUserText(editor
.getCaretModel().getOffset(), 1);
1081 private static EditorActionHandler
getDefaultActionHandler() {
1082 return EditorActionManager
.getInstance().getActionHandler(IdeActions
.ACTION_EDITOR_BACKSPACE
);
1086 private static class Hyperlinks
{
1087 private static final int NO_INDEX
= Integer
.MIN_VALUE
;
1088 private final Map
<RangeHighlighter
,HyperlinkInfo
> myHighlighterToMessageInfoMap
= new HashMap
<RangeHighlighter
, HyperlinkInfo
>();
1089 private int myLastIndex
= NO_INDEX
;
1091 public void clear() {
1092 myHighlighterToMessageInfoMap
.clear();
1093 myLastIndex
= NO_INDEX
;
1096 public HyperlinkInfo
getHyperlinkAt(final int offset
) {
1097 for (final RangeHighlighter highlighter
: myHighlighterToMessageInfoMap
.keySet()) {
1098 if (highlighter
.isValid() && containsOffset(offset
, highlighter
)) {
1099 return myHighlighterToMessageInfoMap
.get(highlighter
);
1105 private static boolean containsOffset(final int offset
, final RangeHighlighter highlighter
) {
1106 return highlighter
.getStartOffset() <= offset
&& offset
<= highlighter
.getEndOffset();
1109 public void add(final RangeHighlighter highlighter
, final HyperlinkInfo hyperlinkInfo
) {
1110 myHighlighterToMessageInfoMap
.put(highlighter
, hyperlinkInfo
);
1111 if (myLastIndex
!= NO_INDEX
&& containsOffset(myLastIndex
, highlighter
)) myLastIndex
= NO_INDEX
;
1114 private Map
<RangeHighlighter
,HyperlinkInfo
> getRanges() {
1115 return myHighlighterToMessageInfoMap
;
1119 public JComponent
getPreferredFocusableComponent() {
1120 //ensure editor created
1122 return myEditor
.getContentComponent();
1126 // navigate up/down in stack trace
1127 public boolean hasNextOccurence() {
1128 return next(1, false) != null;
1131 public boolean hasPreviousOccurence() {
1132 return next(-1, false) != null;
1135 public OccurenceInfo
goNextOccurence() {
1136 return next(1, true);
1139 private OccurenceInfo
next(final int delta
, boolean doMove
) {
1140 List
<RangeHighlighter
> ranges
= new ArrayList
<RangeHighlighter
>(myHyperlinks
.getRanges().keySet());
1141 Collections
.sort(ranges
, new Comparator
<RangeHighlighter
>() {
1142 public int compare(final RangeHighlighter o1
, final RangeHighlighter o2
) {
1143 return o1
.getStartOffset() - o2
.getStartOffset();
1147 for (i
= 0; i
<ranges
.size(); i
++) {
1148 RangeHighlighter range
= ranges
.get(i
);
1149 if (range
.getUserData(OLD_HYPERLINK_TEXT_ATTRIBUTES
) != null) {
1153 int newIndex
= ranges
.isEmpty() ?
-1 : i
== ranges
.size() ?
0 : (i
+ delta
+ ranges
.size()) % ranges
.size();
1154 RangeHighlighter next
= newIndex
< ranges
.size() && newIndex
>= 0 ? ranges
.get(newIndex
) : null;
1155 if (next
== null) return null;
1157 scrollTo(next
.getStartOffset());
1159 final HyperlinkInfo hyperlinkInfo
= myHyperlinks
.getRanges().get(next
);
1160 return new OccurenceInfo(new Navigatable() {
1161 public void navigate(final boolean requestFocus
) {
1162 hyperlinkInfo
.navigate(myProject
);
1163 linkFollowed(hyperlinkInfo
);
1166 public boolean canNavigate() {
1170 public boolean canNavigateToSource() {
1173 }, i
, ranges
.size());
1176 public OccurenceInfo
goPreviousOccurence() {
1177 return next(-1, true);
1180 public String
getNextOccurenceActionName() {
1181 return ExecutionBundle
.message("down.the.stack.trace");
1184 public String
getPreviousOccurenceActionName() {
1185 return ExecutionBundle
.message("up.the.stack.trace");
1188 public void addCustomConsoleAction(@NotNull AnAction action
) {
1189 customActions
.add(action
);
1193 public AnAction
[] createConsoleActions() {
1194 //Initializing prev and next occurrences actions
1195 CommonActionsManager actionsManager
= CommonActionsManager
.getInstance();
1196 AnAction prevAction
= actionsManager
.createPrevOccurenceAction(this);
1197 prevAction
.getTemplatePresentation().setText(getPreviousOccurenceActionName());
1198 AnAction nextAction
= actionsManager
.createNextOccurenceAction(this);
1199 nextAction
.getTemplatePresentation().setText(getNextOccurenceActionName());
1200 //Initializing custom actions
1201 AnAction
[] consoleActions
= new AnAction
[2 + customActions
.size()];
1202 consoleActions
[0] = prevAction
;
1203 consoleActions
[1] = nextAction
;
1204 for (int i
= 0; i
< customActions
.size(); ++i
) {
1205 consoleActions
[i
+ 2] = customActions
.get(i
);
1207 return consoleActions
;
1210 public void setEditorEnabled(boolean enabled
) {
1211 myEditor
.getContentComponent().setEnabled(enabled
);
1214 private void fireChange() {
1215 if (myDeferredTypes
.isEmpty()) return;
1216 Collection
<ConsoleViewContentType
> types
= Collections
.unmodifiableCollection(myDeferredTypes
);
1218 for (ChangeListener each
: myListeners
) {
1219 each
.contentAdded(types
);
1222 myDeferredTypes
.clear();
1225 public void addChangeListener(final ChangeListener listener
, final Disposable parent
) {
1226 myListeners
.add(listener
);
1227 Disposer
.register(parent
, new Disposable() {
1228 public void dispose() {
1229 myListeners
.remove(listener
);
1235 * insert text to document
1236 * @param s inserted text
1237 * @param offset relativly to all document text
1239 private void insertUserText(final String s
, int offset
) {
1240 final ConsoleViewImpl consoleView
= this;
1241 final Editor editor
= consoleView
.myEditor
;
1242 final Document document
= editor
.getDocument();
1243 final int startOffset
;
1245 synchronized (consoleView
.LOCK
) {
1246 if (consoleView
.myTokens
.isEmpty()) return;
1247 final TokenInfo info
= consoleView
.myTokens
.get(consoleView
.myTokens
.size() - 1);
1248 if (info
.contentType
!= ConsoleViewContentType
.USER_INPUT
&& !s
.contains("\n")) {
1249 consoleView
.print(s
, ConsoleViewContentType
.USER_INPUT
);
1250 consoleView
.flushDeferredText();
1251 editor
.getCaretModel().moveToOffset(document
.getTextLength());
1252 editor
.getSelectionModel().removeSelection();
1254 } else if (info
.contentType
!= ConsoleViewContentType
.USER_INPUT
) {
1255 insertUserText("temp", offset
);
1256 final TokenInfo newInfo
= consoleView
.myTokens
.get(consoleView
.myTokens
.size() - 1);
1257 replaceUserText(s
, newInfo
.startOffset
, newInfo
.endOffset
);
1263 if (offset
> info
.endOffset
) {
1264 startOffset
= info
.endOffset
;
1266 else if (offset
< info
.startOffset
) {
1267 startOffset
= info
.startOffset
;
1269 startOffset
= offset
;
1271 charCountToAdd
= s
.length();
1273 if (consoleView
.myDeferredUserInput
.length() < info
.endOffset
- info
.startOffset
) return; //user was quick
1275 consoleView
.myDeferredUserInput
.insert(startOffset
- info
.startOffset
, s
);
1277 info
.endOffset
+= charCountToAdd
;
1278 consoleView
.myContentSize
+= charCountToAdd
;
1281 document
.insertString(startOffset
, s
);
1282 editor
.getCaretModel().moveToOffset(startOffset
+ s
.length());
1283 editor
.getScrollingModel().scrollToCaret(ScrollType
.RELATIVE
);
1288 * @param s text for replace
1289 * @param start relativly to all document text
1290 * @param end relativly to all document text
1292 private void replaceUserText(final String s
, int start
, int end
) {
1294 insertUserText(s
, start
);
1297 final ConsoleViewImpl consoleView
= this;
1298 final Editor editor
= consoleView
.myEditor
;
1299 final Document document
= editor
.getDocument();
1300 final int startOffset
;
1301 final int endOffset
;
1303 synchronized (consoleView
.LOCK
) {
1304 if (consoleView
.myTokens
.isEmpty()) return;
1305 final TokenInfo info
= consoleView
.myTokens
.get(consoleView
.myTokens
.size() - 1);
1306 if (info
.contentType
!= ConsoleViewContentType
.USER_INPUT
) {
1307 consoleView
.print(s
, ConsoleViewContentType
.USER_INPUT
);
1308 consoleView
.flushDeferredText();
1309 editor
.getCaretModel().moveToOffset(document
.getTextLength());
1310 editor
.getSelectionModel().removeSelection();
1313 if (consoleView
.myDeferredUserInput
.length() == 0) return;
1314 int charCountToReplace
;
1316 startOffset
= getStartOffset(start
, info
);
1317 endOffset
= getEndOffset(end
, info
);
1319 if (startOffset
== -1 ||
1321 endOffset
<= startOffset
) {
1322 editor
.getSelectionModel().removeSelection();
1323 editor
.getCaretModel().moveToOffset(start
);
1326 charCountToReplace
= s
.length() - endOffset
+ startOffset
;
1328 if (consoleView
.myDeferredUserInput
.length() < info
.endOffset
- info
.startOffset
) return; //user was quick
1330 consoleView
.myDeferredUserInput
.replace(startOffset
- info
.startOffset
, endOffset
- info
.startOffset
, s
);
1332 info
.endOffset
+= charCountToReplace
;
1333 if (info
.startOffset
== info
.endOffset
) {
1334 consoleView
.myTokens
.remove(consoleView
.myTokens
.size() - 1);
1336 consoleView
.myContentSize
+= charCountToReplace
;
1339 document
.replaceString(startOffset
, endOffset
, s
);
1340 editor
.getCaretModel().moveToOffset(startOffset
+ s
.length());
1341 editor
.getScrollingModel().scrollToCaret(ScrollType
.RELATIVE
);
1342 editor
.getSelectionModel().removeSelection();
1347 * @param offset relativly to all document text
1348 * @param length lenght of deleted text
1350 private void deleteUserText(int offset
, int length
) {
1351 ConsoleViewImpl consoleView
= this;
1352 final Editor editor
= consoleView
.myEditor
;
1353 final Document document
= editor
.getDocument();
1354 final int startOffset
;
1355 final int endOffset
;
1357 synchronized (consoleView
.LOCK
) {
1358 if (consoleView
.myTokens
.isEmpty()) return;
1359 final TokenInfo info
= consoleView
.myTokens
.get(consoleView
.myTokens
.size() - 1);
1360 if (info
.contentType
!= ConsoleViewContentType
.USER_INPUT
) return;
1361 if (consoleView
.myDeferredUserInput
.length() == 0) return;
1362 int charCountToDelete
;
1364 startOffset
= getStartOffset(offset
, info
);
1365 endOffset
= getEndOffset(offset
+ length
, info
);
1366 if (startOffset
== -1 ||
1368 endOffset
<= startOffset
) {
1369 editor
.getSelectionModel().removeSelection();
1370 editor
.getCaretModel().moveToOffset(offset
);
1374 consoleView
.myDeferredUserInput
.delete(startOffset
- info
.startOffset
, endOffset
- info
.startOffset
);
1375 charCountToDelete
= endOffset
- startOffset
;
1377 info
.endOffset
-= charCountToDelete
;
1378 if (info
.startOffset
== info
.endOffset
) {
1379 consoleView
.myTokens
.remove(consoleView
.myTokens
.size() - 1);
1381 consoleView
.myContentSize
-= charCountToDelete
;
1384 document
.deleteString(startOffset
, endOffset
);
1385 editor
.getCaretModel().moveToOffset(startOffset
);
1386 editor
.getScrollingModel().scrollToCaret(ScrollType
.RELATIVE
);
1387 editor
.getSelectionModel().removeSelection();
1390 //util methods for add, replace, delete methods
1391 private static int getStartOffset(int offset
, TokenInfo info
) {
1393 if (offset
>= info
.startOffset
&& offset
< info
.endOffset
) {
1394 startOffset
= offset
;
1395 } else if (offset
< info
.startOffset
) {
1396 startOffset
= info
.startOffset
;
1403 private static int getEndOffset(int offset
, TokenInfo info
) {
1405 if (offset
> info
.endOffset
) {
1406 endOffset
= info
.endOffset
;
1407 } else if (offset
<= info
.startOffset
) {