AI.Tools.File.Edit.WhitespaceFitter (fnord v0.8.83)

View Source

Deterministic, language-agnostic whitespace fitting for file hunks.

This module is intentionally not wired into AI.Tools.File.Edit yet. It exists as a proof-of-concept for how we might:

  • Infer indentation style (tabs vs spaces, indent width) from local context
  • Re-base a replacement hunk's indentation to match the original region
  • Prepare new_hunk_fitted that can be spliced in literally

The goal is to make fuzzy / whitespace-tolerant matching safer by ensuring that once we have found the right region, we can adjust the replacement's indentation to dovetail with the surrounding code without relying on language-specific formatters or additional LLM calls.

Summary

Functions

Fit a replacement hunk's indentation to match local context.

Infer indentation style (tabs vs spaces, and space width) from a list of lines.

Types

indent_style()

@type indent_style() :: %{type: :spaces | :tabs, width: pos_integer()}

line_info()

@type line_info() :: %{
  indent_cols: non_neg_integer(),
  content: String.t(),
  raw: String.t()
}

Functions

fit(context_before, orig_hunk, context_after, new_hunk_raw)

@spec fit([String.t()], [String.t()], [String.t()], String.t()) :: String.t()

Fit a replacement hunk's indentation to match local context.

Inputs:

  • context_before - lines before the original hunk (nearest first preferred)
  • orig_hunk - the original lines in the region being replaced
  • context_after - lines after the original hunk
  • new_hunk_raw - the proposed replacement text (may have arbitrary indentation)

Output:

  • A single string containing new_hunk_raw with indentation adjusted to match the inferred style and depth of the original region.

Behavior (high level):

  • Infer indentation style from context_before ++ orig_hunk ++ context_after.
  • Determine the target base indentation for the region using the original hunk, falling back to neighbors if needed.
  • Compute relative indentation within new_hunk_raw and re-base it at the target depth, preserving the replacement's internal structure.

This function is deliberately conservative: it only changes leading whitespace and leaves the rest of each line untouched.

infer_indent_style(lines)

@spec infer_indent_style([String.t()]) :: indent_style()

Infer indentation style (tabs vs spaces, and space width) from a list of lines.

This looks only at leading whitespace on non-empty lines. If it sees any leading tabs and no spaced indentation, it assumes a tab-indented style. Otherwise, it looks at the distribution of leading space counts and picks a representative width (e.g., 2 or 4).

If there is not enough information, it falls back to %{type: :spaces, width: 2}.