diff --git a/reader/src/test/java/org/jline/terminal/impl/ExternalTerminalTest.java b/reader/src/test/java/org/jline/terminal/impl/ExternalTerminalTest.java index 76a069a24..6f5854699 100644 --- a/reader/src/test/java/org/jline/terminal/impl/ExternalTerminalTest.java +++ b/reader/src/test/java/org/jline/terminal/impl/ExternalTerminalTest.java @@ -8,11 +8,13 @@ */ package org.jline.terminal.impl; +import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.nio.charset.StandardCharsets; @@ -51,6 +53,43 @@ public void tearDown() { System.clearProperty(TerminalBuilder.PROP_PROVIDERS); } + @Test + void testEOL() throws IOException { + { + PipedInputStream in = new PipedInputStream(); + PipedOutputStream outIn = new PipedOutputStream(in); + BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); + outIn.write("abc\rdef\nghi\r\njkl\r".getBytes()); + + assertEquals("abc", reader.readLine()); + assertEquals("def", reader.readLine()); + assertEquals("ghi", reader.readLine()); + assertEquals("jkl", reader.readLine()); + } + { + PipedInputStream in = new PipedInputStream(); + PipedOutputStream outIn = new PipedOutputStream(in); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + outIn.write("abc\rdef\nghi\r\njkl\n".getBytes()); + + Terminal terminal = TerminalBuilder.builder() + .type("ansi") + .streams(in, out) + .paused(true) + .build(); + LineReader reader = LineReaderBuilder.builder().terminal(terminal).build(); + Attributes attributes = terminal.getAttributes(); + attributes.setInputFlag(InputFlag.INORMEOL, true); + terminal.setAttributes(attributes); + terminal.resume(); + + assertEquals("abc", reader.readLine()); + assertEquals("def", reader.readLine()); + assertEquals("ghi", reader.readLine()); + assertEquals("jkl", reader.readLine()); + } + } + @Test public void testInput() throws IOException, InterruptedException { PipedInputStream in = new PipedInputStream(); diff --git a/terminal/src/main/java/org/jline/terminal/Attributes.java b/terminal/src/main/java/org/jline/terminal/Attributes.java index a33977fa4..03e8ca929 100644 --- a/terminal/src/main/java/org/jline/terminal/Attributes.java +++ b/terminal/src/main/java/org/jline/terminal/Attributes.java @@ -56,7 +56,9 @@ public enum InputFlag { IXOFF, /* enable input flow control */ IXANY, /* any char will restart after stop */ IMAXBEL, /* ring bell on input queue full */ - IUTF8 /* maintain state for UTF-8 VERASE */ + IUTF8, /* maintain state for UTF-8 VERASE */ + + INORMEOL /* normalize end-of-line */ } /* diff --git a/terminal/src/main/java/org/jline/terminal/impl/AbstractPty.java b/terminal/src/main/java/org/jline/terminal/impl/AbstractPty.java index aeed6b3c6..94e03acd7 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/AbstractPty.java +++ b/terminal/src/main/java/org/jline/terminal/impl/AbstractPty.java @@ -9,6 +9,7 @@ package org.jline.terminal.impl; import java.io.FileDescriptor; +import java.io.FilterInputStream; import java.io.IOError; import java.io.IOException; import java.io.InputStream; @@ -34,6 +35,7 @@ public abstract class AbstractPty implements Pty { protected final TerminalProvider provider; protected final SystemStream systemStream; private Attributes current; + private boolean skipNextLf; public AbstractPty(TerminalProvider provider, SystemStream systemStream) { this.provider = provider; @@ -49,10 +51,32 @@ public void setAttr(Attributes attr) throws IOException { @Override public InputStream getSlaveInput() throws IOException { InputStream si = doGetSlaveInput(); + InputStream nsi = new FilterInputStream(si) { + @Override + public int read() throws IOException { + for (; ; ) { + int c = super.read(); + if (current.getInputFlag(Attributes.InputFlag.INORMEOL)) { + if (c == '\r') { + skipNextLf = true; + c = '\n'; + } else if (c == '\n') { + if (skipNextLf) { + skipNextLf = false; + continue; + } + } else { + skipNextLf = false; + } + } + return c; + } + } + }; if (Boolean.parseBoolean(System.getProperty(PROP_NON_BLOCKING_READS, "true"))) { - return new PtyInputStream(si); + return new PtyInputStream(nsi); } else { - return si; + return nsi; } } diff --git a/terminal/src/main/java/org/jline/terminal/impl/AbstractWindowsTerminal.java b/terminal/src/main/java/org/jline/terminal/impl/AbstractWindowsTerminal.java index 240c1d087..e3108286c 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/AbstractWindowsTerminal.java +++ b/terminal/src/main/java/org/jline/terminal/impl/AbstractWindowsTerminal.java @@ -85,6 +85,7 @@ public abstract class AbstractWindowsTerminal extends AbstractTerminal protected MouseTracking tracking = MouseTracking.Off; protected boolean focusTracking = false; private volatile boolean closing; + protected boolean skipNextLf; @SuppressWarnings("this-escape") public AbstractWindowsTerminal( @@ -496,7 +497,19 @@ public void processInputChar(char c) throws IOException { raise(Signal.INFO); } } - if (c == '\r') { + if (attributes.getInputFlag(Attributes.InputFlag.INORMEOL)) { + if (c == '\r') { + skipNextLf = true; + c = '\n'; + } else if (c == '\n') { + if (skipNextLf) { + skipNextLf = false; + return; + } + } else { + skipNextLf = false; + } + } else if (c == '\r') { if (attributes.getInputFlag(Attributes.InputFlag.IGNCR)) { return; } diff --git a/terminal/src/main/java/org/jline/terminal/impl/DumbTerminal.java b/terminal/src/main/java/org/jline/terminal/impl/DumbTerminal.java index 34984a25f..c8e4e6838 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/DumbTerminal.java +++ b/terminal/src/main/java/org/jline/terminal/impl/DumbTerminal.java @@ -34,6 +34,7 @@ public class DumbTerminal extends AbstractTerminal { private final PrintWriter writer; private final Attributes attributes; private final Size size; + private boolean skipNextLf; public DumbTerminal(InputStream in, OutputStream out) throws IOException { this(TYPE_DUMB, TYPE_DUMB, in, out, null); @@ -79,7 +80,19 @@ public int read(long timeout, boolean isPeek) throws IOException { continue; } } - if (c == '\r') { + if (attributes.getInputFlag(Attributes.InputFlag.INORMEOL)) { + if (c == '\r') { + skipNextLf = true; + c = '\n'; + } else if (c == '\n') { + if (skipNextLf) { + skipNextLf = false; + continue; + } + } else { + skipNextLf = false; + } + } else if (c == '\r') { if (attributes.getInputFlag(Attributes.InputFlag.IGNCR)) { continue; } diff --git a/terminal/src/main/java/org/jline/terminal/impl/LineDisciplineTerminal.java b/terminal/src/main/java/org/jline/terminal/impl/LineDisciplineTerminal.java index 7a60f2067..860b99f50 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/LineDisciplineTerminal.java +++ b/terminal/src/main/java/org/jline/terminal/impl/LineDisciplineTerminal.java @@ -75,6 +75,8 @@ public class LineDisciplineTerminal extends AbstractTerminal { protected final Size size; + protected boolean skipNextLf; + public LineDisciplineTerminal(String name, String type, OutputStream masterOutput, Charset encoding) throws IOException { this(name, type, masterOutput, encoding, SignalHandler.SIG_DFL); @@ -253,7 +255,19 @@ protected boolean doProcessInputByte(int c) throws IOException { raise(Signal.INFO); } } - if (c == '\r') { + if (attributes.getInputFlag(InputFlag.INORMEOL)) { + if (c == '\r') { + skipNextLf = true; + c = '\n'; + } else if (c == '\n') { + if (skipNextLf) { + skipNextLf = false; + return false; + } + } else { + skipNextLf = false; + } + } else if (c == '\r') { if (attributes.getInputFlag(InputFlag.IGNCR)) { return false; } diff --git a/terminal/src/main/java/org/jline/terminal/impl/exec/ExecPty.java b/terminal/src/main/java/org/jline/terminal/impl/exec/ExecPty.java index 7428f93ec..51eed3481 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/exec/ExecPty.java +++ b/terminal/src/main/java/org/jline/terminal/impl/exec/ExecPty.java @@ -120,7 +120,7 @@ protected void doSetAttr(Attributes attr) throws IOException { protected List getFlagsToSet(Attributes attr, Attributes current) { List commands = new ArrayList<>(); for (InputFlag flag : InputFlag.values()) { - if (attr.getInputFlag(flag) != current.getInputFlag(flag)) { + if (attr.getInputFlag(flag) != current.getInputFlag(flag) && flag != InputFlag.INORMEOL) { commands.add((attr.getInputFlag(flag) ? flag.name() : "-" + flag.name()).toLowerCase()); } }