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