This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven.git


The following commit(s) were added to refs/heads/master by this push:
     new 782e8679bf [MNG-7995] Switch to JLine to provide line editing (#1279)
782e8679bf is described below

commit 782e8679bf487bcd6ad6ef36d1d1e92b00d418a4
Author: Guillaume Nodet <gno...@gmail.com>
AuthorDate: Mon Jan 8 11:37:09 2024 +0100

    [MNG-7995] Switch to JLine to provide line editing (#1279)
---
 apache-maven/pom.xml                               |   4 +-
 .../licenses/unrecognized-jline-3.25.0.txt         |  35 +
 .../apache/maven/api/services/MessageBuilder.java  | 107 ++-
 .../maven/api/services/MessageBuilderFactory.java  |  12 +-
 .../maven/internal/impl/DefaultMessageBuilder.java | 100 +--
 .../impl/DefaultMessageBuilderFactory.java         |   6 +-
 .../main/resources/META-INF/maven/extension.xml    |   4 +
 maven-embedder/pom.xml                             |  19 +-
 .../main/java/org/apache/maven/cli/CLIManager.java |   2 +-
 .../org/apache/maven/cli/CLIReportingUtils.java    |   2 +-
 .../main/java/org/apache/maven/cli/MavenCli.java   |   8 +-
 .../maven/cli/jansi/JansiMessageBuilder.java       | 171 ----
 .../cli/jansi/JansiMessageBuilderFactory.java      |  55 --
 .../org/apache/maven/cli/jansi/MessageUtils.java   | 201 -----
 .../java/org/apache/maven/cli/jansi/Style.java     | 148 ----
 .../cli/jline/JLineMessageBuilderFactory.java      | 294 +++++++
 .../org/apache/maven/cli/jline/MessageUtils.java   | 101 +++
 .../transfer/AbstractMavenTransferListener.java    |  45 +-
 .../cli/transfer/ConsoleMavenTransferListener.java |   6 +-
 .../apache/maven/cli/transfer/FileSizeFormat.java  |  34 +
 .../src/main/java/org/fusesource/jansi/Ansi.java   | 952 +++++++++++++++++++++
 .../java/org/apache/maven/cli/MavenCliTest.java    |   8 +-
 .../maven/cli/event/ExecutionEventLoggerTest.java  |   6 +-
 .../transfer/ConsoleMavenTransferListenerTest.java |   2 +
 .../java/org/slf4j/impl/MavenSimpleLogger.java     |   2 +-
 .../java/org/slf4j/impl/MavenSimpleLoggerTest.java |  16 +
 pom.xml                                            |  17 +-
 27 files changed, 1601 insertions(+), 756 deletions(-)

diff --git a/apache-maven/pom.xml b/apache-maven/pom.xml
index 5f47f9dc57..eb198048e4 100644
--- a/apache-maven/pom.xml
+++ b/apache-maven/pom.xml
@@ -98,8 +98,8 @@ under the License.
       <artifactId>maven-slf4j-provider</artifactId>
     </dependency>
     <dependency>
-      <groupId>org.fusesource.jansi</groupId>
-      <artifactId>jansi</artifactId>
+      <groupId>org.jline</groupId>
+      <artifactId>jline</artifactId>
     </dependency>
 
     <!-- DI Runtime -->
diff --git 
a/apache-maven/src/main/appended-resources/licenses/unrecognized-jline-3.25.0.txt
 
b/apache-maven/src/main/appended-resources/licenses/unrecognized-jline-3.25.0.txt
new file mode 100644
index 0000000000..b62fe45716
--- /dev/null
+++ 
b/apache-maven/src/main/appended-resources/licenses/unrecognized-jline-3.25.0.txt
@@ -0,0 +1,35 @@
+Copyright (c) 2002-2023, the original author or authors.
+All rights reserved.
+
+https://opensource.org/licenses/BSD-3-Clause
+
+Redistribution and use in source and binary forms, with or
+without modification, are permitted provided that the following
+conditions are met:
+
+Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with
+the distribution.
+
+Neither the name of JLine nor the names of its contributors
+may be used to endorse or promote products derived from this
+software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git 
a/api/maven-api-core/src/main/java/org/apache/maven/api/services/MessageBuilder.java
 
b/api/maven-api-core/src/main/java/org/apache/maven/api/services/MessageBuilder.java
index d00ad8d954..8a996032e2 100644
--- 
a/api/maven-api-core/src/main/java/org/apache/maven/api/services/MessageBuilder.java
+++ 
b/api/maven-api-core/src/main/java/org/apache/maven/api/services/MessageBuilder.java
@@ -26,7 +26,7 @@ import org.apache.maven.api.annotations.Nonnull;
  * @since 4.0.0
  * @see MessageBuilderFactory
  */
-public interface MessageBuilder {
+public interface MessageBuilder extends Appendable {
 
     /**
      * Append message content in trace style.
@@ -36,7 +36,9 @@ public interface MessageBuilder {
      * @return the current builder
      */
     @Nonnull
-    MessageBuilder trace(Object message);
+    default MessageBuilder trace(Object message) {
+        return style(".trace:-bold,f:magenta", message);
+    }
 
     /**
      * Append message content in debug style.
@@ -46,7 +48,9 @@ public interface MessageBuilder {
      * @return the current builder
      */
     @Nonnull
-    MessageBuilder debug(Object message);
+    default MessageBuilder debug(Object message) {
+        return style(".debug:-bold,f:cyan", message);
+    }
 
     /**
      * Append message content in info style.
@@ -56,7 +60,9 @@ public interface MessageBuilder {
      * @return the current builder
      */
     @Nonnull
-    MessageBuilder info(Object message);
+    default MessageBuilder info(Object message) {
+        return style(".info:-bold,f:blue", message);
+    }
 
     /**
      * Append message content in warning style.
@@ -66,7 +72,9 @@ public interface MessageBuilder {
      * @return the current builder
      */
     @Nonnull
-    MessageBuilder warning(Object message);
+    default MessageBuilder warning(Object message) {
+        return style(".warning:-bold,f:yellow", message);
+    }
 
     /**
      * Append message content in error style.
@@ -76,7 +84,9 @@ public interface MessageBuilder {
      * @return the current builder
      */
     @Nonnull
-    MessageBuilder error(Object message);
+    default MessageBuilder error(Object message) {
+        return style(".error:-bold,f:red", message);
+    }
 
     /**
      * Append message content in success style.
@@ -86,7 +96,9 @@ public interface MessageBuilder {
      * @return the current builder
      */
     @Nonnull
-    MessageBuilder success(Object message);
+    default MessageBuilder success(Object message) {
+        return style(".success:-bold,f:green", message);
+    }
 
     /**
      * Append message content in failure style.
@@ -96,7 +108,9 @@ public interface MessageBuilder {
      * @return the current builder
      */
     @Nonnull
-    MessageBuilder failure(Object message);
+    default MessageBuilder failure(Object message) {
+        return style(".failure:-bold,f:red", message);
+    }
 
     /**
      * Append message content in strong style.
@@ -106,7 +120,9 @@ public interface MessageBuilder {
      * @return the current builder
      */
     @Nonnull
-    MessageBuilder strong(Object message);
+    default MessageBuilder strong(Object message) {
+        return style(".strong:-bold", message);
+    }
 
     /**
      * Append message content in mojo style.
@@ -116,7 +132,9 @@ public interface MessageBuilder {
      * @return the current builder
      */
     @Nonnull
-    MessageBuilder mojo(Object message);
+    default MessageBuilder mojo(Object message) {
+        return style(".mojo:-f:green", message);
+    }
 
     /**
      * Append message content in project style.
@@ -126,11 +144,35 @@ public interface MessageBuilder {
      * @return the current builder
      */
     @Nonnull
-    MessageBuilder project(Object message);
+    default MessageBuilder project(Object message) {
+        return style(".project:-f:cyan", message);
+    }
+
+    @Nonnull
+    default MessageBuilder style(String style, Object message) {
+        return style(style).a(message).resetStyle();
+    }
+
+    MessageBuilder style(String style);
+
+    MessageBuilder resetStyle();
 
     //
     // message building methods modelled after Ansi methods
     //
+
+    @Nonnull
+    @Override
+    MessageBuilder append(CharSequence cs);
+
+    @Nonnull
+    @Override
+    MessageBuilder append(CharSequence cs, int start, int end);
+
+    @Nonnull
+    @Override
+    MessageBuilder append(char c);
+
     /**
      * Append content to the message buffer.
      *
@@ -140,7 +182,9 @@ public interface MessageBuilder {
      * @return the current builder
      */
     @Nonnull
-    MessageBuilder a(char[] value, int offset, int len);
+    default MessageBuilder a(char[] value, int offset, int len) {
+        return append(String.valueOf(value, offset, len));
+    }
 
     /**
      * Append content to the message buffer.
@@ -149,7 +193,9 @@ public interface MessageBuilder {
      * @return the current builder
      */
     @Nonnull
-    MessageBuilder a(char[] value);
+    default MessageBuilder a(char[] value) {
+        return append(String.valueOf(value));
+    }
 
     /**
      * Append content to the message buffer.
@@ -160,7 +206,9 @@ public interface MessageBuilder {
      * @return the current builder
      */
     @Nonnull
-    MessageBuilder a(CharSequence value, int start, int end);
+    default MessageBuilder a(CharSequence value, int start, int end) {
+        return append(value, start, end);
+    }
 
     /**
      * Append content to the message buffer.
@@ -169,7 +217,9 @@ public interface MessageBuilder {
      * @return the current builder
      */
     @Nonnull
-    MessageBuilder a(CharSequence value);
+    default MessageBuilder a(CharSequence value) {
+        return append(value);
+    }
 
     /**
      * Append content to the message buffer.
@@ -178,7 +228,9 @@ public interface MessageBuilder {
      * @return the current builder
      */
     @Nonnull
-    MessageBuilder a(Object value);
+    default MessageBuilder a(Object value) {
+        return append(String.valueOf(value));
+    }
 
     /**
      * Append newline to the message buffer.
@@ -186,7 +238,9 @@ public interface MessageBuilder {
      * @return the current builder
      */
     @Nonnull
-    MessageBuilder newline();
+    default MessageBuilder newline() {
+        return append(System.lineSeparator());
+    }
 
     /**
      * Append formatted content to the buffer.
@@ -197,20 +251,23 @@ public interface MessageBuilder {
      * @return the current builder
      */
     @Nonnull
-    MessageBuilder format(String pattern, Object... args);
+    default MessageBuilder format(String pattern, Object... args) {
+        return append(String.format(pattern, args));
+    }
 
     /**
-     * Return the built message.
+     * Set the buffer length.
      *
-     * @return the message
+     * @param length the new length
+     * @return the current builder
      */
-    @Nonnull
-    String build();
+    MessageBuilder setLength(int length);
 
     /**
-     * Set the buffer length.
+     * Return the built message.
      *
-     * @param length the new length
+     * @return the message
      */
-    void setLength(int length);
+    @Nonnull
+    String build();
 }
diff --git 
a/api/maven-api-core/src/main/java/org/apache/maven/api/services/MessageBuilderFactory.java
 
b/api/maven-api-core/src/main/java/org/apache/maven/api/services/MessageBuilderFactory.java
index ac4691ecfb..9a595ae110 100644
--- 
a/api/maven-api-core/src/main/java/org/apache/maven/api/services/MessageBuilderFactory.java
+++ 
b/api/maven-api-core/src/main/java/org/apache/maven/api/services/MessageBuilderFactory.java
@@ -48,21 +48,11 @@ public interface MessageBuilderFactory extends Service {
     @Nonnull
     MessageBuilder builder();
 
-    /**
-     * Creates a new message builder backed by the given string builder.
-     * @param stringBuilder a string builder
-     * @return a new message builder
-     */
-    @Nonnull
-    MessageBuilder builder(@Nonnull StringBuilder stringBuilder);
-
     /**
      * Creates a new message builder of the specified size.
      * @param size the initial size of the message builder buffer
      * @return a new message builder
      */
     @Nonnull
-    default MessageBuilder builder(int size) {
-        return builder(new StringBuilder(size));
-    }
+    MessageBuilder builder(int size);
 }
diff --git 
a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultMessageBuilder.java
 
b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultMessageBuilder.java
index 08f28d9553..692b1e37e5 100644
--- 
a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultMessageBuilder.java
+++ 
b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultMessageBuilder.java
@@ -36,111 +36,36 @@ public class DefaultMessageBuilder implements 
MessageBuilder {
     }
 
     @Override
-    @Nonnull
-    public MessageBuilder trace(Object o) {
-        return a(o);
-    }
-
-    @Override
-    @Nonnull
-    public MessageBuilder debug(Object o) {
-        return a(o);
-    }
-
-    @Override
-    @Nonnull
-    public MessageBuilder info(Object o) {
-        return a(o);
-    }
-
-    @Override
-    @Nonnull
-    public MessageBuilder warning(Object o) {
-        return a(o);
-    }
-
-    @Override
-    @Nonnull
-    public MessageBuilder error(Object o) {
-        return a(o);
-    }
-
-    @Override
-    @Nonnull
-    public MessageBuilder success(Object o) {
-        return a(o);
-    }
-
-    @Override
-    @Nonnull
-    public MessageBuilder failure(Object o) {
-        return a(o);
-    }
-
-    @Override
-    @Nonnull
-    public MessageBuilder strong(Object o) {
-        return a(o);
-    }
-
-    @Override
-    @Nonnull
-    public MessageBuilder mojo(Object o) {
-        return a(o);
-    }
-
-    @Override
-    @Nonnull
-    public MessageBuilder project(Object o) {
-        return a(o);
-    }
-
-    @Override
-    @Nonnull
-    public MessageBuilder a(char[] chars, int i, int i1) {
-        buffer.append(chars, i, i1);
-        return this;
-    }
-
-    @Override
-    @Nonnull
-    public MessageBuilder a(char[] chars) {
-        buffer.append(chars);
+    public MessageBuilder style(String style) {
         return this;
     }
 
     @Override
-    @Nonnull
-    public MessageBuilder a(CharSequence charSequence, int i, int i1) {
-        buffer.append(charSequence, i, i1);
+    public MessageBuilder resetStyle() {
         return this;
     }
 
     @Override
-    @Nonnull
-    public MessageBuilder a(CharSequence charSequence) {
-        buffer.append(charSequence);
+    public MessageBuilder append(CharSequence cs) {
+        buffer.append(cs);
         return this;
     }
 
     @Override
-    @Nonnull
-    public MessageBuilder a(Object o) {
-        buffer.append(o);
+    public MessageBuilder append(CharSequence cs, int start, int end) {
+        buffer.append(cs, start, end);
         return this;
     }
 
     @Override
-    @Nonnull
-    public MessageBuilder newline() {
-        buffer.append(System.getProperty("line.separator"));
+    public MessageBuilder append(char c) {
+        buffer.append(c);
         return this;
     }
 
     @Override
-    @Nonnull
-    public MessageBuilder format(String s, Object... objects) {
-        buffer.append(String.format(s, objects));
+    public MessageBuilder setLength(int length) {
+        buffer.setLength(length);
         return this;
     }
 
@@ -154,9 +79,4 @@ public class DefaultMessageBuilder implements MessageBuilder 
{
     public String toString() {
         return build();
     }
-
-    @Override
-    public void setLength(int length) {
-        buffer.setLength(length);
-    }
 }
diff --git 
a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultMessageBuilderFactory.java
 
b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultMessageBuilderFactory.java
index ea4c94f492..bb2f6c982e 100644
--- 
a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultMessageBuilderFactory.java
+++ 
b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultMessageBuilderFactory.java
@@ -22,8 +22,6 @@ import javax.inject.Inject;
 import javax.inject.Named;
 import javax.inject.Singleton;
 
-import java.util.Objects;
-
 import org.apache.maven.api.annotations.Experimental;
 import org.apache.maven.api.annotations.Nonnull;
 import org.apache.maven.api.services.MessageBuilder;
@@ -57,7 +55,7 @@ public class DefaultMessageBuilderFactory implements 
MessageBuilderFactory {
 
     @Override
     @Nonnull
-    public MessageBuilder builder(@Nonnull StringBuilder stringBuilder) {
-        return new 
DefaultMessageBuilder(Objects.requireNonNull(stringBuilder));
+    public MessageBuilder builder(int size) {
+        return new DefaultMessageBuilder(new StringBuilder(size));
     }
 }
diff --git a/maven-core/src/main/resources/META-INF/maven/extension.xml 
b/maven-core/src/main/resources/META-INF/maven/extension.xml
index 1823336d83..8a87ea31f3 100644
--- a/maven-core/src/main/resources/META-INF/maven/extension.xml
+++ b/maven-core/src/main/resources/META-INF/maven/extension.xml
@@ -99,6 +99,10 @@ under the License.
     <exportedPackage>org.codehaus.plexus.logging</exportedPackage>
     <exportedPackage>org.codehaus.plexus.personality</exportedPackage>
 
+    <!-- plexus-interactivity-api -->
+    
<exportedPackage>org.codehaus.plexus.components.interactivity</exportedPackage>
+    <exportedPackage>org.fusesource.jansi.Ansi</exportedPackage>
+
     <!-- javax.inject (JSR-330) -->
     <exportedPackage>javax.inject.*</exportedPackage>
     <!-- javax.enterprise.inject (JSR-299): Must never be exported if needed 
at plugin level, plugin adds it
diff --git a/maven-embedder/pom.xml b/maven-embedder/pom.xml
index be4ee0bee3..fcda8edbd4 100644
--- a/maven-embedder/pom.xml
+++ b/maven-embedder/pom.xml
@@ -88,6 +88,16 @@ under the License.
       <groupId>org.codehaus.plexus</groupId>
       <artifactId>plexus-interpolation</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.codehaus.plexus</groupId>
+      <artifactId>plexus-interactivity-api</artifactId>
+      <exclusions>
+        <exclusion>
+          <groupId>*</groupId>
+          <artifactId>*</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
     <dependency>
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-api</artifactId>
@@ -97,11 +107,6 @@ under the License.
       <artifactId>commons-cli</artifactId>
     </dependency>
 
-    <dependency>
-      <groupId>org.fusesource.jansi</groupId>
-      <artifactId>jansi</artifactId>
-      <optional>true</optional>
-    </dependency>
     <dependency>
       <groupId>ch.qos.logback</groupId>
       <artifactId>logback-classic</artifactId>
@@ -112,6 +117,10 @@ under the License.
       <artifactId>slf4j-simple</artifactId>
       <optional>true</optional>
     </dependency>
+    <dependency>
+      <groupId>org.jline</groupId>
+      <artifactId>jline</artifactId>
+    </dependency>
 
     <dependency>
       <groupId>javax.inject</groupId>
diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/CLIManager.java 
b/maven-embedder/src/main/java/org/apache/maven/cli/CLIManager.java
index baf1c2127c..cc356cca3c 100644
--- a/maven-embedder/src/main/java/org/apache/maven/cli/CLIManager.java
+++ b/maven-embedder/src/main/java/org/apache/maven/cli/CLIManager.java
@@ -28,7 +28,7 @@ import org.apache.commons.cli.HelpFormatter;
 import org.apache.commons.cli.Option;
 import org.apache.commons.cli.Options;
 import org.apache.commons.cli.ParseException;
-import org.apache.maven.cli.jansi.MessageUtils;
+import org.apache.maven.cli.jline.MessageUtils;
 
 /**
  */
diff --git 
a/maven-embedder/src/main/java/org/apache/maven/cli/CLIReportingUtils.java 
b/maven-embedder/src/main/java/org/apache/maven/cli/CLIReportingUtils.java
index 5fd319871d..a5538ca93c 100644
--- a/maven-embedder/src/main/java/org/apache/maven/cli/CLIReportingUtils.java
+++ b/maven-embedder/src/main/java/org/apache/maven/cli/CLIReportingUtils.java
@@ -25,7 +25,7 @@ import java.util.Date;
 import java.util.Locale;
 import java.util.Properties;
 
-import org.apache.maven.cli.jansi.MessageUtils;
+import org.apache.maven.cli.jline.MessageUtils;
 import org.apache.maven.utils.Os;
 import org.slf4j.Logger;
 
diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java 
b/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java
index 67e458d829..a2b51a9c47 100644
--- a/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java
+++ b/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java
@@ -58,8 +58,8 @@ import org.apache.maven.cli.event.ExecutionEventLogger;
 import org.apache.maven.cli.internal.BootstrapCoreExtensionManager;
 import org.apache.maven.cli.internal.extension.io.CoreExtensionsStaxReader;
 import org.apache.maven.cli.internal.extension.model.CoreExtension;
-import org.apache.maven.cli.jansi.JansiMessageBuilderFactory;
-import org.apache.maven.cli.jansi.MessageUtils;
+import org.apache.maven.cli.jline.JLineMessageBuilderFactory;
+import org.apache.maven.cli.jline.MessageUtils;
 import org.apache.maven.cli.logging.Slf4jConfiguration;
 import org.apache.maven.cli.logging.Slf4jConfigurationFactory;
 import org.apache.maven.cli.logging.Slf4jLoggerManager;
@@ -188,7 +188,7 @@ public class MavenCli {
     // This supports painless invocation by the Verifier during embedded 
execution of the core ITs
     public MavenCli(ClassWorld classWorld) {
         this.classWorld = classWorld;
-        this.messageBuilderFactory = new JansiMessageBuilderFactory();
+        this.messageBuilderFactory = new JLineMessageBuilderFactory();
     }
 
     public static void main(String[] args) {
@@ -1676,7 +1676,7 @@ public class MavenCli {
     //
 
     protected TransferListener getConsoleTransferListener(boolean 
printResourceNames) {
-        return new ConsoleMavenTransferListener(System.out, 
printResourceNames);
+        return new ConsoleMavenTransferListener(messageBuilderFactory, 
System.out, printResourceNames);
     }
 
     protected TransferListener getBatchTransferListener() {
diff --git 
a/maven-embedder/src/main/java/org/apache/maven/cli/jansi/JansiMessageBuilder.java
 
b/maven-embedder/src/main/java/org/apache/maven/cli/jansi/JansiMessageBuilder.java
deleted file mode 100644
index be51f309b2..0000000000
--- 
a/maven-embedder/src/main/java/org/apache/maven/cli/jansi/JansiMessageBuilder.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.maven.cli.jansi;
-
-import org.apache.maven.api.annotations.Experimental;
-import org.apache.maven.api.annotations.Nonnull;
-import org.apache.maven.api.services.MessageBuilder;
-import org.fusesource.jansi.Ansi;
-
-@Experimental
-public class JansiMessageBuilder implements MessageBuilder {
-    private final Ansi ansi;
-    private StringBuilder sb;
-
-    @SuppressWarnings("magicnumber")
-    public JansiMessageBuilder() {
-        this.sb = new StringBuilder(80);
-        this.ansi = Ansi.ansi();
-    }
-
-    public JansiMessageBuilder(StringBuilder sb) {
-        this.sb = sb;
-        this.ansi = Ansi.ansi(sb);
-    }
-
-    @Override
-    @Nonnull
-    public MessageBuilder trace(Object o) {
-        return style(Style.TRACE, o);
-    }
-
-    @Override
-    @Nonnull
-    public MessageBuilder debug(Object o) {
-        return style(Style.DEBUG, o);
-    }
-
-    @Override
-    @Nonnull
-    public MessageBuilder info(Object o) {
-        return style(Style.INFO, o);
-    }
-
-    @Override
-    @Nonnull
-    public MessageBuilder warning(Object o) {
-        return style(Style.WARNING, o);
-    }
-
-    @Override
-    @Nonnull
-    public MessageBuilder error(Object o) {
-        return style(Style.ERROR, o);
-    }
-
-    @Override
-    @Nonnull
-    public MessageBuilder success(Object o) {
-        return style(Style.SUCCESS, o);
-    }
-
-    @Override
-    @Nonnull
-    public MessageBuilder failure(Object o) {
-        return style(Style.FAILURE, o);
-    }
-
-    @Override
-    @Nonnull
-    public MessageBuilder strong(Object o) {
-        return style(Style.STRONG, o);
-    }
-
-    @Override
-    @Nonnull
-    public MessageBuilder mojo(Object o) {
-        return style(Style.MOJO, o);
-    }
-
-    @Override
-    @Nonnull
-    public MessageBuilder project(Object o) {
-        return style(Style.PROJECT, o);
-    }
-
-    private MessageBuilder style(Style style, Object o) {
-        style.apply(ansi).a(o).reset();
-        return this;
-    }
-
-    @Override
-    @Nonnull
-    public MessageBuilder a(char[] chars, int i, int i1) {
-        ansi.a(chars, i, i1);
-        return this;
-    }
-
-    @Override
-    @Nonnull
-    public MessageBuilder a(char[] chars) {
-        ansi.a(chars);
-        return this;
-    }
-
-    @Override
-    @Nonnull
-    public MessageBuilder a(CharSequence charSequence, int i, int i1) {
-        ansi.a(charSequence, i, i1);
-        return this;
-    }
-
-    @Override
-    @Nonnull
-    public MessageBuilder a(CharSequence charSequence) {
-        ansi.a(charSequence);
-        return this;
-    }
-
-    @Override
-    @Nonnull
-    public MessageBuilder a(Object o) {
-        ansi.a(o);
-        return this;
-    }
-
-    @Override
-    @Nonnull
-    public MessageBuilder newline() {
-        ansi.newline();
-        return this;
-    }
-
-    @Override
-    @Nonnull
-    public MessageBuilder format(String s, Object... objects) {
-        ansi.format(s, objects);
-        return this;
-    }
-
-    @Override
-    @Nonnull
-    public String build() {
-        return ansi.toString();
-    }
-
-    @Override
-    public String toString() {
-        return build();
-    }
-
-    @Override
-    public void setLength(int length) {
-        sb.setLength(length);
-    }
-}
diff --git 
a/maven-embedder/src/main/java/org/apache/maven/cli/jansi/JansiMessageBuilderFactory.java
 
b/maven-embedder/src/main/java/org/apache/maven/cli/jansi/JansiMessageBuilderFactory.java
deleted file mode 100644
index c7d3225183..0000000000
--- 
a/maven-embedder/src/main/java/org/apache/maven/cli/jansi/JansiMessageBuilderFactory.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.maven.cli.jansi;
-
-import javax.inject.Named;
-import javax.inject.Singleton;
-
-import org.apache.maven.api.annotations.Experimental;
-import org.apache.maven.api.annotations.Nonnull;
-import org.apache.maven.api.services.MessageBuilder;
-import org.apache.maven.api.services.MessageBuilderFactory;
-
-@Experimental
-@Named
-@Singleton
-public class JansiMessageBuilderFactory implements MessageBuilderFactory {
-
-    @Override
-    public boolean isColorEnabled() {
-        return MessageUtils.isColorEnabled();
-    }
-
-    @Override
-    public int getTerminalWidth() {
-        return MessageUtils.getTerminalWidth();
-    }
-
-    @Override
-    @Nonnull
-    public MessageBuilder builder() {
-        return builder(new StringBuilder());
-    }
-
-    @Override
-    @Nonnull
-    public MessageBuilder builder(@Nonnull StringBuilder stringBuilder) {
-        return MessageUtils.builder(stringBuilder);
-    }
-}
diff --git 
a/maven-embedder/src/main/java/org/apache/maven/cli/jansi/MessageUtils.java 
b/maven-embedder/src/main/java/org/apache/maven/cli/jansi/MessageUtils.java
deleted file mode 100644
index 3d0a5d41f7..0000000000
--- a/maven-embedder/src/main/java/org/apache/maven/cli/jansi/MessageUtils.java
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.maven.cli.jansi;
-
-import org.apache.maven.api.services.MessageBuilder;
-import org.apache.maven.internal.impl.DefaultMessageBuilder;
-import org.fusesource.jansi.Ansi;
-import org.fusesource.jansi.AnsiConsole;
-import org.fusesource.jansi.AnsiMode;
-
-/**
- * Colored message utils, to manage colors.  This is the core implementation 
of the
- * {@link JansiMessageBuilderFactory} and {@link JansiMessageBuilder} classes.
- * This class should not be used outside of maven-embedder and the public
- * {@link org.apache.maven.api.services.MessageBuilderFactory} should be used 
instead.
- * <p>
- * Internally, <a href="http://fusesource.github.io/jansi/";>Jansi</a> is used 
to render
- * <a href="https://en.wikipedia.org/wiki/ANSI_escape_code#Colors";>ANSI 
colors</a> on any platform.
- * <p>
- *
- * @see MessageBuilder
- * @see org.apache.maven.api.services.MessageBuilderFactory
- * @see JansiMessageBuilderFactory
- * @see JansiMessageBuilder
- * @since 4.0.0
- */
-public class MessageUtils {
-    private static final boolean JANSI;
-
-    /** Reference to the JVM shutdown hook, if registered */
-    private static Thread shutdownHook;
-
-    /** Synchronization monitor for the "uninstall" */
-    private static final Object STARTUP_SHUTDOWN_MONITOR = new Object();
-
-    static {
-        boolean jansi = true;
-        try {
-            // Jansi is provided by Maven core since 3.5.0
-            Class.forName("org.fusesource.jansi.Ansi");
-        } catch (ClassNotFoundException cnfe) {
-            jansi = false;
-        }
-        JANSI = jansi;
-    }
-
-    /**
-     * Install color support.
-     * This method is called by Maven core, and calling it is not necessary in 
plugins.
-     */
-    public static void systemInstall() {
-        if (JANSI) {
-            AnsiConsole.systemInstall();
-        }
-    }
-
-    /**
-     * Undo a previous {@link #systemInstall()}.  If {@link #systemInstall()} 
was called
-     * multiple times, {@link #systemUninstall()} must be called call the same 
number of times before
-     * it is actually uninstalled.
-     */
-    public static void systemUninstall() {
-        synchronized (STARTUP_SHUTDOWN_MONITOR) {
-            doSystemUninstall();
-
-            // hook can only set when Jansi is true
-            if (shutdownHook != null) {
-                try {
-                    Runtime.getRuntime().removeShutdownHook(shutdownHook);
-                } catch (IllegalStateException ex) {
-                    // ignore - VM is already shutting down
-                }
-            }
-        }
-    }
-
-    private static void doSystemUninstall() {
-        if (JANSI) {
-            AnsiConsole.systemUninstall();
-        }
-    }
-
-    /**
-     * Enables message color (if Jansi is available).
-     * @param flag to enable Jansi
-     */
-    public static void setColorEnabled(boolean flag) {
-        if (JANSI) {
-            AnsiConsole.out().setMode(flag ? AnsiMode.Force : AnsiMode.Strip);
-            Ansi.setEnabled(flag);
-            System.setProperty(
-                    AnsiConsole.JANSI_MODE, flag ? 
AnsiConsole.JANSI_MODE_FORCE : AnsiConsole.JANSI_MODE_STRIP);
-            boolean installed = AnsiConsole.isInstalled();
-            while (AnsiConsole.isInstalled()) {
-                AnsiConsole.systemUninstall();
-            }
-            if (installed) {
-                AnsiConsole.systemInstall();
-            }
-        }
-    }
-
-    /**
-     * Is message color enabled: requires Jansi available (through Maven) and 
the color has not been disabled.
-     * @return whether colored messages are enabled
-     */
-    public static boolean isColorEnabled() {
-        return JANSI ? Ansi.isEnabled() : false;
-    }
-
-    /**
-     * Create a default message buffer.
-     * @return a new buffer
-     */
-    public static MessageBuilder builder() {
-        return builder(new StringBuilder());
-    }
-
-    /**
-     * Create a message buffer with an internal buffer of defined size.
-     * @param size size of the buffer
-     * @return a new buffer
-     */
-    public static MessageBuilder builder(int size) {
-        return builder(new StringBuilder(size));
-    }
-
-    /**
-     * Create a message buffer with defined String builder.
-     * @param builder initial content of the message buffer
-     * @return a new buffer
-     */
-    public static MessageBuilder builder(StringBuilder builder) {
-        return JANSI && isColorEnabled() ? new JansiMessageBuilder(builder) : 
new DefaultMessageBuilder(builder);
-    }
-
-    /**
-     * Remove any ANSI code from a message (colors or other escape sequences).
-     * @param msg message eventually containing ANSI codes
-     * @return the message with ANSI codes removed
-     */
-    public static String stripAnsiCodes(String msg) {
-        return msg.replaceAll("\u001B\\[[;\\d]*[ -/]*[@-~]", "");
-    }
-
-    /**
-     * Register a shutdown hook with the JVM runtime, uninstalling Ansi 
support on
-     * JVM shutdown unless is has already been uninstalled at that time.
-     * <p>Delegates to {@link #doSystemUninstall()} for the actual uninstall 
procedure
-     *
-     * @see Runtime#addShutdownHook(Thread)
-     * @see MessageUtils#systemUninstall()
-     * @see #doSystemUninstall()
-     */
-    public static void registerShutdownHook() {
-        if (JANSI && shutdownHook == null) {
-            // No shutdown hook registered yet.
-            shutdownHook = new Thread() {
-                @Override
-                public void run() {
-                    synchronized (STARTUP_SHUTDOWN_MONITOR) {
-                        while (AnsiConsole.isInstalled()) {
-                            doSystemUninstall();
-                        }
-                    }
-                }
-            };
-            Runtime.getRuntime().addShutdownHook(shutdownHook);
-        }
-    }
-
-    /**
-     * Get the terminal width or -1 if the width cannot be determined.
-     *
-     * @return the terminal width
-     */
-    public static int getTerminalWidth() {
-        if (JANSI) {
-            int width = AnsiConsole.getTerminalWidth();
-            return width > 0 ? width : -1;
-        } else {
-            return -1;
-        }
-    }
-}
diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/jansi/Style.java 
b/maven-embedder/src/main/java/org/apache/maven/cli/jansi/Style.java
deleted file mode 100644
index 16ae2ad428..0000000000
--- a/maven-embedder/src/main/java/org/apache/maven/cli/jansi/Style.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.maven.cli.jansi;
-
-import java.util.Locale;
-
-import org.fusesource.jansi.Ansi;
-import org.fusesource.jansi.Ansi.Color;
-
-/**
- * Configurable message styles.
- * @since 4.0.0
- */
-enum Style {
-    TRACE("bold,magenta"),
-    DEBUG("bold,cyan"),
-    INFO("bold,blue"),
-    WARNING("bold,yellow"),
-    ERROR("bold,red"),
-    SUCCESS("bold,green"),
-    FAILURE("bold,red"),
-    STRONG("bold"),
-    MOJO("green"),
-    PROJECT("cyan");
-
-    private final boolean bold;
-
-    private final boolean bright;
-
-    private final Color color;
-
-    private final boolean bgBright;
-
-    private final Color bgColor;
-
-    Style(String defaultValue) {
-        boolean currentBold = false;
-        boolean currentBright = false;
-        Color currentColor = null;
-        boolean currentBgBright = false;
-        Color currentBgColor = null;
-
-        String value = System.getProperty("style." + 
name().toLowerCase(Locale.ENGLISH), defaultValue)
-                .toLowerCase(Locale.ENGLISH);
-
-        for (String token : value.split(",")) {
-            if ("bold".equals(token)) {
-                currentBold = true;
-            } else if (token.startsWith("bg")) {
-                token = token.substring(2);
-                if (token.startsWith("bright")) {
-                    currentBgBright = true;
-                    token = token.substring(6);
-                }
-                currentBgColor = toColor(token);
-            } else {
-                if (token.startsWith("bright")) {
-                    currentBright = true;
-                    token = token.substring(6);
-                }
-                currentColor = toColor(token);
-            }
-        }
-
-        this.bold = currentBold;
-        this.bright = currentBright;
-        this.color = currentColor;
-        this.bgBright = currentBgBright;
-        this.bgColor = currentBgColor;
-    }
-
-    private static Color toColor(String token) {
-        for (Color color : Color.values()) {
-            if (color.toString().equalsIgnoreCase(token)) {
-                return color;
-            }
-        }
-        return null;
-    }
-
-    Ansi apply(Ansi ansi) {
-        if (bold) {
-            ansi.bold();
-        }
-        if (color != null) {
-            if (bright) {
-                ansi.fgBright(color);
-            } else {
-                ansi.fg(color);
-            }
-        }
-        if (bgColor != null) {
-            if (bgBright) {
-                ansi.bgBright(bgColor);
-            } else {
-                ansi.bg(bgColor);
-            }
-        }
-        return ansi;
-    }
-
-    @Override
-    public String toString() {
-        if (!bold && color == null && bgColor == null) {
-            return name();
-        }
-        StringBuilder sb = new StringBuilder(name() + '=');
-        if (bold) {
-            sb.append("bold");
-        }
-        if (color != null) {
-            if (sb.length() > 0) {
-                sb.append(',');
-            }
-            if (bright) {
-                sb.append("bright");
-            }
-            sb.append(color.name());
-        }
-        if (bgColor != null) {
-            if (sb.length() > 0) {
-                sb.append(',');
-            }
-            sb.append("bg");
-            if (bgBright) {
-                sb.append("bright");
-            }
-            sb.append(bgColor.name());
-        }
-        return sb.toString();
-    }
-}
diff --git 
a/maven-embedder/src/main/java/org/apache/maven/cli/jline/JLineMessageBuilderFactory.java
 
b/maven-embedder/src/main/java/org/apache/maven/cli/jline/JLineMessageBuilderFactory.java
new file mode 100644
index 0000000000..139b9bf4a4
--- /dev/null
+++ 
b/maven-embedder/src/main/java/org/apache/maven/cli/jline/JLineMessageBuilderFactory.java
@@ -0,0 +1,294 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.cli.jline;
+
+import javax.annotation.Priority;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.maven.api.annotations.Experimental;
+import org.apache.maven.api.services.MessageBuilder;
+import org.apache.maven.api.services.MessageBuilderFactory;
+import org.codehaus.plexus.components.interactivity.InputHandler;
+import org.codehaus.plexus.components.interactivity.OutputHandler;
+import org.codehaus.plexus.components.interactivity.Prompter;
+import org.codehaus.plexus.components.interactivity.PrompterException;
+import org.jline.utils.AttributedStringBuilder;
+import org.jline.utils.AttributedStyle;
+import org.jline.utils.StyleResolver;
+
+import static org.jline.utils.AttributedStyle.DEFAULT;
+
+@Experimental
+@Named
+@Singleton
+@Priority(10)
+public class JLineMessageBuilderFactory implements MessageBuilderFactory, 
Prompter, InputHandler, OutputHandler {
+
+    private final StyleResolver resolver;
+
+    public JLineMessageBuilderFactory() {
+        this.resolver = new MavenStyleResolver();
+    }
+
+    @Override
+    public boolean isColorEnabled() {
+        return false;
+    }
+
+    @Override
+    public int getTerminalWidth() {
+        return MessageUtils.getTerminalWidth();
+    }
+
+    @Override
+    public MessageBuilder builder() {
+        return new JlineMessageBuilder();
+    }
+
+    @Override
+    public MessageBuilder builder(int size) {
+        return new JlineMessageBuilder(size);
+    }
+
+    @Override
+    public String readLine() throws IOException {
+        return doPrompt(null, true);
+    }
+
+    @Override
+    public String readPassword() throws IOException {
+        return doPrompt(null, true);
+    }
+
+    @Override
+    public List<String> readMultipleLines() throws IOException {
+        List<String> lines = new ArrayList<>();
+        for (String line = this.readLine(); line != null && !line.isEmpty(); 
line = readLine()) {
+            lines.add(line);
+        }
+        return lines;
+    }
+
+    @Override
+    public void write(String line) throws IOException {
+        doDisplay(line);
+    }
+
+    @Override
+    public void writeLine(String line) throws IOException {
+        doDisplay(line + System.lineSeparator());
+    }
+
+    @Override
+    public String prompt(String message) throws PrompterException {
+        return prompt(message, null, null);
+    }
+
+    @Override
+    public String prompt(String message, String defaultReply) throws 
PrompterException {
+        return prompt(message, null, defaultReply);
+    }
+
+    @Override
+    public String prompt(String message, List possibleValues) throws 
PrompterException {
+        return prompt(message, possibleValues, null);
+    }
+
+    @Override
+    public String prompt(String message, List possibleValues, String 
defaultReply) throws PrompterException {
+        return doPrompt(message, possibleValues, defaultReply, false);
+    }
+
+    @Override
+    public String promptForPassword(String message) throws PrompterException {
+        return doPrompt(message, null, null, true);
+    }
+
+    @Override
+    public void showMessage(String message) throws PrompterException {
+        try {
+            doDisplay(message);
+        } catch (IOException e) {
+            throw new PrompterException("Failed to present prompt", e);
+        }
+    }
+
+    String doPrompt(String message, List<Object> possibleValues, String 
defaultReply, boolean password)
+            throws PrompterException {
+        String formattedMessage = formatMessage(message, possibleValues, 
defaultReply);
+        String line;
+        do {
+            try {
+                line = doPrompt(formattedMessage, password);
+                if (line == null && defaultReply == null) {
+                    throw new IOException("EOF");
+                }
+            } catch (IOException e) {
+                throw new PrompterException("Failed to prompt user", e);
+            }
+            if (line == null || line.isEmpty()) {
+                line = defaultReply;
+            }
+            if (line != null && (possibleValues != null && 
!possibleValues.contains(line))) {
+                try {
+                    doDisplay("Invalid selection.\n");
+                } catch (IOException e) {
+                    throw new PrompterException("Failed to present feedback", 
e);
+                }
+            }
+        } while (line == null || (possibleValues != null && 
!possibleValues.contains(line)));
+        return line;
+    }
+
+    private String formatMessage(String message, List<Object> possibleValues, 
String defaultReply) {
+        StringBuilder formatted = new StringBuilder(message.length() * 2);
+        formatted.append(message);
+        if (possibleValues != null && !possibleValues.isEmpty()) {
+            formatted.append(" (");
+            for (Iterator<?> it = possibleValues.iterator(); it.hasNext(); ) {
+                String possibleValue = String.valueOf(it.next());
+                formatted.append(possibleValue);
+                if (it.hasNext()) {
+                    formatted.append('/');
+                }
+            }
+            formatted.append(')');
+        }
+        if (defaultReply != null) {
+            formatted.append(' ').append(defaultReply).append(": ");
+        }
+        return formatted.toString();
+    }
+
+    private void doDisplay(String message) throws IOException {
+        try {
+            MessageUtils.terminal.writer().print(message);
+            MessageUtils.terminal.flush();
+        } catch (Exception e) {
+            throw new IOException("Unable to display message", e);
+        }
+    }
+
+    private String doPrompt(String message, boolean password) throws 
IOException {
+        try {
+            return MessageUtils.reader.readLine(message != null ? message + ": 
" : null, password ? '*' : null);
+        } catch (Exception e) {
+            throw new IOException("Unable to prompt user", e);
+        }
+    }
+
+    class JlineMessageBuilder implements MessageBuilder {
+
+        final AttributedStringBuilder builder;
+
+        JlineMessageBuilder() {
+            builder = new AttributedStringBuilder();
+        }
+
+        JlineMessageBuilder(int size) {
+            builder = new AttributedStringBuilder(size);
+        }
+
+        @Override
+        public MessageBuilder style(String style) {
+            if (MessageUtils.isColorEnabled()) {
+                builder.style(resolver.resolve(style));
+            }
+            return this;
+        }
+
+        @Override
+        public MessageBuilder resetStyle() {
+            builder.style(DEFAULT);
+            return this;
+        }
+
+        @Override
+        public MessageBuilder append(CharSequence cs) {
+            builder.append(cs);
+            return this;
+        }
+
+        @Override
+        public MessageBuilder append(CharSequence cs, int start, int end) {
+            builder.append(cs, start, end);
+            return this;
+        }
+
+        @Override
+        public MessageBuilder append(char c) {
+            builder.append(c);
+            return this;
+        }
+
+        @Override
+        public MessageBuilder setLength(int length) {
+            builder.setLength(length);
+            return this;
+        }
+
+        @Override
+        public String build() {
+            return builder.toAnsi(MessageUtils.terminal);
+        }
+
+        @Override
+        public String toString() {
+            return build();
+        }
+    }
+
+    static class MavenStyleResolver extends StyleResolver {
+
+        private final Map<String, AttributedStyle> styles = new 
ConcurrentHashMap<>();
+
+        MavenStyleResolver() {
+            super(s -> System.getProperty("style." + s));
+        }
+
+        @Override
+        public AttributedStyle resolve(String spec) {
+            return styles.computeIfAbsent(spec, this::doResolve);
+        }
+
+        @Override
+        public AttributedStyle resolve(String spec, String defaultSpec) {
+            return resolve(defaultSpec != null ? spec + ":-" + defaultSpec : 
spec);
+        }
+
+        private AttributedStyle doResolve(String spec) {
+            String def = null;
+            int i = spec.indexOf(":-");
+            if (i != -1) {
+                String[] parts = spec.split(":-");
+                spec = parts[0].trim();
+                def = parts[1].trim();
+            }
+            return super.resolve(spec, def);
+        }
+    }
+}
diff --git 
a/maven-embedder/src/main/java/org/apache/maven/cli/jline/MessageUtils.java 
b/maven-embedder/src/main/java/org/apache/maven/cli/jline/MessageUtils.java
new file mode 100644
index 0000000000..d1fb0f94e3
--- /dev/null
+++ b/maven-embedder/src/main/java/org/apache/maven/cli/jline/MessageUtils.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.cli.jline;
+
+import java.io.IOError;
+import java.io.IOException;
+import java.io.PrintStream;
+
+import org.apache.maven.api.services.MessageBuilder;
+import org.apache.maven.api.services.MessageBuilderFactory;
+import org.jline.jansi.AnsiConsole;
+import org.jline.reader.LineReader;
+import org.jline.reader.LineReaderBuilder;
+import org.jline.terminal.Terminal;
+import org.jline.terminal.TerminalBuilder;
+
+public class MessageUtils {
+
+    static Terminal terminal;
+    static LineReader reader;
+    static MessageBuilderFactory messageBuilderFactory = new 
JLineMessageBuilderFactory();
+    static boolean colorEnabled = true;
+    static Thread shutdownHook;
+    static final Object STARTUP_SHUTDOWN_MONITOR = new Object();
+
+    static PrintStream prevOut;
+    static PrintStream prevErr;
+
+    public static void systemInstall() {
+        try {
+            terminal = 
TerminalBuilder.builder().name("Maven").dumb(true).build();
+            reader = LineReaderBuilder.builder().terminal(terminal).build();
+            AnsiConsole.setTerminal(terminal);
+            AnsiConsole.systemInstall();
+        } catch (IOException e) {
+            throw new IOError(e);
+        }
+    }
+
+    public static void registerShutdownHook() {
+        if (shutdownHook == null) {
+            shutdownHook = new Thread(() -> {
+                synchronized (MessageUtils.STARTUP_SHUTDOWN_MONITOR) {
+                    MessageUtils.doSystemUninstall();
+                }
+            });
+            Runtime.getRuntime().addShutdownHook(shutdownHook);
+        }
+    }
+
+    public static void systemUninstall() {
+        doSystemUninstall();
+        if (shutdownHook != null) {
+            try {
+                Runtime.getRuntime().removeShutdownHook(shutdownHook);
+            } catch (IllegalStateException var3) {
+                // ignore
+            }
+        }
+    }
+
+    private static void doSystemUninstall() {
+        try {
+            AnsiConsole.systemUninstall();
+        } finally {
+            terminal = null;
+        }
+    }
+
+    public static void setColorEnabled(boolean enabled) {
+        colorEnabled = enabled;
+    }
+
+    public static boolean isColorEnabled() {
+        return colorEnabled && terminal != null;
+    }
+
+    public static int getTerminalWidth() {
+        return terminal != null ? terminal.getWidth() : -1;
+    }
+
+    public static MessageBuilder builder() {
+        return messageBuilderFactory.builder();
+    }
+}
diff --git 
a/maven-embedder/src/main/java/org/apache/maven/cli/transfer/AbstractMavenTransferListener.java
 
b/maven-embedder/src/main/java/org/apache/maven/cli/transfer/AbstractMavenTransferListener.java
index bc6526806c..c38e411e0d 100644
--- 
a/maven-embedder/src/main/java/org/apache/maven/cli/transfer/AbstractMavenTransferListener.java
+++ 
b/maven-embedder/src/main/java/org/apache/maven/cli/transfer/AbstractMavenTransferListener.java
@@ -21,7 +21,8 @@ package org.apache.maven.cli.transfer;
 import java.io.PrintStream;
 import java.util.Locale;
 
-import org.apache.maven.cli.jansi.MessageUtils;
+import org.apache.maven.api.services.MessageBuilder;
+import org.apache.maven.api.services.MessageBuilderFactory;
 import org.eclipse.aether.transfer.AbstractTransferListener;
 import org.eclipse.aether.transfer.TransferCancelledException;
 import org.eclipse.aether.transfer.TransferEvent;
@@ -31,31 +32,27 @@ import org.eclipse.aether.transfer.TransferResource;
  * AbstractMavenTransferListener
  */
 public abstract class AbstractMavenTransferListener extends 
AbstractTransferListener {
+    public static final String STYLE = ".transfer:-faint";
 
-    private static final String ESC = "\u001B";
-    private static final String ANSI_DARK_SET = ESC + "[90m";
-    private static final String ANSI_DARK_RESET = ESC + "[0m";
+    protected final MessageBuilderFactory messageBuilderFactory;
+    protected final PrintStream out;
 
-    protected PrintStream out;
-
-    protected AbstractMavenTransferListener(PrintStream out) {
+    protected AbstractMavenTransferListener(MessageBuilderFactory 
messageBuilderFactory, PrintStream out) {
+        this.messageBuilderFactory = messageBuilderFactory;
         this.out = out;
     }
 
     @Override
     public void transferInitiated(TransferEvent event) {
-        String darkOn = MessageUtils.isColorEnabled() ? ANSI_DARK_SET : "";
-        String darkOff = MessageUtils.isColorEnabled() ? ANSI_DARK_RESET : "";
-
         String action = event.getRequestType() == 
TransferEvent.RequestType.PUT ? "Uploading" : "Downloading";
         String direction = event.getRequestType() == 
TransferEvent.RequestType.PUT ? "to" : "from";
 
         TransferResource resource = event.getResource();
-        StringBuilder message = new StringBuilder();
-        message.append(darkOn).append(action).append(' 
').append(direction).append(' ');
-        message.append(darkOff).append(resource.getRepositoryId());
-        message.append(darkOn).append(": 
").append(resource.getRepositoryUrl());
-        message.append(darkOff).append(resource.getResourceName());
+        MessageBuilder message = messageBuilderFactory.builder();
+        message.style(STYLE).append(action).append(' 
').append(direction).append(' ');
+        message.resetStyle().append(resource.getRepositoryId());
+        message.style(STYLE).append(": ").append(resource.getRepositoryUrl());
+        message.resetStyle().append(resource.getResourceName());
 
         out.println(message.toString());
     }
@@ -70,9 +67,6 @@ public abstract class AbstractMavenTransferListener extends 
AbstractTransferList
 
     @Override
     public void transferSucceeded(TransferEvent event) {
-        String darkOn = MessageUtils.isColorEnabled() ? ANSI_DARK_SET : "";
-        String darkOff = MessageUtils.isColorEnabled() ? ANSI_DARK_RESET : "";
-
         String action = (event.getRequestType() == 
TransferEvent.RequestType.PUT ? "Uploaded" : "Downloaded");
         String direction = event.getRequestType() == 
TransferEvent.RequestType.PUT ? "to" : "from";
 
@@ -80,13 +74,12 @@ public abstract class AbstractMavenTransferListener extends 
AbstractTransferList
         long contentLength = event.getTransferredBytes();
         FileSizeFormat format = new FileSizeFormat(Locale.ENGLISH);
 
-        StringBuilder message = new StringBuilder();
-        message.append(action).append(darkOn).append(' 
').append(direction).append(' ');
-        message.append(darkOff).append(resource.getRepositoryId());
-        message.append(darkOn).append(": 
").append(resource.getRepositoryUrl());
-        message.append(darkOff).append(resource.getResourceName());
-        message.append(darkOn).append(" (");
-        format.format(message, contentLength);
+        MessageBuilder message = messageBuilderFactory.builder();
+        message.append(action).style(STYLE).append(' 
').append(direction).append(' ');
+        message.resetStyle().append(resource.getRepositoryId());
+        message.style(STYLE).append(": ").append(resource.getRepositoryUrl());
+        message.resetStyle().append(resource.getResourceName());
+        message.style(STYLE).append(" (").append(format.format(contentLength));
 
         long duration = System.currentTimeMillis() - 
resource.getTransferStartTime();
         if (duration > 0L) {
@@ -96,7 +89,7 @@ public abstract class AbstractMavenTransferListener extends 
AbstractTransferList
             message.append("/s");
         }
 
-        message.append(')').append(darkOff);
+        message.append(')').resetStyle();
         out.println(message.toString());
     }
 }
diff --git 
a/maven-embedder/src/main/java/org/apache/maven/cli/transfer/ConsoleMavenTransferListener.java
 
b/maven-embedder/src/main/java/org/apache/maven/cli/transfer/ConsoleMavenTransferListener.java
index 78addd1349..2d54f663f5 100644
--- 
a/maven-embedder/src/main/java/org/apache/maven/cli/transfer/ConsoleMavenTransferListener.java
+++ 
b/maven-embedder/src/main/java/org/apache/maven/cli/transfer/ConsoleMavenTransferListener.java
@@ -24,6 +24,7 @@ import java.util.LinkedHashMap;
 import java.util.Locale;
 import java.util.Map;
 
+import org.apache.maven.api.services.MessageBuilderFactory;
 import org.eclipse.aether.transfer.TransferCancelledException;
 import org.eclipse.aether.transfer.TransferEvent;
 import org.eclipse.aether.transfer.TransferResource;
@@ -41,8 +42,9 @@ public class ConsoleMavenTransferListener extends 
AbstractMavenTransferListener
     private boolean printResourceNames;
     private int lastLength;
 
-    public ConsoleMavenTransferListener(PrintStream out, boolean 
printResourceNames) {
-        super(out);
+    public ConsoleMavenTransferListener(
+            MessageBuilderFactory messageBuilderFactory, PrintStream out, 
boolean printResourceNames) {
+        super(messageBuilderFactory, out);
         this.printResourceNames = printResourceNames;
     }
 
diff --git 
a/maven-embedder/src/main/java/org/apache/maven/cli/transfer/FileSizeFormat.java
 
b/maven-embedder/src/main/java/org/apache/maven/cli/transfer/FileSizeFormat.java
index 6ae2054e65..35078f9c0c 100644
--- 
a/maven-embedder/src/main/java/org/apache/maven/cli/transfer/FileSizeFormat.java
+++ 
b/maven-embedder/src/main/java/org/apache/maven/cli/transfer/FileSizeFormat.java
@@ -20,6 +20,8 @@ package org.apache.maven.cli.transfer;
 
 import java.util.Locale;
 
+import org.apache.maven.api.services.MessageBuilder;
+
 /**
  * Formats file size with the associated <a 
href="https://en.wikipedia.org/wiki/Metric_prefix";>SI</a> prefix
  * (GB, MB, kB) and using the patterns <code>#0.0</code> for numbers between 1 
and 10
@@ -147,6 +149,38 @@ public class FileSizeFormat {
         }
     }
 
+    public void format(MessageBuilder builder, long size) {
+        format(builder, size, null, false);
+    }
+
+    public void format(MessageBuilder builder, long size, ScaleUnit unit) {
+        format(builder, size, unit, false);
+    }
+
+    @SuppressWarnings("checkstyle:magicnumber")
+    private void format(MessageBuilder builder, long size, ScaleUnit unit, 
boolean omitSymbol) {
+        if (size < 0L) {
+            throw new IllegalArgumentException("file size cannot be negative: 
" + size);
+        }
+        if (unit == null) {
+            unit = ScaleUnit.getScaleUnit(size);
+        }
+
+        double scaledSize = (double) size / unit.bytes();
+
+        if (unit == ScaleUnit.BYTE) {
+            builder.append(Long.toString(size));
+        } else if (scaledSize < 0.05d || scaledSize >= 10.0d) {
+            builder.append(Long.toString(Math.round(scaledSize)));
+        } else {
+            builder.append(Double.toString(Math.round(scaledSize * 10d) / 
10d));
+        }
+
+        if (!omitSymbol) {
+            builder.append(" ").append(unit.symbol());
+        }
+    }
+
     public String formatProgress(long progressedSize, long size) {
         StringBuilder sb = new StringBuilder();
         formatProgress(sb, progressedSize, size);
diff --git a/maven-embedder/src/main/java/org/fusesource/jansi/Ansi.java 
b/maven-embedder/src/main/java/org/fusesource/jansi/Ansi.java
new file mode 100644
index 0000000000..8cf2051511
--- /dev/null
+++ b/maven-embedder/src/main/java/org/fusesource/jansi/Ansi.java
@@ -0,0 +1,952 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.fusesource.jansi;
+
+import java.util.ArrayList;
+
+/**
+ * Provides a fluent API for generating
+ * <a href="https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_sequences";>ANSI 
escape sequences</a>.
+ *
+ * This class comes from Jansi and is provided for backward compatibility
+ * with maven-shared-utils, while Maven has migrated to JLine (into which 
Jansi has been merged
+ * since JLine 3.25.0).
+ */
+@SuppressWarnings({"checkstyle:MagicNumber", "unused"})
+public class Ansi implements Appendable {
+
+    private static final char FIRST_ESC_CHAR = 27;
+    private static final char SECOND_ESC_CHAR = '[';
+
+    /**
+     * <a href="https://en.wikipedia.org/wiki/ANSI_escape_code#Colors";>ANSI 8 
colors</a> for fluent API
+     */
+    public enum Color {
+        BLACK(0, "BLACK"),
+        RED(1, "RED"),
+        GREEN(2, "GREEN"),
+        YELLOW(3, "YELLOW"),
+        BLUE(4, "BLUE"),
+        MAGENTA(5, "MAGENTA"),
+        CYAN(6, "CYAN"),
+        WHITE(7, "WHITE"),
+        DEFAULT(9, "DEFAULT");
+
+        private final int value;
+        private final String name;
+
+        Color(int index, String name) {
+            this.value = index;
+            this.name = name;
+        }
+
+        @Override
+        public String toString() {
+            return name;
+        }
+
+        public int value() {
+            return value;
+        }
+
+        public int fg() {
+            return value + 30;
+        }
+
+        public int bg() {
+            return value + 40;
+        }
+
+        public int fgBright() {
+            return value + 90;
+        }
+
+        public int bgBright() {
+            return value + 100;
+        }
+    }
+
+    /**
+     * Display attributes, also know as
+     * <a 
href="https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters">SGR
+     * (Select Graphic Rendition) parameters</a>.
+     */
+    public enum Attribute {
+        RESET(0, "RESET"),
+        INTENSITY_BOLD(1, "INTENSITY_BOLD"),
+        INTENSITY_FAINT(2, "INTENSITY_FAINT"),
+        ITALIC(3, "ITALIC_ON"),
+        UNDERLINE(4, "UNDERLINE_ON"),
+        BLINK_SLOW(5, "BLINK_SLOW"),
+        BLINK_FAST(6, "BLINK_FAST"),
+        NEGATIVE_ON(7, "NEGATIVE_ON"),
+        CONCEAL_ON(8, "CONCEAL_ON"),
+        STRIKETHROUGH_ON(9, "STRIKETHROUGH_ON"),
+        UNDERLINE_DOUBLE(21, "UNDERLINE_DOUBLE"),
+        INTENSITY_BOLD_OFF(22, "INTENSITY_BOLD_OFF"),
+        ITALIC_OFF(23, "ITALIC_OFF"),
+        UNDERLINE_OFF(24, "UNDERLINE_OFF"),
+        BLINK_OFF(25, "BLINK_OFF"),
+        NEGATIVE_OFF(27, "NEGATIVE_OFF"),
+        CONCEAL_OFF(28, "CONCEAL_OFF"),
+        STRIKETHROUGH_OFF(29, "STRIKETHROUGH_OFF");
+
+        private final int value;
+        private final String name;
+
+        Attribute(int index, String name) {
+            this.value = index;
+            this.name = name;
+        }
+
+        @Override
+        public String toString() {
+            return name;
+        }
+
+        public int value() {
+            return value;
+        }
+    }
+
+    /**
+     * ED (Erase in Display) / EL (Erase in Line) parameter (see
+     * <a 
href="https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_sequences";>CSI 
sequence J and K</a>)
+     * @see Ansi#eraseScreen(Erase)
+     * @see Ansi#eraseLine(Erase)
+     */
+    public enum Erase {
+        FORWARD(0, "FORWARD"),
+        BACKWARD(1, "BACKWARD"),
+        ALL(2, "ALL");
+
+        private final int value;
+        private final String name;
+
+        Erase(int index, String name) {
+            this.value = index;
+            this.name = name;
+        }
+
+        @Override
+        public String toString() {
+            return name;
+        }
+
+        public int value() {
+            return value;
+        }
+    }
+
+    @FunctionalInterface
+    public interface Consumer {
+        void apply(Ansi ansi);
+    }
+
+    public static boolean isEnabled() {
+        return org.apache.maven.cli.jline.MessageUtils.isColorEnabled() && 
org.jline.jansi.Ansi.isEnabled();
+    }
+
+    public static Ansi ansi() {
+        if (isEnabled()) {
+            return new Ansi();
+        } else {
+            return new NoAnsi();
+        }
+    }
+
+    public static Ansi ansi(StringBuilder builder) {
+        if (isEnabled()) {
+            return new Ansi(builder);
+        } else {
+            return new NoAnsi(builder);
+        }
+    }
+
+    public static Ansi ansi(int size) {
+        if (isEnabled()) {
+            return new Ansi(size);
+        } else {
+            return new NoAnsi(size);
+        }
+    }
+
+    private static class NoAnsi extends Ansi {
+        NoAnsi() {
+            super();
+        }
+
+        NoAnsi(int size) {
+            super(size);
+        }
+
+        NoAnsi(StringBuilder builder) {
+            super(builder);
+        }
+
+        @Override
+        public Ansi fg(Color color) {
+            return this;
+        }
+
+        @Override
+        public Ansi bg(Color color) {
+            return this;
+        }
+
+        @Override
+        public Ansi fgBright(Color color) {
+            return this;
+        }
+
+        @Override
+        public Ansi bgBright(Color color) {
+            return this;
+        }
+
+        @Override
+        public Ansi fg(int color) {
+            return this;
+        }
+
+        @Override
+        public Ansi fgRgb(int r, int g, int b) {
+            return this;
+        }
+
+        @Override
+        public Ansi bg(int color) {
+            return this;
+        }
+
+        @Override
+        public Ansi bgRgb(int r, int g, int b) {
+            return this;
+        }
+
+        @Override
+        public Ansi a(Attribute attribute) {
+            return this;
+        }
+
+        @Override
+        public Ansi cursor(int row, int column) {
+            return this;
+        }
+
+        @Override
+        public Ansi cursorToColumn(int x) {
+            return this;
+        }
+
+        @Override
+        public Ansi cursorUp(int y) {
+            return this;
+        }
+
+        @Override
+        public Ansi cursorRight(int x) {
+            return this;
+        }
+
+        @Override
+        public Ansi cursorDown(int y) {
+            return this;
+        }
+
+        @Override
+        public Ansi cursorLeft(int x) {
+            return this;
+        }
+
+        @Override
+        public Ansi cursorDownLine() {
+            return this;
+        }
+
+        @Override
+        public Ansi cursorDownLine(final int n) {
+            return this;
+        }
+
+        @Override
+        public Ansi cursorUpLine() {
+            return this;
+        }
+
+        @Override
+        public Ansi cursorUpLine(final int n) {
+            return this;
+        }
+
+        @Override
+        public Ansi eraseScreen() {
+            return this;
+        }
+
+        @Override
+        public Ansi eraseScreen(Erase kind) {
+            return this;
+        }
+
+        @Override
+        public Ansi eraseLine() {
+            return this;
+        }
+
+        @Override
+        public Ansi eraseLine(Erase kind) {
+            return this;
+        }
+
+        @Override
+        public Ansi scrollUp(int rows) {
+            return this;
+        }
+
+        @Override
+        public Ansi scrollDown(int rows) {
+            return this;
+        }
+
+        @Override
+        public Ansi saveCursorPosition() {
+            return this;
+        }
+
+        @Override
+        @Deprecated
+        public Ansi restorCursorPosition() {
+            return this;
+        }
+
+        @Override
+        public Ansi restoreCursorPosition() {
+            return this;
+        }
+
+        @Override
+        public Ansi reset() {
+            return this;
+        }
+    }
+
+    private final StringBuilder builder;
+    private final ArrayList<Integer> attributeOptions = new ArrayList<>(5);
+
+    public Ansi() {
+        this(new StringBuilder(80));
+    }
+
+    public Ansi(Ansi parent) {
+        this(new StringBuilder(parent.builder));
+        attributeOptions.addAll(parent.attributeOptions);
+    }
+
+    public Ansi(int size) {
+        this(new StringBuilder(size));
+    }
+
+    public Ansi(StringBuilder builder) {
+        this.builder = builder;
+    }
+
+    public Ansi fg(Color color) {
+        attributeOptions.add(color.fg());
+        return this;
+    }
+
+    public Ansi fg(int color) {
+        attributeOptions.add(38);
+        attributeOptions.add(5);
+        attributeOptions.add(color & 0xff);
+        return this;
+    }
+
+    public Ansi fgRgb(int color) {
+        return fgRgb(color >> 16, color >> 8, color);
+    }
+
+    public Ansi fgRgb(int r, int g, int b) {
+        attributeOptions.add(38);
+        attributeOptions.add(2);
+        attributeOptions.add(r & 0xff);
+        attributeOptions.add(g & 0xff);
+        attributeOptions.add(b & 0xff);
+        return this;
+    }
+
+    public Ansi fgBlack() {
+        return this.fg(Color.BLACK);
+    }
+
+    public Ansi fgBlue() {
+        return this.fg(Color.BLUE);
+    }
+
+    public Ansi fgCyan() {
+        return this.fg(Color.CYAN);
+    }
+
+    public Ansi fgDefault() {
+        return this.fg(Color.DEFAULT);
+    }
+
+    public Ansi fgGreen() {
+        return this.fg(Color.GREEN);
+    }
+
+    public Ansi fgMagenta() {
+        return this.fg(Color.MAGENTA);
+    }
+
+    public Ansi fgRed() {
+        return this.fg(Color.RED);
+    }
+
+    public Ansi fgYellow() {
+        return this.fg(Color.YELLOW);
+    }
+
+    public Ansi bg(Color color) {
+        attributeOptions.add(color.bg());
+        return this;
+    }
+
+    public Ansi bg(int color) {
+        attributeOptions.add(48);
+        attributeOptions.add(5);
+        attributeOptions.add(color & 0xff);
+        return this;
+    }
+
+    public Ansi bgRgb(int color) {
+        return bgRgb(color >> 16, color >> 8, color);
+    }
+
+    public Ansi bgRgb(int r, int g, int b) {
+        attributeOptions.add(48);
+        attributeOptions.add(2);
+        attributeOptions.add(r & 0xff);
+        attributeOptions.add(g & 0xff);
+        attributeOptions.add(b & 0xff);
+        return this;
+    }
+
+    public Ansi bgCyan() {
+        return this.bg(Color.CYAN);
+    }
+
+    public Ansi bgDefault() {
+        return this.bg(Color.DEFAULT);
+    }
+
+    public Ansi bgGreen() {
+        return this.bg(Color.GREEN);
+    }
+
+    public Ansi bgMagenta() {
+        return this.bg(Color.MAGENTA);
+    }
+
+    public Ansi bgRed() {
+        return this.bg(Color.RED);
+    }
+
+    public Ansi bgYellow() {
+        return this.bg(Color.YELLOW);
+    }
+
+    public Ansi fgBright(Color color) {
+        attributeOptions.add(color.fgBright());
+        return this;
+    }
+
+    public Ansi fgBrightBlack() {
+        return this.fgBright(Color.BLACK);
+    }
+
+    public Ansi fgBrightBlue() {
+        return this.fgBright(Color.BLUE);
+    }
+
+    public Ansi fgBrightCyan() {
+        return this.fgBright(Color.CYAN);
+    }
+
+    public Ansi fgBrightDefault() {
+        return this.fgBright(Color.DEFAULT);
+    }
+
+    public Ansi fgBrightGreen() {
+        return this.fgBright(Color.GREEN);
+    }
+
+    public Ansi fgBrightMagenta() {
+        return this.fgBright(Color.MAGENTA);
+    }
+
+    public Ansi fgBrightRed() {
+        return this.fgBright(Color.RED);
+    }
+
+    public Ansi fgBrightYellow() {
+        return this.fgBright(Color.YELLOW);
+    }
+
+    public Ansi bgBright(Color color) {
+        attributeOptions.add(color.bgBright());
+        return this;
+    }
+
+    public Ansi bgBrightCyan() {
+        return this.bgBright(Color.CYAN);
+    }
+
+    public Ansi bgBrightDefault() {
+        return this.bgBright(Color.DEFAULT);
+    }
+
+    public Ansi bgBrightGreen() {
+        return this.bgBright(Color.GREEN);
+    }
+
+    public Ansi bgBrightMagenta() {
+        return this.bgBright(Color.MAGENTA);
+    }
+
+    public Ansi bgBrightRed() {
+        return this.bgBright(Color.RED);
+    }
+
+    public Ansi bgBrightYellow() {
+        return this.bgBright(Color.YELLOW);
+    }
+
+    public Ansi a(Attribute attribute) {
+        attributeOptions.add(attribute.value());
+        return this;
+    }
+
+    /**
+     * Moves the cursor to row n, column m. The values are 1-based.
+     * Any values less than 1 are mapped to 1.
+     *
+     * @param row    row (1-based) from top
+     * @param column column (1 based) from left
+     * @return this Ansi instance
+     */
+    public Ansi cursor(final int row, final int column) {
+        return appendEscapeSequence('H', Math.max(1, row), Math.max(1, 
column));
+    }
+
+    /**
+     * Moves the cursor to column n. The parameter n is 1-based.
+     * If n is less than 1 it is moved to the first column.
+     *
+     * @param x the index (1-based) of the column to move to
+     * @return this Ansi instance
+     */
+    public Ansi cursorToColumn(final int x) {
+        return appendEscapeSequence('G', Math.max(1, x));
+    }
+
+    /**
+     * Moves the cursor up. If the parameter y is negative it moves the cursor 
down.
+     *
+     * @param y the number of lines to move up
+     * @return this Ansi instance
+     */
+    public Ansi cursorUp(final int y) {
+        return y > 0 ? appendEscapeSequence('A', y) : y < 0 ? cursorDown(-y) : 
this;
+    }
+
+    /**
+     * Moves the cursor down. If the parameter y is negative it moves the 
cursor up.
+     *
+     * @param y the number of lines to move down
+     * @return this Ansi instance
+     */
+    public Ansi cursorDown(final int y) {
+        return y > 0 ? appendEscapeSequence('B', y) : y < 0 ? cursorUp(-y) : 
this;
+    }
+
+    /**
+     * Moves the cursor right. If the parameter x is negative it moves the 
cursor left.
+     *
+     * @param x the number of characters to move right
+     * @return this Ansi instance
+     */
+    public Ansi cursorRight(final int x) {
+        return x > 0 ? appendEscapeSequence('C', x) : x < 0 ? cursorLeft(-x) : 
this;
+    }
+
+    /**
+     * Moves the cursor left. If the parameter x is negative it moves the 
cursor right.
+     *
+     * @param x the number of characters to move left
+     * @return this Ansi instance
+     */
+    public Ansi cursorLeft(final int x) {
+        return x > 0 ? appendEscapeSequence('D', x) : x < 0 ? cursorRight(-x) 
: this;
+    }
+
+    /**
+     * Moves the cursor relative to the current position. The cursor is moved 
right if x is
+     * positive, left if negative and down if y is positive and up if negative.
+     *
+     * @param x the number of characters to move horizontally
+     * @param y the number of lines to move vertically
+     * @return this Ansi instance
+     * @since 2.2
+     */
+    public Ansi cursorMove(final int x, final int y) {
+        return cursorRight(x).cursorDown(y);
+    }
+
+    /**
+     * Moves the cursor to the beginning of the line below.
+     *
+     * @return this Ansi instance
+     */
+    public Ansi cursorDownLine() {
+        return appendEscapeSequence('E');
+    }
+
+    /**
+     * Moves the cursor to the beginning of the n-th line below. If the 
parameter n is negative it
+     * moves the cursor to the beginning of the n-th line above.
+     *
+     * @param n the number of lines to move the cursor
+     * @return this Ansi instance
+     */
+    public Ansi cursorDownLine(final int n) {
+        return n < 0 ? cursorUpLine(-n) : appendEscapeSequence('E', n);
+    }
+
+    /**
+     * Moves the cursor to the beginning of the line above.
+     *
+     * @return this Ansi instance
+     */
+    public Ansi cursorUpLine() {
+        return appendEscapeSequence('F');
+    }
+
+    /**
+     * Moves the cursor to the beginning of the n-th line above. If the 
parameter n is negative it
+     * moves the cursor to the beginning of the n-th line below.
+     *
+     * @param n the number of lines to move the cursor
+     * @return this Ansi instance
+     */
+    public Ansi cursorUpLine(final int n) {
+        return n < 0 ? cursorDownLine(-n) : appendEscapeSequence('F', n);
+    }
+
+    public Ansi eraseScreen() {
+        return appendEscapeSequence('J', Erase.ALL.value());
+    }
+
+    public Ansi eraseScreen(final Erase kind) {
+        return appendEscapeSequence('J', kind.value());
+    }
+
+    public Ansi eraseLine() {
+        return appendEscapeSequence('K');
+    }
+
+    public Ansi eraseLine(final Erase kind) {
+        return appendEscapeSequence('K', kind.value());
+    }
+
+    public Ansi scrollUp(final int rows) {
+        if (rows == Integer.MIN_VALUE) {
+            return scrollDown(Integer.MAX_VALUE);
+        }
+        return rows > 0 ? appendEscapeSequence('S', rows) : rows < 0 ? 
scrollDown(-rows) : this;
+    }
+
+    public Ansi scrollDown(final int rows) {
+        if (rows == Integer.MIN_VALUE) {
+            return scrollUp(Integer.MAX_VALUE);
+        }
+        return rows > 0 ? appendEscapeSequence('T', rows) : rows < 0 ? 
scrollUp(-rows) : this;
+    }
+
+    @Deprecated
+    public Ansi restorCursorPosition() {
+        return restoreCursorPosition();
+    }
+
+    public Ansi saveCursorPosition() {
+        saveCursorPositionSCO();
+        return saveCursorPositionDEC();
+    }
+
+    // SCO command
+    public Ansi saveCursorPositionSCO() {
+        return appendEscapeSequence('s');
+    }
+
+    // DEC command
+    public Ansi saveCursorPositionDEC() {
+        builder.append(FIRST_ESC_CHAR);
+        builder.append('7');
+        return this;
+    }
+
+    public Ansi restoreCursorPosition() {
+        restoreCursorPositionSCO();
+        return restoreCursorPositionDEC();
+    }
+
+    // SCO command
+    public Ansi restoreCursorPositionSCO() {
+        return appendEscapeSequence('u');
+    }
+
+    // DEC command
+    public Ansi restoreCursorPositionDEC() {
+        builder.append(FIRST_ESC_CHAR);
+        builder.append('8');
+        return this;
+    }
+
+    public Ansi reset() {
+        return a(Attribute.RESET);
+    }
+
+    public Ansi bold() {
+        return a(Attribute.INTENSITY_BOLD);
+    }
+
+    public Ansi boldOff() {
+        return a(Attribute.INTENSITY_BOLD_OFF);
+    }
+
+    public Ansi a(String value) {
+        flushAttributes();
+        builder.append(value);
+        return this;
+    }
+
+    public Ansi a(boolean value) {
+        flushAttributes();
+        builder.append(value);
+        return this;
+    }
+
+    public Ansi a(char value) {
+        flushAttributes();
+        builder.append(value);
+        return this;
+    }
+
+    public Ansi a(char[] value, int offset, int len) {
+        flushAttributes();
+        builder.append(value, offset, len);
+        return this;
+    }
+
+    public Ansi a(char[] value) {
+        flushAttributes();
+        builder.append(value);
+        return this;
+    }
+
+    public Ansi a(CharSequence value, int start, int end) {
+        flushAttributes();
+        builder.append(value, start, end);
+        return this;
+    }
+
+    public Ansi a(CharSequence value) {
+        flushAttributes();
+        builder.append(value);
+        return this;
+    }
+
+    public Ansi a(double value) {
+        flushAttributes();
+        builder.append(value);
+        return this;
+    }
+
+    public Ansi a(float value) {
+        flushAttributes();
+        builder.append(value);
+        return this;
+    }
+
+    public Ansi a(int value) {
+        flushAttributes();
+        builder.append(value);
+        return this;
+    }
+
+    public Ansi a(long value) {
+        flushAttributes();
+        builder.append(value);
+        return this;
+    }
+
+    public Ansi a(Object value) {
+        flushAttributes();
+        builder.append(value);
+        return this;
+    }
+
+    public Ansi a(StringBuffer value) {
+        flushAttributes();
+        builder.append(value);
+        return this;
+    }
+
+    public Ansi newline() {
+        flushAttributes();
+        builder.append(System.getProperty("line.separator"));
+        return this;
+    }
+
+    public Ansi format(String pattern, Object... args) {
+        flushAttributes();
+        builder.append(String.format(pattern, args));
+        return this;
+    }
+
+    /**
+     * Applies another function to this Ansi instance.
+     *
+     * @param fun the function to apply
+     * @return this Ansi instance
+     * @since 2.2
+     */
+    public Ansi apply(Consumer fun) {
+        fun.apply(this);
+        return this;
+    }
+
+    /**
+     * Uses the {@link org.jline.jansi.AnsiRenderer}
+     * to generate the ANSI escape sequences for the supplied text.
+     *
+     * @param text text
+     * @return this
+     * @since 2.2
+     */
+    public Ansi render(final String text) {
+        a(new org.jline.jansi.Ansi().render(text).toString());
+        return this;
+    }
+
+    /**
+     * String formats and renders the supplied arguments.  Uses the {@link 
org.jline.jansi.AnsiRenderer}
+     * to generate the ANSI escape sequences.
+     *
+     * @param text format
+     * @param args arguments
+     * @return this
+     * @since 2.2
+     */
+    public Ansi render(final String text, Object... args) {
+        a(String.format(new org.jline.jansi.Ansi().render(text).toString(), 
args));
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        flushAttributes();
+        return builder.toString();
+    }
+
+    ///////////////////////////////////////////////////////////////////
+    // Private Helper Methods
+    ///////////////////////////////////////////////////////////////////
+
+    private Ansi appendEscapeSequence(char command) {
+        flushAttributes();
+        builder.append(FIRST_ESC_CHAR);
+        builder.append(SECOND_ESC_CHAR);
+        builder.append(command);
+        return this;
+    }
+
+    private Ansi appendEscapeSequence(char command, int option) {
+        flushAttributes();
+        builder.append(FIRST_ESC_CHAR);
+        builder.append(SECOND_ESC_CHAR);
+        builder.append(option);
+        builder.append(command);
+        return this;
+    }
+
+    private Ansi appendEscapeSequence(char command, Object... options) {
+        flushAttributes();
+        return doAppendEscapeSequence(command, options);
+    }
+
+    private void flushAttributes() {
+        if (attributeOptions.isEmpty()) {
+            return;
+        }
+        if (attributeOptions.size() == 1 && attributeOptions.get(0) == 0) {
+            builder.append(FIRST_ESC_CHAR);
+            builder.append(SECOND_ESC_CHAR);
+            builder.append('m');
+        } else {
+            doAppendEscapeSequence('m', attributeOptions.toArray());
+        }
+        attributeOptions.clear();
+    }
+
+    private Ansi doAppendEscapeSequence(char command, Object... options) {
+        builder.append(FIRST_ESC_CHAR);
+        builder.append(SECOND_ESC_CHAR);
+        int size = options.length;
+        for (int i = 0; i < size; i++) {
+            if (i != 0) {
+                builder.append(';');
+            }
+            if (options[i] != null) {
+                builder.append(options[i]);
+            }
+        }
+        builder.append(command);
+        return this;
+    }
+
+    @Override
+    public Ansi append(CharSequence csq) {
+        builder.append(csq);
+        return this;
+    }
+
+    @Override
+    public Ansi append(CharSequence csq, int start, int end) {
+        builder.append(csq, start, end);
+        return this;
+    }
+
+    @Override
+    public Ansi append(char c) {
+        builder.append(c);
+        return this;
+    }
+}
diff --git 
a/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java 
b/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java
index a68976bb37..318ca6a4f9 100644
--- a/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java
+++ b/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java
@@ -35,7 +35,7 @@ import org.apache.commons.cli.Option;
 import org.apache.commons.cli.Options;
 import org.apache.commons.cli.ParseException;
 import org.apache.maven.Maven;
-import org.apache.maven.cli.jansi.MessageUtils;
+import org.apache.maven.cli.jline.MessageUtils;
 import org.apache.maven.cli.transfer.ConsoleMavenTransferListener;
 import org.apache.maven.cli.transfer.QuietMavenTransferListener;
 import org.apache.maven.cli.transfer.Slf4jMavenTransferListener;
@@ -500,7 +500,7 @@ class MavenCliTest {
         String versionOut = new String(systemOut.toByteArray(), 
StandardCharsets.UTF_8);
 
         // then
-        assertEquals(MessageUtils.stripAnsiCodes(versionOut), versionOut);
+        assertEquals(stripAnsiCodes(versionOut), versionOut);
     }
 
     @Test
@@ -675,4 +675,8 @@ class MavenCliTest {
         project.setArtifactId(artifactId);
         return project;
     }
+
+    static String stripAnsiCodes(String msg) {
+        return msg.replaceAll("\u001b\\[[;\\d]*[ -/]*[@-~]", "");
+    }
 }
diff --git 
a/maven-embedder/src/test/java/org/apache/maven/cli/event/ExecutionEventLoggerTest.java
 
b/maven-embedder/src/test/java/org/apache/maven/cli/event/ExecutionEventLoggerTest.java
index 3e260124b5..4ae5326100 100644
--- 
a/maven-embedder/src/test/java/org/apache/maven/cli/event/ExecutionEventLoggerTest.java
+++ 
b/maven-embedder/src/test/java/org/apache/maven/cli/event/ExecutionEventLoggerTest.java
@@ -22,8 +22,8 @@ import java.io.File;
 
 import com.google.common.collect.ImmutableList;
 import org.apache.commons.io.FilenameUtils;
-import org.apache.maven.cli.jansi.JansiMessageBuilderFactory;
-import org.apache.maven.cli.jansi.MessageUtils;
+import org.apache.maven.cli.jline.JLineMessageBuilderFactory;
+import org.apache.maven.cli.jline.MessageUtils;
 import org.apache.maven.execution.ExecutionEvent;
 import org.apache.maven.execution.MavenSession;
 import org.apache.maven.project.MavenProject;
@@ -45,7 +45,7 @@ class ExecutionEventLoggerTest {
 
     private Logger logger;
     private ExecutionEventLogger executionEventLogger;
-    private JansiMessageBuilderFactory messageBuilderFactory = new 
JansiMessageBuilderFactory();
+    private JLineMessageBuilderFactory messageBuilderFactory = new 
JLineMessageBuilderFactory();
 
     @BeforeAll
     static void setUp() {
diff --git 
a/maven-embedder/src/test/java/org/apache/maven/cli/transfer/ConsoleMavenTransferListenerTest.java
 
b/maven-embedder/src/test/java/org/apache/maven/cli/transfer/ConsoleMavenTransferListenerTest.java
index 095124052c..cc726cb71f 100644
--- 
a/maven-embedder/src/test/java/org/apache/maven/cli/transfer/ConsoleMavenTransferListenerTest.java
+++ 
b/maven-embedder/src/test/java/org/apache/maven/cli/transfer/ConsoleMavenTransferListenerTest.java
@@ -26,6 +26,7 @@ import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
+import org.apache.maven.cli.jline.JLineMessageBuilderFactory;
 import org.eclipse.aether.DefaultRepositorySystemSession;
 import org.eclipse.aether.transfer.TransferCancelledException;
 import org.eclipse.aether.transfer.TransferEvent;
@@ -48,6 +49,7 @@ class ConsoleMavenTransferListenerTest {
         Map<String, String> output = new ConcurrentHashMap<String, String>();
 
         ConsoleMavenTransferListener listener = new 
ConsoleMavenTransferListener(
+                new JLineMessageBuilderFactory(),
                 new PrintStream(System.out) {
 
                     @Override
diff --git 
a/maven-slf4j-provider/src/main/java/org/slf4j/impl/MavenSimpleLogger.java 
b/maven-slf4j-provider/src/main/java/org/slf4j/impl/MavenSimpleLogger.java
index 988b2239bc..06a689146d 100644
--- a/maven-slf4j-provider/src/main/java/org/slf4j/impl/MavenSimpleLogger.java
+++ b/maven-slf4j-provider/src/main/java/org/slf4j/impl/MavenSimpleLogger.java
@@ -22,7 +22,7 @@ import java.io.PrintStream;
 
 import org.apache.maven.api.services.MessageBuilder;
 
-import static org.apache.maven.cli.jansi.MessageUtils.builder;
+import static org.apache.maven.cli.jline.MessageUtils.builder;
 
 /**
  * Logger for Maven, that support colorization of levels and stacktraces. This 
class implements 2 methods introduced in
diff --git 
a/maven-slf4j-provider/src/test/java/org/slf4j/impl/MavenSimpleLoggerTest.java 
b/maven-slf4j-provider/src/test/java/org/slf4j/impl/MavenSimpleLoggerTest.java
index b51decb6b9..4fbb3d1498 100644
--- 
a/maven-slf4j-provider/src/test/java/org/slf4j/impl/MavenSimpleLoggerTest.java
+++ 
b/maven-slf4j-provider/src/test/java/org/slf4j/impl/MavenSimpleLoggerTest.java
@@ -24,6 +24,9 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.NoSuchElementException;
 
+import org.apache.maven.cli.jline.MessageUtils;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInfo;
 
@@ -32,6 +35,19 @@ import static 
org.junit.jupiter.api.Assertions.assertLinesMatch;
 
 class MavenSimpleLoggerTest {
 
+    boolean colorEnabled;
+
+    @BeforeEach
+    void setup() {
+        colorEnabled = MessageUtils.isColorEnabled();
+        MessageUtils.setColorEnabled(false);
+    }
+
+    @AfterEach
+    void taerdown() {
+        MessageUtils.setColorEnabled(colorEnabled);
+    }
+
     @Test
     void includesCauseAndSuppressedExceptionsWhenWritingThrowables(TestInfo 
testInfo) throws Exception {
         Exception causeOfSuppressed = new NoSuchElementException("cause of 
suppressed");
diff --git a/pom.xml b/pom.xml
index eb32ccebcc..13d6a5844a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -167,12 +167,13 @@ under the License.
     <guavafailureaccessVersion>1.0.1</guavafailureaccessVersion>
     <hamcrestVersion>2.2</hamcrestVersion>
     <jakartaInjectApiVersion>2.0.1</jakartaInjectApiVersion>
-    <jansiVersion>2.4.1</jansiVersion>
     <javaxAnnotationApiVersion>1.3.2</javaxAnnotationApiVersion>
+    <jlineVersion>3.25.0</jlineVersion>
     <junitVersion>5.10.1</junitVersion>
     <jxpathVersion>1.3</jxpathVersion>
     <logbackClassicVersion>1.2.13</logbackClassicVersion>
     <mockitoVersion>5.7.0</mockitoVersion>
+    <plexusInteractivityVersion>1.1</plexusInteractivityVersion>
     <plexusInterpolationVersion>1.26</plexusInterpolationVersion>
     <plexusTestingVersion>1.0.0</plexusTestingVersion>
     <plexusXmlVersion>4.0.1</plexusXmlVersion>
@@ -309,9 +310,14 @@ under the License.
         <version>${plexusInterpolationVersion}</version>
       </dependency>
       <dependency>
-        <groupId>org.fusesource.jansi</groupId>
-        <artifactId>jansi</artifactId>
-        <version>${jansiVersion}</version>
+        <groupId>org.codehaus.plexus</groupId>
+        <artifactId>plexus-interactivity-api</artifactId>
+        <version>${plexusInteractivityVersion}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.jline</groupId>
+        <artifactId>jline</artifactId>
+        <version>${jlineVersion}</version>
       </dependency>
       <dependency>
         <groupId>org.slf4j</groupId>
@@ -690,6 +696,9 @@ under the License.
                   <ignoredScopes>
                     <ignoredScope>test</ignoredScope>
                   </ignoredScopes>
+                  <excludes>
+                    <exclude>org.jline:jline</exclude>
+                  </excludes>
                 </enforceBytecodeVersion>
               </rules>
               <fail>true</fail>


Reply via email to