mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-137769 ANSI colors are not interpreted on System.err
This commit is contained in:
@@ -19,6 +19,7 @@ import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.util.LineSeparator;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -37,9 +38,10 @@ public class AnsiEscapeDecoder {
|
||||
private static final char BACKSPACE = '\b';
|
||||
|
||||
private final ColoredOutputTypeRegistry myColoredOutputTypeRegistry = ColoredOutputTypeRegistry.getInstance();
|
||||
private Key myCurrentTextAttributes;
|
||||
private String myUnhandledStdout;
|
||||
private String myUnhandledStderr;
|
||||
private ProcessOutputType myCurrentStdoutOutputType;
|
||||
private ProcessOutputType myCurrentStderrOutputType;
|
||||
|
||||
/**
|
||||
* Parses ansi-color codes from text and sends text fragments with color attributes to textAcceptor
|
||||
@@ -78,7 +80,13 @@ public class AnsiEscapeDecoder {
|
||||
// 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, ";");
|
||||
myCurrentTextAttributes = myColoredOutputTypeRegistry.getOutputKey(colorAttribute);
|
||||
ProcessOutputType resultType = myColoredOutputTypeRegistry.getOutputType(colorAttribute, outputType);
|
||||
if (resultType.isStdout()) {
|
||||
myCurrentStdoutOutputType = resultType;
|
||||
}
|
||||
else if (resultType.isStderr()) {
|
||||
myCurrentStderrOutputType = resultType;
|
||||
}
|
||||
}
|
||||
pos = escSeqEndInd + 1;
|
||||
}
|
||||
@@ -93,10 +101,10 @@ public class AnsiEscapeDecoder {
|
||||
|
||||
private void updateUnhandledSuffix(@NotNull String text, @NotNull Key outputType, int unhandledSuffixLength) {
|
||||
String unhandledSuffix = unhandledSuffixLength > 0 ? text.substring(text.length() - unhandledSuffixLength) : null;
|
||||
if (outputType == ProcessOutputTypes.STDOUT) {
|
||||
if (ProcessOutputType.isStdout(outputType)) {
|
||||
myUnhandledStdout = unhandledSuffix;
|
||||
}
|
||||
else if (outputType == ProcessOutputTypes.STDERR) {
|
||||
else if (ProcessOutputType.isStderr(outputType)) {
|
||||
myUnhandledStderr = unhandledSuffix;
|
||||
}
|
||||
}
|
||||
@@ -104,11 +112,11 @@ public class AnsiEscapeDecoder {
|
||||
@NotNull
|
||||
private String prependUnhandledText(@NotNull String text, @NotNull Key outputType) {
|
||||
String prevUnhandledText = null;
|
||||
if (outputType == ProcessOutputTypes.STDOUT) {
|
||||
if (ProcessOutputType.isStdout(outputType)) {
|
||||
prevUnhandledText = myUnhandledStdout;
|
||||
myUnhandledStdout = null;
|
||||
}
|
||||
else if (outputType == ProcessOutputTypes.STDERR) {
|
||||
else if (ProcessOutputType.isStderr(outputType)) {
|
||||
prevUnhandledText = myUnhandledStderr;
|
||||
myUnhandledStderr = null;
|
||||
}
|
||||
@@ -263,10 +271,13 @@ public class AnsiEscapeDecoder {
|
||||
|
||||
@NotNull
|
||||
protected Key getCurrentOutputAttributes(@NotNull Key outputType) {
|
||||
if (outputType == ProcessOutputTypes.STDERR || outputType == ProcessOutputTypes.SYSTEM) {
|
||||
return outputType;
|
||||
if (ProcessOutputType.isStdout(outputType)) {
|
||||
return ObjectUtils.notNull(myCurrentStdoutOutputType, outputType);
|
||||
}
|
||||
return myCurrentTextAttributes != null ? myCurrentTextAttributes : outputType;
|
||||
if (ProcessOutputType.isStderr(outputType)) {
|
||||
return ObjectUtils.notNull(myCurrentStderrOutputType, outputType);
|
||||
}
|
||||
return outputType;
|
||||
}
|
||||
|
||||
public interface ColoredChunksAcceptor extends ColoredTextAcceptor {
|
||||
|
||||
+18
-7
@@ -40,7 +40,8 @@ public class ColoredOutputTypeRegistry {
|
||||
return ServiceManager.getService(ColoredOutputTypeRegistry.class);
|
||||
}
|
||||
|
||||
private final Map<String, Key> myRegisteredKeys = ContainerUtil.newConcurrentMap();
|
||||
private final Map<String, ProcessOutputType> myStdoutAttrsToKeyMap = ContainerUtil.newConcurrentMap();
|
||||
private final Map<String, ProcessOutputType> myStderrAttrsToKeyMap = ContainerUtil.newConcurrentMap();
|
||||
|
||||
private static final TextAttributesKey[] myAnsiColorKeys = new TextAttributesKey[]{
|
||||
ConsoleHighlighter.BLACK,
|
||||
@@ -91,10 +92,12 @@ public class ColoredOutputTypeRegistry {
|
||||
|
||||
see full doc at http://en.wikipedia.org/wiki/ANSI_escape_code
|
||||
*/
|
||||
|
||||
@NotNull
|
||||
public Key getOutputKey(@NonNls String attribute) {
|
||||
final Key key = myRegisteredKeys.get(attribute);
|
||||
public ProcessOutputType getOutputType(@NonNls String attribute, @NotNull Key streamType) {
|
||||
ProcessOutputType streamOutputType = streamType instanceof ProcessOutputType ? (ProcessOutputType)streamType
|
||||
: (ProcessOutputType)ProcessOutputTypes.STDOUT;
|
||||
Map<String, ProcessOutputType> attrsToKeyMap = ProcessOutputType.isStdout(streamType) ? myStdoutAttrsToKeyMap : myStderrAttrsToKeyMap;
|
||||
ProcessOutputType key = attrsToKeyMap.get(attribute);
|
||||
if (key != null) {
|
||||
return key;
|
||||
}
|
||||
@@ -107,15 +110,23 @@ public class ColoredOutputTypeRegistry {
|
||||
}
|
||||
attribute = StringUtil.trimEnd(attribute, "m");
|
||||
if (attribute.equals("0")) {
|
||||
return ProcessOutputTypes.STDOUT;
|
||||
return streamOutputType;
|
||||
}
|
||||
Key newKey = new Key(completeAttribute);
|
||||
ProcessOutputType newKey = new ProcessOutputType(completeAttribute, streamOutputType);
|
||||
AnsiConsoleViewContentType contentType = createAnsiConsoleViewContentType(attribute);
|
||||
ConsoleViewContentType.registerNewConsoleViewType(newKey, contentType);
|
||||
myRegisteredKeys.put(completeAttribute, newKey);
|
||||
attrsToKeyMap.put(completeAttribute, newKey);
|
||||
return newKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use {@link #getOutputType(String, Key)} instead
|
||||
*/
|
||||
@NotNull
|
||||
public Key getOutputKey(@NonNls String attribute) {
|
||||
return getOutputType(attribute, ProcessOutputTypes.STDOUT);
|
||||
}
|
||||
|
||||
private static Color getAnsiColor(final int value) {
|
||||
return getColorByKey(getAnsiColorKey(value));
|
||||
}
|
||||
|
||||
+32
-30
@@ -1,5 +1,6 @@
|
||||
package com.intellij.execution.process;
|
||||
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.testFramework.PlatformTestCase;
|
||||
import com.intellij.testFramework.PlatformTestUtil;
|
||||
@@ -23,26 +24,27 @@ public class AnsiEscapeDecoderTest extends PlatformTestCase {
|
||||
private static final String STDERR_KEY = ProcessOutputTypes.STDERR.toString();
|
||||
|
||||
public void testTextWithoutColors() {
|
||||
check(new ColoredText(""));
|
||||
check(new ColoredText("simple text").addExpected("simple text", STDOUT_KEY));
|
||||
check(new ColoredText("", ProcessOutputTypes.STDOUT));
|
||||
check(new ColoredText("simple text", ProcessOutputTypes.STDOUT).addExpected("simple text", STDOUT_KEY));
|
||||
}
|
||||
|
||||
public void testSingleColoredChunk() {
|
||||
check(new ColoredText("Chrome 35.0.1916 (Linux): Executed 0 of 1\u001B[32m SUCCESS\u001B[39m (0 secs / 0 secs)\n")
|
||||
check(new ColoredText("Chrome 35.0.1916 (Linux): Executed 0 of 1\u001B[32m SUCCESS\u001B[39m (0 secs / 0 secs)\n",
|
||||
ProcessOutputTypes.STDOUT)
|
||||
.addExpected("Chrome 35.0.1916 (Linux): Executed 0 of 1", STDOUT_KEY)
|
||||
.addExpected(" SUCCESS", "\u001B[32m")
|
||||
.addExpected(" (0 secs / 0 secs)\n", "\u001B[39m"));
|
||||
}
|
||||
|
||||
public void testCompoundEscSeq() {
|
||||
check(new ColoredText("E\u001B[41m\u001B[37mE\u001B[0mE")
|
||||
check(new ColoredText("E\u001B[41m\u001B[37mE\u001B[0mE", ProcessOutputTypes.STDOUT)
|
||||
.addExpected("E", STDOUT_KEY)
|
||||
.addExpected("E", "\u001B[41;37m")
|
||||
.addExpected("E", STDOUT_KEY));
|
||||
}
|
||||
|
||||
public void testOtherEscSeq() {
|
||||
check(new ColoredText("Plain\u001B[32mGreen\u001B[39mNormal\u001B[1A\u001B[2K\u001B[31mRed\u001B[39m")
|
||||
check(new ColoredText("Plain\u001B[32mGreen\u001B[39mNormal\u001B[1A\u001B[2K\u001B[31mRed\u001B[39m", ProcessOutputTypes.STDOUT)
|
||||
.addExpected("Plain", STDOUT_KEY)
|
||||
.addExpected("Green", "\u001B[32m")
|
||||
.addExpected("Normal", "\u001B[39m")
|
||||
@@ -51,27 +53,29 @@ public class AnsiEscapeDecoderTest extends PlatformTestCase {
|
||||
|
||||
public void testBackspaceControlSequence() {
|
||||
check(false, ContainerUtil.newArrayList(
|
||||
new ColoredText(" 10% 0/1 build modules\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b 70% 1/1 build modules")
|
||||
new ColoredText(" 10% 0/1 build modules\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b 70% 1/1 build modules", ProcessOutputTypes.STDOUT)
|
||||
.addExpected(" 70% 1/1 build modules", STDOUT_KEY),
|
||||
new ColoredText(
|
||||
"\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b 40% 1/2 build modules\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b 30% 1/3 build modules\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b 25% 1/4 build modules")
|
||||
"\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b 40% 1/2 build modules\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b 30% 1/3 build modules\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b 25% 1/4 build modules",
|
||||
ProcessOutputTypes.STDOUT)
|
||||
.addExpected("\n 25% 1/4 build modules", STDOUT_KEY)
|
||||
));
|
||||
}
|
||||
|
||||
public void testIncompleteEscapeSequences() {
|
||||
check(true, ContainerUtil.newArrayList(
|
||||
new ColoredText("\u001B"),
|
||||
new ColoredText("[33m Hello\u001B[3").addExpected(" Hello", "\u001B[33m"),
|
||||
new ColoredText("4m, Work!").addExpected(", Work!", "\u001B[34m")
|
||||
new ColoredText("\u001B", ProcessOutputTypes.STDOUT),
|
||||
new ColoredText("[33m Hello\u001B[3", ProcessOutputTypes.STDOUT).addExpected(" Hello", "\u001B[33m"),
|
||||
new ColoredText("4m, Work!", ProcessOutputTypes.STDOUT).addExpected(", Work!", "\u001B[34m")
|
||||
));
|
||||
|
||||
check(true, ContainerUtil.newArrayList(
|
||||
new ColoredText("\u001B[1;33m<\u001B[34mnamespace\u001B[1")
|
||||
new ColoredText("\u001B[1;33m<\u001B[34mnamespace\u001B[1", ProcessOutputTypes.STDOUT)
|
||||
.addExpected("<", "\u001B[1;33m")
|
||||
.addExpected("namespace", "\u001B[34m"),
|
||||
new ColoredText(
|
||||
";33m:abcd\u001B[0m\u001B[1;33m>\u001B[0m0\u001B[1;33m</\u001B[34mnamespace\u001B[1;33m:abcd\u001B[0m\u001B[1;33m>\u001B[0m")
|
||||
";33m:abcd\u001B[0m\u001B[1;33m>\u001B[0m0\u001B[1;33m</\u001B[34mnamespace\u001B[1;33m:abcd\u001B[0m\u001B[1;33m>\u001B[0m",
|
||||
ProcessOutputTypes.STDOUT)
|
||||
.addExpected(":abcd", "\u001B[1;33m")
|
||||
.addExpected(">", "\u001B[0;1;33m")
|
||||
.addExpected("0", "stdout")
|
||||
@@ -82,6 +86,17 @@ public class AnsiEscapeDecoderTest extends PlatformTestCase {
|
||||
));
|
||||
}
|
||||
|
||||
public void testStderr() {
|
||||
check(true, ContainerUtil.newArrayList(
|
||||
new ColoredText("\u001B[33m Hello,", ProcessOutputTypes.STDOUT).addExpected(" Hello,", "\u001B[33m"),
|
||||
new ColoredText("World!\n", ProcessOutputTypes.STDERR).addExpected("World!\n", STDERR_KEY),
|
||||
new ColoredText("\u001B[41m Changed stderr background", ProcessOutputTypes.STDERR)
|
||||
.addExpected(" Changed stderr background", "\u001B[41m"),
|
||||
new ColoredText("Unchanged stdout background", ProcessOutputTypes.STDOUT)
|
||||
.addExpected("Unchanged stdout background", "\u001B[33m")
|
||||
));
|
||||
}
|
||||
|
||||
private static void check(@NotNull ColoredText text) {
|
||||
check(true, Collections.singletonList(text));
|
||||
}
|
||||
@@ -93,9 +108,8 @@ public class AnsiEscapeDecoderTest extends PlatformTestCase {
|
||||
AnsiEscapeDecoder.ColoredTextAcceptor acceptor = (text, attributes) -> {
|
||||
actualColoredChunks.add(Pair.create(text, attributes.toString()));
|
||||
};
|
||||
// test stdout
|
||||
for (ColoredText text : texts) {
|
||||
decoder.escapeText(text.myRawText, ProcessOutputTypes.STDOUT, acceptor);
|
||||
decoder.escapeText(text.myRawText, text.myOutputType, acceptor);
|
||||
}
|
||||
List<Pair<String, String>> expectedColoredChunks = new ArrayList<>();
|
||||
for (ColoredText text : texts) {
|
||||
@@ -109,7 +123,7 @@ public class AnsiEscapeDecoderTest extends PlatformTestCase {
|
||||
decoder = new AnsiEscapeDecoder();
|
||||
for (ColoredText text : texts) {
|
||||
for (int i = 0; i < text.myRawText.length(); i++) {
|
||||
decoder.escapeText(String.valueOf(text.myRawText.charAt(i)), ProcessOutputTypes.STDOUT, acceptor);
|
||||
decoder.escapeText(String.valueOf(text.myRawText.charAt(i)), text.myOutputType, acceptor);
|
||||
}
|
||||
}
|
||||
expectedColoredChunks.clear();
|
||||
@@ -123,20 +137,6 @@ public class AnsiEscapeDecoderTest extends PlatformTestCase {
|
||||
}
|
||||
Assert.assertEquals(expectedColoredChunks, actualColoredChunks);
|
||||
}
|
||||
|
||||
// test stderr
|
||||
actualColoredChunks.clear();
|
||||
decoder = new AnsiEscapeDecoder();
|
||||
for (ColoredText text : texts) {
|
||||
decoder.escapeText(text.myRawText, ProcessOutputTypes.STDERR, acceptor);
|
||||
}
|
||||
expectedColoredChunks.clear();
|
||||
for (ColoredText text : texts) {
|
||||
for (Pair<String, String> chunk : text.myExpectedColoredChunks) {
|
||||
expectedColoredChunks.add(Pair.create(chunk.first, STDERR_KEY));
|
||||
}
|
||||
}
|
||||
Assert.assertEquals(expectedColoredChunks, actualColoredChunks);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -213,9 +213,11 @@ public class AnsiEscapeDecoderTest extends PlatformTestCase {
|
||||
private static class ColoredText {
|
||||
private final String myRawText;
|
||||
private final List<Pair<String, String>> myExpectedColoredChunks = new ArrayList<>();
|
||||
private final Key myOutputType;
|
||||
|
||||
public ColoredText(@NotNull String rawText) {
|
||||
public ColoredText(@NotNull String rawText, @NotNull Key outputType) {
|
||||
myRawText = rawText;
|
||||
myOutputType = outputType;
|
||||
}
|
||||
|
||||
private ColoredText addExpected(@NotNull String text, @NotNull String colorKey) {
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.process;
|
||||
|
||||
import com.intellij.openapi.util.Key;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a stream (stdout/stderr) output type. It can be a base output type or colored output type.
|
||||
* Base stdout/stderr output types is constants: {@link ProcessOutputTypes#STDOUT} and
|
||||
* {@link ProcessOutputTypes#STDERR}.<br/>
|
||||
* A colored stdout/stderr output type is created one per each unique color attributes info decoded as
|
||||
* {@code name} parameter, and base stream output type (stdout/stderr) - {@code streamType} parameter.
|
||||
* <p/>
|
||||
* Use {@link com.intellij.execution.ui.ConsoleViewContentType#getConsoleViewType} to get TextAttributes for an instance
|
||||
* of this class.
|
||||
* <p/>
|
||||
* @see {@link com.intellij.execution.process.ColoredOutputTypeRegistry}
|
||||
* @see {@link com.intellij.execution.ui.ConsoleViewContentType#registerNewConsoleViewType}
|
||||
*/
|
||||
@SuppressWarnings({"JavaDoc", "JavadocReference"})
|
||||
public class ProcessOutputType extends Key {
|
||||
private final ProcessOutputType myStreamType;
|
||||
|
||||
public ProcessOutputType(@NotNull String name, @NotNull ProcessOutputType streamType) {
|
||||
super(name);
|
||||
myStreamType = streamType;
|
||||
}
|
||||
|
||||
ProcessOutputType(@NotNull String name) {
|
||||
super(name);
|
||||
myStreamType = null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private ProcessOutputType getStreamType() {
|
||||
return myStreamType != null ? myStreamType : this;
|
||||
}
|
||||
|
||||
public boolean isStdout() {
|
||||
return getStreamType() == ProcessOutputTypes.STDOUT;
|
||||
}
|
||||
|
||||
public boolean isStderr() {
|
||||
return getStreamType() == ProcessOutputTypes.STDERR;
|
||||
}
|
||||
|
||||
public static boolean isStderr(@NotNull Key key) {
|
||||
if (key instanceof ProcessOutputType) {
|
||||
return ((ProcessOutputType)key).isStderr();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isStdout(@NotNull Key key) {
|
||||
if (key instanceof ProcessOutputType) {
|
||||
return ((ProcessOutputType)key).isStdout();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static ProcessOutputType tryCast(@NotNull Key key) {
|
||||
return key instanceof ProcessOutputType ? (ProcessOutputType)key : null;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2009 JetBrains s.r.o.
|
||||
* Copyright 2000-2017 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.
|
||||
@@ -18,7 +18,23 @@ package com.intellij.execution.process;
|
||||
import com.intellij.openapi.util.Key;
|
||||
|
||||
public interface ProcessOutputTypes {
|
||||
Key SYSTEM = new Key("system");
|
||||
Key STDOUT = new Key("stdout");
|
||||
Key STDERR = new Key("stderr");
|
||||
Key SYSTEM = new ProcessOutputType("system");
|
||||
|
||||
/**
|
||||
* Represents process standard output stream.<p>
|
||||
* Please note that stdout colored output type doesn't equal to this instance: use
|
||||
* <pre>{@code ProcessOutputType.isStdout(key)}</pre>
|
||||
* instead of
|
||||
* <pre>{@code ProcessOutputTypes.STDOUT.equals(key)} or ProcessOutputTypes.STDOUT == key</pre>
|
||||
*/
|
||||
Key STDOUT = new ProcessOutputType("stdout");
|
||||
|
||||
/**
|
||||
* Represents process standard error stream.<p>
|
||||
* Please note that stderr colored output type doesn't equal to this instance: use
|
||||
* <pre>{@code ProcessOutputType.isStderr(key)}</pre>
|
||||
* instead of
|
||||
* <pre>{@code ProcessOutputTypes.STDERR.equals(key) or ProcessOutputTypes.STDERR == key}</pre>
|
||||
*/
|
||||
Key STDERR = new ProcessOutputType("stderr");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user