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] |