Bad attempt at diff apply

This commit is contained in:
Silvano Cerza 2025-02-18 11:13:13 +01:00
parent 5edb8ca742
commit 63f9afe738
4 changed files with 202 additions and 34 deletions

View file

@ -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<DiffViewProps> = ({ 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<DiffViewProps> = ({ 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<DiffViewProps> = ({ oldText, newText, onResolve }) => {
});
const view = new EditorView({
state,
state: editorState,
parent: container,
});
@ -131,6 +131,96 @@ const DiffView: React.FC<DiffViewProps> = ({ 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 (
<div
ref={containerRef}
@ -162,6 +252,7 @@ const DiffView: React.FC<DiffViewProps> = ({ oldText, newText, onResolve }) => {
differences={diff(oldText, newText)}
originalEditor={originalEditorView}
modifiedEditor={modifiedEditorView}
onMerge={handleMerge}
/>
</div>
<div

View file

@ -1,4 +1,4 @@
import { App, setIcon } from "obsidian";
import { setIcon } from "obsidian";
import { useEffect, useState } from "react";
import { EditorView } from "@codemirror/view";
import { DiffResult } from "./diff";
@ -8,22 +8,31 @@ interface DiffConnectionProps {
differences: DiffResult[];
originalEditor: EditorView | null;
modifiedEditor: EditorView | null;
onMerge: (from: "left" | "right", chunk: ConnectionChunk) => 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<DiffConnectionProps> = ({
differences,
originalEditor,
modifiedEditor,
onMerge,
}) => {
const [connections, setConnections] = useState<ConnectionChunk[]>([]);
const plugin = usePlugin();
@ -33,7 +42,10 @@ const DiffConnections: React.FC<DiffConnectionProps> = ({
}
useEffect(() => {
if (!originalEditor || !modifiedEditor) return;
if (!originalEditor || !modifiedEditor) {
// Editors still not initialized
return;
}
const updateConnections = () => {
requestAnimationFrame(() => {
@ -153,15 +165,90 @@ const DiffConnections: React.FC<DiffConnectionProps> = ({
}
}
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<DiffConnectionProps> = ({
/>
<ConnectionButtons
chunk={chunk}
onAction={(action) => {
console.log("Action:", action, "for chunk:", chunk);
// TODO: Implement actual resolution actions
}}
onAction={(direction) => onMerge(direction, chunk)}
/>
</g>
);

View file

@ -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;

View file

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