Apply Black formatting only in regions changed since last commit
—
Utilities for diffing text files and processing diff opcodes to extract changed line numbers and content chunks. These functions form the foundation for comparing original code with reformatted code.
Functions for generating and processing diffs between text documents.
def diff_and_get_opcodes(src: TextDocument, dst: TextDocument) -> List[Tuple[str, int, int, int, int]]:
"""
Generate diff opcodes between source and destination documents.
Divides a diff between the original and reformatted content into
alternating chunks of intact (represented by the 'equal' tag) and
modified ('delete', 'replace' or 'insert' tag) lines.
Parameters:
- src: Source document (original content)
- dst: Destination document (reformatted content)
Returns:
List of opcodes, where each opcode is a tuple of:
(tag, src_start, src_end, dst_start, dst_end)
Example opcode: ('replace', 0, 1, 0, 2) means replace line 0-1 in src
with lines 0-2 in dst
"""
def opcodes_to_chunks(
opcodes: List[Tuple[str, int, int, int, int]],
src: TextDocument,
dst: TextDocument
) -> Generator[DiffChunk, None, None]:
"""
Convert diff opcodes into chunks with line content and offsets.
Picks the lines from original and reformatted content for each opcode
and combines line content with the 1-based line offset in the original content.
Parameters:
- opcodes: List of diff opcodes from diff_and_get_opcodes
- src: Source document (original content)
- dst: Destination document (reformatted content)
Yields:
DiffChunk objects containing:
- original_lines_offset: 1-based line number where chunk starts in original
- original_lines: List of original line content
- formatted_lines: List of reformatted line content
"""Functions for extracting changed line numbers from diff opcodes.
def opcodes_to_edit_linenums(opcodes: List[Tuple[str, int, int, int, int]]) -> List[int]:
"""
Convert diff opcodes to a list of changed line numbers.
Parameters:
- opcodes: List of diff opcodes
Returns:
List of 1-based line numbers that were changed
"""from darker.diff import diff_and_get_opcodes, opcodes_to_chunks
from darkgraylib.utils import TextDocument
# Create example documents
original = TextDocument.from_lines([
'for i in range(5): print(i)',
'print("done")'
])
reformatted = TextDocument.from_lines([
'for i in range(5):',
' print(i)',
'print("done")'
])
# Generate diff opcodes
opcodes = diff_and_get_opcodes(original, reformatted)
print(f"Generated {len(opcodes)} opcodes:")
for opcode in opcodes:
tag, src_start, src_end, dst_start, dst_end = opcode
print(f" {tag}: src[{src_start}:{src_end}] -> dst[{dst_start}:{dst_end}]")
# Convert to chunks
chunks = list(opcodes_to_chunks(opcodes, original, reformatted))
print(f"\nGenerated {len(chunks)} chunks:")
for i, chunk in enumerate(chunks):
print(f" Chunk {i+1}: starts at line {chunk.original_lines_offset}")
print(f" Original: {chunk.original_lines}")
print(f" Formatted: {chunk.formatted_lines}")from darker.diff import diff_and_get_opcodes, opcodes_to_edit_linenums
from darkgraylib.utils import TextDocument
# Compare git working tree version with formatted version
with open("myfile.py") as f:
current_content = TextDocument.from_str(f.read())
# Assume we have formatted content from Black/Ruff/etc
# formatted_content = apply_formatter(current_content)
# Get diff and extract changed lines
opcodes = diff_and_get_opcodes(current_content, formatted_content)
changed_lines = opcodes_to_edit_linenums(opcodes)
if changed_lines:
print(f"Formatter would change lines: {changed_lines}")
else:
print("No formatting changes needed")from darker.diff import diff_and_get_opcodes, opcodes_to_chunks
from darker.chooser import choose_lines
from darker.git import EditedLinenumsDiffer
from darkgraylib.utils import TextDocument
from pathlib import Path
def apply_selective_formatting(file_path: Path, original: TextDocument, formatted: TextDocument, git_edits: List[int]) -> TextDocument:
"""Apply formatting only to git-edited regions."""
# Get diff chunks between original and formatted
opcodes = diff_and_get_opcodes(original, formatted)
chunks = list(opcodes_to_chunks(opcodes, original, formatted))
# Choose lines based on git edits
selected_lines = list(choose_lines(chunks, git_edits))
# Reconstruct document
return TextDocument.from_lines(selected_lines)
# Example usage
file_path = Path("src/module.py")
with open(file_path) as f:
original_content = TextDocument.from_str(f.read())
# Get git edits (implementation depends on git integration)
# git_edited_lines = get_git_edited_lines(file_path)
# Apply formatter (Black, Ruff, etc.)
# formatted_content = apply_formatter(original_content)
# Apply selective formatting
# result = apply_selective_formatting(file_path, original_content, formatted_content, git_edited_lines)from darker.diff import diff_and_get_opcodes
def analyze_diff_opcodes(opcodes):
"""Analyze diff opcodes to understand change patterns."""
stats = {
'equal': 0, # unchanged chunks
'delete': 0, # deleted lines
'insert': 0, # inserted lines
'replace': 0 # modified lines
}
for tag, src_start, src_end, dst_start, dst_end in opcodes:
stats[tag] += 1
if tag == 'equal':
print(f"Unchanged: {src_end - src_start} lines")
elif tag == 'delete':
print(f"Deleted: lines {src_start+1}-{src_end}")
elif tag == 'insert':
print(f"Inserted: {dst_end - dst_start} lines at position {src_start+1}")
elif tag == 'replace':
print(f"Replaced: lines {src_start+1}-{src_end} with {dst_end - dst_start} lines")
return stats
# Usage
opcodes = diff_and_get_opcodes(original_doc, formatted_doc)
stats = analyze_diff_opcodes(opcodes)
print(f"Diff summary: {stats}")Install with Tessl CLI
npx tessl i tessl/pypi-darker@3.0.1