diff --git a/platform/lang-impl/src/com/intellij/execution/impl/ConsoleViewImpl.java b/platform/lang-impl/src/com/intellij/execution/impl/ConsoleViewImpl.java index faa9923dabb3..481f9deeff9c 100644 --- a/platform/lang-impl/src/com/intellij/execution/impl/ConsoleViewImpl.java +++ b/platform/lang-impl/src/com/intellij/execution/impl/ConsoleViewImpl.java @@ -68,7 +68,6 @@ import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.tree.IElementType; -import com.intellij.ui.EditorNotificationPanel; import com.intellij.ui.awt.RelativePoint; import com.intellij.util.*; import com.intellij.util.text.CharArrayUtil; @@ -133,14 +132,12 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo private boolean myLastStickingToEnd; private boolean myCancelStickToEnd; - private boolean myTooMuchOfOutput; private boolean myInDocumentUpdate; // If true, then a document is being cleared right now. // Should be accessed in EDT only. @SuppressWarnings("FieldAccessedSynchronizedAndUnsynchronized") private boolean myDocumentClearing; - private int myLastAddedTextLength; private int consoleTooMuchTextBufferRatio; public Editor getEditor() { @@ -773,39 +770,8 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo } } myPsiDisposedCheck.performCheck(); - myLastAddedTextLength = addedText.length(); - if (!myTooMuchOfOutput) { - if (isTheAmountOfTextTooBig(myLastAddedTextLength)) { // disable hyperlinks and folding until new output arriving slows down again - myTooMuchOfOutput = true; - final EditorNotificationPanel comp = - new EditorNotificationPanel().text("Too much output to process").icon(AllIcons.General.ExclMark); - final Alarm tooMuchOutputAlarm = new Alarm(); - //show the notification with a delay to avoid blinking when "too much output" ceases quickly - tooMuchOutputAlarm.addRequest(() -> add(comp, BorderLayout.NORTH), 300); - performWhenNoDeferredOutput(new Runnable() { - @Override - public void run() { - if (!isTheAmountOfTextTooBig(myLastAddedTextLength)) { - try { - highlightHyperlinksAndFoldings(lastProcessedOutput); - } - finally { - myTooMuchOfOutput = false; - remove(comp); - tooMuchOutputAlarm.cancelAllRequests(); - } - } - else { - myLastAddedTextLength = 0; - performLaterWhenNoDeferredOutput(this); - } - } - }); - } - else { - highlightHyperlinksAndFoldings(lastProcessedOutput); - } - } + + highlightHyperlinksAndFoldings(lastProcessedOutput); if (shouldStickToEnd) { scrollToEnd(); @@ -1083,7 +1049,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo additionalAttributes); } else { - myHyperlinks.highlightHyperlinks(additionalHighlight, myFilters); + myHyperlinks.highlightHyperlinks(additionalHighlight, 0); } } diff --git a/platform/platform-impl/src/com/intellij/execution/impl/AsyncFilterRunner.java b/platform/platform-impl/src/com/intellij/execution/impl/AsyncFilterRunner.java new file mode 100644 index 000000000000..4aa2ea1e946d --- /dev/null +++ b/platform/platform-impl/src/com/intellij/execution/impl/AsyncFilterRunner.java @@ -0,0 +1,165 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.execution.impl; + +import com.intellij.execution.filters.Filter; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.RangeMarker; +import com.intellij.openapi.progress.util.ProgressIndicatorUtils; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.NullableComputable; +import com.intellij.openapi.util.Ref; +import com.intellij.util.TimeoutUtil; +import com.intellij.util.concurrency.AppExecutorUtil; +import com.intellij.util.containers.ContainerUtil; +import one.util.streamex.IntStreamEx; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * @author peter + */ +class AsyncFilterRunner { + private static final Logger LOG = Logger.getInstance("#com.intellij.execution.impl.FilterRunner"); + private static final ExecutorService ourExecutor = AppExecutorUtil.createBoundedApplicationPoolExecutor("console filters", 1); + private final EditorHyperlinkSupport myHyperlinks; + private final Editor myEditor; + + AsyncFilterRunner(EditorHyperlinkSupport hyperlinks, Editor editor) { + myHyperlinks = hyperlinks; + myEditor = editor; + } + + void highlightHyperlinks(final Filter customFilter, final int startLine, final int endLine) { + Computable bgComputation = highlightHyperlinksAsync(customFilter, startLine, endLine); + if (ApplicationManager.getApplication().isWriteAccessAllowed()) { + bgComputation.compute().applyHighlights(myHyperlinks); + } else { + runFiltersInBackground(bgComputation); + } + } + + private void runFiltersInBackground(Computable bgComputation) { + AtomicBoolean handled = new AtomicBoolean(); + Future future = ourExecutor.submit(() -> { + FilterResults results = computeWithWritePriority(bgComputation); + if (!results.myResults.isEmpty()) { + ApplicationManager.getApplication().invokeLater(() -> results.applyHighlights(myHyperlinks), ModalityState.any(), o -> handled.get()); + } + return results; + }); + handleSynchronouslyIfQuick(handled, future); + } + + @NotNull + private FilterResults computeWithWritePriority(Computable bgComputation) { + Ref applyResults = Ref.create(FilterResults.EMPTY); + Runnable computeInReadAction = () -> { + if (myEditor.isDisposed()) return; + applyResults.set(bgComputation.compute()); + }; + while (!ProgressIndicatorUtils.runInReadActionWithWriteActionPriority(computeInReadAction)) { + TimeoutUtil.sleep(10); + } + return applyResults.get(); + } + + private void handleSynchronouslyIfQuick(AtomicBoolean handled, Future future) { + try { + future.get(5, TimeUnit.MILLISECONDS).applyHighlights(myHyperlinks); + handled.set(true); + } + catch (TimeoutException ignored) { + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + @NotNull + private Computable highlightHyperlinksAsync(Filter filter, int startLine, int endLine) { + Document document = myEditor.getDocument(); + int markerOffset = document.getLineEndOffset(endLine); + RangeMarker marker = document.createRangeMarker(markerOffset, markerOffset); + List tasks = IntStreamEx.rangeClosed(startLine, endLine).mapToObj(line -> processLine(document, filter, line)).toList(); + return () -> { + List results = new ArrayList<>(); + for (LineHighlighter task : tasks) { + if (!marker.isValid()) return FilterResults.EMPTY; + ContainerUtil.addIfNotNull(results, task.compute()); + } + return new FilterResults(markerOffset, marker, results); + }; + } + + @NotNull + private static LineHighlighter processLine(Document document, Filter filter, int line) { + int lineEnd = document.getLineEndOffset(line); + int endOffset = lineEnd + (lineEnd < document.getTextLength() ? 1 /* for \n */ : 0); + String text = EditorHyperlinkSupport.getLineText(document, line, true); + return () -> checkRange(filter, endOffset, filter.applyFilter(text, endOffset)); + } + + private static Filter.Result checkRange(Filter filter, int endOffset, Filter.Result result) { + if (result != null) { + for (Filter.ResultItem resultItem : result.getResultItems()) { + int start = resultItem.getHighlightStartOffset(); + int end = resultItem.getHighlightEndOffset(); + if (end < start || end > endOffset) { + LOG.error("Filter returned wrong range: start=" + start + "; end=" + end + "; max=" + endOffset + "; filter=" + filter); + } + } + } + return result; + } + + private interface LineHighlighter extends NullableComputable { } + + private static class FilterResults { + static final FilterResults EMPTY = new FilterResults(0, null, Collections.emptyList()); + private int myInitialMarkerOffset; + private RangeMarker myMarker; + private List myResults; + + FilterResults(int initialMarkerOffset, RangeMarker marker, List results) { + myInitialMarkerOffset = initialMarkerOffset; + myMarker = marker; + myResults = results; + } + + void applyHighlights(EditorHyperlinkSupport hyperlinks) { + if (myResults.isEmpty() || !myMarker.isValid()) return; + + int delta = myMarker.getStartOffset() - myInitialMarkerOffset; + for (Filter.Result result : myResults) { + hyperlinks.highlightHyperlinks(result, delta); + } + } + } + +} diff --git a/platform/platform-impl/src/com/intellij/execution/impl/EditorHyperlinkSupport.java b/platform/platform-impl/src/com/intellij/execution/impl/EditorHyperlinkSupport.java index 0099ba9269b9..8180ae13921e 100644 --- a/platform/platform-impl/src/com/intellij/execution/impl/EditorHyperlinkSupport.java +++ b/platform/platform-impl/src/com/intellij/execution/impl/EditorHyperlinkSupport.java @@ -67,10 +67,12 @@ public class EditorHyperlinkSupport { private final Editor myEditor; @NotNull private final Project myProject; + private final AsyncFilterRunner myFilterRunner; public EditorHyperlinkSupport(@NotNull final Editor editor, @NotNull final Project project) { myEditor = editor; myProject = project; + myFilterRunner = new AsyncFilterRunner(this, myEditor); editor.addEditorMouseListener(new EditorMouseAdapter() { @Override @@ -262,30 +264,15 @@ public class EditorHyperlinkSupport { } public void highlightHyperlinks(final Filter customFilter, final int line1, final int endLine) { - final Document document = myEditor.getDocument(); - - final int startLine = Math.max(0, line1); - - for (int line = startLine; line <= endLine; line++) { - int endOffset = document.getLineEndOffset(line); - if (endOffset < document.getTextLength()) { - endOffset++; // add '\n' - } - final String text = getLineText(document, line, true); - Filter.Result result = customFilter.applyFilter(text, endOffset); - if (result != null) { - highlightHyperlinks(result, customFilter); - } - } + myFilterRunner.highlightHyperlinks(customFilter, Math.max(0, line1), endLine); } - void highlightHyperlinks(@NotNull Filter.Result result, @NotNull Filter filter) { + void highlightHyperlinks(@NotNull Filter.Result result, int offsetDelta) { Document document = myEditor.getDocument(); for (Filter.ResultItem resultItem : result.getResultItems()) { - int start = resultItem.getHighlightStartOffset(); - int end = resultItem.getHighlightEndOffset(); - if (end < start || end > document.getTextLength()) { - LOG.error("Filter returned wrong range: start=" + start + "; end=" + end + "; length=" + document.getTextLength() + "; filter=" + filter); + int start = resultItem.getHighlightStartOffset() + offsetDelta; + int end = resultItem.getHighlightEndOffset() + offsetDelta; + if (start < 0 || end < start || end > document.getTextLength()) { continue; } @@ -382,7 +369,7 @@ public class EditorHyperlinkSupport { if (includeEol && endOffset < document.getTextLength()) { endOffset++; } - return document.getCharsSequence().subSequence(document.getLineStartOffset(lineNumber), endOffset).toString(); + return document.getImmutableCharSequence().subSequence(document.getLineStartOffset(lineNumber), endOffset).toString(); } private static class HyperlinkInfoTextAttributes extends TextAttributes {