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 if (ConsoleViewUtil.isReplaceActionEnabledForConsoleViewEditor(myEditor)) {
996 clearHyperlinkAndFoldings();
997 highlightHyperlinksAndFoldings(event.getDocument().createRangeMarker(0, 0));
1000 LOG.warn("unhandled external change: " + event);
1006 protected EditorEx doCreateConsoleEditor() {
1007 return ConsoleViewUtil.setupConsoleEditor(myProject, true, false);
1010 protected MyHighlighter createHighlighter() {
1011 return new MyHighlighter();
1014 private void registerConsoleEditorActions() {
1015 Shortcut[] shortcuts = KeymapManager.getInstance().getActiveKeymap().getShortcuts(IdeActions.ACTION_GOTO_DECLARATION);
1016 CustomShortcutSet shortcutSet = new CustomShortcutSet(ArrayUtil.mergeArrays(shortcuts, CommonShortcuts.ENTER.getShortcuts()));
1017 new HyperlinkNavigationAction().registerCustomShortcutSet(shortcutSet, myEditor.getContentComponent());
1021 new EnterHandler().registerCustomShortcutSet(CommonShortcuts.ENTER, myEditor.getContentComponent());
1022 registerActionHandler(myEditor, IdeActions.ACTION_EDITOR_PASTE, new PasteHandler());
1023 registerActionHandler(myEditor, IdeActions.ACTION_EDITOR_BACKSPACE, new BackSpaceHandler());
1024 registerActionHandler(myEditor, IdeActions.ACTION_EDITOR_DELETE, new DeleteHandler());
1026 registerActionHandler(myEditor, EOFAction.ACTION_ID);
1030 private static void registerActionHandler(final Editor editor, final String actionId) {
1031 AnAction action = ActionManager.getInstance().getAction(actionId);
1032 action.registerCustomShortcutSet(action.getShortcutSet(), editor.getContentComponent());
1035 private static void registerActionHandler(final Editor editor, final String actionId, final AnAction action) {
1036 final Keymap keymap = KeymapManager.getInstance().getActiveKeymap();
1037 final Shortcut[] shortcuts = keymap.getShortcuts(actionId);
1038 action.registerCustomShortcutSet(new CustomShortcutSet(shortcuts), editor.getContentComponent());
1041 private void popupInvoked(MouseEvent mouseEvent) {
1042 final ActionManager actionManager = ActionManager.getInstance();
1043 final HyperlinkInfo info = myHyperlinks != null ? myHyperlinks.getHyperlinkInfoByPoint(mouseEvent.getPoint()) : null;
1044 ActionGroup group = null;
1045 if (info instanceof HyperlinkWithPopupMenuInfo) {
1046 group = ((HyperlinkWithPopupMenuInfo)info).getPopupMenuGroup(mouseEvent);
1048 if (group == null) {
1049 group = (ActionGroup)actionManager.getAction(CONSOLE_VIEW_POPUP_MENU);
1051 final ConsoleActionsPostProcessor[] postProcessors = Extensions.getExtensions(ConsoleActionsPostProcessor.EP_NAME);
1052 AnAction[] result = group.getChildren(null);
1054 for (ConsoleActionsPostProcessor postProcessor : postProcessors) {
1055 result = postProcessor.postProcessPopupActions(this, result);
1057 final DefaultActionGroup processedGroup = new DefaultActionGroup(result);
1058 final ActionPopupMenu menu = actionManager.createActionPopupMenu(ActionPlaces.EDITOR_POPUP, processedGroup);
1059 menu.getComponent().show(mouseEvent.getComponent(), mouseEvent.getX(), mouseEvent.getY());
1062 private void highlightHyperlinksAndFoldings(RangeMarker lastProcessedOutput) {
1063 boolean canHighlightHyperlinks = !myFilters.isEmpty();
1065 if (!canHighlightHyperlinks && myUpdateFoldingsEnabled) {
1068 final int line1 = lastProcessedOutput.isValid() ? myEditor.getDocument().getLineNumber(lastProcessedOutput.getEndOffset()) : 0;
1069 lastProcessedOutput.dispose();
1070 int endLine = myEditor.getDocument().getLineCount() - 1;
1071 ApplicationManager.getApplication().assertIsDispatchThread();
1073 if (canHighlightHyperlinks) {
1074 myHyperlinks.highlightHyperlinks(myFilters, line1, endLine);
1077 if (myAllowHeavyFilters && myFilters.isAnyHeavy() && myFilters.shouldRunHeavy()) {
1078 runHeavyFilters(line1, endLine);
1080 if (myUpdateFoldingsEnabled) {
1081 updateFoldings(line1, endLine, true);
1085 private void runHeavyFilters(int line1, int endLine) {
1086 final int startLine = Math.max(0, line1);
1088 final Document document = myEditor.getDocument();
1089 final int startOffset = document.getLineStartOffset(startLine);
1090 String text = document.getText(new TextRange(startOffset, document.getLineEndOffset(endLine)));
1091 final Document documentCopy = new DocumentImpl(text,true);
1092 documentCopy.setReadOnly(true);
1094 myJLayeredPane.startUpdating();
1095 final int currentValue = myHeavyUpdateTicket;
1096 assert myHeavyAlarm != null;
1097 myHeavyAlarm.addRequest(new Runnable() {
1100 if (!myFilters.shouldRunHeavy()) return;
1102 myFilters.applyHeavyFilter(documentCopy, startOffset, startLine, new Consumer<FilterMixin.AdditionalHighlight>() {
1104 public void consume(final FilterMixin.AdditionalHighlight additionalHighlight) {
1105 addFlushRequest(new MyFlushRunnable() {
1107 public void doRun() {
1108 if (myHeavyUpdateTicket != currentValue) return;
1109 myHyperlinks.addHighlighter(additionalHighlight.getStart(), additionalHighlight.getEnd(),
1110 additionalHighlight.getTextAttributes(null));
1114 public boolean equals(Object o) {
1115 return this == o && super.equals(o);
1122 if (myHeavyAlarm.isEmpty()) {
1123 SwingUtilities.invokeLater(new Runnable() {
1126 myJLayeredPane.finishUpdating();
1135 private void updateFoldings(final int line1, final int endLine, boolean immediately) {
1136 final Document document = myEditor.getDocument();
1137 final CharSequence chars = document.getCharsSequence();
1138 final int startLine = Math.max(0, line1);
1139 final List<FoldRegion> toAdd = new ArrayList<FoldRegion>();
1140 for (int line = startLine; line <= endLine; line++) {
1141 boolean flushOnly = line == endLine;
1143 Grep Console plugin allows to fold empty lines. We need to handle this case in a special way.
1145 Multiple lines are grouped into one folding, but to know when you can create the folding,
1146 you need a line which does not belong to that folding.
1147 When a new line, or a chunk of lines is printed, #addFolding is called for that lines + for an empty string
1148 (which basically does only one thing, gets a folding displayed).
1149 We do not want to process that empty string, but also we do not want to wait for another line
1150 which will create and display the folding - we'd see an unfolded stacktrace until another text came and flushed it.
1151 So therefore the condition, the last line(empty string) should still flush, but not be processed by
1152 com.intellij.execution.ConsoleFolding.
1154 addFolding(document, chars, line, toAdd, flushOnly);
1156 if (!toAdd.isEmpty()) {
1157 doUpdateFolding(toAdd, immediately);
1161 private void doUpdateFolding(final List<FoldRegion> toAdd, final boolean immediately) {
1162 assertIsDispatchThread();
1163 myPendingFoldRegions.addAll(toAdd);
1165 myFoldingAlarm.cancelAllRequests();
1166 final Runnable runnable = new Runnable() {
1169 if (myEditor == null || myEditor.isDisposed()) {
1173 assertIsDispatchThread();
1174 final FoldingModel model = myEditor.getFoldingModel();
1175 final Runnable operation = new Runnable() {
1178 assertIsDispatchThread();
1179 for (FoldRegion region : myPendingFoldRegions) {
1180 region.setExpanded(false);
1181 model.addFoldRegion(region);
1183 myPendingFoldRegions.clear();
1187 model.runBatchFoldingOperation(operation);
1190 model.runBatchFoldingOperationDoNotCollapseCaret(operation);
1194 if (immediately || myPendingFoldRegions.size() > 100) {
1198 myFoldingAlarm.addRequest(runnable, 50);
1202 private void addFolding(Document document, CharSequence chars, int line, List<FoldRegion> toAdd, boolean flushOnly) {
1203 ConsoleFolding current = null;
1205 String commandLinePlaceholder = myCommandLineFolding.getPlaceholder(line);
1206 if (commandLinePlaceholder != null) {
1207 FoldRegion region = myEditor.getFoldingModel()
1208 .createFoldRegion(document.getLineStartOffset(line), document.getLineEndOffset(line), commandLinePlaceholder, null, false);
1212 current = foldingForLine(EditorHyperlinkSupport.getLineText(document, line, false));
1213 if (current != null) {
1214 myFolding.put(line, current);
1218 final ConsoleFolding prevFolding = myFolding.get(line - 1);
1219 if (current == null && prevFolding != null) {
1220 final int lEnd = line - 1;
1222 while (prevFolding.equals(myFolding.get(lStart - 1))) lStart--;
1224 for (int i = lStart; i <= lEnd; i++) {
1225 myFolding.remove(i);
1228 List<String> toFold = new ArrayList<String>(lEnd - lStart + 1);
1229 for (int i = lStart; i <= lEnd; i++) {
1230 toFold.add(EditorHyperlinkSupport.getLineText(document, i, false));
1233 int oStart = document.getLineStartOffset(lStart);
1234 if (oStart > 0) oStart--;
1235 int oEnd = CharArrayUtil.shiftBackward(chars, document.getLineEndOffset(lEnd) - 1, " \t") + 1;
1237 String placeholder = prevFolding.getPlaceholderText(toFold);
1238 FoldRegion region = placeholder == null ? null : myEditor.getFoldingModel().createFoldRegion(oStart, oEnd, placeholder, null, false);
1239 if (region != null) {
1246 private static ConsoleFolding foldingForLine(String lineText) {
1247 for (ConsoleFolding folding : ConsoleFolding.EP_NAME.getExtensions()) {
1248 if (folding.shouldFoldLine(lineText)) {
1256 public static class ClearAllAction extends DumbAwareAction {
1257 private final ConsoleView myConsoleView;
1259 @SuppressWarnings("unused")
1260 public ClearAllAction() {
1264 public ClearAllAction(ConsoleView consoleView) {
1265 super(ExecutionBundle.message("clear.all.from.console.action.name"), "Clear the contents of the console", AllIcons.Actions.GC);
1266 myConsoleView = consoleView;
1270 public void update(AnActionEvent e) {
1271 boolean enabled = myConsoleView != null && myConsoleView.getContentSize() > 0;
1273 enabled = e.getData(LangDataKeys.CONSOLE_VIEW) != null;
1274 Editor editor = e.getData(CommonDataKeys.EDITOR);
1275 if (editor != null && editor.getDocument().getTextLength() == 0) {
1279 e.getPresentation().setEnabled(enabled);
1283 public void actionPerformed(final AnActionEvent e) {
1284 final ConsoleView consoleView = myConsoleView != null ? myConsoleView : e.getData(LangDataKeys.CONSOLE_VIEW);
1285 if (consoleView != null) {
1286 consoleView.clear();
1291 private class MyHighlighter extends DocumentAdapter implements EditorHighlighter {
1292 private HighlighterClient myEditor;
1296 public HighlighterIterator createIterator(final int startOffset) {
1297 final int startIndex = ConsoleUtil.findTokenInfoIndexByOffset(myTokens, startOffset);
1299 return new HighlighterIterator() {
1300 private int myIndex = startIndex;
1303 public TextAttributes getTextAttributes() {
1304 return atEnd() ? null : getTokenInfo().contentType.getAttributes();
1308 public int getStart() {
1309 return atEnd() ? 0 : getTokenInfo().startOffset;
1313 public int getEnd() {
1314 return atEnd() ? 0 : getTokenInfo().endOffset;
1318 public IElementType getTokenType() {
1323 public void advance() {
1328 public void retreat() {
1333 public boolean atEnd() {
1334 return myIndex < 0 || myIndex >= myTokens.size();
1338 public Document getDocument() {
1339 return myEditor.getDocument();
1342 private TokenInfo getTokenInfo() {
1343 return myTokens.get(myIndex);
1349 public void setText(@NotNull final CharSequence text) {
1353 public void setEditor(@NotNull final HighlighterClient editor) {
1354 LOG.assertTrue(myEditor == null, "Highlighters cannot be reused with different editors");
1359 public void setColorScheme(@NotNull EditorColorsScheme scheme) {
1363 private static class MyTypedHandler extends TypedActionHandlerBase {
1365 private MyTypedHandler(final TypedActionHandler originalAction) {
1366 super(originalAction);
1370 public void execute(@NotNull final Editor editor, final char charTyped, @NotNull final DataContext dataContext) {
1371 final ConsoleViewImpl consoleView = editor.getUserData(CONSOLE_VIEW_IN_EDITOR_VIEW);
1372 if (consoleView == null || !consoleView.myState.isRunning() || consoleView.myIsViewer) {
1373 if (myOriginalHandler != null) myOriginalHandler.execute(editor, charTyped, dataContext);
1376 final String s = String.valueOf(charTyped);
1377 SelectionModel selectionModel = editor.getSelectionModel();
1378 if (selectionModel.hasSelection()) {
1379 consoleView.replaceUserText(s, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
1382 consoleView.insertUserText(s, editor.getCaretModel().getOffset());
1388 private abstract static class ConsoleAction extends AnAction implements DumbAware {
1390 public void actionPerformed(final AnActionEvent e) {
1391 final DataContext context = e.getDataContext();
1392 final ConsoleViewImpl console = getRunningConsole(context);
1393 execute(console, context);
1396 protected abstract void execute(ConsoleViewImpl console, final DataContext context);
1399 public void update(final AnActionEvent e) {
1400 final ConsoleViewImpl console = getRunningConsole(e.getDataContext());
1401 e.getPresentation().setEnabled(console != null);
1405 private static ConsoleViewImpl getRunningConsole(final DataContext context) {
1406 final Editor editor = CommonDataKeys.EDITOR.getData(context);
1407 if (editor != null) {
1408 final ConsoleViewImpl console = editor.getUserData(CONSOLE_VIEW_IN_EDITOR_VIEW);
1409 if (console != null && console.myState.isRunning()) {
1417 private static class EnterHandler extends ConsoleAction {
1419 public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
1420 consoleView.print("\n", ConsoleViewContentType.USER_INPUT);
1421 consoleView.flushDeferredText();
1422 final Editor editor = consoleView.myEditor;
1423 editor.getCaretModel().moveToOffset(editor.getDocument().getTextLength());
1424 editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1428 private static class PasteHandler extends ConsoleAction {
1430 public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
1431 String s = CopyPasteManager.getInstance().getContents(DataFlavor.stringFlavor);
1432 if (s == null) return;
1433 s = StringUtil.convertLineSeparators(s);
1434 ApplicationManager.getApplication().assertIsDispatchThread();
1435 Editor editor = consoleView.myEditor;
1436 SelectionModel selectionModel = editor.getSelectionModel();
1437 if (selectionModel.hasSelection()) {
1438 consoleView.replaceUserText(s, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
1441 consoleView.insertUserText(s, editor.getCaretModel().getOffset());
1446 private static class BackSpaceHandler extends ConsoleAction {
1448 public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
1449 final Editor editor = consoleView.myEditor;
1451 if (IncrementalSearchHandler.isHintVisible(editor)) {
1452 getDefaultActionHandler().execute(editor, context);
1456 final Document document = editor.getDocument();
1457 final int length = document.getTextLength();
1462 ApplicationManager.getApplication().assertIsDispatchThread();
1464 SelectionModel selectionModel = editor.getSelectionModel();
1465 if (selectionModel.hasSelection()) {
1466 consoleView.deleteUserText(selectionModel.getSelectionStart(),
1467 selectionModel.getSelectionEnd() - selectionModel.getSelectionStart());
1469 else if (editor.getCaretModel().getOffset() > 0) {
1470 consoleView.deleteUserText(editor.getCaretModel().getOffset() - 1, 1);
1474 private static EditorActionHandler getDefaultActionHandler() {
1475 return EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE);
1479 private static class DeleteHandler extends ConsoleAction {
1481 public void execute(final ConsoleViewImpl consoleView, final DataContext context) {
1482 final Editor editor = consoleView.myEditor;
1484 if (IncrementalSearchHandler.isHintVisible(editor)) {
1485 getDefaultActionHandler().execute(editor, context);
1489 final Document document = editor.getDocument();
1490 final int length = document.getTextLength();
1495 ApplicationManager.getApplication().assertIsDispatchThread();
1496 SelectionModel selectionModel = editor.getSelectionModel();
1497 if (selectionModel.hasSelection()) {
1498 consoleView.deleteUserText(selectionModel.getSelectionStart(),
1499 selectionModel.getSelectionEnd() - selectionModel.getSelectionStart());
1502 consoleView.deleteUserText(editor.getCaretModel().getOffset(), 1);
1506 private static EditorActionHandler getDefaultActionHandler() {
1507 return EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE);
1512 public JComponent getPreferredFocusableComponent() {
1513 //ensure editor created
1515 return myEditor.getContentComponent();
1519 // navigate up/down in stack trace
1521 public boolean hasNextOccurence() {
1522 return calcNextOccurrence(1) != null;
1526 public boolean hasPreviousOccurence() {
1527 return calcNextOccurrence(-1) != null;
1531 public OccurenceInfo goNextOccurence() {
1532 return calcNextOccurrence(1);
1536 protected OccurenceInfo calcNextOccurrence(final int delta) {
1537 final EditorHyperlinkSupport hyperlinks = myHyperlinks;
1538 if (hyperlinks == null) {
1542 return EditorHyperlinkSupport.getNextOccurrence(myEditor, delta, new Consumer<RangeHighlighter>() {
1544 public void consume(RangeHighlighter next) {
1545 int offset = next.getStartOffset();
1547 final HyperlinkInfo hyperlinkInfo = EditorHyperlinkSupport.getHyperlinkInfo(next);
1548 if (hyperlinkInfo instanceof BrowserHyperlinkInfo) {
1551 if (hyperlinkInfo instanceof HyperlinkInfoBase) {
1552 VisualPosition position = myEditor.offsetToVisualPosition(offset);
1553 Point point = myEditor.visualPositionToXY(new VisualPosition(position.getLine() + 1, position.getColumn()));
1554 ((HyperlinkInfoBase)hyperlinkInfo).navigate(myProject, new RelativePoint(myEditor.getContentComponent(), point));
1556 else if (hyperlinkInfo != null) {
1557 hyperlinkInfo.navigate(myProject);
1564 public OccurenceInfo goPreviousOccurence() {
1565 return calcNextOccurrence(-1);
1569 public String getNextOccurenceActionName() {
1570 return ExecutionBundle.message("down.the.stack.trace");
1574 public String getPreviousOccurenceActionName() {
1575 return ExecutionBundle.message("up.the.stack.trace");
1578 public void addCustomConsoleAction(@NotNull AnAction action) {
1579 customActions.add(action);
1584 public AnAction[] createConsoleActions() {
1585 //Initializing prev and next occurrences actions
1586 final CommonActionsManager actionsManager = CommonActionsManager.getInstance();
1587 final AnAction prevAction = actionsManager.createPrevOccurenceAction(this);
1588 prevAction.getTemplatePresentation().setText(getPreviousOccurenceActionName());
1589 final AnAction nextAction = actionsManager.createNextOccurenceAction(this);
1590 nextAction.getTemplatePresentation().setText(getNextOccurenceActionName());
1592 final AnAction switchSoftWrapsAction = new ToggleUseSoftWrapsToolbarAction(SoftWrapAppliancePlaces.CONSOLE) {
1595 * There is a possible case that more than console is open and user toggles soft wraps mode at one of them. We want
1596 * to update another console(s) representation as well when they are switched on after that. Hence, we remember last
1597 * used soft wraps mode and perform update if we see that the current value differs from the stored.
1599 private boolean myLastIsSelected;
1602 protected Editor getEditor(AnActionEvent e) {
1607 public boolean isSelected(AnActionEvent e) {
1608 boolean result = super.isSelected(e);
1609 if (result ^ myLastIsSelected) {
1610 setSelected(null, result);
1612 return myLastIsSelected = result;
1616 public void setSelected(AnActionEvent e, final boolean state) {
1617 super.setSelected(e, state);
1618 if (myEditor == null) {
1622 final String placeholder = myCommandLineFolding.getPlaceholder(0);
1623 final FoldingModel foldingModel = myEditor.getFoldingModel();
1624 final int firstLineEnd = myEditor.getDocument().getLineEndOffset(0);
1625 foldingModel.runBatchFoldingOperation(new Runnable() {
1628 FoldRegion[] regions = foldingModel.getAllFoldRegions();
1629 if (regions.length > 0 && regions[0].getStartOffset() == 0 && regions[0].getEndOffset() == firstLineEnd) {
1630 foldingModel.removeFoldRegion(regions[0]);
1632 if (placeholder != null) {
1633 FoldRegion foldRegion = foldingModel.addFoldRegion(0, firstLineEnd, placeholder);
1634 if (foldRegion != null) {
1635 foldRegion.setExpanded(false);
1642 final AnAction autoScrollToTheEndAction = new ScrollToTheEndToolbarAction(myEditor);
1644 //Initializing custom actions
1645 final AnAction[] consoleActions = new AnAction[6 + customActions.size()];
1646 consoleActions[0] = prevAction;
1647 consoleActions[1] = nextAction;
1648 consoleActions[2] = switchSoftWrapsAction;
1649 consoleActions[3] = autoScrollToTheEndAction;
1650 consoleActions[4] = ActionManager.getInstance().getAction("Print");
1651 consoleActions[5] = new ClearAllAction(this);
1652 for (int i = 0; i < customActions.size(); ++i) {
1653 consoleActions[i + 6] = customActions.get(i);
1655 ConsoleActionsPostProcessor[] postProcessors = Extensions.getExtensions(ConsoleActionsPostProcessor.EP_NAME);
1656 AnAction[] result = consoleActions;
1657 for (ConsoleActionsPostProcessor postProcessor : postProcessors) {
1658 result = postProcessor.postProcess(this, result);
1664 public void allowHeavyFilters() {
1665 myAllowHeavyFilters = true;
1669 public void addChangeListener(@NotNull final ChangeListener listener, @NotNull final Disposable parent) {
1670 myListeners.add(listener);
1671 Disposer.register(parent, new Disposable() {
1673 public void dispose() {
1674 myListeners.remove(listener);
1680 * insert text to document
1682 * @param s inserted text
1683 * @param offset relatively to all document text
1685 private void insertUserText(final String s, int offset) {
1686 ApplicationManager.getApplication().assertIsDispatchThread();
1687 final ConsoleViewImpl consoleView = this;
1688 final ConsoleBuffer buffer = consoleView.myBuffer;
1689 final Editor editor = consoleView.myEditor;
1690 final Document document = editor.getDocument();
1691 final int startOffset;
1693 String textToUse = StringUtil.convertLineSeparators(s);
1694 synchronized (consoleView.LOCK) {
1695 if (consoleView.myTokens.isEmpty()) {
1696 addToken(0, null, ConsoleViewContentType.SYSTEM_OUTPUT);
1698 final TokenInfo info = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1699 if (info.contentType != ConsoleViewContentType.USER_INPUT && !StringUtil.containsChar(textToUse, '\n')) {
1700 consoleView.print(textToUse, ConsoleViewContentType.USER_INPUT);
1701 consoleView.flushDeferredText();
1702 editor.getCaretModel().moveToOffset(document.getTextLength());
1703 editor.getSelectionModel().removeSelection();
1706 if (info.contentType != ConsoleViewContentType.USER_INPUT) {
1707 insertUserText("temp", offset);
1708 final TokenInfo newInfo = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1709 replaceUserText(textToUse, newInfo.startOffset, newInfo.endOffset);
1713 final int deferredOffset = myContentSize - buffer.getLength() - buffer.getUserInputLength();
1714 if (offset > info.endOffset) {
1715 startOffset = info.endOffset;
1718 startOffset = Math.max(deferredOffset, Math.max(info.startOffset, offset));
1721 buffer.addUserText(startOffset - deferredOffset, textToUse);
1723 int charCountToAdd = textToUse.length();
1724 info.endOffset += charCountToAdd;
1725 consoleView.myContentSize += charCountToAdd;
1729 myInDocumentUpdate = true;
1730 document.insertString(startOffset, textToUse);
1733 myInDocumentUpdate = false;
1735 // Math.max is needed when cyclic buffer is used
1736 editor.getCaretModel().moveToOffset(Math.min(startOffset + textToUse.length(), document.getTextLength()));
1737 editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1743 * @param s text for replace
1744 * @param start relatively to all document text
1745 * @param end relatively to all document text
1747 private void replaceUserText(final String s, int start, int end) {
1749 insertUserText(s, start);
1752 final ConsoleViewImpl consoleView = this;
1753 final ConsoleBuffer buffer = consoleView.myBuffer;
1754 final Editor editor = consoleView.myEditor;
1755 final Document document = editor.getDocument();
1756 final int startOffset;
1757 final int endOffset;
1759 synchronized (consoleView.LOCK) {
1760 if (consoleView.myTokens.isEmpty()) return;
1761 final TokenInfo info = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1762 if (info.contentType != ConsoleViewContentType.USER_INPUT) {
1763 consoleView.print(s, ConsoleViewContentType.USER_INPUT);
1764 consoleView.flushDeferredText();
1765 editor.getCaretModel().moveToOffset(document.getTextLength());
1766 editor.getSelectionModel().removeSelection();
1769 if (buffer.getUserInputLength() <= 0) return;
1771 final int deferredOffset = myContentSize - buffer.getLength() - buffer.getUserInputLength();
1773 startOffset = getStartOffset(start, info, deferredOffset);
1774 endOffset = getEndOffset(end, info);
1776 if (startOffset == -1 ||
1778 endOffset <= startOffset) {
1779 editor.getSelectionModel().removeSelection();
1780 editor.getCaretModel().moveToOffset(start);
1783 int charCountToReplace = s.length() - endOffset + startOffset;
1785 buffer.replaceUserText(startOffset - deferredOffset, endOffset - deferredOffset, s);
1787 info.endOffset += charCountToReplace;
1788 if (info.startOffset == info.endOffset) {
1789 consoleView.myTokens.remove(consoleView.myTokens.size() - 1);
1791 consoleView.myContentSize += charCountToReplace;
1795 myInDocumentUpdate = true;
1796 document.replaceString(startOffset, endOffset, s);
1799 myInDocumentUpdate = false;
1801 editor.getCaretModel().moveToOffset(startOffset + s.length());
1802 editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1803 editor.getSelectionModel().removeSelection();
1809 * @param offset relatively to all document text
1810 * @param length length of deleted text
1812 private void deleteUserText(int offset, int length) {
1813 ConsoleViewImpl consoleView = this;
1814 ConsoleBuffer buffer = consoleView.myBuffer;
1815 final Editor editor = consoleView.myEditor;
1816 final Document document = editor.getDocument();
1817 final int startOffset;
1818 final int endOffset;
1820 synchronized (consoleView.LOCK) {
1821 if (consoleView.myTokens.isEmpty()) return;
1822 final TokenInfo info = consoleView.myTokens.get(consoleView.myTokens.size() - 1);
1823 if (info.contentType != ConsoleViewContentType.USER_INPUT) return;
1824 if (myBuffer.getUserInputLength() == 0) return;
1826 final int deferredOffset = myContentSize - buffer.getLength() - buffer.getUserInputLength();
1827 startOffset = getStartOffset(offset, info, deferredOffset);
1828 endOffset = getEndOffset(offset + length, info);
1829 if (startOffset == -1 ||
1831 endOffset <= startOffset ||
1832 startOffset < deferredOffset) {
1833 editor.getSelectionModel().removeSelection();
1834 editor.getCaretModel().moveToOffset(offset);
1838 buffer.removeUserText(startOffset - deferredOffset, endOffset - deferredOffset);
1842 myInDocumentUpdate = true;
1843 document.deleteString(startOffset, endOffset);
1846 myInDocumentUpdate = false;
1848 editor.getCaretModel().moveToOffset(startOffset);
1849 editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1850 editor.getSelectionModel().removeSelection();
1853 //util methods for add, replace, delete methods
1854 private static int getStartOffset(int offset, TokenInfo info, int deferredOffset) {
1856 if (offset >= info.startOffset && offset < info.endOffset) {
1857 startOffset = Math.max(offset, deferredOffset);
1859 else if (offset < info.startOffset) {
1860 startOffset = Math.max(info.startOffset, deferredOffset);
1868 private static int getEndOffset(int offset, TokenInfo info) {
1870 if (offset > info.endOffset) {
1871 endOffset = info.endOffset;
1873 else if (offset <= info.startOffset) {
1882 public boolean isRunning() {
1883 return myState.isRunning();
1887 * Command line used to launch application/test from idea may be quite long.
1888 * Hence, it takes many visual lines during representation if soft wraps are enabled
1889 * or, otherwise, takes many columns and makes horizontal scrollbar thumb too small.
1891 * Our point is to fold such long command line and represent it as a single visual line by default.
1893 private class CommandLineFolding extends ConsoleFolding {
1896 * Checks if target line should be folded and returns its placeholder if the examination succeeds.
1898 * @param line index of line to check
1899 * @return placeholder text if given line should be folded; <code>null</code> otherwise
1902 private String getPlaceholder(int line) {
1903 if (myEditor == null || line != 0) {
1907 String text = EditorHyperlinkSupport.getLineText(myEditor.getDocument(), 0, false);
1908 // Don't fold the first line if the line is not that big.
1909 if (text.length() < 1000) {
1913 if (text.charAt(0) == '"') {
1914 index = text.indexOf('"', 1) + 1;
1917 boolean nonWhiteSpaceFound = false;
1918 for (; index < text.length(); index++) {
1919 char c = text.charAt(index);
1920 if (c != ' ' && c != '\t') {
1921 nonWhiteSpaceFound = true;
1924 if (nonWhiteSpaceFound) {
1929 assert index <= text.length();
1930 return text.substring(0, index) + " ...";
1934 public boolean shouldFoldLine(String line) {
1939 public String getPlaceholderText(List<String> lines) {
1940 // Is not expected to be called.
1945 private class MyFlushRunnable implements Runnable {
1946 private volatile boolean myValid = true;
1948 public final void run() {
1949 synchronized (myCurrentRequests) {
1950 myCurrentRequests.remove(this);
1957 protected void doRun() {
1958 flushDeferredText();
1961 public void invalidate() {
1965 public boolean isValid() {
1970 public boolean equals(Object o) {
1971 if (this == o) return true;
1972 if (o == null || getClass() != o.getClass()) return false;
1974 MyFlushRunnable runnable = (MyFlushRunnable)o;
1976 return myValid == runnable.myValid;
1980 public int hashCode() {
1981 return getClass().hashCode();
1985 private final class MyClearRunnable extends MyFlushRunnable {
1987 public void doRun() {
1988 flushDeferredText(true);
1993 public Project getProject() {
1997 private class HyperlinkNavigationAction extends DumbAwareAction {
1999 public void actionPerformed(AnActionEvent e) {
2000 Runnable runnable = myHyperlinks.getLinkNavigationRunnable(myEditor.getCaretModel().getLogicalPosition());
2001 assert runnable != null;
2006 public void update(AnActionEvent e) {
2007 e.getPresentation().setEnabled(myHyperlinks.getLinkNavigationRunnable(myEditor.getCaretModel().getLogicalPosition()) != null);
2012 public String getText() {
2013 return myEditor.getDocument().getText();