From 63f9afe738e77fa9f1a39ddd8c8838a7da65ad11 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Tue, 18 Feb 2025 11:13:13 +0100 Subject: [PATCH] Bad attempt at diff apply --- src/views/conflicts-resolution/component.tsx | 99 ++++++++++++++- .../conflicts-resolution/diff-connections.tsx | 114 +++++++++++++++--- src/views/conflicts-resolution/diff.ts | 17 +-- src/views/conflicts-resolution/view.tsx | 6 +- 4 files changed, 202 insertions(+), 34 deletions(-) diff --git a/src/views/conflicts-resolution/component.tsx b/src/views/conflicts-resolution/component.tsx index 5dddcb0..8cb7049 100644 --- a/src/views/conflicts-resolution/component.tsx +++ b/src/views/conflicts-resolution/component.tsx @@ -4,7 +4,7 @@ import { EditorState } from "@codemirror/state"; import { markdown } from "@codemirror/lang-markdown"; import diff from "./diff"; import { createDiffHighlightPlugin } from "./diff-highlight-plugin"; -import DiffConnections from "./diff-connections"; +import DiffConnections, { ConnectionChunk } from "./diff-connections"; // Add styles for diff highlighting const styles = document.createElement("style"); @@ -45,7 +45,7 @@ const DiffView: React.FC = ({ oldText, newText, onResolve }) => { readOnly: boolean = false, isOriginal: boolean = false, ) => { - const differences = diff(oldText, newText); + const differences = diff(oldText || "", newText || ""); const highlightPlugin = createDiffHighlightPlugin({ diff: differences, @@ -62,7 +62,7 @@ const DiffView: React.FC = ({ oldText, newText, onResolve }) => { ] : []; - const state = EditorState.create({ + const editorState = EditorState.create({ doc: content, extensions: [ // basicSetup minus line numbers @@ -90,7 +90,7 @@ const DiffView: React.FC = ({ oldText, newText, onResolve }) => { }); const view = new EditorView({ - state, + state: editorState, parent: container, }); @@ -131,6 +131,96 @@ const DiffView: React.FC = ({ oldText, newText, onResolve }) => { }; }, [oldText, newText]); + const handleMerge = (direction: "left" | "right", chunk: ConnectionChunk) => { + if (!originalEditorView || !modifiedEditorView) { + return; + } + + const sourceEditor = + direction === "left" ? modifiedEditorView : originalEditorView; + const targetEditor = + direction === "left" ? originalEditorView : modifiedEditorView; + + const sourceLine = + direction === "left" ? chunk.targetStartLine : chunk.startLine; + const sourceEndLine = + direction === "left" ? chunk.targetEndLine : chunk.endLine; + const targetLine = + direction === "left" ? chunk.startLine : chunk.targetStartLine; + const targetEndLine = + direction === "left" ? chunk.endLine : chunk.targetEndLine; + + const sourceDoc = sourceEditor.state.doc; + const targetDoc = targetEditor.state.doc; + + // Handle source content + let contentToInsert = ""; + if (sourceLine > sourceDoc.lines) { + // Adding new lines at the end + contentToInsert = "\n" + chunk.content.join("\n"); + } else { + const sourceFrom = sourceDoc.line(sourceLine).from; + const sourceTo = + sourceEndLine <= sourceDoc.lines + ? sourceDoc.line(sourceEndLine).to + : sourceDoc.length; + contentToInsert = sourceEditor.state.sliceDoc(sourceFrom, sourceTo); + } + + // Handle target position + let targetFrom = 0; + let targetTo = 0; + + if (targetLine > targetDoc.lines) { + // Appending to end of file + targetFrom = targetTo = targetDoc.length; + if (!contentToInsert.startsWith("\n") && targetDoc.length > 0) { + contentToInsert = "\n" + contentToInsert; + } + } else { + targetFrom = targetDoc.line(targetLine).from; + targetTo = + targetEndLine <= targetDoc.lines + ? targetDoc.line(targetEndLine).to + : targetDoc.length; + } + + // Apply the change + const change = { + from: targetFrom, + to: targetTo, + insert: contentToInsert, + }; + + targetEditor.dispatch({ changes: change }); + + // Update result editor if it exists + // if (resultEditor) { + // const resultDoc = resultEditor.state.doc; + // let resultFrom = targetFrom; + // let resultTo = targetTo; + + // // Handle case where result editor might have different content length + // if (resultFrom > resultDoc.length) { + // resultFrom = resultTo = resultDoc.length; + // if (!contentToInsert.startsWith("\n") && resultDoc.length > 0) { + // contentToInsert = "\n" + contentToInsert; + // } + // } + + // resultEditor.dispatch({ + // changes: { + // from: resultFrom, + // to: resultTo, + // insert: contentToInsert, + // }, + // }); + // } + + // Notify parent + onResolve(targetEditor.state.doc.toString()); + }; + return (
= ({ oldText, newText, onResolve }) => { differences={diff(oldText, newText)} originalEditor={originalEditorView} modifiedEditor={modifiedEditorView} + onMerge={handleMerge} />
void; } type ConnectionType = "add" | "remove" | "modify"; -type ConnectionChunk = { +export type ConnectionChunk = { + // For rendering startTop: number; startBottom: number; endTop: number; endBottom: number; + // For merging + startLine: number; + endLine: number; + targetStartLine: number; + targetEndLine: number; type: ConnectionType; + content: string[]; }; const DiffConnections: React.FC = ({ differences, originalEditor, modifiedEditor, + onMerge, }) => { const [connections, setConnections] = useState([]); const plugin = usePlugin(); @@ -33,7 +42,10 @@ const DiffConnections: React.FC = ({ } useEffect(() => { - if (!originalEditor || !modifiedEditor) return; + if (!originalEditor || !modifiedEditor) { + // Editors still not initialized + return; + } const updateConnections = () => { requestAnimationFrame(() => { @@ -153,15 +165,90 @@ const DiffConnections: React.FC = ({ } } - return { - startTop: startTop === Infinity ? endTop : startTop, - startBottom: startBottom === -Infinity ? endBottom : startBottom, - endTop: endTop === Infinity ? startTop : endTop, - endBottom: endBottom === -Infinity ? startBottom : endBottom, - type: (chunk.some((d) => d.type === "modify") - ? "modify" - : chunk[0].type) as ConnectionType, + // Track actual content and line numbers + const firstDiff = chunk[0]; + const lastDiff = chunk[chunk.length - 1]; + + // For original (left) content + const originalDoc = originalEditor.state.doc; + let startLine = 0; + let endLine = 0; + if (firstDiff.type === "remove" || firstDiff.type === "modify") { + for (let i = 1; i <= originalDoc.lines + 1; i++) { + if (i <= originalDoc.lines) { + const line = originalDoc.line(i); + const content = firstDiff.oldValue || firstDiff.value; + if (line.text === content) { + startLine = i; + break; + } + } else if (startLine === 0) { + // Content should be added at the end + startLine = originalDoc.lines + 1; + } + } + + if (lastDiff.type === "remove" || lastDiff.type === "modify") { + endLine = + startLine + + chunk.filter((d) => d.type === "remove" || d.type === "modify") + .length - + 1; + } else { + endLine = startLine; + } + } + + // For modified (right) content + const modifiedDoc = modifiedEditor.state.doc; + let targetStartLine = 0; + let targetEndLine = 0; + if (firstDiff.type === "add" || firstDiff.type === "modify") { + for (let i = 1; i <= modifiedDoc.lines + 1; i++) { + if (i <= modifiedDoc.lines) { + const line = modifiedDoc.line(i); + if (line.text === firstDiff.value) { + targetStartLine = i; + break; + } + } else if (targetStartLine === 0) { + // Content should be added at the end + targetStartLine = modifiedDoc.lines + 1; + } + } + + if (lastDiff.type === "add" || lastDiff.type === "modify") { + targetEndLine = + targetStartLine + + chunk.filter((d) => d.type === "add" || d.type === "modify").length - + 1; + } else { + targetEndLine = targetStartLine; + } + } + + // Collect the actual content + const content = chunk + .filter((d) => d.type === "add" || d.type === "modify") + .map((d) => d.value); + + const res = { + startTop, + startBottom, + endTop, + endBottom, + startLine, + endLine, + targetStartLine, + targetEndLine, + type: chunk[0].type as ConnectionType, + content, }; + + console.log("ProcessChunk Result"); + console.log(res); + + return res; }; return ( @@ -192,10 +279,7 @@ const DiffConnections: React.FC = ({ /> { - console.log("Action:", action, "for chunk:", chunk); - // TODO: Implement actual resolution actions - }} + onAction={(direction) => onMerge(direction, chunk)} /> ); diff --git a/src/views/conflicts-resolution/diff.ts b/src/views/conflicts-resolution/diff.ts index e45eee4..074d723 100644 --- a/src/views/conflicts-resolution/diff.ts +++ b/src/views/conflicts-resolution/diff.ts @@ -13,6 +13,7 @@ function diff(oldText: string, newText: string): DiffResult[] { const oldLines = oldText.split("\n"); const newLines = newText.split("\n"); const result: DiffResult[] = []; + // This is an index in oldText let position = 0; // First pass: find exact matches and obvious modifications @@ -56,22 +57,12 @@ function diff(oldText: string, newText: string): DiffResult[] { }); } - position += - Math.max(oldLine ? oldLine.length : 0, newLine ? newLine.length : 0) + 1; + const oldLineLength = oldLine ? oldLine.length : 0; + const newLineLength = newLine ? newLine.length : 0; + position += Math.max(oldLineLength, newLineLength) + 1; } return result; } -function similarity(s1: string, s2: string): number { - if (s1 === s2) return 1.0; - - // Simple word-based similarity - const words1 = s1.split(/\s+/); - const words2 = s2.split(/\s+/); - - const commonWords = words1.filter((w) => words2.includes(w)).length; - return commonWords / Math.max(words1.length, words2.length); -} - export default diff; diff --git a/src/views/conflicts-resolution/view.tsx b/src/views/conflicts-resolution/view.tsx index 9c5ed5c..f0b5615 100644 --- a/src/views/conflicts-resolution/view.tsx +++ b/src/views/conflicts-resolution/view.tsx @@ -10,13 +10,15 @@ export const CONFLICTS_RESOLUTION_VIEW_TYPE = "conflicts-resolution-view"; const oldText1 = `# My Document This is a test Some content here +Some line +Another line Final line`; const newText1 = `# My Document This is a modified test Some new content here -Final line -Added line`; +Some line +Final line`; // Test Case 2: Markdown with formatting const oldText2 = `# Title