Repository: Textualize/rich (python)

Issue:
Missing whitespace in traceback
In tracebacks sometimes there is a missing blank line between traceback frames. Not sure what causes it.

Pull request description:
[traceback] Fix "missing whitespace" bug
It turns out that we intentionally don't add such a whitespace at the beginning of the trace rendering, but we were actually doing so for the 2nd item of the stack rather than the 1st one

closes #2206 

## Type of changes

- [x] Bug fix
- [ ] 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

#### Before:

We can see a blank line before the 1st frame of the stack, and a missing blank line before the 2nd one:
![Screenshot from 2022-04-26 11-37-56](https://user-images.githubusercontent.com/722388/165282563-d7041a9d-745f-470b-a247-baebbecf792a.png)


#### After:

The 2nd frame is now properly displayed with a blank line before it, while the 1st frame starts right away at the beginning of the Panel:
![Screenshot from 2022-04-26 11-37-35](https://user-images.githubusercontent.com/722388/165282576-1f04516f-66c6-4677-8e92-05fea992cda6.png)


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 638ede8d..5001a075 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Fixed recursion error in Jupyter progress bars https://github.com/Textualize/rich/issues/2047
 - Complex numbers are now identified by the highlighter https://github.com/Textualize/rich/issues/2214
 - Fix crash on IDLE and forced is_terminal detection to False because IDLE can't do escape codes https://github.com/Textualize/rich/issues/2222
+- Fixed missing blank line in traceback rendering https://github.com/Textualize/rich/issues/2206
 
 ### Changed
 
diff --git a/rich/traceback.py b/rich/traceback.py
index 8f092c66..5feefb93 100644
--- a/rich/traceback.py
+++ b/rich/traceback.py
@@ -584,7 +584,7 @@ class Traceback:
                 )
                 excluded = False
 
-            first = frame_index == 1
+            first = frame_index == 0
             frame_filename = frame.filename
             suppressed = any(frame_filename.startswith(path) for path in self.suppress)
 


Test patch:
diff --git a/tests/test_traceback.py b/tests/test_traceback.py
index e6b4de3f..c4994c87 100644
--- a/tests/test_traceback.py
+++ b/tests/test_traceback.py
@@ -1,4 +1,5 @@
 import io
+import re
 import sys
 
 import pytest
@@ -21,10 +22,17 @@ CAPTURED_EXCEPTION = 'Traceback (most recent call last):\n╭──────
 def test_handler():
     console = Console(file=io.StringIO(), width=100, color_system=None)
     expected_old_handler = sys.excepthook
+
+    def level1():
+        level2()
+
+    def level2():
+        return 1 / 0
+
     try:
         old_handler = install(console=console)
         try:
-            1 / 0
+            level1()
         except Exception:
             exc_type, exc_value, traceback = sys.exc_info()
             sys.excepthook(exc_type, exc_value, traceback)
@@ -32,6 +40,30 @@ def test_handler():
             print(repr(rendered_exception))
             assert "Traceback" in rendered_exception
             assert "ZeroDivisionError" in rendered_exception
+
+            frame_blank_line_possible_preambles = (
+                # Start of the stack rendering:
+                "╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮",
+                # Each subsequent frame (starting with the file name) should then be preceded with a blank line:
+                "│" + (" " * 98) + "│",
+            )
+            for frame_start in re.finditer(
+                "^│ .+rich/tests/test_traceback\.py:",
+                rendered_exception,
+                flags=re.MULTILINE,
+            ):
+                frame_start_index = frame_start.start()
+                for preamble in frame_blank_line_possible_preambles:
+                    preamble_start, preamble_end = (
+                        frame_start_index - len(preamble) - 1,
+                        frame_start_index - 1,
+                    )
+                    if rendered_exception[preamble_start:preamble_end] == preamble:
+                        break
+                else:
+                    pytest.fail(
+                        f"Frame {frame_start[0]} doesn't have the expected preamble"
+                    )
     finally:
         sys.excepthook = old_handler
         assert old_handler == expected_old_handler