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 fa948f97f41d..8be006a3601f 100644 --- a/platform/platform-api/src/com/intellij/execution/process/AnsiEscapeDecoder.java +++ b/platform/platform-api/src/com/intellij/execution/process/AnsiEscapeDecoder.java @@ -54,44 +54,52 @@ public class AnsiEscapeDecoder { public void escapeText(@NotNull String text, @NotNull Key outputType, @NotNull ColoredTextAcceptor textAcceptor) { text = prependUnhandledText(text, outputType); text = normalizeAsciiControlCharacters(text); - int pos = 0; + int pos = 0, findEscSeqFromIndex = 0; List> chunks = null; int unhandledSuffixLength = 0; while (true) { - int escSeqBeginInd = findEscSeqBeginIndex(text, pos); + int escSeqBeginInd = findEscSeqBeginIndex(text, findEscSeqFromIndex); if (escSeqBeginInd < 0) { if (escSeqBeginInd < -1) { unhandledSuffixLength = decodeUnhandledSuffixLength(escSeqBeginInd); } - if (pos < text.length() - unhandledSuffixLength) { - chunks = processTextChunk(chunks, text.substring(pos, text.length() - unhandledSuffixLength), outputType, textAcceptor); - } break; } - if (pos < escSeqBeginInd) { - chunks = processTextChunk(chunks, text.substring(pos, escSeqBeginInd), outputType, textAcceptor); - } int escSeqEndInd = findConsecutiveEscSequencesEndIndex(text, escSeqBeginInd); if (escSeqEndInd < 0) { - if (escSeqEndInd < -1) { + if (escSeqEndInd == -1) { + // malformed escape sequence => add ESC[ + findEscSeqFromIndex = escSeqBeginInd + 2; + } + else { unhandledSuffixLength = decodeUnhandledSuffixLength(escSeqEndInd); - } - break; - } - if (text.charAt(escSeqEndInd) == 'm') { - String escSeq = text.substring(escSeqBeginInd, escSeqEndInd + 1); - // this is a simple fix for RUBY-8996: - // we replace several consecutive escape sequences with one which contains all these sequences - String colorAttribute = StringUtil.replace(escSeq, M_CSI, ";"); - ProcessOutputType resultType = myColoredOutputTypeRegistry.getOutputType(colorAttribute, outputType); - if (resultType.isStdout()) { - myCurrentStdoutOutputType = resultType; - } - else if (resultType.isStderr()) { - myCurrentStderrOutputType = resultType; + break; } } - pos = escSeqEndInd + 1; + else { + assert escSeqBeginInd <= escSeqEndInd; + if (pos < escSeqBeginInd) { + chunks = processTextChunk(chunks, text.substring(pos, escSeqBeginInd), outputType, textAcceptor); + } + pos = escSeqEndInd + 1; + findEscSeqFromIndex = pos; + if (text.charAt(escSeqEndInd) == 'm') { + String escSeq = text.substring(escSeqBeginInd, escSeqEndInd + 1); + // this is a simple fix for RUBY-8996: + // we replace several consecutive escape sequences with one which contains all these sequences + String colorAttribute = StringUtil.replace(escSeq, M_CSI, ";"); + ProcessOutputType resultType = myColoredOutputTypeRegistry.getOutputType(colorAttribute, outputType); + if (resultType.isStdout()) { + myCurrentStdoutOutputType = resultType; + } + else if (resultType.isStderr()) { + myCurrentStderrOutputType = resultType; + } + } + } + } + if (pos < text.length() - unhandledSuffixLength) { + chunks = processTextChunk(chunks, text.substring(pos, text.length() - unhandledSuffixLength), outputType, textAcceptor); } updateUnhandledSuffix(text, outputType, unhandledSuffixLength); if (chunks != null && textAcceptor instanceof ColoredChunksAcceptor) { @@ -190,7 +198,7 @@ public class AnsiEscapeDecoder { * If the return value is -1, no string suffix should be kept => a malformed escape sequence has been encountered. * If the return value is less than -1, no actual handing of the incomplete escape sequence should be performed, * the string suffix length should be decoded with {@code #decodeUnhandledSuffixLength(the return value)} and the suffix - * should be preserved until the next output chunks is available. + * should be preserved until the next output chunk is available. */ private static int findConsecutiveEscSequencesEndIndex(@NotNull String text, int firstEscSeqBeginInd) { int escSeqBeginInd = firstEscSeqBeginInd; 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 26cbdbe5af0e..8414bf41a37f 100644 --- a/platform/platform-tests/testSrc/com/intellij/execution/process/AnsiEscapeDecoderTest.java +++ b/platform/platform-tests/testSrc/com/intellij/execution/process/AnsiEscapeDecoderTest.java @@ -69,6 +69,32 @@ public class AnsiEscapeDecoderTest extends PlatformTestCase { ); } + public void testMalformedSequence() { + check(false, Collections.singletonList(new ColoredText("\u001B[32mGreen\u001B[\1World\n", ProcessOutputTypes.STDOUT) + .addExpected("Green\u001B[\1World\n", "\u001B[32m") + )); + check(false, Collections.singletonList(new ColoredText("\u001B\n", ProcessOutputTypes.STDOUT) + .addExpected("\u001B\n", ProcessOutputTypes.STDOUT.toString()) + )); + check(false, Collections.singletonList(new ColoredText("\u001B[\n", ProcessOutputTypes.STDOUT) + .addExpected("\u001B[\n", ProcessOutputTypes.STDOUT.toString()) + )); + check(false, ContainerUtil.newArrayList( + new ColoredText("\u001B\nHello,", ProcessOutputTypes.STDOUT) + .addExpected("\u001B\nHello,", ProcessOutputTypes.STDOUT.toString()), + new ColoredText("\u001B[31mWorld", ProcessOutputTypes.STDOUT) + .addExpected("World", "\u001B[31m") + )); + check(false, ContainerUtil.newArrayList( + new ColoredText("\u001BHello,", ProcessOutputTypes.STDOUT) + .addExpected("\u001BHello,", ProcessOutputTypes.STDOUT.toString()) + )); + check(false, ContainerUtil.newArrayList( + new ColoredText("\u001B[Hello,", ProcessOutputTypes.STDOUT) + .addExpected("ello,", ProcessOutputTypes.STDOUT.toString()) + )); + } + public void testIncompleteEscapeSequences() { check(true, ContainerUtil.newArrayList( new ColoredText("\u001B", ProcessOutputTypes.STDOUT),