diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/buildtool/BuildToolConsoleProcessAdapter.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/buildtool/BuildToolConsoleProcessAdapter.java index 786e0ea42308..17c9dd4fde4f 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/buildtool/BuildToolConsoleProcessAdapter.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/buildtool/BuildToolConsoleProcessAdapter.java @@ -7,12 +7,14 @@ import com.intellij.execution.process.ProcessEvent; import com.intellij.openapi.util.Key; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.maven.execution.MavenExternalExecutor; @ApiStatus.Experimental public class BuildToolConsoleProcessAdapter extends ProcessAdapter { private final MavenBuildEventProcessor myEventParser; private final boolean myProcessText; private final AnsiEscapeDecoder myDecoder = new AnsiEscapeDecoder(); + private final MavenExternalExecutor.MavenSpyEventsBuffer myMavenSpyEventsBuffer; /** @@ -22,6 +24,13 @@ public class BuildToolConsoleProcessAdapter extends ProcessAdapter { public BuildToolConsoleProcessAdapter(MavenBuildEventProcessor eventParser, @Deprecated boolean processText) { myEventParser = eventParser; myProcessText = processText; + if (processText) { + myMavenSpyEventsBuffer = new MavenExternalExecutor.MavenSpyEventsBuffer((l, k) -> myDecoder.escapeText(l, k, myEventParser)); + } + else { + myMavenSpyEventsBuffer = null; + } + } @Override @@ -32,7 +41,7 @@ public class BuildToolConsoleProcessAdapter extends ProcessAdapter { @Override public void onTextAvailable(@NotNull ProcessEvent event, @NotNull Key outputType) { if (myProcessText) { - myDecoder.escapeText(event.getText(), outputType, myEventParser); + myMavenSpyEventsBuffer.addText(event.getText(), outputType); } } diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/buildtool/BuildViewMavenConsole.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/buildtool/BuildViewMavenConsole.java index 17407b6dc706..ba2a2bfd7ae9 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/buildtool/BuildViewMavenConsole.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/buildtool/BuildViewMavenConsole.java @@ -134,7 +134,7 @@ public class BuildViewMavenConsole extends MavenConsole { myEventParser.onTextAvailable(text, type == OutputType.ERROR); } - public void onTextAvailable(String text, Key outputType) { + public void sendToEventParser(String text, Key outputType) { myDecoder.escapeText(text, outputType, myEventParser); } diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/execution/MavenExternalExecutor.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/execution/MavenExternalExecutor.java index c8d39a262368..f7940a65a840 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/execution/MavenExternalExecutor.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/execution/MavenExternalExecutor.java @@ -19,6 +19,7 @@ package org.jetbrains.idea.maven.execution; import com.intellij.execution.ExecutionException; +import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.execution.configurations.JavaParameters; import com.intellij.execution.process.AnsiEscapeDecoder; import com.intellij.execution.process.OSProcessHandler; @@ -27,6 +28,7 @@ import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.registry.Registry; +import com.intellij.util.Consumer; import com.intellij.util.io.BaseDataReader; import com.intellij.util.io.BaseOutputReader; import org.jetbrains.annotations.NonNls; @@ -38,6 +40,8 @@ import org.jetbrains.idea.maven.project.MavenConsole; import org.jetbrains.idea.maven.project.MavenGeneralSettings; import org.jetbrains.idea.maven.server.MavenServerConsole; +import java.util.function.BiConsumer; + /** * @deprecated external executor should work through maven run configuration */ @@ -51,7 +55,7 @@ public class MavenExternalExecutor extends MavenExecutor { private JavaParameters myJavaParameters; private ExecutionException myParameterCreationError; - private final AnsiEscapeDecoder myDecoder = new AnsiEscapeDecoder(); + public MavenExternalExecutor(Project project, @@ -79,40 +83,7 @@ public class MavenExternalExecutor extends MavenExecutor { } myProcessHandler = - new OSProcessHandler(myJavaParameters.toCommandLine()) { - @Override - public void notifyTextAvailable(@NotNull String text, @NotNull Key outputType) { - // todo move this logic to ConsoleAdapter class - if (myConsole instanceof BuildViewMavenConsole) { - ((BuildViewMavenConsole)myConsole).onTextAvailable(text, outputType); - } - if (!myConsole.isSuppressed(text) && (!MavenSpyOutputParser.isSpyLog(text) || Registry.is("maven.spy.events.debug"))) { - myDecoder.escapeText(text, outputType, (t, ot) -> super.notifyTextAvailable(t, ot)); - } - updateProgress(indicator, text); - } - - @NotNull - @Override - protected BaseOutputReader.Options readerOptions() { - return new BaseOutputReader.Options() { - @Override - public BaseDataReader.SleepingPolicy policy() { - return BaseDataReader.SleepingPolicy.BLOCKING; - } - - @Override - public boolean splitToLines() { - return true; - } - - @Override - public boolean sendIncompleteLines() { - return false; - } - }; - } - }; + new MyLineSplittingProcessHandler(myJavaParameters.toCommandLine(), myConsole, line -> updateProgress(indicator, line)); myConsole.attachToProcess(myProcessHandler); } @@ -155,4 +126,133 @@ public class MavenExternalExecutor extends MavenExecutor { } } } + + private static class MyLineSplittingProcessHandler extends OSProcessHandler { + private final MavenSpyEventsBuffer mySpyEventsBuffer; + private final MavenSimpleConsoleEventsBuffer mySimpleConsoleEventsBuffer; + private final @NotNull MavenConsole myConsole; + private final AnsiEscapeDecoder myDecoder = new AnsiEscapeDecoder(); + @Nullable private final Consumer myProgressConsumer; + + MyLineSplittingProcessHandler(@NotNull GeneralCommandLine commandLine, + @NotNull MavenConsole console, + @Nullable Consumer progressConsumer + ) throws ExecutionException { + super(commandLine); + myProgressConsumer = progressConsumer; + this.myConsole = console; + + mySpyEventsBuffer = new MavenSpyEventsBuffer((line, key) -> { + sendToMavenEventParser(line, key); + if (myProgressConsumer != null) { + myProgressConsumer.consume(line); + } + }); + mySimpleConsoleEventsBuffer = new MavenSimpleConsoleEventsBuffer((line, key) -> printSimpleOutput(line, key), Registry + .is("maven.spy.events.debug")); + } + + @Override + public void notifyTextAvailable(@NotNull String text, @NotNull Key outputType) { + mySpyEventsBuffer.addText(text, outputType); + mySimpleConsoleEventsBuffer.addText(text, outputType); + } + + + private void sendToMavenEventParser(@NotNull String text, @NotNull Key outputType) { + if (myConsole instanceof BuildViewMavenConsole) { + ((BuildViewMavenConsole)myConsole).sendToEventParser(text, outputType); + } + } + + private void printSimpleOutput(@NotNull String text, @NotNull Key outputType) { + if (!myConsole.isSuppressed(text)) { + myDecoder.escapeText(text, outputType, (t, ot) -> super.notifyTextAvailable(t, ot)); + } + } + + @NotNull + @Override + protected BaseOutputReader.Options readerOptions() { + return new BaseOutputReader.Options() { + @Override + public BaseDataReader.SleepingPolicy policy() { + return BaseDataReader.SleepingPolicy.BLOCKING; + } + + @Override + public boolean splitToLines() { + return true; + } + + @Override + public boolean sendIncompleteLines() { + return true; + } + }; + } + } + + /** + * Expect to receive onle one line or part of it. splitToLines should be enabled + */ + public static class MavenSpyEventsBuffer { + private final StringBuilder myBuffer = new StringBuilder(); + private final BiConsumer myConsumer; + + public MavenSpyEventsBuffer(BiConsumer consumer) {myConsumer = consumer;} + + public void addText(@NotNull String text, @NotNull Key outputType) { + if (text.charAt(text.length() - 1) == '\n') { + String textToSend = myBuffer.length() == 0 ? text : myBuffer.toString() + text; + myConsumer.accept(textToSend, outputType); + myBuffer.setLength(0); + } + else { + myBuffer.append(text); + } + } + } + + public static class MavenSimpleConsoleEventsBuffer { + private final StringBuilder myBuffer = new StringBuilder(); + private final BiConsumer myConsumer; + private final boolean myShowSpyOutput; + private boolean isProcessingSpyNow; + + public MavenSimpleConsoleEventsBuffer(BiConsumer consumer, boolean showSpyOutput) { + myConsumer = consumer; + myShowSpyOutput = showSpyOutput; + } + + public void addText(@NotNull String text, @NotNull Key outputType) { + if (myShowSpyOutput) { + myConsumer.accept(text, outputType); + return; + } + + boolean lastChunk = text.charAt(text.length() - 1) == '\n'; + if (isProcessingSpyNow) { + myBuffer.setLength(0); + + isProcessingSpyNow = !lastChunk; + return; + } + + + String textToSend = myBuffer.length() == 0 ? text : myBuffer.toString() + text; + if (textToSend.length() >= MavenSpyOutputParser.PREFIX.length() || lastChunk) { + myBuffer.setLength(0); + if (!MavenSpyOutputParser.isSpyLog(text)) { + myConsumer.accept(textToSend, outputType); + } + else { + isProcessingSpyNow = !lastChunk; + } + } + else { + myBuffer.append(text); + } + } + } } diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/execution/MavenRunConfiguration.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/execution/MavenRunConfiguration.java index 51b7af8449eb..4c0b9a761ffa 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/execution/MavenRunConfiguration.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/execution/MavenRunConfiguration.java @@ -39,7 +39,6 @@ import org.jetbrains.idea.maven.buildtool.MavenBuildEventProcessor; import org.jetbrains.idea.maven.dom.MavenDomUtil; import org.jetbrains.idea.maven.dom.MavenPropertyResolver; import org.jetbrains.idea.maven.dom.model.MavenDomProjectModel; -import org.jetbrains.idea.maven.externalSystemIntegration.output.parsers.MavenSpyOutputParser; import org.jetbrains.idea.maven.model.MavenConstants; import org.jetbrains.idea.maven.project.MavenGeneralSettings; import org.jetbrains.idea.maven.project.MavenGeneralSettingsEditor; @@ -372,7 +371,7 @@ public class MavenRunConfiguration extends LocatableConfigurationBase implements @Override public boolean sendIncompleteLines() { - return false; + return true; } }; } @@ -453,29 +452,40 @@ public class MavenRunConfiguration extends LocatableConfigurationBase implements } private ProcessListener filtered(ProcessListener listener) { - return new ProcessListener() { - @Override - public void startNotified(@NotNull ProcessEvent event) { - listener.startNotified(event); - } + return new ProcessListenerWithFilteredSpyOutput(listener); + } - @Override - public void processTerminated(@NotNull ProcessEvent event) { - listener.processTerminated(event); - } + private class ProcessListenerWithFilteredSpyOutput implements ProcessListener { + private final ProcessListener myListener; + private final MavenExternalExecutor.MavenSimpleConsoleEventsBuffer mySimpleConsoleEventsBuffer; - @Override - public void processWillTerminate(@NotNull ProcessEvent event, boolean willBeDestroyed) { - listener.processWillTerminate(event, willBeDestroyed); - } + ProcessListenerWithFilteredSpyOutput(ProcessListener listener) { + myListener = listener; + mySimpleConsoleEventsBuffer = new MavenExternalExecutor.MavenSimpleConsoleEventsBuffer( + (l, k) -> myListener.onTextAvailable(new ProcessEvent(MavenHandlerFilterSpyWrapper.this, l), k), + Registry.is("maven.spy.events.debug") + ); + } - @Override - public void onTextAvailable(@NotNull ProcessEvent event, @NotNull Key outputType) { - if (!MavenSpyOutputParser.isSpyLog(event.getText()) || Registry.is("maven.spy.events.debug")) { - listener.onTextAvailable(event, outputType); - } - } - }; + @Override + public void startNotified(@NotNull ProcessEvent event) { + myListener.startNotified(event); + } + + @Override + public void processTerminated(@NotNull ProcessEvent event) { + myListener.processTerminated(event); + } + + @Override + public void processWillTerminate(@NotNull ProcessEvent event, boolean willBeDestroyed) { + myListener.processWillTerminate(event, willBeDestroyed); + } + + @Override + public void onTextAvailable(@NotNull ProcessEvent event, @NotNull Key outputType) { + mySimpleConsoleEventsBuffer.addText(event.getText(), outputType); + } } } } diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/externalSystemIntegration/output/parsers/MavenSpyOutputParser.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/externalSystemIntegration/output/parsers/MavenSpyOutputParser.java index 88c20887a9f3..aa48a7434b0f 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/externalSystemIntegration/output/parsers/MavenSpyOutputParser.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/externalSystemIntegration/output/parsers/MavenSpyOutputParser.java @@ -19,7 +19,7 @@ import java.util.function.Consumer; import java.util.stream.Collectors; public class MavenSpyOutputParser { - private final static String PREFIX = "[IJ]-"; + public final static String PREFIX = "[IJ]-"; private final static String SEPARATOR = "-[IJ]-"; private final static String NEWLINE = "-[N]-"; public static final String DOWNLOAD_DEPENDENCIES_NAME = "dependencies"; diff --git a/plugins/maven/src/test/java/org/jetbrains/idea/maven/execution/MavenSimpleConsoleTest.java b/plugins/maven/src/test/java/org/jetbrains/idea/maven/execution/MavenSimpleConsoleTest.java new file mode 100644 index 000000000000..ce4998581a7a --- /dev/null +++ b/plugins/maven/src/test/java/org/jetbrains/idea/maven/execution/MavenSimpleConsoleTest.java @@ -0,0 +1,137 @@ +// Copyright 2000-2019 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. +package org.jetbrains.idea.maven.execution; + +import com.intellij.openapi.util.Key; +import com.intellij.testFramework.UsefulTestCase; + +public class MavenSimpleConsoleTest extends UsefulTestCase { + + + public void testSmoke() { + doTest(false, new String[]{ + "first\n", + "second\n", + "third\n" + }, + "first\n" + + "second\n" + + "third\n" + ); + } + + public void testIncompleteLine() { + doTest(false, new String[]{ + "fi", + "rst\n", + "second\n", + "third\n" + }, + "first\n" + + "second\n" + + "third\n" + ); + } + + public void testIncompleteLineNewLine() { + doTest(false, new String[]{ + "fi", + "rst\n", + "second\n", + "third", + "\n" + }, + "first\n" + + "second\n" + + "third\n" + ); + } + + public void testFilterSpy() { + doTest(false, new String[]{ + "fi", + "rst\n", + "second\n", + "[IJ]-spy-output\n", + "third", + "\n" + }, + "first\n" + + "second\n" + + "third\n" + ); + } + + public void testShortStrings() { + doTest(false, new String[]{ + "1\n", + "2\n", + "three\n", + "[IJ]-spy-output\n", + "end\n", + "\n" + }, + "1\n2\nthree\nend\n\n" + ); + } + + public void testFilterSpySplittedTimes() { + doTest(false, new String[]{ + "fi", + "rst\n", + "second\n", + "[IJ]-spy-output", + "-and-this-is-still-spy-output", + "-and-this-is-still-spy-output again\n", + "and this is is not", + "\n" + }, + "first\nsecond\nand this is is not\n" + ); + } + + public void testFilterSpySeveralTimes() { + doTest(false, new String[]{ + "fi", + "rst\n", + "second\n", + "[IJ]-spy-output-1\n", + "[IJ]-spy-output-2\n", + "third\n", + "[IJ]-spy-output-3\n", + "end\n" + }, + "first\nsecond\nthird\nend\n" + ); + } + + public void testDoNotFilterSpy() { + doTest(true, new String[]{ + "fi", + "rst\n", + "second\n", + "[IJ]-spy-output-1\n", + "[IJ]-spy-output-2\n", + "third\n", + "[IJ]-spy-output-3\n", + "end\n" + }, + "first\n" + + "second\n" + + "[IJ]-spy-output-1\n" + + "[IJ]-spy-output-2\n" + + "third\n" + + "[IJ]-spy-output-3\n" + + "end\n" + ); + } + + private static void doTest(boolean showSpyOutput, String[] text, String expected) { + StringBuilder actual = new StringBuilder(); + MavenExternalExecutor.MavenSimpleConsoleEventsBuffer buffer = + new MavenExternalExecutor.MavenSimpleConsoleEventsBuffer((l, k) -> actual.append(l), showSpyOutput); + for (String s : text) { + buffer.addText(s, Key.create("test")); + } + assertEquals(expected, actual.toString()); + } +} \ No newline at end of file diff --git a/plugins/maven/src/test/java/org/jetbrains/idea/maven/execution/MavenSpyBufferTest.java b/plugins/maven/src/test/java/org/jetbrains/idea/maven/execution/MavenSpyBufferTest.java new file mode 100644 index 000000000000..8c1e4ec9eea4 --- /dev/null +++ b/plugins/maven/src/test/java/org/jetbrains/idea/maven/execution/MavenSpyBufferTest.java @@ -0,0 +1,75 @@ +// Copyright 2000-2019 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. +package org.jetbrains.idea.maven.execution; + +import com.intellij.openapi.util.Key; +import com.intellij.testFramework.UsefulTestCase; + +import java.util.ArrayList; +import java.util.List; + +public class MavenSpyBufferTest extends UsefulTestCase { + + public void testSmoke() { + doTest(new String[]{ + "first\n", + "second\n", + "third\n" + }, new String[]{ + "first\n", + "second\n", + "third\n" + }); + } + + public void testIncompleteLine() { + doTest(new String[]{ + "fi", + "rst\n", + "second\n", + "third\n" + }, new String[]{ + "first\n", + "second\n", + "third\n" + }); + } + + public void testIncompleteLineNewLine() { + doTest(new String[]{ + "fi", + "rst\n", + "second\n", + "third", + "\n" + }, new String[]{ + "first\n", + "second\n", + "third\n" + }); + } + + public void testIJ() { + doTest(new String[]{ + "[", + "I", + "J", + "]", + "-", + "very", + " long", + " string\n" + }, new String[]{ + "[IJ]-very long string\n" + }); + } + + private static void doTest(String[] text, String[] expected) { + List actual = new ArrayList<>(); + MavenExternalExecutor.MavenSpyEventsBuffer spyEventsBuffer = + new MavenExternalExecutor.MavenSpyEventsBuffer((l, k) -> actual.add(l)); + for (String s : text) { + spyEventsBuffer.addText(s, Key.create("test")); + } + assertOrderedEquals(actual, expected); + } +} \ No newline at end of file