mirror of
https://github.com/tiennm99/serena.git
synced 2026-08-23 02:24:23 +00:00
Move classes CodeDiff and LineChange to test_symbol_editing
This commit is contained in:
committed by
Dominik Jain
parent
65b20c4276
commit
8c36377dc2
+2
-148
@@ -3,9 +3,8 @@ import logging
|
||||
import os
|
||||
from abc import ABC, abstractmethod
|
||||
from collections.abc import Iterator, Sequence
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from difflib import SequenceMatcher
|
||||
from typing import TYPE_CHECKING, Any, Literal, NamedTuple, Self, Union
|
||||
from dataclasses import asdict, dataclass
|
||||
from typing import TYPE_CHECKING, Any, Self, Union
|
||||
|
||||
from sensai.util.string import ToStringMixin
|
||||
|
||||
@@ -21,151 +20,6 @@ if TYPE_CHECKING:
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LineChange(NamedTuple):
|
||||
"""Represents a change to a specific line or range of lines."""
|
||||
|
||||
operation: Literal["insert", "delete", "replace"]
|
||||
original_start: int
|
||||
original_end: int
|
||||
modified_start: int
|
||||
modified_end: int
|
||||
original_lines: list[str]
|
||||
modified_lines: list[str]
|
||||
|
||||
|
||||
@dataclass
|
||||
class CodeDiff:
|
||||
"""
|
||||
Represents the difference between original and modified code.
|
||||
Provides object-oriented access to diff information including line numbers.
|
||||
"""
|
||||
|
||||
relative_path: str
|
||||
original_content: str
|
||||
modified_content: str
|
||||
_line_changes: list[LineChange] = field(init=False)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""Compute the diff using difflib's SequenceMatcher."""
|
||||
original_lines = self.original_content.splitlines(keepends=True)
|
||||
modified_lines = self.modified_content.splitlines(keepends=True)
|
||||
|
||||
matcher = SequenceMatcher(None, original_lines, modified_lines)
|
||||
self._line_changes = []
|
||||
|
||||
for tag, orig_start, orig_end, mod_start, mod_end in matcher.get_opcodes():
|
||||
if tag == "equal":
|
||||
continue
|
||||
if tag == "insert":
|
||||
self._line_changes.append(
|
||||
LineChange(
|
||||
operation="insert",
|
||||
original_start=orig_start,
|
||||
original_end=orig_start,
|
||||
modified_start=mod_start,
|
||||
modified_end=mod_end,
|
||||
original_lines=[],
|
||||
modified_lines=modified_lines[mod_start:mod_end],
|
||||
)
|
||||
)
|
||||
elif tag == "delete":
|
||||
self._line_changes.append(
|
||||
LineChange(
|
||||
operation="delete",
|
||||
original_start=orig_start,
|
||||
original_end=orig_end,
|
||||
modified_start=mod_start,
|
||||
modified_end=mod_start,
|
||||
original_lines=original_lines[orig_start:orig_end],
|
||||
modified_lines=[],
|
||||
)
|
||||
)
|
||||
elif tag == "replace":
|
||||
self._line_changes.append(
|
||||
LineChange(
|
||||
operation="replace",
|
||||
original_start=orig_start,
|
||||
original_end=orig_end,
|
||||
modified_start=mod_start,
|
||||
modified_end=mod_end,
|
||||
original_lines=original_lines[orig_start:orig_end],
|
||||
modified_lines=modified_lines[mod_start:mod_end],
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def line_changes(self) -> list[LineChange]:
|
||||
"""Get all line changes in the diff."""
|
||||
return self._line_changes
|
||||
|
||||
@property
|
||||
def has_changes(self) -> bool:
|
||||
"""Check if there are any changes."""
|
||||
return len(self._line_changes) > 0
|
||||
|
||||
@property
|
||||
def added_lines(self) -> list[tuple[int, str]]:
|
||||
"""Get all added lines with their line numbers (0-based) in the modified file."""
|
||||
result = []
|
||||
for change in self._line_changes:
|
||||
if change.operation in ("insert", "replace"):
|
||||
for i, line in enumerate(change.modified_lines):
|
||||
result.append((change.modified_start + i, line))
|
||||
return result
|
||||
|
||||
@property
|
||||
def deleted_lines(self) -> list[tuple[int, str]]:
|
||||
"""Get all deleted lines with their line numbers (0-based) in the original file."""
|
||||
result = []
|
||||
for change in self._line_changes:
|
||||
if change.operation in ("delete", "replace"):
|
||||
for i, line in enumerate(change.original_lines):
|
||||
result.append((change.original_start + i, line))
|
||||
return result
|
||||
|
||||
@property
|
||||
def modified_line_numbers(self) -> list[int]:
|
||||
"""Get all line numbers (0-based) that were modified in the modified file."""
|
||||
line_nums: set[int] = set()
|
||||
for change in self._line_changes:
|
||||
if change.operation in ("insert", "replace"):
|
||||
line_nums.update(range(change.modified_start, change.modified_end))
|
||||
return sorted(line_nums)
|
||||
|
||||
@property
|
||||
def affected_original_line_numbers(self) -> list[int]:
|
||||
"""Get all line numbers (0-based) that were affected in the original file."""
|
||||
line_nums: set[int] = set()
|
||||
for change in self._line_changes:
|
||||
if change.operation in ("delete", "replace"):
|
||||
line_nums.update(range(change.original_start, change.original_end))
|
||||
return sorted(line_nums)
|
||||
|
||||
def get_unified_diff(self, context_lines: int = 3) -> str:
|
||||
"""Get the unified diff as a string."""
|
||||
import difflib
|
||||
|
||||
original_lines = self.original_content.splitlines(keepends=True)
|
||||
modified_lines = self.modified_content.splitlines(keepends=True)
|
||||
|
||||
diff = difflib.unified_diff(
|
||||
original_lines, modified_lines, fromfile=f"a/{self.relative_path}", tofile=f"b/{self.relative_path}", n=context_lines
|
||||
)
|
||||
return "".join(diff)
|
||||
|
||||
def get_context_diff(self, context_lines: int = 3) -> str:
|
||||
"""Get the context diff as a string."""
|
||||
import difflib
|
||||
|
||||
original_lines = self.original_content.splitlines(keepends=True)
|
||||
modified_lines = self.modified_content.splitlines(keepends=True)
|
||||
|
||||
diff = difflib.context_diff(
|
||||
original_lines, modified_lines, fromfile=f"a/{self.relative_path}", tofile=f"b/{self.relative_path}", n=context_lines
|
||||
)
|
||||
return "".join(diff)
|
||||
|
||||
|
||||
@dataclass
|
||||
class LanguageServerSymbolLocation:
|
||||
"""
|
||||
|
||||
@@ -6,13 +6,14 @@ import time
|
||||
from abc import abstractmethod
|
||||
from collections.abc import Iterator
|
||||
from contextlib import contextmanager
|
||||
from dataclasses import dataclass, field
|
||||
from difflib import SequenceMatcher
|
||||
from pathlib import Path
|
||||
from typing import Literal
|
||||
from typing import Literal, NamedTuple
|
||||
|
||||
import pytest
|
||||
|
||||
from serena.code_editor import CodeEditor, LanguageServerCodeEditor
|
||||
from serena.symbol import CodeDiff
|
||||
from solidlsp.ls_config import Language
|
||||
from src.serena.symbol import LanguageServerSymbolRetriever
|
||||
from test.conftest import create_ls, get_repo_path
|
||||
@@ -22,6 +23,151 @@ pytestmark = pytest.mark.snapshot
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LineChange(NamedTuple):
|
||||
"""Represents a change to a specific line or range of lines."""
|
||||
|
||||
operation: Literal["insert", "delete", "replace"]
|
||||
original_start: int
|
||||
original_end: int
|
||||
modified_start: int
|
||||
modified_end: int
|
||||
original_lines: list[str]
|
||||
modified_lines: list[str]
|
||||
|
||||
|
||||
@dataclass
|
||||
class CodeDiff:
|
||||
"""
|
||||
Represents the difference between original and modified code.
|
||||
Provides object-oriented access to diff information including line numbers.
|
||||
"""
|
||||
|
||||
relative_path: str
|
||||
original_content: str
|
||||
modified_content: str
|
||||
_line_changes: list[LineChange] = field(init=False)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""Compute the diff using difflib's SequenceMatcher."""
|
||||
original_lines = self.original_content.splitlines(keepends=True)
|
||||
modified_lines = self.modified_content.splitlines(keepends=True)
|
||||
|
||||
matcher = SequenceMatcher(None, original_lines, modified_lines)
|
||||
self._line_changes = []
|
||||
|
||||
for tag, orig_start, orig_end, mod_start, mod_end in matcher.get_opcodes():
|
||||
if tag == "equal":
|
||||
continue
|
||||
if tag == "insert":
|
||||
self._line_changes.append(
|
||||
LineChange(
|
||||
operation="insert",
|
||||
original_start=orig_start,
|
||||
original_end=orig_start,
|
||||
modified_start=mod_start,
|
||||
modified_end=mod_end,
|
||||
original_lines=[],
|
||||
modified_lines=modified_lines[mod_start:mod_end],
|
||||
)
|
||||
)
|
||||
elif tag == "delete":
|
||||
self._line_changes.append(
|
||||
LineChange(
|
||||
operation="delete",
|
||||
original_start=orig_start,
|
||||
original_end=orig_end,
|
||||
modified_start=mod_start,
|
||||
modified_end=mod_start,
|
||||
original_lines=original_lines[orig_start:orig_end],
|
||||
modified_lines=[],
|
||||
)
|
||||
)
|
||||
elif tag == "replace":
|
||||
self._line_changes.append(
|
||||
LineChange(
|
||||
operation="replace",
|
||||
original_start=orig_start,
|
||||
original_end=orig_end,
|
||||
modified_start=mod_start,
|
||||
modified_end=mod_end,
|
||||
original_lines=original_lines[orig_start:orig_end],
|
||||
modified_lines=modified_lines[mod_start:mod_end],
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def line_changes(self) -> list[LineChange]:
|
||||
"""Get all line changes in the diff."""
|
||||
return self._line_changes
|
||||
|
||||
@property
|
||||
def has_changes(self) -> bool:
|
||||
"""Check if there are any changes."""
|
||||
return len(self._line_changes) > 0
|
||||
|
||||
@property
|
||||
def added_lines(self) -> list[tuple[int, str]]:
|
||||
"""Get all added lines with their line numbers (0-based) in the modified file."""
|
||||
result = []
|
||||
for change in self._line_changes:
|
||||
if change.operation in ("insert", "replace"):
|
||||
for i, line in enumerate(change.modified_lines):
|
||||
result.append((change.modified_start + i, line))
|
||||
return result
|
||||
|
||||
@property
|
||||
def deleted_lines(self) -> list[tuple[int, str]]:
|
||||
"""Get all deleted lines with their line numbers (0-based) in the original file."""
|
||||
result = []
|
||||
for change in self._line_changes:
|
||||
if change.operation in ("delete", "replace"):
|
||||
for i, line in enumerate(change.original_lines):
|
||||
result.append((change.original_start + i, line))
|
||||
return result
|
||||
|
||||
@property
|
||||
def modified_line_numbers(self) -> list[int]:
|
||||
"""Get all line numbers (0-based) that were modified in the modified file."""
|
||||
line_nums: set[int] = set()
|
||||
for change in self._line_changes:
|
||||
if change.operation in ("insert", "replace"):
|
||||
line_nums.update(range(change.modified_start, change.modified_end))
|
||||
return sorted(line_nums)
|
||||
|
||||
@property
|
||||
def affected_original_line_numbers(self) -> list[int]:
|
||||
"""Get all line numbers (0-based) that were affected in the original file."""
|
||||
line_nums: set[int] = set()
|
||||
for change in self._line_changes:
|
||||
if change.operation in ("delete", "replace"):
|
||||
line_nums.update(range(change.original_start, change.original_end))
|
||||
return sorted(line_nums)
|
||||
|
||||
def get_unified_diff(self, context_lines: int = 3) -> str:
|
||||
"""Get the unified diff as a string."""
|
||||
import difflib
|
||||
|
||||
original_lines = self.original_content.splitlines(keepends=True)
|
||||
modified_lines = self.modified_content.splitlines(keepends=True)
|
||||
|
||||
diff = difflib.unified_diff(
|
||||
original_lines, modified_lines, fromfile=f"a/{self.relative_path}", tofile=f"b/{self.relative_path}", n=context_lines
|
||||
)
|
||||
return "".join(diff)
|
||||
|
||||
def get_context_diff(self, context_lines: int = 3) -> str:
|
||||
"""Get the context diff as a string."""
|
||||
import difflib
|
||||
|
||||
original_lines = self.original_content.splitlines(keepends=True)
|
||||
modified_lines = self.modified_content.splitlines(keepends=True)
|
||||
|
||||
diff = difflib.context_diff(
|
||||
original_lines, modified_lines, fromfile=f"a/{self.relative_path}", tofile=f"b/{self.relative_path}", n=context_lines
|
||||
)
|
||||
return "".join(diff)
|
||||
|
||||
|
||||
class EditingTest:
|
||||
def __init__(self, language: Language, rel_path: str):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user