console: fix blank console bug caused by invoking "clear(); print();" while previous...
[idea/community.git] / platform / lang-impl / src / com / intellij / execution / impl / ConsoleViewImpl.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.intellij.execution.impl;
18
19 import com.google.common.base.CharMatcher;
20 import com.intellij.codeInsight.navigation.IncrementalSearchHandler;
21 import com.intellij.codeInsight.template.impl.editorActions.TypedActionHandlerBase;
22 import com.intellij.execution.ConsoleFolding;
23 import com.intellij.execution.ExecutionBundle;
24 import com.intellij.execution.actions.ConsoleActionsPostProcessor;
25 import com.intellij.execution.actions.EOFAction;
26 import com.intellij.execution.filters.*;
27 import com.intellij.execution.process.ProcessHandler;
28 import com.intellij.execution.ui.ConsoleView;
29 import com.intellij.execution.ui.ConsoleViewContentType;
30 import com.intellij.execution.ui.ObservableConsoleView;
31 import com.intellij.icons.AllIcons;
32 import com.intellij.ide.CommonActionsManager;
33 import com.intellij.ide.OccurenceNavigator;
34 import com.intellij.openapi.Disposable;
35 import com.intellij.openapi.actionSystem.*;
36 import com.intellij.openapi.application.ApplicationManager;
37 import com.intellij.openapi.application.ModalityState;
38 import com.intellij.openapi.command.CommandProcessor;
39 import com.intellij.openapi.diagnostic.Logger;
40 import com.intellij.openapi.editor.*;
41 import com.intellij.openapi.editor.actionSystem.*;
42 import com.intellij.openapi.editor.actions.ScrollToTheEndToolbarAction;
43 import com.intellij.openapi.editor.actions.ToggleUseSoftWrapsToolbarAction;
44 import com.intellij.openapi.editor.colors.EditorColorsScheme;
45 import com.intellij.openapi.editor.event.*;
46 import com.intellij.openapi.editor.ex.DocumentEx;
47 import com.intellij.openapi.editor.ex.EditorEx;
48 import com.intellij.openapi.editor.ex.util.EditorUtil;
49 import com.intellij.openapi.editor.highlighter.EditorHighlighter;
50 import com.intellij.openapi.editor.highlighter.HighlighterClient;
51 import com.intellij.openapi.editor.highlighter.HighlighterIterator;
52 import com.intellij.openapi.editor.impl.DocumentImpl;
53 import com.intellij.openapi.editor.impl.softwrap.SoftWrapAppliancePlaces;
54 import com.intellij.openapi.editor.markup.RangeHighlighter;
55 import com.intellij.openapi.editor.markup.TextAttributes;
56 import com.intellij.openapi.extensions.Extensions;
57 import com.intellij.openapi.fileEditor.OpenFileDescriptor;
58 import com.intellij.openapi.ide.CopyPasteManager;
59 import com.intellij.openapi.keymap.Keymap;
60 import com.intellij.openapi.keymap.KeymapManager;
61 import com.intellij.openapi.project.DumbAware;
62 import com.intellij.openapi.project.DumbAwareAction;
63 import com.intellij.openapi.project.DumbService;
64 import com.intellij.openapi.project.Project;
65 import com.intellij.openapi.util.*;
66 import com.intellij.openapi.util.registry.Registry;
67 import com.intellij.openapi.util.text.StringUtil;
68 import com.intellij.psi.search.GlobalSearchScope;
69 import com.intellij.psi.tree.IElementType;
70 import com.intellij.ui.EditorNotificationPanel;
71 import com.intellij.ui.awt.RelativePoint;
72 import com.intellij.util.*;
73 import com.intellij.util.text.CharArrayUtil;
74 import com.intellij.util.ui.UIUtil;
75 import gnu.trove.TIntObjectHashMap;
76 import org.jetbrains.annotations.NonNls;
77 import org.jetbrains.annotations.NotNull;
78 import org.jetbrains.annotations.Nullable;
79
80 import javax.swing.*;
81 import java.awt.*;
82 import java.awt.datatransfer.DataFlavor;
83 import java.awt.event.MouseAdapter;
84 import java.awt.event.MouseEvent;
85 import java.awt.event.MouseWheelEvent;
86 import java.io.IOException;
87 import java.util.*;
88 import java.util.List;
89 import java.util.concurrent.CopyOnWriteArraySet;
90
91 public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableConsoleView, DataProvider, OccurenceNavigator {
92   @NonNls private static final String CONSOLE_VIEW_POPUP_MENU = "ConsoleView.PopupMenu";
93   private static final Logger LOG = Logger.getInstance("#com.intellij.execution.impl.ConsoleViewImpl");
94
95   private static final int DEFAULT_FLUSH_DELAY = SystemProperties.getIntProperty("console.flush.delay.ms", 200);
96
97   private static final CharMatcher NEW_LINE_MATCHER = CharMatcher.anyOf("\n\r");
98
99   public static final Key<ConsoleViewImpl> CONSOLE_VIEW_IN_EDITOR_VIEW = Key.create("CONSOLE_VIEW_IN_EDITOR_VIEW");
100
101   private static boolean ourTypedHandlerInitialized;
102
103   private static synchronized void initTypedHandler() {
104     if (ourTypedHandlerInitialized) return;
105     final EditorActionManager actionManager = EditorActionManager.getInstance();
106     final TypedAction typedAction = actionManager.getTypedAction();
107     typedAction.setupHandler(new MyTypedHandler(typedAction.getHandler()));
108     ourTypedHandlerInitialized = true;
109   }
110
111
112   private final CommandLineFolding myCommandLineFolding = new CommandLineFolding();
113
114   private final DisposedPsiManagerCheck myPsiDisposedCheck;
115   private final boolean myIsViewer;
116
117   private ConsoleState myState;
118
119   private final Alarm mySpareTimeAlarm = new Alarm(this);
120   @Nullable
121   private final Alarm myHeavyAlarm;
122   private volatile int myHeavyUpdateTicket;
123
124   private final Collection<ChangeListener> myListeners = new CopyOnWriteArraySet<ChangeListener>();
125   private final List<AnAction> customActions = new ArrayList<AnAction>();
126   private final ConsoleBuffer myBuffer = new ConsoleBuffer();
127   private boolean myUpdateFoldingsEnabled = true;
128   private EditorHyperlinkSupport myHyperlinks;
129   private MyDiffContainer myJLayeredPane;
130   private JPanel myMainPanel;
131   private boolean myAllowHeavyFilters;
132   private boolean myLastStickingToEnd;
133   private boolean myCancelStickToEnd;
134
135   private boolean myTooMuchOfOutput;
136   private boolean myInDocumentUpdate;
137
138   // If true, then a document is being cleared right now.
139   // Should be accessed in EDT only.
140   @SuppressWarnings("FieldAccessedSynchronizedAndUnsynchronized")
141   private boolean myDocumentClearing;
142   private int myLastAddedTextLength;
143   private int consoleTooMuchTextBufferRatio;
144
145   public Editor getEditor() {
146     return myEditor;
147   }
148
149   public EditorHyperlinkSupport getHyperlinks() {
150     return myHyperlinks;
151   }
152
153   public void scrollToEnd() {
154     if (myEditor == null) return;
155     EditorUtil.scrollToTheEnd(myEditor);
156     myCancelStickToEnd = false;
157   }
158
159   public void foldImmediately() {
160     ApplicationManager.getApplication().assertIsDispatchThread();
161     if (!myFlushAlarm.isEmpty()) {
162       cancelAllFlushRequests();
163       new MyFlushRunnable().run();
164     }
165
166     myFoldingAlarm.cancelAllRequests();
167
168     myPendingFoldRegions.clear();
169     final FoldingModel model = myEditor.getFoldingModel();
170     model.runBatchFoldingOperation(new Runnable() {
171       @Override
172       public void run() {
173         for (FoldRegion region : model.getAllFoldRegions()) {
174           model.removeFoldRegion(region);
175         }
176       }
177     });
178     myFolding.clear();
179
180     updateFoldings(0, myEditor.getDocument().getLineCount() - 1, true);
181   }
182
183   static class TokenInfo {
184     final ConsoleViewContentType contentType;
185     int startOffset;
186     int endOffset;
187
188     TokenInfo(ConsoleViewContentType contentType, int startOffset, int endOffset) {
189       this.contentType = contentType;
190       this.startOffset = startOffset;
191       this.endOffset = endOffset;
192     }
193
194     public int getLength() {
195       return endOffset - startOffset;
196     }
197
198     @Override
199     public String toString() {
200       return contentType + "[" + startOffset + ";" + endOffset + "]";
201     }
202
203     @Nullable
204     public HyperlinkInfo getHyperlinkInfo() {
205       return null;
206     }
207   }
208
209   static class HyperlinkTokenInfo extends TokenInfo {
210     private final HyperlinkInfo myHyperlinkInfo;
211
212     HyperlinkTokenInfo(final ConsoleViewContentType contentType, final int startOffset, final int endOffset, HyperlinkInfo hyperlinkInfo) {
213       super(contentType, startOffset, endOffset);
214       myHyperlinkInfo = hyperlinkInfo;
215     }
216
217     @Override
218     public HyperlinkInfo getHyperlinkInfo() {
219       return myHyperlinkInfo;
220     }
221   }
222
223   private final Project myProject;
224
225   private boolean myOutputPaused;
226
227   private EditorEx myEditor;
228
229   private final Object LOCK = new Object();
230
231   /**
232    * Holds number of symbols managed by the current console.
233    * <p/>
234    * Total number is assembled as a sum of symbols that are already pushed to the document and number of deferred symbols that
235    * are awaiting to be pushed to the document.
236    */
237   private int myContentSize;
238
239   /**
240    * Holds information about lexical division by offsets of the text already pushed to document.
241    * <p/>
242    * Target offsets are anchored to the document here.
243    */
244   private final List<TokenInfo> myTokens = new ArrayList<TokenInfo>();
245
246   private final TIntObjectHashMap<ConsoleFolding> myFolding = new TIntObjectHashMap<ConsoleFolding>();
247
248   private String myHelpId;
249
250   private final Alarm myFlushUserInputAlarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, this);
251   private final Alarm myFlushAlarm = new Alarm(this);
252
253   private final Set<MyFlushRunnable> myCurrentRequests = new HashSet<MyFlushRunnable>();
254
255   protected final CompositeFilter myFilters;
256
257   @Nullable private final InputFilter myInputMessageFilter;
258
259   private final Alarm myFoldingAlarm = new Alarm(this);
260   private final List<FoldRegion> myPendingFoldRegions = new ArrayList<FoldRegion>();
261
262   public ConsoleViewImpl(final Project project, boolean viewer) {
263     this(project, GlobalSearchScope.allScope(project), viewer, true);
264   }
265
266   public ConsoleViewImpl(@NotNull final Project project,
267                          @NotNull GlobalSearchScope searchScope,
268                          boolean viewer,
269                          boolean usePredefinedMessageFilter) {
270     this(project, searchScope, viewer,
271          new ConsoleState.NotStartedStated() {
272            @Override
273            public ConsoleState attachTo(ConsoleViewImpl console, ProcessHandler processHandler) {
274              return new ConsoleViewRunningState(console, processHandler, this, true, true);
275            }
276          },
277          usePredefinedMessageFilter);
278   }
279
280   protected ConsoleViewImpl(@NotNull final Project project,
281                             @NotNull GlobalSearchScope searchScope,
282                             boolean viewer,
283                             @NotNull final ConsoleState initialState,
284                             boolean usePredefinedMessageFilter)
285   {
286     super(new BorderLayout());
287     initTypedHandler();
288     myIsViewer = viewer;
289     myState = initialState;
290     myPsiDisposedCheck = new DisposedPsiManagerCheck(project);
291     myProject = project;
292
293     myFilters = new CompositeFilter(project);
294     if (usePredefinedMessageFilter) {
295       for (ConsoleFilterProvider eachProvider : Extensions.getExtensions(ConsoleFilterProvider.FILTER_PROVIDERS)) {
296         Filter[] filters;
297         if (eachProvider instanceof ConsoleDependentFilterProvider) {
298           filters = ((ConsoleDependentFilterProvider)eachProvider).getDefaultFilters(this, project, searchScope);
299         }
300         else if (eachProvider instanceof ConsoleFilterProviderEx) {
301           filters = ((ConsoleFilterProviderEx)eachProvider).getDefaultFilters(project, searchScope);
302         }
303         else {
304           filters = eachProvider.getDefaultFilters(project);
305         }
306         for (Filter filter : filters) {
307           myFilters.addFilter(filter);
308         }
309       }
310     }
311     myFilters.setForceUseAllFilters(true);
312     myHeavyUpdateTicket = 0;
313     myHeavyAlarm = myFilters.isAnyHeavy() ? new Alarm(Alarm.ThreadToUse.POOLED_THREAD, this) : null;
314
315
316     ConsoleInputFilterProvider[] inputFilters = Extensions.getExtensions(ConsoleInputFilterProvider.INPUT_FILTER_PROVIDERS);
317     if (inputFilters.length > 0) {
318       CompositeInputFilter compositeInputFilter = new CompositeInputFilter(project);
319       myInputMessageFilter = compositeInputFilter;
320       for (ConsoleInputFilterProvider eachProvider : inputFilters) {
321         InputFilter[] filters = eachProvider.getDefaultFilters(project);
322         for (InputFilter filter : filters) {
323           compositeInputFilter.addFilter(filter);
324         }
325       }
326     }
327     else {
328       myInputMessageFilter = null;
329     }
330
331     consoleTooMuchTextBufferRatio = Registry.intValue("console.too.much.text.buffer.ratio");
332
333     project.getMessageBus().connect(this).subscribe(DumbService.DUMB_MODE, new DumbService.DumbModeListener() {
334       private long myLastStamp;
335
336       @Override
337       public void enteredDumbMode() {
338         if (myEditor == null) return;
339         myLastStamp = myEditor.getDocument().getModificationStamp();
340
341       }
342
343       @Override
344       public void exitDumbMode() {
345         ApplicationManager.getApplication().invokeLater(new Runnable() {
346           @Override
347           public void run() {
348             if (myEditor == null || project.isDisposed() || DumbService.getInstance(project).isDumb()) return;
349             
350             DocumentEx document = myEditor.getDocument();
351             if (myLastStamp != document.getModificationStamp()) {
352               clearHyperlinkAndFoldings();
353               highlightHyperlinksAndFoldings(document.createRangeMarker(0, 0));
354             }
355           }
356         });
357       }
358     });
359
360   }
361
362   @Override
363   public void attachToProcess(final ProcessHandler processHandler) {
364     myState = myState.attachTo(this, processHandler);
365   }
366
367   @Override
368   public void clear() {
369     if (myEditor == null) return;
370     synchronized (LOCK) {
371       // real document content will be cleared on next flush;
372       myContentSize = 0;
373       myBuffer.clear();
374       myFolding.clear();
375     }
376     if (myFlushAlarm.isDisposed()) return;
377     cancelAllFlushRequests();
378     addFlushRequest(new MyClearRunnable());
379     cancelHeavyAlarm();
380   }
381
382   @Override
383   public void scrollTo(final int offset) {
384     if (myEditor == null) return;
385     class ScrollRunnable extends MyFlushRunnable {
386       private final int myOffset = offset;
387
388       @Override
389       public void doRun() {
390         flushDeferredText();
391         if (myEditor == null) return;
392         int moveOffset = Math.min(offset, myEditor.getDocument().getTextLength());
393         if (myBuffer.isUseCyclicBuffer() && moveOffset >= myEditor.getDocument().getTextLength()) {
394           moveOffset = 0;
395         }
396         myEditor.getCaretModel().moveToOffset(moveOffset);
397         myEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
398       }
399
400       @Override
401       public boolean equals(Object o) {
402         return super.equals(o) && myOffset == ((ScrollRunnable)o).myOffset;
403       }
404     }
405     addFlushRequest(new ScrollRunnable());
406   }
407
408   public void requestScrollingToEnd() {
409     if (myEditor == null) {
410       return;
411     }
412
413     addFlushRequest(new MyFlushRunnable() {
414       @Override
415       public void doRun() {
416         flushDeferredText();
417         if (myEditor == null || myFlushAlarm.isDisposed()) {
418           return;
419         }
420
421         scrollToEnd();
422       }
423     });
424   }
425
426   private void addFlushRequest(MyFlushRunnable scrollRunnable) {
427     addFlushRequest(scrollRunnable, 0);
428   }
429
430   private void addFlushRequest(@NotNull MyFlushRunnable flushRunnable, final int millis) {
431     synchronized (myCurrentRequests) {
432       if (!myFlushAlarm.isDisposed() && myCurrentRequests.add(flushRunnable)) {
433         myFlushAlarm.addRequest(flushRunnable, millis, getStateForUpdate());
434       }
435     }
436   }
437
438
439   private static void assertIsDispatchThread() {
440     ApplicationManager.getApplication().assertIsDispatchThread();
441   }
442
443   @Override
444   public void setOutputPaused(final boolean value) {
445     myOutputPaused = value;
446     if (!value) {
447       requestFlushImmediately();
448     }
449   }
450
451   @Override
452   public boolean isOutputPaused() {
453     return myOutputPaused;
454   }
455
456   @Override
457   public boolean hasDeferredOutput() {
458     synchronized (LOCK) {
459       return myBuffer.getLength() > 0;
460     }
461   }
462
463   @Override
464   public void performWhenNoDeferredOutput(final Runnable runnable) {
465     //Q: implement in another way without timer?
466     if (!hasDeferredOutput()) {
467       runnable.run();
468     }
469     else {
470       performLaterWhenNoDeferredOutput(runnable);
471     }
472   }
473
474   private void performLaterWhenNoDeferredOutput(final Runnable runnable) {
475     if (mySpareTimeAlarm.isDisposed()) return;
476     mySpareTimeAlarm.addRequest(
477       new Runnable() {
478         @Override
479         public void run() {
480           performWhenNoDeferredOutput(runnable);
481         }
482       },
483       100,
484       ModalityState.stateForComponent(myJLayeredPane)
485     );
486   }
487
488   @Override
489   public JComponent getComponent() {
490     if (myMainPanel == null) {
491       myMainPanel = new JPanel(new BorderLayout());
492       myJLayeredPane = new MyDiffContainer(myMainPanel, myFilters.getUpdateMessage());
493       Disposer.register(this, myJLayeredPane);
494       add(myJLayeredPane, BorderLayout.CENTER);
495     }
496
497     if (myEditor == null) {
498       initConsoleEditor();
499       requestFlushImmediately();
500       myMainPanel.add(createCenterComponent(), BorderLayout.CENTER);
501     }
502     return this;
503   }
504
505   /**
506    * Adds transparent (actually, non-opaque) component over console.
507    * It will be as big as console. Use it to draw on console because it does not prevent user from console usage.
508    *
509    * @param component component to add
510    */
511   public final void addLayerToPane(@NotNull final JComponent component) {
512     getComponent(); // Make sure component exists
513     component.setOpaque(false);
514     component.setVisible(true);
515     myJLayeredPane.add(component, 0);
516   }
517
518   protected void initConsoleEditor() {
519     myEditor = createConsoleEditor();
520     registerConsoleEditorActions();
521     myEditor.getScrollPane().setBorder(null);
522     MouseAdapter mouseListener = new MouseAdapter() {
523       @Override
524       public void mousePressed(MouseEvent e) {
525         updateStickToEndState(true);
526       }
527
528       @Override
529       public void mouseDragged(MouseEvent e) {
530         updateStickToEndState(false);
531       }
532       
533       @Override
534       public void mouseWheelMoved(MouseWheelEvent e) {
535         updateStickToEndState(false);
536       }
537     };
538     myEditor.getScrollPane().addMouseWheelListener(mouseListener);
539     myEditor.getScrollPane().getVerticalScrollBar().addMouseListener(mouseListener);
540     myEditor.getScrollPane().getVerticalScrollBar().addMouseMotionListener(mouseListener);
541     myHyperlinks = new EditorHyperlinkSupport(myEditor, myProject);
542     myEditor.getScrollingModel().addVisibleAreaListener(new VisibleAreaListener() {
543       @Override
544       public void visibleAreaChanged(VisibleAreaEvent e) {
545         // There is a possible case that the console text is populated while the console is not shown (e.g. we're debugging and
546         // 'Debugger' tab is active while 'Console' is not). It's also possible that newly added text contains long lines that
547         // are soft wrapped. We want to update viewport position then when the console becomes visible.
548         Rectangle oldR = e.getOldRectangle();
549
550         if (oldR != null && oldR.height <= 0 &&
551             e.getNewRectangle().height > 0 &&
552             isStickingToEnd()) {
553           scrollToEnd();
554         }
555       }
556     });
557   }
558
559   private void updateStickToEndState(boolean useImmediatePosition) {
560     if (myEditor == null) return;
561
562     JScrollBar scrollBar = myEditor.getScrollPane().getVerticalScrollBar();
563     int scrollBarPosition = useImmediatePosition ? scrollBar.getValue() : 
564                             myEditor.getScrollingModel().getVisibleAreaOnScrollingFinished().y;
565     boolean vscrollAtBottom = scrollBarPosition == scrollBar.getMaximum() - scrollBar.getVisibleAmount();
566     boolean stickingToEnd = isStickingToEnd();
567
568     if (!vscrollAtBottom && stickingToEnd) {
569       myCancelStickToEnd = true;
570     } else if (vscrollAtBottom && !stickingToEnd) {
571       scrollToEnd();
572     }
573   }
574
575   protected JComponent createCenterComponent() {
576     return myEditor.getComponent();
577   }
578
579   @Override
580   public void dispose() {
581     myState = myState.dispose();
582     if (myEditor != null) {
583       cancelAllFlushRequests();
584       mySpareTimeAlarm.cancelAllRequests();
585       disposeEditor();
586       synchronized (LOCK) {
587         myBuffer.clear();
588       }
589       myEditor = null;
590       myHyperlinks = null;
591     }
592   }
593
594   private void cancelAllFlushRequests() {
595     synchronized (myCurrentRequests) {
596       for (MyFlushRunnable request : myCurrentRequests) {
597         request.invalidate();
598       }
599       myCurrentRequests.clear();
600       myFlushAlarm.cancelAllRequests();
601     }
602   }
603
604   protected void disposeEditor() {
605     UIUtil.invokeAndWaitIfNeeded(new Runnable() {
606       @Override
607       public void run() {
608         if (!myEditor.isDisposed()) {
609           EditorFactory.getInstance().releaseEditor(myEditor);
610         }
611       }
612     });
613   }
614
615   @Override
616   public void print(@NotNull String s, @NotNull ConsoleViewContentType contentType) {
617     if (myInputMessageFilter == null) {
618       printHyperlink(s, contentType, null);
619       return;
620     }
621
622     List<Pair<String, ConsoleViewContentType>> result = myInputMessageFilter.applyFilter(s, contentType);
623     if (result == null) {
624       printHyperlink(s, contentType, null);
625     }
626     else {
627       for (Pair<String, ConsoleViewContentType> pair : result) {
628         if (pair.first != null) {
629           printHyperlink(pair.first, pair.second == null ? contentType : pair.second, null);
630         }
631       }
632     }
633   }
634
635   private void printHyperlink(@NotNull String s, @NotNull ConsoleViewContentType contentType, @Nullable HyperlinkInfo info) {
636     synchronized (LOCK) {
637       Pair<String, Integer> pair = myBuffer.print(s, contentType, info);
638       s = pair.first;
639       myContentSize += s.length() - pair.second;
640
641       if (contentType == ConsoleViewContentType.USER_INPUT && NEW_LINE_MATCHER.indexIn(s) >= 0) {
642         flushDeferredUserInput();
643       }
644       if (myEditor != null) {
645         final boolean shouldFlushNow = myBuffer.isUseCyclicBuffer() && myBuffer.getLength() >= myBuffer.getCyclicBufferSize();
646         addFlushRequest(new MyFlushRunnable(), shouldFlushNow ? 0 : DEFAULT_FLUSH_DELAY);
647       }
648     }
649   }
650
651   private void addToken(int length, @Nullable HyperlinkInfo info, ConsoleViewContentType contentType) {
652     ConsoleUtil.addToken(length, info, contentType, myTokens);
653   }
654
655   private static ModalityState getStateForUpdate() {
656     return null;//myStateForUpdate != null ? myStateForUpdate.compute() : ModalityState.stateForComponent(this);
657   }
658
659   private void requestFlushImmediately() {
660     if (myEditor != null) {
661       addFlushRequest(new MyFlushRunnable());
662     }
663   }
664
665   @Override
666   public int getContentSize() {
667     synchronized (LOCK) {
668       return myContentSize;
669     }
670   }
671
672   @Override
673   public boolean canPause() {
674     return true;
675   }
676
677   public void flushDeferredText() {
678     flushDeferredText(false);
679   }
680
681   private void flushDeferredText(boolean clear) {
682     ApplicationManager.getApplication().assertIsDispatchThread();
683     if (myProject.isDisposed()) {
684       return;
685     }
686     EditorEx editor = myEditor;
687     if (editor == null) {
688       //already disposed
689       return;
690     }
691     final boolean shouldStickToEnd = clear || !myCancelStickToEnd && isStickingToEnd();
692     myCancelStickToEnd = false; // Cancel only needs to last for one update. Next time, isStickingToEnd() will be false.
693     if (clear) {
694       final DocumentEx document = editor.getDocument();
695       synchronized (LOCK) {
696         myTokens.clear();
697         clearHyperlinkAndFoldings();
698       }
699       final int documentTextLength = document.getTextLength();
700       if (documentTextLength > 0) {
701         CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
702           @Override
703           public void run() {
704             document.setInBulkUpdate(true);
705             try {
706               myInDocumentUpdate = true;
707               myDocumentClearing = true;
708               document.deleteString(0, documentTextLength);
709             }
710             finally {
711               document.setInBulkUpdate(false);
712               myDocumentClearing = false;
713               myInDocumentUpdate = false;
714             }
715           }
716         }, null, DocCommandGroupId.noneGroupId(document));
717       }
718       return;
719     }
720
721
722     final String addedText;
723     final Collection<ConsoleViewContentType> contentTypes;
724     int deferredTokensSize;
725     synchronized (LOCK) {
726       if (myOutputPaused) return;
727       if (myBuffer.isEmpty()) return;
728
729       addedText = myBuffer.getText();
730
731       contentTypes = Collections.unmodifiableCollection(new HashSet<ConsoleViewContentType>(myBuffer.getDeferredTokenTypes()));
732       List<TokenInfo> deferredTokens = myBuffer.getDeferredTokens();
733       for (TokenInfo deferredToken : deferredTokens) {
734         addToken(deferredToken.getLength(), deferredToken.getHyperlinkInfo(), deferredToken.contentType);
735       }
736       deferredTokensSize = deferredTokens.size();
737       myBuffer.clear(false);
738       cancelHeavyAlarm();
739     }
740     final Document document = myEditor.getDocument();
741     final RangeMarker lastProcessedOutput = document.createRangeMarker(document.getTextLength(), document.getTextLength());
742
743     CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
744       @Override
745       public void run() {
746         if (!shouldStickToEnd) {
747           myEditor.getScrollingModel().accumulateViewportChanges();
748         }
749         try {
750           myInDocumentUpdate = true;
751           String[] strings = addedText.split("\\r", -1); // limit must be any negative number to avoid discarding of trailing empty strings
752           for (int i = 0; i < strings.length - 1; i++) {
753             document.insertString(document.getTextLength(), strings[i]);
754             int lastLine = document.getLineCount() - 1;
755             if (lastLine >= 0) {
756               ConsoleUtil.updateTokensOnTextRemoval(myTokens, document.getTextLength(), document.getTextLength() + 1);
757               document.deleteString(document.getLineStartOffset(lastLine), document.getTextLength());
758             }
759           }
760           if (strings.length > 0) {
761             document.insertString(document.getTextLength(), strings[strings.length - 1]);
762             myContentSize -= strings.length - 1;
763           }
764         }
765         finally {
766           myInDocumentUpdate = false;
767           if (!shouldStickToEnd) {
768             myEditor.getScrollingModel().flushViewportChanges();
769           }
770         }
771         if (!contentTypes.isEmpty()) {
772           for (ChangeListener each : myListeners) {
773             each.contentAdded(contentTypes);
774           }
775         }
776       }
777     }, null, DocCommandGroupId.noneGroupId(document));
778     synchronized (LOCK) {
779       for (int i = myTokens.size() - 1; i >= 0 && deferredTokensSize > 0; i--, deferredTokensSize--) {
780         TokenInfo token = myTokens.get(i);
781         final HyperlinkInfo info = token.getHyperlinkInfo();
782         if (info != null) {
783           myHyperlinks.createHyperlink(token.startOffset, token.endOffset, null, info);
784         }
785       }
786     }
787     myPsiDisposedCheck.performCheck();
788     myLastAddedTextLength = addedText.length();
789     if (!myTooMuchOfOutput) {
790       if (isTheAmountOfTextTooBig(myLastAddedTextLength)) { // disable hyperlinks and folding until new output arriving slows down again
791         myTooMuchOfOutput = true;
792         final EditorNotificationPanel comp =
793           new EditorNotificationPanel().text("Too much output to process").icon(AllIcons.General.ExclMark);
794         final Alarm tooMuchOutputAlarm = new Alarm();
795         //show the notification with a delay to avoid blinking when "too much output" ceases quickly
796         tooMuchOutputAlarm.addRequest(new Runnable() {
797           @Override
798           public void run() {
799             add(comp, BorderLayout.NORTH);
800           }
801         }, 300);
802         performWhenNoDeferredOutput(new Runnable() {
803           @Override
804           public void run() {
805             if (!isTheAmountOfTextTooBig(myLastAddedTextLength)) {
806               try {
807                 highlightHyperlinksAndFoldings(lastProcessedOutput);
808               }
809               finally {
810                 myTooMuchOfOutput = false;
811                 remove(comp);
812                 tooMuchOutputAlarm.cancelAllRequests();
813               }
814             }
815             else {
816               myLastAddedTextLength = 0;
817               performLaterWhenNoDeferredOutput(this);
818             }
819           }
820         });
821       }
822       else {
823         highlightHyperlinksAndFoldings(lastProcessedOutput);
824       }
825     }
826
827     if (shouldStickToEnd) {
828       scrollToEnd();
829     }
830   }
831
832   private boolean isStickingToEnd() {
833     if (myEditor == null) return myLastStickingToEnd;
834     Document document = myEditor.getDocument();
835     int caretOffset = myEditor.getCaretModel().getOffset();
836     myLastStickingToEnd = document.getLineNumber(caretOffset) >= document.getLineCount() - 1;
837     return myLastStickingToEnd;
838   }
839
840   private boolean isTheAmountOfTextTooBig(final int textLength) {
841     return myBuffer.isUseCyclicBuffer() && textLength > myBuffer.getCyclicBufferSize() / consoleTooMuchTextBufferRatio;
842   }
843
844   private void clearHyperlinkAndFoldings() {
845     myEditor.getMarkupModel().removeAllHighlighters();
846
847     myPendingFoldRegions.clear();
848     myFolding.clear();
849     myFoldingAlarm.cancelAllRequests();
850     myEditor.getFoldingModel().runBatchFoldingOperation(new Runnable() {
851       @Override
852       public void run() {
853         myEditor.getFoldingModel().clearFoldRegions();
854       }
855     });
856
857     cancelHeavyAlarm();
858   }
859
860   private void cancelHeavyAlarm() {
861     if (myHeavyAlarm != null && !myHeavyAlarm.isDisposed()) {
862       myHeavyAlarm.cancelAllRequests();
863       ++myHeavyUpdateTicket;
864     }
865   }
866
867   private void flushDeferredUserInput() {
868     final String textToSend = myBuffer.cutFirstUserInputLine();
869     if (textToSend == null) {
870       return;
871     }
872     myFlushUserInputAlarm.addRequest(new Runnable() {
873       @Override
874       public void run() {
875         if (myState.isRunning()) {
876           try {
877             // this may block forever, see IDEA-54340
878             myState.sendUserInput(textToSend);
879           }
880           catch (IOException ignored) {
881           }
882         }
883       }
884     }, 0);
885   }
886
887   @Override
888   public Object getData(final String dataId) {
889     if (CommonDataKeys.NAVIGATABLE.is(dataId)) {
890       if (myEditor == null) {
891         return null;
892       }
893       final LogicalPosition pos = myEditor.getCaretModel().getLogicalPosition();
894       final HyperlinkInfo info = myHyperlinks.getHyperlinkInfoByLineAndCol(pos.line, pos.column);
895       final OpenFileDescriptor openFileDescriptor = info instanceof FileHyperlinkInfo ? ((FileHyperlinkInfo)info).getDescriptor() : null;
896       if (openFileDescriptor == null || !openFileDescriptor.getFile().isValid()) {
897         return null;
898       }
899       return openFileDescriptor;
900     }
901
902     if (CommonDataKeys.EDITOR.is(dataId)) {
903       return myEditor;
904     }
905     if (PlatformDataKeys.HELP_ID.is(dataId)) {
906       return myHelpId;
907     }
908     if (LangDataKeys.CONSOLE_VIEW.is(dataId)) {
909       return this;
910     }
911     return null;
912   }
913
914   @Override
915   public void setHelpId(final String helpId) {
916     myHelpId = helpId;
917   }
918
919   public void setUpdateFoldingsEnabled(boolean updateFoldingsEnabled) {
920     myUpdateFoldingsEnabled = updateFoldingsEnabled;
921   }
922
923   @Override
924   public void addMessageFilter(final Filter filter) {
925     myFilters.addFilter(filter);
926   }
927
928   @Override
929   public void printHyperlink(final String hyperlinkText, final HyperlinkInfo info) {
930     printHyperlink(hyperlinkText, ConsoleViewContentType.NORMAL_OUTPUT, info);
931   }
932
933   private EditorEx createConsoleEditor() {
934     return ApplicationManager.getApplication().runReadAction(new Computable<EditorEx>() {
935       @Override
936       public EditorEx compute() {
937         EditorEx editor = doCreateConsoleEditor();
938         editor.setContextMenuGroupId(null); // disabling default context menu
939         editor.addEditorMouseListener(new EditorPopupHandler() {
940           @Override
941           public void invokePopup(final EditorMouseEvent event) {
942             popupInvoked(event.getMouseEvent());
943           }
944         });
945         editor.getDocument().addDocumentListener(new DocumentAdapter() {
946           @Override
947           public void documentChanged(DocumentEvent event) {
948             onDocumentChanged(event);
949           }
950         }, ConsoleViewImpl.this);
951
952         int bufferSize = myBuffer.isUseCyclicBuffer() ? myBuffer.getCyclicBufferSize() : 0;
953         editor.getDocument().setCyclicBufferSize(bufferSize);
954
955         editor.putUserData(CONSOLE_VIEW_IN_EDITOR_VIEW, ConsoleViewImpl.this);
956
957         editor.getSettings().setAllowSingleLogicalLineFolding(true); // We want to fold long soft-wrapped command lines
958         editor.setHighlighter(createHighlighter());
959
960         return editor;
961       }
962     });
963   }
964
965   private void onDocumentChanged(DocumentEvent event) {
966     if (event.getNewLength() == 0) {
967       // string has been removed, adjust token ranges
968       synchronized (LOCK) {
969         ConsoleUtil.updateTokensOnTextRemoval(myTokens, event.getOffset(), event.getOffset() + event.getOldLength());
970         int toRemoveLen = event.getOldLength();
971         if (!myDocumentClearing) {
972           // If document is being cleared now, then this event has been occurred as a result of calling clear() method.
973           // At start clear() method sets 'myContentSize' to 0, so there is no need to perform update again.
974           // Moreover, performing update of 'myContentSize' breaks executing "console.print();" immediately after "console.clear();".
975           myContentSize -= Math.min(myContentSize, toRemoveLen);
976         }
977       }
978     }
979     else if (!myInDocumentUpdate) {
980       int newFragmentLength = event.getNewFragment().length();
981       // track external appends
982       if (event.getOldFragment().length() == 0 && newFragmentLength > 0) {
983         synchronized (LOCK) {
984           myContentSize += newFragmentLength;
985           addToken(newFragmentLength, null, ConsoleViewContentType.NORMAL_OUTPUT);
986         }
987       }
988       else {
989         LOG.warn("unhandled external change: " + event);
990       }
991     }
992   }
993
994   protected EditorEx doCreateConsoleEditor() {
995     return ConsoleViewUtil.setupConsoleEditor(myProject, true, false);
996   }
997
998   protected MyHighlighter createHighlighter() {
999     return new MyHighlighter();
1000   }
1001
1002   private void registerConsoleEditorActions() {
1003     Shortcut[] shortcuts = KeymapManager.getInstance().getActiveKeymap().getShortcuts(IdeActions.ACTION_GOTO_DECLARATION);
1004     CustomShortcutSet shortcutSet = new CustomShortcutSet(ArrayUtil.mergeArrays(shortcuts, CommonShortcuts.ENTER.getShortcuts()));
1005     new HyperlinkNavigationAction().registerCustomShortcutSet(shortcutSet, myEditor.getContentComponent());
1006
1007
1008     if (!myIsViewer) {
1009       new EnterHandler().registerCustomShortcutSet(CommonShortcuts.ENTER, myEditor.getContentComponent());
1010       registerActionHandler(myEditor, IdeActions.ACTION_EDITOR_PASTE, new PasteHandler());
1011       registerActionHandler(myEditor, IdeActions.ACTION_EDITOR_BACKSPACE, new BackSpaceHandler());
1012       registerActionHandler(myEditor, IdeActions.ACTION_EDITOR_DELETE, new DeleteHandler());
1013
1014       registerActionHandler(myEditor, EOFAction.ACTION_ID, ActionManager.getInstance().getAction(EOFAction.ACTION_ID));
1015     }
1016   }
1017
1018   private static void registerActionHandler(final Editor editor, final String actionId, final AnAction action) {
1019     final Keymap keymap = KeymapManager.getInstance().getActiveKeymap();
1020     final Shortcut[] shortcuts = keymap.getShortcuts(actionId);
1021     action.registerCustomShortcutSet(new CustomShortcutSet(shortcuts), editor.getContentComponent());
1022   }
1023
1024   private void popupInvoked(MouseEvent mouseEvent) {
1025     final ActionManager actionManager = ActionManager.getInstance();
1026     final HyperlinkInfo info = myHyperlinks != null ? myHyperlinks.getHyperlinkInfoByPoint(mouseEvent.getPoint()) : null;
1027     ActionGroup group = null;
1028     if (info instanceof HyperlinkWithPopupMenuInfo) {
1029       group = ((HyperlinkWithPopupMenuInfo)info).getPopupMenuGroup(mouseEvent);
1030     }
1031     if (group == null) {
1032       group = (ActionGroup)actionManager.getAction(CONSOLE_VIEW_POPUP_MENU);
1033     }
1034     final ConsoleActionsPostProcessor[] postProcessors = Extensions.getExtensions(ConsoleActionsPostProcessor.EP_NAME);
1035     AnAction[] result = group.getChildren(null);
1036
1037     for (ConsoleActionsPostProcessor postProcessor : postProcessors) {
1038       result = postProcessor.postProcessPopupActions(this, result);
1039     }
1040     final DefaultActionGroup processedGroup = new DefaultActionGroup(result);
1041     final ActionPopupMenu menu = actionManager.createActionPopupMenu(ActionPlaces.EDITOR_POPUP, processedGroup);
1042     menu.getComponent().show(mouseEvent.getComponent(), mouseEvent.getX(), mouseEvent.getY());
1043   }
1044
1045   private void highlightHyperlinksAndFoldings(RangeMarker lastProcessedOutput) {
1046     boolean canHighlightHyperlinks = !myFilters.isEmpty();
1047
1048     if (!canHighlightHyperlinks && myUpdateFoldingsEnabled) {
1049       return;
1050     }
1051     final int line1 = lastProcessedOutput.isValid() ? myEditor.getDocument().getLineNumber(lastProcessedOutput.getEndOffset()) : 0;
1052     lastProcessedOutput.dispose();
1053     int endLine = myEditor.getDocument().getLineCount() - 1;
1054     ApplicationManager.getApplication().assertIsDispatchThread();
1055
1056     if (canHighlightHyperlinks) {
1057       myHyperlinks.highlightHyperlinks(myFilters, line1, endLine);
1058     }
1059
1060     if (myAllowHeavyFilters && myFilters.isAnyHeavy() && myFilters.shouldRunHeavy()) {
1061       runHeavyFilters(line1, endLine);
1062     }
1063     if (myUpdateFoldingsEnabled) {
1064       updateFoldings(line1, endLine, true);
1065     }
1066   }
1067
1068   private void runHeavyFilters(int line1, int endLine) {
1069     final int startLine = Math.max(0, line1);
1070
1071     final Document document = myEditor.getDocument();
1072     final int startOffset = document.getLineStartOffset(startLine);
1073     String text = document.getText(new TextRange(startOffset, document.getLineEndOffset(endLine)));
1074     final Document documentCopy = new DocumentImpl(text,true);
1075     documentCopy.setReadOnly(true);
1076
1077     myJLayeredPane.startUpdating();
1078     final int currentValue = myHeavyUpdateTicket;
1079     assert myHeavyAlarm != null;
1080     myHeavyAlarm.addRequest(new Runnable() {
1081       @Override
1082       public void run() {
1083         if (!myFilters.shouldRunHeavy()) return;
1084         try {
1085           myFilters.applyHeavyFilter(documentCopy, startOffset, startLine, new Consumer<FilterMixin.AdditionalHighlight>() {
1086             @Override
1087             public void consume(final FilterMixin.AdditionalHighlight additionalHighlight) {
1088               addFlushRequest(new MyFlushRunnable() {
1089                 @Override
1090                 public void doRun() {
1091                   if (myHeavyUpdateTicket != currentValue) return;
1092                   myHyperlinks.addHighlighter(additionalHighlight.getStart(), additionalHighlight.getEnd(),
1093                                               additionalHighlight.getTextAttributes(null));
1094                 }
1095
1096                 @Override
1097                 public boolean equals(Object o) {
1098                   return this == o && super.equals(o);
1099                 }
1100               });
1101             }
1102           });
1103         }
1104         finally {
1105           if (myHeavyAlarm.isEmpty()) {
1106             SwingUtilities.invokeLater(new Runnable() {
1107               @Override
1108               public void run() {
1109                 myJLayeredPane.finishUpdating();
1110               }
1111             });
1112           }
1113         }
1114       }
1115     }, 0);
1116   }
1117
1118   private void updateFoldings(final int line1, final int endLine, boolean immediately) {
1119     final Document document = myEditor.getDocument();
1120     final CharSequence chars = document.getCharsSequence();
1121     final int startLine = Math.max(0, line1);
1122     final List<FoldRegion> toAdd = new ArrayList<FoldRegion>();
1123     for (int line = startLine; line <= endLine; line++) {
1124       boolean flushOnly = line == endLine;
1125       /*
1126       Grep Console plugin allows to fold empty lines. We need to handle this case in a special way.
1127
1128       Multiple lines are grouped into one folding, but to know when you can create the folding,
1129       you need a line which does not belong to that folding.
1130       When a new line, or a chunk of lines is printed, #addFolding is called for that lines + for an empty string
1131       (which basically does only one thing, gets a folding displayed).
1132       We do not want to process that empty string, but also we do not want to wait for another line
1133       which will create and display the folding - we'd see an unfolded stacktrace until another text came and flushed it.
1134       So therefore the condition, the last line(empty string) should still flush, but not be processed by
1135       com.intellij.execution.ConsoleFolding.
1136        */
1137       addFolding(document, chars, line, toAdd, flushOnly);
1138     }
1139     if (!toAdd.isEmpty()) {
1140       doUpdateFolding(toAdd, immediately);
1141     }
1142   }
1143
1144   private void doUpdateFolding(final List<FoldRegion> toAdd, final boolean immediately) {
1145     assertIsDispatchThread();
1146     myPendingFoldRegions.addAll(toAdd);
1147
1148     myFoldingAlarm.cancelAllRequests();
1149     final Runnable runnable = new Runnable() {
1150       @Override
1151       public void run() {
1152         if (myEditor == null || myEditor.isDisposed()) {
1153           return;
1154         }
1155
1156         assertIsDispatchThread();
1157         final FoldingModel model = myEditor.getFoldingModel();
1158         final Runnable operation = new Runnable() {
1159           @Override
1160           public void run() {
1161             assertIsDispatchThread();
1162             for (FoldRegion region : myPendingFoldRegions) {
1163               region.setExpanded(false);
1164               model.addFoldRegion(region);
1165             }
1166             myPendingFoldRegions.clear();
1167           }
1168         };
1169         if (immediately) {
1170           model.runBatchFoldingOperation(operation);
1171         }
1172         else {
1173           model.runBatchFoldingOperationDoNotCollapseCaret(operation);
1174         }
1175       }
1176     };
1177     if (immediately || myPendingFoldRegions.size() > 100) {
1178       runnable.run();
1179     }
1180     else {
1181       myFoldingAlarm.addRequest(runnable, 50);
1182     }
1183   }
1184
1185   private void addFolding(Document document, CharSequence chars, int line, List<FoldRegion> toAdd, boolean flushOnly) {
1186     ConsoleFolding current = null;
1187     if (!flushOnly) {
1188       String commandLinePlaceholder = myCommandLineFolding.getPlaceholder(line);
1189       if (commandLinePlaceholder != null) {
1190         FoldRegion region = myEditor.getFoldingModel()
1191           .createFoldRegion(document.getLineStartOffset(line), document.getLineEndOffset(line), commandLinePlaceholder, null, false);
1192         toAdd.add(region);
1193         return;
1194       }
1195       current = foldingForLine(EditorHyperlinkSupport.getLineText(document, line, false));
1196       if (current != null) {
1197         myFolding.put(line, current);
1198       }
1199     }
1200
1201     final ConsoleFolding prevFolding = myFolding.get(line - 1);
1202     if (current == null && prevFolding != null) {
1203       final int lEnd = line - 1;
1204       int lStart = lEnd;
1205       while (prevFolding.equals(myFolding.get(lStart - 1))) lStart--;
1206
1207       for (int i = lStart; i <= lEnd; i++) {
1208         myFolding.remove(i);
1209       }
1210
1211       List<String> toFold = new ArrayList<String>(lEnd - lStart + 1);
1212       for (int i = lStart; i <= lEnd; i++) {
1213         toFold.add(EditorHyperlinkSupport.getLineText(document, i, false));
1214       }
1215
1216       int oStart = document.getLineStartOffset(lStart);
1217       if (oStart > 0) oStart--;
1218       int oEnd = CharArrayUtil.shiftBackward(chars, document.getLineEndOffset(lEnd) - 1, " \t") + 1;
1219
1220       String placeholder = prevFolding.getPlaceholderText(toFold);
1221       FoldRegion region = placeholder == null ? null : myEditor.getFoldingModel().createFoldRegion(oStart, oEnd, placeholder, null, false);
1222       if (region != null) {
1223         toAdd.add(region);
1224       }
1225     }
1226   }
1227
1228   @Nullable
1229   private static ConsoleFolding foldingForLine(String lineText) {
1230     for (ConsoleFolding folding : ConsoleFolding.EP_NAME.getExtensions()) {
1231       if (folding.shouldFoldLine(lineText)) {
1232         return folding;
1233       }
1234     }
1235     return null;
1236   }
1237
1238
1239   public static class ClearAllAction extends DumbAwareAction {
1240     private final ConsoleView myConsoleView;
1241
1242     @SuppressWarnings("unused")
1243     public ClearAllAction() {
1244       this(null);
1245     }
1246
1247     public ClearAllAction(ConsoleView consoleView) {
1248       super(ExecutionBundle.message("clear.all.from.console.action.name"), "Clear the contents of the console", AllIcons.Actions.GC);
1249       myConsoleView = consoleView;
1250     }
1251
1252     @Override
1253     public void update(AnActionEvent e) {
1254       boolean enabled = myConsoleView != null && myConsoleView.getContentSize() > 0;
1255       if (!enabled) {
1256         enabled = e.getData(LangDataKeys.CONSOLE_VIEW) != null;
1257         Editor editor = e.getData(CommonDataKeys.EDITOR);
1258         if (editor != null && editor.getDocument().getTextLength() == 0) {
1259           enabled = false;
1260         }
1261       }
1262       e.getPresentation().setEnabled(enabled);
1263     }
1264
1265     @Override
1266     public void actionPerformed(final AnActionEvent e) {
1267       final ConsoleView consoleView = myConsoleView != null ? myConsoleView : e.getData(LangDataKeys.CONSOLE_VIEW);
1268       if (consoleView != null) {
1269         consoleView.clear();
1270       }
1271     }
1272   }
1273
1274   private class MyHighlighter extends DocumentAdapter implements EditorHighlighter {
1275     private HighlighterClient myEditor;
1276
1277     @NotNull
1278     @Override
1279     public HighlighterIterator createIterator(final int startOffset) {
1280       final int startIndex = ConsoleUtil.findTokenInfoIndexByOffset(myTokens, startOffset);
1281
1282       return new HighlighterIterator() {
1283         private int myIndex = startIndex;
1284
1285         @Override
1286         public TextAttributes getTextAttributes() {
1287           return atEnd() ? null : getTokenInfo().contentType.getAttributes();
1288         }
1289
1290         @Override
1291         public int getStart() {
1292           return atEnd() ? 0 : getTokenInfo().startOffset;
1293         }
1294
1295         @Override
1296         public int getEnd() {
1297           return atEnd() ? 0 : getTokenInfo().endOffset;
1298         }
1299
1300         @Override
1301         public IElementType getTokenType() {
1302           return null;
1303         }
1304
1305         @Override
1306         public void advance() {
1307           myIndex++;
1308         }
1309
1310         @Override
1311         public void retreat() {
1312           myIndex--;
1313         }
1314
1315         @Override
1316         public boolean atEnd() {
1317           return myIndex < 0 || myIndex >= myTokens.size();
1318         }
1319
1320         @Override
1321         public Document getDocument() {
1322           return myEditor.getDocument();
1323         }
1324
1325         private TokenInfo getTokenInfo() {
1326           return myTokens.get(myIndex);
1327         }
1328       };
1329     }
1330
1331     @Override
1332     public void setText(@NotNull final CharSequence text) {
1333     }
1334
1335     @Override
1336     public void setEditor(@NotNull final HighlighterClient editor) {
1337       LOG.assertTrue(myEditor == null, "Highlighters cannot be reused with different editors");
1338       myEditor = editor;
1339     }
1340
1341     @Override
1342     public void setColorScheme(@NotNull EditorColorsScheme scheme) {
1343     }
1344   }
1345
1346   private static class MyTypedHandler extends TypedActionHandlerBase {
1347
1348     private MyTypedHandler(final TypedActionHandler originalAction) {
1349       super(originalAction);
1350     }
1351
1352     @Override
1353     public void execute(@NotNull final Editor editor, final char charTyped, @NotNull final DataContext dataContext) {
1354       final ConsoleViewImpl consoleView = editor.getUserData(CONSOLE_VIEW_IN_EDITOR_VIEW);
1355       if (consoleView == null || !consoleView.myState.isRunning() || consoleView.myIsViewer) {
1356         if (myOriginalHandler != null) myOriginalHandler.execute(editor, charTyped, dataContext);
1357       }
1358       else {
1359         final String s = String.valueOf(charTyped);
1360         SelectionModel selectionModel = editor.getSelectionModel();
1361         if (selectionModel.hasSelection()) {
1362           consoleView.replaceUserText(s, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
1363         }
1364         else {
1365           consoleView.insertUserText(s, editor.getCaretModel().getOffset());
1366         }
1367       }
1368     }
1369   }
1370
1371   private abstract static class ConsoleAction extends AnAction implements DumbAware {
1372     @Override
1373     public void actionPerformed(final AnActionEvent e) {
1374       final DataContext context = e.getDataContext();
1375       final ConsoleViewImpl console = getRunningConsole(context);
1376       execute(console, context);
1377     }
1378
1379     protected abstract void execute(ConsoleViewImpl console, final DataContext context);
1380
1381     @Override
1382     public void update(final AnActionEvent e) {
1383       final ConsoleViewImpl console = getRunningConsole(e.getDataContext());
1384       e.getPresentation().setEnabled(console != null);
1385     }
1386
1387     @Nullable
1388     private static ConsoleViewImpl getRunningConsole(final DataContext context) {
1389       final Editor editor = CommonDataKeys.EDITOR.getData(context);
1390       if (editor != null) {
1391         final ConsoleViewImpl console = editor.getUserData(CONSOLE_VIEW_IN_EDITOR_VIEW);
1392         if (console != null && console.myState.isRunning()) {
1393           return console;
1394         }
1395       }
1396       return null;
1397     }
1398   }
1399
1400   private static class EnterHandler extends ConsoleAction {
1401     @Override
1402     public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
1403       consoleView.print("\n", ConsoleViewContentType.USER_INPUT);
1404       consoleView.flushDeferredText();
1405       final Editor editor = consoleView.myEditor;
1406       editor.getCaretModel().moveToOffset(editor.getDocument().getTextLength());
1407       editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1408     }
1409   }
1410
1411   private static class PasteHandler extends ConsoleAction {
1412     @Override
1413     public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
1414       String s = CopyPasteManager.getInstance().getContents(DataFlavor.stringFlavor);
1415       if (s == null) return;
1416       ApplicationManager.getApplication().assertIsDispatchThread();
1417       Editor editor = consoleView.myEditor;
1418       SelectionModel selectionModel = editor.getSelectionModel();
1419       if (selectionModel.hasSelection()) {
1420         consoleView.replaceUserText(s, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
1421       }
1422       else {
1423         consoleView.insertUserText(s, editor.getCaretModel().getOffset());
1424       }
1425     }
1426   }
1427
1428   private static class BackSpaceHandler extends ConsoleAction {
1429     @Override
1430     public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
1431       final Editor editor = consoleView.myEditor;
1432
1433       if (IncrementalSearchHandler.isHintVisible(editor)) {
1434         getDefaultActionHandler().execute(editor, context);
1435         return;
1436       }
1437
1438       final Document document = editor.getDocument();
1439       final int length = document.getTextLength();
1440       if (length == 0) {
1441         return;
1442       }
1443
1444       ApplicationManager.getApplication().assertIsDispatchThread();
1445
1446       SelectionModel selectionModel = editor.getSelectionModel();
1447       if (selectionModel.hasSelection()) {
1448         consoleView.deleteUserText(selectionModel.getSelectionStart(),
1449                                    selectionModel.getSelectionEnd() - selectionModel.getSelectionStart());
1450       }
1451       else if (editor.getCaretModel().getOffset() > 0) {
1452         consoleView.deleteUserText(editor.getCaretModel().getOffset() - 1, 1);
1453       }
1454     }
1455
1456     private static EditorActionHandler getDefaultActionHandler() {
1457       return EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE);
1458     }
1459   }
1460
1461   private static class DeleteHandler extends ConsoleAction {
1462     @Override
1463     public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
1464       final Editor editor = consoleView.myEditor;
1465
1466       if (IncrementalSearchHandler.isHintVisible(editor)) {
1467         getDefaultActionHandler().execute(editor, context);
1468         return;
1469       }
1470
1471       final Document document = editor.getDocument();
1472       final int length = document.getTextLength();
1473       if (length == 0) {
1474         return;
1475       }
1476
1477       ApplicationManager.getApplication().assertIsDispatchThread();
1478       SelectionModel selectionModel = editor.getSelectionModel();
1479       if (selectionModel.hasSelection()) {
1480         consoleView.deleteUserText(selectionModel.getSelectionStart(),
1481                                    selectionModel.getSelectionEnd() - selectionModel.getSelectionStart());
1482       }
1483       else {
1484         consoleView.deleteUserText(editor.getCaretModel().getOffset(), 1);
1485       }
1486     }
1487
1488     private static EditorActionHandler getDefaultActionHandler() {
1489       return EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE);
1490     }
1491   }
1492
1493   @Override
1494   public JComponent getPreferredFocusableComponent() {
1495     //ensure editor created
1496     getComponent();
1497     return myEditor.getContentComponent();
1498   }
1499
1500
1501   // navigate up/down in stack trace
1502   @Override
1503   public boolean hasNextOccurence() {
1504     return calcNextOccurrence(1) != null;
1505   }
1506
1507   @Override
1508   public boolean hasPreviousOccurence() {
1509     return calcNextOccurrence(-1) != null;
1510   }
1511
1512   @Override
1513   public OccurenceInfo goNextOccurence() {
1514     return calcNextOccurrence(1);
1515   }
1516
1517   @Nullable
1518   protected OccurenceInfo calcNextOccurrence(final int delta) {
1519     final EditorHyperlinkSupport hyperlinks = myHyperlinks;
1520     if (hyperlinks == null) {
1521       return null;
1522     }
1523
1524     return EditorHyperlinkSupport.getNextOccurrence(myEditor, delta, new Consumer<RangeHighlighter>() {
1525       @Override
1526       public void consume(RangeHighlighter next) {
1527         int offset = next.getStartOffset();
1528         scrollTo(offset);
1529         final HyperlinkInfo hyperlinkInfo = EditorHyperlinkSupport.getHyperlinkInfo(next);
1530         if (hyperlinkInfo instanceof BrowserHyperlinkInfo) {
1531           return;
1532         }
1533         if (hyperlinkInfo instanceof HyperlinkInfoBase) {
1534           VisualPosition position = myEditor.offsetToVisualPosition(offset);
1535           Point point = myEditor.visualPositionToXY(new VisualPosition(position.getLine() + 1, position.getColumn()));
1536           ((HyperlinkInfoBase)hyperlinkInfo).navigate(myProject, new RelativePoint(myEditor.getContentComponent(), point));
1537         }
1538         else if (hyperlinkInfo != null) {
1539           hyperlinkInfo.navigate(myProject);
1540         }
1541       }
1542     });
1543   }
1544
1545   @Override
1546   public OccurenceInfo goPreviousOccurence() {
1547     return calcNextOccurrence(-1);
1548   }
1549
1550   @Override
1551   public String getNextOccurenceActionName() {
1552     return ExecutionBundle.message("down.the.stack.trace");
1553   }
1554
1555   @Override
1556   public String getPreviousOccurenceActionName() {
1557     return ExecutionBundle.message("up.the.stack.trace");
1558   }
1559
1560   public void addCustomConsoleAction(@NotNull AnAction action) {
1561     customActions.add(action);
1562   }
1563
1564   @Override
1565   @NotNull
1566   public AnAction[] createConsoleActions() {
1567     //Initializing prev and next occurrences actions
1568     final CommonActionsManager actionsManager = CommonActionsManager.getInstance();
1569     final AnAction prevAction = actionsManager.createPrevOccurenceAction(this);
1570     prevAction.getTemplatePresentation().setText(getPreviousOccurenceActionName());
1571     final AnAction nextAction = actionsManager.createNextOccurenceAction(this);
1572     nextAction.getTemplatePresentation().setText(getNextOccurenceActionName());
1573
1574     final AnAction switchSoftWrapsAction = new ToggleUseSoftWrapsToolbarAction(SoftWrapAppliancePlaces.CONSOLE) {
1575
1576       /**
1577        * There is a possible case that more than console is open and user toggles soft wraps mode at one of them. We want
1578        * to update another console(s) representation as well when they are switched on after that. Hence, we remember last
1579        * used soft wraps mode and perform update if we see that the current value differs from the stored.
1580        */
1581       private boolean myLastIsSelected;
1582
1583       @Override
1584       protected Editor getEditor(AnActionEvent e) {
1585         return myEditor;
1586       }
1587
1588       @Override
1589       public boolean isSelected(AnActionEvent e) {
1590         boolean result = super.isSelected(e);
1591         if (result ^ myLastIsSelected) {
1592           setSelected(null, result);
1593         }
1594         return myLastIsSelected = result;
1595       }
1596
1597       @Override
1598       public void setSelected(AnActionEvent e, final boolean state) {
1599         super.setSelected(e, state);
1600         if (myEditor == null) {
1601           return;
1602         }
1603
1604         final String placeholder = myCommandLineFolding.getPlaceholder(0);
1605         final FoldingModel foldingModel = myEditor.getFoldingModel();
1606         final int firstLineEnd = myEditor.getDocument().getLineEndOffset(0);
1607         foldingModel.runBatchFoldingOperation(new Runnable() {
1608           @Override
1609           public void run() {
1610             FoldRegion[] regions = foldingModel.getAllFoldRegions();
1611             if (regions.length > 0 && regions[0].getStartOffset() == 0 && regions[0].getEndOffset() == firstLineEnd) {
1612               foldingModel.removeFoldRegion(regions[0]);
1613             }
1614             if (placeholder != null) {
1615               FoldRegion foldRegion = foldingModel.addFoldRegion(0, firstLineEnd, placeholder);
1616               if (foldRegion != null) {
1617                 foldRegion.setExpanded(false);
1618               }
1619             }
1620           }
1621         });
1622       }
1623     };
1624     final AnAction autoScrollToTheEndAction = new ScrollToTheEndToolbarAction(myEditor);
1625
1626     //Initializing custom actions
1627     final AnAction[] consoleActions = new AnAction[6 + customActions.size()];
1628     consoleActions[0] = prevAction;
1629     consoleActions[1] = nextAction;
1630     consoleActions[2] = switchSoftWrapsAction;
1631     consoleActions[3] = autoScrollToTheEndAction;
1632     consoleActions[4] = ActionManager.getInstance().getAction("Print");
1633     consoleActions[5] = new ClearAllAction(this);
1634     for (int i = 0; i < customActions.size(); ++i) {
1635       consoleActions[i + 6] = customActions.get(i);
1636     }
1637     ConsoleActionsPostProcessor[] postProcessors = Extensions.getExtensions(ConsoleActionsPostProcessor.EP_NAME);
1638     AnAction[] result = consoleActions;
1639     for (ConsoleActionsPostProcessor postProcessor : postProcessors) {
1640       result = postProcessor.postProcess(this, result);
1641     }
1642     return result;
1643   }
1644
1645   @Override
1646   public void allowHeavyFilters() {
1647     myAllowHeavyFilters = true;
1648   }
1649
1650   @Override
1651   public void addChangeListener(@NotNull final ChangeListener listener, @NotNull final Disposable parent) {
1652     myListeners.add(listener);
1653     Disposer.register(parent, new Disposable() {
1654       @Override
1655       public void dispose() {
1656         myListeners.remove(listener);
1657       }
1658     });
1659   }
1660
1661   /**
1662    * insert text to document
1663    *
1664    * @param s      inserted text
1665    * @param offset relatively to all document text
1666    */
1667   private void insertUserText(final String s, int offset) {
1668     ApplicationManager.getApplication().assertIsDispatchThread();
1669     final ConsoleViewImpl consoleView = this;
1670     final ConsoleBuffer buffer = consoleView.myBuffer;
1671     final Editor editor = consoleView.myEditor;
1672     final Document document = editor.getDocument();
1673     final int startOffset;
1674
1675     String textToUse = StringUtil.convertLineSeparators(s);
1676     synchronized (consoleView.LOCK) {
1677       if (consoleView.myTokens.isEmpty()) {
1678         addToken(0, null, ConsoleViewContentType.SYSTEM_OUTPUT);
1679       }
1680       final TokenInfo info = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1681       if (info.contentType != ConsoleViewContentType.USER_INPUT && !StringUtil.containsChar(textToUse, '\n')) {
1682         consoleView.print(textToUse, ConsoleViewContentType.USER_INPUT);
1683         consoleView.flushDeferredText();
1684         editor.getCaretModel().moveToOffset(document.getTextLength());
1685         editor.getSelectionModel().removeSelection();
1686         return;
1687       }
1688       if (info.contentType != ConsoleViewContentType.USER_INPUT) {
1689         insertUserText("temp", offset);
1690         final TokenInfo newInfo = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1691         replaceUserText(textToUse, newInfo.startOffset, newInfo.endOffset);
1692         return;
1693       }
1694
1695       final int deferredOffset = myContentSize - buffer.getLength() - buffer.getUserInputLength();
1696       if (offset > info.endOffset) {
1697         startOffset = info.endOffset;
1698       }
1699       else {
1700         startOffset = Math.max(deferredOffset, Math.max(info.startOffset, offset));
1701       }
1702
1703       buffer.addUserText(startOffset - deferredOffset, textToUse);
1704
1705       int charCountToAdd = textToUse.length();
1706       info.endOffset += charCountToAdd;
1707       consoleView.myContentSize += charCountToAdd;
1708     }
1709
1710     try {
1711       myInDocumentUpdate = true;
1712       document.insertString(startOffset, textToUse);
1713     }
1714     finally {
1715       myInDocumentUpdate = false;
1716     }
1717     // Math.max is needed when cyclic buffer is used
1718     editor.getCaretModel().moveToOffset(Math.min(startOffset + textToUse.length(), document.getTextLength()));
1719     editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1720   }
1721
1722   /**
1723    * replace text
1724    *
1725    * @param s     text for replace
1726    * @param start relatively to all document text
1727    * @param end   relatively to all document text
1728    */
1729   private void replaceUserText(final String s, int start, int end) {
1730     if (start == end) {
1731       insertUserText(s, start);
1732       return;
1733     }
1734     final ConsoleViewImpl consoleView = this;
1735     final ConsoleBuffer buffer = consoleView.myBuffer;
1736     final Editor editor = consoleView.myEditor;
1737     final Document document = editor.getDocument();
1738     final int startOffset;
1739     final int endOffset;
1740
1741     synchronized (consoleView.LOCK) {
1742       if (consoleView.myTokens.isEmpty()) return;
1743       final TokenInfo info = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1744       if (info.contentType != ConsoleViewContentType.USER_INPUT) {
1745         consoleView.print(s, ConsoleViewContentType.USER_INPUT);
1746         consoleView.flushDeferredText();
1747         editor.getCaretModel().moveToOffset(document.getTextLength());
1748         editor.getSelectionModel().removeSelection();
1749         return;
1750       }
1751       if (buffer.getUserInputLength() <= 0) return;
1752
1753       final int deferredOffset = myContentSize - buffer.getLength() - buffer.getUserInputLength();
1754
1755       startOffset = getStartOffset(start, info, deferredOffset);
1756       endOffset = getEndOffset(end, info);
1757
1758       if (startOffset == -1 ||
1759           endOffset == -1 ||
1760           endOffset <= startOffset) {
1761         editor.getSelectionModel().removeSelection();
1762         editor.getCaretModel().moveToOffset(start);
1763         return;
1764       }
1765       int charCountToReplace = s.length() - endOffset + startOffset;
1766
1767       buffer.replaceUserText(startOffset - deferredOffset, endOffset - deferredOffset, s);
1768
1769       info.endOffset += charCountToReplace;
1770       if (info.startOffset == info.endOffset) {
1771         consoleView.myTokens.remove(consoleView.myTokens.size() - 1);
1772       }
1773       consoleView.myContentSize += charCountToReplace;
1774     }
1775
1776     try {
1777       myInDocumentUpdate = true;
1778       document.replaceString(startOffset, endOffset, s);
1779     }
1780     finally {
1781       myInDocumentUpdate = false;
1782     }
1783     editor.getCaretModel().moveToOffset(startOffset + s.length());
1784     editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1785     editor.getSelectionModel().removeSelection();
1786   }
1787
1788   /**
1789    * delete text
1790    *
1791    * @param offset relatively to all document text
1792    * @param length length of deleted text
1793    */
1794   private void deleteUserText(int offset, int length) {
1795     ConsoleViewImpl consoleView = this;
1796     ConsoleBuffer buffer = consoleView.myBuffer;
1797     final Editor editor = consoleView.myEditor;
1798     final Document document = editor.getDocument();
1799     final int startOffset;
1800     final int endOffset;
1801
1802     synchronized (consoleView.LOCK) {
1803       if (consoleView.myTokens.isEmpty()) return;
1804       final TokenInfo info = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1805       if (info.contentType != ConsoleViewContentType.USER_INPUT) return;
1806       if (myBuffer.getUserInputLength() == 0) return;
1807
1808       final int deferredOffset = myContentSize - buffer.getLength() - buffer.getUserInputLength();
1809       startOffset = getStartOffset(offset, info, deferredOffset);
1810       endOffset = getEndOffset(offset + length, info);
1811       if (startOffset == -1 ||
1812           endOffset == -1 ||
1813           endOffset <= startOffset ||
1814           startOffset < deferredOffset) {
1815         editor.getSelectionModel().removeSelection();
1816         editor.getCaretModel().moveToOffset(offset);
1817         return;
1818       }
1819
1820       buffer.removeUserText(startOffset - deferredOffset, endOffset - deferredOffset);
1821     }
1822
1823     try {
1824       myInDocumentUpdate = true;
1825       document.deleteString(startOffset, endOffset);
1826     }
1827     finally {
1828       myInDocumentUpdate = false;
1829     }
1830     editor.getCaretModel().moveToOffset(startOffset);
1831     editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1832     editor.getSelectionModel().removeSelection();
1833   }
1834
1835   //util methods for add, replace, delete methods
1836   private static int getStartOffset(int offset, TokenInfo info, int deferredOffset) {
1837     int startOffset;
1838     if (offset >= info.startOffset && offset < info.endOffset) {
1839       startOffset = Math.max(offset, deferredOffset);
1840     }
1841     else if (offset < info.startOffset) {
1842       startOffset = Math.max(info.startOffset, deferredOffset);
1843     }
1844     else {
1845       startOffset = -1;
1846     }
1847     return startOffset;
1848   }
1849
1850   private static int getEndOffset(int offset, TokenInfo info) {
1851     int endOffset;
1852     if (offset > info.endOffset) {
1853       endOffset = info.endOffset;
1854     }
1855     else if (offset <= info.startOffset) {
1856       endOffset = -1;
1857     }
1858     else {
1859       endOffset = offset;
1860     }
1861     return endOffset;
1862   }
1863
1864   public boolean isRunning() {
1865     return myState.isRunning();
1866   }
1867
1868   /**
1869    * Command line used to launch application/test from idea may be quite long.
1870    * Hence, it takes many visual lines during representation if soft wraps are enabled
1871    * or, otherwise, takes many columns and makes horizontal scrollbar thumb too small.
1872    * <p/>
1873    * Our point is to fold such long command line and represent it as a single visual line by default.
1874    */
1875   private class CommandLineFolding extends ConsoleFolding {
1876
1877     /**
1878      * Checks if target line should be folded and returns its placeholder if the examination succeeds.
1879      *
1880      * @param line index of line to check
1881      * @return placeholder text if given line should be folded; <code>null</code> otherwise
1882      */
1883     @Nullable
1884     private String getPlaceholder(int line) {
1885       if (myEditor == null || line != 0) {
1886         return null;
1887       }
1888
1889       String text = EditorHyperlinkSupport.getLineText(myEditor.getDocument(), 0, false);
1890       // Don't fold the first line if the line is not that big.
1891       if (text.length() < 1000) {
1892         return null;
1893       }
1894       int index = 0;
1895       if (text.charAt(0) == '"') {
1896         index = text.indexOf('"', 1) + 1;
1897       }
1898       if (index == 0) {
1899         boolean nonWhiteSpaceFound = false;
1900         for (; index < text.length(); index++) {
1901           char c = text.charAt(index);
1902           if (c != ' ' && c != '\t') {
1903             nonWhiteSpaceFound = true;
1904             continue;
1905           }
1906           if (nonWhiteSpaceFound) {
1907             break;
1908           }
1909         }
1910       }
1911       assert index <= text.length();
1912       return text.substring(0, index) + " ...";
1913     }
1914
1915     @Override
1916     public boolean shouldFoldLine(String line) {
1917       return false;
1918     }
1919
1920     @Override
1921     public String getPlaceholderText(List<String> lines) {
1922       // Is not expected to be called.
1923       return "<...>";
1924     }
1925   }
1926
1927   private class MyFlushRunnable implements Runnable {
1928     private volatile boolean myValid = true;
1929     @Override
1930     public final void run() {
1931       synchronized (myCurrentRequests) {
1932         myCurrentRequests.remove(this);
1933       }
1934       if (myValid) {
1935         doRun();
1936       }
1937     }
1938
1939     protected void doRun() {
1940       flushDeferredText();
1941     }
1942
1943     public void invalidate() {
1944       myValid = false;
1945     }
1946
1947     public boolean isValid() {
1948       return myValid;
1949     }
1950
1951     @Override
1952     public boolean equals(Object o) {
1953       if (this == o) return true;
1954       if (o == null || getClass() != o.getClass()) return false;
1955
1956       MyFlushRunnable runnable = (MyFlushRunnable)o;
1957
1958       return myValid == runnable.myValid;
1959     }
1960
1961     @Override
1962     public int hashCode() {
1963       return getClass().hashCode();
1964     }
1965   }
1966
1967   private final class MyClearRunnable extends MyFlushRunnable {
1968     @Override
1969     public void doRun() {
1970       flushDeferredText(true);
1971     }
1972   }
1973
1974   @NotNull
1975   public Project getProject() {
1976     return myProject;
1977   }
1978
1979   private class HyperlinkNavigationAction extends DumbAwareAction {
1980     @Override
1981     public void actionPerformed(AnActionEvent e) {
1982       Runnable runnable = myHyperlinks.getLinkNavigationRunnable(myEditor.getCaretModel().getLogicalPosition());
1983       assert runnable != null;
1984       runnable.run();
1985     }
1986
1987     @Override
1988     public void update(AnActionEvent e) {
1989       e.getPresentation().setEnabled(myHyperlinks.getLinkNavigationRunnable(myEditor.getCaretModel().getLogicalPosition()) != null);
1990     }
1991   }
1992
1993   @NotNull
1994   public String getText() {
1995     return myEditor.getDocument().getText();
1996   }
1997 }
1998