summaryrefslogtreecommitdiff
path: root/continuedev/src
diff options
context:
space:
mode:
authorNate Sesti <sestinj@gmail.com>2023-06-12 10:50:33 -0700
committerNate Sesti <sestinj@gmail.com>2023-06-12 10:50:33 -0700
commit792b65745b89bb59802294357378113493d25b63 (patch)
tree1d038ca5e1ac837fa704b454136f597bdd02797d /continuedev/src
parentc66f39173bec7e45955dc8b96ae6c44ed5bdc162 (diff)
downloadsncontinue-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.py3
-rw-r--r--continuedev/src/continuedev/libs/util/calculate_diff.py152
-rw-r--r--continuedev/src/continuedev/libs/util/copy_codebase.py38
-rw-r--r--continuedev/src/continuedev/models/filesystem_edit.py4
-rw-r--r--continuedev/src/continuedev/steps/chat.py12
-rw-r--r--continuedev/src/continuedev/steps/react.py33
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]