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 handlers = new ConcurrentHashMap<>(); protected final Set bools = new HashSet<>(); protected final Map ints = new HashMap<>(); @@ -88,9 +91,25 @@ public AbstractTerminal(String name, String type) throws IOException { @SuppressWarnings("this-escape") public AbstractTerminal(String name, String type, Charset encoding, SignalHandler signalHandler) throws IOException { + this(name, type, encoding, encoding, encoding, encoding, signalHandler); + } + + @SuppressWarnings("this-escape") + public AbstractTerminal( + String name, + String type, + Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, + SignalHandler signalHandler) + throws IOException { this.name = name; this.type = type != null ? type : "ansi"; this.encoding = encoding != null ? encoding : Charset.defaultCharset(); + this.stdinEncoding = stdinEncoding != null ? stdinEncoding : this.encoding; + this.stdoutEncoding = stdoutEncoding != null ? stdoutEncoding : this.encoding; + this.stderrEncoding = stderrEncoding != null ? stderrEncoding : this.encoding; this.palette = new ColorPalette(this); for (Signal signal : Signal.values()) { handlers.put(signal, signalHandler); @@ -209,6 +228,21 @@ public Charset encoding() { return this.encoding; } + @Override + public Charset stdinEncoding() { + return this.stdinEncoding; + } + + @Override + public Charset stdoutEncoding() { + return this.stdoutEncoding; + } + + @Override + public Charset stderrEncoding() { + return this.stderrEncoding; + } + public void flush() { writer().flush(); } 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 8dd4cfd59..a2e43e0ea 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/AbstractWindowsTerminal.java +++ b/terminal/src/main/java/org/jline/terminal/impl/AbstractWindowsTerminal.java @@ -146,15 +146,51 @@ public AbstractWindowsTerminal( Console outConsole, int outConsoleMode) throws IOException { - super(name, type, encoding, signalHandler); + this( + provider, + systemStream, + writer, + name, + type, + encoding, + encoding, + encoding, + encoding, + nativeSignals, + signalHandler, + inConsole, + inConsoleMode, + outConsole, + outConsoleMode); + } + + @SuppressWarnings("this-escape") + public AbstractWindowsTerminal( + TerminalProvider provider, + SystemStream systemStream, + Writer writer, + String name, + String type, + Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, + boolean nativeSignals, + SignalHandler signalHandler, + Console inConsole, + int inConsoleMode, + Console outConsole, + int outConsoleMode) + throws IOException { + super(name, type, encoding, stdinEncoding, stdoutEncoding, stderrEncoding, signalHandler); this.provider = provider; this.systemStream = systemStream; NonBlockingPumpReader reader = NonBlocking.nonBlockingPumpReader(); this.slaveInputPipe = reader.getWriter(); this.reader = reader; - this.input = NonBlocking.nonBlockingStream(reader, encoding()); + this.input = NonBlocking.nonBlockingStream(reader, stdinEncoding()); this.writer = new PrintWriter(writer); - this.output = new WriterOutputStream(writer, encoding()); + this.output = new WriterOutputStream(writer, stdoutEncoding()); this.inConsole = inConsole; this.outConsole = outConsole; parseInfoCmp(); diff --git a/terminal/src/main/java/org/jline/terminal/impl/Diag.java b/terminal/src/main/java/org/jline/terminal/impl/Diag.java index 5f55280d8..342bab92d 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/Diag.java +++ b/terminal/src/main/java/org/jline/terminal/impl/Diag.java @@ -195,6 +195,9 @@ private void testProvider(TerminalProvider provider) { "xterm", false, StandardCharsets.UTF_8, + StandardCharsets.UTF_8, + StandardCharsets.UTF_8, + StandardCharsets.UTF_8, false, Terminal.SignalHandler.SIG_DFL, false, 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 a127228f1..8dbd88204 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/DumbTerminal.java +++ b/terminal/src/main/java/org/jline/terminal/impl/DumbTerminal.java @@ -91,7 +91,24 @@ public DumbTerminal( Charset encoding, SignalHandler signalHandler) throws IOException { - super(name, type, encoding, signalHandler); + this(provider, systemStream, name, type, in, out, encoding, encoding, encoding, encoding, signalHandler); + } + + @SuppressWarnings("this-escape") + public DumbTerminal( + TerminalProvider provider, + SystemStream systemStream, + String name, + String type, + InputStream in, + OutputStream out, + Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, + SignalHandler signalHandler) + throws IOException { + super(name, type, encoding, stdinEncoding, stdoutEncoding, stderrEncoding, signalHandler); this.provider = provider; this.systemStream = systemStream; NonBlockingInputStream nbis = NonBlocking.nonBlocking(getName(), in); @@ -142,8 +159,8 @@ public int read(long timeout, boolean isPeek) throws IOException { } }; this.output = out; - this.reader = NonBlocking.nonBlocking(getName(), input, encoding()); - this.writer = new PrintWriter(new OutputStreamWriter(output, encoding())); + this.reader = NonBlocking.nonBlocking(getName(), input, stdinEncoding()); + this.writer = new PrintWriter(new OutputStreamWriter(output, stdoutEncoding())); this.attributes = new Attributes(); this.attributes.setControlChar(ControlChar.VERASE, (char) 127); this.attributes.setControlChar(ControlChar.VWERASE, (char) 23); diff --git a/terminal/src/main/java/org/jline/terminal/impl/DumbTerminalProvider.java b/terminal/src/main/java/org/jline/terminal/impl/DumbTerminalProvider.java index 00c4e8bd3..0af348746 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/DumbTerminalProvider.java +++ b/terminal/src/main/java/org/jline/terminal/impl/DumbTerminalProvider.java @@ -64,6 +64,9 @@ public Terminal sysTerminal( String type, boolean ansiPassThrough, Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, boolean nativeSignals, Terminal.SignalHandler signalHandler, boolean paused, @@ -77,6 +80,9 @@ public Terminal sysTerminal( new FileInputStream(FileDescriptor.in), new FileOutputStream(systemStream == SystemStream.Error ? FileDescriptor.err : FileDescriptor.out), encoding, + stdinEncoding, + stdoutEncoding, + stderrEncoding, signalHandler); } @@ -87,6 +93,9 @@ public 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/main/java/org/jline/terminal/impl/ExternalTerminal.java b/terminal/src/main/java/org/jline/terminal/impl/ExternalTerminal.java index 1eaed4f93..fd06d9385 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/ExternalTerminal.java +++ b/terminal/src/main/java/org/jline/terminal/impl/ExternalTerminal.java @@ -69,7 +69,17 @@ public class ExternalTerminal extends LineDisciplineTerminal { public ExternalTerminal( String name, String type, InputStream masterInput, OutputStream masterOutput, Charset encoding) throws IOException { - this(null, name, type, masterInput, masterOutput, encoding, SignalHandler.SIG_DFL); + this( + null, + name, + type, + masterInput, + masterOutput, + encoding, + encoding, + encoding, + encoding, + SignalHandler.SIG_DFL); } public ExternalTerminal( @@ -81,7 +91,18 @@ public ExternalTerminal( Charset encoding, SignalHandler signalHandler) throws IOException { - this(provider, name, type, masterInput, masterOutput, encoding, signalHandler, false); + this( + provider, + name, + type, + masterInput, + masterOutput, + encoding, + encoding, + encoding, + encoding, + signalHandler, + false); } public ExternalTerminal( @@ -91,10 +112,107 @@ public ExternalTerminal( InputStream masterInput, OutputStream masterOutput, Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, + SignalHandler signalHandler) + throws IOException { + this( + provider, + name, + type, + masterInput, + masterOutput, + encoding, + stdinEncoding, + stdoutEncoding, + stderrEncoding, + signalHandler, + false); + } + + public ExternalTerminal( + TerminalProvider provider, + String name, + String type, + InputStream masterInput, + OutputStream masterOutput, + Charset encoding, + SignalHandler signalHandler, + boolean paused) + throws IOException { + this( + provider, + name, + type, + masterInput, + masterOutput, + encoding, + encoding, + encoding, + encoding, + signalHandler, + paused, + null, + null); + } + + public ExternalTerminal( + TerminalProvider provider, + String name, + String type, + InputStream masterInput, + OutputStream masterOutput, + Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, SignalHandler signalHandler, boolean paused) throws IOException { - this(provider, name, type, masterInput, masterOutput, encoding, signalHandler, paused, null, null); + this( + provider, + name, + type, + masterInput, + masterOutput, + encoding, + stdinEncoding, + stdoutEncoding, + stderrEncoding, + signalHandler, + paused, + null, + null); + } + + @SuppressWarnings("this-escape") + public ExternalTerminal( + TerminalProvider provider, + String name, + String type, + InputStream masterInput, + OutputStream masterOutput, + Charset encoding, + SignalHandler signalHandler, + boolean paused, + Attributes attributes, + Size size) + throws IOException { + this( + provider, + name, + type, + masterInput, + masterOutput, + encoding, + encoding, + encoding, + encoding, + signalHandler, + paused, + attributes, + size); } @SuppressWarnings("this-escape") @@ -105,12 +223,15 @@ public ExternalTerminal( InputStream masterInput, OutputStream masterOutput, Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, SignalHandler signalHandler, boolean paused, Attributes attributes, Size size) throws IOException { - super(name, type, masterOutput, encoding, signalHandler); + super(name, type, masterOutput, encoding, stdinEncoding, stdoutEncoding, stderrEncoding, signalHandler); this.provider = provider; this.masterInput = masterInput; if (attributes != null) { 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 7c24ae285..bb55f1e7a 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/LineDisciplineTerminal.java +++ b/terminal/src/main/java/org/jline/terminal/impl/LineDisciplineTerminal.java @@ -121,13 +121,27 @@ public LineDisciplineTerminal(String name, String type, OutputStream masterOutpu public LineDisciplineTerminal( String name, String type, OutputStream masterOutput, Charset encoding, SignalHandler signalHandler) throws IOException { - super(name, type, encoding, signalHandler); + this(name, type, masterOutput, encoding, encoding, encoding, encoding, signalHandler); + } + + @SuppressWarnings("this-escape") + public LineDisciplineTerminal( + String name, + String type, + OutputStream masterOutput, + Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, + SignalHandler signalHandler) + throws IOException { + super(name, type, encoding, stdinEncoding, stdoutEncoding, stderrEncoding, signalHandler); NonBlockingPumpInputStream input = NonBlocking.nonBlockingPumpInputStream(PIPE_SIZE); this.slaveInputPipe = input.getOutputStream(); this.slaveInput = input; - this.slaveReader = NonBlocking.nonBlocking(getName(), slaveInput, encoding()); + this.slaveReader = NonBlocking.nonBlocking(getName(), slaveInput, stdinEncoding()); this.slaveOutput = new FilteringOutputStream(); - this.slaveWriter = new PrintWriter(new OutputStreamWriter(slaveOutput, encoding())); + this.slaveWriter = new PrintWriter(new OutputStreamWriter(slaveOutput, stdoutEncoding())); this.masterOutput = masterOutput; this.attributes = getDefaultTerminalAttributes(); this.size = new Size(160, 50); diff --git a/terminal/src/main/java/org/jline/terminal/impl/PosixPtyTerminal.java b/terminal/src/main/java/org/jline/terminal/impl/PosixPtyTerminal.java index 973d2a670..51510435e 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/PosixPtyTerminal.java +++ b/terminal/src/main/java/org/jline/terminal/impl/PosixPtyTerminal.java @@ -96,15 +96,32 @@ public PosixPtyTerminal( SignalHandler signalHandler, boolean paused) throws IOException { - super(name, type, pty, encoding, signalHandler); + this(name, type, pty, in, out, encoding, encoding, encoding, encoding, signalHandler, paused); + } + + @SuppressWarnings("this-escape") + public PosixPtyTerminal( + String name, + String type, + Pty pty, + InputStream in, + OutputStream out, + Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, + SignalHandler signalHandler, + boolean paused) + throws IOException { + super(name, type, pty, encoding, stdinEncoding, stdoutEncoding, stderrEncoding, signalHandler); this.in = Objects.requireNonNull(in); this.out = Objects.requireNonNull(out); this.masterInput = pty.getMasterInput(); this.masterOutput = pty.getMasterOutput(); this.input = new InputStreamWrapper(NonBlocking.nonBlocking(name, pty.getSlaveInput())); this.output = pty.getSlaveOutput(); - this.reader = NonBlocking.nonBlocking(name, input, encoding()); - this.writer = new PrintWriter(new OutputStreamWriter(output, encoding())); + this.reader = NonBlocking.nonBlocking(name, input, stdinEncoding()); + this.writer = new PrintWriter(new OutputStreamWriter(output, stdoutEncoding())); parseInfoCmp(); if (!paused) { resume(); diff --git a/terminal/src/main/java/org/jline/terminal/impl/PosixSysTerminal.java b/terminal/src/main/java/org/jline/terminal/impl/PosixSysTerminal.java index 61ec04d26..0cda74ef0 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/PosixSysTerminal.java +++ b/terminal/src/main/java/org/jline/terminal/impl/PosixSysTerminal.java @@ -69,11 +69,26 @@ public class PosixSysTerminal extends AbstractPosixTerminal { public PosixSysTerminal( String name, String type, Pty pty, Charset encoding, boolean nativeSignals, SignalHandler signalHandler) throws IOException { - super(name, type, pty, encoding, signalHandler); + this(name, type, pty, encoding, encoding, encoding, encoding, nativeSignals, signalHandler); + } + + @SuppressWarnings("this-escape") + public PosixSysTerminal( + String name, + String type, + Pty pty, + Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, + boolean nativeSignals, + SignalHandler signalHandler) + throws IOException { + super(name, type, pty, encoding, stdinEncoding, stdoutEncoding, stderrEncoding, signalHandler); this.input = NonBlocking.nonBlocking(getName(), pty.getSlaveInput()); this.output = new FastBufferedOutputStream(pty.getSlaveOutput()); - this.reader = NonBlocking.nonBlocking(getName(), input, encoding()); - this.writer = new PrintWriter(new OutputStreamWriter(output, encoding())); + this.reader = NonBlocking.nonBlocking(getName(), input, stdinEncoding()); + this.writer = new PrintWriter(new OutputStreamWriter(output, stdoutEncoding())); parseInfoCmp(); if (nativeSignals) { for (final Signal signal : Signal.values()) { diff --git a/terminal/src/main/java/org/jline/terminal/impl/exec/ExecTerminalProvider.java b/terminal/src/main/java/org/jline/terminal/impl/exec/ExecTerminalProvider.java index 6bfcd3541..bf71048dd 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/exec/ExecTerminalProvider.java +++ b/terminal/src/main/java/org/jline/terminal/impl/exec/ExecTerminalProvider.java @@ -127,6 +127,9 @@ public Terminal sysTerminal( String type, boolean ansiPassThrough, Charset encoding, + Charset stdinEncoding, + Charset stdoutEncoding, + Charset stderrEncoding, boolean nativeSignals, Terminal.SignalHandler signalHandler, boolean paused, @@ -134,10 +137,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); } } @@ -176,9 +199,45 @@ 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 { if (OSUtils.IS_CYGWIN || OSUtils.IS_MSYSTEM) { 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); } else { return null; } @@ -214,8 +273,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); } /** @@ -247,12 +334,28 @@ 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 { - return new ExternalTerminal(this, name, type, in, out, encoding, signalHandler, paused, attributes, size); + return new ExternalTerminal( + this, + name, + type, + in, + out, + encoding, + stdinEncoding, + stdoutEncoding, + stderrEncoding, + signalHandler, + paused, + attributes, + size); } /** diff --git a/terminal/src/main/java/org/jline/terminal/spi/TerminalProvider.java b/terminal/src/main/java/org/jline/terminal/spi/TerminalProvider.java index 2c1a2fd32..8ac8a0bd8 100644 --- a/terminal/src/main/java/org/jline/terminal/spi/TerminalProvider.java +++ b/terminal/src/main/java/org/jline/terminal/spi/TerminalProvider.java @@ -83,12 +83,65 @@ public interface TerminalProvider { * @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 + * @deprecated Use {@link #sysTerminal(String, String, boolean, Charset, Charset, Charset, Charset, boolean, Terminal.SignalHandler, boolean, SystemStream)} instead + */ + @Deprecated + default Terminal sysTerminal( + String name, + String type, + boolean ansiPassThrough, + Charset encoding, + boolean nativeSignals, + Terminal.SignalHandler signalHandler, + boolean paused, + SystemStream systemStream) + throws IOException { + return sysTerminal( + name, + type, + ansiPassThrough, + encoding, + encoding, + encoding, + encoding, + nativeSignals, + signalHandler, + paused, + systemStream); + } + + /** + * Creates a terminal connected to a system stream. + * + *

+ * 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); + } +}