diff --git a/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/FfmTerminalProvider.java b/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/FfmTerminalProvider.java index 4ea0579d9..c8670ff1a 100644 --- a/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/FfmTerminalProvider.java +++ b/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/FfmTerminalProvider.java @@ -49,6 +49,9 @@ public Terminal sysTerminal( String type, boolean ansiPassThrough, Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, boolean nativeSignals, Terminal.SignalHandler signalHandler, boolean paused, @@ -56,7 +59,18 @@ public Terminal sysTerminal( throws IOException { if (OSUtils.IS_WINDOWS) { return NativeWinSysTerminal.createTerminal( - this, systemStream, name, type, ansiPassThrough, encoding, nativeSignals, signalHandler, paused); + this, + systemStream, + name, + type, + ansiPassThrough, + encoding, + stdinEncoding, + stdoutEncoding, + stderrEncoding, + nativeSignals, + signalHandler, + paused); } else { Pty pty = new FfmNativePty( this, @@ -68,7 +82,16 @@ public Terminal sysTerminal( systemStream == SystemStream.Output ? 1 : 2, systemStream == SystemStream.Output ? FileDescriptor.out : FileDescriptor.err, CLibrary.ttyName(0)); - return new PosixSysTerminal(name, type, pty, encoding, nativeSignals, signalHandler); + return new PosixSysTerminal( + name, + type, + pty, + encoding, + stdinEncoding, + stdoutEncoding, + stderrEncoding, + nativeSignals, + signalHandler); } } @@ -79,13 +102,27 @@ public Terminal newTerminal( InputStream in, OutputStream out, Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, Terminal.SignalHandler signalHandler, boolean paused, Attributes attributes, Size size) throws IOException { Pty pty = CLibrary.openpty(this, attributes, size); - return new PosixPtyTerminal(name, type, pty, in, out, encoding, signalHandler, paused); + return new PosixPtyTerminal( + name, + type, + pty, + in, + out, + encoding, + stdinEncoding, + stdoutEncoding, + stderrEncoding, + signalHandler, + paused); } @Override diff --git a/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/NativeWinSysTerminal.java b/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/NativeWinSysTerminal.java index f09868539..b5f1cc77b 100644 --- a/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/NativeWinSysTerminal.java +++ b/terminal-ffm/src/main/java/org/jline/terminal/impl/ffm/NativeWinSysTerminal.java @@ -51,6 +51,35 @@ public static NativeWinSysTerminal createTerminal( SignalHandler signalHandler, boolean paused) throws IOException { + return createTerminal( + provider, + systemStream, + name, + type, + ansiPassThrough, + encoding, + encoding, + encoding, + encoding, + nativeSignals, + signalHandler, + paused); + } + + public static NativeWinSysTerminal createTerminal( + TerminalProvider provider, + SystemStream systemStream, + String name, + String type, + boolean ansiPassThrough, + Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, + boolean nativeSignals, + SignalHandler signalHandler, + boolean paused) + throws IOException { try (java.lang.foreign.Arena arena = java.lang.foreign.Arena.ofConfined()) { // Get input console mode java.lang.foreign.MemorySegment consoleIn = GetStdHandle(STD_INPUT_HANDLE); @@ -100,6 +129,9 @@ public static NativeWinSysTerminal createTerminal( name, type, encoding, + stdinEncoding, + stdoutEncoding, + stderrEncoding, nativeSignals, signalHandler, consoleIn, @@ -157,6 +189,41 @@ private static java.lang.foreign.MemorySegment allocateInt(java.lang.foreign.Are java.lang.foreign.MemorySegment outConsole, int outConsoleMode) throws IOException { + this( + provider, + systemStream, + writer, + name, + type, + encoding, + encoding, + encoding, + encoding, + nativeSignals, + signalHandler, + inConsole, + inConsoleMode, + outConsole, + outConsoleMode); + } + + NativeWinSysTerminal( + TerminalProvider provider, + SystemStream systemStream, + Writer writer, + String name, + String type, + Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, + boolean nativeSignals, + SignalHandler signalHandler, + java.lang.foreign.MemorySegment inConsole, + int inConsoleMode, + java.lang.foreign.MemorySegment outConsole, + int outConsoleMode) + throws IOException { super( provider, systemStream, @@ -164,6 +231,9 @@ private static java.lang.foreign.MemorySegment allocateInt(java.lang.foreign.Are name, type, encoding, + stdinEncoding, + stdoutEncoding, + stderrEncoding, nativeSignals, signalHandler, inConsole, diff --git a/terminal-ffm/src/test/java/org/jline/terminal/impl/ffm/FfmTest.java b/terminal-ffm/src/test/java/org/jline/terminal/impl/ffm/FfmTest.java index 90f14362a..1737c6040 100644 --- a/terminal-ffm/src/test/java/org/jline/terminal/impl/ffm/FfmTest.java +++ b/terminal-ffm/src/test/java/org/jline/terminal/impl/ffm/FfmTest.java @@ -33,6 +33,9 @@ public void testNewTerminalWithNull() throws IOException { new ByteArrayInputStream(new byte[0]), new ByteArrayOutputStream(), Charset.defaultCharset(), + Charset.defaultCharset(), + Charset.defaultCharset(), + Charset.defaultCharset(), Terminal.SignalHandler.SIG_DFL, false, null, @@ -50,6 +53,9 @@ public void testNewTerminalNoNull() throws IOException { new ByteArrayInputStream(new byte[0]), new ByteArrayOutputStream(), Charset.defaultCharset(), + Charset.defaultCharset(), + Charset.defaultCharset(), + Charset.defaultCharset(), Terminal.SignalHandler.SIG_DFL, false, new Attributes(), diff --git a/terminal-jansi/src/main/java/org/jline/terminal/impl/jansi/JansiTerminalProvider.java b/terminal-jansi/src/main/java/org/jline/terminal/impl/jansi/JansiTerminalProvider.java index a7c72d1a8..3fa787073 100644 --- a/terminal-jansi/src/main/java/org/jline/terminal/impl/jansi/JansiTerminalProvider.java +++ b/terminal-jansi/src/main/java/org/jline/terminal/impl/jansi/JansiTerminalProvider.java @@ -139,6 +139,9 @@ public Terminal sysTerminal( String type, boolean ansiPassThrough, Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, boolean nativeSignals, Terminal.SignalHandler signalHandler, boolean paused, @@ -146,10 +149,30 @@ public Terminal sysTerminal( throws IOException { if (OSUtils.IS_WINDOWS) { return winSysTerminal( - name, type, ansiPassThrough, encoding, nativeSignals, signalHandler, paused, systemStream); + name, + type, + ansiPassThrough, + encoding, + stdinEncoding, + stdoutEncoding, + stderrEncoding, + nativeSignals, + signalHandler, + paused, + systemStream); } else { return posixSysTerminal( - name, type, ansiPassThrough, encoding, nativeSignals, signalHandler, paused, systemStream); + name, + type, + ansiPassThrough, + encoding, + stdinEncoding, + stdoutEncoding, + stderrEncoding, + nativeSignals, + signalHandler, + paused, + systemStream); } } @@ -163,8 +186,46 @@ public Terminal winSysTerminal( boolean paused, SystemStream systemStream) throws IOException { + return winSysTerminal( + name, + type, + ansiPassThrough, + encoding, + encoding, + encoding, + encoding, + nativeSignals, + signalHandler, + paused, + systemStream); + } + + public Terminal winSysTerminal( + String name, + String type, + boolean ansiPassThrough, + Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, + boolean nativeSignals, + Terminal.SignalHandler signalHandler, + boolean paused, + SystemStream systemStream) + throws IOException { JansiWinSysTerminal terminal = JansiWinSysTerminal.createTerminal( - this, systemStream, name, type, ansiPassThrough, encoding, nativeSignals, signalHandler, paused); + this, + systemStream, + name, + type, + ansiPassThrough, + encoding, + stdinEncoding, + stdoutEncoding, + stderrEncoding, + nativeSignals, + signalHandler, + paused); terminal.disableScrolling(); return terminal; } @@ -179,8 +240,36 @@ public Terminal posixSysTerminal( boolean paused, SystemStream systemStream) throws IOException { + return posixSysTerminal( + name, + type, + ansiPassThrough, + encoding, + encoding, + encoding, + encoding, + nativeSignals, + signalHandler, + paused, + systemStream); + } + + public Terminal posixSysTerminal( + String name, + String type, + boolean ansiPassThrough, + Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, + boolean nativeSignals, + Terminal.SignalHandler signalHandler, + boolean paused, + SystemStream systemStream) + throws IOException { Pty pty = current(systemStream); - return new PosixSysTerminal(name, type, pty, encoding, nativeSignals, signalHandler); + return new PosixSysTerminal( + name, type, pty, encoding, stdinEncoding, stdoutEncoding, stderrEncoding, nativeSignals, signalHandler); } @Override @@ -190,13 +279,27 @@ public Terminal newTerminal( InputStream in, OutputStream out, Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, Terminal.SignalHandler signalHandler, boolean paused, Attributes attributes, Size size) throws IOException { Pty pty = open(attributes, size); - return new PosixPtyTerminal(name, type, pty, in, out, encoding, signalHandler, paused); + return new PosixPtyTerminal( + name, + type, + pty, + in, + out, + encoding, + stdinEncoding, + stdoutEncoding, + stderrEncoding, + signalHandler, + paused); } @Override diff --git a/terminal-jansi/src/main/java/org/jline/terminal/impl/jansi/win/JansiWinSysTerminal.java b/terminal-jansi/src/main/java/org/jline/terminal/impl/jansi/win/JansiWinSysTerminal.java index 4cb85f269..72f6e733b 100644 --- a/terminal-jansi/src/main/java/org/jline/terminal/impl/jansi/win/JansiWinSysTerminal.java +++ b/terminal-jansi/src/main/java/org/jline/terminal/impl/jansi/win/JansiWinSysTerminal.java @@ -54,6 +54,35 @@ public static JansiWinSysTerminal createTerminal( SignalHandler signalHandler, boolean paused) throws IOException { + return createTerminal( + provider, + systemStream, + name, + type, + ansiPassThrough, + encoding, + encoding, + encoding, + encoding, + nativeSignals, + signalHandler, + paused); + } + + public static JansiWinSysTerminal createTerminal( + TerminalProvider provider, + SystemStream systemStream, + String name, + String type, + boolean ansiPassThrough, + Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, + boolean nativeSignals, + SignalHandler signalHandler, + boolean paused) + throws IOException { // Get input console mode int[] inMode = new int[1]; if (Kernel32.GetConsoleMode(consoleIn, inMode) == 0) { @@ -90,6 +119,9 @@ public static JansiWinSysTerminal createTerminal( name, type, encoding, + stdinEncoding, + stdoutEncoding, + stderrEncoding, nativeSignals, signalHandler, consoleIn, @@ -160,6 +192,41 @@ public static boolean isWindowsSystemStream(SystemStream stream) { long outConsole, int outMode) throws IOException { + this( + provider, + systemStream, + writer, + name, + type, + encoding, + encoding, + encoding, + encoding, + nativeSignals, + signalHandler, + inConsole, + inMode, + outConsole, + outMode); + } + + JansiWinSysTerminal( + TerminalProvider provider, + SystemStream systemStream, + Writer writer, + String name, + String type, + Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, + boolean nativeSignals, + SignalHandler signalHandler, + long inConsole, + int inMode, + long outConsole, + int outMode) + throws IOException { super( provider, systemStream, @@ -167,6 +234,9 @@ public static boolean isWindowsSystemStream(SystemStream stream) { name, type, encoding, + stdinEncoding, + stdoutEncoding, + stderrEncoding, nativeSignals, signalHandler, inConsole, diff --git a/terminal-jansi/src/test/java/org/jline/terminal/impl/jansi/JansiTerminalProviderTest.java b/terminal-jansi/src/test/java/org/jline/terminal/impl/jansi/JansiTerminalProviderTest.java index de8416eb7..b4d7dfba4 100644 --- a/terminal-jansi/src/test/java/org/jline/terminal/impl/jansi/JansiTerminalProviderTest.java +++ b/terminal-jansi/src/test/java/org/jline/terminal/impl/jansi/JansiTerminalProviderTest.java @@ -50,6 +50,9 @@ void testNewTerminal() throws IOException { pis, baos, Charset.defaultCharset(), + Charset.defaultCharset(), + Charset.defaultCharset(), + Charset.defaultCharset(), Terminal.SignalHandler.SIG_DFL, true, null, diff --git a/terminal-jna/src/main/java/org/jline/terminal/impl/jna/JnaTerminalProvider.java b/terminal-jna/src/main/java/org/jline/terminal/impl/jna/JnaTerminalProvider.java index 5c4f5cf3f..56755b401 100644 --- a/terminal-jna/src/main/java/org/jline/terminal/impl/jna/JnaTerminalProvider.java +++ b/terminal-jna/src/main/java/org/jline/terminal/impl/jna/JnaTerminalProvider.java @@ -50,6 +50,9 @@ public Terminal sysTerminal( String type, boolean ansiPassThrough, Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, boolean nativeSignals, Terminal.SignalHandler signalHandler, boolean paused, @@ -57,10 +60,30 @@ public Terminal sysTerminal( throws IOException { if (OSUtils.IS_WINDOWS) { return winSysTerminal( - name, type, ansiPassThrough, encoding, nativeSignals, signalHandler, paused, systemStream); + name, + type, + ansiPassThrough, + encoding, + stdinEncoding, + stdoutEncoding, + stderrEncoding, + nativeSignals, + signalHandler, + paused, + systemStream); } else { return posixSysTerminal( - name, type, ansiPassThrough, encoding, nativeSignals, signalHandler, paused, systemStream); + name, + type, + ansiPassThrough, + encoding, + stdinEncoding, + stdoutEncoding, + stderrEncoding, + nativeSignals, + signalHandler, + paused, + systemStream); } } @@ -74,8 +97,70 @@ public Terminal winSysTerminal( boolean paused, SystemStream systemStream) throws IOException { + return winSysTerminal( + name, + type, + ansiPassThrough, + encoding, + encoding, + encoding, + encoding, + nativeSignals, + signalHandler, + paused, + systemStream); + } + + public Terminal winSysTerminal( + String name, + String type, + boolean ansiPassThrough, + Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, + boolean nativeSignals, + Terminal.SignalHandler signalHandler, + boolean paused, + SystemStream systemStream) + throws IOException { return JnaWinSysTerminal.createTerminal( - this, systemStream, name, type, ansiPassThrough, encoding, nativeSignals, signalHandler, paused); + this, + systemStream, + name, + type, + ansiPassThrough, + encoding, + stdinEncoding, + stdoutEncoding, + stderrEncoding, + nativeSignals, + signalHandler, + paused); + } + + public Terminal posixSysTerminal( + String name, + String type, + boolean ansiPassThrough, + Charset encoding, + boolean nativeSignals, + Terminal.SignalHandler signalHandler, + boolean paused, + SystemStream systemStream) + throws IOException { + return posixSysTerminal( + name, + type, + ansiPassThrough, + encoding, + encoding, + encoding, + encoding, + nativeSignals, + signalHandler, + paused, + systemStream); } public Terminal posixSysTerminal( @@ -83,13 +168,17 @@ public Terminal posixSysTerminal( String type, boolean ansiPassThrough, Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, boolean nativeSignals, Terminal.SignalHandler signalHandler, boolean paused, SystemStream systemStream) throws IOException { Pty pty = current(systemStream); - return new PosixSysTerminal(name, type, pty, encoding, nativeSignals, signalHandler); + return new PosixSysTerminal( + name, type, pty, encoding, stdinEncoding, stdoutEncoding, stderrEncoding, nativeSignals, signalHandler); } @Override @@ -99,13 +188,27 @@ public Terminal newTerminal( InputStream in, OutputStream out, Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, Terminal.SignalHandler signalHandler, boolean paused, Attributes attributes, Size size) throws IOException { Pty pty = open(attributes, size); - return new PosixPtyTerminal(name, type, pty, in, out, encoding, signalHandler, paused); + return new PosixPtyTerminal( + name, + type, + pty, + in, + out, + encoding, + stdinEncoding, + stdoutEncoding, + stderrEncoding, + signalHandler, + paused); } @Override diff --git a/terminal-jna/src/main/java/org/jline/terminal/impl/jna/win/JnaWinSysTerminal.java b/terminal-jna/src/main/java/org/jline/terminal/impl/jna/win/JnaWinSysTerminal.java index 30a61a054..1f255c727 100644 --- a/terminal-jna/src/main/java/org/jline/terminal/impl/jna/win/JnaWinSysTerminal.java +++ b/terminal-jna/src/main/java/org/jline/terminal/impl/jna/win/JnaWinSysTerminal.java @@ -43,6 +43,35 @@ public static JnaWinSysTerminal createTerminal( SignalHandler signalHandler, boolean paused) throws IOException { + return createTerminal( + provider, + systemStream, + name, + type, + ansiPassThrough, + encoding, + encoding, + encoding, + encoding, + nativeSignals, + signalHandler, + paused); + } + + public static JnaWinSysTerminal createTerminal( + TerminalProvider provider, + SystemStream systemStream, + String name, + String type, + boolean ansiPassThrough, + Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, + boolean nativeSignals, + SignalHandler signalHandler, + boolean paused) + throws IOException { // Get input console mode IntByReference inMode = new IntByReference(); Kernel32.INSTANCE.GetConsoleMode(JnaWinSysTerminal.consoleIn, inMode); @@ -85,6 +114,9 @@ public static JnaWinSysTerminal createTerminal( name, type, encoding, + stdinEncoding, + stdoutEncoding, + stderrEncoding, nativeSignals, signalHandler, JnaWinSysTerminal.consoleIn, @@ -146,6 +178,41 @@ public static boolean isWindowsSystemStream(SystemStream stream) { Pointer outConsole, int outConsoleMode) throws IOException { + this( + provider, + systemStream, + writer, + name, + type, + encoding, + encoding, + encoding, + encoding, + nativeSignals, + signalHandler, + inConsole, + inConsoleMode, + outConsole, + outConsoleMode); + } + + JnaWinSysTerminal( + TerminalProvider provider, + SystemStream systemStream, + Writer writer, + String name, + String type, + Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, + boolean nativeSignals, + SignalHandler signalHandler, + Pointer inConsole, + int inConsoleMode, + Pointer outConsole, + int outConsoleMode) + throws IOException { super( provider, systemStream, @@ -153,6 +220,9 @@ public static boolean isWindowsSystemStream(SystemStream stream) { name, type, encoding, + stdinEncoding, + stdoutEncoding, + stderrEncoding, nativeSignals, signalHandler, inConsole, diff --git a/terminal-jna/src/test/java/org/jline/terminal/impl/jna/JnaTerminalProviderTest.java b/terminal-jna/src/test/java/org/jline/terminal/impl/jna/JnaTerminalProviderTest.java index 42363339c..3c808cf9b 100644 --- a/terminal-jna/src/test/java/org/jline/terminal/impl/jna/JnaTerminalProviderTest.java +++ b/terminal-jna/src/test/java/org/jline/terminal/impl/jna/JnaTerminalProviderTest.java @@ -51,6 +51,9 @@ void testNewTerminal() throws IOException { pis, baos, Charset.defaultCharset(), + Charset.defaultCharset(), + Charset.defaultCharset(), + Charset.defaultCharset(), Terminal.SignalHandler.SIG_DFL, true, null, diff --git a/terminal-jni/src/main/java/org/jline/terminal/impl/jni/JniTerminalProvider.java b/terminal-jni/src/main/java/org/jline/terminal/impl/jni/JniTerminalProvider.java index 220ab1801..b2cc9c0b2 100644 --- a/terminal-jni/src/main/java/org/jline/terminal/impl/jni/JniTerminalProvider.java +++ b/terminal-jni/src/main/java/org/jline/terminal/impl/jni/JniTerminalProvider.java @@ -107,6 +107,9 @@ public Terminal sysTerminal( String type, boolean ansiPassThrough, Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, boolean nativeSignals, Terminal.SignalHandler signalHandler, boolean paused, @@ -114,10 +117,30 @@ public Terminal sysTerminal( throws IOException { if (OSUtils.IS_WINDOWS) { return winSysTerminal( - name, type, ansiPassThrough, encoding, nativeSignals, signalHandler, paused, systemStream); + name, + type, + ansiPassThrough, + encoding, + stdinEncoding, + stdoutEncoding, + stderrEncoding, + nativeSignals, + signalHandler, + paused, + systemStream); } else { return posixSysTerminal( - name, type, ansiPassThrough, encoding, nativeSignals, signalHandler, paused, systemStream); + name, + type, + ansiPassThrough, + encoding, + stdinEncoding, + stdoutEncoding, + stderrEncoding, + nativeSignals, + signalHandler, + paused, + systemStream); } } @@ -131,8 +154,70 @@ public Terminal winSysTerminal( boolean paused, SystemStream systemStream) throws IOException { + return winSysTerminal( + name, + type, + ansiPassThrough, + encoding, + encoding, + encoding, + encoding, + nativeSignals, + signalHandler, + paused, + systemStream); + } + + public Terminal winSysTerminal( + String name, + String type, + boolean ansiPassThrough, + Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, + boolean nativeSignals, + Terminal.SignalHandler signalHandler, + boolean paused, + SystemStream systemStream) + throws IOException { return NativeWinSysTerminal.createTerminal( - this, systemStream, name, type, ansiPassThrough, encoding, nativeSignals, signalHandler, paused); + this, + systemStream, + name, + type, + ansiPassThrough, + encoding, + stdinEncoding, + stdoutEncoding, + stderrEncoding, + nativeSignals, + signalHandler, + paused); + } + + public Terminal posixSysTerminal( + String name, + String type, + boolean ansiPassThrough, + Charset encoding, + boolean nativeSignals, + Terminal.SignalHandler signalHandler, + boolean paused, + SystemStream systemStream) + throws IOException { + return posixSysTerminal( + name, + type, + ansiPassThrough, + encoding, + encoding, + encoding, + encoding, + nativeSignals, + signalHandler, + paused, + systemStream); } public Terminal posixSysTerminal( @@ -140,13 +225,17 @@ public Terminal posixSysTerminal( String type, boolean ansiPassThrough, Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, boolean nativeSignals, Terminal.SignalHandler signalHandler, boolean paused, SystemStream systemStream) throws IOException { Pty pty = current(systemStream); - return new PosixSysTerminal(name, type, pty, encoding, nativeSignals, signalHandler); + return new PosixSysTerminal( + name, type, pty, encoding, stdinEncoding, stdoutEncoding, stderrEncoding, nativeSignals, signalHandler); } @Override @@ -156,13 +245,27 @@ public Terminal newTerminal( InputStream in, OutputStream out, Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, Terminal.SignalHandler signalHandler, boolean paused, Attributes attributes, Size size) throws IOException { Pty pty = open(attributes, size); - return new PosixPtyTerminal(name, type, pty, in, out, encoding, signalHandler, paused); + return new PosixPtyTerminal( + name, + type, + pty, + in, + out, + encoding, + stdinEncoding, + stdoutEncoding, + stderrEncoding, + signalHandler, + paused); } @Override diff --git a/terminal-jni/src/main/java/org/jline/terminal/impl/jni/win/NativeWinSysTerminal.java b/terminal-jni/src/main/java/org/jline/terminal/impl/jni/win/NativeWinSysTerminal.java index 8bc46b8bf..ec355e3a2 100644 --- a/terminal-jni/src/main/java/org/jline/terminal/impl/jni/win/NativeWinSysTerminal.java +++ b/terminal-jni/src/main/java/org/jline/terminal/impl/jni/win/NativeWinSysTerminal.java @@ -57,6 +57,35 @@ public static NativeWinSysTerminal createTerminal( SignalHandler signalHandler, boolean paused) throws IOException { + return createTerminal( + provider, + systemStream, + name, + type, + ansiPassThrough, + encoding, + encoding, + encoding, + encoding, + nativeSignals, + signalHandler, + paused); + } + + public static NativeWinSysTerminal createTerminal( + TerminalProvider provider, + SystemStream systemStream, + String name, + String type, + boolean ansiPassThrough, + Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, + boolean nativeSignals, + SignalHandler signalHandler, + boolean paused) + throws IOException { // Get input console mode int[] inMode = new int[1]; if (Kernel32.GetConsoleMode(consoleIn, inMode) == 0) { @@ -93,6 +122,9 @@ public static NativeWinSysTerminal createTerminal( name, type, encoding, + stdinEncoding, + stdoutEncoding, + stderrEncoding, nativeSignals, signalHandler, consoleIn, @@ -163,6 +195,41 @@ public static boolean isWindowsSystemStream(SystemStream stream) { long outConsole, int outMode) throws IOException { + this( + provider, + systemStream, + writer, + name, + type, + encoding, + encoding, + encoding, + encoding, + nativeSignals, + signalHandler, + inConsole, + inMode, + outConsole, + outMode); + } + + NativeWinSysTerminal( + TerminalProvider provider, + SystemStream systemStream, + Writer writer, + String name, + String type, + Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, + boolean nativeSignals, + SignalHandler signalHandler, + long inConsole, + int inMode, + long outConsole, + int outMode) + throws IOException { super( provider, systemStream, @@ -170,6 +237,9 @@ public static boolean isWindowsSystemStream(SystemStream stream) { name, type, encoding, + stdinEncoding, + stdoutEncoding, + stderrEncoding, nativeSignals, signalHandler, inConsole, diff --git a/terminal-jni/src/test/java/org/jline/terminal/impl/jni/JniTerminalProviderTest.java b/terminal-jni/src/test/java/org/jline/terminal/impl/jni/JniTerminalProviderTest.java index e08a4024b..419f89556 100644 --- a/terminal-jni/src/test/java/org/jline/terminal/impl/jni/JniTerminalProviderTest.java +++ b/terminal-jni/src/test/java/org/jline/terminal/impl/jni/JniTerminalProviderTest.java @@ -44,6 +44,9 @@ void testNewTerminal() throws IOException { pis, baos, Charset.defaultCharset(), + Charset.defaultCharset(), + Charset.defaultCharset(), + Charset.defaultCharset(), Terminal.SignalHandler.SIG_DFL, true, null, diff --git a/terminal/src/main/java/org/jline/terminal/Terminal.java b/terminal/src/main/java/org/jline/terminal/Terminal.java index ec1abbf68..ae68586ac 100644 --- a/terminal/src/main/java/org/jline/terminal/Terminal.java +++ b/terminal/src/main/java/org/jline/terminal/Terminal.java @@ -388,10 +388,62 @@ interface SignalHandler { * Returns the {@link Charset} that should be used to encode characters * for {@link #input()} and {@link #output()}. * + *
This method returns a general encoding that can be used for both input and output. + * For stream-specific encodings, use {@link #stdinEncoding()}, {@link #stdoutEncoding()}, + * and {@link #stderrEncoding()}.
+ * * @return The terminal encoding + * @see #stdinEncoding() + * @see #stdoutEncoding() + * @see #stderrEncoding() */ Charset encoding(); + /** + * Returns the {@link Charset} that should be used to decode characters + * from standard input ({@link #input()}). + * + *This method returns the encoding specifically for standard input. + * If no specific stdin encoding was configured, it falls back to the + * general encoding from {@link #encoding()}.
+ * + * @return The standard input encoding + * @see #encoding() + */ + default Charset stdinEncoding() { + return encoding(); + } + + /** + * Returns the {@link Charset} that should be used to encode characters + * for standard output ({@link #output()}). + * + *This method returns the encoding specifically for standard output. + * If no specific stdout encoding was configured, it falls back to the + * general encoding from {@link #encoding()}.
+ * + * @return The standard output encoding + * @see #encoding() + */ + default Charset stdoutEncoding() { + return encoding(); + } + + /** + * Returns the {@link Charset} that should be used to encode characters + * for standard error. + * + *This method returns the encoding specifically for standard error. + * If no specific stderr encoding was configured, it falls back to the + * general encoding from {@link #encoding()}.
+ * + * @return The standard error encoding + * @see #encoding() + */ + default Charset stderrEncoding() { + return encoding(); + } + /** * Retrieve the input stream for this terminal. * In some rare cases, there may be a need to access the diff --git a/terminal/src/main/java/org/jline/terminal/TerminalBuilder.java b/terminal/src/main/java/org/jline/terminal/TerminalBuilder.java index b13ec7182..a0994d4e5 100644 --- a/terminal/src/main/java/org/jline/terminal/TerminalBuilder.java +++ b/terminal/src/main/java/org/jline/terminal/TerminalBuilder.java @@ -135,6 +135,9 @@ public final class TerminalBuilder { // public static final String PROP_ENCODING = "org.jline.terminal.encoding"; + public static final String PROP_STDIN_ENCODING = "org.jline.terminal.stdin.encoding"; + public static final String PROP_STDOUT_ENCODING = "org.jline.terminal.stdout.encoding"; + public static final String PROP_STDERR_ENCODING = "org.jline.terminal.stderr.encoding"; public static final String PROP_CODEPAGE = "org.jline.terminal.codepage"; public static final String PROP_TYPE = "org.jline.terminal.type"; public static final String PROP_PROVIDER = "org.jline.terminal.provider"; @@ -286,6 +289,9 @@ public static TerminalBuilder builder() { private OutputStream out; private String type; private Charset encoding; + private Charset stdinEncoding; + private Charset stdoutEncoding; + private Charset stderrEncoding; private int codepage; private Boolean system; private SystemOutput systemOutput; @@ -452,6 +458,10 @@ public TerminalBuilder color(boolean color) { *Use {@link Terminal#encoding()} to get the {@link Charset} that * should be used for a {@link Terminal}.
* + *This method sets a single encoding for all streams (stdin, stdout, stderr). + * To set separate encodings for each stream, use {@link #stdinEncoding(Charset)}, + * {@link #stdoutEncoding(Charset)}, and {@link #stderrEncoding(Charset)}.
+ * * @param encoding The encoding to use or null to automatically select one * @return The builder * @throws UnsupportedCharsetException If the given encoding is not supported @@ -471,6 +481,10 @@ public TerminalBuilder encoding(String encoding) throws UnsupportedCharsetExcept *Use {@link Terminal#encoding()} to get the {@link Charset} that * should be used to read/write from a {@link Terminal}.
* + *This method sets a single encoding for all streams (stdin, stdout, stderr). + * To set separate encodings for each stream, use {@link #stdinEncoding(Charset)}, + * {@link #stdoutEncoding(Charset)}, and {@link #stderrEncoding(Charset)}.
+ * * @param encoding The encoding to use or null to automatically select one * @return The builder * @see Terminal#encoding() @@ -480,6 +494,96 @@ public TerminalBuilder encoding(Charset encoding) { return this; } + /** + * Set the encoding to use for reading from standard input. + * If {@code null} (the default value), JLine will use the value from + * the "stdin.encoding" system property if set, or fall back to the + * general encoding. + * + * @param encoding The encoding to use or null to automatically select one + * @return The builder + * @throws UnsupportedCharsetException If the given encoding is not supported + * @see Terminal#stdinEncoding() + */ + public TerminalBuilder stdinEncoding(String encoding) throws UnsupportedCharsetException { + return stdinEncoding(encoding != null ? Charset.forName(encoding) : null); + } + + /** + * Set the {@link Charset} to use for reading from standard input. + * If {@code null} (the default value), JLine will use the value from + * the "stdin.encoding" system property if set, or fall back to the + * general encoding. + * + * @param encoding The encoding to use or null to automatically select one + * @return The builder + * @see Terminal#stdinEncoding() + */ + public TerminalBuilder stdinEncoding(Charset encoding) { + this.stdinEncoding = encoding; + return this; + } + + /** + * Set the encoding to use for writing to standard output. + * If {@code null} (the default value), JLine will use the value from + * the "stdout.encoding" system property if set, or fall back to the + * general encoding. + * + * @param encoding The encoding to use or null to automatically select one + * @return The builder + * @throws UnsupportedCharsetException If the given encoding is not supported + * @see Terminal#stdoutEncoding() + */ + public TerminalBuilder stdoutEncoding(String encoding) throws UnsupportedCharsetException { + return stdoutEncoding(encoding != null ? Charset.forName(encoding) : null); + } + + /** + * Set the {@link Charset} to use for writing to standard output. + * If {@code null} (the default value), JLine will use the value from + * the "stdout.encoding" system property if set, or fall back to the + * general encoding. + * + * @param encoding The encoding to use or null to automatically select one + * @return The builder + * @see Terminal#stdoutEncoding() + */ + public TerminalBuilder stdoutEncoding(Charset encoding) { + this.stdoutEncoding = encoding; + return this; + } + + /** + * Set the encoding to use for writing to standard error. + * If {@code null} (the default value), JLine will use the value from + * the "stderr.encoding" system property if set, or fall back to the + * general encoding. + * + * @param encoding The encoding to use or null to automatically select one + * @return The builder + * @throws UnsupportedCharsetException If the given encoding is not supported + * @see Terminal#stderrEncoding() + */ + public TerminalBuilder stderrEncoding(String encoding) throws UnsupportedCharsetException { + return stderrEncoding(encoding != null ? Charset.forName(encoding) : null); + } + + /** + * Set the {@link Charset} to use for writing to standard error. + * If {@code null} (the default value), JLine will use the value from + * the "stderr.encoding" system property if set, or fall back to the + * general encoding. + * + * @param encoding The encoding to use or null to automatically select one + * @return The builder + * @see Terminal#stderrEncoding() + */ + public TerminalBuilder stderrEncoding(Charset encoding) { + this.stderrEncoding = encoding; + return this; + } + /** * @param codepage the codepage * @return The builder @@ -583,6 +687,9 @@ private Terminal doBuild() throws IOException { name = "JLine terminal"; } Charset encoding = computeEncoding(); + Charset stdinEncoding = computeStdinEncoding(); + Charset stdoutEncoding = computeStdoutEncoding(); + Charset stderrEncoding = computeStderrEncoding(); String type = computeType(); String provider = this.provider; @@ -636,6 +743,9 @@ private Terminal doBuild() throws IOException { type, ansiPassThrough, encoding, + stdinEncoding, + stdoutEncoding, + stderrEncoding, nativeSignals, signalHandler, paused, @@ -679,7 +789,18 @@ private Terminal doBuild() throws IOException { } type = getDumbTerminalType(dumb, systemStream); terminal = new DumbTerminalProvider() - .sysTerminal(name, type, false, encoding, nativeSignals, signalHandler, paused, systemStream); + .sysTerminal( + name, + type, + false, + encoding, + stdinEncoding, + stdoutEncoding, + stderrEncoding, + nativeSignals, + signalHandler, + paused, + systemStream); if (OSUtils.IS_WINDOWS) { Attributes attr = terminal.getAttributes(); attr.setInputFlag(Attributes.InputFlag.IGNCR, true); @@ -691,7 +812,18 @@ private Terminal doBuild() throws IOException { if (terminal == null) { try { terminal = prov.newTerminal( - name, type, in, out, encoding, signalHandler, paused, attributes, size); + name, + type, + in, + out, + encoding, + stdinEncoding, + stdoutEncoding, + stderrEncoding, + signalHandler, + paused, + attributes, + size); } catch (Throwable t) { Log.debug("Error creating " + prov.name() + " based terminal: ", t.getMessage(), t); exception.addSuppressed(t); @@ -841,6 +973,48 @@ public Charset computeEncoding() { return encoding; } + public Charset computeStdinEncoding() { + return computeSpecificEncoding(this.stdinEncoding, PROP_STDIN_ENCODING, "stdin.encoding"); + } + + public Charset computeStdoutEncoding() { + return computeSpecificEncoding(this.stdoutEncoding, PROP_STDOUT_ENCODING, "stdout.encoding"); + } + + public Charset computeStderrEncoding() { + return computeSpecificEncoding(this.stderrEncoding, PROP_STDERR_ENCODING, "stderr.encoding"); + } + + /** + * Helper method to compute encoding from a specific field, JLine property, and standard Java property. + * + * @param specificEncoding the specific encoding field value + * @param jlineProperty the JLine-specific property name + * @param standardProperty the standard Java property name + * @return the computed encoding, falling back to the general encoding if needed + */ + private Charset computeSpecificEncoding(Charset specificEncoding, String jlineProperty, String standardProperty) { + Charset encoding = specificEncoding; + if (encoding == null) { + // First try JLine specific property + String charsetName = System.getProperty(jlineProperty); + if (charsetName != null && Charset.isSupported(charsetName)) { + encoding = Charset.forName(charsetName); + } + // Then try standard Java property + if (encoding == null) { + charsetName = System.getProperty(standardProperty); + if (charsetName != null && Charset.isSupported(charsetName)) { + encoding = Charset.forName(charsetName); + } + } + } + if (encoding == null) { + encoding = computeEncoding(); + } + return encoding; + } + /** * Get the list of available terminal providers. * This list is sorted according to the {@link #PROP_PROVIDERS} system property. diff --git a/terminal/src/main/java/org/jline/terminal/impl/AbstractPosixTerminal.java b/terminal/src/main/java/org/jline/terminal/impl/AbstractPosixTerminal.java index 027eb5b15..2f7ac7b3c 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/AbstractPosixTerminal.java +++ b/terminal/src/main/java/org/jline/terminal/impl/AbstractPosixTerminal.java @@ -71,7 +71,20 @@ public AbstractPosixTerminal(String name, String type, Pty pty) throws IOExcepti public AbstractPosixTerminal(String name, String type, Pty pty, Charset encoding, SignalHandler signalHandler) throws IOException { - super(name, type, encoding, signalHandler); + this(name, type, pty, encoding, encoding, encoding, encoding, signalHandler); + } + + public AbstractPosixTerminal( + String name, + String type, + Pty pty, + Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, + SignalHandler signalHandler) + throws IOException { + super(name, type, encoding, stdinEncoding, stdoutEncoding, stderrEncoding, signalHandler); Objects.requireNonNull(pty); this.pty = pty; this.originalAttributes = this.pty.getAttr(); diff --git a/terminal/src/main/java/org/jline/terminal/impl/AbstractTerminal.java b/terminal/src/main/java/org/jline/terminal/impl/AbstractTerminal.java index 18eeb34ef..3ead1aaba 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/AbstractTerminal.java +++ b/terminal/src/main/java/org/jline/terminal/impl/AbstractTerminal.java @@ -72,6 +72,9 @@ public abstract class AbstractTerminal implements TerminalExt { protected final String name; protected final String type; protected final Charset encoding; + protected final Charset stdinEncoding; + protected final Charset stdoutEncoding; + protected final Charset stderrEncoding; protected final Map+ * This method creates a terminal that is connected to one of the standard + * system streams (standard input, standard output, or standard error). Such + * terminals typically represent the actual terminal window or console that + * the application is running in. + *
+ * + * @param name the name of the terminal + * @param type the terminal type (e.g., "xterm", "dumb") + * @param ansiPassThrough whether to pass through ANSI escape sequences + * @param encoding the general character encoding to use + * @param stdinEncoding the character encoding to use for standard input + * @param stdoutEncoding the character encoding to use for standard output + * @param stderrEncoding the character encoding to use for standard error + * @param nativeSignals whether to use native signal handling + * @param signalHandler the signal handler to use + * @param paused whether the terminal should start in a paused state + * @param systemStream the system stream to connect to + * @return a new terminal connected to the specified system stream + * @throws IOException if an I/O error occurs */ Terminal sysTerminal( String name, String type, boolean ansiPassThrough, Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, boolean nativeSignals, Terminal.SignalHandler signalHandler, boolean paused, @@ -116,6 +169,59 @@ Terminal sysTerminal( * @param size the initial terminal size * @return a new terminal connected to the specified streams * @throws IOException if an I/O error occurs + * @deprecated Use {@link #newTerminal(String, String, InputStream, OutputStream, Charset, Charset, Charset, Charset, Terminal.SignalHandler, boolean, Attributes, Size)} instead + */ + @Deprecated + default Terminal newTerminal( + String name, + String type, + InputStream masterInput, + OutputStream masterOutput, + Charset encoding, + Terminal.SignalHandler signalHandler, + boolean paused, + Attributes attributes, + Size size) + throws IOException { + return newTerminal( + name, + type, + masterInput, + masterOutput, + encoding, + encoding, + encoding, + encoding, + signalHandler, + paused, + attributes, + size); + } + + /** + * Creates a new terminal with custom input and output streams. + * + *+ * This method creates a terminal that is connected to the specified input and + * output streams. Such terminals can be used for various purposes, such as + * connecting to remote terminals over network connections or creating virtual + * terminals for testing. + *
+ * + * @param name the name of the terminal + * @param type the terminal type (e.g., "xterm", "dumb") + * @param masterInput the input stream to read from + * @param masterOutput the output stream to write to + * @param encoding the general character encoding to use + * @param stdinEncoding the character encoding to use for standard input + * @param stdoutEncoding the character encoding to use for standard output + * @param stderrEncoding the character encoding to use for standard error + * @param signalHandler the signal handler to use + * @param paused whether the terminal should start in a paused state + * @param attributes the initial terminal attributes + * @param size the initial terminal size + * @return a new terminal connected to the specified streams + * @throws IOException if an I/O error occurs */ Terminal newTerminal( String name, @@ -123,6 +229,9 @@ Terminal newTerminal( InputStream masterInput, OutputStream masterOutput, Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, Terminal.SignalHandler signalHandler, boolean paused, Attributes attributes, diff --git a/terminal/src/test/java/org/jline/terminal/EncodingTest.java b/terminal/src/test/java/org/jline/terminal/EncodingTest.java new file mode 100644 index 000000000..f577c2da1 --- /dev/null +++ b/terminal/src/test/java/org/jline/terminal/EncodingTest.java @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2002-2025, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.terminal; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import org.jline.terminal.impl.DumbTerminal; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Tests for terminal encoding functionality. + */ +public class EncodingTest { + + /** + * Test that the default encoding methods return the main encoding when no specific encodings are set. + */ + @Test + public void testDefaultEncodings() throws IOException { + Terminal terminal = createTestTerminal(StandardCharsets.UTF_8, null, null, null); + + assertEquals(StandardCharsets.UTF_8, terminal.encoding()); + assertEquals(StandardCharsets.UTF_8, terminal.stdinEncoding()); + assertEquals(StandardCharsets.UTF_8, terminal.stdoutEncoding()); + assertEquals(StandardCharsets.UTF_8, terminal.stderrEncoding()); + } + + /** + * Test that specific encodings are used when set. + */ + @Test + public void testSpecificEncodings() throws IOException { + Terminal terminal = createTestTerminal( + StandardCharsets.UTF_8, + StandardCharsets.ISO_8859_1, + StandardCharsets.UTF_16, + StandardCharsets.US_ASCII); + + assertEquals(StandardCharsets.UTF_8, terminal.encoding()); + assertEquals(StandardCharsets.ISO_8859_1, terminal.stdinEncoding()); + assertEquals(StandardCharsets.UTF_16, terminal.stdoutEncoding()); + assertEquals(StandardCharsets.US_ASCII, terminal.stderrEncoding()); + } + + /** + * Test that the TerminalBuilder correctly sets encodings. + */ + @Test + public void testTerminalBuilderEncodings() throws IOException { + Terminal terminal = TerminalBuilder.builder() + .dumb(true) + .encoding(StandardCharsets.UTF_8) + .stdinEncoding(StandardCharsets.ISO_8859_1) + .stdoutEncoding(StandardCharsets.UTF_16) + .stderrEncoding(StandardCharsets.US_ASCII) + .build(); + + assertEquals(StandardCharsets.UTF_8, terminal.encoding()); + assertEquals(StandardCharsets.ISO_8859_1, terminal.stdinEncoding()); + assertEquals(StandardCharsets.UTF_16, terminal.stdoutEncoding()); + assertEquals(StandardCharsets.US_ASCII, terminal.stderrEncoding()); + } + + /** + * Test that JLine system properties are correctly used for encodings. + */ + @Test + public void testJLineSystemPropertyEncodings() throws IOException { + String oldEncoding = System.getProperty(TerminalBuilder.PROP_ENCODING); + String oldStdinEncoding = System.getProperty(TerminalBuilder.PROP_STDIN_ENCODING); + String oldStdoutEncoding = System.getProperty(TerminalBuilder.PROP_STDOUT_ENCODING); + String oldStderrEncoding = System.getProperty(TerminalBuilder.PROP_STDERR_ENCODING); + + try { + System.setProperty(TerminalBuilder.PROP_ENCODING, "UTF-8"); + System.setProperty(TerminalBuilder.PROP_STDIN_ENCODING, "ISO-8859-1"); + System.setProperty(TerminalBuilder.PROP_STDOUT_ENCODING, "UTF-16"); + System.setProperty(TerminalBuilder.PROP_STDERR_ENCODING, "US-ASCII"); + + Terminal terminal = TerminalBuilder.builder().dumb(true).build(); + + assertEquals(StandardCharsets.UTF_8, terminal.encoding()); + assertEquals(StandardCharsets.ISO_8859_1, terminal.stdinEncoding()); + assertEquals(StandardCharsets.UTF_16, terminal.stdoutEncoding()); + assertEquals(StandardCharsets.US_ASCII, terminal.stderrEncoding()); + } finally { + // Restore original system properties + if (oldEncoding != null) { + System.setProperty(TerminalBuilder.PROP_ENCODING, oldEncoding); + } else { + System.clearProperty(TerminalBuilder.PROP_ENCODING); + } + + if (oldStdinEncoding != null) { + System.setProperty(TerminalBuilder.PROP_STDIN_ENCODING, oldStdinEncoding); + } else { + System.clearProperty(TerminalBuilder.PROP_STDIN_ENCODING); + } + + if (oldStdoutEncoding != null) { + System.setProperty(TerminalBuilder.PROP_STDOUT_ENCODING, oldStdoutEncoding); + } else { + System.clearProperty(TerminalBuilder.PROP_STDOUT_ENCODING); + } + + if (oldStderrEncoding != null) { + System.setProperty(TerminalBuilder.PROP_STDERR_ENCODING, oldStderrEncoding); + } else { + System.clearProperty(TerminalBuilder.PROP_STDERR_ENCODING); + } + } + } + + /** + * Test that standard Java system properties are correctly used for encodings. + */ + @Test + public void testStandardJavaSystemPropertyEncodings() throws IOException { + String oldStdinEncoding = System.getProperty("stdin.encoding"); + String oldStdoutEncoding = System.getProperty("stdout.encoding"); + String oldStderrEncoding = System.getProperty("stderr.encoding"); + + try { + System.setProperty("stdin.encoding", "ISO-8859-1"); + System.setProperty("stdout.encoding", "UTF-16"); + System.setProperty("stderr.encoding", "US-ASCII"); + + Terminal terminal = TerminalBuilder.builder().dumb(true).build(); + + assertEquals(StandardCharsets.ISO_8859_1, terminal.stdinEncoding()); + assertEquals(StandardCharsets.UTF_16, terminal.stdoutEncoding()); + assertEquals(StandardCharsets.US_ASCII, terminal.stderrEncoding()); + } finally { + // Restore original system properties + if (oldStdinEncoding != null) { + System.setProperty("stdin.encoding", oldStdinEncoding); + } else { + System.clearProperty("stdin.encoding"); + } + + if (oldStdoutEncoding != null) { + System.setProperty("stdout.encoding", oldStdoutEncoding); + } else { + System.clearProperty("stdout.encoding"); + } + + if (oldStderrEncoding != null) { + System.setProperty("stderr.encoding", oldStderrEncoding); + } else { + System.clearProperty("stderr.encoding"); + } + } + } + + /** + * Test that JLine system properties take precedence over standard Java system properties. + */ + @Test + public void testSystemPropertyPrecedence() throws IOException { + String oldJLineStdinEncoding = System.getProperty(TerminalBuilder.PROP_STDIN_ENCODING); + String oldJLineStdoutEncoding = System.getProperty(TerminalBuilder.PROP_STDOUT_ENCODING); + String oldJLineStderrEncoding = System.getProperty(TerminalBuilder.PROP_STDERR_ENCODING); + String oldStdinEncoding = System.getProperty("stdin.encoding"); + String oldStdoutEncoding = System.getProperty("stdout.encoding"); + String oldStderrEncoding = System.getProperty("stderr.encoding"); + + try { + // Set both JLine and standard properties with different values + System.setProperty(TerminalBuilder.PROP_STDIN_ENCODING, "ISO-8859-1"); + System.setProperty(TerminalBuilder.PROP_STDOUT_ENCODING, "UTF-16"); + System.setProperty(TerminalBuilder.PROP_STDERR_ENCODING, "US-ASCII"); + System.setProperty("stdin.encoding", "UTF-8"); + System.setProperty("stdout.encoding", "UTF-8"); + System.setProperty("stderr.encoding", "UTF-8"); + + Terminal terminal = TerminalBuilder.builder().dumb(true).build(); + + // JLine properties should take precedence + assertEquals(StandardCharsets.ISO_8859_1, terminal.stdinEncoding()); + assertEquals(StandardCharsets.UTF_16, terminal.stdoutEncoding()); + assertEquals(StandardCharsets.US_ASCII, terminal.stderrEncoding()); + } finally { + // Restore original system properties + if (oldJLineStdinEncoding != null) { + System.setProperty(TerminalBuilder.PROP_STDIN_ENCODING, oldJLineStdinEncoding); + } else { + System.clearProperty(TerminalBuilder.PROP_STDIN_ENCODING); + } + + if (oldJLineStdoutEncoding != null) { + System.setProperty(TerminalBuilder.PROP_STDOUT_ENCODING, oldJLineStdoutEncoding); + } else { + System.clearProperty(TerminalBuilder.PROP_STDOUT_ENCODING); + } + + if (oldJLineStderrEncoding != null) { + System.setProperty(TerminalBuilder.PROP_STDERR_ENCODING, oldJLineStderrEncoding); + } else { + System.clearProperty(TerminalBuilder.PROP_STDERR_ENCODING); + } + + if (oldStdinEncoding != null) { + System.setProperty("stdin.encoding", oldStdinEncoding); + } else { + System.clearProperty("stdin.encoding"); + } + + if (oldStdoutEncoding != null) { + System.setProperty("stdout.encoding", oldStdoutEncoding); + } else { + System.clearProperty("stdout.encoding"); + } + + if (oldStderrEncoding != null) { + System.setProperty("stderr.encoding", oldStderrEncoding); + } else { + System.clearProperty("stderr.encoding"); + } + } + } + + /** + * Test that the compute methods in TerminalBuilder work correctly. + */ + @Test + public void testComputeEncodings() { + TerminalBuilder builder = TerminalBuilder.builder() + .encoding(StandardCharsets.UTF_8) + .stdinEncoding(StandardCharsets.ISO_8859_1) + .stdoutEncoding(StandardCharsets.UTF_16) + .stderrEncoding(StandardCharsets.US_ASCII); + + assertEquals(StandardCharsets.UTF_8, builder.computeEncoding()); + assertEquals(StandardCharsets.ISO_8859_1, builder.computeStdinEncoding()); + assertEquals(StandardCharsets.UTF_16, builder.computeStdoutEncoding()); + assertEquals(StandardCharsets.US_ASCII, builder.computeStderrEncoding()); + } + + /** + * Test that the compute methods in TerminalBuilder fall back correctly. + */ + @Test + public void testComputeEncodingsFallback() { + TerminalBuilder builder = TerminalBuilder.builder().encoding(StandardCharsets.UTF_8); + + String stdin = System.clearProperty("stdin.encoding"); + String stdout = System.clearProperty("stdout.encoding"); + String stderr = System.clearProperty("stderr.encoding"); + try { + assertEquals(StandardCharsets.UTF_8, builder.computeEncoding()); + assertEquals(StandardCharsets.UTF_8, builder.computeStdinEncoding()); + assertEquals(StandardCharsets.UTF_8, builder.computeStdoutEncoding()); + assertEquals(StandardCharsets.UTF_8, builder.computeStderrEncoding()); + } finally { + if (stdin != null) { + System.setProperty("stdin.encoding", stdin); + } + if (stdout != null) { + System.setProperty("stdout.encoding", stdout); + } + if (stderr != null) { + System.setProperty("stderr.encoding", stderr); + } + } + } + + /** + * Helper method to create a test terminal with specific encodings. + */ + private Terminal createTestTerminal( + Charset encoding, Charset stdinEncoding, Charset stdoutEncoding, Charset stderrEncoding) + throws IOException { + + ByteArrayInputStream in = new ByteArrayInputStream(new byte[0]); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + return new DumbTerminal( + null, + null, + "test", + "dumb", + in, + out, + encoding, + stdinEncoding != null ? stdinEncoding : encoding, + stdoutEncoding != null ? stdoutEncoding : encoding, + stderrEncoding != null ? stderrEncoding : encoding, + Terminal.SignalHandler.SIG_DFL); + } +} diff --git a/terminal/src/test/java/org/jline/terminal/MultiEncodingTerminalTest.java b/terminal/src/test/java/org/jline/terminal/MultiEncodingTerminalTest.java new file mode 100644 index 000000000..441fc81c8 --- /dev/null +++ b/terminal/src/test/java/org/jline/terminal/MultiEncodingTerminalTest.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2002-2025, the original author(s). + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.terminal; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; + +import org.jline.terminal.impl.DumbTerminal; +import org.jline.utils.NonBlockingReader; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Tests for terminal functionality with multiple encodings. + */ +public class MultiEncodingTerminalTest { + + /** + * Test reading from stdin with a specific encoding. + */ + @Test + public void testReadWithEncoding() throws IOException { + // Create input with ISO-8859-1 encoded text + String testString = "café"; // é is 0xE9 in ISO-8859-1 + byte[] isoBytes = testString.getBytes(StandardCharsets.ISO_8859_1); + + ByteArrayInputStream in = new ByteArrayInputStream(isoBytes); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + // Create terminal with ISO-8859-1 for stdin + Terminal terminal = new DumbTerminal( + null, + null, + "test", + "dumb", + in, + out, + StandardCharsets.UTF_8, + StandardCharsets.ISO_8859_1, + StandardCharsets.UTF_8, + StandardCharsets.UTF_8, + Terminal.SignalHandler.SIG_DFL); + + // Read characters from the terminal + NonBlockingReader reader = terminal.reader(); + StringBuilder result = new StringBuilder(); + int c; + while ((c = reader.read(1)) != -1) { + result.append((char) c); + } + + // Verify the text was correctly decoded using ISO-8859-1 + assertEquals(testString, result.toString()); + } + + /** + * Test writing to stdout with a specific encoding. + */ + @Test + public void testWriteWithEncoding() throws IOException { + ByteArrayInputStream in = new ByteArrayInputStream(new byte[0]); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + // Create terminal with UTF-16 for stdout + Terminal terminal = new DumbTerminal( + null, + null, + "test", + "dumb", + in, + out, + StandardCharsets.UTF_8, + StandardCharsets.UTF_8, + StandardCharsets.UTF_16, + StandardCharsets.UTF_8, + Terminal.SignalHandler.SIG_DFL); + + // Write a string with non-ASCII characters + String testString = "こんにちは"; // Hello in Japanese + PrintWriter writer = terminal.writer(); + writer.write(testString); + writer.flush(); + + // Verify the output was encoded using UTF-16 + byte[] expectedBytes = testString.getBytes(StandardCharsets.UTF_16); + byte[] actualBytes = out.toByteArray(); + + // UTF-16 includes a BOM (Byte Order Mark) at the beginning + // We need to compare the actual content + String expected = new String(expectedBytes, StandardCharsets.UTF_16); + String actual = new String(actualBytes, StandardCharsets.UTF_16); + + assertEquals(expected, actual); + } + + /** + * Test that different encodings can be used simultaneously. + */ + @Test + public void testMultipleEncodings() throws IOException { + // Create input with ISO-8859-1 encoded text + String inputString = "café"; // é is 0xE9 in ISO-8859-1 + byte[] isoBytes = inputString.getBytes(StandardCharsets.ISO_8859_1); + + ByteArrayInputStream in = new ByteArrayInputStream(isoBytes); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + + // Create terminal with different encodings for each stream + // DumbTerminal doesn't have a constructor that takes an error stream + // So we'll use a regular DumbTerminal and test stdin/stdout only + DumbTerminal terminal = new DumbTerminal( + null, + null, + "test", + "dumb", + in, + out, + StandardCharsets.UTF_8, + StandardCharsets.ISO_8859_1, + StandardCharsets.UTF_16, + StandardCharsets.UTF_8, + Terminal.SignalHandler.SIG_DFL); + + // Read from stdin (ISO-8859-1) + NonBlockingReader reader = terminal.reader(); + StringBuilder result = new StringBuilder(); + int c; + while ((c = reader.read(1)) != -1) { + result.append((char) c); + } + + // Write to stdout (UTF-16) + String outputString = "こんにちは"; // Hello in Japanese + terminal.writer().write(outputString); + terminal.writer().flush(); + + // Verify stdin was correctly decoded using ISO-8859-1 + assertEquals(inputString, result.toString()); + + // Verify stdout was correctly encoded using UTF-16 + String outputResult = new String(out.toByteArray(), StandardCharsets.UTF_16); + assertEquals(outputString, outputResult); + } +}