2 * Copyright 2000-2016 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.intellij.execution.impl;
19 import com.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.command.undo.UndoUtil;
40 import com.intellij.openapi.diagnostic.Logger;
41 import com.intellij.openapi.editor.*;
42 import com.intellij.openapi.editor.actionSystem.*;
43 import com.intellij.openapi.editor.actions.ScrollToTheEndToolbarAction;
44 import com.intellij.openapi.editor.actions.ToggleUseSoftWrapsToolbarAction;
45 import com.intellij.openapi.editor.colors.EditorColorsScheme;
46 import com.intellij.openapi.editor.event.*;
47 import com.intellij.openapi.editor.ex.DocumentEx;
48 import com.intellij.openapi.editor.ex.EditorEx;
49 import com.intellij.openapi.editor.ex.util.EditorUtil;
50 import com.intellij.openapi.editor.highlighter.EditorHighlighter;
51 import com.intellij.openapi.editor.highlighter.HighlighterClient;
52 import com.intellij.openapi.editor.highlighter.HighlighterIterator;
53 import com.intellij.openapi.editor.impl.DocumentImpl;
54 import com.intellij.openapi.editor.impl.softwrap.SoftWrapAppliancePlaces;
55 import com.intellij.openapi.editor.markup.RangeHighlighter;
56 import com.intellij.openapi.editor.markup.TextAttributes;
57 import com.intellij.openapi.extensions.Extensions;
58 import com.intellij.openapi.fileEditor.OpenFileDescriptor;
59 import com.intellij.openapi.ide.CopyPasteManager;
60 import com.intellij.openapi.keymap.Keymap;
61 import com.intellij.openapi.keymap.KeymapManager;
62 import com.intellij.openapi.project.DumbAware;
63 import com.intellij.openapi.project.DumbAwareAction;
64 import com.intellij.openapi.project.DumbService;
65 import com.intellij.openapi.project.Project;
66 import com.intellij.openapi.util.*;
67 import com.intellij.openapi.util.registry.Registry;
68 import com.intellij.openapi.util.text.StringUtil;
69 import com.intellij.psi.search.GlobalSearchScope;
70 import com.intellij.psi.tree.IElementType;
71 import com.intellij.ui.EditorNotificationPanel;
72 import com.intellij.ui.awt.RelativePoint;
73 import com.intellij.util.*;
74 import com.intellij.util.text.CharArrayUtil;
75 import com.intellij.util.ui.UIUtil;
76 import gnu.trove.TIntObjectHashMap;
77 import org.jetbrains.annotations.NonNls;
78 import org.jetbrains.annotations.NotNull;
79 import org.jetbrains.annotations.Nullable;
83 import java.awt.datatransfer.DataFlavor;
84 import java.awt.event.MouseAdapter;
85 import java.awt.event.MouseEvent;
86 import java.awt.event.MouseWheelEvent;
87 import java.io.IOException;
89 import java.util.List;
90 import java.util.concurrent.CopyOnWriteArraySet;
92 public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableConsoleView, DataProvider, OccurenceNavigator {
93 @NonNls private static final String CONSOLE_VIEW_POPUP_MENU = "ConsoleView.PopupMenu";
94 private static final Logger LOG = Logger.getInstance("#com.intellij.execution.impl.ConsoleViewImpl");
96 private static final int DEFAULT_FLUSH_DELAY = SystemProperties.getIntProperty("console.flush.delay.ms", 200);
98 private static final CharMatcher NEW_LINE_MATCHER = CharMatcher.anyOf("\n\r");
100 public static final Key<ConsoleViewImpl> CONSOLE_VIEW_IN_EDITOR_VIEW = Key.create("CONSOLE_VIEW_IN_EDITOR_VIEW");
102 private static boolean ourTypedHandlerInitialized;
104 private static synchronized void initTypedHandler() {
105 if (ourTypedHandlerInitialized) return;
106 final EditorActionManager actionManager = EditorActionManager.getInstance();
107 final TypedAction typedAction = actionManager.getTypedAction();
108 typedAction.setupHandler(new MyTypedHandler(typedAction.getHandler()));
109 ourTypedHandlerInitialized = true;
113 private final CommandLineFolding myCommandLineFolding = new CommandLineFolding();
115 private final DisposedPsiManagerCheck myPsiDisposedCheck;
116 private final boolean myIsViewer;
118 private ConsoleState myState;
120 private final Alarm mySpareTimeAlarm = new Alarm(this);
122 private final Alarm myHeavyAlarm;
123 private volatile int myHeavyUpdateTicket;
125 private final Collection<ChangeListener> myListeners = new CopyOnWriteArraySet<ChangeListener>();
126 private final List<AnAction> customActions = new ArrayList<AnAction>();
127 private final ConsoleBuffer myBuffer = new ConsoleBuffer();
128 private boolean myUpdateFoldingsEnabled = true;
129 private EditorHyperlinkSupport myHyperlinks;
130 private MyDiffContainer myJLayeredPane;
131 private JPanel myMainPanel;
132 private boolean myAllowHeavyFilters;
133 private boolean myLastStickingToEnd;
134 private boolean myCancelStickToEnd;
136 private boolean myTooMuchOfOutput;
137 private boolean myInDocumentUpdate;
139 // If true, then a document is being cleared right now.
140 // Should be accessed in EDT only.
141 @SuppressWarnings("FieldAccessedSynchronizedAndUnsynchronized")
142 private boolean myDocumentClearing;
143 private int myLastAddedTextLength;
144 private int consoleTooMuchTextBufferRatio;
146 public Editor getEditor() {
150 public EditorHyperlinkSupport getHyperlinks() {
154 public void scrollToEnd() {
155 if (myEditor == null) return;
156 EditorUtil.scrollToTheEnd(myEditor);
157 myCancelStickToEnd = false;
160 public void foldImmediately() {
161 ApplicationManager.getApplication().assertIsDispatchThread();
162 if (!myFlushAlarm.isEmpty()) {
163 cancelAllFlushRequests();
164 new MyFlushRunnable().run();
167 myFoldingAlarm.cancelAllRequests();
169 myPendingFoldRegions.clear();
170 final FoldingModel model = myEditor.getFoldingModel();
171 model.runBatchFoldingOperation(new Runnable() {
174 for (FoldRegion region : model.getAllFoldRegions()) {
175 model.removeFoldRegion(region);
181 updateFoldings(0, myEditor.getDocument().getLineCount() - 1, true);
184 static class TokenInfo {
185 final ConsoleViewContentType contentType;
189 TokenInfo(ConsoleViewContentType contentType, int startOffset, int endOffset) {
190 this.contentType = contentType;
191 this.startOffset = startOffset;
192 this.endOffset = endOffset;
195 public int getLength() {
196 return endOffset - startOffset;
200 public String toString() {
201 return contentType + "[" + startOffset + ";" + endOffset + "]";
205 public HyperlinkInfo getHyperlinkInfo() {
210 static class HyperlinkTokenInfo extends TokenInfo {
211 private final HyperlinkInfo myHyperlinkInfo;
213 HyperlinkTokenInfo(final ConsoleViewContentType contentType, final int startOffset, final int endOffset, HyperlinkInfo hyperlinkInfo) {
214 super(contentType, startOffset, endOffset);
215 myHyperlinkInfo = hyperlinkInfo;
219 public HyperlinkInfo getHyperlinkInfo() {
220 return myHyperlinkInfo;
224 private final Project myProject;
226 private boolean myOutputPaused;
228 private EditorEx myEditor;
230 private final Object LOCK = new Object();
233 * Holds number of symbols managed by the current console.
235 * Total number is assembled as a sum of symbols that are already pushed to the document and number of deferred symbols that
236 * are awaiting to be pushed to the document.
238 private int myContentSize;
241 * Holds information about lexical division by offsets of the text already pushed to document.
243 * Target offsets are anchored to the document here.
245 private final List<TokenInfo> myTokens = new ArrayList<TokenInfo>();
247 private final TIntObjectHashMap<ConsoleFolding> myFolding = new TIntObjectHashMap<ConsoleFolding>();
249 private String myHelpId;
251 private final Alarm myFlushUserInputAlarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, this);
252 private final Alarm myFlushAlarm = new Alarm(this);
254 private final Set<MyFlushRunnable> myCurrentRequests = new HashSet<MyFlushRunnable>();
256 protected final CompositeFilter myFilters;
258 @Nullable private final InputFilter myInputMessageFilter;
260 private final Alarm myFoldingAlarm = new Alarm(this);
261 private final List<FoldRegion> myPendingFoldRegions = new ArrayList<FoldRegion>();
263 public ConsoleViewImpl(final Project project, boolean viewer) {
264 this(project, GlobalSearchScope.allScope(project), viewer, true);
267 public ConsoleViewImpl(@NotNull final Project project,
268 @NotNull GlobalSearchScope searchScope,
270 boolean usePredefinedMessageFilter) {
271 this(project, searchScope, viewer,
272 new ConsoleState.NotStartedStated() {
274 public ConsoleState attachTo(ConsoleViewImpl console, ProcessHandler processHandler) {
275 return new ConsoleViewRunningState(console, processHandler, this, true, true);
278 usePredefinedMessageFilter);
281 protected ConsoleViewImpl(@NotNull final Project project,
282 @NotNull GlobalSearchScope searchScope,
284 @NotNull final ConsoleState initialState,
285 boolean usePredefinedMessageFilter)
287 super(new BorderLayout());
290 myState = initialState;
291 myPsiDisposedCheck = new DisposedPsiManagerCheck(project);
294 myFilters = new CompositeFilter(project);
295 if (usePredefinedMessageFilter) {
296 for (ConsoleFilterProvider eachProvider : Extensions.getExtensions(ConsoleFilterProvider.FILTER_PROVIDERS)) {
298 if (eachProvider instanceof ConsoleDependentFilterProvider) {
299 filters = ((ConsoleDependentFilterProvider)eachProvider).getDefaultFilters(this, project, searchScope);
301 else if (eachProvider instanceof ConsoleFilterProviderEx) {
302 filters = ((ConsoleFilterProviderEx)eachProvider).getDefaultFilters(project, searchScope);
305 filters = eachProvider.getDefaultFilters(project);
307 for (Filter filter : filters) {
308 myFilters.addFilter(filter);
312 myFilters.setForceUseAllFilters(true);
313 myHeavyUpdateTicket = 0;
314 myHeavyAlarm = myFilters.isAnyHeavy() ? new Alarm(Alarm.ThreadToUse.POOLED_THREAD, this) : null;
317 ConsoleInputFilterProvider[] inputFilters = Extensions.getExtensions(ConsoleInputFilterProvider.INPUT_FILTER_PROVIDERS);
318 if (inputFilters.length > 0) {
319 CompositeInputFilter compositeInputFilter = new CompositeInputFilter(project);
320 myInputMessageFilter = compositeInputFilter;
321 for (ConsoleInputFilterProvider eachProvider : inputFilters) {
322 InputFilter[] filters = eachProvider.getDefaultFilters(project);
323 for (InputFilter filter : filters) {
324 compositeInputFilter.addFilter(filter);
329 myInputMessageFilter = null;
332 consoleTooMuchTextBufferRatio = Registry.intValue("console.too.much.text.buffer.ratio");
334 project.getMessageBus().connect(this).subscribe(DumbService.DUMB_MODE, new DumbService.DumbModeListener() {
335 private long myLastStamp;
338 public void enteredDumbMode() {
339 if (myEditor == null) return;
340 myLastStamp = myEditor.getDocument().getModificationStamp();
345 public void exitDumbMode() {
346 ApplicationManager.getApplication().invokeLater(new Runnable() {
349 if (myEditor == null || project.isDisposed() || DumbService.getInstance(project).isDumb()) return;
351 DocumentEx document = myEditor.getDocument();
352 if (myLastStamp != document.getModificationStamp()) {
353 clearHyperlinkAndFoldings();
354 highlightHyperlinksAndFoldings(document.createRangeMarker(0, 0));
364 public void attachToProcess(final ProcessHandler processHandler) {
365 myState = myState.attachTo(this, processHandler);
369 public void clear() {
370 if (myEditor == null) return;
371 synchronized (LOCK) {
372 // real document content will be cleared on next flush;
377 if (myFlushAlarm.isDisposed()) return;
378 cancelAllFlushRequests();
379 addFlushRequest(new MyClearRunnable());
384 public void scrollTo(final int offset) {
385 if (myEditor == null) return;
386 class ScrollRunnable extends MyFlushRunnable {
387 private final int myOffset = offset;
390 public void doRun() {
392 if (myEditor == null) return;
393 int moveOffset = Math.min(offset, myEditor.getDocument().getTextLength());
394 if (myBuffer.isUseCyclicBuffer() && moveOffset >= myEditor.getDocument().getTextLength()) {
397 myEditor.getCaretModel().moveToOffset(moveOffset);
398 myEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
402 public boolean equals(Object o) {
403 return super.equals(o) && myOffset == ((ScrollRunnable)o).myOffset;
406 addFlushRequest(new ScrollRunnable());
409 public void requestScrollingToEnd() {
410 if (myEditor == null) {
414 addFlushRequest(new MyFlushRunnable() {
416 public void doRun() {
418 if (myEditor == null || myFlushAlarm.isDisposed()) {
427 private void addFlushRequest(MyFlushRunnable scrollRunnable) {
428 addFlushRequest(scrollRunnable, 0);
431 private void addFlushRequest(@NotNull MyFlushRunnable flushRunnable, final int millis) {
432 synchronized (myCurrentRequests) {
433 if (!myFlushAlarm.isDisposed() && myCurrentRequests.add(flushRunnable)) {
434 myFlushAlarm.addRequest(flushRunnable, millis, getStateForUpdate());
440 private static void assertIsDispatchThread() {
441 ApplicationManager.getApplication().assertIsDispatchThread();
445 public void setOutputPaused(final boolean value) {
446 myOutputPaused = value;
448 requestFlushImmediately();
453 public boolean isOutputPaused() {
454 return myOutputPaused;
457 public void setEmulateCarriageReturn(boolean emulate) {
458 myBuffer.setKeepSlashR(emulate);
462 public boolean hasDeferredOutput() {
463 synchronized (LOCK) {
464 return myBuffer.getLength() > 0;
469 public void performWhenNoDeferredOutput(final Runnable runnable) {
470 //Q: implement in another way without timer?
471 if (!hasDeferredOutput()) {
475 performLaterWhenNoDeferredOutput(runnable);
479 private void performLaterWhenNoDeferredOutput(final Runnable runnable) {
480 if (mySpareTimeAlarm.isDisposed()) return;
481 mySpareTimeAlarm.addRequest(
485 performWhenNoDeferredOutput(runnable);
489 ModalityState.stateForComponent(myJLayeredPane)
494 public JComponent getComponent() {
495 if (myMainPanel == null) {
496 myMainPanel = new JPanel(new BorderLayout());
497 myJLayeredPane = new MyDiffContainer(myMainPanel, myFilters.getUpdateMessage());
498 Disposer.register(this, myJLayeredPane);
499 add(myJLayeredPane, BorderLayout.CENTER);
502 if (myEditor == null) {
504 requestFlushImmediately();
505 myMainPanel.add(createCenterComponent(), BorderLayout.CENTER);
511 * Adds transparent (actually, non-opaque) component over console.
512 * It will be as big as console. Use it to draw on console because it does not prevent user from console usage.
514 * @param component component to add
516 public final void addLayerToPane(@NotNull final JComponent component) {
517 getComponent(); // Make sure component exists
518 component.setOpaque(false);
519 component.setVisible(true);
520 myJLayeredPane.add(component, 0);
523 private void initConsoleEditor() {
524 myEditor = createConsoleEditor();
525 registerConsoleEditorActions();
526 myEditor.getScrollPane().setBorder(null);
527 MouseAdapter mouseListener = new MouseAdapter() {
529 public void mousePressed(MouseEvent e) {
530 updateStickToEndState(true);
534 public void mouseDragged(MouseEvent e) {
535 updateStickToEndState(false);
539 public void mouseWheelMoved(MouseWheelEvent e) {
540 updateStickToEndState(false);
543 myEditor.getScrollPane().addMouseWheelListener(mouseListener);
544 myEditor.getScrollPane().getVerticalScrollBar().addMouseListener(mouseListener);
545 myEditor.getScrollPane().getVerticalScrollBar().addMouseMotionListener(mouseListener);
546 myHyperlinks = new EditorHyperlinkSupport(myEditor, myProject);
547 myEditor.getScrollingModel().addVisibleAreaListener(new VisibleAreaListener() {
549 public void visibleAreaChanged(VisibleAreaEvent e) {
550 // There is a possible case that the console text is populated while the console is not shown (e.g. we're debugging and
551 // 'Debugger' tab is active while 'Console' is not). It's also possible that newly added text contains long lines that
552 // are soft wrapped. We want to update viewport position then when the console becomes visible.
553 Rectangle oldR = e.getOldRectangle();
555 if (oldR != null && oldR.height <= 0 &&
556 e.getNewRectangle().height > 0 &&
564 private void updateStickToEndState(boolean useImmediatePosition) {
565 if (myEditor == null) return;
567 JScrollBar scrollBar = myEditor.getScrollPane().getVerticalScrollBar();
568 int scrollBarPosition = useImmediatePosition ? scrollBar.getValue() :
569 myEditor.getScrollingModel().getVisibleAreaOnScrollingFinished().y;
570 boolean vscrollAtBottom = scrollBarPosition == scrollBar.getMaximum() - scrollBar.getVisibleAmount();
571 boolean stickingToEnd = isStickingToEnd();
573 if (!vscrollAtBottom && stickingToEnd) {
574 myCancelStickToEnd = true;
575 } else if (vscrollAtBottom && !stickingToEnd) {
580 protected JComponent createCenterComponent() {
581 return myEditor.getComponent();
585 public void dispose() {
586 myState = myState.dispose();
587 if (myEditor != null) {
588 cancelAllFlushRequests();
589 mySpareTimeAlarm.cancelAllRequests();
591 synchronized (LOCK) {
599 private void cancelAllFlushRequests() {
600 synchronized (myCurrentRequests) {
601 for (MyFlushRunnable request : myCurrentRequests) {
602 request.invalidate();
604 myCurrentRequests.clear();
605 myFlushAlarm.cancelAllRequests();
609 protected void disposeEditor() {
610 UIUtil.invokeAndWaitIfNeeded(new Runnable() {
613 if (!myEditor.isDisposed()) {
614 EditorFactory.getInstance().releaseEditor(myEditor);
621 public void print(@NotNull String s, @NotNull ConsoleViewContentType contentType) {
622 if (myInputMessageFilter == null) {
623 printHyperlink(s, contentType, null);
627 List<Pair<String, ConsoleViewContentType>> result = myInputMessageFilter.applyFilter(s, contentType);
628 if (result == null) {
629 printHyperlink(s, contentType, null);
632 for (Pair<String, ConsoleViewContentType> pair : result) {
633 if (pair.first != null) {
634 printHyperlink(pair.first, pair.second == null ? contentType : pair.second, null);
640 private void printHyperlink(@NotNull String s, @NotNull ConsoleViewContentType contentType, @Nullable HyperlinkInfo info) {
641 synchronized (LOCK) {
642 Pair<String, Integer> pair = myBuffer.print(s, contentType, info);
644 myContentSize += s.length() - pair.second;
646 if (contentType == ConsoleViewContentType.USER_INPUT && NEW_LINE_MATCHER.indexIn(s) >= 0) {
647 flushDeferredUserInput();
649 if (myEditor != null) {
650 final boolean shouldFlushNow = myBuffer.isUseCyclicBuffer() && myBuffer.getLength() >= myBuffer.getCyclicBufferSize();
651 addFlushRequest(new MyFlushRunnable(), shouldFlushNow ? 0 : DEFAULT_FLUSH_DELAY);
656 private void addToken(int length, @Nullable HyperlinkInfo info, ConsoleViewContentType contentType) {
657 ConsoleUtil.addToken(length, info, contentType, myTokens);
660 private static ModalityState getStateForUpdate() {
661 return null;//myStateForUpdate != null ? myStateForUpdate.compute() : ModalityState.stateForComponent(this);
664 private void requestFlushImmediately() {
665 if (myEditor != null) {
666 addFlushRequest(new MyFlushRunnable());
671 public int getContentSize() {
672 synchronized (LOCK) {
673 return myContentSize;
678 public boolean canPause() {
682 public void flushDeferredText() {
683 flushDeferredText(false);
686 private void flushDeferredText(boolean clear) {
687 ApplicationManager.getApplication().assertIsDispatchThread();
688 if (myProject.isDisposed()) {
691 EditorEx editor = myEditor;
692 if (editor == null) {
696 final boolean shouldStickToEnd = clear || !myCancelStickToEnd && isStickingToEnd();
697 myCancelStickToEnd = false; // Cancel only needs to last for one update. Next time, isStickingToEnd() will be false.
699 final DocumentEx document = editor.getDocument();
700 synchronized (LOCK) {
702 clearHyperlinkAndFoldings();
704 final int documentTextLength = document.getTextLength();
705 if (documentTextLength > 0) {
706 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
709 document.setInBulkUpdate(true);
711 myInDocumentUpdate = true;
712 myDocumentClearing = true;
713 document.deleteString(0, documentTextLength);
716 document.setInBulkUpdate(false);
717 myDocumentClearing = false;
718 myInDocumentUpdate = false;
721 }, null, DocCommandGroupId.noneGroupId(document));
727 final String addedText;
728 final Collection<ConsoleViewContentType> contentTypes;
729 int deferredTokensSize;
730 synchronized (LOCK) {
731 if (myOutputPaused) return;
732 if (myBuffer.isEmpty()) return;
734 addedText = myBuffer.getText();
736 contentTypes = Collections.unmodifiableCollection(new HashSet<ConsoleViewContentType>(myBuffer.getDeferredTokenTypes()));
737 List<TokenInfo> deferredTokens = myBuffer.getDeferredTokens();
738 for (TokenInfo deferredToken : deferredTokens) {
739 addToken(deferredToken.getLength(), deferredToken.getHyperlinkInfo(), deferredToken.contentType);
741 deferredTokensSize = deferredTokens.size();
742 myBuffer.clear(false);
745 final Document document = myEditor.getDocument();
746 final RangeMarker lastProcessedOutput = document.createRangeMarker(document.getTextLength(), document.getTextLength());
748 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
751 if (!shouldStickToEnd) {
752 myEditor.getScrollingModel().accumulateViewportChanges();
755 myInDocumentUpdate = true;
756 String[] strings = addedText.split("\\r", -1); // limit must be any negative number to avoid discarding of trailing empty strings
757 for (int i = 0; i < strings.length - 1; i++) {
758 document.insertString(document.getTextLength(), strings[i]);
759 int lastLine = document.getLineCount() - 1;
761 ConsoleUtil.updateTokensOnTextRemoval(myTokens, document.getTextLength(), document.getTextLength() + 1);
762 document.deleteString(document.getLineStartOffset(lastLine), document.getTextLength());
765 if (strings.length > 0) {
766 document.insertString(document.getTextLength(), strings[strings.length - 1]);
767 myContentSize -= strings.length - 1;
771 myInDocumentUpdate = false;
772 if (!shouldStickToEnd) {
773 myEditor.getScrollingModel().flushViewportChanges();
776 if (!contentTypes.isEmpty()) {
777 for (ChangeListener each : myListeners) {
778 each.contentAdded(contentTypes);
782 }, null, DocCommandGroupId.noneGroupId(document));
783 synchronized (LOCK) {
784 for (int i = myTokens.size() - 1; i >= 0 && deferredTokensSize > 0; i--, deferredTokensSize--) {
785 TokenInfo token = myTokens.get(i);
786 final HyperlinkInfo info = token.getHyperlinkInfo();
788 myHyperlinks.createHyperlink(token.startOffset, token.endOffset, null, info);
792 myPsiDisposedCheck.performCheck();
793 myLastAddedTextLength = addedText.length();
794 if (!myTooMuchOfOutput) {
795 if (isTheAmountOfTextTooBig(myLastAddedTextLength)) { // disable hyperlinks and folding until new output arriving slows down again
796 myTooMuchOfOutput = true;
797 final EditorNotificationPanel comp =
798 new EditorNotificationPanel().text("Too much output to process").icon(AllIcons.General.ExclMark);
799 final Alarm tooMuchOutputAlarm = new Alarm();
800 //show the notification with a delay to avoid blinking when "too much output" ceases quickly
801 tooMuchOutputAlarm.addRequest(new Runnable() {
804 add(comp, BorderLayout.NORTH);
807 performWhenNoDeferredOutput(new Runnable() {
810 if (!isTheAmountOfTextTooBig(myLastAddedTextLength)) {
812 highlightHyperlinksAndFoldings(lastProcessedOutput);
815 myTooMuchOfOutput = false;
817 tooMuchOutputAlarm.cancelAllRequests();
821 myLastAddedTextLength = 0;
822 performLaterWhenNoDeferredOutput(this);
828 highlightHyperlinksAndFoldings(lastProcessedOutput);
832 if (shouldStickToEnd) {
837 private boolean isStickingToEnd() {
838 if (myEditor == null) return myLastStickingToEnd;
839 Document document = myEditor.getDocument();
840 int caretOffset = myEditor.getCaretModel().getOffset();
841 myLastStickingToEnd = document.getLineNumber(caretOffset) >= document.getLineCount() - 1;
842 return myLastStickingToEnd;
845 private boolean isTheAmountOfTextTooBig(final int textLength) {
846 return myBuffer.isUseCyclicBuffer() && textLength > myBuffer.getCyclicBufferSize() / consoleTooMuchTextBufferRatio;
849 private void clearHyperlinkAndFoldings() {
850 myEditor.getMarkupModel().removeAllHighlighters();
852 myPendingFoldRegions.clear();
854 myFoldingAlarm.cancelAllRequests();
855 myEditor.getFoldingModel().runBatchFoldingOperation(new Runnable() {
858 myEditor.getFoldingModel().clearFoldRegions();
865 private void cancelHeavyAlarm() {
866 if (myHeavyAlarm != null && !myHeavyAlarm.isDisposed()) {
867 myHeavyAlarm.cancelAllRequests();
868 ++myHeavyUpdateTicket;
872 private void flushDeferredUserInput() {
873 final String textToSend = myBuffer.cutFirstUserInputLine();
874 if (textToSend == null) {
877 myFlushUserInputAlarm.addRequest(new Runnable() {
880 if (myState.isRunning()) {
882 // this may block forever, see IDEA-54340
883 myState.sendUserInput(textToSend);
885 catch (IOException ignored) {
893 public Object getData(final String dataId) {
894 if (CommonDataKeys.NAVIGATABLE.is(dataId)) {
895 if (myEditor == null) {
898 final LogicalPosition pos = myEditor.getCaretModel().getLogicalPosition();
899 final HyperlinkInfo info = myHyperlinks.getHyperlinkInfoByLineAndCol(pos.line, pos.column);
900 final OpenFileDescriptor openFileDescriptor = info instanceof FileHyperlinkInfo ? ((FileHyperlinkInfo)info).getDescriptor() : null;
901 if (openFileDescriptor == null || !openFileDescriptor.getFile().isValid()) {
904 return openFileDescriptor;
907 if (CommonDataKeys.EDITOR.is(dataId)) {
910 if (PlatformDataKeys.HELP_ID.is(dataId)) {
913 if (LangDataKeys.CONSOLE_VIEW.is(dataId)) {
920 public void setHelpId(final String helpId) {
924 public void setUpdateFoldingsEnabled(boolean updateFoldingsEnabled) {
925 myUpdateFoldingsEnabled = updateFoldingsEnabled;
929 public void addMessageFilter(final Filter filter) {
930 myFilters.addFilter(filter);
934 public void printHyperlink(final String hyperlinkText, final HyperlinkInfo info) {
935 printHyperlink(hyperlinkText, ConsoleViewContentType.NORMAL_OUTPUT, info);
938 private EditorEx createConsoleEditor() {
939 return ApplicationManager.getApplication().runReadAction(new Computable<EditorEx>() {
941 public EditorEx compute() {
942 EditorEx editor = doCreateConsoleEditor();
943 LOG.assertTrue(UndoUtil.isUndoDisabledFor(editor.getDocument()));
944 editor.setContextMenuGroupId(null); // disabling default context menu
945 editor.addEditorMouseListener(new EditorPopupHandler() {
947 public void invokePopup(final EditorMouseEvent event) {
948 popupInvoked(event.getMouseEvent());
951 editor.getDocument().addDocumentListener(new DocumentAdapter() {
953 public void documentChanged(DocumentEvent event) {
954 onDocumentChanged(event);
956 }, ConsoleViewImpl.this);
958 int bufferSize = myBuffer.isUseCyclicBuffer() ? myBuffer.getCyclicBufferSize() : 0;
959 editor.getDocument().setCyclicBufferSize(bufferSize);
961 editor.putUserData(CONSOLE_VIEW_IN_EDITOR_VIEW, ConsoleViewImpl.this);
963 editor.getSettings().setAllowSingleLogicalLineFolding(true); // We want to fold long soft-wrapped command lines
964 editor.setHighlighter(createHighlighter());
971 private void onDocumentChanged(DocumentEvent event) {
972 if (event.getNewLength() == 0) {
973 // string has been removed, adjust token ranges
974 synchronized (LOCK) {
975 ConsoleUtil.updateTokensOnTextRemoval(myTokens, event.getOffset(), event.getOffset() + event.getOldLength());
976 int toRemoveLen = event.getOldLength();
977 if (!myDocumentClearing) {
978 // If document is being cleared now, then this event has been occurred as a result of calling clear() method.
979 // At start clear() method sets 'myContentSize' to 0, so there is no need to perform update again.
980 // Moreover, performing update of 'myContentSize' breaks executing "console.print();" immediately after "console.clear();".
981 myContentSize -= Math.min(myContentSize, toRemoveLen);
985 else if (!myInDocumentUpdate) {
986 int newFragmentLength = event.getNewFragment().length();
987 // track external appends
988 if (event.getOldFragment().length() == 0 && newFragmentLength > 0) {
989 synchronized (LOCK) {
990 myContentSize += newFragmentLength;
991 addToken(newFragmentLength, null, ConsoleViewContentType.NORMAL_OUTPUT);
995 LOG.warn("unhandled external change: " + event);
1000 protected EditorEx doCreateConsoleEditor() {
1001 return ConsoleViewUtil.setupConsoleEditor(myProject, true, false);
1004 protected MyHighlighter createHighlighter() {
1005 return new MyHighlighter();
1008 private void registerConsoleEditorActions() {
1009 Shortcut[] shortcuts = KeymapManager.getInstance().getActiveKeymap().getShortcuts(IdeActions.ACTION_GOTO_DECLARATION);
1010 CustomShortcutSet shortcutSet = new CustomShortcutSet(ArrayUtil.mergeArrays(shortcuts, CommonShortcuts.ENTER.getShortcuts()));
1011 new HyperlinkNavigationAction().registerCustomShortcutSet(shortcutSet, myEditor.getContentComponent());
1015 new EnterHandler().registerCustomShortcutSet(CommonShortcuts.ENTER, myEditor.getContentComponent());
1016 registerActionHandler(myEditor, IdeActions.ACTION_EDITOR_PASTE, new PasteHandler());
1017 registerActionHandler(myEditor, IdeActions.ACTION_EDITOR_BACKSPACE, new BackSpaceHandler());
1018 registerActionHandler(myEditor, IdeActions.ACTION_EDITOR_DELETE, new DeleteHandler());
1020 registerActionHandler(myEditor, EOFAction.ACTION_ID);
1024 private static void registerActionHandler(final Editor editor, final String actionId) {
1025 AnAction action = ActionManager.getInstance().getAction(actionId);
1026 action.registerCustomShortcutSet(action.getShortcutSet(), editor.getContentComponent());
1029 private static void registerActionHandler(final Editor editor, final String actionId, final AnAction action) {
1030 final Keymap keymap = KeymapManager.getInstance().getActiveKeymap();
1031 final Shortcut[] shortcuts = keymap.getShortcuts(actionId);
1032 action.registerCustomShortcutSet(new CustomShortcutSet(shortcuts), editor.getContentComponent());
1035 private void popupInvoked(MouseEvent mouseEvent) {
1036 final ActionManager actionManager = ActionManager.getInstance();
1037 final HyperlinkInfo info = myHyperlinks != null ? myHyperlinks.getHyperlinkInfoByPoint(mouseEvent.getPoint()) : null;
1038 ActionGroup group = null;
1039 if (info instanceof HyperlinkWithPopupMenuInfo) {
1040 group = ((HyperlinkWithPopupMenuInfo)info).getPopupMenuGroup(mouseEvent);
1042 if (group == null) {
1043 group = (ActionGroup)actionManager.getAction(CONSOLE_VIEW_POPUP_MENU);
1045 final ConsoleActionsPostProcessor[] postProcessors = Extensions.getExtensions(ConsoleActionsPostProcessor.EP_NAME);
1046 AnAction[] result = group.getChildren(null);
1048 for (ConsoleActionsPostProcessor postProcessor : postProcessors) {
1049 result = postProcessor.postProcessPopupActions(this, result);
1051 final DefaultActionGroup processedGroup = new DefaultActionGroup(result);
1052 final ActionPopupMenu menu = actionManager.createActionPopupMenu(ActionPlaces.EDITOR_POPUP, processedGroup);
1053 menu.getComponent().show(mouseEvent.getComponent(), mouseEvent.getX(), mouseEvent.getY());
1056 private void highlightHyperlinksAndFoldings(RangeMarker lastProcessedOutput) {
1057 boolean canHighlightHyperlinks = !myFilters.isEmpty();
1059 if (!canHighlightHyperlinks && myUpdateFoldingsEnabled) {
1062 final int line1 = lastProcessedOutput.isValid() ? myEditor.getDocument().getLineNumber(lastProcessedOutput.getEndOffset()) : 0;
1063 lastProcessedOutput.dispose();
1064 int endLine = myEditor.getDocument().getLineCount() - 1;
1065 ApplicationManager.getApplication().assertIsDispatchThread();
1067 if (canHighlightHyperlinks) {
1068 myHyperlinks.highlightHyperlinks(myFilters, line1, endLine);
1071 if (myAllowHeavyFilters && myFilters.isAnyHeavy() && myFilters.shouldRunHeavy()) {
1072 runHeavyFilters(line1, endLine);
1074 if (myUpdateFoldingsEnabled) {
1075 updateFoldings(line1, endLine, true);
1079 private void runHeavyFilters(int line1, int endLine) {
1080 final int startLine = Math.max(0, line1);
1082 final Document document = myEditor.getDocument();
1083 final int startOffset = document.getLineStartOffset(startLine);
1084 String text = document.getText(new TextRange(startOffset, document.getLineEndOffset(endLine)));
1085 final Document documentCopy = new DocumentImpl(text,true);
1086 documentCopy.setReadOnly(true);
1088 myJLayeredPane.startUpdating();
1089 final int currentValue = myHeavyUpdateTicket;
1090 assert myHeavyAlarm != null;
1091 myHeavyAlarm.addRequest(new Runnable() {
1094 if (!myFilters.shouldRunHeavy()) return;
1096 myFilters.applyHeavyFilter(documentCopy, startOffset, startLine, new Consumer<FilterMixin.AdditionalHighlight>() {
1098 public void consume(final FilterMixin.AdditionalHighlight additionalHighlight) {
1099 addFlushRequest(new MyFlushRunnable() {
1101 public void doRun() {
1102 if (myHeavyUpdateTicket != currentValue) return;
1103 myHyperlinks.addHighlighter(additionalHighlight.getStart(), additionalHighlight.getEnd(),
1104 additionalHighlight.getTextAttributes(null));
1108 public boolean equals(Object o) {
1109 return this == o && super.equals(o);
1116 if (myHeavyAlarm.isEmpty()) {
1117 SwingUtilities.invokeLater(new Runnable() {
1120 myJLayeredPane.finishUpdating();
1129 private void updateFoldings(final int line1, final int endLine, boolean immediately) {
1130 final Document document = myEditor.getDocument();
1131 final CharSequence chars = document.getCharsSequence();
1132 final int startLine = Math.max(0, line1);
1133 final List<FoldRegion> toAdd = new ArrayList<FoldRegion>();
1134 for (int line = startLine; line <= endLine; line++) {
1135 boolean flushOnly = line == endLine;
1137 Grep Console plugin allows to fold empty lines. We need to handle this case in a special way.
1139 Multiple lines are grouped into one folding, but to know when you can create the folding,
1140 you need a line which does not belong to that folding.
1141 When a new line, or a chunk of lines is printed, #addFolding is called for that lines + for an empty string
1142 (which basically does only one thing, gets a folding displayed).
1143 We do not want to process that empty string, but also we do not want to wait for another line
1144 which will create and display the folding - we'd see an unfolded stacktrace until another text came and flushed it.
1145 So therefore the condition, the last line(empty string) should still flush, but not be processed by
1146 com.intellij.execution.ConsoleFolding.
1148 addFolding(document, chars, line, toAdd, flushOnly);
1150 if (!toAdd.isEmpty()) {
1151 doUpdateFolding(toAdd, immediately);
1155 private void doUpdateFolding(final List<FoldRegion> toAdd, final boolean immediately) {
1156 assertIsDispatchThread();
1157 myPendingFoldRegions.addAll(toAdd);
1159 myFoldingAlarm.cancelAllRequests();
1160 final Runnable runnable = new Runnable() {
1163 if (myEditor == null || myEditor.isDisposed()) {
1167 assertIsDispatchThread();
1168 final FoldingModel model = myEditor.getFoldingModel();
1169 final Runnable operation = new Runnable() {
1172 assertIsDispatchThread();
1173 for (FoldRegion region : myPendingFoldRegions) {
1174 region.setExpanded(false);
1175 model.addFoldRegion(region);
1177 myPendingFoldRegions.clear();
1181 model.runBatchFoldingOperation(operation);
1184 model.runBatchFoldingOperationDoNotCollapseCaret(operation);
1188 if (immediately || myPendingFoldRegions.size() > 100) {
1192 myFoldingAlarm.addRequest(runnable, 50);
1196 private void addFolding(Document document, CharSequence chars, int line, List<FoldRegion> toAdd, boolean flushOnly) {
1197 ConsoleFolding current = null;
1199 String commandLinePlaceholder = myCommandLineFolding.getPlaceholder(line);
1200 if (commandLinePlaceholder != null) {
1201 FoldRegion region = myEditor.getFoldingModel()
1202 .createFoldRegion(document.getLineStartOffset(line), document.getLineEndOffset(line), commandLinePlaceholder, null, false);
1206 current = foldingForLine(EditorHyperlinkSupport.getLineText(document, line, false));
1207 if (current != null) {
1208 myFolding.put(line, current);
1212 final ConsoleFolding prevFolding = myFolding.get(line - 1);
1213 if (current == null && prevFolding != null) {
1214 final int lEnd = line - 1;
1216 while (prevFolding.equals(myFolding.get(lStart - 1))) lStart--;
1218 for (int i = lStart; i <= lEnd; i++) {
1219 myFolding.remove(i);
1222 List<String> toFold = new ArrayList<String>(lEnd - lStart + 1);
1223 for (int i = lStart; i <= lEnd; i++) {
1224 toFold.add(EditorHyperlinkSupport.getLineText(document, i, false));
1227 int oStart = document.getLineStartOffset(lStart);
1228 if (oStart > 0) oStart--;
1229 int oEnd = CharArrayUtil.shiftBackward(chars, document.getLineEndOffset(lEnd) - 1, " \t") + 1;
1231 String placeholder = prevFolding.getPlaceholderText(toFold);
1232 FoldRegion region = placeholder == null ? null : myEditor.getFoldingModel().createFoldRegion(oStart, oEnd, placeholder, null, false);
1233 if (region != null) {
1240 private static ConsoleFolding foldingForLine(String lineText) {
1241 for (ConsoleFolding folding : ConsoleFolding.EP_NAME.getExtensions()) {
1242 if (folding.shouldFoldLine(lineText)) {
1250 public static class ClearAllAction extends DumbAwareAction {
1251 private final ConsoleView myConsoleView;
1253 @SuppressWarnings("unused")
1254 public ClearAllAction() {
1258 public ClearAllAction(ConsoleView consoleView) {
1259 super(ExecutionBundle.message("clear.all.from.console.action.name"), "Clear the contents of the console", AllIcons.Actions.GC);
1260 myConsoleView = consoleView;
1264 public void update(AnActionEvent e) {
1265 boolean enabled = myConsoleView != null && myConsoleView.getContentSize() > 0;
1267 enabled = e.getData(LangDataKeys.CONSOLE_VIEW) != null;
1268 Editor editor = e.getData(CommonDataKeys.EDITOR);
1269 if (editor != null && editor.getDocument().getTextLength() == 0) {
1273 e.getPresentation().setEnabled(enabled);
1277 public void actionPerformed(final AnActionEvent e) {
1278 final ConsoleView consoleView = myConsoleView != null ? myConsoleView : e.getData(LangDataKeys.CONSOLE_VIEW);
1279 if (consoleView != null) {
1280 consoleView.clear();
1285 private class MyHighlighter extends DocumentAdapter implements EditorHighlighter {
1286 private HighlighterClient myEditor;
1290 public HighlighterIterator createIterator(final int startOffset) {
1291 final int startIndex = ConsoleUtil.findTokenInfoIndexByOffset(myTokens, startOffset);
1293 return new HighlighterIterator() {
1294 private int myIndex = startIndex;
1297 public TextAttributes getTextAttributes() {
1298 return atEnd() ? null : getTokenInfo().contentType.getAttributes();
1302 public int getStart() {
1303 return atEnd() ? 0 : getTokenInfo().startOffset;
1307 public int getEnd() {
1308 return atEnd() ? 0 : getTokenInfo().endOffset;
1312 public IElementType getTokenType() {
1317 public void advance() {
1322 public void retreat() {
1327 public boolean atEnd() {
1328 return myIndex < 0 || myIndex >= myTokens.size();
1332 public Document getDocument() {
1333 return myEditor.getDocument();
1336 private TokenInfo getTokenInfo() {
1337 return myTokens.get(myIndex);
1343 public void setText(@NotNull final CharSequence text) {
1347 public void setEditor(@NotNull final HighlighterClient editor) {
1348 LOG.assertTrue(myEditor == null, "Highlighters cannot be reused with different editors");
1353 public void setColorScheme(@NotNull EditorColorsScheme scheme) {
1357 private static class MyTypedHandler extends TypedActionHandlerBase {
1359 private MyTypedHandler(final TypedActionHandler originalAction) {
1360 super(originalAction);
1364 public void execute(@NotNull final Editor editor, final char charTyped, @NotNull final DataContext dataContext) {
1365 final ConsoleViewImpl consoleView = editor.getUserData(CONSOLE_VIEW_IN_EDITOR_VIEW);
1366 if (consoleView == null || !consoleView.myState.isRunning() || consoleView.myIsViewer) {
1367 if (myOriginalHandler != null) myOriginalHandler.execute(editor, charTyped, dataContext);
1370 final String s = String.valueOf(charTyped);
1371 SelectionModel selectionModel = editor.getSelectionModel();
1372 if (selectionModel.hasSelection()) {
1373 consoleView.replaceUserText(s, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
1376 consoleView.insertUserText(s, editor.getCaretModel().getOffset());
1382 private abstract static class ConsoleAction extends AnAction implements DumbAware {
1384 public void actionPerformed(final AnActionEvent e) {
1385 final DataContext context = e.getDataContext();
1386 final ConsoleViewImpl console = getRunningConsole(context);
1387 execute(console, context);
1390 protected abstract void execute(ConsoleViewImpl console, final DataContext context);
1393 public void update(final AnActionEvent e) {
1394 final ConsoleViewImpl console = getRunningConsole(e.getDataContext());
1395 e.getPresentation().setEnabled(console != null);
1399 private static ConsoleViewImpl getRunningConsole(final DataContext context) {
1400 final Editor editor = CommonDataKeys.EDITOR.getData(context);
1401 if (editor != null) {
1402 final ConsoleViewImpl console = editor.getUserData(CONSOLE_VIEW_IN_EDITOR_VIEW);
1403 if (console != null && console.myState.isRunning()) {
1411 private static class EnterHandler extends ConsoleAction {
1413 public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
1414 consoleView.print("\n", ConsoleViewContentType.USER_INPUT);
1415 consoleView.flushDeferredText();
1416 final Editor editor = consoleView.myEditor;
1417 editor.getCaretModel().moveToOffset(editor.getDocument().getTextLength());
1418 editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1422 private static class PasteHandler extends ConsoleAction {
1424 public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
1425 String s = CopyPasteManager.getInstance().getContents(DataFlavor.stringFlavor);
1426 if (s == null) return;
1427 s = StringUtil.convertLineSeparators(s);
1428 ApplicationManager.getApplication().assertIsDispatchThread();
1429 Editor editor = consoleView.myEditor;
1430 SelectionModel selectionModel = editor.getSelectionModel();
1431 if (selectionModel.hasSelection()) {
1432 consoleView.replaceUserText(s, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
1435 consoleView.insertUserText(s, editor.getCaretModel().getOffset());
1440 private static class BackSpaceHandler extends ConsoleAction {
1442 public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
1443 final Editor editor = consoleView.myEditor;
1445 if (IncrementalSearchHandler.isHintVisible(editor)) {
1446 getDefaultActionHandler().execute(editor, context);
1450 final Document document = editor.getDocument();
1451 final int length = document.getTextLength();
1456 ApplicationManager.getApplication().assertIsDispatchThread();
1458 SelectionModel selectionModel = editor.getSelectionModel();
1459 if (selectionModel.hasSelection()) {
1460 consoleView.deleteUserText(selectionModel.getSelectionStart(),
1461 selectionModel.getSelectionEnd() - selectionModel.getSelectionStart());
1463 else if (editor.getCaretModel().getOffset() > 0) {
1464 consoleView.deleteUserText(editor.getCaretModel().getOffset() - 1, 1);
1468 private static EditorActionHandler getDefaultActionHandler() {
1469 return EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE);
1473 private static class DeleteHandler extends ConsoleAction {
1475 public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
1476 final Editor editor = consoleView.myEditor;
1478 if (IncrementalSearchHandler.isHintVisible(editor)) {
1479 getDefaultActionHandler().execute(editor, context);
1483 final Document document = editor.getDocument();
1484 final int length = document.getTextLength();
1489 ApplicationManager.getApplication().assertIsDispatchThread();
1490 SelectionModel selectionModel = editor.getSelectionModel();
1491 if (selectionModel.hasSelection()) {
1492 consoleView.deleteUserText(selectionModel.getSelectionStart(),
1493 selectionModel.getSelectionEnd() - selectionModel.getSelectionStart());
1496 consoleView.deleteUserText(editor.getCaretModel().getOffset(), 1);
1500 private static EditorActionHandler getDefaultActionHandler() {
1501 return EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE);
1506 public JComponent getPreferredFocusableComponent() {
1507 //ensure editor created
1509 return myEditor.getContentComponent();
1513 // navigate up/down in stack trace
1515 public boolean hasNextOccurence() {
1516 return calcNextOccurrence(1) != null;
1520 public boolean hasPreviousOccurence() {
1521 return calcNextOccurrence(-1) != null;
1525 public OccurenceInfo goNextOccurence() {
1526 return calcNextOccurrence(1);
1530 protected OccurenceInfo calcNextOccurrence(final int delta) {
1531 final EditorHyperlinkSupport hyperlinks = myHyperlinks;
1532 if (hyperlinks == null) {
1536 return EditorHyperlinkSupport.getNextOccurrence(myEditor, delta, new Consumer<RangeHighlighter>() {
1538 public void consume(RangeHighlighter next) {
1539 int offset = next.getStartOffset();
1541 final HyperlinkInfo hyperlinkInfo = EditorHyperlinkSupport.getHyperlinkInfo(next);
1542 if (hyperlinkInfo instanceof BrowserHyperlinkInfo) {
1545 if (hyperlinkInfo instanceof HyperlinkInfoBase) {
1546 VisualPosition position = myEditor.offsetToVisualPosition(offset);
1547 Point point = myEditor.visualPositionToXY(new VisualPosition(position.getLine() + 1, position.getColumn()));
1548 ((HyperlinkInfoBase)hyperlinkInfo).navigate(myProject, new RelativePoint(myEditor.getContentComponent(), point));
1550 else if (hyperlinkInfo != null) {
1551 hyperlinkInfo.navigate(myProject);
1558 public OccurenceInfo goPreviousOccurence() {
1559 return calcNextOccurrence(-1);
1563 public String getNextOccurenceActionName() {
1564 return ExecutionBundle.message("down.the.stack.trace");
1568 public String getPreviousOccurenceActionName() {
1569 return ExecutionBundle.message("up.the.stack.trace");
1572 public void addCustomConsoleAction(@NotNull AnAction action) {
1573 customActions.add(action);
1578 public AnAction[] createConsoleActions() {
1579 //Initializing prev and next occurrences actions
1580 final CommonActionsManager actionsManager = CommonActionsManager.getInstance();
1581 final AnAction prevAction = actionsManager.createPrevOccurenceAction(this);
1582 prevAction.getTemplatePresentation().setText(getPreviousOccurenceActionName());
1583 final AnAction nextAction = actionsManager.createNextOccurenceAction(this);
1584 nextAction.getTemplatePresentation().setText(getNextOccurenceActionName());
1586 final AnAction switchSoftWrapsAction = new ToggleUseSoftWrapsToolbarAction(SoftWrapAppliancePlaces.CONSOLE) {
1589 * There is a possible case that more than console is open and user toggles soft wraps mode at one of them. We want
1590 * to update another console(s) representation as well when they are switched on after that. Hence, we remember last
1591 * used soft wraps mode and perform update if we see that the current value differs from the stored.
1593 private boolean myLastIsSelected;
1596 protected Editor getEditor(AnActionEvent e) {
1601 public boolean isSelected(AnActionEvent e) {
1602 boolean result = super.isSelected(e);
1603 if (result ^ myLastIsSelected) {
1604 setSelected(null, result);
1606 return myLastIsSelected = result;
1610 public void setSelected(AnActionEvent e, final boolean state) {
1611 super.setSelected(e, state);
1612 if (myEditor == null) {
1616 final String placeholder = myCommandLineFolding.getPlaceholder(0);
1617 final FoldingModel foldingModel = myEditor.getFoldingModel();
1618 final int firstLineEnd = myEditor.getDocument().getLineEndOffset(0);
1619 foldingModel.runBatchFoldingOperation(new Runnable() {
1622 FoldRegion[] regions = foldingModel.getAllFoldRegions();
1623 if (regions.length > 0 && regions[0].getStartOffset() == 0 && regions[0].getEndOffset() == firstLineEnd) {
1624 foldingModel.removeFoldRegion(regions[0]);
1626 if (placeholder != null) {
1627 FoldRegion foldRegion = foldingModel.addFoldRegion(0, firstLineEnd, placeholder);
1628 if (foldRegion != null) {
1629 foldRegion.setExpanded(false);
1636 final AnAction autoScrollToTheEndAction = new ScrollToTheEndToolbarAction(myEditor);
1638 //Initializing custom actions
1639 final AnAction[] consoleActions = new AnAction[6 + customActions.size()];
1640 consoleActions[0] = prevAction;
1641 consoleActions[1] = nextAction;
1642 consoleActions[2] = switchSoftWrapsAction;
1643 consoleActions[3] = autoScrollToTheEndAction;
1644 consoleActions[4] = ActionManager.getInstance().getAction("Print");
1645 consoleActions[5] = new ClearAllAction(this);
1646 for (int i = 0; i < customActions.size(); ++i) {
1647 consoleActions[i + 6] = customActions.get(i);
1649 ConsoleActionsPostProcessor[] postProcessors = Extensions.getExtensions(ConsoleActionsPostProcessor.EP_NAME);
1650 AnAction[] result = consoleActions;
1651 for (ConsoleActionsPostProcessor postProcessor : postProcessors) {
1652 result = postProcessor.postProcess(this, result);
1658 public void allowHeavyFilters() {
1659 myAllowHeavyFilters = true;
1663 public void addChangeListener(@NotNull final ChangeListener listener, @NotNull final Disposable parent) {
1664 myListeners.add(listener);
1665 Disposer.register(parent, new Disposable() {
1667 public void dispose() {
1668 myListeners.remove(listener);
1674 * insert text to document
1676 * @param s inserted text
1677 * @param offset relatively to all document text
1679 private void insertUserText(final String s, int offset) {
1680 ApplicationManager.getApplication().assertIsDispatchThread();
1681 final ConsoleViewImpl consoleView = this;
1682 final ConsoleBuffer buffer = consoleView.myBuffer;
1683 final Editor editor = consoleView.myEditor;
1684 final Document document = editor.getDocument();
1685 final int startOffset;
1687 String textToUse = StringUtil.convertLineSeparators(s);
1688 synchronized (consoleView.LOCK) {
1689 if (consoleView.myTokens.isEmpty()) {
1690 addToken(0, null, ConsoleViewContentType.SYSTEM_OUTPUT);
1692 final TokenInfo info = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1693 if (info.contentType != ConsoleViewContentType.USER_INPUT && !StringUtil.containsChar(textToUse, '\n')) {
1694 consoleView.print(textToUse, ConsoleViewContentType.USER_INPUT);
1695 consoleView.flushDeferredText();
1696 editor.getCaretModel().moveToOffset(document.getTextLength());
1697 editor.getSelectionModel().removeSelection();
1700 if (info.contentType != ConsoleViewContentType.USER_INPUT) {
1701 insertUserText("temp", offset);
1702 final TokenInfo newInfo = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1703 replaceUserText(textToUse, newInfo.startOffset, newInfo.endOffset);
1707 final int deferredOffset = myContentSize - buffer.getLength() - buffer.getUserInputLength();
1708 if (offset > info.endOffset) {
1709 startOffset = info.endOffset;
1712 startOffset = Math.max(deferredOffset, Math.max(info.startOffset, offset));
1715 buffer.addUserText(startOffset - deferredOffset, textToUse);
1717 int charCountToAdd = textToUse.length();
1718 info.endOffset += charCountToAdd;
1719 consoleView.myContentSize += charCountToAdd;
1723 myInDocumentUpdate = true;
1724 document.insertString(startOffset, textToUse);
1727 myInDocumentUpdate = false;
1729 // Math.max is needed when cyclic buffer is used
1730 editor.getCaretModel().moveToOffset(Math.min(startOffset + textToUse.length(), document.getTextLength()));
1731 editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1737 * @param s text for replace
1738 * @param start relatively to all document text
1739 * @param end relatively to all document text
1741 private void replaceUserText(final String s, int start, int end) {
1743 insertUserText(s, start);
1746 final ConsoleViewImpl consoleView = this;
1747 final ConsoleBuffer buffer = consoleView.myBuffer;
1748 final Editor editor = consoleView.myEditor;
1749 final Document document = editor.getDocument();
1750 final int startOffset;
1751 final int endOffset;
1753 synchronized (consoleView.LOCK) {
1754 if (consoleView.myTokens.isEmpty()) return;
1755 final TokenInfo info = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1756 if (info.contentType != ConsoleViewContentType.USER_INPUT) {
1757 consoleView.print(s, ConsoleViewContentType.USER_INPUT);
1758 consoleView.flushDeferredText();
1759 editor.getCaretModel().moveToOffset(document.getTextLength());
1760 editor.getSelectionModel().removeSelection();
1763 if (buffer.getUserInputLength() <= 0) return;
1765 final int deferredOffset = myContentSize - buffer.getLength() - buffer.getUserInputLength();
1767 startOffset = getStartOffset(start, info, deferredOffset);
1768 endOffset = getEndOffset(end, info);
1770 if (startOffset == -1 ||
1772 endOffset <= startOffset) {
1773 editor.getSelectionModel().removeSelection();
1774 editor.getCaretModel().moveToOffset(start);
1777 int charCountToReplace = s.length() - endOffset + startOffset;
1779 buffer.replaceUserText(startOffset - deferredOffset, endOffset - deferredOffset, s);
1781 info.endOffset += charCountToReplace;
1782 if (info.startOffset == info.endOffset) {
1783 consoleView.myTokens.remove(consoleView.myTokens.size() - 1);
1785 consoleView.myContentSize += charCountToReplace;
1789 myInDocumentUpdate = true;
1790 document.replaceString(startOffset, endOffset, s);
1793 myInDocumentUpdate = false;
1795 editor.getCaretModel().moveToOffset(startOffset + s.length());
1796 editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1797 editor.getSelectionModel().removeSelection();
1803 * @param offset relatively to all document text
1804 * @param length length of deleted text
1806 private void deleteUserText(int offset, int length) {
1807 ConsoleViewImpl consoleView = this;
1808 ConsoleBuffer buffer = consoleView.myBuffer;
1809 final Editor editor = consoleView.myEditor;
1810 final Document document = editor.getDocument();
1811 final int startOffset;
1812 final int endOffset;
1814 synchronized (consoleView.LOCK) {
1815 if (consoleView.myTokens.isEmpty()) return;
1816 final TokenInfo info = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1817 if (info.contentType != ConsoleViewContentType.USER_INPUT) return;
1818 if (myBuffer.getUserInputLength() == 0) return;
1820 final int deferredOffset = myContentSize - buffer.getLength() - buffer.getUserInputLength();
1821 startOffset = getStartOffset(offset, info, deferredOffset);
1822 endOffset = getEndOffset(offset + length, info);
1823 if (startOffset == -1 ||
1825 endOffset <= startOffset ||
1826 startOffset < deferredOffset) {
1827 editor.getSelectionModel().removeSelection();
1828 editor.getCaretModel().moveToOffset(offset);
1832 buffer.removeUserText(startOffset - deferredOffset, endOffset - deferredOffset);
1836 myInDocumentUpdate = true;
1837 document.deleteString(startOffset, endOffset);
1840 myInDocumentUpdate = false;
1842 editor.getCaretModel().moveToOffset(startOffset);
1843 editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1844 editor.getSelectionModel().removeSelection();
1847 //util methods for add, replace, delete methods
1848 private static int getStartOffset(int offset, TokenInfo info, int deferredOffset) {
1850 if (offset >= info.startOffset && offset < info.endOffset) {
1851 startOffset = Math.max(offset, deferredOffset);
1853 else if (offset < info.startOffset) {
1854 startOffset = Math.max(info.startOffset, deferredOffset);
1862 private static int getEndOffset(int offset, TokenInfo info) {
1864 if (offset > info.endOffset) {
1865 endOffset = info.endOffset;
1867 else if (offset <= info.startOffset) {
1876 public boolean isRunning() {
1877 return myState.isRunning();
1881 * Command line used to launch application/test from idea may be quite long.
1882 * Hence, it takes many visual lines during representation if soft wraps are enabled
1883 * or, otherwise, takes many columns and makes horizontal scrollbar thumb too small.
1885 * Our point is to fold such long command line and represent it as a single visual line by default.
1887 private class CommandLineFolding extends ConsoleFolding {
1890 * Checks if target line should be folded and returns its placeholder if the examination succeeds.
1892 * @param line index of line to check
1893 * @return placeholder text if given line should be folded; <code>null</code> otherwise
1896 private String getPlaceholder(int line) {
1897 if (myEditor == null || line != 0) {
1901 String text = EditorHyperlinkSupport.getLineText(myEditor.getDocument(), 0, false);
1902 // Don't fold the first line if the line is not that big.
1903 if (text.length() < 1000) {
1907 if (text.charAt(0) == '"') {
1908 index = text.indexOf('"', 1) + 1;
1911 boolean nonWhiteSpaceFound = false;
1912 for (; index < text.length(); index++) {
1913 char c = text.charAt(index);
1914 if (c != ' ' && c != '\t') {
1915 nonWhiteSpaceFound = true;
1918 if (nonWhiteSpaceFound) {
1923 assert index <= text.length();
1924 return text.substring(0, index) + " ...";
1928 public boolean shouldFoldLine(String line) {
1933 public String getPlaceholderText(List<String> lines) {
1934 // Is not expected to be called.
1939 private class MyFlushRunnable implements Runnable {
1940 private volatile boolean myValid = true;
1942 public final void run() {
1943 synchronized (myCurrentRequests) {
1944 myCurrentRequests.remove(this);
1951 protected void doRun() {
1952 flushDeferredText();
1955 public void invalidate() {
1959 public boolean isValid() {
1964 public boolean equals(Object o) {
1965 if (this == o) return true;
1966 if (o == null || getClass() != o.getClass()) return false;
1968 MyFlushRunnable runnable = (MyFlushRunnable)o;
1970 return myValid == runnable.myValid;
1974 public int hashCode() {
1975 return getClass().hashCode();
1979 private final class MyClearRunnable extends MyFlushRunnable {
1981 public void doRun() {
1982 flushDeferredText(true);
1987 public Project getProject() {
1991 private class HyperlinkNavigationAction extends DumbAwareAction {
1993 public void actionPerformed(AnActionEvent e) {
1994 Runnable runnable = myHyperlinks.getLinkNavigationRunnable(myEditor.getCaretModel().getLogicalPosition());
1995 assert runnable != null;
2000 public void update(AnActionEvent e) {
2001 e.getPresentation().setEnabled(myHyperlinks.getLinkNavigationRunnable(myEditor.getCaretModel().getLogicalPosition()) != null);
2006 public String getText() {
2007 return myEditor.getDocument().getText();