Repository: Textualize/rich
Pull request #2332 (2022-06-09 13:30:16)

Allow Jupyter to vary width/height using COLUMNS/LINES
## Type of changes

- [ ] Bug fix
- [x] New feature
- [ ] Documentation / docstrings
- [ ] Tests
- [ ] Other

## Checklist

- [x] I've run the latest [black](https://github.com/psf/black) with default args on new code.
- [x] I've updated CHANGELOG.md and CONTRIBUTORS.md where appropriate.
- [x] I've added tests for new code.
- [x] I accept that @willmcgugan may be pedantic in the code review.

## Description

I'm opening this is a very small PR just to get initial feedback on whether it sounds like a good idea. If the response is positive then I will update tests as required.

`COLUMNS` and `LINES` environment-variables can already be used to set `Console._width` and `Console._height`: https://github.com/Textualize/rich/blob/51889e2e663bbda8d59b2a68f23322f578681cea/rich/console.py#L676-L683

However, these environment variables are ignored when using Jupyter. If `width` or `height` are not explicitly set in `__init__`  then rich assumes default values: https://github.com/Textualize/rich/blob/51889e2e663bbda8d59b2a68f23322f578681cea/rich/console.py#L658-L660

This PR makes it so that `COLUMNS` and `LINES` can be used to set `Console._width` and `Console._height` even when  `self.is_jupyter` is `True`. `width`/`height` arguments still take precedence over the environment variables, exactly the same as when outside Jupyter.

## Further context

We are very excited to start usubg this beautiful library in kedro, an open source data science pipeline framework (see https://github.com/kedro-org/kedro/issues/1464 if you're interested). A core part of that is to use the rich logging handler, which we love.

When the rich logging handler is used in Jupyter, the fixed width of 93 characters wraps messages too narrowly for our needs. This has been brought up before (https://github.com/Textualize/rich/issues/504 https://bytemeta.vip/repo/willmcgugan/rich/issues/1870 https://github.com/Textualize/rich/discussions/344). From these I understand that rich doesn't know the width of the screen in Jupyter and why another custom logger will not be added to the package. (Although I'm not sure exactly where the default value of `width = 93` comes from, and looking through git history couldn't answer this? Would be curious to know.) We also do not want users to need to supply a custom logging handler just to accommodate this case.

By making `COLUMNS` and `LINES` recognised in Jupyter, I think it would immediately address several people's requirements without any real extra complexity being added to rich. So this seems like an easy win to me, but I'm very happy to receive an explanation of what I've missed here if it's not a good idea!


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 70652e14..02f49528 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
+### Added
+
+- Environment variables `JUPYTER_COLUMNS` and `JUPYTER_LINES` to control width and height of console in Jupyter
+
+### Changed
+
+- Default width of Jupyter console size is increased to 115
+
 ### Fixed
 
 - Fix text wrapping edge case https://github.com/Textualize/rich/pull/2296
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index c24f21b1..0c150113 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -24,6 +24,7 @@ The following people have contributed to the development of Rich:
 - [Alexander Mancevice](https://github.com/amancevice)
 - [Will McGugan](https://github.com/willmcgugan)
 - [Paul McGuire](https://github.com/ptmcg)
+- [Antony Milne](https://github.com/AntonyMilneQB)
 - [Nathan Page](https://github.com/nathanrpage97)
 - [Avi Perl](https://github.com/avi-perl)
 - [Laurent Peuch](https://github.com/psycojoker)
diff --git a/docs/source/console.rst b/docs/source/console.rst
index d3df2828..c6fc8d84 100644
--- a/docs/source/console.rst
+++ b/docs/source/console.rst
@@ -421,3 +421,5 @@ Rich respects some standard environment variables.
 Setting the environment variable ``TERM`` to ``"dumb"`` or ``"unknown"`` will disable color/style and some features that require moving the cursor, such as progress bars.
 
 If the environment variable ``NO_COLOR`` is set, Rich will disable all color in the output.
+
+If ``width``/``height`` arguments are not explicitly provided as arguments to ``Console`` then the environment variables ``COLUMNS``/``LINES`` can be used to set the console width/height. ``JUPYTER_COLUMNS``/``JUPYTER_LINES`` behave similarly and are used in Jupyter.
diff --git a/rich/console.py b/rich/console.py
index eba27611..da6becb8 100644
--- a/rich/console.py
+++ b/rich/console.py
@@ -72,6 +72,8 @@ if TYPE_CHECKING:
     from .live import Live
     from .status import Status
 
+JUPYTER_DEFAULT_COLUMNS = 115
+JUPYTER_DEFAULT_LINES = 100
 WINDOWS = platform.system() == "Windows"
 
 HighlighterType = Callable[[Union[str, "Text"]], "Text"]
@@ -656,8 +658,18 @@ class Console:
 
         self.is_jupyter = _is_jupyter() if force_jupyter is None else force_jupyter
         if self.is_jupyter:
-            width = width or 93
-            height = height or 100
+            if width is None:
+                jupyter_columns = self._environ.get("JUPYTER_COLUMNS")
+                if jupyter_columns is not None and jupyter_columns.isdigit():
+                    width = int(jupyter_columns)
+                else:
+                    width = JUPYTER_DEFAULT_COLUMNS
+            if height is None:
+                jupyter_lines = self._environ.get("JUPYTER_LINES")
+                if jupyter_lines is not None and jupyter_lines.isdigit():
+                    height = int(jupyter_lines)
+                else:
+                    height = JUPYTER_DEFAULT_LINES
 
         self.soft_wrap = soft_wrap
         self._width = width
diff --git a/rich/diagnose.py b/rich/diagnose.py
index 09c4e062..91e55bd8 100644
--- a/rich/diagnose.py
+++ b/rich/diagnose.py
@@ -22,6 +22,8 @@ def report() -> None:  # pragma: no cover
         "TERM_PROGRAM",
         "COLUMNS",
         "LINES",
+        "JUPYTER_COLUMNS",
+        "JUPYTER_LINES",
         "JPY_PARENT_PID",
         "VSCODE_VERBOSE_LOGGING",
     )


Test patch:
diff --git a/tests/test_jupyter.py b/tests/test_jupyter.py
index a69f211b..7c327e62 100644
--- a/tests/test_jupyter.py
+++ b/tests/test_jupyter.py
@@ -3,5 +3,30 @@ from rich.console import Console
 
 def test_jupyter():
     console = Console(force_jupyter=True)
-    assert console.width == 93
+    assert console.width == 115
+    assert console.height == 100
     assert console.color_system == "truecolor"
+
+
+def test_jupyter_columns_env():
+    console = Console(_environ={"JUPYTER_COLUMNS": "314"}, force_jupyter=True)
+    assert console.width == 314
+    # width take precedence
+    console = Console(width=40, _environ={"JUPYTER_COLUMNS": "314"}, force_jupyter=True)
+    assert console.width == 40
+    # Should not fail
+    console = Console(
+        width=40, _environ={"JUPYTER_COLUMNS": "broken"}, force_jupyter=True
+    )
+
+
+def test_jupyter_lines_env():
+    console = Console(_environ={"JUPYTER_LINES": "220"}, force_jupyter=True)
+    assert console.height == 220
+    # height take precedence
+    console = Console(height=40, _environ={"JUPYTER_LINES": "220"}, force_jupyter=True)
+    assert console.height == 40
+    # Should not fail
+    console = Console(
+        width=40, _environ={"JUPYTER_LINES": "broken"}, force_jupyter=True
+    )