Repository: pallets/click

Problem statement:
Use of Setuptools' "extras_require" for colorama?
Might it make sense to use Setuptools' ["extras"](https://setuptools.readthedocs.io/en/latest/setuptools.html#declaring-extras-optional-features-with-their-own-dependencies) feature for automatically installing colorama on Windows?

Currently the docs contain fragments to the effect of "_if_ colorama is installed," and advise the user to install colorama if they wish to lend click extra functionality on Windows.

So, might a

```python
extras_require={"wincolors":  ["colorama"]}
```

allowing users to `pip install click[wincolors]` be a better option?


Hints:
I think in this case I'd like to just make it an automatically installed dependency on Windows.

Patch:
diff --git a/examples/colors/colors.py b/examples/colors/colors.py
--- a/examples/colors/colors.py
+++ b/examples/colors/colors.py
@@ -23,9 +23,8 @@
 
 @click.command()
 def cli():
-    """This script prints some colors.  If colorama is installed this will
-    also work on Windows.  It will also automatically remove all ANSI
-    styles if data is piped into a file.
+    """This script prints some colors. It will also automatically remove
+    all ANSI styles if data is piped into a file.
 
     Give it a try!
     """
diff --git a/examples/colors/setup.py b/examples/colors/setup.py
--- a/examples/colors/setup.py
+++ b/examples/colors/setup.py
@@ -5,11 +5,7 @@
     version="1.0",
     py_modules=["colors"],
     include_package_data=True,
-    install_requires=[
-        "click",
-        # Colorama is only required for Windows.
-        "colorama",
-    ],
+    install_requires=["click"],
     entry_points="""
         [console_scripts]
         colors=colors:cli
diff --git a/examples/termui/setup.py b/examples/termui/setup.py
--- a/examples/termui/setup.py
+++ b/examples/termui/setup.py
@@ -5,11 +5,7 @@
     version="1.0",
     py_modules=["termui"],
     include_package_data=True,
-    install_requires=[
-        "click",
-        # Colorama is only required for Windows.
-        "colorama",
-    ],
+    install_requires=["click"],
     entry_points="""
         [console_scripts]
         termui=termui:cli
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -1,3 +1,3 @@
 from setuptools import setup
 
-setup(name="click")
+setup(name="click", install_requires=["colorama; platform_system == 'Windows'"])
diff --git a/src/click/_compat.py b/src/click/_compat.py
--- a/src/click/_compat.py
+++ b/src/click/_compat.py
@@ -490,9 +490,8 @@ def should_strip_ansi(stream=None, color=None):
     return not color
 
 
-# If we're on Windows, we provide transparent integration through
-# colorama.  This will make ANSI colors through the echo function
-# work automatically.
+# On Windows, wrap the output streams with colorama to support ANSI
+# color codes.
 # NOTE: double check is needed so mypy does not analyze this on Linux
 if sys.platform.startswith("win") and WIN:
     from ._winconsole import _get_windows_console_stream
@@ -502,47 +501,44 @@ def _get_argv_encoding():
 
         return locale.getpreferredencoding()
 
-    try:
+    _ansi_stream_wrappers: t.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary()
+
+    def auto_wrap_for_ansi(
+        stream: t.TextIO, color: t.Optional[bool] = None
+    ) -> t.TextIO:
+        """Support ANSI color and style codes on Windows by wrapping a
+        stream with colorama.
+        """
+        try:
+            cached = _ansi_stream_wrappers.get(stream)
+        except Exception:
+            cached = None
+
+        if cached is not None:
+            return cached
+
         import colorama
-    except ImportError:
-        pass
-    else:
-        _ansi_stream_wrappers: t.MutableMapping[
-            t.TextIO, t.TextIO
-        ] = WeakKeyDictionary()
-
-        def auto_wrap_for_ansi(
-            stream: t.TextIO, color: t.Optional[bool] = None
-        ) -> t.TextIO:
-            """This function wraps a stream so that calls through colorama
-            are issued to the win32 console API to recolor on demand.  It
-            also ensures to reset the colors if a write call is interrupted
-            to not destroy the console afterwards.
-            """
-            try:
-                cached = _ansi_stream_wrappers.get(stream)
-            except Exception:
-                cached = None
-            if cached is not None:
-                return cached
-            strip = should_strip_ansi(stream, color)
-            ansi_wrapper = colorama.AnsiToWin32(stream, strip=strip)
-            rv = ansi_wrapper.stream
-            _write = rv.write
-
-            def _safe_write(s):
-                try:
-                    return _write(s)
-                except BaseException:
-                    ansi_wrapper.reset_all()
-                    raise
-
-            rv.write = _safe_write
+
+        strip = should_strip_ansi(stream, color)
+        ansi_wrapper = colorama.AnsiToWin32(stream, strip=strip)
+        rv = ansi_wrapper.stream
+        _write = rv.write
+
+        def _safe_write(s):
             try:
-                _ansi_stream_wrappers[stream] = rv
-            except Exception:
-                pass
-            return rv
+                return _write(s)
+            except BaseException:
+                ansi_wrapper.reset_all()
+                raise
+
+        rv.write = _safe_write
+
+        try:
+            _ansi_stream_wrappers[stream] = rv
+        except Exception:
+            pass
+
+        return rv
 
 
 else:
diff --git a/src/click/termui.py b/src/click/termui.py
--- a/src/click/termui.py
+++ b/src/click/termui.py
@@ -418,9 +418,6 @@ def clear():
     """
     if not isatty(sys.stdout):
         return
-    # If we're on Windows and we don't have colorama available, then we
-    # clear the screen by shelling out.  Otherwise we can use an escape
-    # sequence.
     if WIN:
         os.system("cls")
     else:
diff --git a/src/click/utils.py b/src/click/utils.py
--- a/src/click/utils.py
+++ b/src/click/utils.py
@@ -172,51 +172,43 @@ def __iter__(self):
 
 
 def echo(message=None, file=None, nl=True, err=False, color=None):
-    """Prints a message plus a newline to the given file or stdout.  On
-    first sight, this looks like the print function, but it has improved
-    support for handling Unicode and binary data that does not fail no
-    matter how badly configured the system is.
-
-    Primarily it means that you can print binary data as well as Unicode
-    data on both 2.x and 3.x to the given file in the most appropriate way
-    possible.  This is a very carefree function in that it will try its
-    best to not fail.  As of Click 6.0 this includes support for unicode
-    output on the Windows console.
-
-    In addition to that, if `colorama`_ is installed, the echo function will
-    also support clever handling of ANSI codes.  Essentially it will then
-    do the following:
-
-    -   add transparent handling of ANSI color codes on Windows.
-    -   hide ANSI codes automatically if the destination file is not a
-        terminal.
-
-    .. _colorama: https://pypi.org/project/colorama/
+    """Print a message and newline to stdout or a file. This should be
+    used instead of :func:`print` because it provides better support
+    for different data, files, and environments.
+
+    Compared to :func:`print`, this does the following:
+
+    -   Ensures that the output encoding is not misconfigured on Linux.
+    -   Supports Unicode in the Windows console.
+    -   Supports writing to binary outputs, and supports writing bytes
+        to text outputs.
+    -   Supports colors and styles on Windows.
+    -   Removes ANSI color and style codes if the output does not look
+        like an interactive terminal.
+    -   Always flushes the output.
+
+    :param message: The string or bytes to output. Other objects are
+        converted to strings.
+    :param file: The file to write to. Defaults to ``stdout``.
+    :param err: Write to ``stderr`` instead of ``stdout``.
+    :param nl: Print a newline after the message. Enabled by default.
+    :param color: Force showing or hiding colors and other styles. By
+        default Click will remove color if the output does not look like
+        an interactive terminal.
 
     .. versionchanged:: 6.0
-       As of Click 6.0 the echo function will properly support unicode
-       output on the windows console.  Not that click does not modify
-       the interpreter in any way which means that `sys.stdout` or the
-       print statement or function will still not provide unicode support.
+        Support Unicode output on the Windows console. Click does not
+        modify ``sys.stdout``, so ``sys.stdout.write()`` and ``print()``
+        will still not support Unicode.
 
-    .. versionchanged:: 2.0
-       Starting with version 2.0 of Click, the echo function will work
-       with colorama if it's installed.
+    .. versionchanged:: 4.0
+        Added the ``color`` parameter.
 
     .. versionadded:: 3.0
-       The `err` parameter was added.
+        Added the ``err`` parameter.
 
-    .. versionchanged:: 4.0
-       Added the `color` flag.
-
-    :param message: the message to print
-    :param file: the file to write to (defaults to ``stdout``)
-    :param err: if set to true the file defaults to ``stderr`` instead of
-                ``stdout``.  This is faster and easier than calling
-                :func:`get_text_stderr` yourself.
-    :param nl: if set to `True` (the default) a newline is printed afterwards.
-    :param color: controls if the terminal supports ANSI colors or not.  The
-                  default is autodetection.
+    .. versionchanged:: 2.0
+        Support colors on Windows if colorama is installed.
     """
     if file is None:
         if err:
@@ -247,11 +239,8 @@ def echo(message=None, file=None, nl=True, err=False, color=None):
             binary_file.flush()
             return
 
-    # ANSI-style support.  If there is no message or we are dealing with
-    # bytes nothing is happening.  If we are connected to a file we want
-    # to strip colors.  If we are on windows we either wrap the stream
-    # to strip the color or we use the colorama support to translate the
-    # ansi codes to API calls.
+    # ANSI style code support. For no message or bytes, nothing happens.
+    # When outputting to a file instead of a terminal, strip codes.
     if message and not is_bytes(message):
         color = resolve_color_default(color)
         if should_strip_ansi(file, color):


Test patch:
diff --git a/requirements/tests.in b/requirements/tests.in
--- a/requirements/tests.in
+++ b/requirements/tests.in
@@ -1,3 +1,2 @@
 pytest
-colorama
 importlib_metadata
diff --git a/requirements/tests.txt b/requirements/tests.txt
--- a/requirements/tests.txt
+++ b/requirements/tests.txt
@@ -6,8 +6,6 @@
 #
 attrs==20.3.0
     # via pytest
-colorama==0.4.4
-    # via -r requirements/tests.in
 importlib-metadata==3.4.0
     # via -r requirements/tests.in
 iniconfig==1.1.1
diff --git a/tests/test_imports.py b/tests/test_imports.py
--- a/tests/test_imports.py
+++ b/tests/test_imports.py
@@ -41,7 +41,6 @@ def tracking_import(module, locals=None, globals=None, fromlist=None,
     "itertools",
     "io",
     "threading",
-    "colorama",
     "errno",
     "fcntl",
     "datetime",
@@ -51,7 +50,7 @@ def tracking_import(module, locals=None, globals=None, fromlist=None,
 }
 
 if WIN:
-    ALLOWED_IMPORTS.update(["ctypes", "ctypes.wintypes", "msvcrt", "time", "zlib"])
+    ALLOWED_IMPORTS.update(["ctypes", "ctypes.wintypes", "msvcrt", "time"])
 
 
 def test_light_imports():