summaryrefslogtreecommitdiff
path: root/platform/platform-api/src/com/intellij/execution/process/AnsiEscapeDecoder.java
diff options
context:
space:
mode:
Diffstat (limited to 'platform/platform-api/src/com/intellij/execution/process/AnsiEscapeDecoder.java')
-rw-r--r--platform/platform-api/src/com/intellij/execution/process/AnsiEscapeDecoder.java101
1 files changed, 63 insertions, 38 deletions
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 0ecf7c8a6dd0..58a8e40db2d6 100644
--- a/platform/platform-api/src/com/intellij/execution/process/AnsiEscapeDecoder.java
+++ b/platform/platform-api/src/com/intellij/execution/process/AnsiEscapeDecoder.java
@@ -19,80 +19,105 @@ import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import java.util.List;
+import java.util.regex.Pattern;
/**
+ * See <a href="http://en.wikipedia.org/wiki/ANSI_escape_code">ANSI escape code</a>.
+ *
* @author traff
*/
public class AnsiEscapeDecoder {
- private static final char TEXT_ATTRS_PREFIX_CH = '\u001B';
- private static final String TEXT_ATTRS_PREFIX = Character.toString(TEXT_ATTRS_PREFIX_CH) + "[";
- private static final String TEXT_ATTRS_PATTERN = "m" + TEXT_ATTRS_PREFIX_CH + "\\[";
+ 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 Key myCurrentColor;
+ private Key myCurrentTextAttributes;
/**
* Parses ansi-color codes from text and sends text fragments with color attributes to textAcceptor
*
- * @param text
- * @param outputType
- * @param textAcceptor if implements ColoredTextAcceptor then it will receive text fragments with color attributes
- * if implements ColoredChunksAcceptor then it will receive list of pairs <text, attribute>
+ * @param text a string with ANSI escape sequences
+ * @param outputType stdout/stderr/system (from {@link ProcessOutputTypes})
+ * @param textAcceptor receives text fragments with color attributes.
+ * It can implement ColoredChunksAcceptor to receive list of pairs (text, attribute).
*/
- public void escapeText(String text, Key outputType, ColoredTextAcceptor textAcceptor) {
- final List<Pair<String, Key>> textChunks = ContainerUtil.newArrayList();
+ public void escapeText(@NotNull String text, @NotNull Key outputType, @NotNull ColoredTextAcceptor textAcceptor) {
+ List<Pair<String, Key>> chunks = null;
int pos = 0;
while (true) {
- int macroPos = text.indexOf(TEXT_ATTRS_PREFIX, pos);
- if (macroPos < 0) break;
- if (pos != macroPos) {
- textChunks.add(Pair.create(text.substring(pos, macroPos), getCurrentOutputAttributes(outputType)));
+ int escSeqBeginInd = text.indexOf(CSI, pos);
+ if (escSeqBeginInd < 0) {
+ break;
}
- final int macroEndPos = getEndMacroPos(text, macroPos);
- if (macroEndPos < 0) {
+ if (pos < escSeqBeginInd) {
+ chunks = processTextChunk(chunks, text.substring(pos, escSeqBeginInd), outputType, textAcceptor);
+ }
+ final int escSeqEndInd = findEscSeqEndIndex(text, escSeqBeginInd);
+ if (escSeqEndInd < 0) {
break;
}
+ 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
- final String colorAttribute = text.substring(macroPos, macroEndPos).replaceAll(TEXT_ATTRS_PATTERN, ";");
- myCurrentColor = ColoredOutputTypeRegistry.getInstance().getOutputKey(colorAttribute);
- pos = macroEndPos;
+ String colorAttribute = INNER_PATTERN.matcher(escSeq).replaceAll(";");
+ myCurrentTextAttributes = ColoredOutputTypeRegistry.getInstance().getOutputKey(colorAttribute);
+ pos = escSeqEndInd;
}
if (pos < text.length()) {
- textChunks.add(Pair.create(text.substring(pos), getCurrentOutputAttributes(outputType)));
+ chunks = processTextChunk(chunks, text.substring(pos), outputType, textAcceptor);
}
- if (textAcceptor instanceof ColoredChunksAcceptor) {
- ((ColoredChunksAcceptor)textAcceptor).coloredChunksAvailable(textChunks);
- }
- else {
- coloredTextAvailable(textChunks, textAcceptor);
+ if (chunks != null && textAcceptor instanceof ColoredChunksAcceptor) {
+ ((ColoredChunksAcceptor)textAcceptor).coloredChunksAvailable(chunks);
}
}
- // selects all consecutive escape sequences
- private static int getEndMacroPos(final String text, int macroPos) {
- int endMacroPos = text.indexOf('m', macroPos);
- while (endMacroPos >= 0) {
- endMacroPos += 1;
- macroPos = text.indexOf(TEXT_ATTRS_PREFIX, endMacroPos);
- if (macroPos != endMacroPos) {
+ /*
+ * Selects all consecutive escape sequences and returns escape sequence end index (exclusive).
+ * If the escape sequence isn't finished, returns -1.
+ */
+ private static int findEscSeqEndIndex(@NotNull String text, int escSeqBeginInd) {
+ escSeqBeginInd = text.indexOf('m', escSeqBeginInd);
+ while (escSeqBeginInd >= 0) {
+ escSeqBeginInd++;
+ if (!text.regionMatches(escSeqBeginInd, CSI, 0, CSI.length())) {
break;
}
- endMacroPos = text.indexOf('m', macroPos);
+ escSeqBeginInd = text.indexOf('m', escSeqBeginInd);
+ }
+ return escSeqBeginInd;
+ }
+
+ @Nullable
+ private List<Pair<String, Key>> processTextChunk(@Nullable List<Pair<String, Key>> buffer,
+ @NotNull String text,
+ @NotNull Key outputType,
+ @NotNull ColoredTextAcceptor textAcceptor) {
+ Key attributes = getCurrentOutputAttributes(outputType);
+ if (textAcceptor instanceof ColoredChunksAcceptor) {
+ if (buffer == null) {
+ buffer = ContainerUtil.newArrayListWithCapacity(1);
+ }
+ buffer.add(Pair.create(text, attributes));
+ }
+ else {
+ textAcceptor.coloredTextAvailable(text, attributes);
}
- return endMacroPos;
+ return buffer;
}
- protected Key getCurrentOutputAttributes(final Key outputType) {
+ @NotNull
+ protected Key getCurrentOutputAttributes(@NotNull Key outputType) {
if (outputType == ProcessOutputTypes.STDERR || outputType == ProcessOutputTypes.SYSTEM) {
return outputType;
}
- return myCurrentColor != null ? myCurrentColor : outputType;
+ return myCurrentTextAttributes != null ? myCurrentTextAttributes : outputType;
}
- public void coloredTextAvailable(@NotNull final List<Pair<String, Key>> textChunks, ColoredTextAcceptor textAcceptor) {
- for (final Pair<String, Key> textChunk : textChunks) {
+ public void coloredTextAvailable(@NotNull List<Pair<String, Key>> textChunks, ColoredTextAcceptor textAcceptor) {
+ for (Pair<String, Key> textChunk : textChunks) {
textAcceptor.coloredTextAvailable(textChunk.getFirst(), textChunk.getSecond());
}
}