diff options
| author | Nate Sesti <sestinj@gmail.com> | 2023-06-12 10:50:33 -0700 | 
|---|---|---|
| committer | Nate Sesti <sestinj@gmail.com> | 2023-06-12 10:50:33 -0700 | 
| commit | 792b65745b89bb59802294357378113493d25b63 (patch) | |
| tree | 1d038ca5e1ac837fa704b454136f597bdd02797d /continuedev/src | |
| parent | c66f39173bec7e45955dc8b96ae6c44ed5bdc162 (diff) | |
| download | sncontinue-792b65745b89bb59802294357378113493d25b63.tar.gz sncontinue-792b65745b89bb59802294357378113493d25b63.tar.bz2 sncontinue-792b65745b89bb59802294357378113493d25b63.zip | |
calculate diff and highlight changes
Diffstat (limited to 'continuedev/src')
| -rw-r--r-- | continuedev/src/continuedev/core/sdk.py | 3 | ||||
| -rw-r--r-- | continuedev/src/continuedev/libs/util/calculate_diff.py | 152 | ||||
| -rw-r--r-- | continuedev/src/continuedev/libs/util/copy_codebase.py | 38 | ||||
| -rw-r--r-- | continuedev/src/continuedev/models/filesystem_edit.py | 4 | ||||
| -rw-r--r-- | continuedev/src/continuedev/steps/chat.py | 12 | ||||
| -rw-r--r-- | continuedev/src/continuedev/steps/react.py | 33 | 
6 files changed, 198 insertions, 44 deletions
| diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index a94b5026..2849b0c8 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -149,3 +149,6 @@ class ContinueSDK(AbstractContinueSDK):              history_context.append(ChatMessage(                  content=f"The following code is highlighted:\n```\n{code}\n```", role="user"))          return history_context + +    async def update_ui(self): +        await self.__autopilot.update_subscribers() diff --git a/continuedev/src/continuedev/libs/util/calculate_diff.py b/continuedev/src/continuedev/libs/util/calculate_diff.py new file mode 100644 index 00000000..d778891b --- /dev/null +++ b/continuedev/src/continuedev/libs/util/calculate_diff.py @@ -0,0 +1,152 @@ +import difflib +from typing import List +from ...models.main import Position, Range +from ...models.filesystem import FileEdit +from diff_match_patch import diff_match_patch + + +def calculate_diff_match_patch(filepath: str, original: str, updated: str) -> List[FileEdit]: +    dmp = diff_match_patch() +    diffs = dmp.diff_main(original, updated) +    dmp.diff_cleanupSemantic(diffs) + +    replacements = [] + +    current_index = 0 +    deleted_length = 0 + +    for diff in diffs: +        if diff[0] == diff_match_patch.DIFF_EQUAL: +            current_index += len(diff[1]) +            deleted_length = 0 +        elif diff[0] == diff_match_patch.DIFF_INSERT: +            current_index += deleted_length +            replacements.append((current_index, current_index, diff[1])) +            current_index += len(diff[1]) +            deleted_length = 0 +        elif diff[0] == diff_match_patch.DIFF_DELETE: +            replacements.append( +                (current_index, current_index + len(diff[1]), '')) +            deleted_length += len(diff[1]) +        elif diff[0] == diff_match_patch.DIFF_REPLACE: +            replacements.append( +                (current_index, current_index + len(diff[1]), '')) +            current_index += deleted_length +            replacements.append((current_index, current_index, diff[2])) +            current_index += len(diff[2]) +            deleted_length = 0 + +    return [FileEdit(filepath=filepath, range=Range.from_indices(original, r[0], r[1]), replacement=r[2]) for r in replacements] + + +def calculate_diff(filepath: str, original: str, updated: str) -> List[FileEdit]: +    s = difflib.SequenceMatcher(None, original, updated) +    offset = 0  # The indices are offset by previous deletions/insertions +    edits = [] +    for tag, i1, i2, j1, j2 in s.get_opcodes(): +        i1, i2, j1, j2 = i1 + offset, i2 + offset, j1 + offset, j2 + offset +        replacement = updated[j1:j2] +        if tag == "equal": +            pass +        elif tag == "delete": +            edits.append(FileEdit.from_deletion( +                filepath, Range.from_indices(original, i1, i2))) +            offset -= i2 - i1 +        elif tag == "insert": +            edits.append(FileEdit.from_insertion( +                filepath, Position.from_index(original, i1), replacement)) +            offset += j2 - j1 +        elif tag == "replace": +            edits.append(FileEdit(filepath=filepath, range=Range.from_indices( +                original, i1, i2), replacement=replacement)) +            offset += (j2 - j1) - (i2 - i1) +        else: +            raise Exception("Unexpected difflib.SequenceMatcher tag: " + tag) + +    return edits + + +def calculate_diff2(filepath: str, original: str, updated: str) -> List[FileEdit]: +    edits = [] +    max_iterations = 1000 +    i = 0 +    while not original == updated: +        # TODO - For some reason it can't handle a single newline at the end of the file? +        s = difflib.SequenceMatcher(None, original, updated) +        opcodes = s.get_opcodes() +        for edit_index in range(len(opcodes)): +            tag, i1, i2, j1, j2 = s.get_opcodes()[edit_index] +            replacement = updated[j1:j2] +            if tag == "equal": +                continue +            elif tag == "delete": +                edits.append(FileEdit.from_deletion( +                    filepath, Range.from_indices(original, i1, i2))) +            elif tag == "insert": +                edits.append(FileEdit.from_insertion( +                    filepath, Position.from_index(original, i1), replacement)) +            elif tag == "replace": +                edits.append(FileEdit(filepath=filepath, range=Range.from_indices( +                    original, i1, i2), replacement=replacement)) +            else: +                raise Exception( +                    "Unexpected difflib.SequenceMatcher tag: " + tag) +            break + +        original = apply_edit_to_str(original, edits[-1]) + +        i += 1 +        if i > max_iterations: +            raise Exception("Max iterations reached") + +    return edits + + +def read_range_in_str(s: str, r: Range) -> str: +    lines = s.splitlines()[r.start.line:r.end.line + 1] +    if len(lines) == 0: +        return "" + +    lines[0] = lines[0][r.start.character:] +    lines[-1] = lines[-1][:r.end.character + 1] +    return "\n".join(lines) + + +def apply_edit_to_str(s: str, edit: FileEdit) -> str: +    original = read_range_in_str(s, edit.range) + +    # Split lines and deal with some edge cases (could obviously be nicer) +    lines = s.splitlines() +    if s.startswith("\n"): +        lines.insert(0, "") +    if s.endswith("\n"): +        lines.append("") + +    if len(lines) == 0: +        lines = [""] + +    end = Position(line=edit.range.end.line, +                   character=edit.range.end.character) +    if edit.range.end.line == len(lines) and edit.range.end.character == 0: +        end = Position(line=edit.range.end.line - 1, +                       character=len(lines[min(len(lines) - 1, edit.range.end.line - 1)])) + +    before_lines = lines[:edit.range.start.line] +    after_lines = lines[end.line + 1:] +    between_str = lines[min(len(lines) - 1, edit.range.start.line)][:edit.range.start.character] + \ +        edit.replacement + \ +        lines[min(len(lines) - 1, end.line)][end.character + 1:] + +    new_range = Range( +        start=edit.range.start, +        end=Position( +            line=edit.range.start.line + +            len(edit.replacement.splitlines()) - 1, +            character=edit.range.start.character + +            len(edit.replacement.splitlines() +                [-1]) if edit.replacement != "" else 0 +        ) +    ) + +    lines = before_lines + between_str.splitlines() + after_lines +    return "\n".join(lines) diff --git a/continuedev/src/continuedev/libs/util/copy_codebase.py b/continuedev/src/continuedev/libs/util/copy_codebase.py index af957a34..97143faf 100644 --- a/continuedev/src/continuedev/libs/util/copy_codebase.py +++ b/continuedev/src/continuedev/libs/util/copy_codebase.py @@ -3,13 +3,12 @@ from pathlib import Path  from typing import Iterable, List, Union  from watchdog.observers import Observer  from watchdog.events import PatternMatchingEventHandler -from ..models.main import FileEdit, DeleteDirectory, DeleteFile, AddDirectory, AddFile, FileSystemEdit, Position, Range, RenameFile, RenameDirectory, SequentialFileSystemEdit -from ..models.filesystem import FileSystem -from ..libs.main import Autopilot -from ..libs.map_path import map_path -from ..libs.steps.main import ManualEditAction +from ...models.main import FileEdit, DeleteDirectory, DeleteFile, AddDirectory, AddFile, FileSystemEdit, RenameFile, RenameDirectory, SequentialFileSystemEdit +from ...models.filesystem import FileSystem +from ...core.autopilot import Autopilot +from .map_path import map_path +from ...core.sdk import ManualEditStep  import shutil -import difflib  def create_copy(orig_root: str, copy_root: str = None, ignore: Iterable[str] = []): @@ -36,33 +35,6 @@ def create_copy(orig_root: str, copy_root: str = None, ignore: Iterable[str] = [                  os.symlink(child, map_path(child)) -def calculate_diff(filepath: str, original: str, updated: str) -> List[FileEdit]: -    s = difflib.SequenceMatcher(None, original, updated) -    offset = 0  # The indices are offset by previous deletions/insertions -    edits = [] -    for tag, i1, i2, j1, j2 in s.get_opcodes(): -        i1, i2, j1, j2 = i1 + offset, i2 + offset, j1 + offset, j2 + offset -        replacement = updated[j1:j2] -        if tag == "equal": -            pass -        elif tag == "delete": -            edits.append(FileEdit.from_deletion( -                filepath, Range.from_indices(original, i1, i2))) -            offset -= i2 - i1 -        elif tag == "insert": -            edits.append(FileEdit.from_insertion( -                filepath, Position.from_index(original, i1), replacement)) -            offset += j2 - j1 -        elif tag == "replace": -            edits.append(FileEdit(filepath, Range.from_indices( -                original, i1, i2), replacement)) -            offset += (j2 - j1) - (i2 + i1) -        else: -            raise Exception("Unexpected difflib.SequenceMatcher tag: " + tag) - -    return edits - -  # The whole usage of watchdog here should only be specific to RealFileSystem, you want to have a different "Observer" class for VirtualFileSystem, which would depend on being sent notifications  class CopyCodebaseEventHandler(PatternMatchingEventHandler):      def __init__(self, ignore_directories: List[str], ignore_patterns: List[str], autopilot: Autopilot, orig_root: str, copy_root: str, filesystem: FileSystem): diff --git a/continuedev/src/continuedev/models/filesystem_edit.py b/continuedev/src/continuedev/models/filesystem_edit.py index 8e74b819..b06ca2b3 100644 --- a/continuedev/src/continuedev/models/filesystem_edit.py +++ b/continuedev/src/continuedev/models/filesystem_edit.py @@ -30,8 +30,8 @@ class FileEdit(AtomicFileSystemEdit):          return FileEdit(map_path(self.filepath, orig_root, copy_root), self.range, self.replacement)      @staticmethod -    def from_deletion(filepath: str, start: Position, end: Position) -> "FileEdit": -        return FileEdit(filepath, Range(start, end), "") +    def from_deletion(filepath: str, range: Range) -> "FileEdit": +        return FileEdit(filepath=filepath, range=range, replacement="")      @staticmethod      def from_insertion(filepath: str, position: Position, content: str) -> "FileEdit": diff --git a/continuedev/src/continuedev/steps/chat.py b/continuedev/src/continuedev/steps/chat.py index 80065c24..56e49223 100644 --- a/continuedev/src/continuedev/steps/chat.py +++ b/continuedev/src/continuedev/steps/chat.py @@ -7,6 +7,18 @@ from .core.core import MessageStep  class SimpleChatStep(Step):      user_input: str +    name: str = "Chat"      async def run(self, sdk: ContinueSDK): +<<<<<<< Updated upstream          self.description = sdk.models.gpt35.complete(self.user_input, with_history=await sdk.get_chat_context()) +======= +        # TODO: With history +        self.description = "" +        for chunk in sdk.models.gpt35.stream_chat([{"role": "user", "content": self.user_input}]): +            self.description += chunk +            await sdk.update_ui() + +        self.name = sdk.models.gpt35.complete( +            f"Write a short title for the following chat message: {self.description}").strip() +>>>>>>> Stashed changes diff --git a/continuedev/src/continuedev/steps/react.py b/continuedev/src/continuedev/steps/react.py index 6b6024ce..d98b41c6 100644 --- a/continuedev/src/continuedev/steps/react.py +++ b/continuedev/src/continuedev/steps/react.py @@ -1,5 +1,9 @@  from textwrap import dedent +<<<<<<< Updated upstream  from typing import List, Union +======= +from typing import List, Tuple +>>>>>>> Stashed changes  from ..core.main import Step  from ..core.sdk import ContinueSDK  from .core.core import MessageStep @@ -7,31 +11,42 @@ from .core.core import MessageStep  class NLDecisionStep(Step):      user_input: str +<<<<<<< Updated upstream      steps: List[Step]      hide: bool = True      default_step: Union[Step, None] = None +======= +    steps: List[Tuple[Step, str]] + +    hide: bool = True +>>>>>>> Stashed changes      async def run(self, sdk: ContinueSDK):          step_descriptions = "\n".join([ -            f"- {step.name}: {step.description}" +            f"- {step[0].name}: {step[1]}"              for step in self.steps          ])          prompt = dedent(f"""\ -                        The following steps are available, in the format "- [step name]: [step description]": -                        {step_descriptions} -                         -                        The user gave the following input: -                         -                        {self.user_input} -                         -                        Select the step which should be taken next. Say only the name of the selected step:""") +            The following steps are available, in the format "- [step name]: [step description]": +            {step_descriptions} +             +            The user gave the following input: +             +            {self.user_input} +             +            Select the step which should be taken next to satisfy the user input. Say only the name of the selected step. You must choose one:""")          resp = sdk.models.gpt35.complete(prompt).lower()          step_to_run = None          for step in self.steps: +<<<<<<< Updated upstream              if step.name.lower() in resp:                  step_to_run = step +======= +            if step[0].name.lower() in resp: +                step_to_run = step[0] +>>>>>>> Stashed changes          step_to_run = step_to_run or self.default_step or self.steps[0] | 
