You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by gn...@apache.org on 2015/02/18 09:44:28 UTC
[3/3] karaf git commit: [KARAF-3537] More and less commands do not
handle ansi escape sequences correctly
[KARAF-3537] More and less commands do not handle ansi escape sequences correctly
Project: http://git-wip-us.apache.org/repos/asf/karaf/repo
Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/715a72a0
Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/715a72a0
Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/715a72a0
Branch: refs/heads/master
Commit: 715a72a0b245d818d1411fc3b992580fcd011798
Parents: 17178f2
Author: Guillaume Nodet <gn...@gmail.com>
Authored: Tue Feb 17 17:51:53 2015 +0100
Committer: Guillaume Nodet <gn...@gmail.com>
Committed: Wed Feb 18 09:39:46 2015 +0100
----------------------------------------------------------------------
.../karaf/shell/commands/impl/LessAction.java | 48 +--
.../karaf/shell/commands/impl/MoreAction.java | 37 +-
.../karaf/shell/support/ansi/AnsiSplitter.java | 378 +++++++++++++++++++
.../shell/support/ansi/AnsiSplitterTest.java | 35 ++
4 files changed, 428 insertions(+), 70 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/karaf/blob/715a72a0/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/LessAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/LessAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/LessAction.java
index 4185c06..b2e9448 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/LessAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/LessAction.java
@@ -48,6 +48,7 @@ import org.apache.karaf.shell.api.console.Session;
import org.apache.karaf.shell.api.console.Signal;
import org.apache.karaf.shell.api.console.SignalListener;
import org.apache.karaf.shell.api.console.Terminal;
+import org.apache.karaf.shell.support.ansi.AnsiSplitter;
import org.jledit.jline.NonBlockingInputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -454,7 +455,7 @@ public class LessAction implements Action, SignalListener {
int off = offsetInLine;
for (int l = 0; l < height - 1; l++) {
String line = getLine(lastLineToDisplay);
- if (line.length() > off + width) {
+ if (ansiLength(line) > off + width) {
off += width;
} else {
off = 0;
@@ -468,7 +469,7 @@ public class LessAction implements Action, SignalListener {
}
String line = getLine(firstLineToDisplay);
- if (line.length() > width + offsetInLine) {
+ if (ansiLength(line) > width + offsetInLine) {
offsetInLine += width;
} else {
offsetInLine = 0;
@@ -485,7 +486,8 @@ public class LessAction implements Action, SignalListener {
} else if (firstLineInMemory < firstLineToDisplay) {
firstLineToDisplay--;
String line = getLine(firstLineToDisplay);
- offsetInLine = line.length() - line.length() % width;
+ int length = ansiLength(line);
+ offsetInLine = length - length % width;
} else {
bof();
return;
@@ -557,7 +559,7 @@ public class LessAction implements Action, SignalListener {
curLine = "";
}
if (compiled != null) {
- curLine = compiled.matcher(curLine).replaceAll("\033[7m$1\033[0m");
+ curLine = compiled.matcher(curLine).replaceAll("\033[7m$1\033[27m");
}
}
String toDisplay;
@@ -605,40 +607,12 @@ public class LessAction implements Action, SignalListener {
return compiled;
}
- private String ansiSubstring(String curLine, int begin, int end) {
- int printPos = 0;
- int csi = 0;
- char sgr = '0';
+ private int ansiLength(String curLine) throws IOException {
+ return AnsiSplitter.length(curLine);
+ }
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < curLine.length() && printPos < end; i++) {
- char c = curLine.charAt(i);
- if (csi == 0 && c == '\033') {
- csi++;
- } else if (csi == 1 && c == '[') {
- csi++;
- } else if (csi == 2) {
- sgr = c;
- csi++;
- } else if (csi == 3 && c == 'm') {
- csi = 0;
- if (printPos >= begin) {
- sb.append("\033[").append(sgr).append("m");
- }
- } else {
- if (printPos == begin && sgr != '0') {
- sb.append("\033[7m");
- }
- if (printPos >= begin && printPos < end) {
- sb.append(c);
- }
- ++printPos;
- }
- }
- if (sgr != '0') {
- sb.append("\033[0m");
- }
- return sb.toString();
+ private String ansiSubstring(String curLine, int begin, int end) throws IOException {
+ return AnsiSplitter.substring(curLine, begin, end);
}
String getLine(int line) throws IOException {
http://git-wip-us.apache.org/repos/asf/karaf/blob/715a72a0/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/MoreAction.java
----------------------------------------------------------------------
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/MoreAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/MoreAction.java
index 5db87f6..0d344a9 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/MoreAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/MoreAction.java
@@ -17,20 +17,18 @@
package org.apache.karaf.shell.commands.impl;
import java.io.BufferedReader;
-import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.Method;
-import java.util.LinkedList;
-import java.util.List;
import org.apache.karaf.shell.api.action.Action;
import org.apache.karaf.shell.api.action.Command;
import org.apache.karaf.shell.api.action.Option;
-import org.apache.karaf.shell.api.console.Session;
-import org.apache.karaf.shell.api.console.Terminal;
import org.apache.karaf.shell.api.action.lifecycle.Reference;
import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.api.console.Terminal;
+import org.apache.karaf.shell.support.ansi.AnsiSplitter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -66,7 +64,7 @@ public class MoreAction implements Action {
if (lines == 0) {
lines = terminal.getHeight();
}
- LineSplitter reader = new LineSplitter(new BufferedReader(new InputStreamReader(System.in)), terminal.getWidth());
+ AnsiSplitter.AnsiBufferedReader reader = AnsiSplitter.splitter(System.in, terminal.getWidth());
int count = 0;
int c;
do {
@@ -120,33 +118,6 @@ public class MoreAction implements Action {
return null;
}
- public static class LineSplitter {
-
- private final BufferedReader reader;
- private final int width;
- private final List<String> lines = new LinkedList<String>();
-
- public LineSplitter(BufferedReader reader, int width) {
- this.reader = reader;
- this.width = width;
- }
-
- public String readLine() throws IOException {
- if (lines.isEmpty()) {
- String str = reader.readLine();
- if (str == null) {
- return null;
- }
- while (str.length() > width) {
- lines.add(str.substring(0, width));
- str = str.substring(width);
- }
- lines.add(str);
- }
- return lines.remove(0);
- }
- }
-
protected boolean isTty(OutputStream out) {
try {
Method mth = out.getClass().getDeclaredMethod("getCurrent");
http://git-wip-us.apache.org/repos/asf/karaf/blob/715a72a0/shell/core/src/main/java/org/apache/karaf/shell/support/ansi/AnsiSplitter.java
----------------------------------------------------------------------
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/support/ansi/AnsiSplitter.java b/shell/core/src/main/java/org/apache/karaf/shell/support/ansi/AnsiSplitter.java
new file mode 100644
index 0000000..f855c1c
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/karaf/shell/support/ansi/AnsiSplitter.java
@@ -0,0 +1,378 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.karaf.shell.support.ansi;
+
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.fusesource.jansi.Ansi;
+import org.fusesource.jansi.AnsiOutputStream;
+
+public class AnsiSplitter {
+
+ public static List<String> splitLines(String text, int maxLength) throws IOException {
+ AnsiOutputStreamSplitter splitter = new AnsiOutputStreamSplitter(maxLength);
+ splitter.write(text.getBytes());
+ splitter.close();
+ return splitter.lines;
+ }
+
+ public static String substring(String text, int begin, int end) throws IOException {
+ AnsiOutputStreamSplitter splitter = new AnsiOutputStreamSplitter(begin, end, Integer.MAX_VALUE);
+ splitter.write(text.getBytes());
+ splitter.close();
+ return splitter.lines.get(0);
+ }
+
+ public static int length(String curLine) throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ AnsiOutputStream aos = new AnsiOutputStream(baos);
+ aos.write(curLine.getBytes());
+ aos.close();
+ return baos.toString().length();
+ }
+
+ public static String cut(String text, int maxLength) throws IOException {
+ return splitLines(text, maxLength).get(0);
+ }
+
+ public static AnsiBufferedReader window(InputStream is, int begin, int end) throws IOException {
+ return new AnsiBufferedReader(is, begin, end, Integer.MAX_VALUE);
+ }
+
+ public static AnsiBufferedReader splitter(InputStream is, int maxLength) throws IOException {
+ return new AnsiBufferedReader(is, 0, Integer.MAX_VALUE, maxLength);
+ }
+
+
+ public static class AnsiBufferedReader implements Closeable {
+
+ private final InputStream in;
+ private final AnsiOutputStreamSplitter splitter;
+
+ public AnsiBufferedReader(InputStream in, int begin, int end, int maxLength) {
+ this.in = in;
+ this.splitter = new AnsiOutputStreamSplitter(begin, end, maxLength);
+ }
+
+ public String readLine() throws IOException {
+ while (splitter.lines.isEmpty()) {
+ int c = in.read();
+ if (c < 0) {
+ splitter.flushLine(false);
+ break;
+ } else {
+ splitter.write(c);
+ }
+ }
+ if (splitter.lines.isEmpty()) {
+ return null;
+ } else {
+ return splitter.lines.remove(0);
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ }
+ }
+
+ static class AnsiOutputStreamSplitter extends AnsiOutputStream {
+
+ protected static final int ATTRIBUTE_NEGATIVE_OFF = 27;
+
+ Ansi.Attribute intensity;
+ Ansi.Attribute underline;
+ Ansi.Attribute blink;
+ Ansi.Attribute negative;
+ Ansi.Color fg;
+ Ansi.Color bg;
+
+ private int begin;
+ private int length;
+ private int maxLength;
+ private int escapeLength;
+ private int windowState;
+ private List<String> lines = new ArrayList<>();
+
+ public AnsiOutputStreamSplitter(int maxLength) {
+ this(0, Integer.MAX_VALUE, maxLength);
+ }
+
+ public AnsiOutputStreamSplitter(int begin, int end, int maxLength) {
+ super(new ByteArrayOutputStream());
+ this.begin = begin;
+ this.length = end - begin;
+ this.maxLength = maxLength - begin;
+ this.windowState = begin > 0 ? 0 : 1;
+ reset();
+ }
+
+ protected void reset() {
+ intensity = Ansi.Attribute.INTENSITY_BOLD_OFF;
+ underline = Ansi.Attribute.UNDERLINE_OFF;
+ blink = Ansi.Attribute.BLINK_OFF;
+ negative = Ansi.Attribute.NEGATIVE_OFF;
+ fg = Ansi.Color.DEFAULT;
+ bg = Ansi.Color.DEFAULT;
+ }
+
+ @Override
+ public void write(int data) throws IOException {
+ if (data == '\n') {
+ flushLine(true);
+ } else {
+ if (windowState != 2) {
+ super.write(data);
+ }
+ ByteArrayOutputStream baos = (ByteArrayOutputStream) out;
+ if (windowState == 0 && baos.size() - escapeLength > begin) {
+ windowState = 1;
+ int nbMissing = baos.size() - escapeLength - begin;
+ byte[] old = baos.toByteArray();
+ beginAttributes();
+ baos.write(old, old.length - nbMissing, nbMissing);
+ } else if (windowState == 1 && baos.size() - escapeLength >= length) {
+ windowState = 2;
+ endAttributes();
+ reset();
+ }
+ if (baos.size() - escapeLength >= maxLength) {
+ flushLine(true);
+ }
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (windowState == 0) {
+ beginAttributes();
+ }
+ flushLine(lines.isEmpty());
+ super.close();
+ }
+
+ private void flushLine(boolean force) throws IOException {
+ ByteArrayOutputStream baos = (ByteArrayOutputStream) out;
+ if (windowState == 0) {
+ beginAttributes();
+ }
+ if (force || baos.size() > escapeLength) {
+ endAttributes();
+ lines.add(new String(baos.toByteArray()));
+ beginAttributes();
+ }
+ windowState = 0;
+ }
+
+ private void endAttributes() throws IOException {
+ if (intensity != Ansi.Attribute.INTENSITY_BOLD_OFF) {
+ setAttribute(Ansi.Attribute.INTENSITY_BOLD_OFF);
+ }
+ if (underline != Ansi.Attribute.UNDERLINE_OFF) {
+ setAttribute(Ansi.Attribute.UNDERLINE_OFF);
+ }
+ if (blink != Ansi.Attribute.BLINK_OFF) {
+ setAttribute(Ansi.Attribute.BLINK_OFF);
+ }
+ if (negative != Ansi.Attribute.NEGATIVE_OFF) {
+ setAttribute(Ansi.Attribute.NEGATIVE_OFF);
+ }
+ if (fg != Ansi.Color.DEFAULT) {
+ setAttributeFg(Ansi.Color.DEFAULT);
+ }
+ if (bg != Ansi.Color.DEFAULT) {
+ setAttributeBg(Ansi.Color.DEFAULT);
+ }
+ }
+
+ private void beginAttributes() throws IOException {
+ ((ByteArrayOutputStream) out).reset();
+ escapeLength = 0;
+ if (intensity != Ansi.Attribute.INTENSITY_BOLD_OFF) {
+ setAttribute(intensity);
+ }
+ if (underline != Ansi.Attribute.UNDERLINE_OFF) {
+ setAttribute(underline);
+ }
+ if (blink != Ansi.Attribute.BLINK_OFF) {
+ setAttribute(blink);
+ }
+ if (negative != Ansi.Attribute.NEGATIVE_OFF) {
+ setAttribute(negative);
+ }
+ if (fg != Ansi.Color.DEFAULT) {
+ setAttributeFg(fg);
+ }
+ if (bg != Ansi.Color.DEFAULT) {
+ setAttributeBg(bg);
+ }
+ }
+
+ @Override
+ protected void processAttributeRest() throws IOException {
+ setAttribute(Ansi.Attribute.RESET);
+ reset();
+ }
+
+ @Override
+ protected void processSetAttribute(int attribute) throws IOException {
+ switch(attribute) {
+ case ATTRIBUTE_INTENSITY_BOLD:
+ setIntensity(Ansi.Attribute.INTENSITY_BOLD);
+ break;
+ case ATTRIBUTE_INTENSITY_FAINT:
+ setIntensity(Ansi.Attribute.INTENSITY_FAINT);
+ break;
+ case ATTRIBUTE_INTENSITY_NORMAL:
+ setIntensity(Ansi.Attribute.INTENSITY_BOLD_OFF);
+ break;
+ case ATTRIBUTE_UNDERLINE:
+ setUnderline(Ansi.Attribute.UNDERLINE);
+ break;
+ case ATTRIBUTE_UNDERLINE_DOUBLE:
+ setUnderline(Ansi.Attribute.UNDERLINE_DOUBLE);
+ break;
+ case ATTRIBUTE_UNDERLINE_OFF:
+ setUnderline(Ansi.Attribute.UNDERLINE_OFF);
+ break;
+ case ATTRIBUTE_BLINK_OFF:
+ setBlink(Ansi.Attribute.BLINK_OFF);
+ break;
+ case ATTRIBUTE_BLINK_SLOW:
+ setBlink(Ansi.Attribute.BLINK_SLOW);
+ break;
+ case ATTRIBUTE_BLINK_FAST:
+ setBlink(Ansi.Attribute.BLINK_FAST);
+ break;
+ case ATTRIBUTE_NEGATIVE_ON:
+ setNegative(Ansi.Attribute.NEGATIVE_ON);
+ break;
+ case ATTRIBUTE_NEGATIVE_OFF:
+ setNegative(Ansi.Attribute.NEGATIVE_OFF);
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ protected void processSetForegroundColor(int color) throws IOException {
+ Ansi.Color c;
+ switch (color) {
+ case 0: c = Ansi.Color.BLACK; break;
+ case 1: c = Ansi.Color.RED; break;
+ case 2: c = Ansi.Color.GREEN; break;
+ case 3: c = Ansi.Color.YELLOW; break;
+ case 4: c = Ansi.Color.BLUE; break;
+ case 5: c = Ansi.Color.MAGENTA; break;
+ case 6: c = Ansi.Color.CYAN; break;
+ case 7: c = Ansi.Color.WHITE; break;
+ case 9: c = Ansi.Color.DEFAULT; break;
+ default: return;
+ }
+ if (this.fg != c) {
+ this.fg = c;
+ setAttributeFg(c);
+ }
+ }
+
+ @Override
+ protected void processSetBackgroundColor(int color) throws IOException {
+ Ansi.Color c;
+ switch (color) {
+ case 0: c = Ansi.Color.BLACK; break;
+ case 1: c = Ansi.Color.RED; break;
+ case 2: c = Ansi.Color.GREEN; break;
+ case 3: c = Ansi.Color.YELLOW; break;
+ case 4: c = Ansi.Color.BLUE; break;
+ case 5: c = Ansi.Color.MAGENTA; break;
+ case 6: c = Ansi.Color.CYAN; break;
+ case 7: c = Ansi.Color.WHITE; break;
+ case 9: c = Ansi.Color.DEFAULT; break;
+ default: return;
+ }
+ if (this.bg != c) {
+ this.bg = c;
+ setAttributeBg(c);
+ }
+ }
+
+ @Override
+ protected void processDefaultTextColor() throws IOException {
+ processSetForegroundColor(9);
+ }
+
+ @Override
+ protected void processDefaultBackgroundColor() throws IOException {
+ processSetBackgroundColor(9);
+ }
+
+ protected void setIntensity(Ansi.Attribute intensity) throws IOException {
+ if (this.intensity != intensity) {
+ this.intensity = intensity;
+ setAttribute(intensity);
+ }
+ }
+
+ protected void setUnderline(Ansi.Attribute underline) throws IOException {
+ if (this.underline != underline) {
+ this.underline = underline;
+ setAttribute(underline);
+ }
+ }
+
+ protected void setBlink(Ansi.Attribute blink) throws IOException {
+ if (this.blink != blink) {
+ this.blink = blink;
+ setAttribute(blink);
+ }
+ }
+
+ protected void setNegative(Ansi.Attribute negative) throws IOException {
+ if (this.negative != negative) {
+ this.negative = negative;
+ setAttribute(negative);
+ }
+ }
+
+ private void setAttributeFg(Ansi.Color color) throws IOException {
+ String sequence = Ansi.ansi().fg(color).toString();
+ escapeLength += sequence.length();
+ out.write(sequence.getBytes());
+ }
+
+ private void setAttributeBg(Ansi.Color color) throws IOException {
+ String sequence = Ansi.ansi().bg(color).toString();
+ escapeLength += sequence.length();
+ out.write(sequence.getBytes());
+ }
+
+ private void setAttribute(Ansi.Attribute attribute) throws IOException {
+ String sequence = Ansi.ansi().a(attribute).toString();
+ escapeLength += sequence.length();
+ out.write(sequence.getBytes());
+ }
+
+ }
+}
http://git-wip-us.apache.org/repos/asf/karaf/blob/715a72a0/shell/core/src/test/java/org/apache/karaf/shell/support/ansi/AnsiSplitterTest.java
----------------------------------------------------------------------
diff --git a/shell/core/src/test/java/org/apache/karaf/shell/support/ansi/AnsiSplitterTest.java b/shell/core/src/test/java/org/apache/karaf/shell/support/ansi/AnsiSplitterTest.java
new file mode 100644
index 0000000..be4148c
--- /dev/null
+++ b/shell/core/src/test/java/org/apache/karaf/shell/support/ansi/AnsiSplitterTest.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.karaf.shell.support.ansi;
+
+import java.io.IOException;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class AnsiSplitterTest {
+
+ @Test
+ public void testWindow() throws IOException {
+ String text = "\u001B[1mThis is bold.\u001B[22m";
+ assertEquals("\u001B[1mis\u001B[22m", AnsiSplitter.substring(text, 5, 7));
+ }
+
+}