Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
*
* @author Phillip Webb
* @author Yong-Hyun Kim
* @author Philemon Hilscher
* @since 1.0.0
*/
public abstract class AnsiOutput {
Expand All @@ -43,7 +44,7 @@ public abstract class AnsiOutput {

private static @Nullable Boolean ansiCapable;

private static final String OPERATING_SYSTEM_NAME = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
private static final String OPERATING_SYSTEM_NAME = System.getProperty("os.name");

private static final String ENCODE_START = "\033[";

Expand Down Expand Up @@ -171,13 +172,36 @@ private static boolean detectIfAnsiCapable() {
}
}
}
return !(OPERATING_SYSTEM_NAME.contains("win"));
if (isWindows(OPERATING_SYSTEM_NAME)) {
Integer windowsVersion = parseWindowsMajorVersion(OPERATING_SYSTEM_NAME);
return isWindowsAnsiCapable(windowsVersion);
}
return true;
}
catch (Throwable ex) {
return false;
}
}

static boolean isWindows(String osName) {
return osName.toLowerCase(Locale.ENGLISH).contains("win");
}

static Integer parseWindowsMajorVersion(String osName) {
String[] osNameParts = osName.split("\\s+");
int versionIndex = osNameParts.length == 2 ? 1 : 2;
String plainVersion = osNameParts[versionIndex];
if (plainVersion.contains(".")) {
return Integer.parseInt(plainVersion.substring(0, plainVersion.indexOf(".")));
}
return Integer.parseInt(plainVersion);
}

static boolean isWindowsAnsiCapable(Integer windowsMajorVersion) {
// Windows 11 and Windows Server 2025 or newer systems support ANSI escape sequences
return windowsMajorVersion >= 11 && windowsMajorVersion < 95 || windowsMajorVersion >= 2025;
}

/**
* Possible values to pass to {@link AnsiOutput#setEnabled}. Determines when to output
* ANSI escape sequences for coloring application output.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,25 @@

package org.springframework.boot.ansi;

import java.util.stream.Stream;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import org.springframework.boot.ansi.AnsiOutput.Enabled;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;

/**
* Tests for {@link AnsiOutput}.
*
* @author Phillip Webb
* @author Philemon Hilscher
*/
class AnsiOutputTests {

Expand All @@ -48,4 +55,81 @@ void encoding() {
assertThat(encoded).isEqualTo("ABDEF");
}

private static Stream<Arguments> provideOsNames() {
return Stream.of(
Arguments.of("", false),
Arguments.of("Windows NT 3.51", true),
Arguments.of("Windows 7", true),
Arguments.of("Windows 8", true),
Arguments.of("Windows 8.1", true),
Arguments.of("Windows 10", true),
Arguments.of("Windows 11", true),
Arguments.of("Windows Server 2025", true),
Arguments.of("Linux", false),
Arguments.of("Mac OS X", false),
Arguments.of("Mac OS", false)
);
}

@ParameterizedTest
@MethodSource("provideOsNames")
void testDetectIfIsWindows(String osName, boolean expected) {
boolean actual = AnsiOutput.isWindows(osName);
assertEquals(expected, actual);
}

private static Stream<Arguments> provideWindowsMajorVersions() {
return Stream.of(
Arguments.of(3, false),
Arguments.of(95, false),
Arguments.of(4, false),
Arguments.of(2000, false),
Arguments.of(7, false),
Arguments.of(2008, false),
Arguments.of(8, false),
Arguments.of(2012, false),
Arguments.of(10, false),
Arguments.of(2016, false),
Arguments.of(2019, false),
Arguments.of(2022, false),
Arguments.of(11, true),
Arguments.of(2025, true)
);
}

@ParameterizedTest
@MethodSource("provideWindowsMajorVersions")
void testDetectIfIsWindowsAnsiCapable(Integer majorVersion, boolean expected) {
boolean actual = AnsiOutput.isWindowsAnsiCapable(majorVersion);
assertEquals(expected, actual);
}

private static Stream<Arguments> provideWindowsOsNames() {
return Stream.of(
Arguments.of("Windows NT 3.51", 3),
Arguments.of("Windows 95", 95),
Arguments.of("Windows NT 4.0", 4),
Arguments.of("Windows 2000", 2000),
Arguments.of("Windows 7", 7),
Arguments.of("Windows Server 2008 R2", 2008),
Arguments.of("Windows 8", 8),
Arguments.of("Windows Server 2012", 2012),
Arguments.of("Windows 8.1", 8),
Arguments.of("Windows Server 2012 R2", 2012),
Arguments.of("Windows 10", 10),
Arguments.of("Windows Server 2016", 2016),
Arguments.of("Windows Server 2019", 2019),
Arguments.of("Windows Server 2022", 2022),
Arguments.of("Windows 11", 11),
Arguments.of("Windows Server 2025", 2025)
);
}

@ParameterizedTest
@MethodSource("provideWindowsOsNames")
void testParseWindowsMajorVersion(String osName, Integer expected) {
Integer parsed = AnsiOutput.parseWindowsMajorVersion(osName);
assertEquals(expected, parsed);
}

}