summaryrefslogtreecommitdiff
path: root/server/continuedev/libs/util/calculate_diff.py
diff options
context:
space:
mode:
authorNate Sesti <33237525+sestinj@users.noreply.github.com>2023-10-09 18:37:27 -0700
committerGitHub <noreply@github.com>2023-10-09 18:37:27 -0700
commitf09150617ed2454f3074bcf93f53aae5ae637d40 (patch)
tree5cfe614a64d921dfe58b049f426d67a8b832c71f /server/continuedev/libs/util/calculate_diff.py
parent985304a213f620cdff3f8f65f74ed7e3b79be29d (diff)
downloadsncontinue-f09150617ed2454f3074bcf93f53aae5ae637d40.tar.gz
sncontinue-f09150617ed2454f3074bcf93f53aae5ae637d40.tar.bz2
sncontinue-f09150617ed2454f3074bcf93f53aae5ae637d40.zip
Preview (#541)
* Strong typing (#533) * refactor: :recycle: get rid of continuedev.src.continuedev structure * refactor: :recycle: switching back to server folder * feat: :sparkles: make config.py imports shorter * feat: :bookmark: publish as pre-release vscode extension * refactor: :recycle: refactor and add more completion params to ui * build: :building_construction: download from preview S3 * fix: :bug: fix paths * fix: :green_heart: package:pre-release * ci: :green_heart: more time for tests * fix: :green_heart: fix build scripts * fix: :bug: fix import in run.py * fix: :bookmark: update version to try again * ci: 💚 Update package.json version [skip ci] * refactor: :fire: don't check for old extensions version * fix: :bug: small bug fixes * fix: :bug: fix config.py import paths * ci: 💚 Update package.json version [skip ci] * ci: :green_heart: platform-specific builds test #1 * feat: :green_heart: ship with binary * fix: :green_heart: fix copy statement to include.exe for windows * fix: :green_heart: cd extension before packaging * chore: :loud_sound: count tokens generated * fix: :green_heart: remove npm_config_arch * fix: :green_heart: publish as pre-release! * chore: :bookmark: update version * perf: :green_heart: hardcode distro paths * fix: :bug: fix yaml syntax error * chore: :bookmark: update version * fix: :green_heart: update permissions and version * feat: :bug: kill old server if needed * feat: :lipstick: update marketplace icon for pre-release * ci: 💚 Update package.json version [skip ci] * feat: :sparkles: auto-reload for config.py * feat: :wrench: update default config.py imports * feat: :sparkles: codelens in config.py * feat: :sparkles: select model param count from UI * ci: 💚 Update package.json version [skip ci] * feat: :sparkles: more model options, ollama error handling * perf: :zap: don't show server loading immediately * fix: :bug: fixing small UI details * ci: 💚 Update package.json version [skip ci] * feat: :rocket: headers param on LLM class * fix: :bug: fix headers for openai.;y * feat: :sparkles: highlight code on cmd+shift+L * ci: 💚 Update package.json version [skip ci] * feat: :lipstick: sticky top bar in gui.tsx * fix: :loud_sound: websocket logging and horizontal scrollbar * ci: 💚 Update package.json version [skip ci] * feat: :sparkles: allow AzureOpenAI Service through GGML * ci: 💚 Update package.json version [skip ci] * fix: :bug: fix automigration * ci: 💚 Update package.json version [skip ci] * ci: :green_heart: upload binaries in ci, download apple silicon * chore: :fire: remove notes * fix: :green_heart: use curl to download binary * fix: :green_heart: set permissions on apple silicon binary * fix: :green_heart: testing * fix: :green_heart: cleanup file * fix: :green_heart: fix preview.yaml * fix: :green_heart: only upload once per binary * fix: :green_heart: install rosetta * ci: :green_heart: download binary after tests * ci: 💚 Update package.json version [skip ci] * ci: :green_heart: prepare ci for merge to main --------- Co-authored-by: GitHub Action <action@github.com>
Diffstat (limited to 'server/continuedev/libs/util/calculate_diff.py')
-rw-r--r--server/continuedev/libs/util/calculate_diff.py154
1 files changed, 154 insertions, 0 deletions
diff --git a/server/continuedev/libs/util/calculate_diff.py b/server/continuedev/libs/util/calculate_diff.py
new file mode 100644
index 00000000..99301ae7
--- /dev/null
+++ b/server/continuedev/libs/util/calculate_diff.py
@@ -0,0 +1,154 @@
+import difflib
+from typing import List
+
+from ...models.filesystem import FileEdit
+from ...models.main import Position, Range
+
+
+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]:
+ # original_lines = original.splitlines()
+ # updated_lines = updated.splitlines()
+ # offset = 0
+ # while len(original_lines) and len(updated_lines) and original_lines[0] == updated_lines[0]:
+ # original_lines = original_lines[1:]
+ # updated_lines = updated_lines[1:]
+
+ # while len(original_lines) and len(updated_lines) and original_lines[-1] == updated_lines[-1]:
+ # original_lines = original_lines[:-1]
+ # updated_lines = updated_lines[:-1]
+
+ # original = "\n".join(original_lines)
+ # updated = "\n".join(updated_lines)
+
+ 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:
+ 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 :]
+ )
+
+ 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)