diff --git a/platform/platform-api/src/com/intellij/execution/process/AnsiEscapeDecoder.java b/platform/platform-api/src/com/intellij/execution/process/AnsiEscapeDecoder.java
index 1547d15f0c3f..3f0030cef70e 100644
--- a/platform/platform-api/src/com/intellij/execution/process/AnsiEscapeDecoder.java
+++ b/platform/platform-api/src/com/intellij/execution/process/AnsiEscapeDecoder.java
@@ -24,7 +24,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
-import java.util.regex.Pattern;
/**
* See ANSI escape code.
@@ -34,10 +33,11 @@ import java.util.regex.Pattern;
public class AnsiEscapeDecoder {
private static final char ESC_CHAR = '\u001B'; // Escape sequence start character
private static final String CSI = ESC_CHAR + "["; // "Control Sequence Initiator"
- private static final Pattern INNER_PATTERN = Pattern.compile(Pattern.quote("m" + CSI));
+ private static final String M_CSI = "m" + CSI;
private static final char BACKSPACE = '\b';
private Key myCurrentTextAttributes;
+ private final ColoredOutputTypeRegistry myColoredOutputTypeRegistry = ColoredOutputTypeRegistry.getInstance();
/**
* Parses ansi-color codes from text and sends text fragments with color attributes to textAcceptor
@@ -48,9 +48,9 @@ public class AnsiEscapeDecoder {
* It can implement ColoredChunksAcceptor to receive list of pairs (text, attribute).
*/
public void escapeText(@NotNull String text, @NotNull Key outputType, @NotNull ColoredTextAcceptor textAcceptor) {
- List> chunks = null;
- int pos = 0;
text = normalizeAsciiControlCharacters(text);
+ int pos = 0;
+ List> chunks = null;
while (true) {
int escSeqBeginInd = text.indexOf(CSI, pos);
if (escSeqBeginInd < 0) {
@@ -67,8 +67,8 @@ public class AnsiEscapeDecoder {
String escSeq = text.substring(escSeqBeginInd, escSeqEndInd);
// this is a simple fix for RUBY-8996:
// we replace several consecutive escape sequences with one which contains all these sequences
- String colorAttribute = INNER_PATTERN.matcher(escSeq).replaceAll(";");
- myCurrentTextAttributes = ColoredOutputTypeRegistry.getInstance().getOutputKey(colorAttribute);
+ String colorAttribute = StringUtil.replace(escSeq, M_CSI, ";");
+ myCurrentTextAttributes = myColoredOutputTypeRegistry.getOutputKey(colorAttribute);
}
pos = escSeqEndInd;
}
@@ -189,17 +189,12 @@ public class AnsiEscapeDecoder {
return myCurrentTextAttributes != null ? myCurrentTextAttributes : outputType;
}
- public void coloredTextAvailable(@NotNull List> textChunks, ColoredTextAcceptor textAcceptor) {
- for (Pair textChunk : textChunks) {
- textAcceptor.coloredTextAvailable(textChunk.getFirst(), textChunk.getSecond());
- }
- }
-
public interface ColoredChunksAcceptor extends ColoredTextAcceptor {
- void coloredChunksAvailable(List> chunks);
+ void coloredChunksAvailable(@NotNull List> chunks);
}
+ @FunctionalInterface
public interface ColoredTextAcceptor {
- void coloredTextAvailable(String text, Key attributes);
+ void coloredTextAvailable(@NotNull String text, @NotNull Key attributes);
}
}
diff --git a/platform/platform-tests/testSrc/com/intellij/execution/process/AnsiEscapeDecoderTest.java b/platform/platform-tests/testSrc/com/intellij/execution/process/AnsiEscapeDecoderTest.java
index 99d2ce52c87b..13687c20b01c 100644
--- a/platform/platform-tests/testSrc/com/intellij/execution/process/AnsiEscapeDecoderTest.java
+++ b/platform/platform-tests/testSrc/com/intellij/execution/process/AnsiEscapeDecoderTest.java
@@ -2,14 +2,22 @@ package com.intellij.execution.process;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.io.BufferExposingByteArrayInputStream;
+import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream;
import com.intellij.testFramework.PlatformTestCase;
+import com.intellij.testFramework.PlatformTestUtil;
+import com.intellij.util.Consumer;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.junit.Assert;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
public class AnsiEscapeDecoderTest extends PlatformTestCase {
@@ -79,16 +87,81 @@ public class AnsiEscapeDecoderTest extends PlatformTestCase {
private static AnsiEscapeDecoder.ColoredChunksAcceptor createExpectedAcceptor(@NotNull final Pair... expected) {
return new AnsiEscapeDecoder.ColoredChunksAcceptor() {
@Override
- public void coloredChunksAvailable(List> chunks) {
+ public void coloredChunksAvailable(@NotNull List> chunks) {
List> expectedWithKeyName = toListWithKeyName(Arrays.asList(expected));
List> actualWithKeyName = toListWithKeyName(chunks);
Assert.assertEquals(expectedWithKeyName, actualWithKeyName);
}
@Override
- public void coloredTextAvailable(String text, Key attributes) {
+ public void coloredTextAvailable(@NotNull String text, @NotNull Key attributes) {
throw new RuntimeException(); // shouldn't be called
}
};
}
+
+ public void testPerformance() throws IOException {
+ byte[] buffer = new byte[100000];
+ BufferExposingByteArrayOutputStream outputStream = new BufferExposingByteArrayOutputStream(buffer);
+ BufferExposingByteArrayInputStream inputStream = new BufferExposingByteArrayInputStream(buffer);
+ AtomicBoolean finished = new AtomicBoolean();
+ Process testProcess = new Process() {
+ @Override
+ public OutputStream getOutputStream() {
+ return outputStream;
+ }
+
+ @Override
+ public InputStream getInputStream() {
+ return inputStream;
+ }
+
+ @Override
+ public InputStream getErrorStream() {
+ return inputStream;
+ }
+
+ @Override
+ public int waitFor() {
+ while (!finished.get());
+ return 0;
+ }
+
+ @Override
+ public int exitValue() {
+ return 0;
+ }
+
+ @Override
+ public void destroy() {
+ finished.set(true);
+ }
+ };
+
+ withProcessHandlerFrom(testProcess, handler -> {
+ PlatformTestUtil.startPerformanceTest("ansi color", 15000, ()->{
+ for (int i=0; i<2_000_000;i++) {
+ handler.notifyTextAvailable(i+"Chrome 35.0.1916 (Linux): Executed 0 of 1\u001B[32m SUCCESS\u001B[39m (0 secs / 0 secs)\n", ProcessOutputTypes.STDOUT);
+ handler.notifyTextAvailable(i+"Plain\u001B[32mGreen\u001B[39mNormal\u001B[1A\u001B[2K\u001B[31mRed\u001B[39m\n", ProcessOutputTypes.SYSTEM);
+ }
+ }).assertTiming();
+ });
+ }
+
+ public static void withProcessHandlerFrom(@NotNull Process testProcess, @NotNull Consumer consumer) {
+ KillableColoredProcessHandler handler = new KillableColoredProcessHandler(testProcess, "testProcess");
+ handler.setShouldDestroyProcessRecursively(false);
+ handler.startNotify();
+ handler.notifyTextAvailable("Running stuff...\n", ProcessOutputTypes.STDOUT);
+
+ try {
+ consumer.consume(handler);
+ }
+ finally {
+ handler.doDestroyProcess();
+ handler.notifyProcessTerminated(0);
+ handler.waitFor();
+ }
+
+ }
}
diff --git a/platform/testFramework/src/com/intellij/testFramework/PlatformTestUtil.java b/platform/testFramework/src/com/intellij/testFramework/PlatformTestUtil.java
index 7fcba408fb9f..1887bf1c587e 100644
--- a/platform/testFramework/src/com/intellij/testFramework/PlatformTestUtil.java
+++ b/platform/testFramework/src/com/intellij/testFramework/PlatformTestUtil.java
@@ -601,7 +601,7 @@ public class PlatformTestUtil {
String logMessage = message;
if (duration > expectedOnMyMachine) {
int percentage = (int)(100.0 * (duration - expectedOnMyMachine) / expectedOnMyMachine);
- logMessage += ": " + percentage + "% longer";
+ logMessage += ": " + "\u001B[31;1m " + percentage + "% longer" + "\u001B[0m";
}
logMessage +=
"\n Expected: " + formatTime(expectedOnMyMachine) + "\n Actual: " + formatTime(duration) + "\n " + Timings.getStatistics();
@@ -634,7 +634,9 @@ public class PlatformTestUtil {
System.gc();
String s = "Another epic fail (remaining attempts: " + attempts + "): " + logMessage;
TeamCityLogger.warning(s, null);
- System.err.println(s);
+ if (UsefulTestCase.IS_UNDER_TEAMCITY) {
+ System.err.println(s);
+ }
//if (attempts == 1) {
// try {
// Class.forName("com.intellij.util.ProfilingUtil").getMethod("startCPUProfiling").invoke(null);