oc_chat
Loading...
Searching...
No Matches
oc_ui.terminal.diffview Namespace Reference

Classes

class  DiffLine
class  DiffRender
class  MutationArgsLedger

Functions

str _redact (str text)
dict|None _parse_args (str|None arguments)
str _norm_path (object value)
bool _paths_agree (object arg_path, Iterable[object] result_paths)
str _fmt_bytes (int n)
str _classify (str line)
str _reanchor_hunk (str line, int offset)
tuple[tuple[DiffLine,...], bool, int] _truncate (list[DiffLine] body, int max_lines)
DiffRender|None _build_edit_diff (dict args, dict result, int max_lines)
DiffRender|None _build_write_diff (dict args, dict result, int max_lines)
DiffRender|None build_mutation_diff (str tool, str|None arguments, Any result, *, int max_lines=DEFAULT_MAX_DIFF_LINES)
str _elide (str value)
str|None summarize_mutation_call (str tool, str|None arguments)
str diff_header_line (DiffRender diff)
str|None diff_footer_line (DiffRender diff)
list[str] render_diff_plain (DiffRender|None diff, *, bool color=True)

Variables

tuple MUTATION_TOOLS = ("write_file", "edit_file")
int DEFAULT_MAX_DIFF_LINES = 60
int _CONTEXT_LINES = 3
 _HUNK_RE = re.compile(r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)$")
tuple _ELIDED_ARG_FIELDS = ("content", "old_string", "new_string")
dict _ANSI

Detailed Description

Diff-first rendering model for the mutation tools (plan 188 T1).

Purpose
-------
Every comparator in the field renders an edit as a **diff**; OC Chat
rendered ``write_file`` / ``edit_file`` results as a truncated JSON
blob. This module owns the one shared, framework-free model that turns
a mutation tool's *call arguments* plus its *result payload* into a
renderable unified diff, so all three terminal presenters
(:class:`~oc_ui.terminal.presenter.ConsolePresenter`,
:class:`~oc_ui.terminal.rich_presenter.RichPresenter`, and the
:class:`~oc_ui.terminal.enhanced_presenter.EnhancedPresenter` that
subclasses it) show the same thing and degrade the same way.

Nothing here changes the tool contract. The diff is derived at RENDER
time from data the render lane already carries: the presenter sees the
arguments in :meth:`~oc_ui.presenter_base.Presenter.show_tool_call`
before the tool runs and the result in
:meth:`~oc_ui.presenter_base.Presenter.show_tool_result` after — this
module just pairs them (:class:`MutationArgsLedger`) and folds them
into a diff.

**What the render lane can and cannot know.** ``edit_file`` carries
both sides of the change (``old_string`` / ``new_string``) plus the
real ``match_line``, so its diff is a true, file-anchored unified diff.
``write_file`` carries only the NEW content — the previous contents are
gone by the time the result arrives — so its render is the full written
content as additions, and the header says exactly that rather than
implying a comparison that was never made.

Responsibilities
----------------
- :class:`MutationArgsLedger` — FIFO pairing of ``show_tool_call``
  arguments with the matching ``show_tool_result``.
- :class:`DiffLine` / :class:`DiffRender` — the pure, presenter-neutral
  diff model.
- :func:`build_mutation_diff` — the honesty-gated constructor:
  returns ``None`` (caller falls back to the legacy JSON line) for a
  non-mutation tool, an error / gate / confirmation result, unparseable
  or absent arguments, or a call/result pair whose paths disagree.
- :func:`diff_header_line` — the one-line summary above the body.
- :func:`render_diff_plain` — ANSI (or plain) line renderer used by
  the console presenter; Rich builds its own renderable from the same
  :class:`DiffRender`.

Diagnostics
-----------
Domain: OC_UI.TERMINAL
Levels:
    L2 — lifecycle events
    L3 — semantic details (guard rejections)
    L4 — deep tracing

Contracts
---------
- Framework-free: no ``rich`` / ``prompt_toolkit`` import here, exactly
  like :mod:`oc_ui.terminal.statusline` — the plain console must render
  a diff with no optional dependency installed.
- :func:`build_mutation_diff` NEVER guesses. Any missing or
  contradictory input returns ``None`` so the caller degrades to the
  previous rendering rather than showing a diff that might be wrong.
- Rendered body text is passed through the same secret redaction the
  read path already applies (:func:`oc_chat.tools._sandbox.redact_secrets`),
  so a diff is not a secret side-channel. Redaction is best-effort: an
  import failure degrades to unredacted text rather than losing the
  render (the content is already on the operator's own disk).
- Truncation is explicit: :attr:`DiffRender.truncated` is set, the
  hidden line count is carried, and
  :attr:`DiffRender.full_content_hint` names the on-disk file where the
  complete content lives.

Dependencies
------------
- oc_diagnostics (structured debug logging via _dbg)
- oc_chat.tools._sandbox (lazy; redact_secrets for rendered diff body text)

Function Documentation

◆ _build_edit_diff()

DiffRender | None oc_ui.terminal.diffview._build_edit_diff ( dict args,
dict result,
int max_lines )
protected
Both sides are present — a true, file-anchored unified diff.

Definition at line 279 of file diffview.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _build_write_diff()

DiffRender | None oc_ui.terminal.diffview._build_write_diff ( dict args,
dict result,
int max_lines )
protected
Only the NEW side reaches the render lane — show it as additions.

Definition at line 326 of file diffview.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _classify()

str oc_ui.terminal.diffview._classify ( str line)
protected
Map a raw unified-diff line to a :class:`DiffLine` kind.

Definition at line 250 of file diffview.py.

Here is the caller graph for this function:

◆ _elide()

str oc_ui.terminal.diffview._elide ( str value)
protected
``<12 lines, 1.2 KB>`` — a size marker standing in for a bulky payload.

Definition at line 393 of file diffview.py.

◆ _fmt_bytes()

str oc_ui.terminal.diffview._fmt_bytes ( int n)
protected
Compact byte count: ``29 B`` / ``1.2 KB`` / ``3.4 MB``.

Definition at line 241 of file diffview.py.

◆ _norm_path()

str oc_ui.terminal.diffview._norm_path ( object value)
protected
Normalise a path for comparison: forward slashes, no leading ``./``.

Definition at line 213 of file diffview.py.

Here is the caller graph for this function:

◆ _parse_args()

dict | None oc_ui.terminal.diffview._parse_args ( str | None arguments)
protected
Parse a tool-call ``arguments`` JSON string into a dict, else ``None``.

Definition at line 202 of file diffview.py.

Here is the caller graph for this function:

◆ _paths_agree()

bool oc_ui.terminal.diffview._paths_agree ( object arg_path,
Iterable[object] result_paths )
protected
True when the recorded call path matches a path the result echoes.

Exact (normalised) match first; a basename match is accepted as the
lenient tier because ``write_file``'s ``_tmp`` redirect and the sandbox
clamp can legitimately rewrite the directory part. Anything else is a
mis-pair and the caller refuses to render.

Definition at line 221 of file diffview.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _reanchor_hunk()

str oc_ui.terminal.diffview._reanchor_hunk ( str line,
int offset )
protected
Shift a ``@@`` hunk header by *offset* so it names REAL file lines.

Definition at line 261 of file diffview.py.

Here is the caller graph for this function:

◆ _redact()

str oc_ui.terminal.diffview._redact ( str text)
protected
Apply the shared secret redaction, degrading to *text* if unavailable.

Definition at line 187 of file diffview.py.

Here is the caller graph for this function:

◆ _truncate()

tuple[tuple[DiffLine, ...], bool, int] oc_ui.terminal.diffview._truncate ( list[DiffLine] body,
int max_lines )
protected
Clip *body* to *max_lines*, reporting whether (and how much) was hidden.

Definition at line 272 of file diffview.py.

Here is the caller graph for this function:

◆ build_mutation_diff()

DiffRender | None oc_ui.terminal.diffview.build_mutation_diff ( str tool,
str | None arguments,
Any result,
* ,
int max_lines = DEFAULT_MAX_DIFF_LINES )
Build a :class:`DiffRender` for a mutation tool, or ``None`` to degrade.

``None`` — meaning "render this the old way" — is returned for every
case where a diff would be a guess: a tool this module has no rule
for, a result that is an error / gate / pending-confirmation rather
than an applied change, arguments that are absent or unparseable, and
a call/result pair whose paths disagree (a mis-pairing).

Definition at line 347 of file diffview.py.

Here is the call graph for this function:

◆ diff_footer_line()

str | None oc_ui.terminal.diffview.diff_footer_line ( DiffRender diff)
The truncation notice, or ``None`` when the whole diff is shown.

Definition at line 435 of file diffview.py.

Here is the caller graph for this function:

◆ diff_header_line()

str oc_ui.terminal.diffview.diff_header_line ( DiffRender diff)
The one-line summary rendered above the diff body.

Definition at line 426 of file diffview.py.

Here is the caller graph for this function:

◆ render_diff_plain()

list[str] oc_ui.terminal.diffview.render_diff_plain ( DiffRender | None diff,
* ,
bool color = True )
Render *diff* as a list of terminal lines (ANSI-styled when *color*).

Returns ``[]`` for ``None`` so a caller can branch on emptiness without a
second null check.

Definition at line 454 of file diffview.py.

Here is the call graph for this function:

◆ summarize_mutation_call()

str | None oc_ui.terminal.diffview.summarize_mutation_call ( str tool,
str | None arguments )
Compact ``show_tool_call`` argument rendering for a mutation tool.

``show_tool_call`` fires BEFORE dispatch, so a large ``write_file``
used to dump its entire ``content`` into scrollback and *then* render
the diff underneath — the announce line defeated the diff. For
:data:`MUTATION_TOOLS` the bulky fields (:data:`_ELIDED_ARG_FIELDS`)
become size markers; every other field renders normally, so the path,
the expected count and any future flag stay visible.

Returns ``None`` for a non-mutation tool or unparseable arguments —
the caller then renders the arguments exactly as before.

Definition at line 399 of file diffview.py.

Here is the call graph for this function:

Variable Documentation

◆ _ANSI

dict oc_ui.terminal.diffview._ANSI
protected
Initial value:
1= {
2 "reset": "\033[0m",
3 "dim": "\033[2m",
4 "add": "\033[32m",
5 "del": "\033[31m",
6 "hunk": "\033[36m",
7}

Definition at line 445 of file diffview.py.

◆ _CONTEXT_LINES

int oc_ui.terminal.diffview._CONTEXT_LINES = 3
protected

Definition at line 104 of file diffview.py.

◆ _ELIDED_ARG_FIELDS

tuple oc_ui.terminal.diffview._ELIDED_ARG_FIELDS = ("content", "old_string", "new_string")
protected

Definition at line 390 of file diffview.py.

◆ _HUNK_RE

oc_ui.terminal.diffview._HUNK_RE = re.compile(r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)$")
protected

Definition at line 107 of file diffview.py.

◆ DEFAULT_MAX_DIFF_LINES

int oc_ui.terminal.diffview.DEFAULT_MAX_DIFF_LINES = 60

Definition at line 101 of file diffview.py.

◆ MUTATION_TOOLS

tuple oc_ui.terminal.diffview.MUTATION_TOOLS = ("write_file", "edit_file")

Definition at line 97 of file diffview.py.