2 * Copyright 2000-2015 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.diagnostic.Logger;
40 import com.intellij.openapi.editor.*;
41 import com.intellij.openapi.editor.actionSystem.*;
42 import com.intellij.openapi.editor.actions.ScrollToTheEndToolbarAction;
43 import com.intellij.openapi.editor.actions.ToggleUseSoftWrapsToolbarAction;
44 import com.intellij.openapi.editor.colors.EditorColorsScheme;
45 import com.intellij.openapi.editor.event.*;
46 import com.intellij.openapi.editor.ex.DocumentEx;
47 import com.intellij.openapi.editor.ex.EditorEx;
48 import com.intellij.openapi.editor.ex.util.EditorUtil;
49 import com.intellij.openapi.editor.highlighter.EditorHighlighter;
50 import com.intellij.openapi.editor.highlighter.HighlighterClient;
51 import com.intellij.openapi.editor.highlighter.HighlighterIterator;
52 import com.intellij.openapi.editor.impl.DocumentImpl;
53 import com.intellij.openapi.editor.impl.softwrap.SoftWrapAppliancePlaces;
54 import com.intellij.openapi.editor.markup.RangeHighlighter;
55 import com.intellij.openapi.editor.markup.TextAttributes;
56 import com.intellij.openapi.extensions.Extensions;
57 import com.intellij.openapi.fileEditor.OpenFileDescriptor;
58 import com.intellij.openapi.ide.CopyPasteManager;
59 import com.intellij.openapi.keymap.Keymap;
60 import com.intellij.openapi.keymap.KeymapManager;
61 import com.intellij.openapi.project.DumbAware;
62 import com.intellij.openapi.project.DumbAwareAction;
63 import com.intellij.openapi.project.DumbService;
64 import com.intellij.openapi.project.Project;
65 import com.intellij.openapi.util.*;
66 import com.intellij.openapi.util.registry.Registry;
67 import com.intellij.openapi.util.text.StringUtil;
68 import com.intellij.psi.search.GlobalSearchScope;
69 import com.intellij.psi.tree.IElementType;
70 import com.intellij.ui.EditorNotificationPanel;
71 import com.intellij.ui.awt.RelativePoint;
72 import com.intellij.util.*;
73 import com.intellij.util.text.CharArrayUtil;
74 import com.intellij.util.ui.UIUtil;
75 import gnu.trove.TIntObjectHashMap;
76 import org.jetbrains.annotations.NonNls;
77 import org.jetbrains.annotations.NotNull;
78 import org.jetbrains.annotations.Nullable;
82 import java.awt.datatransfer.DataFlavor;
83 import java.awt.event.MouseAdapter;
84 import java.awt.event.MouseEvent;
85 import java.awt.event.MouseWheelEvent;
86 import java.io.IOException;
88 import java.util.List;
89 import java.util.concurrent.CopyOnWriteArraySet;
91 public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableConsoleView, DataProvider, OccurenceNavigator {
92 @NonNls private static final String CONSOLE_VIEW_POPUP_MENU = "ConsoleView.PopupMenu";
93 private static final Logger LOG = Logger.getInstance("#com.intellij.execution.impl.ConsoleViewImpl");
95 static final int DEFAULT_FLUSH_DELAY = SystemProperties.getIntProperty("console.flush.delay.ms", 200);
97 private static final CharMatcher NEW_LINE_MATCHER = CharMatcher.anyOf("\n\r");
99 public static final Key<ConsoleViewImpl> CONSOLE_VIEW_IN_EDITOR_VIEW = Key.create("CONSOLE_VIEW_IN_EDITOR_VIEW");
101 private static boolean ourTypedHandlerInitialized;
103 private static synchronized void initTypedHandler() {
104 if (ourTypedHandlerInitialized) return;
105 final EditorActionManager actionManager = EditorActionManager.getInstance();
106 final TypedAction typedAction = actionManager.getTypedAction();
107 typedAction.setupHandler(new MyTypedHandler(typedAction.getHandler()));
108 ourTypedHandlerInitialized = true;
112 private final CommandLineFolding myCommandLineFolding = new CommandLineFolding();
114 private final DisposedPsiManagerCheck myPsiDisposedCheck;
115 private final boolean myIsViewer;
117 private ConsoleState myState;
119 private final Alarm mySpareTimeAlarm = new Alarm(this);
121 private final Alarm myHeavyAlarm;
122 private volatile int myHeavyUpdateTicket;
124 private final Collection<ChangeListener> myListeners = new CopyOnWriteArraySet<ChangeListener>();
125 private final List<AnAction> customActions = new ArrayList<AnAction>();
126 private final ConsoleBuffer myBuffer = new ConsoleBuffer();
127 private boolean myUpdateFoldingsEnabled = true;
128 private EditorHyperlinkSupport myHyperlinks;
129 private MyDiffContainer myJLayeredPane;
130 private JPanel myMainPanel;
131 private boolean myAllowHeavyFilters;
132 private boolean myLastStickingToEnd;
133 private boolean myCancelStickToEnd;
135 private boolean myTooMuchOfOutput;
136 private boolean myInDocumentUpdate;
138 // If true, then a document is being cleared right now.
139 // Should be accessed in EDT only.
140 @SuppressWarnings("FieldAccessedSynchronizedAndUnsynchronized")
141 private boolean myDocumentClearing;
142 private int myLastAddedTextLength;
143 private int consoleTooMuchTextBufferRatio;
145 public Editor getEditor() {
149 public EditorHyperlinkSupport getHyperlinks() {
153 public void scrollToEnd() {
154 if (myEditor == null) return;
155 EditorUtil.scrollToTheEnd(myEditor);
156 myCancelStickToEnd = false;
159 public void foldImmediately() {
160 ApplicationManager.getApplication().assertIsDispatchThread();
161 if (!myFlushAlarm.isEmpty()) {
162 cancelAllFlushRequests();
163 new MyFlushRunnable().run();
166 myFoldingAlarm.cancelAllRequests();
168 myPendingFoldRegions.clear();
169 final FoldingModel model = myEditor.getFoldingModel();
170 model.runBatchFoldingOperation(new Runnable() {
173 for (FoldRegion region : model.getAllFoldRegions()) {
174 model.removeFoldRegion(region);
180 updateFoldings(0, myEditor.getDocument().getLineCount() - 1, true);
183 static class TokenInfo {
184 final ConsoleViewContentType contentType;
188 TokenInfo(ConsoleViewContentType contentType, int startOffset, int endOffset) {
189 this.contentType = contentType;
190 this.startOffset = startOffset;
191 this.endOffset = endOffset;
194 public int getLength() {
195 return endOffset - startOffset;
199 public String toString() {
200 return contentType + "[" + startOffset + ";" + endOffset + "]";
204 public HyperlinkInfo getHyperlinkInfo() {
209 static class HyperlinkTokenInfo extends TokenInfo {
210 private final HyperlinkInfo myHyperlinkInfo;
212 HyperlinkTokenInfo(final ConsoleViewContentType contentType, final int startOffset, final int endOffset, HyperlinkInfo hyperlinkInfo) {
213 super(contentType, startOffset, endOffset);
214 myHyperlinkInfo = hyperlinkInfo;
218 public HyperlinkInfo getHyperlinkInfo() {
219 return myHyperlinkInfo;
223 private final Project myProject;
225 private boolean myOutputPaused;
227 private EditorEx myEditor;
229 private final Object LOCK = new Object();
232 * Holds number of symbols managed by the current console.
234 * Total number is assembled as a sum of symbols that are already pushed to the document and number of deferred symbols that
235 * are awaiting to be pushed to the document.
237 private int myContentSize;
240 * Holds information about lexical division by offsets of the text already pushed to document.
242 * Target offsets are anchored to the document here.
244 private final List<TokenInfo> myTokens = new ArrayList<TokenInfo>();
246 private final TIntObjectHashMap<ConsoleFolding> myFolding = new TIntObjectHashMap<ConsoleFolding>();
248 private String myHelpId;
250 private final Alarm myFlushUserInputAlarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, this);
251 private final Alarm myFlushAlarm = new Alarm(this);
253 private final Set<MyFlushRunnable> myCurrentRequests = new HashSet<MyFlushRunnable>();
255 protected final CompositeFilter myFilters;
257 @Nullable private final InputFilter myInputMessageFilter;
259 private final Alarm myFoldingAlarm = new Alarm(this);
260 private final List<FoldRegion> myPendingFoldRegions = new ArrayList<FoldRegion>();
262 public ConsoleViewImpl(final Project project, boolean viewer) {
263 this(project, GlobalSearchScope.allScope(project), viewer, true);
266 public ConsoleViewImpl(@NotNull final Project project,
267 @NotNull GlobalSearchScope searchScope,
269 boolean usePredefinedMessageFilter) {
270 this(project, searchScope, viewer,
271 new ConsoleState.NotStartedStated() {
273 public ConsoleState attachTo(ConsoleViewImpl console, ProcessHandler processHandler) {
274 return new ConsoleViewRunningState(console, processHandler, this, true, true);
277 usePredefinedMessageFilter);
280 protected ConsoleViewImpl(@NotNull final Project project,
281 @NotNull GlobalSearchScope searchScope,
283 @NotNull final ConsoleState initialState,
284 boolean usePredefinedMessageFilter)
286 super(new BorderLayout());
289 myState = initialState;
290 myPsiDisposedCheck = new DisposedPsiManagerCheck(project);
293 myFilters = new CompositeFilter(project);
294 if (usePredefinedMessageFilter) {
295 for (ConsoleFilterProvider eachProvider : Extensions.getExtensions(ConsoleFilterProvider.FILTER_PROVIDERS)) {
297 if (eachProvider instanceof ConsoleDependentFilterProvider) {
298 filters = ((ConsoleDependentFilterProvider)eachProvider).getDefaultFilters(this, project, searchScope);
300 else if (eachProvider instanceof ConsoleFilterProviderEx) {
301 filters = ((ConsoleFilterProviderEx)eachProvider).getDefaultFilters(project, searchScope);
304 filters = eachProvider.getDefaultFilters(project);
306 for (Filter filter : filters) {
307 myFilters.addFilter(filter);
311 myFilters.setForceUseAllFilters(true);
312 myHeavyUpdateTicket = 0;
313 myHeavyAlarm = myFilters.isAnyHeavy() ? new Alarm(Alarm.ThreadToUse.POOLED_THREAD, this) : null;
316 ConsoleInputFilterProvider[] inputFilters = Extensions.getExtensions(ConsoleInputFilterProvider.INPUT_FILTER_PROVIDERS);
317 if (inputFilters.length > 0) {
318 CompositeInputFilter compositeInputFilter = new CompositeInputFilter(project);
319 myInputMessageFilter = compositeInputFilter;
320 for (ConsoleInputFilterProvider eachProvider : inputFilters) {
321 InputFilter[] filters = eachProvider.getDefaultFilters(project);
322 for (InputFilter filter : filters) {
323 compositeInputFilter.addFilter(filter);
328 myInputMessageFilter = null;
331 consoleTooMuchTextBufferRatio = Registry.intValue("console.too.much.text.buffer.ratio");
333 project.getMessageBus().connect(this).subscribe(DumbService.DUMB_MODE, new DumbService.DumbModeListener() {
334 private long myLastStamp;
337 public void enteredDumbMode() {
338 if (myEditor == null) return;
339 myLastStamp = myEditor.getDocument().getModificationStamp();
344 public void exitDumbMode() {
345 ApplicationManager.getApplication().invokeLater(new Runnable() {
348 if (myEditor == null || project.isDisposed() || DumbService.getInstance(project).isDumb()) return;
350 DocumentEx document = myEditor.getDocument();
351 if (myLastStamp != document.getModificationStamp()) {
352 clearHyperlinkAndFoldings();
353 highlightHyperlinksAndFoldings(document.createRangeMarker(0, 0));
363 public void attachToProcess(final ProcessHandler processHandler) {
364 myState = myState.attachTo(this, processHandler);
368 public void clear() {
369 if (myEditor == null) return;
370 synchronized (LOCK) {
371 // real document content will be cleared on next flush;
376 if (myFlushAlarm.isDisposed()) return;
377 cancelAllFlushRequests();
378 addFlushRequest(new MyClearRunnable());
383 public void scrollTo(final int offset) {
384 if (myEditor == null) return;
385 class ScrollRunnable extends MyFlushRunnable {
386 private final int myOffset = offset;
389 public void doRun() {
391 if (myEditor == null) return;
392 int moveOffset = Math.min(offset, myEditor.getDocument().getTextLength());
393 if (myBuffer.isUseCyclicBuffer() && moveOffset >= myEditor.getDocument().getTextLength()) {
396 myEditor.getCaretModel().moveToOffset(moveOffset);
397 myEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
401 public boolean equals(Object o) {
402 return super.equals(o) && myOffset == ((ScrollRunnable)o).myOffset;
405 addFlushRequest(new ScrollRunnable());
408 public void requestScrollingToEnd() {
409 if (myEditor == null) {
413 addFlushRequest(new MyFlushRunnable() {
415 public void doRun() {
417 if (myEditor == null || myFlushAlarm.isDisposed()) {
426 private void addFlushRequest(MyFlushRunnable scrollRunnable) {
427 addFlushRequest(scrollRunnable, 0);
430 private void addFlushRequest(@NotNull MyFlushRunnable flushRunnable, final int millis) {
431 synchronized (myCurrentRequests) {
432 if (!myFlushAlarm.isDisposed() && myCurrentRequests.add(flushRunnable)) {
433 myFlushAlarm.addRequest(flushRunnable, millis, getStateForUpdate());
439 private static void assertIsDispatchThread() {
440 ApplicationManager.getApplication().assertIsDispatchThread();
444 public void setOutputPaused(final boolean value) {
445 myOutputPaused = value;
447 requestFlushImmediately();
452 public boolean isOutputPaused() {
453 return myOutputPaused;
457 public boolean hasDeferredOutput() {
458 synchronized (LOCK) {
459 return myBuffer.getLength() > 0;
464 public void performWhenNoDeferredOutput(final Runnable runnable) {
465 //Q: implement in another way without timer?
466 if (!hasDeferredOutput()) {
470 performLaterWhenNoDeferredOutput(runnable);
474 private void performLaterWhenNoDeferredOutput(final Runnable runnable) {
475 if (mySpareTimeAlarm.isDisposed()) return;
476 mySpareTimeAlarm.addRequest(
480 performWhenNoDeferredOutput(runnable);
484 ModalityState.stateForComponent(myJLayeredPane)
489 public JComponent getComponent() {
490 if (myMainPanel == null) {
491 myMainPanel = new JPanel(new BorderLayout());
492 myJLayeredPane = new MyDiffContainer(myMainPanel, myFilters.getUpdateMessage());
493 Disposer.register(this, myJLayeredPane);
494 add(myJLayeredPane, BorderLayout.CENTER);
497 if (myEditor == null) {
499 requestFlushImmediately();
500 myMainPanel.add(createCenterComponent(), BorderLayout.CENTER);
506 * Adds transparent (actually, non-opaque) component over console.
507 * It will be as big as console. Use it to draw on console because it does not prevent user from console usage.
509 * @param component component to add
511 public final void addLayerToPane(@NotNull final JComponent component) {
512 getComponent(); // Make sure component exists
513 component.setOpaque(false);
514 component.setVisible(true);
515 myJLayeredPane.add(component, 0);
518 protected void initConsoleEditor() {
519 myEditor = createConsoleEditor();
520 registerConsoleEditorActions();
521 myEditor.getScrollPane().setBorder(null);
522 MouseAdapter mouseListener = new MouseAdapter() {
524 public void mousePressed(MouseEvent e) {
525 updateStickToEndState(true);
529 public void mouseDragged(MouseEvent e) {
530 updateStickToEndState(false);
534 public void mouseWheelMoved(MouseWheelEvent e) {
535 updateStickToEndState(false);
538 myEditor.getScrollPane().addMouseWheelListener(mouseListener);
539 myEditor.getScrollPane().getVerticalScrollBar().addMouseListener(mouseListener);
540 myEditor.getScrollPane().getVerticalScrollBar().addMouseMotionListener(mouseListener);
541 myHyperlinks = new EditorHyperlinkSupport(myEditor, myProject);
542 myEditor.getScrollingModel().addVisibleAreaListener(new VisibleAreaListener() {
544 public void visibleAreaChanged(VisibleAreaEvent e) {
545 // There is a possible case that the console text is populated while the console is not shown (e.g. we're debugging and
546 // 'Debugger' tab is active while 'Console' is not). It's also possible that newly added text contains long lines that
547 // are soft wrapped. We want to update viewport position then when the console becomes visible.
548 Rectangle oldR = e.getOldRectangle();
550 if (oldR != null && oldR.height <= 0 &&
551 e.getNewRectangle().height > 0 &&
559 private void updateStickToEndState(boolean useImmediatePosition) {
560 if (myEditor == null) return;
562 JScrollBar scrollBar = myEditor.getScrollPane().getVerticalScrollBar();
563 int scrollBarPosition = useImmediatePosition ? scrollBar.getValue() :
564 myEditor.getScrollingModel().getVisibleAreaOnScrollingFinished().y;
565 boolean vscrollAtBottom = scrollBarPosition == scrollBar.getMaximum() - scrollBar.getVisibleAmount();
566 boolean stickingToEnd = isStickingToEnd();
568 if (!vscrollAtBottom && stickingToEnd) {
569 myCancelStickToEnd = true;
570 } else if (vscrollAtBottom && !stickingToEnd) {
575 protected JComponent createCenterComponent() {
576 return myEditor.getComponent();
580 public void dispose() {
581 myState = myState.dispose();
582 if (myEditor != null) {
583 cancelAllFlushRequests();
584 mySpareTimeAlarm.cancelAllRequests();
586 synchronized (LOCK) {
594 private void cancelAllFlushRequests() {
595 synchronized (myCurrentRequests) {
596 for (MyFlushRunnable request : myCurrentRequests) {
597 request.invalidate();
599 myCurrentRequests.clear();
600 myFlushAlarm.cancelAllRequests();
604 protected void disposeEditor() {
605 UIUtil.invokeAndWaitIfNeeded(new Runnable() {
608 if (!myEditor.isDisposed()) {
609 EditorFactory.getInstance().releaseEditor(myEditor);
616 public void print(@NotNull String s, @NotNull ConsoleViewContentType contentType) {
617 if (myInputMessageFilter == null) {
618 printHyperlink(s, contentType, null);
622 List<Pair<String, ConsoleViewContentType>> result = myInputMessageFilter.applyFilter(s, contentType);
623 if (result == null) {
624 printHyperlink(s, contentType, null);
627 for (Pair<String, ConsoleViewContentType> pair : result) {
628 if (pair.first != null) {
629 printHyperlink(pair.first, pair.second == null ? contentType : pair.second, null);
635 private void printHyperlink(@NotNull String s, @NotNull ConsoleViewContentType contentType, @Nullable HyperlinkInfo info) {
636 synchronized (LOCK) {
637 Pair<String, Integer> pair = myBuffer.print(s, contentType, info);
639 myContentSize += s.length() - pair.second;
641 if (contentType == ConsoleViewContentType.USER_INPUT && NEW_LINE_MATCHER.indexIn(s) >= 0) {
642 flushDeferredUserInput();
644 if (myEditor != null) {
645 final boolean shouldFlushNow = myBuffer.isUseCyclicBuffer() && myBuffer.getLength() >= myBuffer.getCyclicBufferSize();
646 addFlushRequest(new MyFlushRunnable(), shouldFlushNow ? 0 : DEFAULT_FLUSH_DELAY);
651 private void addToken(int length, @Nullable HyperlinkInfo info, ConsoleViewContentType contentType) {
652 ConsoleUtil.addToken(length, info, contentType, myTokens);
655 private static ModalityState getStateForUpdate() {
656 return null;//myStateForUpdate != null ? myStateForUpdate.compute() : ModalityState.stateForComponent(this);
659 private void requestFlushImmediately() {
660 if (myEditor != null) {
661 addFlushRequest(new MyFlushRunnable());
666 public int getContentSize() {
667 synchronized (LOCK) {
668 return myContentSize;
673 public boolean canPause() {
677 public void flushDeferredText() {
678 flushDeferredText(false);
681 private void flushDeferredText(boolean clear) {
682 ApplicationManager.getApplication().assertIsDispatchThread();
683 if (myProject.isDisposed()) {
686 EditorEx editor = myEditor;
687 if (editor == null) {
691 final boolean shouldStickToEnd = clear || !myCancelStickToEnd && isStickingToEnd();
692 myCancelStickToEnd = false; // Cancel only needs to last for one update. Next time, isStickingToEnd() will be false.
694 final DocumentEx document = editor.getDocument();
695 synchronized (LOCK) {
697 clearHyperlinkAndFoldings();
699 final int documentTextLength = document.getTextLength();
700 if (documentTextLength > 0) {
701 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
704 document.setInBulkUpdate(true);
706 myInDocumentUpdate = true;
707 myDocumentClearing = true;
708 document.deleteString(0, documentTextLength);
711 document.setInBulkUpdate(false);
712 myDocumentClearing = false;
713 myInDocumentUpdate = false;
716 }, null, DocCommandGroupId.noneGroupId(document));
721 final String addedText;
722 final Collection<ConsoleViewContentType> contentTypes;
723 int deferredTokensSize;
724 synchronized (LOCK) {
725 if (myOutputPaused) return;
726 if (myBuffer.isEmpty()) return;
728 addedText = myBuffer.getText();
730 contentTypes = Collections.unmodifiableCollection(new HashSet<ConsoleViewContentType>(myBuffer.getDeferredTokenTypes()));
731 List<TokenInfo> deferredTokens = myBuffer.getDeferredTokens();
732 for (TokenInfo deferredToken : deferredTokens) {
733 addToken(deferredToken.getLength(), deferredToken.getHyperlinkInfo(), deferredToken.contentType);
735 deferredTokensSize = deferredTokens.size();
736 myBuffer.clear(false);
739 final Document document = myEditor.getDocument();
740 final RangeMarker lastProcessedOutput = document.createRangeMarker(document.getTextLength(), document.getTextLength());
742 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
745 if (!shouldStickToEnd) {
746 myEditor.getScrollingModel().accumulateViewportChanges();
749 myInDocumentUpdate = true;
750 String[] strings = addedText.split("\\r", -1); // limit must be any negative number to avoid discarding of trailing empty strings
751 for (int i = 0; i < strings.length - 1; i++) {
752 document.insertString(document.getTextLength(), strings[i]);
753 int lastLine = document.getLineCount() - 1;
755 ConsoleUtil.updateTokensOnTextRemoval(myTokens, document.getTextLength(), document.getTextLength() + 1);
756 document.deleteString(document.getLineStartOffset(lastLine), document.getTextLength());
759 if (strings.length > 0) {
760 document.insertString(document.getTextLength(), strings[strings.length - 1]);
761 myContentSize -= strings.length - 1;
765 myInDocumentUpdate = false;
766 if (!shouldStickToEnd) {
767 myEditor.getScrollingModel().flushViewportChanges();
770 if (!contentTypes.isEmpty()) {
771 for (ChangeListener each : myListeners) {
772 each.contentAdded(contentTypes);
776 }, null, DocCommandGroupId.noneGroupId(document));
777 synchronized (LOCK) {
778 for (int i = myTokens.size() - 1; i >= 0 && deferredTokensSize > 0; i--, deferredTokensSize--) {
779 TokenInfo token = myTokens.get(i);
780 final HyperlinkInfo info = token.getHyperlinkInfo();
782 myHyperlinks.createHyperlink(token.startOffset, token.endOffset, null, info);
786 myPsiDisposedCheck.performCheck();
787 myLastAddedTextLength = addedText.length();
788 if (!myTooMuchOfOutput) {
789 if (isTheAmountOfTextTooBig(myLastAddedTextLength)) { // disable hyperlinks and folding until new output arriving slows down again
790 myTooMuchOfOutput = true;
791 final EditorNotificationPanel comp =
792 new EditorNotificationPanel().text("Too much output to process").icon(AllIcons.General.ExclMark);
793 final Alarm tooMuchOutputAlarm = new Alarm();
794 //show the notification with a delay to avoid blinking when "too much output" ceases quickly
795 tooMuchOutputAlarm.addRequest(new Runnable() {
798 add(comp, BorderLayout.NORTH);
801 performWhenNoDeferredOutput(new Runnable() {
804 if (!isTheAmountOfTextTooBig(myLastAddedTextLength)) {
806 highlightHyperlinksAndFoldings(lastProcessedOutput);
809 myTooMuchOfOutput = false;
811 tooMuchOutputAlarm.cancelAllRequests();
815 myLastAddedTextLength = 0;
816 performLaterWhenNoDeferredOutput(this);
822 highlightHyperlinksAndFoldings(lastProcessedOutput);
826 if (shouldStickToEnd) {
831 private boolean isStickingToEnd() {
832 if (myEditor == null) return myLastStickingToEnd;
833 Document document = myEditor.getDocument();
834 int caretOffset = myEditor.getCaretModel().getOffset();
835 myLastStickingToEnd = document.getLineNumber(caretOffset) >= document.getLineCount() - 1;
836 return myLastStickingToEnd;
839 private boolean isTheAmountOfTextTooBig(final int textLength) {
840 return myBuffer.isUseCyclicBuffer() && textLength > myBuffer.getCyclicBufferSize() / consoleTooMuchTextBufferRatio;
843 private void clearHyperlinkAndFoldings() {
844 myEditor.getMarkupModel().removeAllHighlighters();
846 myPendingFoldRegions.clear();
848 myFoldingAlarm.cancelAllRequests();
849 myEditor.getFoldingModel().runBatchFoldingOperation(new Runnable() {
852 myEditor.getFoldingModel().clearFoldRegions();
859 private void cancelHeavyAlarm() {
860 if (myHeavyAlarm != null && !myHeavyAlarm.isDisposed()) {
861 myHeavyAlarm.cancelAllRequests();
862 ++myHeavyUpdateTicket;
866 private void flushDeferredUserInput() {
867 final String textToSend = myBuffer.cutFirstUserInputLine();
868 if (textToSend == null) {
871 myFlushUserInputAlarm.addRequest(new Runnable() {
874 if (myState.isRunning()) {
876 // this may block forever, see IDEA-54340
877 myState.sendUserInput(textToSend);
879 catch (IOException ignored) {
887 public Object getData(final String dataId) {
888 if (CommonDataKeys.NAVIGATABLE.is(dataId)) {
889 if (myEditor == null) {
892 final LogicalPosition pos = myEditor.getCaretModel().getLogicalPosition();
893 final HyperlinkInfo info = myHyperlinks.getHyperlinkInfoByLineAndCol(pos.line, pos.column);
894 final OpenFileDescriptor openFileDescriptor = info instanceof FileHyperlinkInfo ? ((FileHyperlinkInfo)info).getDescriptor() : null;
895 if (openFileDescriptor == null || !openFileDescriptor.getFile().isValid()) {
898 return openFileDescriptor;
901 if (CommonDataKeys.EDITOR.is(dataId)) {
904 if (PlatformDataKeys.HELP_ID.is(dataId)) {
907 if (LangDataKeys.CONSOLE_VIEW.is(dataId)) {
914 public void setHelpId(final String helpId) {
918 public void setUpdateFoldingsEnabled(boolean updateFoldingsEnabled) {
919 myUpdateFoldingsEnabled = updateFoldingsEnabled;
923 public void addMessageFilter(final Filter filter) {
924 myFilters.addFilter(filter);
928 public void printHyperlink(final String hyperlinkText, final HyperlinkInfo info) {
929 printHyperlink(hyperlinkText, ConsoleViewContentType.NORMAL_OUTPUT, info);
932 private EditorEx createConsoleEditor() {
933 return ApplicationManager.getApplication().runReadAction(new Computable<EditorEx>() {
935 public EditorEx compute() {
936 EditorEx editor = doCreateConsoleEditor();
937 editor.setContextMenuGroupId(null); // disabling default context menu
938 editor.addEditorMouseListener(new EditorPopupHandler() {
940 public void invokePopup(final EditorMouseEvent event) {
941 popupInvoked(event.getMouseEvent());
944 editor.getDocument().addDocumentListener(new DocumentAdapter() {
946 public void documentChanged(DocumentEvent event) {
947 onDocumentChanged(event);
949 }, ConsoleViewImpl.this);
951 int bufferSize = myBuffer.isUseCyclicBuffer() ? myBuffer.getCyclicBufferSize() : 0;
952 editor.getDocument().setCyclicBufferSize(bufferSize);
954 editor.putUserData(CONSOLE_VIEW_IN_EDITOR_VIEW, ConsoleViewImpl.this);
956 editor.getSettings().setAllowSingleLogicalLineFolding(true); // We want to fold long soft-wrapped command lines
957 editor.setHighlighter(createHighlighter());
964 private void onDocumentChanged(DocumentEvent event) {
965 if (event.getNewLength() == 0) {
966 // string has been removed, adjust token ranges
967 synchronized (LOCK) {
968 ConsoleUtil.updateTokensOnTextRemoval(myTokens, event.getOffset(), event.getOffset() + event.getOldLength());
969 int toRemoveLen = event.getOldLength();
970 if (!myDocumentClearing) {
971 // If document is being cleared now, then this event has been occurred as a result of calling clear() method.
972 // At start clear() method sets 'myContentSize' to 0, so there is no need to perform update again.
973 // Moreover, performing update of 'myContentSize' breaks executing "console.print();" immediately after "console.clear();".
974 myContentSize -= Math.min(myContentSize, toRemoveLen);
978 else if (!myInDocumentUpdate) {
979 int newFragmentLength = event.getNewFragment().length();
980 // track external appends
981 if (event.getOldFragment().length() == 0 && newFragmentLength > 0) {
982 synchronized (LOCK) {
983 myContentSize += newFragmentLength;
984 addToken(newFragmentLength, null, ConsoleViewContentType.NORMAL_OUTPUT);
988 LOG.warn("unhandled external change: " + event);
993 protected EditorEx doCreateConsoleEditor() {
994 return ConsoleViewUtil.setupConsoleEditor(myProject, true, false);
997 protected MyHighlighter createHighlighter() {
998 return new MyHighlighter();
1001 private void registerConsoleEditorActions() {
1002 Shortcut[] shortcuts = KeymapManager.getInstance().getActiveKeymap().getShortcuts(IdeActions.ACTION_GOTO_DECLARATION);
1003 CustomShortcutSet shortcutSet = new CustomShortcutSet(ArrayUtil.mergeArrays(shortcuts, CommonShortcuts.ENTER.getShortcuts()));
1004 new HyperlinkNavigationAction().registerCustomShortcutSet(shortcutSet, myEditor.getContentComponent());
1008 new EnterHandler().registerCustomShortcutSet(CommonShortcuts.ENTER, myEditor.getContentComponent());
1009 registerActionHandler(myEditor, IdeActions.ACTION_EDITOR_PASTE, new PasteHandler());
1010 registerActionHandler(myEditor, IdeActions.ACTION_EDITOR_BACKSPACE, new BackSpaceHandler());
1011 registerActionHandler(myEditor, IdeActions.ACTION_EDITOR_DELETE, new DeleteHandler());
1013 registerActionHandler(myEditor, EOFAction.ACTION_ID, ActionManager.getInstance().getAction(EOFAction.ACTION_ID));
1017 private static void registerActionHandler(final Editor editor, final String actionId, final AnAction action) {
1018 final Keymap keymap = KeymapManager.getInstance().getActiveKeymap();
1019 final Shortcut[] shortcuts = keymap.getShortcuts(actionId);
1020 action.registerCustomShortcutSet(new CustomShortcutSet(shortcuts), editor.getContentComponent());
1023 private void popupInvoked(MouseEvent mouseEvent) {
1024 final ActionManager actionManager = ActionManager.getInstance();
1025 final HyperlinkInfo info = myHyperlinks != null ? myHyperlinks.getHyperlinkInfoByPoint(mouseEvent.getPoint()) : null;
1026 ActionGroup group = null;
1027 if (info instanceof HyperlinkWithPopupMenuInfo) {
1028 group = ((HyperlinkWithPopupMenuInfo)info).getPopupMenuGroup(mouseEvent);
1030 if (group == null) {
1031 group = (ActionGroup)actionManager.getAction(CONSOLE_VIEW_POPUP_MENU);
1033 final ConsoleActionsPostProcessor[] postProcessors = Extensions.getExtensions(ConsoleActionsPostProcessor.EP_NAME);
1034 AnAction[] result = group.getChildren(null);
1036 for (ConsoleActionsPostProcessor postProcessor : postProcessors) {
1037 result = postProcessor.postProcessPopupActions(this, result);
1039 final DefaultActionGroup processedGroup = new DefaultActionGroup(result);
1040 final ActionPopupMenu menu = actionManager.createActionPopupMenu(ActionPlaces.EDITOR_POPUP, processedGroup);
1041 menu.getComponent().show(mouseEvent.getComponent(), mouseEvent.getX(), mouseEvent.getY());
1044 private void highlightHyperlinksAndFoldings(RangeMarker lastProcessedOutput) {
1045 boolean canHighlightHyperlinks = !myFilters.isEmpty();
1047 if (!canHighlightHyperlinks && myUpdateFoldingsEnabled) {
1050 final int line1 = lastProcessedOutput.isValid() ? myEditor.getDocument().getLineNumber(lastProcessedOutput.getEndOffset()) : 0;
1051 lastProcessedOutput.dispose();
1052 int endLine = myEditor.getDocument().getLineCount() - 1;
1053 ApplicationManager.getApplication().assertIsDispatchThread();
1055 if (canHighlightHyperlinks) {
1056 myHyperlinks.highlightHyperlinks(myFilters, line1, endLine);
1059 if (myAllowHeavyFilters && myFilters.isAnyHeavy() && myFilters.shouldRunHeavy()) {
1060 runHeavyFilters(line1, endLine);
1062 if (myUpdateFoldingsEnabled) {
1063 updateFoldings(line1, endLine, true);
1067 private void runHeavyFilters(int line1, int endLine) {
1068 final int startLine = Math.max(0, line1);
1070 final Document document = myEditor.getDocument();
1071 final int startOffset = document.getLineStartOffset(startLine);
1072 String text = document.getText(new TextRange(startOffset, document.getLineEndOffset(endLine)));
1073 final Document documentCopy = new DocumentImpl(text,true);
1074 documentCopy.setReadOnly(true);
1076 myJLayeredPane.startUpdating();
1077 final int currentValue = myHeavyUpdateTicket;
1078 assert myHeavyAlarm != null;
1079 myHeavyAlarm.addRequest(new Runnable() {
1082 if (!myFilters.shouldRunHeavy()) return;
1084 myFilters.applyHeavyFilter(documentCopy, startOffset, startLine, new Consumer<FilterMixin.AdditionalHighlight>() {
1086 public void consume(final FilterMixin.AdditionalHighlight additionalHighlight) {
1087 addFlushRequest(new MyFlushRunnable() {
1089 public void doRun() {
1090 if (myHeavyUpdateTicket != currentValue) return;
1091 myHyperlinks.addHighlighter(additionalHighlight.getStart(), additionalHighlight.getEnd(),
1092 additionalHighlight.getTextAttributes(null));
1096 public boolean equals(Object o) {
1097 return this == o && super.equals(o);
1104 if (myHeavyAlarm.isEmpty()) {
1105 SwingUtilities.invokeLater(new Runnable() {
1108 myJLayeredPane.finishUpdating();
1117 private void updateFoldings(final int line1, final int endLine, boolean immediately) {
1118 final Document document = myEditor.getDocument();
1119 final CharSequence chars = document.getCharsSequence();
1120 final int startLine = Math.max(0, line1);
1121 final List<FoldRegion> toAdd = new ArrayList<FoldRegion>();
1122 for (int line = startLine; line <= endLine; line++) {
1123 boolean flushOnly = line == endLine;
1125 Grep Console plugin allows to fold empty lines. We need to handle this case in a special way.
1127 Multiple lines are grouped into one folding, but to know when you can create the folding,
1128 you need a line which does not belong to that folding.
1129 When a new line, or a chunk of lines is printed, #addFolding is called for that lines + for an empty string
1130 (which basically does only one thing, gets a folding displayed).
1131 We do not want to process that empty string, but also we do not want to wait for another line
1132 which will create and display the folding - we'd see an unfolded stacktrace until another text came and flushed it.
1133 So therefore the condition, the last line(empty string) should still flush, but not be processed by
1134 com.intellij.execution.ConsoleFolding.
1136 addFolding(document, chars, line, toAdd, flushOnly);
1138 if (!toAdd.isEmpty()) {
1139 doUpdateFolding(toAdd, immediately);
1143 private void doUpdateFolding(final List<FoldRegion> toAdd, final boolean immediately) {
1144 assertIsDispatchThread();
1145 myPendingFoldRegions.addAll(toAdd);
1147 myFoldingAlarm.cancelAllRequests();
1148 final Runnable runnable = new Runnable() {
1151 if (myEditor == null || myEditor.isDisposed()) {
1155 assertIsDispatchThread();
1156 final FoldingModel model = myEditor.getFoldingModel();
1157 final Runnable operation = new Runnable() {
1160 assertIsDispatchThread();
1161 for (FoldRegion region : myPendingFoldRegions) {
1162 region.setExpanded(false);
1163 model.addFoldRegion(region);
1165 myPendingFoldRegions.clear();
1169 model.runBatchFoldingOperation(operation);
1172 model.runBatchFoldingOperationDoNotCollapseCaret(operation);
1176 if (immediately || myPendingFoldRegions.size() > 100) {
1180 myFoldingAlarm.addRequest(runnable, 50);
1184 private void addFolding(Document document, CharSequence chars, int line, List<FoldRegion> toAdd, boolean flushOnly) {
1185 ConsoleFolding current = null;
1187 String commandLinePlaceholder = myCommandLineFolding.getPlaceholder(line);
1188 if (commandLinePlaceholder != null) {
1189 FoldRegion region = myEditor.getFoldingModel()
1190 .createFoldRegion(document.getLineStartOffset(line), document.getLineEndOffset(line), commandLinePlaceholder, null, false);
1194 current = foldingForLine(EditorHyperlinkSupport.getLineText(document, line, false));
1195 if (current != null) {
1196 myFolding.put(line, current);
1200 final ConsoleFolding prevFolding = myFolding.get(line - 1);
1201 if (current == null && prevFolding != null) {
1202 final int lEnd = line - 1;
1204 while (prevFolding.equals(myFolding.get(lStart - 1))) lStart--;
1206 for (int i = lStart; i <= lEnd; i++) {
1207 myFolding.remove(i);
1210 List<String> toFold = new ArrayList<String>(lEnd - lStart + 1);
1211 for (int i = lStart; i <= lEnd; i++) {
1212 toFold.add(EditorHyperlinkSupport.getLineText(document, i, false));
1215 int oStart = document.getLineStartOffset(lStart);
1216 if (oStart > 0) oStart--;
1217 int oEnd = CharArrayUtil.shiftBackward(chars, document.getLineEndOffset(lEnd) - 1, " \t") + 1;
1219 String placeholder = prevFolding.getPlaceholderText(toFold);
1220 FoldRegion region = placeholder == null ? null : myEditor.getFoldingModel().createFoldRegion(oStart, oEnd, placeholder, null, false);
1221 if (region != null) {
1228 private static ConsoleFolding foldingForLine(String lineText) {
1229 for (ConsoleFolding folding : ConsoleFolding.EP_NAME.getExtensions()) {
1230 if (folding.shouldFoldLine(lineText)) {
1238 public static class ClearAllAction extends DumbAwareAction {
1239 private final ConsoleView myConsoleView;
1241 @SuppressWarnings("unused")
1242 public ClearAllAction() {
1246 public ClearAllAction(ConsoleView consoleView) {
1247 super(ExecutionBundle.message("clear.all.from.console.action.name"), "Clear the contents of the console", AllIcons.Actions.GC);
1248 myConsoleView = consoleView;
1252 public void update(AnActionEvent e) {
1253 boolean enabled = myConsoleView != null && myConsoleView.getContentSize() > 0;
1255 enabled = e.getData(LangDataKeys.CONSOLE_VIEW) != null;
1256 Editor editor = e.getData(CommonDataKeys.EDITOR);
1257 if (editor != null && editor.getDocument().getTextLength() == 0) {
1261 e.getPresentation().setEnabled(enabled);
1265 public void actionPerformed(final AnActionEvent e) {
1266 final ConsoleView consoleView = myConsoleView != null ? myConsoleView : e.getData(LangDataKeys.CONSOLE_VIEW);
1267 if (consoleView != null) {
1268 consoleView.clear();
1273 private class MyHighlighter extends DocumentAdapter implements EditorHighlighter {
1274 private HighlighterClient myEditor;
1278 public HighlighterIterator createIterator(final int startOffset) {
1279 final int startIndex = ConsoleUtil.findTokenInfoIndexByOffset(myTokens, startOffset);
1281 return new HighlighterIterator() {
1282 private int myIndex = startIndex;
1285 public TextAttributes getTextAttributes() {
1286 return atEnd() ? null : getTokenInfo().contentType.getAttributes();
1290 public int getStart() {
1291 return atEnd() ? 0 : getTokenInfo().startOffset;
1295 public int getEnd() {
1296 return atEnd() ? 0 : getTokenInfo().endOffset;
1300 public IElementType getTokenType() {
1305 public void advance() {
1310 public void retreat() {
1315 public boolean atEnd() {
1316 return myIndex < 0 || myIndex >= myTokens.size();
1320 public Document getDocument() {
1321 return myEditor.getDocument();
1324 private TokenInfo getTokenInfo() {
1325 return myTokens.get(myIndex);
1331 public void setText(@NotNull final CharSequence text) {
1335 public void setEditor(@NotNull final HighlighterClient editor) {
1336 LOG.assertTrue(myEditor == null, "Highlighters cannot be reused with different editors");
1341 public void setColorScheme(@NotNull EditorColorsScheme scheme) {
1345 private static class MyTypedHandler extends TypedActionHandlerBase {
1347 private MyTypedHandler(final TypedActionHandler originalAction) {
1348 super(originalAction);
1352 public void execute(@NotNull final Editor editor, final char charTyped, @NotNull final DataContext dataContext) {
1353 final ConsoleViewImpl consoleView = editor.getUserData(CONSOLE_VIEW_IN_EDITOR_VIEW);
1354 if (consoleView == null || !consoleView.myState.isRunning() || consoleView.myIsViewer) {
1355 if (myOriginalHandler != null) myOriginalHandler.execute(editor, charTyped, dataContext);
1358 final String s = String.valueOf(charTyped);
1359 SelectionModel selectionModel = editor.getSelectionModel();
1360 if (selectionModel.hasSelection()) {
1361 consoleView.replaceUserText(s, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
1364 consoleView.insertUserText(s, editor.getCaretModel().getOffset());
1370 private abstract static class ConsoleAction extends AnAction implements DumbAware {
1372 public void actionPerformed(final AnActionEvent e) {
1373 final DataContext context = e.getDataContext();
1374 final ConsoleViewImpl console = getRunningConsole(context);
1375 execute(console, context);
1378 protected abstract void execute(ConsoleViewImpl console, final DataContext context);
1381 public void update(final AnActionEvent e) {
1382 final ConsoleViewImpl console = getRunningConsole(e.getDataContext());
1383 e.getPresentation().setEnabled(console != null);
1387 private static ConsoleViewImpl getRunningConsole(final DataContext context) {
1388 final Editor editor = CommonDataKeys.EDITOR.getData(context);
1389 if (editor != null) {
1390 final ConsoleViewImpl console = editor.getUserData(CONSOLE_VIEW_IN_EDITOR_VIEW);
1391 if (console != null && console.myState.isRunning()) {
1399 private static class EnterHandler extends ConsoleAction {
1401 public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
1402 consoleView.print("\n", ConsoleViewContentType.USER_INPUT);
1403 consoleView.flushDeferredText();
1404 final Editor editor = consoleView.myEditor;
1405 editor.getCaretModel().moveToOffset(editor.getDocument().getTextLength());
1406 editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1410 private static class PasteHandler extends ConsoleAction {
1412 public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
1413 String s = CopyPasteManager.getInstance().getContents(DataFlavor.stringFlavor);
1414 if (s == null) return;
1415 ApplicationManager.getApplication().assertIsDispatchThread();
1416 Editor editor = consoleView.myEditor;
1417 SelectionModel selectionModel = editor.getSelectionModel();
1418 if (selectionModel.hasSelection()) {
1419 consoleView.replaceUserText(s, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
1422 consoleView.insertUserText(s, editor.getCaretModel().getOffset());
1427 private static class BackSpaceHandler extends ConsoleAction {
1429 public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
1430 final Editor editor = consoleView.myEditor;
1432 if (IncrementalSearchHandler.isHintVisible(editor)) {
1433 getDefaultActionHandler().execute(editor, context);
1437 final Document document = editor.getDocument();
1438 final int length = document.getTextLength();
1443 ApplicationManager.getApplication().assertIsDispatchThread();
1445 SelectionModel selectionModel = editor.getSelectionModel();
1446 if (selectionModel.hasSelection()) {
1447 consoleView.deleteUserText(selectionModel.getSelectionStart(),
1448 selectionModel.getSelectionEnd() - selectionModel.getSelectionStart());
1450 else if (editor.getCaretModel().getOffset() > 0) {
1451 consoleView.deleteUserText(editor.getCaretModel().getOffset() - 1, 1);
1455 private static EditorActionHandler getDefaultActionHandler() {
1456 return EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE);
1460 private static class DeleteHandler extends ConsoleAction {
1462 public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
1463 final Editor editor = consoleView.myEditor;
1465 if (IncrementalSearchHandler.isHintVisible(editor)) {
1466 getDefaultActionHandler().execute(editor, context);
1470 final Document document = editor.getDocument();
1471 final int length = document.getTextLength();
1476 ApplicationManager.getApplication().assertIsDispatchThread();
1477 SelectionModel selectionModel = editor.getSelectionModel();
1478 if (selectionModel.hasSelection()) {
1479 consoleView.deleteUserText(selectionModel.getSelectionStart(),
1480 selectionModel.getSelectionEnd() - selectionModel.getSelectionStart());
1483 consoleView.deleteUserText(editor.getCaretModel().getOffset(), 1);
1487 private static EditorActionHandler getDefaultActionHandler() {
1488 return EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE);
1493 public JComponent getPreferredFocusableComponent() {
1494 //ensure editor created
1496 return myEditor.getContentComponent();
1500 // navigate up/down in stack trace
1502 public boolean hasNextOccurence() {
1503 return calcNextOccurrence(1) != null;
1507 public boolean hasPreviousOccurence() {
1508 return calcNextOccurrence(-1) != null;
1512 public OccurenceInfo goNextOccurence() {
1513 return calcNextOccurrence(1);
1517 protected OccurenceInfo calcNextOccurrence(final int delta) {
1518 final EditorHyperlinkSupport hyperlinks = myHyperlinks;
1519 if (hyperlinks == null) {
1523 return EditorHyperlinkSupport.getNextOccurrence(myEditor, delta, new Consumer<RangeHighlighter>() {
1525 public void consume(RangeHighlighter next) {
1526 int offset = next.getStartOffset();
1528 final HyperlinkInfo hyperlinkInfo = EditorHyperlinkSupport.getHyperlinkInfo(next);
1529 if (hyperlinkInfo instanceof BrowserHyperlinkInfo) {
1532 if (hyperlinkInfo instanceof HyperlinkInfoBase) {
1533 VisualPosition position = myEditor.offsetToVisualPosition(offset);
1534 Point point = myEditor.visualPositionToXY(new VisualPosition(position.getLine() + 1, position.getColumn()));
1535 ((HyperlinkInfoBase)hyperlinkInfo).navigate(myProject, new RelativePoint(myEditor.getContentComponent(), point));
1537 else if (hyperlinkInfo != null) {
1538 hyperlinkInfo.navigate(myProject);
1545 public OccurenceInfo goPreviousOccurence() {
1546 return calcNextOccurrence(-1);
1550 public String getNextOccurenceActionName() {
1551 return ExecutionBundle.message("down.the.stack.trace");
1555 public String getPreviousOccurenceActionName() {
1556 return ExecutionBundle.message("up.the.stack.trace");
1559 public void addCustomConsoleAction(@NotNull AnAction action) {
1560 customActions.add(action);
1565 public AnAction[] createConsoleActions() {
1566 //Initializing prev and next occurrences actions
1567 final CommonActionsManager actionsManager = CommonActionsManager.getInstance();
1568 final AnAction prevAction = actionsManager.createPrevOccurenceAction(this);
1569 prevAction.getTemplatePresentation().setText(getPreviousOccurenceActionName());
1570 final AnAction nextAction = actionsManager.createNextOccurenceAction(this);
1571 nextAction.getTemplatePresentation().setText(getNextOccurenceActionName());
1573 final AnAction switchSoftWrapsAction = new ToggleUseSoftWrapsToolbarAction(SoftWrapAppliancePlaces.CONSOLE) {
1576 * There is a possible case that more than console is open and user toggles soft wraps mode at one of them. We want
1577 * to update another console(s) representation as well when they are switched on after that. Hence, we remember last
1578 * used soft wraps mode and perform update if we see that the current value differs from the stored.
1580 private boolean myLastIsSelected;
1583 protected Editor getEditor(AnActionEvent e) {
1588 public boolean isSelected(AnActionEvent e) {
1589 boolean result = super.isSelected(e);
1590 if (result ^ myLastIsSelected) {
1591 setSelected(null, result);
1593 return myLastIsSelected = result;
1597 public void setSelected(AnActionEvent e, final boolean state) {
1598 super.setSelected(e, state);
1599 if (myEditor == null) {
1603 final String placeholder = myCommandLineFolding.getPlaceholder(0);
1604 final FoldingModel foldingModel = myEditor.getFoldingModel();
1605 final int firstLineEnd = myEditor.getDocument().getLineEndOffset(0);
1606 foldingModel.runBatchFoldingOperation(new Runnable() {
1609 FoldRegion[] regions = foldingModel.getAllFoldRegions();
1610 if (regions.length > 0 && regions[0].getStartOffset() == 0 && regions[0].getEndOffset() == firstLineEnd) {
1611 foldingModel.removeFoldRegion(regions[0]);
1613 if (placeholder != null) {
1614 FoldRegion foldRegion = foldingModel.addFoldRegion(0, firstLineEnd, placeholder);
1615 if (foldRegion != null) {
1616 foldRegion.setExpanded(false);
1623 final AnAction autoScrollToTheEndAction = new ScrollToTheEndToolbarAction(myEditor);
1625 //Initializing custom actions
1626 final AnAction[] consoleActions = new AnAction[6 + customActions.size()];
1627 consoleActions[0] = prevAction;
1628 consoleActions[1] = nextAction;
1629 consoleActions[2] = switchSoftWrapsAction;
1630 consoleActions[3] = autoScrollToTheEndAction;
1631 consoleActions[4] = ActionManager.getInstance().getAction("Print");
1632 consoleActions[5] = new ClearAllAction(this);
1633 for (int i = 0; i < customActions.size(); ++i) {
1634 consoleActions[i + 6] = customActions.get(i);
1636 ConsoleActionsPostProcessor[] postProcessors = Extensions.getExtensions(ConsoleActionsPostProcessor.EP_NAME);
1637 AnAction[] result = consoleActions;
1638 for (ConsoleActionsPostProcessor postProcessor : postProcessors) {
1639 result = postProcessor.postProcess(this, result);
1645 public void allowHeavyFilters() {
1646 myAllowHeavyFilters = true;
1650 public void addChangeListener(@NotNull final ChangeListener listener, @NotNull final Disposable parent) {
1651 myListeners.add(listener);
1652 Disposer.register(parent, new Disposable() {
1654 public void dispose() {
1655 myListeners.remove(listener);
1661 * insert text to document
1663 * @param s inserted text
1664 * @param offset relatively to all document text
1666 private void insertUserText(final String s, int offset) {
1667 ApplicationManager.getApplication().assertIsDispatchThread();
1668 final ConsoleViewImpl consoleView = this;
1669 final ConsoleBuffer buffer = consoleView.myBuffer;
1670 final Editor editor = consoleView.myEditor;
1671 final Document document = editor.getDocument();
1672 final int startOffset;
1674 String textToUse = StringUtil.convertLineSeparators(s);
1675 synchronized (consoleView.LOCK) {
1676 if (consoleView.myTokens.isEmpty()) {
1677 addToken(0, null, ConsoleViewContentType.SYSTEM_OUTPUT);
1679 final TokenInfo info = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1680 if (info.contentType != ConsoleViewContentType.USER_INPUT && !StringUtil.containsChar(textToUse, '\n')) {
1681 consoleView.print(textToUse, ConsoleViewContentType.USER_INPUT);
1682 consoleView.flushDeferredText();
1683 editor.getCaretModel().moveToOffset(document.getTextLength());
1684 editor.getSelectionModel().removeSelection();
1687 if (info.contentType != ConsoleViewContentType.USER_INPUT) {
1688 insertUserText("temp", offset);
1689 final TokenInfo newInfo = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1690 replaceUserText(textToUse, newInfo.startOffset, newInfo.endOffset);
1694 final int deferredOffset = myContentSize - buffer.getLength() - buffer.getUserInputLength();
1695 if (offset > info.endOffset) {
1696 startOffset = info.endOffset;
1699 startOffset = Math.max(deferredOffset, Math.max(info.startOffset, offset));
1702 buffer.addUserText(startOffset - deferredOffset, textToUse);
1704 int charCountToAdd = textToUse.length();
1705 info.endOffset += charCountToAdd;
1706 consoleView.myContentSize += charCountToAdd;
1710 myInDocumentUpdate = true;
1711 document.insertString(startOffset, textToUse);
1714 myInDocumentUpdate = false;
1716 // Math.max is needed when cyclic buffer is used
1717 editor.getCaretModel().moveToOffset(Math.min(startOffset + textToUse.length(), document.getTextLength()));
1718 editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1724 * @param s text for replace
1725 * @param start relatively to all document text
1726 * @param end relatively to all document text
1728 private void replaceUserText(final String s, int start, int end) {
1730 insertUserText(s, start);
1733 final ConsoleViewImpl consoleView = this;
1734 final ConsoleBuffer buffer = consoleView.myBuffer;
1735 final Editor editor = consoleView.myEditor;
1736 final Document document = editor.getDocument();
1737 final int startOffset;
1738 final int endOffset;
1740 synchronized (consoleView.LOCK) {
1741 if (consoleView.myTokens.isEmpty()) return;
1742 final TokenInfo info = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1743 if (info.contentType != ConsoleViewContentType.USER_INPUT) {
1744 consoleView.print(s, ConsoleViewContentType.USER_INPUT);
1745 consoleView.flushDeferredText();
1746 editor.getCaretModel().moveToOffset(document.getTextLength());
1747 editor.getSelectionModel().removeSelection();
1750 if (buffer.getUserInputLength() <= 0) return;
1752 final int deferredOffset = myContentSize - buffer.getLength() - buffer.getUserInputLength();
1754 startOffset = getStartOffset(start, info, deferredOffset);
1755 endOffset = getEndOffset(end, info);
1757 if (startOffset == -1 ||
1759 endOffset <= startOffset) {
1760 editor.getSelectionModel().removeSelection();
1761 editor.getCaretModel().moveToOffset(start);
1764 int charCountToReplace = s.length() - endOffset + startOffset;
1766 buffer.replaceUserText(startOffset - deferredOffset, endOffset - deferredOffset, s);
1768 info.endOffset += charCountToReplace;
1769 if (info.startOffset == info.endOffset) {
1770 consoleView.myTokens.remove(consoleView.myTokens.size() - 1);
1772 consoleView.myContentSize += charCountToReplace;
1776 myInDocumentUpdate = true;
1777 document.replaceString(startOffset, endOffset, s);
1780 myInDocumentUpdate = false;
1782 editor.getCaretModel().moveToOffset(startOffset + s.length());
1783 editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1784 editor.getSelectionModel().removeSelection();
1790 * @param offset relatively to all document text
1791 * @param length length of deleted text
1793 private void deleteUserText(int offset, int length) {
1794 ConsoleViewImpl consoleView = this;
1795 ConsoleBuffer buffer = consoleView.myBuffer;
1796 final Editor editor = consoleView.myEditor;
1797 final Document document = editor.getDocument();
1798 final int startOffset;
1799 final int endOffset;
1801 synchronized (consoleView.LOCK) {
1802 if (consoleView.myTokens.isEmpty()) return;
1803 final TokenInfo info = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1804 if (info.contentType != ConsoleViewContentType.USER_INPUT) return;
1805 if (myBuffer.getUserInputLength() == 0) return;
1807 final int deferredOffset = myContentSize - buffer.getLength() - buffer.getUserInputLength();
1808 startOffset = getStartOffset(offset, info, deferredOffset);
1809 endOffset = getEndOffset(offset + length, info);
1810 if (startOffset == -1 ||
1812 endOffset <= startOffset ||
1813 startOffset < deferredOffset) {
1814 editor.getSelectionModel().removeSelection();
1815 editor.getCaretModel().moveToOffset(offset);
1819 buffer.removeUserText(startOffset - deferredOffset, endOffset - deferredOffset);
1823 myInDocumentUpdate = true;
1824 document.deleteString(startOffset, endOffset);
1827 myInDocumentUpdate = false;
1829 editor.getCaretModel().moveToOffset(startOffset);
1830 editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1831 editor.getSelectionModel().removeSelection();
1834 //util methods for add, replace, delete methods
1835 private static int getStartOffset(int offset, TokenInfo info, int deferredOffset) {
1837 if (offset >= info.startOffset && offset < info.endOffset) {
1838 startOffset = Math.max(offset, deferredOffset);
1840 else if (offset < info.startOffset) {
1841 startOffset = Math.max(info.startOffset, deferredOffset);
1849 private static int getEndOffset(int offset, TokenInfo info) {
1851 if (offset > info.endOffset) {
1852 endOffset = info.endOffset;
1854 else if (offset <= info.startOffset) {
1863 public boolean isRunning() {
1864 return myState.isRunning();
1868 * Command line used to launch application/test from idea may be quite long.
1869 * Hence, it takes many visual lines during representation if soft wraps are enabled
1870 * or, otherwise, takes many columns and makes horizontal scrollbar thumb too small.
1872 * Our point is to fold such long command line and represent it as a single visual line by default.
1874 private class CommandLineFolding extends ConsoleFolding {
1877 * Checks if target line should be folded and returns its placeholder if the examination succeeds.
1879 * @param line index of line to check
1880 * @return placeholder text if given line should be folded; <code>null</code> otherwise
1883 private String getPlaceholder(int line) {
1884 if (myEditor == null || line != 0) {
1888 String text = EditorHyperlinkSupport.getLineText(myEditor.getDocument(), 0, false);
1889 // Don't fold the first line if the line is not that big.
1890 if (text.length() < 1000) {
1894 if (text.charAt(0) == '"') {
1895 index = text.indexOf('"', 1) + 1;
1898 boolean nonWhiteSpaceFound = false;
1899 for (; index < text.length(); index++) {
1900 char c = text.charAt(index);
1901 if (c != ' ' && c != '\t') {
1902 nonWhiteSpaceFound = true;
1905 if (nonWhiteSpaceFound) {
1910 assert index <= text.length();
1911 return text.substring(0, index) + " ...";
1915 public boolean shouldFoldLine(String line) {
1920 public String getPlaceholderText(List<String> lines) {
1921 // Is not expected to be called.
1926 private class MyFlushRunnable implements Runnable {
1927 private volatile boolean myValid = true;
1929 public final void run() {
1930 synchronized (myCurrentRequests) {
1931 myCurrentRequests.remove(this);
1938 protected void doRun() {
1939 flushDeferredText();
1942 public void invalidate() {
1946 public boolean isValid() {
1951 public boolean equals(Object o) {
1952 if (this == o) return true;
1953 if (o == null || getClass() != o.getClass()) return false;
1955 MyFlushRunnable runnable = (MyFlushRunnable)o;
1957 return myValid == runnable.myValid;
1961 public int hashCode() {
1962 return getClass().hashCode();
1966 private final class MyClearRunnable extends MyFlushRunnable {
1968 public void doRun() {
1969 flushDeferredText(true);
1974 public Project getProject() {
1978 private class HyperlinkNavigationAction extends DumbAwareAction {
1980 public void actionPerformed(AnActionEvent e) {
1981 Runnable runnable = myHyperlinks.getLinkNavigationRunnable(myEditor.getCaretModel().getLogicalPosition());
1982 assert runnable != null;
1987 public void update(AnActionEvent e) {
1988 e.getPresentation().setEnabled(myHyperlinks.getLinkNavigationRunnable(myEditor.getCaretModel().getLogicalPosition()) != null);
1993 public String getText() {
1994 return myEditor.getDocument().getText();