4c3181f92741917bcadee160aefe8cfa63e28c41
[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     }
719
720
721     final String addedText;
722     final Collection<ConsoleViewContentType> contentTypes;
723     int deferredTokensSize;
724     synchronized (LOCK) {
725       if (myOutputPaused) return;
726       if (myBuffer.isEmpty()) return;
727
728       addedText = myBuffer.getText();
729
730       contentTypes = Collections.unmodifiableCollection(new HashSet<ConsoleViewContentType>(myBuffer.getDeferredTokenTypes()));
731       List<TokenInfo> deferredTokens = myBuffer.getDeferredTokens();
732       for (TokenInfo deferredToken : deferredTokens) {
733         addToken(deferredToken.getLength(), deferredToken.getHyperlinkInfo(), deferredToken.contentType);
734       }
735       deferredTokensSize = deferredTokens.size();
736       myBuffer.clear(false);
737       cancelHeavyAlarm();
738     }
739     final Document document = myEditor.getDocument();
740     final RangeMarker lastProcessedOutput = document.createRangeMarker(document.getTextLength(), document.getTextLength());
741
742     CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
743       @Override
744       public void run() {
745         if (!shouldStickToEnd) {
746           myEditor.getScrollingModel().accumulateViewportChanges();
747         }
748         try {
749           myInDocumentUpdate = true;
750           String[] strings = addedText.split("\\r", -1); // limit must be any negative number to avoid discarding of trailing empty strings
751           for (int i = 0; i < strings.length - 1; i++) {
752             document.insertString(document.getTextLength(), strings[i]);
753             int lastLine = document.getLineCount() - 1;
754             if (lastLine >= 0) {
755               ConsoleUtil.updateTokensOnTextRemoval(myTokens, document.getTextLength(), document.getTextLength() + 1);
756               document.deleteString(document.getLineStartOffset(lastLine), document.getTextLength());
757             }
758           }
759           if (strings.length > 0) {
760             document.insertString(document.getTextLength(), strings[strings.length - 1]);
761             myContentSize -= strings.length - 1;
762           }
763         }
764         finally {
765           myInDocumentUpdate = false;
766           if (!shouldStickToEnd) {
767             myEditor.getScrollingModel().flushViewportChanges();
768           }
769         }
770         if (!contentTypes.isEmpty()) {
771           for (ChangeListener each : myListeners) {
772             each.contentAdded(contentTypes);
773           }
774         }
775       }
776     }, null, DocCommandGroupId.noneGroupId(document));
777     synchronized (LOCK) {
778       for (int i = myTokens.size() - 1; i >= 0 && deferredTokensSize > 0; i--, deferredTokensSize--) {
779         TokenInfo token = myTokens.get(i);
780         final HyperlinkInfo info = token.getHyperlinkInfo();
781         if (info != null) {
782           myHyperlinks.createHyperlink(token.startOffset, token.endOffset, null, info);
783         }
784       }
785     }
786     myPsiDisposedCheck.performCheck();
787     myLastAddedTextLength = addedText.length();
788     if (!myTooMuchOfOutput) {
789       if (isTheAmountOfTextTooBig(myLastAddedTextLength)) { // disable hyperlinks and folding until new output arriving slows down again
790         myTooMuchOfOutput = true;
791         final EditorNotificationPanel comp =
792           new EditorNotificationPanel().text("Too much output to process").icon(AllIcons.General.ExclMark);
793         final Alarm tooMuchOutputAlarm = new Alarm();
794         //show the notification with a delay to avoid blinking when "too much output" ceases quickly
795         tooMuchOutputAlarm.addRequest(new Runnable() {
796           @Override
797           public void run() {
798             add(comp, BorderLayout.NORTH);
799           }
800         }, 300);
801         performWhenNoDeferredOutput(new Runnable() {
802           @Override
803           public void run() {
804             if (!isTheAmountOfTextTooBig(myLastAddedTextLength)) {
805               try {
806                 highlightHyperlinksAndFoldings(lastProcessedOutput);
807               }
808               finally {
809                 myTooMuchOfOutput = false;
810                 remove(comp);
811                 tooMuchOutputAlarm.cancelAllRequests();
812               }
813             }
814             else {
815               myLastAddedTextLength = 0;
816               performLaterWhenNoDeferredOutput(this);
817             }
818           }
819         });
820       }
821       else {
822         highlightHyperlinksAndFoldings(lastProcessedOutput);
823       }
824     }
825
826     if (shouldStickToEnd) {
827       scrollToEnd();
828     }
829   }
830
831   private boolean isStickingToEnd() {
832     if (myEditor == null) return myLastStickingToEnd;
833     Document document = myEditor.getDocument();
834     int caretOffset = myEditor.getCaretModel().getOffset();
835     myLastStickingToEnd = document.getLineNumber(caretOffset) >= document.getLineCount() - 1;
836     return myLastStickingToEnd;
837   }
838
839   private boolean isTheAmountOfTextTooBig(final int textLength) {
840     return myBuffer.isUseCyclicBuffer() && textLength > myBuffer.getCyclicBufferSize() / consoleTooMuchTextBufferRatio;
841   }
842
843   private void clearHyperlinkAndFoldings() {
844     myEditor.getMarkupModel().removeAllHighlighters();
845
846     myPendingFoldRegions.clear();
847     myFolding.clear();
848     myFoldingAlarm.cancelAllRequests();
849     myEditor.getFoldingModel().runBatchFoldingOperation(new Runnable() {
850       @Override
851       public void run() {
852         myEditor.getFoldingModel().clearFoldRegions();
853       }
854     });
855
856     cancelHeavyAlarm();
857   }
858
859   private void cancelHeavyAlarm() {
860     if (myHeavyAlarm != null && !myHeavyAlarm.isDisposed()) {
861       myHeavyAlarm.cancelAllRequests();
862       ++myHeavyUpdateTicket;
863     }
864   }
865
866   private void flushDeferredUserInput() {
867     final String textToSend = myBuffer.cutFirstUserInputLine();
868     if (textToSend == null) {
869       return;
870     }
871     myFlushUserInputAlarm.addRequest(new Runnable() {
872       @Override
873       public void run() {
874         if (myState.isRunning()) {
875           try {
876             // this may block forever, see IDEA-54340
877             myState.sendUserInput(textToSend);
878           }
879           catch (IOException ignored) {
880           }
881         }
882       }
883     }, 0);
884   }
885
886   @Override
887   public Object getData(final String dataId) {
888     if (CommonDataKeys.NAVIGATABLE.is(dataId)) {
889       if (myEditor == null) {
890         return null;
891       }
892       final LogicalPosition pos = myEditor.getCaretModel().getLogicalPosition();
893       final HyperlinkInfo info = myHyperlinks.getHyperlinkInfoByLineAndCol(pos.line, pos.column);
894       final OpenFileDescriptor openFileDescriptor = info instanceof FileHyperlinkInfo ? ((FileHyperlinkInfo)info).getDescriptor() : null;
895       if (openFileDescriptor == null || !openFileDescriptor.getFile().isValid()) {
896         return null;
897       }
898       return openFileDescriptor;
899     }
900
901     if (CommonDataKeys.EDITOR.is(dataId)) {
902       return myEditor;
903     }
904     if (PlatformDataKeys.HELP_ID.is(dataId)) {
905       return myHelpId;
906     }
907     if (LangDataKeys.CONSOLE_VIEW.is(dataId)) {
908       return this;
909     }
910     return null;
911   }
912
913   @Override
914   public void setHelpId(final String helpId) {
915     myHelpId = helpId;
916   }
917
918   public void setUpdateFoldingsEnabled(boolean updateFoldingsEnabled) {
919     myUpdateFoldingsEnabled = updateFoldingsEnabled;
920   }
921
922   @Override
923   public void addMessageFilter(final Filter filter) {
924     myFilters.addFilter(filter);
925   }
926
927   @Override
928   public void printHyperlink(final String hyperlinkText, final HyperlinkInfo info) {
929     printHyperlink(hyperlinkText, ConsoleViewContentType.NORMAL_OUTPUT, info);
930   }
931
932   private EditorEx createConsoleEditor() {
933     return ApplicationManager.getApplication().runReadAction(new Computable<EditorEx>() {
934       @Override
935       public EditorEx compute() {
936         EditorEx editor = doCreateConsoleEditor();
937         editor.setContextMenuGroupId(null); // disabling default context menu
938         editor.addEditorMouseListener(new EditorPopupHandler() {
939           @Override
940           public void invokePopup(final EditorMouseEvent event) {
941             popupInvoked(event.getMouseEvent());
942           }
943         });
944         editor.getDocument().addDocumentListener(new DocumentAdapter() {
945           @Override
946           public void documentChanged(DocumentEvent event) {
947             onDocumentChanged(event);
948           }
949         }, ConsoleViewImpl.this);
950
951         int bufferSize = myBuffer.isUseCyclicBuffer() ? myBuffer.getCyclicBufferSize() : 0;
952         editor.getDocument().setCyclicBufferSize(bufferSize);
953
954         editor.putUserData(CONSOLE_VIEW_IN_EDITOR_VIEW, ConsoleViewImpl.this);
955
956         editor.getSettings().setAllowSingleLogicalLineFolding(true); // We want to fold long soft-wrapped command lines
957         editor.setHighlighter(createHighlighter());
958
959         return editor;
960       }
961     });
962   }
963
964   private void onDocumentChanged(DocumentEvent event) {
965     if (event.getNewLength() == 0) {
966       // string has been removed, adjust token ranges
967       synchronized (LOCK) {
968         ConsoleUtil.updateTokensOnTextRemoval(myTokens, event.getOffset(), event.getOffset() + event.getOldLength());
969         int toRemoveLen = event.getOldLength();
970         if (!myDocumentClearing) {
971           // If document is being cleared now, then this event has been occurred as a result of calling clear() method.
972           // At start clear() method sets 'myContentSize' to 0, so there is no need to perform update again.
973           // Moreover, performing update of 'myContentSize' breaks executing "console.print();" immediately after "console.clear();".
974           myContentSize -= Math.min(myContentSize, toRemoveLen);
975         }
976       }
977     }
978     else if (!myInDocumentUpdate) {
979       int newFragmentLength = event.getNewFragment().length();
980       // track external appends
981       if (event.getOldFragment().length() == 0 && newFragmentLength > 0) {
982         synchronized (LOCK) {
983           myContentSize += newFragmentLength;
984           addToken(newFragmentLength, null, ConsoleViewContentType.NORMAL_OUTPUT);
985         }
986       }
987       else {
988         LOG.warn("unhandled external change: " + event);
989       }
990     }
991   }
992
993   protected EditorEx doCreateConsoleEditor() {
994     return ConsoleViewUtil.setupConsoleEditor(myProject, true, false);
995   }
996
997   protected MyHighlighter createHighlighter() {
998     return new MyHighlighter();
999   }
1000
1001   private void registerConsoleEditorActions() {
1002     Shortcut[] shortcuts = KeymapManager.getInstance().getActiveKeymap().getShortcuts(IdeActions.ACTION_GOTO_DECLARATION);
1003     CustomShortcutSet shortcutSet = new CustomShortcutSet(ArrayUtil.mergeArrays(shortcuts, CommonShortcuts.ENTER.getShortcuts()));
1004     new HyperlinkNavigationAction().registerCustomShortcutSet(shortcutSet, myEditor.getContentComponent());
1005
1006
1007     if (!myIsViewer) {
1008       new EnterHandler().registerCustomShortcutSet(CommonShortcuts.ENTER, myEditor.getContentComponent());
1009       registerActionHandler(myEditor, IdeActions.ACTION_EDITOR_PASTE, new PasteHandler());
1010       registerActionHandler(myEditor, IdeActions.ACTION_EDITOR_BACKSPACE, new BackSpaceHandler());
1011       registerActionHandler(myEditor, IdeActions.ACTION_EDITOR_DELETE, new DeleteHandler());
1012
1013       registerActionHandler(myEditor, EOFAction.ACTION_ID, ActionManager.getInstance().getAction(EOFAction.ACTION_ID));
1014     }
1015   }
1016
1017   private static void registerActionHandler(final Editor editor, final String actionId, final AnAction action) {
1018     final Keymap keymap = KeymapManager.getInstance().getActiveKeymap();
1019     final Shortcut[] shortcuts = keymap.getShortcuts(actionId);
1020     action.registerCustomShortcutSet(new CustomShortcutSet(shortcuts), editor.getContentComponent());
1021   }
1022
1023   private void popupInvoked(MouseEvent mouseEvent) {
1024     final ActionManager actionManager = ActionManager.getInstance();
1025     final HyperlinkInfo info = myHyperlinks != null ? myHyperlinks.getHyperlinkInfoByPoint(mouseEvent.getPoint()) : null;
1026     ActionGroup group = null;
1027     if (info instanceof HyperlinkWithPopupMenuInfo) {
1028       group = ((HyperlinkWithPopupMenuInfo)info).getPopupMenuGroup(mouseEvent);
1029     }
1030     if (group == null) {
1031       group = (ActionGroup)actionManager.getAction(CONSOLE_VIEW_POPUP_MENU);
1032     }
1033     final ConsoleActionsPostProcessor[] postProcessors = Extensions.getExtensions(ConsoleActionsPostProcessor.EP_NAME);
1034     AnAction[] result = group.getChildren(null);
1035
1036     for (ConsoleActionsPostProcessor postProcessor : postProcessors) {
1037       result = postProcessor.postProcessPopupActions(this, result);
1038     }
1039     final DefaultActionGroup processedGroup = new DefaultActionGroup(result);
1040     final ActionPopupMenu menu = actionManager.createActionPopupMenu(ActionPlaces.EDITOR_POPUP, processedGroup);
1041     menu.getComponent().show(mouseEvent.getComponent(), mouseEvent.getX(), mouseEvent.getY());
1042   }
1043
1044   private void highlightHyperlinksAndFoldings(RangeMarker lastProcessedOutput) {
1045     boolean canHighlightHyperlinks = !myFilters.isEmpty();
1046
1047     if (!canHighlightHyperlinks && myUpdateFoldingsEnabled) {
1048       return;
1049     }
1050     final int line1 = lastProcessedOutput.isValid() ? myEditor.getDocument().getLineNumber(lastProcessedOutput.getEndOffset()) : 0;
1051     lastProcessedOutput.dispose();
1052     int endLine = myEditor.getDocument().getLineCount() - 1;
1053     ApplicationManager.getApplication().assertIsDispatchThread();
1054
1055     if (canHighlightHyperlinks) {
1056       myHyperlinks.highlightHyperlinks(myFilters, line1, endLine);
1057     }
1058
1059     if (myAllowHeavyFilters && myFilters.isAnyHeavy() && myFilters.shouldRunHeavy()) {
1060       runHeavyFilters(line1, endLine);
1061     }
1062     if (myUpdateFoldingsEnabled) {
1063       updateFoldings(line1, endLine, true);
1064     }
1065   }
1066
1067   private void runHeavyFilters(int line1, int endLine) {
1068     final int startLine = Math.max(0, line1);
1069
1070     final Document document = myEditor.getDocument();
1071     final int startOffset = document.getLineStartOffset(startLine);
1072     String text = document.getText(new TextRange(startOffset, document.getLineEndOffset(endLine)));
1073     final Document documentCopy = new DocumentImpl(text,true);
1074     documentCopy.setReadOnly(true);
1075
1076     myJLayeredPane.startUpdating();
1077     final int currentValue = myHeavyUpdateTicket;
1078     assert myHeavyAlarm != null;
1079     myHeavyAlarm.addRequest(new Runnable() {
1080       @Override
1081       public void run() {
1082         if (!myFilters.shouldRunHeavy()) return;
1083         try {
1084           myFilters.applyHeavyFilter(documentCopy, startOffset, startLine, new Consumer<FilterMixin.AdditionalHighlight>() {
1085             @Override
1086             public void consume(final FilterMixin.AdditionalHighlight additionalHighlight) {
1087               addFlushRequest(new MyFlushRunnable() {
1088                 @Override
1089                 public void doRun() {
1090                   if (myHeavyUpdateTicket != currentValue) return;
1091                   myHyperlinks.addHighlighter(additionalHighlight.getStart(), additionalHighlight.getEnd(),
1092                                               additionalHighlight.getTextAttributes(null));
1093                 }
1094
1095                 @Override
1096                 public boolean equals(Object o) {
1097                   return this == o && super.equals(o);
1098                 }
1099               });
1100             }
1101           });
1102         }
1103         finally {
1104           if (myHeavyAlarm.isEmpty()) {
1105             SwingUtilities.invokeLater(new Runnable() {
1106               @Override
1107               public void run() {
1108                 myJLayeredPane.finishUpdating();
1109               }
1110             });
1111           }
1112         }
1113       }
1114     }, 0);
1115   }
1116
1117   private void updateFoldings(final int line1, final int endLine, boolean immediately) {
1118     final Document document = myEditor.getDocument();
1119     final CharSequence chars = document.getCharsSequence();
1120     final int startLine = Math.max(0, line1);
1121     final List<FoldRegion> toAdd = new ArrayList<FoldRegion>();
1122     for (int line = startLine; line <= endLine; line++) {
1123       boolean flushOnly = line == endLine;
1124       /*
1125       Grep Console plugin allows to fold empty lines. We need to handle this case in a special way.
1126
1127       Multiple lines are grouped into one folding, but to know when you can create the folding,
1128       you need a line which does not belong to that folding.
1129       When a new line, or a chunk of lines is printed, #addFolding is called for that lines + for an empty string
1130       (which basically does only one thing, gets a folding displayed).
1131       We do not want to process that empty string, but also we do not want to wait for another line
1132       which will create and display the folding - we'd see an unfolded stacktrace until another text came and flushed it.
1133       So therefore the condition, the last line(empty string) should still flush, but not be processed by
1134       com.intellij.execution.ConsoleFolding.
1135        */
1136       addFolding(document, chars, line, toAdd, flushOnly);
1137     }
1138     if (!toAdd.isEmpty()) {
1139       doUpdateFolding(toAdd, immediately);
1140     }
1141   }
1142
1143   private void doUpdateFolding(final List<FoldRegion> toAdd, final boolean immediately) {
1144     assertIsDispatchThread();
1145     myPendingFoldRegions.addAll(toAdd);
1146
1147     myFoldingAlarm.cancelAllRequests();
1148     final Runnable runnable = new Runnable() {
1149       @Override
1150       public void run() {
1151         if (myEditor == null || myEditor.isDisposed()) {
1152           return;
1153         }
1154
1155         assertIsDispatchThread();
1156         final FoldingModel model = myEditor.getFoldingModel();
1157         final Runnable operation = new Runnable() {
1158           @Override
1159           public void run() {
1160             assertIsDispatchThread();
1161             for (FoldRegion region : myPendingFoldRegions) {
1162               region.setExpanded(false);
1163               model.addFoldRegion(region);
1164             }
1165             myPendingFoldRegions.clear();
1166           }
1167         };
1168         if (immediately) {
1169           model.runBatchFoldingOperation(operation);
1170         }
1171         else {
1172           model.runBatchFoldingOperationDoNotCollapseCaret(operation);
1173         }
1174       }
1175     };
1176     if (immediately || myPendingFoldRegions.size() > 100) {
1177       runnable.run();
1178     }
1179     else {
1180       myFoldingAlarm.addRequest(runnable, 50);
1181     }
1182   }
1183
1184   private void addFolding(Document document, CharSequence chars, int line, List<FoldRegion> toAdd, boolean flushOnly) {
1185     ConsoleFolding current = null;
1186     if (!flushOnly) {
1187       String commandLinePlaceholder = myCommandLineFolding.getPlaceholder(line);
1188       if (commandLinePlaceholder != null) {
1189         FoldRegion region = myEditor.getFoldingModel()
1190           .createFoldRegion(document.getLineStartOffset(line), document.getLineEndOffset(line), commandLinePlaceholder, null, false);
1191         toAdd.add(region);
1192         return;
1193       }
1194       current = foldingForLine(EditorHyperlinkSupport.getLineText(document, line, false));
1195       if (current != null) {
1196         myFolding.put(line, current);
1197       }
1198     }
1199
1200     final ConsoleFolding prevFolding = myFolding.get(line - 1);
1201     if (current == null && prevFolding != null) {
1202       final int lEnd = line - 1;
1203       int lStart = lEnd;
1204       while (prevFolding.equals(myFolding.get(lStart - 1))) lStart--;
1205
1206       for (int i = lStart; i <= lEnd; i++) {
1207         myFolding.remove(i);
1208       }
1209
1210       List<String> toFold = new ArrayList<String>(lEnd - lStart + 1);
1211       for (int i = lStart; i <= lEnd; i++) {
1212         toFold.add(EditorHyperlinkSupport.getLineText(document, i, false));
1213       }
1214
1215       int oStart = document.getLineStartOffset(lStart);
1216       if (oStart > 0) oStart--;
1217       int oEnd = CharArrayUtil.shiftBackward(chars, document.getLineEndOffset(lEnd) - 1, " \t") + 1;
1218
1219       String placeholder = prevFolding.getPlaceholderText(toFold);
1220       FoldRegion region = placeholder == null ? null : myEditor.getFoldingModel().createFoldRegion(oStart, oEnd, placeholder, null, false);
1221       if (region != null) {
1222         toAdd.add(region);
1223       }
1224     }
1225   }
1226
1227   @Nullable
1228   private static ConsoleFolding foldingForLine(String lineText) {
1229     for (ConsoleFolding folding : ConsoleFolding.EP_NAME.getExtensions()) {
1230       if (folding.shouldFoldLine(lineText)) {
1231         return folding;
1232       }
1233     }
1234     return null;
1235   }
1236
1237
1238   public static class ClearAllAction extends DumbAwareAction {
1239     private final ConsoleView myConsoleView;
1240
1241     @SuppressWarnings("unused")
1242     public ClearAllAction() {
1243       this(null);
1244     }
1245
1246     public ClearAllAction(ConsoleView consoleView) {
1247       super(ExecutionBundle.message("clear.all.from.console.action.name"), "Clear the contents of the console", AllIcons.Actions.GC);
1248       myConsoleView = consoleView;
1249     }
1250
1251     @Override
1252     public void update(AnActionEvent e) {
1253       boolean enabled = myConsoleView != null && myConsoleView.getContentSize() > 0;
1254       if (!enabled) {
1255         enabled = e.getData(LangDataKeys.CONSOLE_VIEW) != null;
1256         Editor editor = e.getData(CommonDataKeys.EDITOR);
1257         if (editor != null && editor.getDocument().getTextLength() == 0) {
1258           enabled = false;
1259         }
1260       }
1261       e.getPresentation().setEnabled(enabled);
1262     }
1263
1264     @Override
1265     public void actionPerformed(final AnActionEvent e) {
1266       final ConsoleView consoleView = myConsoleView != null ? myConsoleView : e.getData(LangDataKeys.CONSOLE_VIEW);
1267       if (consoleView != null) {
1268         consoleView.clear();
1269       }
1270     }
1271   }
1272
1273   private class MyHighlighter extends DocumentAdapter implements EditorHighlighter {
1274     private HighlighterClient myEditor;
1275
1276     @NotNull
1277     @Override
1278     public HighlighterIterator createIterator(final int startOffset) {
1279       final int startIndex = ConsoleUtil.findTokenInfoIndexByOffset(myTokens, startOffset);
1280
1281       return new HighlighterIterator() {
1282         private int myIndex = startIndex;
1283
1284         @Override
1285         public TextAttributes getTextAttributes() {
1286           return atEnd() ? null : getTokenInfo().contentType.getAttributes();
1287         }
1288
1289         @Override
1290         public int getStart() {
1291           return atEnd() ? 0 : getTokenInfo().startOffset;
1292         }
1293
1294         @Override
1295         public int getEnd() {
1296           return atEnd() ? 0 : getTokenInfo().endOffset;
1297         }
1298
1299         @Override
1300         public IElementType getTokenType() {
1301           return null;
1302         }
1303
1304         @Override
1305         public void advance() {
1306           myIndex++;
1307         }
1308
1309         @Override
1310         public void retreat() {
1311           myIndex--;
1312         }
1313
1314         @Override
1315         public boolean atEnd() {
1316           return myIndex < 0 || myIndex >= myTokens.size();
1317         }
1318
1319         @Override
1320         public Document getDocument() {
1321           return myEditor.getDocument();
1322         }
1323
1324         private TokenInfo getTokenInfo() {
1325           return myTokens.get(myIndex);
1326         }
1327       };
1328     }
1329
1330     @Override
1331     public void setText(@NotNull final CharSequence text) {
1332     }
1333
1334     @Override
1335     public void setEditor(@NotNull final HighlighterClient editor) {
1336       LOG.assertTrue(myEditor == null, "Highlighters cannot be reused with different editors");
1337       myEditor = editor;
1338     }
1339
1340     @Override
1341     public void setColorScheme(@NotNull EditorColorsScheme scheme) {
1342     }
1343   }
1344
1345   private static class MyTypedHandler extends TypedActionHandlerBase {
1346
1347     private MyTypedHandler(final TypedActionHandler originalAction) {
1348       super(originalAction);
1349     }
1350
1351     @Override
1352     public void execute(@NotNull final Editor editor, final char charTyped, @NotNull final DataContext dataContext) {
1353       final ConsoleViewImpl consoleView = editor.getUserData(CONSOLE_VIEW_IN_EDITOR_VIEW);
1354       if (consoleView == null || !consoleView.myState.isRunning() || consoleView.myIsViewer) {
1355         if (myOriginalHandler != null) myOriginalHandler.execute(editor, charTyped, dataContext);
1356       }
1357       else {
1358         final String s = String.valueOf(charTyped);
1359         SelectionModel selectionModel = editor.getSelectionModel();
1360         if (selectionModel.hasSelection()) {
1361           consoleView.replaceUserText(s, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
1362         }
1363         else {
1364           consoleView.insertUserText(s, editor.getCaretModel().getOffset());
1365         }
1366       }
1367     }
1368   }
1369
1370   private abstract static class ConsoleAction extends AnAction implements DumbAware {
1371     @Override
1372     public void actionPerformed(final AnActionEvent e) {
1373       final DataContext context = e.getDataContext();
1374       final ConsoleViewImpl console = getRunningConsole(context);
1375       execute(console, context);
1376     }
1377
1378     protected abstract void execute(ConsoleViewImpl console, final DataContext context);
1379
1380     @Override
1381     public void update(final AnActionEvent e) {
1382       final ConsoleViewImpl console = getRunningConsole(e.getDataContext());
1383       e.getPresentation().setEnabled(console != null);
1384     }
1385
1386     @Nullable
1387     private static ConsoleViewImpl getRunningConsole(final DataContext context) {
1388       final Editor editor = CommonDataKeys.EDITOR.getData(context);
1389       if (editor != null) {
1390         final ConsoleViewImpl console = editor.getUserData(CONSOLE_VIEW_IN_EDITOR_VIEW);
1391         if (console != null && console.myState.isRunning()) {
1392           return console;
1393         }
1394       }
1395       return null;
1396     }
1397   }
1398
1399   private static class EnterHandler extends ConsoleAction {
1400     @Override
1401     public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
1402       consoleView.print("\n", ConsoleViewContentType.USER_INPUT);
1403       consoleView.flushDeferredText();
1404       final Editor editor = consoleView.myEditor;
1405       editor.getCaretModel().moveToOffset(editor.getDocument().getTextLength());
1406       editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1407     }
1408   }
1409
1410   private static class PasteHandler extends ConsoleAction {
1411     @Override
1412     public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
1413       String s = CopyPasteManager.getInstance().getContents(DataFlavor.stringFlavor);
1414       if (s == null) return;
1415       ApplicationManager.getApplication().assertIsDispatchThread();
1416       Editor editor = consoleView.myEditor;
1417       SelectionModel selectionModel = editor.getSelectionModel();
1418       if (selectionModel.hasSelection()) {
1419         consoleView.replaceUserText(s, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
1420       }
1421       else {
1422         consoleView.insertUserText(s, editor.getCaretModel().getOffset());
1423       }
1424     }
1425   }
1426
1427   private static class BackSpaceHandler extends ConsoleAction {
1428     @Override
1429     public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
1430       final Editor editor = consoleView.myEditor;
1431
1432       if (IncrementalSearchHandler.isHintVisible(editor)) {
1433         getDefaultActionHandler().execute(editor, context);
1434         return;
1435       }
1436
1437       final Document document = editor.getDocument();
1438       final int length = document.getTextLength();
1439       if (length == 0) {
1440         return;
1441       }
1442
1443       ApplicationManager.getApplication().assertIsDispatchThread();
1444
1445       SelectionModel selectionModel = editor.getSelectionModel();
1446       if (selectionModel.hasSelection()) {
1447         consoleView.deleteUserText(selectionModel.getSelectionStart(),
1448                                    selectionModel.getSelectionEnd() - selectionModel.getSelectionStart());
1449       }
1450       else if (editor.getCaretModel().getOffset() > 0) {
1451         consoleView.deleteUserText(editor.getCaretModel().getOffset() - 1, 1);
1452       }
1453     }
1454
1455     private static EditorActionHandler getDefaultActionHandler() {
1456       return EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE);
1457     }
1458   }
1459
1460   private static class DeleteHandler extends ConsoleAction {
1461     @Override
1462     public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
1463       final Editor editor = consoleView.myEditor;
1464
1465       if (IncrementalSearchHandler.isHintVisible(editor)) {
1466         getDefaultActionHandler().execute(editor, context);
1467         return;
1468       }
1469
1470       final Document document = editor.getDocument();
1471       final int length = document.getTextLength();
1472       if (length == 0) {
1473         return;
1474       }
1475
1476       ApplicationManager.getApplication().assertIsDispatchThread();
1477       SelectionModel selectionModel = editor.getSelectionModel();
1478       if (selectionModel.hasSelection()) {
1479         consoleView.deleteUserText(selectionModel.getSelectionStart(),
1480                                    selectionModel.getSelectionEnd() - selectionModel.getSelectionStart());
1481       }
1482       else {
1483         consoleView.deleteUserText(editor.getCaretModel().getOffset(), 1);
1484       }
1485     }
1486
1487     private static EditorActionHandler getDefaultActionHandler() {
1488       return EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE);
1489     }
1490   }
1491
1492   @Override
1493   public JComponent getPreferredFocusableComponent() {
1494     //ensure editor created
1495     getComponent();
1496     return myEditor.getContentComponent();
1497   }
1498
1499
1500   // navigate up/down in stack trace
1501   @Override
1502   public boolean hasNextOccurence() {
1503     return calcNextOccurrence(1) != null;
1504   }
1505
1506   @Override
1507   public boolean hasPreviousOccurence() {
1508     return calcNextOccurrence(-1) != null;
1509   }
1510
1511   @Override
1512   public OccurenceInfo goNextOccurence() {
1513     return calcNextOccurrence(1);
1514   }
1515
1516   @Nullable
1517   protected OccurenceInfo calcNextOccurrence(final int delta) {
1518     final EditorHyperlinkSupport hyperlinks = myHyperlinks;
1519     if (hyperlinks == null) {
1520       return null;
1521     }
1522
1523     return EditorHyperlinkSupport.getNextOccurrence(myEditor, delta, new Consumer<RangeHighlighter>() {
1524       @Override
1525       public void consume(RangeHighlighter next) {
1526         int offset = next.getStartOffset();
1527         scrollTo(offset);
1528         final HyperlinkInfo hyperlinkInfo = EditorHyperlinkSupport.getHyperlinkInfo(next);
1529         if (hyperlinkInfo instanceof BrowserHyperlinkInfo) {
1530           return;
1531         }
1532         if (hyperlinkInfo instanceof HyperlinkInfoBase) {
1533           VisualPosition position = myEditor.offsetToVisualPosition(offset);
1534           Point point = myEditor.visualPositionToXY(new VisualPosition(position.getLine() + 1, position.getColumn()));
1535           ((HyperlinkInfoBase)hyperlinkInfo).navigate(myProject, new RelativePoint(myEditor.getContentComponent(), point));
1536         }
1537         else if (hyperlinkInfo != null) {
1538           hyperlinkInfo.navigate(myProject);
1539         }
1540       }
1541     });
1542   }
1543
1544   @Override
1545   public OccurenceInfo goPreviousOccurence() {
1546     return calcNextOccurrence(-1);
1547   }
1548
1549   @Override
1550   public String getNextOccurenceActionName() {
1551     return ExecutionBundle.message("down.the.stack.trace");
1552   }
1553
1554   @Override
1555   public String getPreviousOccurenceActionName() {
1556     return ExecutionBundle.message("up.the.stack.trace");
1557   }
1558
1559   public void addCustomConsoleAction(@NotNull AnAction action) {
1560     customActions.add(action);
1561   }
1562
1563   @Override
1564   @NotNull
1565   public AnAction[] createConsoleActions() {
1566     //Initializing prev and next occurrences actions
1567     final CommonActionsManager actionsManager = CommonActionsManager.getInstance();
1568     final AnAction prevAction = actionsManager.createPrevOccurenceAction(this);
1569     prevAction.getTemplatePresentation().setText(getPreviousOccurenceActionName());
1570     final AnAction nextAction = actionsManager.createNextOccurenceAction(this);
1571     nextAction.getTemplatePresentation().setText(getNextOccurenceActionName());
1572
1573     final AnAction switchSoftWrapsAction = new ToggleUseSoftWrapsToolbarAction(SoftWrapAppliancePlaces.CONSOLE) {
1574
1575       /**
1576        * There is a possible case that more than console is open and user toggles soft wraps mode at one of them. We want
1577        * to update another console(s) representation as well when they are switched on after that. Hence, we remember last
1578        * used soft wraps mode and perform update if we see that the current value differs from the stored.
1579        */
1580       private boolean myLastIsSelected;
1581
1582       @Override
1583       protected Editor getEditor(AnActionEvent e) {
1584         return myEditor;
1585       }
1586
1587       @Override
1588       public boolean isSelected(AnActionEvent e) {
1589         boolean result = super.isSelected(e);
1590         if (result ^ myLastIsSelected) {
1591           setSelected(null, result);
1592         }
1593         return myLastIsSelected = result;
1594       }
1595
1596       @Override
1597       public void setSelected(AnActionEvent e, final boolean state) {
1598         super.setSelected(e, state);
1599         if (myEditor == null) {
1600           return;
1601         }
1602
1603         final String placeholder = myCommandLineFolding.getPlaceholder(0);
1604         final FoldingModel foldingModel = myEditor.getFoldingModel();
1605         final int firstLineEnd = myEditor.getDocument().getLineEndOffset(0);
1606         foldingModel.runBatchFoldingOperation(new Runnable() {
1607           @Override
1608           public void run() {
1609             FoldRegion[] regions = foldingModel.getAllFoldRegions();
1610             if (regions.length > 0 && regions[0].getStartOffset() == 0 && regions[0].getEndOffset() == firstLineEnd) {
1611               foldingModel.removeFoldRegion(regions[0]);
1612             }
1613             if (placeholder != null) {
1614               FoldRegion foldRegion = foldingModel.addFoldRegion(0, firstLineEnd, placeholder);
1615               if (foldRegion != null) {
1616                 foldRegion.setExpanded(false);
1617               }
1618             }
1619           }
1620         });
1621       }
1622     };
1623     final AnAction autoScrollToTheEndAction = new ScrollToTheEndToolbarAction(myEditor);
1624
1625     //Initializing custom actions
1626     final AnAction[] consoleActions = new AnAction[6 + customActions.size()];
1627     consoleActions[0] = prevAction;
1628     consoleActions[1] = nextAction;
1629     consoleActions[2] = switchSoftWrapsAction;
1630     consoleActions[3] = autoScrollToTheEndAction;
1631     consoleActions[4] = ActionManager.getInstance().getAction("Print");
1632     consoleActions[5] = new ClearAllAction(this);
1633     for (int i = 0; i < customActions.size(); ++i) {
1634       consoleActions[i + 6] = customActions.get(i);
1635     }
1636     ConsoleActionsPostProcessor[] postProcessors = Extensions.getExtensions(ConsoleActionsPostProcessor.EP_NAME);
1637     AnAction[] result = consoleActions;
1638     for (ConsoleActionsPostProcessor postProcessor : postProcessors) {
1639       result = postProcessor.postProcess(this, result);
1640     }
1641     return result;
1642   }
1643
1644   @Override
1645   public void allowHeavyFilters() {
1646     myAllowHeavyFilters = true;
1647   }
1648
1649   @Override
1650   public void addChangeListener(@NotNull final ChangeListener listener, @NotNull final Disposable parent) {
1651     myListeners.add(listener);
1652     Disposer.register(parent, new Disposable() {
1653       @Override
1654       public void dispose() {
1655         myListeners.remove(listener);
1656       }
1657     });
1658   }
1659
1660   /**
1661    * insert text to document
1662    *
1663    * @param s      inserted text
1664    * @param offset relatively to all document text
1665    */
1666   private void insertUserText(final String s, int offset) {
1667     ApplicationManager.getApplication().assertIsDispatchThread();
1668     final ConsoleViewImpl consoleView = this;
1669     final ConsoleBuffer buffer = consoleView.myBuffer;
1670     final Editor editor = consoleView.myEditor;
1671     final Document document = editor.getDocument();
1672     final int startOffset;
1673
1674     String textToUse = StringUtil.convertLineSeparators(s);
1675     synchronized (consoleView.LOCK) {
1676       if (consoleView.myTokens.isEmpty()) {
1677         addToken(0, null, ConsoleViewContentType.SYSTEM_OUTPUT);
1678       }
1679       final TokenInfo info = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1680       if (info.contentType != ConsoleViewContentType.USER_INPUT && !StringUtil.containsChar(textToUse, '\n')) {
1681         consoleView.print(textToUse, ConsoleViewContentType.USER_INPUT);
1682         consoleView.flushDeferredText();
1683         editor.getCaretModel().moveToOffset(document.getTextLength());
1684         editor.getSelectionModel().removeSelection();
1685         return;
1686       }
1687       if (info.contentType != ConsoleViewContentType.USER_INPUT) {
1688         insertUserText("temp", offset);
1689         final TokenInfo newInfo = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1690         replaceUserText(textToUse, newInfo.startOffset, newInfo.endOffset);
1691         return;
1692       }
1693
1694       final int deferredOffset = myContentSize - buffer.getLength() - buffer.getUserInputLength();
1695       if (offset > info.endOffset) {
1696         startOffset = info.endOffset;
1697       }
1698       else {
1699         startOffset = Math.max(deferredOffset, Math.max(info.startOffset, offset));
1700       }
1701
1702       buffer.addUserText(startOffset - deferredOffset, textToUse);
1703
1704       int charCountToAdd = textToUse.length();
1705       info.endOffset += charCountToAdd;
1706       consoleView.myContentSize += charCountToAdd;
1707     }
1708
1709     try {
1710       myInDocumentUpdate = true;
1711       document.insertString(startOffset, textToUse);
1712     }
1713     finally {
1714       myInDocumentUpdate = false;
1715     }
1716     // Math.max is needed when cyclic buffer is used
1717     editor.getCaretModel().moveToOffset(Math.min(startOffset + textToUse.length(), document.getTextLength()));
1718     editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1719   }
1720
1721   /**
1722    * replace text
1723    *
1724    * @param s     text for replace
1725    * @param start relatively to all document text
1726    * @param end   relatively to all document text
1727    */
1728   private void replaceUserText(final String s, int start, int end) {
1729     if (start == end) {
1730       insertUserText(s, start);
1731       return;
1732     }
1733     final ConsoleViewImpl consoleView = this;
1734     final ConsoleBuffer buffer = consoleView.myBuffer;
1735     final Editor editor = consoleView.myEditor;
1736     final Document document = editor.getDocument();
1737     final int startOffset;
1738     final int endOffset;
1739
1740     synchronized (consoleView.LOCK) {
1741       if (consoleView.myTokens.isEmpty()) return;
1742       final TokenInfo info = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1743       if (info.contentType != ConsoleViewContentType.USER_INPUT) {
1744         consoleView.print(s, ConsoleViewContentType.USER_INPUT);
1745         consoleView.flushDeferredText();
1746         editor.getCaretModel().moveToOffset(document.getTextLength());
1747         editor.getSelectionModel().removeSelection();
1748         return;
1749       }
1750       if (buffer.getUserInputLength() <= 0) return;
1751
1752       final int deferredOffset = myContentSize - buffer.getLength() - buffer.getUserInputLength();
1753
1754       startOffset = getStartOffset(start, info, deferredOffset);
1755       endOffset = getEndOffset(end, info);
1756
1757       if (startOffset == -1 ||
1758           endOffset == -1 ||
1759           endOffset <= startOffset) {
1760         editor.getSelectionModel().removeSelection();
1761         editor.getCaretModel().moveToOffset(start);
1762         return;
1763       }
1764       int charCountToReplace = s.length() - endOffset + startOffset;
1765
1766       buffer.replaceUserText(startOffset - deferredOffset, endOffset - deferredOffset, s);
1767
1768       info.endOffset += charCountToReplace;
1769       if (info.startOffset == info.endOffset) {
1770         consoleView.myTokens.remove(consoleView.myTokens.size() - 1);
1771       }
1772       consoleView.myContentSize += charCountToReplace;
1773     }
1774
1775     try {
1776       myInDocumentUpdate = true;
1777       document.replaceString(startOffset, endOffset, s);
1778     }
1779     finally {
1780       myInDocumentUpdate = false;
1781     }
1782     editor.getCaretModel().moveToOffset(startOffset + s.length());
1783     editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1784     editor.getSelectionModel().removeSelection();
1785   }
1786
1787   /**
1788    * delete text
1789    *
1790    * @param offset relatively to all document text
1791    * @param length length of deleted text
1792    */
1793   private void deleteUserText(int offset, int length) {
1794     ConsoleViewImpl consoleView = this;
1795     ConsoleBuffer buffer = consoleView.myBuffer;
1796     final Editor editor = consoleView.myEditor;
1797     final Document document = editor.getDocument();
1798     final int startOffset;
1799     final int endOffset;
1800
1801     synchronized (consoleView.LOCK) {
1802       if (consoleView.myTokens.isEmpty()) return;
1803       final TokenInfo info = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1804       if (info.contentType != ConsoleViewContentType.USER_INPUT) return;
1805       if (myBuffer.getUserInputLength() == 0) return;
1806
1807       final int deferredOffset = myContentSize - buffer.getLength() - buffer.getUserInputLength();
1808       startOffset = getStartOffset(offset, info, deferredOffset);
1809       endOffset = getEndOffset(offset + length, info);
1810       if (startOffset == -1 ||
1811           endOffset == -1 ||
1812           endOffset <= startOffset ||
1813           startOffset < deferredOffset) {
1814         editor.getSelectionModel().removeSelection();
1815         editor.getCaretModel().moveToOffset(offset);
1816         return;
1817       }
1818
1819       buffer.removeUserText(startOffset - deferredOffset, endOffset - deferredOffset);
1820     }
1821
1822     try {
1823       myInDocumentUpdate = true;
1824       document.deleteString(startOffset, endOffset);
1825     }
1826     finally {
1827       myInDocumentUpdate = false;
1828     }
1829     editor.getCaretModel().moveToOffset(startOffset);
1830     editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1831     editor.getSelectionModel().removeSelection();
1832   }
1833
1834   //util methods for add, replace, delete methods
1835   private static int getStartOffset(int offset, TokenInfo info, int deferredOffset) {
1836     int startOffset;
1837     if (offset >= info.startOffset && offset < info.endOffset) {
1838       startOffset = Math.max(offset, deferredOffset);
1839     }
1840     else if (offset < info.startOffset) {
1841       startOffset = Math.max(info.startOffset, deferredOffset);
1842     }
1843     else {
1844       startOffset = -1;
1845     }
1846     return startOffset;
1847   }
1848
1849   private static int getEndOffset(int offset, TokenInfo info) {
1850     int endOffset;
1851     if (offset > info.endOffset) {
1852       endOffset = info.endOffset;
1853     }
1854     else if (offset <= info.startOffset) {
1855       endOffset = -1;
1856     }
1857     else {
1858       endOffset = offset;
1859     }
1860     return endOffset;
1861   }
1862
1863   public boolean isRunning() {
1864     return myState.isRunning();
1865   }
1866
1867   /**
1868    * Command line used to launch application/test from idea may be quite long.
1869    * Hence, it takes many visual lines during representation if soft wraps are enabled
1870    * or, otherwise, takes many columns and makes horizontal scrollbar thumb too small.
1871    * <p/>
1872    * Our point is to fold such long command line and represent it as a single visual line by default.
1873    */
1874   private class CommandLineFolding extends ConsoleFolding {
1875
1876     /**
1877      * Checks if target line should be folded and returns its placeholder if the examination succeeds.
1878      *
1879      * @param line index of line to check
1880      * @return placeholder text if given line should be folded; <code>null</code> otherwise
1881      */
1882     @Nullable
1883     private String getPlaceholder(int line) {
1884       if (myEditor == null || line != 0) {
1885         return null;
1886       }
1887
1888       String text = EditorHyperlinkSupport.getLineText(myEditor.getDocument(), 0, false);
1889       // Don't fold the first line if the line is not that big.
1890       if (text.length() < 1000) {
1891         return null;
1892       }
1893       int index = 0;
1894       if (text.charAt(0) == '"') {
1895         index = text.indexOf('"', 1) + 1;
1896       }
1897       if (index == 0) {
1898         boolean nonWhiteSpaceFound = false;
1899         for (; index < text.length(); index++) {
1900           char c = text.charAt(index);
1901           if (c != ' ' && c != '\t') {
1902             nonWhiteSpaceFound = true;
1903             continue;
1904           }
1905           if (nonWhiteSpaceFound) {
1906             break;
1907           }
1908         }
1909       }
1910       assert index <= text.length();
1911       return text.substring(0, index) + " ...";
1912     }
1913
1914     @Override
1915     public boolean shouldFoldLine(String line) {
1916       return false;
1917     }
1918
1919     @Override
1920     public String getPlaceholderText(List<String> lines) {
1921       // Is not expected to be called.
1922       return "<...>";
1923     }
1924   }
1925
1926   private class MyFlushRunnable implements Runnable {
1927     private volatile boolean myValid = true;
1928     @Override
1929     public final void run() {
1930       synchronized (myCurrentRequests) {
1931         myCurrentRequests.remove(this);
1932       }
1933       if (myValid) {
1934         doRun();
1935       }
1936     }
1937
1938     protected void doRun() {
1939       flushDeferredText();
1940     }
1941
1942     public void invalidate() {
1943       myValid = false;
1944     }
1945
1946     public boolean isValid() {
1947       return myValid;
1948     }
1949
1950     @Override
1951     public boolean equals(Object o) {
1952       if (this == o) return true;
1953       if (o == null || getClass() != o.getClass()) return false;
1954
1955       MyFlushRunnable runnable = (MyFlushRunnable)o;
1956
1957       return myValid == runnable.myValid;
1958     }
1959
1960     @Override
1961     public int hashCode() {
1962       return getClass().hashCode();
1963     }
1964   }
1965
1966   private final class MyClearRunnable extends MyFlushRunnable {
1967     @Override
1968     public void doRun() {
1969       flushDeferredText(true);
1970     }
1971   }
1972
1973   @NotNull
1974   public Project getProject() {
1975     return myProject;
1976   }
1977
1978   private class HyperlinkNavigationAction extends DumbAwareAction {
1979     @Override
1980     public void actionPerformed(AnActionEvent e) {
1981       Runnable runnable = myHyperlinks.getLinkNavigationRunnable(myEditor.getCaretModel().getLogicalPosition());
1982       assert runnable != null;
1983       runnable.run();
1984     }
1985
1986     @Override
1987     public void update(AnActionEvent e) {
1988       e.getPresentation().setEnabled(myHyperlinks.getLinkNavigationRunnable(myEditor.getCaretModel().getLogicalPosition()) != null);
1989     }
1990   }
1991
1992   @NotNull
1993   public String getText() {
1994     return myEditor.getDocument().getText();
1995   }
1996 }
1997