1 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
3 package com.intellij.execution.impl;
5 import com.google.common.base.CharMatcher;
6 import com.intellij.codeInsight.folding.impl.FoldingUtil;
7 import com.intellij.codeInsight.navigation.IncrementalSearchHandler;
8 import com.intellij.codeInsight.template.impl.editorActions.TypedActionHandlerBase;
9 import com.intellij.execution.ConsoleFolding;
10 import com.intellij.execution.ExecutionBundle;
11 import com.intellij.execution.actions.ClearConsoleAction;
12 import com.intellij.execution.actions.ConsoleActionsPostProcessor;
13 import com.intellij.execution.actions.EOFAction;
14 import com.intellij.execution.filters.*;
15 import com.intellij.execution.filters.Filter.ResultItem;
16 import com.intellij.execution.process.ProcessHandler;
17 import com.intellij.execution.ui.ConsoleView;
18 import com.intellij.execution.ui.ConsoleViewContentType;
19 import com.intellij.execution.ui.ObservableConsoleView;
20 import com.intellij.ide.CommonActionsManager;
21 import com.intellij.ide.OccurenceNavigator;
22 import com.intellij.ide.startup.StartupManagerEx;
23 import com.intellij.openapi.Disposable;
24 import com.intellij.openapi.actionSystem.*;
25 import com.intellij.openapi.application.ApplicationManager;
26 import com.intellij.openapi.application.ModalityState;
27 import com.intellij.openapi.application.ReadAction;
28 import com.intellij.openapi.command.undo.UndoUtil;
29 import com.intellij.openapi.diagnostic.Logger;
30 import com.intellij.openapi.editor.*;
31 import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
32 import com.intellij.openapi.editor.actionSystem.EditorActionManager;
33 import com.intellij.openapi.editor.actionSystem.TypedAction;
34 import com.intellij.openapi.editor.actionSystem.TypedActionHandler;
35 import com.intellij.openapi.editor.actions.ScrollToTheEndToolbarAction;
36 import com.intellij.openapi.editor.actions.ToggleUseSoftWrapsToolbarAction;
37 import com.intellij.openapi.editor.colors.EditorColorsManager;
38 import com.intellij.openapi.editor.event.EditorMouseEvent;
39 import com.intellij.openapi.editor.ex.DocumentEx;
40 import com.intellij.openapi.editor.ex.EditorEx;
41 import com.intellij.openapi.editor.ex.MarkupModelEx;
42 import com.intellij.openapi.editor.ex.RangeHighlighterEx;
43 import com.intellij.openapi.editor.ex.util.EditorUtil;
44 import com.intellij.openapi.editor.impl.ContextMenuPopupHandler;
45 import com.intellij.openapi.editor.impl.DocumentImpl;
46 import com.intellij.openapi.editor.impl.DocumentMarkupModel;
47 import com.intellij.openapi.editor.impl.RangeMarkerImpl;
48 import com.intellij.openapi.editor.impl.softwrap.SoftWrapAppliancePlaces;
49 import com.intellij.openapi.editor.markup.*;
50 import com.intellij.openapi.fileEditor.OpenFileDescriptor;
51 import com.intellij.openapi.ide.CopyPasteManager;
52 import com.intellij.openapi.keymap.Keymap;
53 import com.intellij.openapi.keymap.KeymapManager;
54 import com.intellij.openapi.keymap.KeymapUtil;
55 import com.intellij.openapi.project.*;
56 import com.intellij.openapi.util.*;
57 import com.intellij.openapi.util.text.StringUtil;
58 import com.intellij.psi.search.GlobalSearchScope;
59 import com.intellij.ui.IdeBorderFactory;
60 import com.intellij.ui.SideBorder;
61 import com.intellij.ui.awt.RelativePoint;
62 import com.intellij.util.*;
63 import com.intellij.util.text.CharArrayUtil;
64 import com.intellij.util.ui.UIUtil;
65 import org.jetbrains.annotations.*;
69 import java.awt.datatransfer.DataFlavor;
70 import java.awt.event.MouseAdapter;
71 import java.awt.event.MouseEvent;
72 import java.awt.event.MouseWheelEvent;
73 import java.io.IOException;
74 import java.util.List;
76 import java.util.concurrent.*;
77 import java.util.concurrent.atomic.AtomicBoolean;
79 public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableConsoleView, DataProvider, OccurenceNavigator {
80 @NonNls private static final String CONSOLE_VIEW_POPUP_MENU = "ConsoleView.PopupMenu";
81 private static final Logger LOG = Logger.getInstance(ConsoleViewImpl.class);
83 private static final int DEFAULT_FLUSH_DELAY = SystemProperties.getIntProperty("console.flush.delay.ms", 200);
85 public static final Key<ConsoleViewImpl> CONSOLE_VIEW_IN_EDITOR_VIEW = Key.create("CONSOLE_VIEW_IN_EDITOR_VIEW");
86 private static final Key<ConsoleViewContentType> CONTENT_TYPE = Key.create("ConsoleViewContentType");
87 private static final Key<Boolean> USER_INPUT_SENT = Key.create("USER_INPUT_SENT");
88 private static final Key<Boolean> MANUAL_HYPERLINK = Key.create("MANUAL_HYPERLINK");
89 private static final char BACKSPACE = '\b';
91 private static boolean ourTypedHandlerInitialized;
92 private final Alarm myFlushUserInputAlarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, this);
93 private static final CharMatcher NEW_LINE_MATCHER = CharMatcher.anyOf("\n\r");
95 private final CommandLineFolding myCommandLineFolding = new CommandLineFolding();
97 private final DisposedPsiManagerCheck myPsiDisposedCheck;
99 private final boolean myIsViewer;
101 private ConsoleState myState;
103 private final Alarm mySpareTimeAlarm = new Alarm(this);
106 private final Alarm myHeavyAlarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, this);
107 private volatile int myHeavyUpdateTicket;
108 private final Collection<ChangeListener> myListeners = new CopyOnWriteArraySet<>();
110 private final List<AnAction> customActions = new ArrayList<>();
111 /** the text from {@link #print(String, ConsoleViewContentType)} goes there and stays there until {@link #flushDeferredText()} is called */
112 private final TokenBuffer myDeferredBuffer = new TokenBuffer(ConsoleBuffer.useCycleBuffer() ? ConsoleBuffer.getCycleBufferSize() : Integer.MAX_VALUE);
114 private boolean myUpdateFoldingsEnabled = true;
116 private EditorHyperlinkSupport myHyperlinks;
117 private MyDiffContainer myJLayeredPane;
118 private JPanel myMainPanel;
119 private boolean myAllowHeavyFilters;
120 private boolean myLastStickingToEnd;
121 private boolean myCancelStickToEnd;
123 private final Alarm myFlushAlarm = new Alarm(this);
125 private final Project myProject;
127 private boolean myOutputPaused;
129 private EditorEx myEditor;
131 private final Object LOCK = new Object();
133 private String myHelpId;
135 private final boolean myUsePredefinedMessageFilter;
137 private final GlobalSearchScope mySearchScope;
139 private final List<Filter> myCustomFilters = new SmartList<>();
142 private final InputFilter myInputMessageFilter;
144 public ConsoleViewImpl(@NotNull Project project, boolean viewer) {
145 this(project, GlobalSearchScope.allScope(project), viewer, true);
148 public ConsoleViewImpl(@NotNull Project project,
149 @NotNull GlobalSearchScope searchScope,
151 boolean usePredefinedMessageFilter) {
152 this(project, searchScope, viewer,
153 new ConsoleState.NotStartedStated() {
156 public ConsoleState attachTo(@NotNull ConsoleViewImpl console, @NotNull ProcessHandler processHandler) {
157 return new ConsoleViewRunningState(console, processHandler, this, true, true);
160 usePredefinedMessageFilter);
163 protected ConsoleViewImpl(@NotNull Project project,
164 @NotNull GlobalSearchScope searchScope,
166 @NotNull ConsoleState initialState,
167 boolean usePredefinedMessageFilter) {
168 super(new BorderLayout());
171 myState = initialState;
172 myPsiDisposedCheck = new DisposedPsiManagerCheck(project);
174 myUsePredefinedMessageFilter = usePredefinedMessageFilter;
175 mySearchScope = searchScope;
177 List<ConsoleInputFilterProvider> inputFilters = ConsoleInputFilterProvider.INPUT_FILTER_PROVIDERS.getExtensionList();
178 if (!inputFilters.isEmpty()) {
179 CompositeInputFilter compositeInputFilter = new CompositeInputFilter(project);
180 myInputMessageFilter = compositeInputFilter;
181 for (ConsoleInputFilterProvider eachProvider : inputFilters) {
182 InputFilter[] filters = eachProvider.getDefaultFilters(project);
183 for (InputFilter filter : filters) {
184 compositeInputFilter.addFilter(filter);
189 myInputMessageFilter = (text, contentType) -> null;
192 project.getMessageBus().connect(this).subscribe(DumbService.DUMB_MODE, new DumbService.DumbModeListener() {
193 private long myLastStamp;
196 public void enteredDumbMode() {
197 if (myEditor == null) return;
198 myLastStamp = myEditor.getDocument().getModificationStamp();
203 public void exitDumbMode() {
204 ApplicationManager.getApplication().invokeLater(() -> {
205 if (myEditor == null || project.isDisposed() || DumbService.getInstance(project).isDumb()) return;
207 DocumentEx document = myEditor.getDocument();
208 if (myLastStamp != document.getModificationStamp()) {
209 rehighlightHyperlinksAndFoldings();
214 ApplicationManager.getApplication().getMessageBus().connect(this).subscribe(EditorColorsManager.TOPIC, scheme -> {
215 ApplicationManager.getApplication().assertIsDispatchThread();
216 if (isDisposed() || myEditor == null) return;
217 MarkupModel model = DocumentMarkupModel.forDocument(myEditor.getDocument(), project, false);
218 for (RangeHighlighter tokenMarker : model.getAllHighlighters()) {
219 ConsoleViewContentType contentType = tokenMarker.getUserData(CONTENT_TYPE);
220 if (contentType != null && contentType.getAttributesKey() == null && tokenMarker instanceof RangeHighlighterEx) {
221 ((RangeHighlighterEx)tokenMarker).setTextAttributes(contentType.getAttributes());
227 private static synchronized void initTypedHandler() {
228 if (ourTypedHandlerInitialized) return;
229 EditorActionManager.getInstance();
230 TypedAction typedAction = TypedAction.getInstance();
231 typedAction.setupHandler(new MyTypedHandler(typedAction.getHandler()));
232 ourTypedHandlerInitialized = true;
235 public Editor getEditor() {
239 public EditorHyperlinkSupport getHyperlinks() {
243 public void scrollToEnd() {
244 if (myEditor == null) return;
245 EditorUtil.scrollToTheEnd(myEditor, true);
246 myCancelStickToEnd = false;
249 public void foldImmediately() {
250 ApplicationManager.getApplication().assertIsDispatchThread();
251 if (!myFlushAlarm.isEmpty()) {
252 cancelAllFlushRequests();
256 FoldingModel model = myEditor.getFoldingModel();
257 model.runBatchFoldingOperation(() -> {
258 for (FoldRegion region : model.getAllFoldRegions()) {
259 model.removeFoldRegion(region);
263 updateFoldings(0, myEditor.getDocument().getLineCount() - 1);
267 public void attachToProcess(@NotNull ProcessHandler processHandler) {
268 myState = myState.attachTo(this, processHandler);
272 public void clear() {
273 if (myEditor == null) return;
274 synchronized (LOCK) {
275 // real document content will be cleared on next flush;
276 myDeferredBuffer.clear();
278 if (!myFlushAlarm.isDisposed()) {
279 cancelAllFlushRequests();
280 addFlushRequest(0, CLEAR);
286 public void scrollTo(int offset) {
287 if (myEditor == null) return;
288 final class ScrollRunnable extends FlushRunnable {
289 private ScrollRunnable() {
290 super(true); // each request must be executed
294 public void doRun() {
296 if (myEditor == null) return;
297 int moveOffset = Math.min(offset, myEditor.getDocument().getTextLength());
298 if (ConsoleBuffer.useCycleBuffer() && moveOffset >= myEditor.getDocument().getTextLength()) {
301 myEditor.getCaretModel().moveToOffset(moveOffset);
302 myEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
305 addFlushRequest(0, new ScrollRunnable());
309 public void requestScrollingToEnd() {
310 if (myEditor == null) {
314 addFlushRequest(0, new FlushRunnable(true) {
316 public void doRun() {
318 if (myEditor != null && !myFlushAlarm.isDisposed()) {
325 private void addFlushRequest(int millis, @NotNull FlushRunnable flushRunnable) {
326 flushRunnable.queue(millis);
330 public void setOutputPaused(boolean value) {
331 myOutputPaused = value;
333 requestFlushImmediately();
338 public boolean isOutputPaused() {
339 return myOutputPaused;
342 private boolean keepSlashR = true;
343 public void setEmulateCarriageReturn(boolean emulate) {
344 keepSlashR = emulate;
348 public boolean hasDeferredOutput() {
349 synchronized (LOCK) {
350 return myDeferredBuffer.length() > 0;
355 public void performWhenNoDeferredOutput(@NotNull Runnable runnable) {
356 //Q: implement in another way without timer?
357 if (hasDeferredOutput()) {
358 performLaterWhenNoDeferredOutput(runnable);
365 private void performLaterWhenNoDeferredOutput(@NotNull Runnable runnable) {
366 if (mySpareTimeAlarm.isDisposed()) return;
367 if (myJLayeredPane == null) {
370 mySpareTimeAlarm.addRequest(
371 () -> performWhenNoDeferredOutput(runnable),
373 ModalityState.stateForComponent(myJLayeredPane)
379 public JComponent getComponent() {
380 if (myMainPanel == null) {
381 myMainPanel = new JPanel(new BorderLayout());
382 myJLayeredPane = new MyDiffContainer(myMainPanel, createCompositeFilter().getUpdateMessage());
383 Disposer.register(this, myJLayeredPane);
384 add(myJLayeredPane, BorderLayout.CENTER);
387 if (myEditor == null) {
389 requestFlushImmediately();
390 myMainPanel.add(createCenterComponent(), BorderLayout.CENTER);
396 protected CompositeFilter createCompositeFilter() {
397 List<Filter> predefinedFilters = myUsePredefinedMessageFilter ?
398 ConsoleViewUtil.computeConsoleFilters(myProject, this, mySearchScope) :
399 Collections.emptyList();
400 CompositeFilter compositeFilter = new CompositeFilter(myProject, predefinedFilters);
401 compositeFilter.setForceUseAllFilters(true);
402 myCustomFilters.forEach(compositeFilter::addFilter);
403 return compositeFilter;
407 * Adds transparent (actually, non-opaque) component over console.
408 * It will be as big as console. Use it to draw on console because it does not prevent user from console usage.
410 * @param component component to add
412 public final void addLayerToPane(@NotNull JComponent component) {
413 getComponent(); // Make sure component exists
414 component.setOpaque(false);
415 component.setVisible(true);
416 myJLayeredPane.add(component, 0);
419 private void initConsoleEditor() {
420 myEditor = createConsoleEditor();
421 registerConsoleEditorActions();
422 myEditor.getScrollPane().setBorder(IdeBorderFactory.createBorder(SideBorder.LEFT));
423 MouseAdapter mouseListener = new MouseAdapter() {
425 public void mousePressed(MouseEvent e) {
426 updateStickToEndState(true);
430 public void mouseDragged(MouseEvent e) {
431 updateStickToEndState(false);
435 public void mouseWheelMoved(MouseWheelEvent e) {
436 if (e.isShiftDown()) return; // ignore horizontal scrolling
437 updateStickToEndState(false);
440 myEditor.getScrollPane().addMouseWheelListener(mouseListener);
441 myEditor.getScrollPane().getVerticalScrollBar().addMouseListener(mouseListener);
442 myEditor.getScrollPane().getVerticalScrollBar().addMouseMotionListener(mouseListener);
443 myHyperlinks = EditorHyperlinkSupport.get(myEditor);
444 myEditor.getScrollingModel().addVisibleAreaListener(e -> {
445 // There is a possible case that the console text is populated while the console is not shown (e.g. we're debugging and
446 // 'Debugger' tab is active while 'Console' is not). It's also possible that newly added text contains long lines that
447 // are soft wrapped. We want to update viewport position then when the console becomes visible.
448 Rectangle oldR = e.getOldRectangle();
450 if (oldR != null && oldR.height <= 0 &&
451 e.getNewRectangle().height > 0 &&
458 private void updateStickToEndState(boolean useImmediatePosition) {
459 if (myEditor == null) return;
461 JScrollBar scrollBar = myEditor.getScrollPane().getVerticalScrollBar();
462 int scrollBarPosition = useImmediatePosition ? scrollBar.getValue() :
463 myEditor.getScrollingModel().getVisibleAreaOnScrollingFinished().y;
464 boolean vscrollAtBottom = scrollBarPosition == scrollBar.getMaximum() - scrollBar.getVisibleAmount();
465 boolean stickingToEnd = isStickingToEnd();
467 if (!vscrollAtBottom && stickingToEnd) {
468 myCancelStickToEnd = true;
469 } else if (vscrollAtBottom && !stickingToEnd) {
475 protected JComponent createCenterComponent() {
476 return myEditor.getComponent();
480 public void dispose() {
481 myState = myState.dispose();
482 if (myEditor != null) {
483 cancelAllFlushRequests();
484 mySpareTimeAlarm.cancelAllRequests();
486 myEditor.putUserData(CONSOLE_VIEW_IN_EDITOR_VIEW, null);
487 synchronized (LOCK) {
488 myDeferredBuffer.clear();
495 private void cancelAllFlushRequests() {
496 myFlushAlarm.cancelAllRequests();
497 CLEAR.clearRequested();
498 FLUSH.clearRequested();
502 public void waitAllRequests() {
503 ApplicationManager.getApplication().assertIsDispatchThread();
504 Future<?> future = ApplicationManager.getApplication().executeOnPooledThread(() -> {
507 myFlushAlarm.waitForAllExecuted(10, TimeUnit.SECONDS);
508 myFlushUserInputAlarm.waitForAllExecuted(10, TimeUnit.SECONDS);
509 myFlushAlarm.waitForAllExecuted(10, TimeUnit.SECONDS);
510 myFlushUserInputAlarm.waitForAllExecuted(10, TimeUnit.SECONDS);
513 catch (CancellationException e) {
516 catch (InterruptedException | ExecutionException | TimeoutException e) {
517 throw new RuntimeException(e);
524 future.get(10, TimeUnit.MILLISECONDS);
527 catch (TimeoutException ignored) {
529 UIUtil.dispatchAllInvocationEvents();
532 catch (InterruptedException | ExecutionException e) {
533 throw new RuntimeException(e);
537 protected void disposeEditor() {
538 UIUtil.invokeAndWaitIfNeeded((Runnable)() -> {
539 if (!myEditor.isDisposed()) {
540 EditorFactory.getInstance().releaseEditor(myEditor);
546 public void print(@NotNull String text, @NotNull ConsoleViewContentType contentType) {
547 List<Pair<String, ConsoleViewContentType>> result = myInputMessageFilter.applyFilter(text, contentType);
548 if (result == null) {
549 print(text, contentType, null);
552 for (Pair<String, ConsoleViewContentType> pair : result) {
553 if (pair.first != null) {
554 print(pair.first, pair.second == null ? contentType : pair.second, null);
560 protected void print(@NotNull String text, @NotNull ConsoleViewContentType contentType, @Nullable HyperlinkInfo info) {
561 text = StringUtil.convertLineSeparators(text, keepSlashR);
562 synchronized (LOCK) {
563 myDeferredBuffer.print(text, contentType, info);
565 if (contentType == ConsoleViewContentType.USER_INPUT) {
566 requestFlushImmediately();
568 else if (myEditor != null) {
569 boolean shouldFlushNow = myDeferredBuffer.length() >= myDeferredBuffer.getCycleBufferSize();
570 addFlushRequest(shouldFlushNow ? 0 : DEFAULT_FLUSH_DELAY, FLUSH);
575 // send text which was typed in the console to the running process
576 private void sendUserInput(@NotNull CharSequence typedText) {
577 ApplicationManager.getApplication().assertIsDispatchThread();
578 if (myState.isRunning() && NEW_LINE_MATCHER.indexIn(typedText) >= 0) {
579 StringBuilder textToSend = new StringBuilder();
580 // compute text input from the console contents:
581 // all range markers beginning from the caret offset backwards, marked as user input and not marked as already sent
582 for (RangeMarker marker = findTokenMarker(myEditor.getCaretModel().getOffset()-1);
584 marker = ((RangeMarkerImpl)marker).findRangeMarkerBefore()) {
585 ConsoleViewContentType tokenType = getTokenType(marker);
586 if (tokenType != null) {
587 if (tokenType != ConsoleViewContentType.USER_INPUT || marker.getUserData(USER_INPUT_SENT) == Boolean.TRUE) {
590 marker.putUserData(USER_INPUT_SENT, true);
591 textToSend.insert(0, marker.getDocument().getText(TextRange.create(marker)));
594 if (textToSend.length() != 0) {
595 myFlushUserInputAlarm.addRequest(() -> {
596 if (myState.isRunning()) {
598 // this may block forever, see IDEA-54340
599 myState.sendUserInput(textToSend.toString());
601 catch (IOException ignored) {
609 protected ModalityState getStateForUpdate() {
613 private void requestFlushImmediately() {
614 if (myEditor != null) {
615 addFlushRequest(0, FLUSH);
620 * Holds number of symbols managed by the current console.
622 * Total number is assembled as a sum of symbols that are already pushed to the document and number of deferred symbols that
623 * are awaiting to be pushed to the document.
626 public int getContentSize() {
627 synchronized (LOCK) {
628 return (myEditor == null ? 0 : myEditor.getDocument().getTextLength())
629 + myDeferredBuffer.length();
634 public boolean canPause() {
638 public void flushDeferredText() {
639 ApplicationManager.getApplication().assertIsDispatchThread();
640 if (isDisposed()) return;
641 boolean shouldStickToEnd = !myCancelStickToEnd && isStickingToEnd();
642 myCancelStickToEnd = false; // Cancel only needs to last for one update. Next time, isStickingToEnd() will be false.
644 Ref<CharSequence> addedTextRef = Ref.create();
645 List<TokenBuffer.TokenInfo> deferredTokens;
646 Document document = myEditor.getDocument();
648 synchronized (LOCK) {
649 if (myOutputPaused) return;
651 deferredTokens = myDeferredBuffer.drain();
652 if (deferredTokens.isEmpty()) return;
656 RangeMarker lastProcessedOutput = document.createRangeMarker(document.getTextLength(), document.getTextLength());
658 if (!shouldStickToEnd) {
659 myEditor.getScrollingModel().accumulateViewportChanges();
661 Collection<ConsoleViewContentType> contentTypes = new HashSet<>();
662 List<Pair<String, ConsoleViewContentType>> contents = new ArrayList<>();
664 // the text can contain one "\r" at the start meaning we should delete the last line
665 boolean startsWithCR = deferredTokens.get(0) == TokenBuffer.CR_TOKEN;
667 // remove last line if any
668 if (document.getLineCount() != 0) {
669 int lineStartOffset = document.getLineStartOffset(document.getLineCount() - 1);
670 document.deleteString(lineStartOffset, document.getTextLength());
673 int startIndex = startsWithCR ? 1 : 0;
674 List<TokenBuffer.TokenInfo> refinedTokens = new ArrayList<>(deferredTokens.size() - startIndex);
675 int backspacePrefixLength = evaluateBackspacesInTokens(deferredTokens, startIndex, refinedTokens);
676 if (backspacePrefixLength > 0) {
677 int lineCount = document.getLineCount();
678 if (lineCount != 0) {
679 int lineStartOffset = document.getLineStartOffset(lineCount - 1);
680 document.deleteString(Math.max(lineStartOffset, document.getTextLength() - backspacePrefixLength), document.getTextLength());
683 addedTextRef.set(TokenBuffer.getRawText(refinedTokens));
684 document.insertString(document.getTextLength(), addedTextRef.get());
685 // add token information as range markers
686 // start from the end because portion of the text can be stripped from the document beginning because of a cycle buffer
687 int offset = document.getTextLength();
689 for (int i = refinedTokens.size() - 1; i >= 0; i--) {
690 TokenBuffer.TokenInfo token = refinedTokens.get(i);
691 contentTypes.add(token.contentType);
692 contents.add(new Pair<>(token.getText(), token.contentType));
693 tokenLength += token.length();
694 TokenBuffer.TokenInfo prevToken = i == 0 ? null : refinedTokens.get(i - 1);
695 if (prevToken != null && token.contentType == prevToken.contentType && token.getHyperlinkInfo() == prevToken.getHyperlinkInfo()) {
696 // do not create highlighter yet because can merge previous token with the current
699 int start = Math.max(0, offset - tokenLength);
700 if (start == offset) {
703 HyperlinkInfo info = token.getHyperlinkInfo();
705 myHyperlinks.createHyperlink(start, offset, null, info).putUserData(MANUAL_HYPERLINK, true);
707 createTokenRangeHighlighter(token.contentType, start, offset);
713 if (!shouldStickToEnd) {
714 myEditor.getScrollingModel().flushViewportChanges();
717 if (!contentTypes.isEmpty()) {
718 for (ChangeListener each : myListeners) {
719 each.contentAdded(contentTypes);
722 if (!contents.isEmpty()) {
723 for (ChangeListener each : myListeners) {
724 for (int i = contents.size() - 1; i >= 0; i--) {
725 each.textAdded(contents.get(i).first, contents.get(i).second);
729 myPsiDisposedCheck.performCheck();
731 int startLine = lastProcessedOutput.isValid() ? myEditor.getDocument().getLineNumber(lastProcessedOutput.getEndOffset()) : 0;
732 lastProcessedOutput.dispose();
733 highlightHyperlinksAndFoldings(startLine);
735 if (shouldStickToEnd) {
738 sendUserInput(addedTextRef.get());
741 private static int evaluateBackspacesInTokens(@NotNull List<? extends TokenBuffer.TokenInfo> source,
742 int sourceStartIndex,
743 @NotNull List<? super TokenBuffer.TokenInfo> dest) {
744 int backspacesFromNextToken = 0;
745 for (int i = source.size() - 1; i >= sourceStartIndex; i--) {
746 TokenBuffer.TokenInfo token = source.get(i);
747 TokenBuffer.TokenInfo newToken;
748 if (StringUtil.containsChar(token.getText(), BACKSPACE) || backspacesFromNextToken > 0) {
749 StringBuilder tokenTextBuilder = new StringBuilder(token.getText().length() + backspacesFromNextToken);
750 tokenTextBuilder.append(token.getText());
751 for (int j = 0; j < backspacesFromNextToken; j++) {
752 tokenTextBuilder.append(BACKSPACE);
754 normalizeBackspaceCharacters(tokenTextBuilder);
755 backspacesFromNextToken = getBackspacePrefixLength(tokenTextBuilder);
756 String newText = tokenTextBuilder.substring(backspacesFromNextToken);
757 newToken = new TokenBuffer.TokenInfo(token.contentType, newText, token.getHyperlinkInfo());
764 Collections.reverse(dest);
765 return backspacesFromNextToken;
768 private static int getBackspacePrefixLength(@NotNull CharSequence text) {
770 while (prefix < text.length() && text.charAt(prefix) == BACKSPACE) {
776 // convert all "a\bc" sequences to "c", not crossing the line boundaries in the process
777 private static void normalizeBackspaceCharacters(@NotNull StringBuilder text) {
778 int ind = StringUtil.indexOf(text, BACKSPACE);
784 for (int i = 0; i < text.length(); i++) {
785 char ch = text.charAt(i);
787 if (ch == BACKSPACE) {
788 assert guardLength <= newLength;
789 if (guardLength == newLength) {
790 // Backspace is the first char in a new line:
791 // Keep backspace at the first line (guardLength == 0) as it might be in the middle of the actual line,
792 // handle it later (see getBackspacePrefixLength).
793 // Otherwise (for non-first lines), skip backspace as it can't be interpreted if located right after line ending.
794 append = guardLength == 0;
797 append = text.charAt(newLength - 1) == BACKSPACE;
799 newLength--; // interpret \b: delete prev char
807 text.setCharAt(newLength, ch);
809 if (ch == '\r' || ch == '\n') guardLength = newLength;
812 text.setLength(newLength);
815 private void createTokenRangeHighlighter(@NotNull ConsoleViewContentType contentType,
818 ApplicationManager.getApplication().assertIsDispatchThread();
819 MarkupModelEx model = (MarkupModelEx)DocumentMarkupModel.forDocument(myEditor.getDocument(), getProject(), true);
820 int layer = HighlighterLayer.SYNTAX + 1; // make custom filters able to draw their text attributes over the default ones
821 model.addRangeHighlighterAndChangeAttributes(
822 contentType.getAttributesKey(), startOffset, endOffset, layer, HighlighterTargetArea.EXACT_RANGE, false,
824 // fallback for contentTypes which provide only attributes
825 if (ex.getTextAttributesKey() == null) {
826 ex.setTextAttributes(contentType.getAttributes());
828 ex.putUserData(CONTENT_TYPE, contentType);
832 private boolean isDisposed() {
833 return myProject.isDisposed() || myEditor == null;
836 protected void doClear() {
837 ApplicationManager.getApplication().assertIsDispatchThread();
839 if (isDisposed()) return;
841 DocumentEx document = myEditor.getDocument();
842 int documentTextLength = document.getTextLength();
843 if (documentTextLength > 0) {
844 DocumentUtil.executeInBulk(document, true, () -> document.deleteString(0, documentTextLength));
846 synchronized (LOCK) {
847 clearHyperlinkAndFoldings();
849 MarkupModel model = DocumentMarkupModel.forDocument(myEditor.getDocument(), getProject(), true);
850 model.removeAllHighlighters(); // remove all empty highlighters leftovers if any
851 myEditor.getInlayModel().getInlineElementsInRange(0, 0).forEach(Disposer::dispose); // remove inlays if any
854 private boolean isStickingToEnd() {
855 if (myEditor == null) return myLastStickingToEnd;
856 Document document = myEditor.getDocument();
857 int caretOffset = myEditor.getCaretModel().getOffset();
858 myLastStickingToEnd = document.getLineNumber(caretOffset) >= document.getLineCount() - 1;
859 return myLastStickingToEnd;
862 private void clearHyperlinkAndFoldings() {
863 for (RangeHighlighter highlighter : myEditor.getMarkupModel().getAllHighlighters()) {
864 if (highlighter.getUserData(MANUAL_HYPERLINK) == null) {
865 myEditor.getMarkupModel().removeHighlighter(highlighter);
869 myEditor.getFoldingModel().runBatchFoldingOperation(() -> myEditor.getFoldingModel().clearFoldRegions());
874 private void cancelHeavyAlarm() {
875 if (!myHeavyAlarm.isDisposed()) {
876 myHeavyAlarm.cancelAllRequests();
877 ++myHeavyUpdateTicket;
882 public Object getData(@NotNull String dataId) {
883 if (myEditor == null) {
886 if (CommonDataKeys.NAVIGATABLE.is(dataId)) {
887 int offset = myEditor.getCaretModel().getOffset();
888 HyperlinkInfo info = myHyperlinks.getHyperlinkAt(offset);
889 OpenFileDescriptor openFileDescriptor = info instanceof FileHyperlinkInfo ? ((FileHyperlinkInfo)info).getDescriptor() : null;
890 if (openFileDescriptor == null || !openFileDescriptor.getFile().isValid()) {
893 return openFileDescriptor;
896 if (CommonDataKeys.EDITOR.is(dataId)) {
899 if (PlatformDataKeys.HELP_ID.is(dataId)) {
902 if (LangDataKeys.CONSOLE_VIEW.is(dataId)) {
909 public void setHelpId(@NotNull String helpId) {
913 public void setUpdateFoldingsEnabled(boolean updateFoldingsEnabled) {
914 myUpdateFoldingsEnabled = updateFoldingsEnabled;
918 public void addMessageFilter(@NotNull Filter filter) {
919 myCustomFilters.add(filter);
923 public void printHyperlink(@NotNull String hyperlinkText, @Nullable HyperlinkInfo info) {
924 print(hyperlinkText, ConsoleViewContentType.NORMAL_OUTPUT, info);
928 private EditorEx createConsoleEditor() {
929 return ReadAction.compute(() -> {
930 EditorEx editor = doCreateConsoleEditor();
931 LOG.assertTrue(UndoUtil.isUndoDisabledFor(editor.getDocument()));
932 editor.installPopupHandler(new ContextMenuPopupHandler() {
934 public ActionGroup getActionGroup(@NotNull EditorMouseEvent event) {
935 return getPopupGroup(event);
939 int bufferSize = ConsoleBuffer.useCycleBuffer() ? ConsoleBuffer.getCycleBufferSize() : 0;
940 editor.getDocument().setCyclicBufferSize(bufferSize);
942 editor.putUserData(CONSOLE_VIEW_IN_EDITOR_VIEW, this);
944 editor.getSettings().setAllowSingleLogicalLineFolding(true); // We want to fold long soft-wrapped command lines
951 protected EditorEx doCreateConsoleEditor() {
952 return ConsoleViewUtil.setupConsoleEditor(myProject, true, false);
955 private void registerConsoleEditorActions() {
956 Shortcut[] shortcuts = KeymapUtil.getActiveKeymapShortcuts(IdeActions.ACTION_GOTO_DECLARATION).getShortcuts();
957 CustomShortcutSet shortcutSet = new CustomShortcutSet(ArrayUtil.mergeArrays(shortcuts, CommonShortcuts.ENTER.getShortcuts()));
958 new HyperlinkNavigationAction().registerCustomShortcutSet(shortcutSet, myEditor.getContentComponent());
962 new EnterHandler().registerCustomShortcutSet(CommonShortcuts.ENTER, myEditor.getContentComponent());
963 registerActionHandler(myEditor, IdeActions.ACTION_EDITOR_PASTE, new PasteHandler());
964 registerActionHandler(myEditor, IdeActions.ACTION_EDITOR_BACKSPACE, new BackSpaceHandler());
965 registerActionHandler(myEditor, IdeActions.ACTION_EDITOR_DELETE, new DeleteHandler());
966 registerActionHandler(myEditor, IdeActions.ACTION_EDITOR_TAB, new TabHandler());
968 registerActionHandler(myEditor, EOFAction.ACTION_ID);
972 private static void registerActionHandler(@NotNull Editor editor, @NotNull String actionId) {
973 AnAction action = ActionManager.getInstance().getAction(actionId);
974 action.registerCustomShortcutSet(action.getShortcutSet(), editor.getContentComponent());
977 private static void registerActionHandler(@NotNull Editor editor, @NotNull String actionId, @NotNull AnAction action) {
978 Keymap keymap = KeymapManager.getInstance().getActiveKeymap();
979 Shortcut[] shortcuts = keymap.getShortcuts(actionId);
980 action.registerCustomShortcutSet(new CustomShortcutSet(shortcuts), editor.getContentComponent());
984 private ActionGroup getPopupGroup(@NotNull EditorMouseEvent event) {
985 ActionManager actionManager = ActionManager.getInstance();
986 HyperlinkInfo info = myHyperlinks != null ? myHyperlinks.getHyperlinkInfoByEvent(event) : null;
987 ActionGroup group = null;
988 if (info instanceof HyperlinkWithPopupMenuInfo) {
989 group = ((HyperlinkWithPopupMenuInfo)info).getPopupMenuGroup(event.getMouseEvent());
992 group = (ActionGroup)actionManager.getAction(CONSOLE_VIEW_POPUP_MENU);
994 List<ConsoleActionsPostProcessor> postProcessors = ConsoleActionsPostProcessor.EP_NAME.getExtensionList();
995 AnAction[] result = group.getChildren(null);
997 for (ConsoleActionsPostProcessor postProcessor : postProcessors) {
998 result = postProcessor.postProcessPopupActions(this, result);
1000 return new DefaultActionGroup(result);
1003 private void highlightHyperlinksAndFoldings(int startLine) {
1004 ApplicationManager.getApplication().assertIsDispatchThread();
1005 CompositeFilter compositeFilter = createCompositeFilter();
1006 boolean canHighlightHyperlinks = !compositeFilter.isEmpty();
1008 if (!canHighlightHyperlinks && !myUpdateFoldingsEnabled) {
1011 DocumentEx document = myEditor.getDocument();
1012 if (document.getTextLength() == 0) return;
1014 int endLine = Math.max(0, document.getLineCount() - 1);
1016 if (canHighlightHyperlinks) {
1017 myHyperlinks.highlightHyperlinks(compositeFilter, startLine, endLine);
1020 if (myAllowHeavyFilters && compositeFilter.isAnyHeavy() && compositeFilter.shouldRunHeavy()) {
1021 runHeavyFilters(compositeFilter, startLine, endLine);
1023 if (myUpdateFoldingsEnabled) {
1024 updateFoldings(startLine, endLine);
1028 public void rehighlightHyperlinksAndFoldings() {
1029 if (myEditor == null || myProject.isDisposed()) return;
1031 clearHyperlinkAndFoldings();
1032 highlightHyperlinksAndFoldings(0);
1035 private void runHeavyFilters(@NotNull CompositeFilter compositeFilter, int line1, int endLine) {
1036 int startLine = Math.max(0, line1);
1038 Document document = myEditor.getDocument();
1039 int startOffset = document.getLineStartOffset(startLine);
1040 String text = document.getText(new TextRange(startOffset, document.getLineEndOffset(endLine)));
1041 Document documentCopy = new DocumentImpl(text,true);
1042 documentCopy.setReadOnly(true);
1044 myJLayeredPane.startUpdating();
1045 int currentValue = myHeavyUpdateTicket;
1046 myHeavyAlarm.addRequest(() -> {
1047 if (!compositeFilter.shouldRunHeavy()) return;
1049 compositeFilter.applyHeavyFilter(documentCopy, startOffset, startLine, additionalHighlight ->
1050 addFlushRequest(0, new FlushRunnable(true) {
1052 public void doRun() {
1053 if (myHeavyUpdateTicket != currentValue) return;
1054 TextAttributes additionalAttributes = additionalHighlight.getTextAttributes(null);
1055 if (additionalAttributes != null) {
1056 ResultItem item = additionalHighlight.getResultItems().get(0);
1057 myHyperlinks.addHighlighter(item.getHighlightStartOffset(), item.getHighlightEndOffset(), additionalAttributes);
1060 myHyperlinks.highlightHyperlinks(additionalHighlight, 0);
1066 catch (IndexNotReadyException ignore) {
1069 if (myHeavyAlarm.getActiveRequestCount() <= 1) { // only the current request
1070 UIUtil.invokeLaterIfNeeded(() -> myJLayeredPane.finishUpdating());
1076 protected void updateFoldings(int startLine, int endLine) {
1077 ApplicationManager.getApplication().assertIsDispatchThread();
1078 myEditor.getFoldingModel().runBatchFoldingOperation(() -> {
1079 Document document = myEditor.getDocument();
1081 FoldRegion existingRegion = null;
1082 if (startLine > 0) {
1083 int prevLineStart = document.getLineStartOffset(startLine - 1);
1084 FoldRegion[] regions = FoldingUtil.getFoldRegionsAtOffset(myEditor, prevLineStart);
1085 if (regions.length == 1) {
1086 existingRegion = regions[0];
1089 ConsoleFolding lastFolding = findFoldingByRegion(existingRegion);
1090 int lastStartLine = Integer.MAX_VALUE;
1091 if (lastFolding != null) {
1092 int offset = existingRegion.getStartOffset();
1097 lastStartLine = document.getLineNumber(offset);
1098 if (document.getLineStartOffset(lastStartLine) != offset) lastStartLine++;
1102 for (int line = startLine; line <= endLine; line++) {
1104 Grep Console plugin allows to fold empty lines. We need to handle this case in a special way.
1106 Multiple lines are grouped into one folding, but to know when you can create the folding,
1107 you need a line which does not belong to that folding.
1108 When a new line, or a chunk of lines is printed, #addFolding is called for that lines + for an empty string
1109 (which basically does only one thing, gets a folding displayed).
1110 We do not want to process that empty string, but also we do not want to wait for another line
1111 which will create and display the folding - we'd see an unfolded stacktrace until another text came and flushed it.
1112 So therefore the condition, the last line(empty string) should still flush, but not be processed by
1113 com.intellij.execution.ConsoleFolding.
1115 ConsoleFolding next = line < endLine ? foldingForLine(line, document) : null;
1116 if (next != lastFolding) {
1117 if (lastFolding != null) {
1118 boolean isExpanded = false;
1119 if (line > startLine && existingRegion != null && lastStartLine < startLine) {
1120 isExpanded = existingRegion.isExpanded();
1121 myEditor.getFoldingModel().removeFoldRegion(existingRegion);
1123 addFoldRegion(document, lastFolding, lastStartLine, line - 1, isExpanded);
1126 lastStartLine = line;
1127 existingRegion = null;
1133 private static final Key<String> USED_FOLDING_FQN_KEY = Key.create("USED_FOLDING_KEY");
1135 private void addFoldRegion(@NotNull Document document, @NotNull ConsoleFolding folding, int startLine, int endLine, boolean isExpanded) {
1136 List<String> toFold = new ArrayList<>(endLine - startLine + 1);
1137 for (int i = startLine; i <= endLine; i++) {
1138 toFold.add(EditorHyperlinkSupport.getLineText(document, i, false));
1141 int oStart = document.getLineStartOffset(startLine);
1142 if (oStart > 0 && folding.shouldBeAttachedToThePreviousLine()) oStart--;
1143 int oEnd = CharArrayUtil.shiftBackward(document.getImmutableCharSequence(), document.getLineEndOffset(endLine) - 1, " \t") + 1;
1145 String placeholder = folding.getPlaceholderText(getProject(), toFold);
1146 FoldRegion region = placeholder == null ? null : myEditor.getFoldingModel().addFoldRegion(oStart, oEnd, placeholder);
1147 if (region != null) {
1148 region.setExpanded(isExpanded);
1149 region.putUserData(USED_FOLDING_FQN_KEY, getFoldingFqn(folding));
1154 @Contract("null -> null")
1155 private ConsoleFolding findFoldingByRegion(@Nullable FoldRegion region) {
1156 String lastFoldingFqn = USED_FOLDING_FQN_KEY.get(region);
1157 if (lastFoldingFqn == null) return null;
1158 ConsoleFolding consoleFolding = ConsoleFolding.EP_NAME.getByKey(lastFoldingFqn, ConsoleViewImpl.class, ConsoleViewImpl::getFoldingFqn);
1159 return consoleFolding != null && consoleFolding.isEnabledForConsole(this) ? consoleFolding : null;
1163 private static String getFoldingFqn(@NotNull ConsoleFolding consoleFolding) {
1164 return consoleFolding.getClass().getName();
1168 private ConsoleFolding foldingForLine(int line, @NotNull Document document) {
1169 String lineText = EditorHyperlinkSupport.getLineText(document, line, false);
1170 if (line == 0 && myCommandLineFolding.shouldFoldLine(myProject, lineText)) {
1171 return myCommandLineFolding;
1174 for (ConsoleFolding extension : ConsoleFolding.EP_NAME.getExtensions()) {
1175 if (extension.isEnabledForConsole(this) && extension.shouldFoldLine(myProject, lineText)) {
1182 private static class ClearThisConsoleAction extends ClearConsoleAction {
1183 private final ConsoleView myConsoleView;
1185 ClearThisConsoleAction(@NotNull ConsoleView consoleView) {
1186 myConsoleView = consoleView;
1190 public void update(@NotNull AnActionEvent e) {
1191 boolean enabled = myConsoleView.getContentSize() > 0;
1192 e.getPresentation().setEnabled(enabled);
1196 public void actionPerformed(@NotNull AnActionEvent e) {
1197 myConsoleView.clear();
1202 * @deprecated use {@link ClearConsoleAction} instead
1205 @ApiStatus.ScheduledForRemoval(inVersion = "2020.1")
1206 public static class ClearAllAction extends ClearConsoleAction {
1209 // finds range marker the [offset..offset+1) belongs to
1210 private RangeMarker findTokenMarker(int offset) {
1211 RangeMarker[] marker = new RangeMarker[1];
1212 MarkupModelEx model = (MarkupModelEx)DocumentMarkupModel.forDocument(myEditor.getDocument(), getProject(), true);
1213 model.processRangeHighlightersOverlappingWith(offset, offset, m->{
1214 if (getTokenType(m) == null || m.getStartOffset() > offset || offset + 1 > m.getEndOffset()) return true;
1222 private static ConsoleViewContentType getTokenType(@Nullable RangeMarker m) {
1223 return m == null ? null : m.getUserData(CONTENT_TYPE);
1226 private static final class MyTypedHandler extends TypedActionHandlerBase {
1227 private MyTypedHandler(TypedActionHandler originalAction) {
1228 super(originalAction);
1232 public void execute(@NotNull Editor editor, char charTyped, @NotNull DataContext dataContext) {
1233 ConsoleViewImpl consoleView = editor.getUserData(CONSOLE_VIEW_IN_EDITOR_VIEW);
1234 if (consoleView == null || !consoleView.myState.isRunning() || consoleView.myIsViewer) {
1235 if (myOriginalHandler != null) {
1236 myOriginalHandler.execute(editor, charTyped, dataContext);
1240 String text = String.valueOf(charTyped);
1241 consoleView.type(editor, text);
1245 private void type(@NotNull Editor editor, @NotNull String text) {
1246 flushDeferredText();
1247 SelectionModel selectionModel = editor.getSelectionModel();
1249 int lastOffset = selectionModel.hasSelection() ? selectionModel.getSelectionStart() : editor.getCaretModel().getOffset() - 1;
1250 RangeMarker marker = findTokenMarker(lastOffset);
1251 if (getTokenType(marker) != ConsoleViewContentType.USER_INPUT) {
1252 print(text, ConsoleViewContentType.USER_INPUT);
1253 moveScrollRemoveSelection(editor, editor.getDocument().getTextLength());
1257 String textToUse = StringUtil.convertLineSeparators(text);
1259 if (selectionModel.hasSelection()) {
1260 Document document = editor.getDocument();
1261 int start = selectionModel.getSelectionStart();
1262 int end = selectionModel.getSelectionEnd();
1263 document.deleteString(start, end);
1264 selectionModel.removeSelection();
1268 typeOffset = selectionModel.hasSelection() ? selectionModel.getSelectionStart() : editor.getCaretModel().getOffset();
1270 insertUserText(typeOffset, textToUse);
1273 private abstract static class ConsoleAction extends AnAction implements DumbAware {
1275 public void actionPerformed(@NotNull AnActionEvent e) {
1276 ApplicationManager.getApplication().assertIsDispatchThread();
1277 DataContext context = e.getDataContext();
1278 ConsoleViewImpl console = getRunningConsole(context);
1279 if (console != null) {
1280 execute(console, context);
1284 protected abstract void execute(@NotNull ConsoleViewImpl console, @NotNull DataContext context);
1287 public void update(@NotNull AnActionEvent e) {
1288 ConsoleViewImpl console = getRunningConsole(e.getDataContext());
1289 e.getPresentation().setEnabled(console != null);
1293 private static ConsoleViewImpl getRunningConsole(@NotNull DataContext context) {
1294 Editor editor = CommonDataKeys.EDITOR.getData(context);
1295 if (editor != null) {
1296 ConsoleViewImpl console = editor.getUserData(CONSOLE_VIEW_IN_EDITOR_VIEW);
1297 if (console != null && console.myState.isRunning()) {
1305 private static class EnterHandler extends ConsoleAction {
1307 public void execute(@NotNull ConsoleViewImpl consoleView, @NotNull DataContext context) {
1308 consoleView.print("\n", ConsoleViewContentType.USER_INPUT);
1309 consoleView.flushDeferredText();
1310 Editor editor = consoleView.myEditor;
1311 moveScrollRemoveSelection(editor, editor.getDocument().getTextLength());
1315 private static class PasteHandler extends ConsoleAction {
1317 public void execute(@NotNull ConsoleViewImpl consoleView, @NotNull DataContext context) {
1318 String text = CopyPasteManager.getInstance().getContents(DataFlavor.stringFlavor);
1319 if (text == null) return;
1320 Editor editor = consoleView.myEditor;
1321 consoleView.type(editor, text);
1325 private static class BackSpaceHandler extends ConsoleAction {
1327 public void execute(@NotNull ConsoleViewImpl consoleView, @NotNull DataContext context) {
1328 Editor editor = consoleView.myEditor;
1330 if (IncrementalSearchHandler.isHintVisible(editor)) {
1331 getDefaultActionHandler().execute(editor, null, context);
1335 Document document = editor.getDocument();
1336 int length = document.getTextLength();
1341 SelectionModel selectionModel = editor.getSelectionModel();
1342 if (selectionModel.hasSelection()) {
1343 consoleView.deleteUserText(selectionModel.getSelectionStart(),
1344 selectionModel.getSelectionEnd() - selectionModel.getSelectionStart());
1346 else if (editor.getCaretModel().getOffset() > 0) {
1347 consoleView.deleteUserText(editor.getCaretModel().getOffset() - 1, 1);
1351 private static EditorActionHandler getDefaultActionHandler() {
1352 return EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE);
1356 private static class DeleteHandler extends ConsoleAction {
1358 public void execute(@NotNull ConsoleViewImpl consoleView, @NotNull DataContext context) {
1359 Editor editor = consoleView.myEditor;
1361 if (IncrementalSearchHandler.isHintVisible(editor)) {
1362 getDefaultActionHandler().execute(editor, null, context);
1366 consoleView.flushDeferredText();
1367 Document document = editor.getDocument();
1368 int length = document.getTextLength();
1373 SelectionModel selectionModel = editor.getSelectionModel();
1374 if (selectionModel.hasSelection()) {
1375 consoleView.deleteUserText(selectionModel.getSelectionStart(),
1376 selectionModel.getSelectionEnd() - selectionModel.getSelectionStart());
1379 consoleView.deleteUserText(editor.getCaretModel().getOffset(), 1);
1383 private static EditorActionHandler getDefaultActionHandler() {
1384 return EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE);
1388 private static class TabHandler extends ConsoleAction {
1390 protected void execute(@NotNull ConsoleViewImpl console, @NotNull DataContext context) {
1391 console.type(console.myEditor, "\t");
1397 public JComponent getPreferredFocusableComponent() {
1398 //ensure editor created
1400 return myEditor.getContentComponent();
1404 // navigate up/down in stack trace
1406 public boolean hasNextOccurence() {
1407 return calcNextOccurrence(1) != null;
1411 public boolean hasPreviousOccurence() {
1412 return calcNextOccurrence(-1) != null;
1416 public OccurenceInfo goNextOccurence() {
1417 return calcNextOccurrence(1);
1421 protected OccurenceInfo calcNextOccurrence(int delta) {
1422 if (myHyperlinks == null) {
1426 return EditorHyperlinkSupport.getNextOccurrence(myEditor, delta, next -> {
1427 int offset = next.getStartOffset();
1429 HyperlinkInfo hyperlinkInfo = EditorHyperlinkSupport.getHyperlinkInfo(next);
1430 if (hyperlinkInfo instanceof BrowserHyperlinkInfo) {
1433 if (hyperlinkInfo instanceof HyperlinkInfoBase) {
1434 VisualPosition position = myEditor.offsetToVisualPosition(offset);
1435 Point point = myEditor.visualPositionToXY(new VisualPosition(position.getLine() + 1, position.getColumn()));
1436 ((HyperlinkInfoBase)hyperlinkInfo).navigate(myProject, new RelativePoint(myEditor.getContentComponent(), point));
1438 else if (hyperlinkInfo != null) {
1439 hyperlinkInfo.navigate(myProject);
1445 public OccurenceInfo goPreviousOccurence() {
1446 return calcNextOccurrence(-1);
1451 public String getNextOccurenceActionName() {
1452 return ExecutionBundle.message("down.the.stack.trace");
1457 public String getPreviousOccurenceActionName() {
1458 return ExecutionBundle.message("up.the.stack.trace");
1461 public void addCustomConsoleAction(@NotNull AnAction action) {
1462 customActions.add(action);
1466 public AnAction @NotNull [] createConsoleActions() {
1467 //Initializing prev and next occurrences actions
1468 CommonActionsManager actionsManager = CommonActionsManager.getInstance();
1469 AnAction prevAction = actionsManager.createPrevOccurenceAction(this);
1470 prevAction.getTemplatePresentation().setText(getPreviousOccurenceActionName());
1471 AnAction nextAction = actionsManager.createNextOccurenceAction(this);
1472 nextAction.getTemplatePresentation().setText(getNextOccurenceActionName());
1474 AnAction switchSoftWrapsAction = new ToggleUseSoftWrapsToolbarAction(SoftWrapAppliancePlaces.CONSOLE) {
1476 protected Editor getEditor(@NotNull AnActionEvent e) {
1480 AnAction autoScrollToTheEndAction = new ScrollToTheEndToolbarAction(myEditor);
1482 List<AnAction> consoleActions = new ArrayList<>();
1483 consoleActions.add(prevAction);
1484 consoleActions.add(nextAction);
1485 consoleActions.add(switchSoftWrapsAction);
1486 consoleActions.add(autoScrollToTheEndAction);
1487 consoleActions.add(ActionManager.getInstance().getAction("Print"));
1488 consoleActions.add(new ClearThisConsoleAction(this));
1489 consoleActions.addAll(customActions);
1490 List<ConsoleActionsPostProcessor> postProcessors = ConsoleActionsPostProcessor.EP_NAME.getExtensionList();
1491 AnAction[] result = consoleActions.toArray(AnAction.EMPTY_ARRAY);
1492 for (ConsoleActionsPostProcessor postProcessor : postProcessors) {
1493 result = postProcessor.postProcess(this, result);
1499 public void allowHeavyFilters() {
1500 myAllowHeavyFilters = true;
1504 public void addChangeListener(@NotNull ChangeListener listener, @NotNull Disposable parent) {
1505 myListeners.add(listener);
1506 Disposer.register(parent, () -> myListeners.remove(listener));
1509 private void insertUserText(int offset, @NotNull String text) {
1510 List<Pair<String, ConsoleViewContentType>> result = myInputMessageFilter.applyFilter(text, ConsoleViewContentType.USER_INPUT);
1511 if (result == null) {
1512 doInsertUserInput(offset, text);
1515 for (Pair<String, ConsoleViewContentType> pair : result) {
1516 String chunkText = pair.getFirst();
1517 ConsoleViewContentType chunkType = pair.getSecond();
1518 if (chunkType.equals(ConsoleViewContentType.USER_INPUT)) {
1519 doInsertUserInput(offset, chunkText);
1520 offset += chunkText.length();
1523 print(chunkText, chunkType, null);
1529 private void doInsertUserInput(int offset, @NotNull String text) {
1530 ApplicationManager.getApplication().assertIsDispatchThread();
1531 Editor editor = myEditor;
1532 Document document = editor.getDocument();
1534 int oldDocLength = document.getTextLength();
1535 document.insertString(offset, text);
1536 int newStartOffset = Math.max(0,document.getTextLength() - oldDocLength + offset - text.length()); // take care of trim document
1537 int newEndOffset = document.getTextLength() - oldDocLength + offset; // take care of trim document
1539 if (findTokenMarker(newEndOffset) == null) {
1540 createTokenRangeHighlighter(ConsoleViewContentType.USER_INPUT, newStartOffset, newEndOffset);
1543 moveScrollRemoveSelection(editor, newEndOffset);
1544 sendUserInput(text);
1547 private static void moveScrollRemoveSelection(@NotNull Editor editor, int offset) {
1548 editor.getCaretModel().moveToOffset(offset);
1549 editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1550 editor.getSelectionModel().removeSelection();
1553 private void deleteUserText(int startOffset, int length) {
1554 Editor editor = myEditor;
1555 Document document = editor.getDocument();
1557 RangeMarker marker = findTokenMarker(startOffset);
1558 if (getTokenType(marker) != ConsoleViewContentType.USER_INPUT) return;
1560 int endOffset = startOffset + length;
1561 if (startOffset >= 0 && endOffset >= 0 && endOffset > startOffset) {
1562 document.deleteString(startOffset, endOffset);
1564 moveScrollRemoveSelection(editor, startOffset);
1567 public boolean isRunning() {
1568 return myState.isRunning();
1573 ConsoleState getState() {
1578 * Command line used to launch application/test from idea may be quite long.
1579 * Hence, it takes many visual lines during representation if soft wraps are enabled
1580 * or, otherwise, takes many columns and makes horizontal scrollbar thumb too small.
1582 * Our point is to fold such long command line and represent it as a single visual line by default.
1584 private class CommandLineFolding extends ConsoleFolding {
1587 public boolean shouldFoldLine(@NotNull Project project, @NotNull String line) {
1588 return line.length() >= 1000 && myState.isCommandLine(line);
1592 public String getPlaceholderText(@NotNull Project project, @NotNull List<String> lines) {
1593 String text = lines.get(0);
1596 if (text.charAt(0) == '"') {
1597 index = text.indexOf('"', 1) + 1;
1600 for (boolean nonWhiteSpaceFound = false; index < text.length(); index++) {
1601 char c = text.charAt(index);
1602 if (c != ' ' && c != '\t') {
1603 nonWhiteSpaceFound = true;
1606 if (nonWhiteSpaceFound) {
1611 assert index <= text.length();
1612 return text.substring(0, index) + " ...";
1616 private class FlushRunnable implements Runnable {
1617 // Does request of this class was myFlushAlarm.addRequest()-ed but not yet executed
1618 private final AtomicBoolean requested = new AtomicBoolean();
1619 private final boolean adHoc; // true if requests of this class should not be merged (i.e they can be requested multiple times)
1621 private FlushRunnable(boolean adHoc) {
1625 void queue(long delay) {
1626 if (myFlushAlarm.isDisposed()) return;
1627 if (adHoc || requested.compareAndSet(false, true)) {
1628 myFlushAlarm.addRequest(this, delay, getStateForUpdate());
1631 void clearRequested() {
1632 requested.set(false);
1636 public final void run() {
1637 if (isDisposed()) return;
1638 // flush requires UndoManger/CommandProcessor properly initialized
1639 if (!StartupManagerEx.getInstanceEx(myProject).startupActivityPassed()) {
1640 addFlushRequest(DEFAULT_FLUSH_DELAY, FLUSH);
1647 protected void doRun() {
1648 flushDeferredText();
1652 private final FlushRunnable FLUSH = new FlushRunnable(false);
1654 private final class ClearRunnable extends FlushRunnable {
1655 private ClearRunnable() {
1660 public void doRun() {
1664 private final ClearRunnable CLEAR = new ClearRunnable();
1667 public Project getProject() {
1671 private class HyperlinkNavigationAction extends DumbAwareAction {
1673 public void actionPerformed(@NotNull AnActionEvent e) {
1674 Runnable runnable = myHyperlinks.getLinkNavigationRunnable(myEditor.getCaretModel().getLogicalPosition());
1675 assert runnable != null;
1680 public void update(@NotNull AnActionEvent e) {
1681 e.getPresentation().setEnabled(myHyperlinks.getLinkNavigationRunnable(myEditor.getCaretModel().getLogicalPosition()) != null);
1686 public String getText() {
1687 return myEditor.getDocument().getText();