# Jupyter Notebook Support

Specialized formatting functions for Jupyter notebooks and individual notebook cells, with support for IPython magics, cell-specific formatting rules, and notebook JSON structure handling.

## Capabilities

### Cell Formatting

Format individual Jupyter notebook cells with special handling for IPython magics and trailing semicolons.

```python { .api }
def format_cell(src: str, *, fast: bool, mode: Mode) -> str:
    """
    Format individual Jupyter notebook cell content.

    Parameters:
    - src: Cell source code to format
    - fast: Skip AST safety checks if True
    - mode: Mode configuration with is_ipynb=True recommended

    Returns:
    Formatted cell source code

    Note:
    - Handles IPython magics (%, %%, !, !!) 
    - Preserves trailing semicolons for output suppression
    - Recognizes python_cell_magics configured in mode
    
    Raises:
    - InvalidInput: If cell source cannot be parsed
    - NothingChanged: If no formatting changes needed
    """
```

### Notebook File Formatting  

Format complete Jupyter notebook JSON strings, processing only Python code cells while preserving notebook structure.

```python { .api }
def format_ipynb_string(src_contents: str, *, fast: bool, mode: Mode) -> str:
    """
    Format complete Jupyter notebook JSON string.

    Parameters:
    - src_contents: Complete notebook JSON as string
    - fast: Skip AST safety checks if True  
    - mode: Mode configuration controlling formatting

    Returns:
    Formatted notebook JSON string

    Note:
    - Only processes cells with Python kernels
    - Preserves all notebook metadata and structure
    - Handles cell magics and IPython-specific syntax
    
    Raises:
    - InvalidInput: If notebook JSON is invalid
    - NothingChanged: If no formatting changes needed
    """
```

### Magic Command Support

Constants and utilities for handling IPython magic commands in notebook cells.

```python { .api }
# Set of recognized Python cell magic commands
PYTHON_CELL_MAGICS: set[str]
```

Common Python cell magics include:
- `%%time` - Time execution of cell
- `%%timeit` - Benchmark cell execution  
- `%%capture` - Capture cell output
- `%%writefile` - Write cell contents to file
- `%%bash` - Execute cell as bash script (when containing Python)
- `%%python` - Explicit Python execution
- `%%script python` - Script execution

## Usage Examples

### Basic Cell Formatting

```python
import black

# Configure mode for Jupyter
mode = black.Mode(is_ipynb=True)

# Format a simple cell
cell_code = """
def calculate(x,y):
    return x+y
result=calculate(5,3)
print(result)
"""

formatted_cell = black.format_cell(cell_code, fast=False, mode=mode)
print(formatted_cell)
# Output: 
# def calculate(x, y):
#     return x + y
# 
# result = calculate(5, 3)
# print(result)
```

### Magic Command Handling

```python
# Cell with magic commands
magic_cell = """
%%time
import numpy as np
data=np.random.rand(1000,1000)
result=data.sum()
print(f"Sum: {result}")
"""

# Configure with custom cell magics
mode = black.Mode(
    is_ipynb=True,
    python_cell_magics={'%%time', '%%timeit', '%%capture', '%%writefile'}
)

formatted = black.format_cell(magic_cell, fast=False, mode=mode)
print(formatted)
# Output:
# %%time
# import numpy as np
# 
# data = np.random.rand(1000, 1000)
# result = data.sum()
# print(f"Sum: {result}")
```

### Trailing Semicolon Preservation

```python
# Cell with output suppression
suppressed_cell = """
import matplotlib.pyplot as plt
plt.plot([1,2,3],[4,5,6]);
plt.show();
"""

formatted = black.format_cell(suppressed_cell, fast=False, mode=mode)
print(formatted)
# Output:
# import matplotlib.pyplot as plt
# 
# plt.plot([1, 2, 3], [4, 5, 6]);
# plt.show();
# Note: Semicolons preserved for output suppression
```

### Complete Notebook Formatting

```python
import json

# Example notebook content
notebook_json = '''
{
  "cells": [
    {
      "cell_type": "code",
      "source": ["def hello(name):print(f'Hello {name}!')"],
      "metadata": {},
      "execution_count": null,
      "outputs": []
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 4
}
'''

# Format the entire notebook
mode = black.Mode(is_ipynb=True, line_length=79)
try:
    formatted_notebook = black.format_ipynb_string(notebook_json, fast=False, mode=mode)
    
    # Parse to verify formatting
    notebook_data = json.loads(formatted_notebook)
    print("Formatted cell source:")
    for cell in notebook_data['cells']:
        if cell['cell_type'] == 'code':
            print(''.join(cell['source']))
            
except black.NothingChanged:
    print("Notebook already properly formatted")
```

### File-based Notebook Formatting

```python
from pathlib import Path

# Format .ipynb file in place
notebook_path = Path("analysis.ipynb")

# Mode automatically detected from .ipynb extension
changed = black.format_file_in_place(
    notebook_path,
    fast=False,
    mode=black.Mode(line_length=88),  # is_ipynb set automatically
    write_back=black.WriteBack.YES
)

if changed:
    print(f"Formatted {notebook_path}")
else:
    print(f"No changes needed for {notebook_path}")
```

### Mixed Magic and Python Code

```python
# Complex cell with multiple magics
complex_cell = '''
%load_ext autoreload
%autoreload 2
import pandas as pd
%%time
df = pd.read_csv("data.csv")
processed = df.groupby("category").sum()
!ls -la processed_data/
%%writefile output.py
print("Results saved")
'''

mode = black.Mode(
    is_ipynb=True,
    python_cell_magics={'%%time', '%%writefile'}
)

formatted = black.format_cell(complex_cell, fast=False, mode=mode)
print(formatted)
# Line magics (%) preserved, cell magics (%%) in python_cell_magics formatted
```

### Custom Magic Configuration

```python
# Define custom set of Python cell magics
custom_magics = {
    '%%time',
    '%%timeit', 
    '%%capture',
    '%%writefile',
    '%%bash',  # If containing Python code
    '%%script python',
    '%%cython',  # For Cython cells with Python syntax
}

mode = black.Mode(
    is_ipynb=True,
    python_cell_magics=custom_magics,
    line_length=100  # Longer lines often preferred in notebooks
)

# Format with custom magic handling  
formatted = black.format_cell(notebook_cell, fast=False, mode=mode)
```

## Error Handling

### Invalid Cell Content

```python
try:
    # Cell with syntax errors
    bad_cell = "def incomplete_function("
    formatted = black.format_cell(bad_cell, fast=False, mode=mode)
except black.InvalidInput as e:
    print(f"Cannot format cell: {e}")
    # Handle syntax errors gracefully
```

### Notebook Structure Issues  

```python
try:
    # Malformed notebook JSON
    bad_notebook = '{"cells": invalid json}'
    formatted = black.format_ipynb_string(bad_notebook, fast=False, mode=mode)
except (json.JSONDecodeError, black.InvalidInput) as e:
    print(f"Invalid notebook format: {e}")
```

## Integration with IPython

```python
# For use in IPython/Jupyter environments
def format_current_cell():
    """Format the current cell in Jupyter notebook."""
    from IPython import get_ipython
    
    ip = get_ipython()
    if ip is None:
        return "Not running in IPython"
    
    # Get current cell content (implementation depends on Jupyter version)
    cell_content = ip.user_ns.get('_i', '')  # Last input
    
    mode = black.Mode(is_ipynb=True)
    try:
        formatted = black.format_cell(cell_content, fast=False, mode=mode) 
        return formatted
    except Exception as e:
        return f"Formatting error: {e}"

# Use as magic command
%load_ext black  # If black provides IPython extension
%black          # Format current cell
```

## Types

```python { .api }
# Cell magic type
CellMagic = str

# Notebook cell content  
CellSource = str | list[str]

# Complete notebook structure (simplified)
NotebookDict = dict[str, Any]
```