Metadata-Version: 2.4
Name: ipython-postfix-completion
Version: 0.2.0
Summary: Configurable postfix completion extension for IPython.
Author: IPython Postfix Completion Contributors
License-Expression: BSD-3-Clause
Project-URL: Homepage, https://github.com/fishandsheep/ipython-postfix-completion
Project-URL: Repository, https://github.com/fishandsheep/ipython-postfix-completion
Project-URL: Issues, https://github.com/fishandsheep/ipython-postfix-completion/issues
Project-URL: Changelog, https://github.com/fishandsheep/ipython-postfix-completion/blob/main/CHANGELOG.md
Keywords: ipython,completion,postfix,extension
Classifier: Framework :: IPython
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: ipython<10,>=9.0
Requires-Dist: traitlets>=5.13
Provides-Extra: test
Requires-Dist: pytest>=7; extra == "test"
Provides-Extra: dev
Requires-Dist: build; extra == "dev"
Requires-Dist: pip-audit>=2.7; extra == "dev"
Requires-Dist: ruff>=0.8; extra == "dev"
Requires-Dist: twine; extra == "dev"
Requires-Dist: ipython-postfix-completion[test]; extra == "dev"
Dynamic: license-file

# IPython Postfix Completion

Configurable postfix completion extension for IPython.

Package on PyPI: `ipython-postfix-completion`

## Install

Install into the current Python environment with `uv`:

```bash
uvx --with ipython-postfix-completion ipython
```

Install into the current ipython environment with `uv`:

```bash
uv tool install ipython --with ipython-postfix-completion
```

## Load

Inside IPython:

```python
%load_ext ipython_postfix_completion
```

To load it automatically, add this to `ipython_config.py`:

```python
c.InteractiveShellApp.extensions = ["ipython_postfix_completion"]
```

Smart Tab key bindings are supported in terminal IPython. Postfix matcher
completion can also work in other IPython frontends, but this package does not
promise frontend-specific Tab behavior outside the terminal.

## Quick Example: Add a `for` Template

Add a template for the current IPython session:

```python
%postfix_template add for "for item in {expr}:\n{indent}    "
```

Use it:

```python
items.for<Tab>
```

It expands to:

```python
for item in items:
    
```

Runtime templates only affect the current IPython session. Put templates in
`ipython_config.py` if you want them to persist.

## Runtime Magic

List effective templates:

```python
%postfix_template list
```

Add or override a template for the current session:

```python
%postfix_template add debug "print({expr}=)"
%postfix_template add forin "for item in {expr}:\n{indent}    "
```

Disable a template for the current session:

```python
%postfix_template remove tuple
```

Reset one runtime change:

```python
%postfix_template reset forin
```

Reset all runtime changes:

```python
%postfix_template reset --all
```

## Persistent Config

Add persistent templates in `ipython_config.py`:

```python
c.PostfixCompletionConfig.templates = {
    "debug": "print({expr}=)",
    "forin": "for item in {expr}:\n{indent}    ",
}

c.PostfixCompletionConfig.disabled_templates = ["tuple"]
```

Smart Tab jump is enabled by default. Disable it while keeping postfix
completion with:

```python
c.PostfixCompletionConfig.smart_tab_jump = False
```

Template names must match `[A-Za-z_][A-Za-z0-9_]*`.

Templates must include `{expr}` and may also use `{indent}`. No other template
fields are allowed.

## `.var` Placeholder

The built-in `.var` template creates an assignment and selects `key` as an
editable placeholder:

```text
"hello".var<Tab>  ->  key = "hello"
                       ^^^ selected
```

While `key` remains selected:

- Tab accepts `key` and moves cursor to the end of the assignment.
- Enter behaves like Tab for this selection only; press Enter again to submit.
- Any other typed text replaces `key` with a custom variable name.

In 0.1.x, `.var` produced `expr = ` with the cursor after the assignment
target. Version 0.2.0 changes this to `key = expr` with an editable
placeholder; use a custom template if you need the old behavior.

## Smart Tab Jump

When cursor is immediately before a valid Python closing token, Tab moves over
it without changing source text. Repeated Tab presses exit nested constructs:

```text
"hello|"                 -> "hello"|
print("hello|")          -> print("hello"|) -> print("hello")|
print(f"{name|}")        -> print(f"{name}|") -> print(f"{name}"|) -> print(f"{name}")|
items[index|]            -> items[index]|
list[dict[str, int|]]    -> list[dict[str, int]|] -> list[dict[str, int]]|
{"name": value|}         -> {"name": value}|
```

`|` marks cursor and is not typed. Supported closers are single and triple
quotes plus `)`, `]`, and `}`. Detection follows Python tokens, including
multiline input, string prefixes, and f-string expressions. Tab still accepts
the `.var` name selection or an exact postfix template first; otherwise it
falls back to IPython completion or indentation. Ambiguous `< >`, colon, and
comma are intentionally excluded.

## Built-in Templates

Default templates:

| Name | Expansion |
| --- | --- |
| `print` | `print({expr})` |
| `len` | `len({expr})` |
| `not` | `not {expr}` |
| `par` | `({expr})` |
| `var` | `key = {expr}`; selects `key`; Tab or Enter accepts it |
| `await` | `await {expr}` |
| `return` | `return {expr}` |
| `if` | `if {expr}:\n{indent}    ` |
| `while` | `while {expr}:\n{indent}    ` |
| `raise` | `raise {expr}` |
| `yield` | `yield {expr}` |
| `str` | `str({expr})` |
| `list` | `list({expr})` |
| `set` | `set({expr})` |
| `dict` | `dict({expr})` |
| `tuple` | `tuple({expr})` |

Use `%postfix_template list` in IPython to see the exact effective set, including
custom and disabled templates.

## Local Validation

Run tests:

```bash
uv run --extra test pytest -q
uv run --extra dev ruff check .
uv run --extra dev ruff format --check .
uv run --isolated --no-project --with "ipython>=9,<10" --with "traitlets>=5.13" --with "pip-audit>=2.7" pip-audit --strict --local
```

Build and check release artifacts:

```bash
uv run --extra dev python -m build
uv run --extra dev python -m twine check dist/*
```

Validate the wheel in a clean local virtual environment:

```bash
uv venv .venv-check
uv pip install --python .venv-check/bin/python dist/*.whl
.venv-check/bin/ipython
```

Then inside IPython:

```python
%load_ext ipython_postfix_completion
%postfix_template add for "for item in {expr}:\n{indent}    "
%postfix_template list
```

## Publish

Publishing uses GitHub Actions and PyPI Trusted Publishing. Configure the
existing PyPI project once under **Manage > Publishing > Add a new publisher**:

| Setting | Value |
| --- | --- |
| Owner | `fishandsheep` |
| Repository | `ipython-postfix-completion` |
| Workflow | `publish.yml` |
| Environment | `pypi` |

For each release, update `project.version` in `pyproject.toml`, commit and push
the change, then create a matching `v` tag. For this release:

```bash
git tag v0.2.0
git push origin v0.2.0
```

The workflow verifies the tag against `project.version`, runs tests, builds and
checks both distributions, then publishes them to PyPI using a short-lived OIDC
credential. PyPI versions are immutable: never reuse a published version or tag;
fixes require the next version.

See [CHANGELOG.md](CHANGELOG.md) for release notes and migration guidance.
