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
.OccurenceNavigator
;
28 import com
.intellij
.openapi
.Disposable
;
29 import com
.intellij
.openapi
.actionSystem
.*;
30 import com
.intellij
.openapi
.application
.ApplicationManager
;
31 import com
.intellij
.openapi
.application
.ModalityState
;
32 import com
.intellij
.openapi
.command
.CommandProcessor
;
33 import com
.intellij
.openapi
.diagnostic
.Logger
;
34 import com
.intellij
.openapi
.diff
.actions
.DiffActions
;
35 import com
.intellij
.openapi
.editor
.*;
36 import com
.intellij
.openapi
.editor
.actionSystem
.*;
37 import com
.intellij
.openapi
.editor
.colors
.CodeInsightColors
;
38 import com
.intellij
.openapi
.editor
.colors
.EditorColors
;
39 import com
.intellij
.openapi
.editor
.colors
.EditorColorsManager
;
40 import com
.intellij
.openapi
.editor
.colors
.EditorColorsScheme
;
41 import com
.intellij
.openapi
.editor
.event
.*;
42 import com
.intellij
.openapi
.editor
.ex
.EditorEx
;
43 import com
.intellij
.openapi
.editor
.ex
.MarkupModelEx
;
44 import com
.intellij
.openapi
.editor
.highlighter
.EditorHighlighter
;
45 import com
.intellij
.openapi
.editor
.highlighter
.HighlighterClient
;
46 import com
.intellij
.openapi
.editor
.highlighter
.HighlighterIterator
;
47 import com
.intellij
.openapi
.editor
.impl
.EditorFactoryImpl
;
48 import com
.intellij
.openapi
.editor
.markup
.HighlighterLayer
;
49 import com
.intellij
.openapi
.editor
.markup
.HighlighterTargetArea
;
50 import com
.intellij
.openapi
.editor
.markup
.RangeHighlighter
;
51 import com
.intellij
.openapi
.editor
.markup
.TextAttributes
;
52 import com
.intellij
.openapi
.extensions
.Extensions
;
53 import com
.intellij
.openapi
.fileEditor
.OpenFileDescriptor
;
54 import com
.intellij
.openapi
.fileTypes
.FileType
;
55 import com
.intellij
.openapi
.ide
.CopyPasteManager
;
56 import com
.intellij
.openapi
.keymap
.Keymap
;
57 import com
.intellij
.openapi
.keymap
.KeymapManager
;
58 import com
.intellij
.openapi
.project
.DumbAware
;
59 import com
.intellij
.openapi
.project
.Project
;
60 import com
.intellij
.openapi
.util
.Computable
;
61 import com
.intellij
.openapi
.util
.Disposer
;
62 import com
.intellij
.openapi
.util
.Key
;
63 import com
.intellij
.openapi
.util
.text
.LineTokenizer
;
64 import com
.intellij
.openapi
.util
.text
.StringUtil
;
65 import com
.intellij
.pom
.Navigatable
;
66 import com
.intellij
.psi
.PsiDocumentManager
;
67 import com
.intellij
.psi
.PsiFile
;
68 import com
.intellij
.psi
.PsiFileFactory
;
69 import com
.intellij
.psi
.tree
.IElementType
;
70 import com
.intellij
.util
.Alarm
;
71 import com
.intellij
.util
.EditorPopupHandler
;
72 import com
.intellij
.util
.LocalTimeCounter
;
73 import com
.intellij
.util
.containers
.HashMap
;
74 import org
.jetbrains
.annotations
.NotNull
;
75 import org
.jetbrains
.annotations
.Nullable
;
76 import org
.jetbrains
.annotations
.TestOnly
;
80 import java
.awt
.datatransfer
.DataFlavor
;
81 import java
.awt
.datatransfer
.Transferable
;
82 import java
.awt
.event
.KeyEvent
;
83 import java
.awt
.event
.KeyListener
;
84 import java
.awt
.event
.MouseEvent
;
85 import java
.awt
.event
.MouseMotionAdapter
;
86 import java
.io
.IOException
;
88 import java
.util
.List
;
89 import java
.util
.concurrent
.CopyOnWriteArraySet
;
91 public class ConsoleViewImpl
extends JPanel
implements ConsoleView
, ObservableConsoleView
, DataProvider
, OccurenceNavigator
{
92 private static final Logger LOG
= Logger
.getInstance("#com.intellij.execution.impl.ConsoleViewImpl");
94 private static final int FLUSH_DELAY
= 200; //TODO : make it an option
96 private static final Key
<ConsoleViewImpl
> CONSOLE_VIEW_IN_EDITOR_VIEW
= Key
.create("CONSOLE_VIEW_IN_EDITOR_VIEW");
98 final EditorActionManager actionManager
= EditorActionManager
.getInstance();
99 final TypedAction typedAction
= actionManager
.getTypedAction();
100 typedAction
.setupHandler(new MyTypedHandler(typedAction
.getHandler()));
103 private final DisposedPsiManagerCheck myPsiDisposedCheck
;
104 private ConsoleState myState
= ConsoleState
.NOT_STARTED
;
105 private final int CYCLIC_BUFFER_SIZE
= getCycleBufferSize();
106 private final boolean isViewer
;
107 private Computable
<ModalityState
> myStateForUpdate
;
109 private static int getCycleBufferSize() {
110 final String cycleBufferSizeProperty
= System
.getProperty("idea.cycle.buffer.size");
111 if (cycleBufferSizeProperty
== null) return 1024 * 1024;
113 return Integer
.parseInt(cycleBufferSizeProperty
) * 1024;
115 catch (NumberFormatException e
) {
120 private final boolean USE_CYCLIC_BUFFER
= useCycleBuffer();
122 private static boolean useCycleBuffer() {
123 final String useCycleBufferProperty
= System
.getProperty("idea.cycle.buffer.size");
124 return useCycleBufferProperty
== null || !"disabled".equalsIgnoreCase(useCycleBufferProperty
);
127 private static final int HYPERLINK_LAYER
= HighlighterLayer
.SELECTION
- 123;
128 private final Alarm mySpareTimeAlarm
= new Alarm();
130 private final CopyOnWriteArraySet
<ChangeListener
> myListeners
= new CopyOnWriteArraySet
<ChangeListener
>();
131 private final Set
<ConsoleViewContentType
> myDeferredTypes
= new HashSet
<ConsoleViewContentType
>();
132 private final ArrayList
<AnAction
> customActions
= new ArrayList
<AnAction
>();
135 public Editor
getEditor() {
139 public void scrollToEnd() {
140 myEditor
.getCaretModel().moveToOffset(myEditor
.getDocument().getTextLength());
143 private static class TokenInfo
{
144 private final ConsoleViewContentType contentType
;
145 private int startOffset
;
146 private int endOffset
;
147 private final TextAttributes attributes
;
149 private TokenInfo(final ConsoleViewContentType contentType
, final int startOffset
, final int endOffset
) {
150 this.contentType
= contentType
;
151 this.startOffset
= startOffset
;
152 this.endOffset
= endOffset
;
153 attributes
= contentType
.getAttributes();
157 private final Project myProject
;
159 private boolean myOutputPaused
;
161 private Editor myEditor
;
163 private final Object LOCK
= new Object();
165 private int myContentSize
;
166 private StringBuffer myDeferredOutput
= new StringBuffer();
167 private StringBuffer myDeferredUserInput
= new StringBuffer();
169 private ArrayList
<TokenInfo
> myTokens
= new ArrayList
<TokenInfo
>();
170 private final Hyperlinks myHyperlinks
= new Hyperlinks();
172 private String myHelpId
;
174 private final Alarm myFlushAlarm
= new Alarm();
176 private final Runnable myFlushDeferredRunnable
= new Runnable() {
182 private final CompositeFilter myPredefinedMessageFilter
;
183 private final CompositeFilter myCustomFilter
;
185 private ArrayList
<String
> myHistory
= new ArrayList
<String
>();
186 private int myHistorySize
= 20;
188 private ArrayList
<ConsoleInputListener
> myConsoleInputListeners
= new ArrayList
<ConsoleInputListener
>();
190 public void addConsoleUserInputLestener(ConsoleInputListener consoleInputListener
) {
191 myConsoleInputListeners
.add(consoleInputListener
);
195 * By default history works for one session. If
196 * you want to import previous session, set it up here.
197 * @param history where you can save history
199 public void importHistory(Collection
<String
> history
) {
200 this.myHistory
.clear();
201 this.myHistory
.addAll(history
);
202 while (this.myHistory
.size() > myHistorySize
) {
203 this.myHistory
.remove(0);
207 public List
<String
> getHistory() {
208 return Collections
.unmodifiableList(myHistory
);
211 public void setHistorySize(int historySize
) {
212 this.myHistorySize
= historySize
;
215 public int getHistorySize() {
216 return myHistorySize
;
219 private FileType myFileType
;
222 * Use it for custom highlighting for user text.
223 * This will be highlighted as appropriate file to this file type.
224 * @param fileType according to which use highlighting
226 public void setFileType(FileType fileType
) {
227 myFileType
= fileType
;
230 public ConsoleViewImpl(final Project project
, boolean viewer
) {
231 this(project
, viewer
, null);
234 public ConsoleViewImpl(final Project project
, boolean viewer
, FileType fileType
) {
235 super(new BorderLayout());
237 myPsiDisposedCheck
= new DisposedPsiManagerCheck(project
);
239 myFileType
= fileType
;
241 myCustomFilter
= new CompositeFilter(project
);
242 myPredefinedMessageFilter
= new CompositeFilter(project
);
243 for (ConsoleFilterProvider filterProvider
: Extensions
.getExtensions(ConsoleFilterProvider
.FILTER_PROVIDERS
)) {
244 for (Filter filter
: filterProvider
.getDefaultFilters(project
)) {
245 myPredefinedMessageFilter
.addFilter(filter
);
249 Disposer
.register(project
, this);
252 public void attachToProcess(final ProcessHandler processHandler
){
253 myState
= myState
.attachTo(this, processHandler
);
256 public void clear() {
257 assertIsDispatchThread();
259 final Document document
;
262 if (USE_CYCLIC_BUFFER
) {
263 myDeferredOutput
= new StringBuffer(Math
.min(myDeferredOutput
.length(), CYCLIC_BUFFER_SIZE
));
266 myDeferredOutput
= new StringBuffer();
268 myDeferredTypes
.clear();
269 myDeferredUserInput
= new StringBuffer();
270 myHyperlinks
.clear();
272 if (myEditor
== null) return;
273 myEditor
.getMarkupModel().removeAllHighlighters();
274 document
= myEditor
.getDocument();
276 CommandProcessor
.getInstance().executeCommand(myProject
, new Runnable() {
278 document
.deleteString(0, document
.getTextLength());
280 }, null, DocCommandGroupId
.noneGroupId(document
));
283 public void scrollTo(final int offset
) {
284 assertIsDispatchThread();
286 if (myEditor
== null) return;
287 int moveOffset
= offset
;
288 if (USE_CYCLIC_BUFFER
&& moveOffset
>= myEditor
.getDocument().getTextLength()) {
291 myEditor
.getCaretModel().moveToOffset(moveOffset
);
292 myEditor
.getScrollingModel().scrollToCaret(ScrollType
.MAKE_VISIBLE
);
295 private static void assertIsDispatchThread() {
296 ApplicationManager
.getApplication().assertIsDispatchThread();
299 public void setOutputPaused(final boolean value
) {
300 myOutputPaused
= value
;
302 requestFlushImmediately();
306 public boolean isOutputPaused() {
307 return myOutputPaused
;
310 public boolean hasDeferredOutput() {
312 return myDeferredOutput
.length() > 0;
316 public void performWhenNoDeferredOutput(final Runnable runnable
) {
317 //Q: implement in another way without timer?
318 if (!hasDeferredOutput()){
322 mySpareTimeAlarm
.addRequest(
325 performWhenNoDeferredOutput(runnable
);
333 public JComponent
getComponent() {
334 if (myEditor
== null){
335 myEditor
= createEditor();
336 requestFlushImmediately();
337 add(createCenterComponent(), BorderLayout
.CENTER
);
339 myEditor
.getDocument().addDocumentListener(new DocumentAdapter() {
340 public void documentChanged(DocumentEvent e
) {
341 if (e
.getNewLength() == 0 && e
.getOffset() == 0) {
342 // string has beeen removed from the beginning, move tokens down
343 synchronized (LOCK
) {
344 int toRemoveLen
= e
.getOldLength();
345 int tIndex
= findTokenInfoIndexByOffset(toRemoveLen
);
346 ArrayList
<TokenInfo
> newTokens
= new ArrayList
<TokenInfo
>(myTokens
.subList(tIndex
, myTokens
.size()));
347 for (TokenInfo token
: newTokens
) {
348 token
.startOffset
-= toRemoveLen
;
349 token
.endOffset
-= toRemoveLen
;
351 if (!newTokens
.isEmpty()) {
352 newTokens
.get(0).startOffset
= 0;
354 myContentSize
-= Math
.min(myContentSize
, toRemoveLen
);
355 myTokens
= newTokens
;
364 protected JComponent
createCenterComponent() {
365 return myEditor
.getComponent();
368 public void setModalityStateForUpdate(Computable
<ModalityState
> stateComputable
) {
369 myStateForUpdate
= stateComputable
;
374 public void dispose(){
375 myState
= myState
.dispose();
376 if (myEditor
!= null){
377 myFlushAlarm
.cancelAllRequests();
378 mySpareTimeAlarm
.cancelAllRequests();
380 synchronized (LOCK
) {
381 myDeferredOutput
= new StringBuffer();
387 protected void disposeEditor() {
388 if (!myEditor
.isDisposed()) {
389 EditorFactory
.getInstance().releaseEditor(myEditor
);
393 public void print(String s
, final ConsoleViewContentType contentType
) {
395 myDeferredTypes
.add(contentType
);
397 s
= StringUtil
.convertLineSeparators(s
);
398 myContentSize
+= s
.length();
399 myDeferredOutput
.append(s
);
400 if (contentType
== ConsoleViewContentType
.USER_INPUT
){
401 myDeferredUserInput
.append(s
);
404 boolean needNew
= true;
405 if (!myTokens
.isEmpty()){
406 final TokenInfo lastToken
= myTokens
.get(myTokens
.size() - 1);
407 if (lastToken
.contentType
== contentType
){
408 lastToken
.endOffset
= myContentSize
; // optimization
413 myTokens
.add(new TokenInfo(contentType
, myContentSize
- s
.length(), myContentSize
));
416 if (s
.indexOf('\n') >= 0 || s
.indexOf('\r') >= 0) {
417 if (contentType
== ConsoleViewContentType
.USER_INPUT
) {
418 flushDeferredUserInput();
421 if (myFlushAlarm
.getActiveRequestCount() == 0 && myEditor
!= null) {
422 final boolean shouldFlushNow
= USE_CYCLIC_BUFFER
&& myDeferredOutput
.length() > CYCLIC_BUFFER_SIZE
;
423 myFlushAlarm
.addRequest(myFlushDeferredRunnable
, shouldFlushNow?
0 : FLUSH_DELAY
, getStateForUpdate());
428 private ModalityState
getStateForUpdate() {
429 return myStateForUpdate
!= null ? myStateForUpdate
.compute() : ModalityState
.stateForComponent(this);
432 private void requestFlushImmediately() {
433 if (myEditor
!= null) {
434 myFlushAlarm
.addRequest(myFlushDeferredRunnable
, 0, getStateForUpdate());
438 public int getContentSize() { return myContentSize
; }
440 public boolean canPause() {
444 private void flushDeferredText() {
445 ApplicationManager
.getApplication().assertIsDispatchThread();
446 if (myProject
.isDisposed()) {
451 synchronized (LOCK
) {
452 if (myOutputPaused
) return;
453 if (myDeferredOutput
.length() == 0) return;
454 if (myEditor
== null) return;
456 text
= myDeferredOutput
.substring(0, myDeferredOutput
.length());
457 if (USE_CYCLIC_BUFFER
) {
458 myDeferredOutput
= new StringBuffer(Math
.min(myDeferredOutput
.length(), CYCLIC_BUFFER_SIZE
));
461 myDeferredOutput
.setLength(0);
464 final Document document
= myEditor
.getDocument();
465 final int oldLineCount
= document
.getLineCount();
466 final boolean isAtEndOfDocument
= myEditor
.getCaretModel().getOffset() == document
.getTextLength();
467 boolean cycleUsed
= USE_CYCLIC_BUFFER
&& document
.getTextLength() + text
.length() > CYCLIC_BUFFER_SIZE
;
468 CommandProcessor
.getInstance().executeCommand(myProject
, new Runnable() {
470 document
.insertString(document
.getTextLength(), text
);
471 synchronized (LOCK
) {
475 }, null, DocCommandGroupId
.noneGroupId(document
));
476 myPsiDisposedCheck
.performCheck();
477 final int newLineCount
= document
.getLineCount();
479 final int lineCount
= LineTokenizer
.calcLineCount(text
, true);
480 for (Iterator
<RangeHighlighter
> it
= myHyperlinks
.getRanges().keySet().iterator(); it
.hasNext();) {
481 if (!it
.next().isValid()) {
485 highlightHyperlinks(newLineCount
>= lineCount
+ 1 ? newLineCount
- lineCount
- 1 : 0, newLineCount
- 1);
487 else if (oldLineCount
< newLineCount
) {
488 highlightHyperlinks(oldLineCount
- 1, newLineCount
- 2);
491 if (isAtEndOfDocument
) {
492 myEditor
.getCaretModel().moveToOffset(myEditor
.getDocument().getTextLength());
493 myEditor
.getSelectionModel().removeSelection();
494 myEditor
.getScrollingModel().scrollToCaret(ScrollType
.RELATIVE
);
498 private void flushDeferredUserInput() {
499 if (myState
.isRunning()){
500 final String text
= myDeferredUserInput
.substring(0, myDeferredUserInput
.length());
501 final int index
= Math
.max(text
.lastIndexOf('\n'), text
.lastIndexOf('\r'));
502 if (index
< 0) return;
504 myState
.sendUserInput(text
.substring(0, index
+ 1));
506 catch(IOException e
){
509 myDeferredUserInput
.setLength(0);
510 myDeferredUserInput
.append(text
.substring(index
+ 1));
514 public Object
getData(final String dataId
) {
515 if (PlatformDataKeys
.NAVIGATABLE
.is(dataId
)){
516 if (myEditor
== null) {
519 final LogicalPosition pos
= myEditor
.getCaretModel().getLogicalPosition();
520 final HyperlinkInfo info
= getHyperlinkInfoByLineAndCol(pos
.line
, pos
.column
);
521 final OpenFileDescriptor openFileDescriptor
= info
instanceof FileHyperlinkInfo ?
((FileHyperlinkInfo
)info
).getDescriptor() : null;
522 if (openFileDescriptor
== null || !openFileDescriptor
.getFile().isValid()) {
525 return openFileDescriptor
;
528 if (PlatformDataKeys
.EDITOR
.is(dataId
)) {
531 if (PlatformDataKeys
.HELP_ID
.is(dataId
)) {
537 public void setHelpId(final String helpId
) {
541 public void addMessageFilter(final Filter filter
) {
542 myCustomFilter
.addFilter(filter
);
545 public void printHyperlink(final String hyperlinkText
, final HyperlinkInfo info
) {
546 if (myEditor
== null) return;
547 print(hyperlinkText
, ConsoleViewContentType
.NORMAL_OUTPUT
);
549 final int textLength
= myEditor
.getDocument().getTextLength();
550 addHyperlink(textLength
- hyperlinkText
.length(), textLength
, null, info
, getHyperlinkAttributes());
553 private static TextAttributes
getHyperlinkAttributes() {
554 return EditorColorsManager
.getInstance().getGlobalScheme().getAttributes(CodeInsightColors
.HYPERLINK_ATTRIBUTES
);
557 private static TextAttributes
getFollowedHyperlinkAttributes() {
558 return EditorColorsManager
.getInstance().getGlobalScheme().getAttributes(CodeInsightColors
.FOLLOWED_HYPERLINK_ATTRIBUTES
);
561 private Editor
createEditor() {
562 return ApplicationManager
.getApplication().runReadAction(new Computable
<Editor
>() {
563 public Editor
compute() {
564 return doCreateEditor();
569 private Editor
doCreateEditor() {
570 final EditorEx editor
= createRealEditor();
571 editor
.addEditorMouseListener(new EditorPopupHandler() {
572 public void invokePopup(final EditorMouseEvent event
) {
573 final MouseEvent mouseEvent
= event
.getMouseEvent();
574 popupInvoked(mouseEvent
.getComponent(), mouseEvent
.getX(), mouseEvent
.getY());
579 final int bufferSize
= USE_CYCLIC_BUFFER ? CYCLIC_BUFFER_SIZE
: 0;
580 editor
.getDocument().setCyclicBufferSize(bufferSize
);
582 editor
.putUserData(CONSOLE_VIEW_IN_EDITOR_VIEW
, this);
584 editor
.getContentComponent().addMouseMotionListener(
585 new MouseMotionAdapter(){
586 public void mouseMoved(final MouseEvent e
){
587 final HyperlinkInfo info
= getHyperlinkInfoByPoint(e
.getPoint());
589 editor
.getContentComponent().setCursor(Cursor
.getPredefinedCursor(Cursor
.HAND_CURSOR
));
592 editor
.getContentComponent().setCursor(Cursor
.getPredefinedCursor(Cursor
.TEXT_CURSOR
));
600 protected EditorEx
createRealEditor() {
601 final EditorFactoryImpl document
= (EditorFactoryImpl
) EditorFactory
.getInstance();
602 final Document editorDocument
= document
.createDocument(true);
603 editorDocument
.addDocumentListener(new DocumentListener() {
604 public void beforeDocumentChange(DocumentEvent event
) {
607 public void documentChanged(DocumentEvent event
) {
608 if (myFileType
!= null) {
609 highlightUserTokens();
613 final EditorEx editor
= (EditorEx
)document
.createViewer(editorDocument
, myProject
);
614 final EditorHighlighter highlighter
= new MyHighlighter();
615 editor
.setHighlighter(highlighter
);
617 final EditorSettings editorSettings
= editor
.getSettings();
618 editorSettings
.setLineMarkerAreaShown(false);
619 editorSettings
.setLineNumbersShown(false);
620 editorSettings
.setFoldingOutlineShown(false);
621 editorSettings
.setAdditionalPageAtBottom(false);
622 editorSettings
.setAdditionalColumnsCount(0);
623 editorSettings
.setAdditionalLinesCount(0);
625 final EditorColorsScheme scheme
= editor
.getColorsScheme();
626 editor
.setBackgroundColor(scheme
.getColor(ConsoleViewContentType
.CONSOLE_BACKGROUND_KEY
));
627 scheme
.setColor(EditorColors
.CARET_ROW_COLOR
, null);
628 scheme
.setColor(EditorColors
.RIGHT_MARGIN_COLOR
, null);
630 editor
.addEditorMouseListener(new EditorMouseAdapter() {
631 public void mouseReleased(final EditorMouseEvent e
) {
632 final MouseEvent mouseEvent
= e
.getMouseEvent();
633 if (!mouseEvent
.isPopupTrigger()) {
639 final ConsoleViewImpl consoleView
= this;
640 editor
.getContentComponent().addKeyListener(new KeyListener() {
641 private int historyPosition
= myHistory
.size();
643 public void keyTyped(KeyEvent e
) {
647 public void keyPressed(KeyEvent e
) {
650 public void keyReleased(KeyEvent e
) {
651 if (e
.isAltDown() && !e
.isControlDown() && !e
.isMetaDown() && !e
.isShiftDown()) {
652 if (e
.getKeyCode() == KeyEvent
.VK_UP
) {
654 if (historyPosition
< 0) historyPosition
= 0;
658 else if (e
.getKeyCode() == KeyEvent
.VK_DOWN
) {
660 if (historyPosition
> myHistory
.size()) historyPosition
= myHistory
.size();
666 historyPosition
= myHistory
.size();
670 private void replaceString() {
673 if (myHistory
.size() == historyPosition
) str
= "";
674 else str
= myHistory
.get(historyPosition
);
675 synchronized (LOCK
) {
676 if (myTokens
.isEmpty()) return;
677 final TokenInfo info
= myTokens
.get(myTokens
.size() - 1);
678 if (info
.contentType
!= ConsoleViewContentType
.USER_INPUT
) {
679 consoleView
.insertUserText(str
, 0);
682 consoleView
.replaceUserText(str
, info
.startOffset
, info
.endOffset
);
688 setEditorUpActions(editor
);
692 private void highlightUserTokens() {
693 if (myTokens
.isEmpty()) return;
694 final TokenInfo token
= myTokens
.get(myTokens
.size() - 1);
695 if (token
.contentType
== ConsoleViewContentType
.USER_INPUT
) {
696 String text
= myEditor
.getDocument().getText().substring(token
.startOffset
, token
.endOffset
);
697 PsiFile file
= PsiFileFactory
.getInstance(myProject
).
698 createFileFromText("dummy", myFileType
, text
, LocalTimeCounter
.currentTime(), true);
699 Document document
= PsiDocumentManager
.getInstance(myProject
).getDocument(file
);
700 assert document
!= null;
701 Editor editor
= EditorFactory
.getInstance().createEditor(document
, myProject
, myFileType
, false);
703 RangeHighlighter
[] allHighlighters
= myEditor
.getMarkupModel().getAllHighlighters();
704 for (RangeHighlighter highlighter
: allHighlighters
) {
705 if (highlighter
.getStartOffset() >= token
.startOffset
) {
706 myEditor
.getMarkupModel().removeHighlighter(highlighter
);
709 HighlighterIterator iterator
= ((EditorEx
) editor
).getHighlighter().createIterator(0);
710 while (!iterator
.atEnd()) {
711 myEditor
.getMarkupModel().addRangeHighlighter(iterator
.getStart() + token
.startOffset
, iterator
.getEnd() + token
.startOffset
, HighlighterLayer
.SYNTAX
,
712 iterator
.getTextAttributes(),
713 HighlighterTargetArea
.EXACT_RANGE
);
718 EditorFactory
.getInstance().releaseEditor(editor
);
723 private static void setEditorUpActions(final Editor editor
) {
724 new EnterHandler().registerCustomShortcutSet(CommonShortcuts
.ENTER
, editor
.getContentComponent());
725 registerActionHandler(editor
, IdeActions
.ACTION_EDITOR_PASTE
, new PasteHandler());
726 registerActionHandler(editor
, IdeActions
.ACTION_EDITOR_BACKSPACE
, new BackSpaceHandler());
727 registerActionHandler(editor
, IdeActions
.ACTION_EDITOR_DELETE
, new DeleteHandler());
730 private static void registerActionHandler(final Editor editor
, final String actionId
, final AnAction action
) {
731 final Keymap keymap
=KeymapManager
.getInstance().getActiveKeymap();
732 final Shortcut
[] shortcuts
= keymap
.getShortcuts(actionId
);
733 action
.registerCustomShortcutSet(new CustomShortcutSet(shortcuts
), editor
.getContentComponent());
736 private void popupInvoked(final Component component
, final int x
, final int y
){
737 final DefaultActionGroup group
= new DefaultActionGroup();
738 group
.add(new ClearAllAction());
739 group
.add(new CopyAction());
740 group
.addSeparator();
741 final ActionManager actionManager
= ActionManager
.getInstance();
742 group
.add(actionManager
.getAction(DiffActions
.COMPARE_WITH_CLIPBOARD
));
743 final ActionPopupMenu menu
= actionManager
.createActionPopupMenu(ActionPlaces
.UNKNOWN
, group
);
744 menu
.getComponent().show(component
, x
, y
);
747 private void navigate(final EditorMouseEvent event
){
748 if (event
.getMouseEvent().isPopupTrigger()) return;
749 final Point p
= event
.getMouseEvent().getPoint();
750 final HyperlinkInfo info
= getHyperlinkInfoByPoint(p
);
752 info
.navigate(myProject
);
757 private static final Key
<TextAttributes
> OLD_HYPERLINK_TEXT_ATTRIBUTES
= Key
.create("OLD_HYPERLINK_TEXT_ATTRIBUTES");
758 private void linkFollowed(final HyperlinkInfo info
) {
759 MarkupModelEx markupModel
= (MarkupModelEx
)myEditor
.getMarkupModel();
760 for (Map
.Entry
<RangeHighlighter
,HyperlinkInfo
> entry
: myHyperlinks
.getRanges().entrySet()) {
761 RangeHighlighter range
= entry
.getKey();
762 TextAttributes oldAttr
= range
.getUserData(OLD_HYPERLINK_TEXT_ATTRIBUTES
);
763 if (oldAttr
!= null) {
764 markupModel
.setRangeHighlighterAttributes(range
, oldAttr
);
765 range
.putUserData(OLD_HYPERLINK_TEXT_ATTRIBUTES
, null);
767 if (entry
.getValue() == info
) {
768 TextAttributes oldAttributes
= range
.getTextAttributes();
769 range
.putUserData(OLD_HYPERLINK_TEXT_ATTRIBUTES
, oldAttributes
);
770 TextAttributes attributes
= getFollowedHyperlinkAttributes().clone();
771 assert oldAttributes
!= null;
772 attributes
.setFontType(oldAttributes
.getFontType());
773 attributes
.setEffectType(oldAttributes
.getEffectType());
774 attributes
.setEffectColor(oldAttributes
.getEffectColor());
775 attributes
.setForegroundColor(oldAttributes
.getForegroundColor());
776 markupModel
.setRangeHighlighterAttributes(range
, attributes
);
779 //refresh highlighter text attributes
780 RangeHighlighter dummy
= markupModel
.addRangeHighlighter(0, 0, HYPERLINK_LAYER
, getHyperlinkAttributes(), HighlighterTargetArea
.EXACT_RANGE
);
781 markupModel
.removeHighlighter(dummy
);
784 private HyperlinkInfo
getHyperlinkInfoByPoint(final Point p
){
785 if (myEditor
== null) return null;
786 final LogicalPosition pos
= myEditor
.xyToLogicalPosition(new Point(p
.x
, p
.y
));
787 return getHyperlinkInfoByLineAndCol(pos
.line
, pos
.column
);
790 private HyperlinkInfo
getHyperlinkInfoByLineAndCol(final int line
, final int col
) {
791 final int offset
= myEditor
.logicalPositionToOffset(new LogicalPosition(line
, col
));
792 return myHyperlinks
.getHyperlinkAt(offset
);
795 private void highlightHyperlinks(final int line1
, final int line2
){
796 ApplicationManager
.getApplication().assertIsDispatchThread();
797 PsiDocumentManager
.getInstance(myProject
).commitAllDocuments();
798 final Document document
= myEditor
.getDocument();
799 final CharSequence chars
= document
.getCharsSequence();
800 final TextAttributes hyperlinkAttributes
= getHyperlinkAttributes();
802 for(int line
= line1
; line
<= line2
; line
++) {
803 if (line
< 0) continue;
804 final int startOffset
= document
.getLineStartOffset(line
);
805 int endOffset
= document
.getLineEndOffset(line
);
806 if (endOffset
< document
.getTextLength()){
807 endOffset
++; // add '\n'
809 final String text
= chars
.subSequence(startOffset
, endOffset
).toString();
810 Filter
.Result result
= myCustomFilter
.applyFilter(text
, endOffset
);
811 if (result
== null) {
812 result
= myPredefinedMessageFilter
.applyFilter(text
, endOffset
);
815 final int highlightStartOffset
= result
.highlightStartOffset
;
816 final int highlightEndOffset
= result
.highlightEndOffset
;
817 final HyperlinkInfo hyperlinkInfo
= result
.hyperlinkInfo
;
818 addHyperlink(highlightStartOffset
, highlightEndOffset
, result
.highlightAttributes
, hyperlinkInfo
, hyperlinkAttributes
);
823 private void addHyperlink(final int highlightStartOffset
,
824 final int highlightEndOffset
,
825 final TextAttributes highlightAttributes
,
826 final HyperlinkInfo hyperlinkInfo
,
827 final TextAttributes hyperlinkAttributes
) {
828 TextAttributes textAttributes
= highlightAttributes
!= null ? highlightAttributes
: hyperlinkAttributes
;
829 final RangeHighlighter highlighter
= myEditor
.getMarkupModel().addRangeHighlighter(highlightStartOffset
,
833 HighlighterTargetArea
.EXACT_RANGE
);
834 myHyperlinks
.add(highlighter
, hyperlinkInfo
);
837 private class ClearAllAction
extends AnAction
implements DumbAware
{
838 private ClearAllAction(){
839 super(ExecutionBundle
.message("clear.all.from.console.action.name"));
842 public void actionPerformed(final AnActionEvent e
){
847 private class CopyAction
extends AnAction
implements DumbAware
{
848 private CopyAction(){
849 super(myEditor
!= null && myEditor
.getSelectionModel().hasSelection() ? ExecutionBundle
.message("copy.selected.content.action.name") : ExecutionBundle
.message("copy.content.action.name"));
852 public void actionPerformed(final AnActionEvent e
){
853 if (myEditor
== null) return;
854 if (myEditor
.getSelectionModel().hasSelection()){
855 myEditor
.getSelectionModel().copySelectionToClipboard();
858 myEditor
.getSelectionModel().setSelection(0, myEditor
.getDocument().getTextLength());
859 myEditor
.getSelectionModel().copySelectionToClipboard();
860 myEditor
.getSelectionModel().removeSelection();
865 private class MyHighlighter
extends DocumentAdapter
implements EditorHighlighter
{
866 private HighlighterClient myEditor
;
868 public HighlighterIterator
createIterator(final int startOffset
) {
869 final int startIndex
= findTokenInfoIndexByOffset(startOffset
);
871 return new HighlighterIterator(){
872 private int myIndex
= startIndex
;
874 public TextAttributes
getTextAttributes() {
875 if (myFileType
!= null && getTokenInfo().contentType
== ConsoleViewContentType
.USER_INPUT
) {
876 return ConsoleViewContentType
.NORMAL_OUTPUT
.getAttributes();
878 return getTokenInfo() == null ?
null : getTokenInfo().attributes
;
881 public int getStart() {
882 return getTokenInfo() == null ?
0 : getTokenInfo().startOffset
;
885 public int getEnd() {
886 return getTokenInfo() == null ?
0 : getTokenInfo().endOffset
;
889 public IElementType
getTokenType() {
893 public void advance() {
897 public void retreat() {
901 public boolean atEnd() {
902 return myIndex
< 0 || myIndex
>= myTokens
.size();
905 public Document
getDocument() {
906 return myEditor
.getDocument();
909 private TokenInfo
getTokenInfo() {
910 return myTokens
.get(myIndex
);
915 public void setText(final CharSequence text
) {
918 public void setEditor(final HighlighterClient editor
) {
919 LOG
.assertTrue(myEditor
== null, "Highlighters cannot be reused with different editors");
923 public void setColorScheme(EditorColorsScheme scheme
) {
927 private int findTokenInfoIndexByOffset(final int offset
) {
929 int high
= myTokens
.size() - 1;
932 final int mid
= (low
+ high
) / 2;
933 final TokenInfo midVal
= myTokens
.get(mid
);
934 if (offset
< midVal
.startOffset
){
937 else if (offset
>= midVal
.endOffset
){
944 return myTokens
.size();
947 private static class MyTypedHandler
implements TypedActionHandler
{
948 private final TypedActionHandler myOriginalHandler
;
950 private MyTypedHandler(final TypedActionHandler originalAction
) {
951 myOriginalHandler
= originalAction
;
954 public void execute(@NotNull final Editor editor
, final char charTyped
, @NotNull final DataContext dataContext
) {
955 final ConsoleViewImpl consoleView
= editor
.getUserData(CONSOLE_VIEW_IN_EDITOR_VIEW
);
956 if (consoleView
== null || !consoleView
.myState
.isRunning() || consoleView
.isViewer
){
957 myOriginalHandler
.execute(editor
, charTyped
, dataContext
);
960 final String s
= String
.valueOf(charTyped
);
961 SelectionModel selectionModel
= editor
.getSelectionModel();
962 if (selectionModel
.hasSelection()) {
963 consoleView
.replaceUserText(s
, selectionModel
.getSelectionStart(), selectionModel
.getSelectionEnd());
965 consoleView
.insertUserText(s
, editor
.getCaretModel().getOffset());
971 private abstract static class ConsoleAction
extends AnAction
implements DumbAware
{
972 public void actionPerformed(final AnActionEvent e
) {
973 final DataContext context
= e
.getDataContext();
974 final ConsoleViewImpl console
= getRunningConsole(context
);
975 execute(console
, context
);
978 protected abstract void execute(ConsoleViewImpl console
, final DataContext context
);
980 public void update(final AnActionEvent e
) {
981 final ConsoleViewImpl console
= getRunningConsole(e
.getDataContext());
982 e
.getPresentation().setEnabled(console
!= null);
986 private static ConsoleViewImpl
getRunningConsole(final DataContext context
) {
987 final Editor editor
= PlatformDataKeys
.EDITOR
.getData(context
);
988 if (editor
!= null) {
989 final ConsoleViewImpl console
= editor
.getUserData(CONSOLE_VIEW_IN_EDITOR_VIEW
);
990 if (console
!= null && console
.myState
.isRunning()) {
998 private static class EnterHandler
extends ConsoleAction
{
999 public void execute(final ConsoleViewImpl consoleView
, final DataContext context
) {
1000 synchronized (consoleView
.LOCK
) {
1001 String str
= consoleView
.myDeferredUserInput
.toString();
1002 if (StringUtil
.isNotEmpty(str
)) {
1003 consoleView
.myHistory
.remove(str
);
1004 consoleView
.myHistory
.add(str
);
1005 if (consoleView
.myHistory
.size() > consoleView
.myHistorySize
) consoleView
.myHistory
.remove(0);
1007 for (ConsoleInputListener listener
: consoleView
.myConsoleInputListeners
) {
1008 listener
.textEntered(str
);
1011 consoleView
.print("\n", ConsoleViewContentType
.USER_INPUT
);
1012 consoleView
.flushDeferredText();
1013 final Editor editor
= consoleView
.myEditor
;
1014 editor
.getCaretModel().moveToOffset(editor
.getDocument().getTextLength());
1015 editor
.getScrollingModel().scrollToCaret(ScrollType
.RELATIVE
);
1019 private static class PasteHandler
extends ConsoleAction
{
1020 public void execute(final ConsoleViewImpl consoleView
, final DataContext context
) {
1021 final Transferable content
= CopyPasteManager
.getInstance().getContents();
1022 if (content
== null) return;
1025 s
= (String
)content
.getTransferData(DataFlavor
.stringFlavor
);
1027 catch(Exception e
) {
1028 consoleView
.getToolkit().beep();
1030 if (s
== null) return;
1031 Editor editor
= consoleView
.myEditor
;
1032 SelectionModel selectionModel
= editor
.getSelectionModel();
1033 if (selectionModel
.hasSelection()) {
1034 consoleView
.replaceUserText(s
, selectionModel
.getSelectionStart(), selectionModel
.getSelectionEnd());
1036 consoleView
.insertUserText(s
, editor
.getCaretModel().getOffset());
1041 private static class BackSpaceHandler
extends ConsoleAction
{
1042 public void execute(final ConsoleViewImpl consoleView
, final DataContext context
) {
1043 final Editor editor
= consoleView
.myEditor
;
1045 if (IncrementalSearchHandler
.isHintVisible(editor
)) {
1046 getDefaultActionHandler().execute(editor
, context
);
1050 final Document document
= editor
.getDocument();
1051 final int length
= document
.getTextLength();
1056 SelectionModel selectionModel
= editor
.getSelectionModel();
1057 if (selectionModel
.hasSelection()) {
1058 consoleView
.deleteUserText(selectionModel
.getSelectionStart(),
1059 selectionModel
.getSelectionEnd() - selectionModel
.getSelectionStart());
1060 } else if (editor
.getCaretModel().getOffset() > 0) {
1061 consoleView
.deleteUserText(editor
.getCaretModel().getOffset() - 1, 1);
1065 private static EditorActionHandler
getDefaultActionHandler() {
1066 return EditorActionManager
.getInstance().getActionHandler(IdeActions
.ACTION_EDITOR_BACKSPACE
);
1070 private static class DeleteHandler
extends ConsoleAction
{
1071 public void execute(final ConsoleViewImpl consoleView
, final DataContext context
) {
1072 final Editor editor
= consoleView
.myEditor
;
1074 if (IncrementalSearchHandler
.isHintVisible(editor
)) {
1075 getDefaultActionHandler().execute(editor
, context
);
1079 final Document document
= editor
.getDocument();
1080 final int length
= document
.getTextLength();
1085 SelectionModel selectionModel
= editor
.getSelectionModel();
1086 if (selectionModel
.hasSelection()) {
1087 consoleView
.deleteUserText(selectionModel
.getSelectionStart(),
1088 selectionModel
.getSelectionEnd() - selectionModel
.getSelectionStart());
1090 consoleView
.deleteUserText(editor
.getCaretModel().getOffset(), 1);
1094 private static EditorActionHandler
getDefaultActionHandler() {
1095 return EditorActionManager
.getInstance().getActionHandler(IdeActions
.ACTION_EDITOR_BACKSPACE
);
1099 private static class Hyperlinks
{
1100 private static final int NO_INDEX
= Integer
.MIN_VALUE
;
1101 private final Map
<RangeHighlighter
,HyperlinkInfo
> myHighlighterToMessageInfoMap
= new HashMap
<RangeHighlighter
, HyperlinkInfo
>();
1102 private int myLastIndex
= NO_INDEX
;
1104 public void clear() {
1105 myHighlighterToMessageInfoMap
.clear();
1106 myLastIndex
= NO_INDEX
;
1109 public HyperlinkInfo
getHyperlinkAt(final int offset
) {
1110 for (final RangeHighlighter highlighter
: myHighlighterToMessageInfoMap
.keySet()) {
1111 if (highlighter
.isValid() && containsOffset(offset
, highlighter
)) {
1112 return myHighlighterToMessageInfoMap
.get(highlighter
);
1118 private static boolean containsOffset(final int offset
, final RangeHighlighter highlighter
) {
1119 return highlighter
.getStartOffset() <= offset
&& offset
<= highlighter
.getEndOffset();
1122 public void add(final RangeHighlighter highlighter
, final HyperlinkInfo hyperlinkInfo
) {
1123 myHighlighterToMessageInfoMap
.put(highlighter
, hyperlinkInfo
);
1124 if (myLastIndex
!= NO_INDEX
&& containsOffset(myLastIndex
, highlighter
)) myLastIndex
= NO_INDEX
;
1127 private Map
<RangeHighlighter
,HyperlinkInfo
> getRanges() {
1128 return myHighlighterToMessageInfoMap
;
1132 public JComponent
getPreferredFocusableComponent() {
1133 //ensure editor created
1135 return myEditor
.getContentComponent();
1139 // navigate up/down in stack trace
1140 public boolean hasNextOccurence() {
1141 return next(1, false) != null;
1144 public boolean hasPreviousOccurence() {
1145 return next(-1, false) != null;
1148 public OccurenceInfo
goNextOccurence() {
1149 return next(1, true);
1152 private OccurenceInfo
next(final int delta
, boolean doMove
) {
1153 List
<RangeHighlighter
> ranges
= new ArrayList
<RangeHighlighter
>(myHyperlinks
.getRanges().keySet());
1154 Collections
.sort(ranges
, new Comparator
<RangeHighlighter
>() {
1155 public int compare(final RangeHighlighter o1
, final RangeHighlighter o2
) {
1156 return o1
.getStartOffset() - o2
.getStartOffset();
1160 for (i
= 0; i
<ranges
.size(); i
++) {
1161 RangeHighlighter range
= ranges
.get(i
);
1162 if (range
.getUserData(OLD_HYPERLINK_TEXT_ATTRIBUTES
) != null) {
1166 int newIndex
= ranges
.isEmpty() ?
-1 : i
== ranges
.size() ?
0 : (i
+ delta
+ ranges
.size()) % ranges
.size();
1167 RangeHighlighter next
= newIndex
< ranges
.size() && newIndex
>= 0 ? ranges
.get(newIndex
) : null;
1168 if (next
== null) return null;
1170 scrollTo(next
.getStartOffset());
1172 final HyperlinkInfo hyperlinkInfo
= myHyperlinks
.getRanges().get(next
);
1173 return new OccurenceInfo(new Navigatable() {
1174 public void navigate(final boolean requestFocus
) {
1175 hyperlinkInfo
.navigate(myProject
);
1176 linkFollowed(hyperlinkInfo
);
1179 public boolean canNavigate() {
1183 public boolean canNavigateToSource() {
1186 }, i
, ranges
.size());
1189 public OccurenceInfo
goPreviousOccurence() {
1190 return next(-1, true);
1193 public String
getNextOccurenceActionName() {
1194 return ExecutionBundle
.message("down.the.stack.trace");
1197 public String
getPreviousOccurenceActionName() {
1198 return ExecutionBundle
.message("up.the.stack.trace");
1201 public void addCustomConsoleAction(@NotNull AnAction action
) {
1202 customActions
.add(action
);
1206 public AnAction
[] createConsoleActions() {
1207 //Initializing prev and next occurrences actions
1208 CommonActionsManager actionsManager
= CommonActionsManager
.getInstance();
1209 AnAction prevAction
= actionsManager
.createPrevOccurenceAction(this);
1210 prevAction
.getTemplatePresentation().setText(getPreviousOccurenceActionName());
1211 AnAction nextAction
= actionsManager
.createNextOccurenceAction(this);
1212 nextAction
.getTemplatePresentation().setText(getNextOccurenceActionName());
1213 //Initializing custom actions
1214 AnAction
[] consoleActions
= new AnAction
[2 + customActions
.size()];
1215 consoleActions
[0] = prevAction
;
1216 consoleActions
[1] = nextAction
;
1217 for (int i
= 0; i
< customActions
.size(); ++i
) {
1218 consoleActions
[i
+ 2] = customActions
.get(i
);
1220 return consoleActions
;
1223 public void setEditorEnabled(boolean enabled
) {
1224 myEditor
.getContentComponent().setEnabled(enabled
);
1227 private void fireChange() {
1228 if (myDeferredTypes
.isEmpty()) return;
1229 Collection
<ConsoleViewContentType
> types
= Collections
.unmodifiableCollection(myDeferredTypes
);
1231 for (ChangeListener each
: myListeners
) {
1232 each
.contentAdded(types
);
1235 myDeferredTypes
.clear();
1238 public void addChangeListener(final ChangeListener listener
, final Disposable parent
) {
1239 myListeners
.add(listener
);
1240 Disposer
.register(parent
, new Disposable() {
1241 public void dispose() {
1242 myListeners
.remove(listener
);
1248 * insert text to document
1249 * @param s inserted text
1250 * @param offset relativly to all document text
1252 private void insertUserText(final String s
, int offset
) {
1253 final ConsoleViewImpl consoleView
= this;
1254 final Editor editor
= consoleView
.myEditor
;
1255 final Document document
= editor
.getDocument();
1256 final int startOffset
;
1258 synchronized (consoleView
.LOCK
) {
1259 if (consoleView
.myTokens
.isEmpty()) return;
1260 final TokenInfo info
= consoleView
.myTokens
.get(consoleView
.myTokens
.size() - 1);
1261 if (info
.contentType
!= ConsoleViewContentType
.USER_INPUT
&& !s
.contains("\n")) {
1262 consoleView
.print(s
, ConsoleViewContentType
.USER_INPUT
);
1263 consoleView
.flushDeferredText();
1264 editor
.getCaretModel().moveToOffset(document
.getTextLength());
1265 editor
.getSelectionModel().removeSelection();
1267 } else if (info
.contentType
!= ConsoleViewContentType
.USER_INPUT
) {
1268 insertUserText("temp", offset
);
1269 final TokenInfo newInfo
= consoleView
.myTokens
.get(consoleView
.myTokens
.size() - 1);
1270 replaceUserText(s
, newInfo
.startOffset
, newInfo
.endOffset
);
1276 if (offset
> info
.endOffset
) {
1277 startOffset
= info
.endOffset
;
1279 else if (offset
< info
.startOffset
) {
1280 startOffset
= info
.startOffset
;
1282 startOffset
= offset
;
1284 charCountToAdd
= s
.length();
1286 if (consoleView
.myDeferredUserInput
.length() < info
.endOffset
- info
.startOffset
) return; //user was quick
1288 consoleView
.myDeferredUserInput
.insert(startOffset
- info
.startOffset
, s
);
1290 info
.endOffset
+= charCountToAdd
;
1291 consoleView
.myContentSize
+= charCountToAdd
;
1294 document
.insertString(startOffset
, s
);
1295 editor
.getCaretModel().moveToOffset(startOffset
+ s
.length());
1296 editor
.getScrollingModel().scrollToCaret(ScrollType
.RELATIVE
);
1301 * @param s text for replace
1302 * @param start relativly to all document text
1303 * @param end relativly to all document text
1305 private void replaceUserText(final String s
, int start
, int end
) {
1307 insertUserText(s
, start
);
1310 final ConsoleViewImpl consoleView
= this;
1311 final Editor editor
= consoleView
.myEditor
;
1312 final Document document
= editor
.getDocument();
1313 final int startOffset
;
1314 final int endOffset
;
1316 synchronized (consoleView
.LOCK
) {
1317 if (consoleView
.myTokens
.isEmpty()) return;
1318 final TokenInfo info
= consoleView
.myTokens
.get(consoleView
.myTokens
.size() - 1);
1319 if (info
.contentType
!= ConsoleViewContentType
.USER_INPUT
) {
1320 consoleView
.print(s
, ConsoleViewContentType
.USER_INPUT
);
1321 consoleView
.flushDeferredText();
1322 editor
.getCaretModel().moveToOffset(document
.getTextLength());
1323 editor
.getSelectionModel().removeSelection();
1326 if (consoleView
.myDeferredUserInput
.length() == 0) return;
1327 int charCountToReplace
;
1329 startOffset
= getStartOffset(start
, info
);
1330 endOffset
= getEndOffset(end
, info
);
1332 if (startOffset
== -1 ||
1334 endOffset
<= startOffset
) {
1335 editor
.getSelectionModel().removeSelection();
1336 editor
.getCaretModel().moveToOffset(start
);
1339 charCountToReplace
= s
.length() - endOffset
+ startOffset
;
1341 if (consoleView
.myDeferredUserInput
.length() < info
.endOffset
- info
.startOffset
) return; //user was quick
1343 consoleView
.myDeferredUserInput
.replace(startOffset
- info
.startOffset
, endOffset
- info
.startOffset
, s
);
1345 info
.endOffset
+= charCountToReplace
;
1346 if (info
.startOffset
== info
.endOffset
) {
1347 consoleView
.myTokens
.remove(consoleView
.myTokens
.size() - 1);
1349 consoleView
.myContentSize
+= charCountToReplace
;
1352 document
.replaceString(startOffset
, endOffset
, s
);
1353 editor
.getCaretModel().moveToOffset(startOffset
+ s
.length());
1354 editor
.getScrollingModel().scrollToCaret(ScrollType
.RELATIVE
);
1355 editor
.getSelectionModel().removeSelection();
1360 * @param offset relativly to all document text
1361 * @param length lenght of deleted text
1363 private void deleteUserText(int offset
, int length
) {
1364 ConsoleViewImpl consoleView
= this;
1365 final Editor editor
= consoleView
.myEditor
;
1366 final Document document
= editor
.getDocument();
1367 final int startOffset
;
1368 final int endOffset
;
1370 synchronized (consoleView
.LOCK
) {
1371 if (consoleView
.myTokens
.isEmpty()) return;
1372 final TokenInfo info
= consoleView
.myTokens
.get(consoleView
.myTokens
.size() - 1);
1373 if (info
.contentType
!= ConsoleViewContentType
.USER_INPUT
) return;
1374 if (consoleView
.myDeferredUserInput
.length() == 0) return;
1375 int charCountToDelete
;
1377 startOffset
= getStartOffset(offset
, info
);
1378 endOffset
= getEndOffset(offset
+ length
, info
);
1379 if (startOffset
== -1 ||
1381 endOffset
<= startOffset
) {
1382 editor
.getSelectionModel().removeSelection();
1383 editor
.getCaretModel().moveToOffset(offset
);
1387 consoleView
.myDeferredUserInput
.delete(startOffset
- info
.startOffset
, endOffset
- info
.startOffset
);
1388 charCountToDelete
= endOffset
- startOffset
;
1390 info
.endOffset
-= charCountToDelete
;
1391 if (info
.startOffset
== info
.endOffset
) {
1392 consoleView
.myTokens
.remove(consoleView
.myTokens
.size() - 1);
1394 consoleView
.myContentSize
-= charCountToDelete
;
1397 document
.deleteString(startOffset
, endOffset
);
1398 editor
.getCaretModel().moveToOffset(startOffset
);
1399 editor
.getScrollingModel().scrollToCaret(ScrollType
.RELATIVE
);
1400 editor
.getSelectionModel().removeSelection();
1403 //util methods for add, replace, delete methods
1404 private static int getStartOffset(int offset
, TokenInfo info
) {
1406 if (offset
>= info
.startOffset
&& offset
< info
.endOffset
) {
1407 startOffset
= offset
;
1408 } else if (offset
< info
.startOffset
) {
1409 startOffset
= info
.startOffset
;
1416 private static int getEndOffset(int offset
, TokenInfo info
) {
1418 if (offset
> info
.endOffset
) {
1419 endOffset
= info
.endOffset
;
1420 } else if (offset
<= info
.startOffset
) {