Adjust some stuff

This commit is contained in:
Silvano Cerza 2025-03-05 16:46:11 +01:00
parent 132ffff36e
commit 08d5d05331

View file

@ -1,23 +1,7 @@
import * as React from "react";
import CodeMirror, {
EditorState,
RangeSetBuilder,
StateEffect,
StateField,
} from "@uiw/react-codemirror";
import { unifiedMergeView } from "@codemirror/merge";
import {
Decoration,
DecorationSet,
EditorView,
GutterMarker,
keymap,
ViewPlugin,
ViewUpdate,
WidgetType,
} from "@codemirror/view";
import { ChangeSet, Prec, RangeSet, Text } from "@codemirror/state";
import { markdown } from "@codemirror/lang-markdown";
import CodeMirror, { RangeSetBuilder, StateField } from "@uiw/react-codemirror";
import { Decoration, EditorView } from "@codemirror/view";
import diff, { DiffChunk } from "./diff";
const styles = document.createElement("style");
@ -170,152 +154,21 @@ const createUnifiedDocument = (
return { doc: result.join("\n"), mapping: lineMappings };
};
const getLineDecoration = (mapping: LineMapping) => {
if (mapping.oldLine !== null && mapping.newLine !== null) {
// No decoration for this case
return null;
}
if (mapping.oldLine === null) {
// The line comes from the new document, it's an addition
return Decoration.line({ class: "cm-addedLine" });
} else if (mapping.newLine === null) {
// The line comes from the old document, it's a deletion
return Decoration.line({ class: "cm-deletedLine" });
}
return null;
};
function findMappingsRanges(mappings: LineMapping[]): number[][] {
const ranges: number[][] = [];
let currentRange: number[] | null = null;
for (const mapping of mappings) {
// If either oldLine or newLine is null
if (mapping.oldLine === null || mapping.newLine === null) {
if (!currentRange) {
currentRange = [mapping.finalLine, mapping.finalLine];
} else {
currentRange[1] = mapping.finalLine;
}
} else if (currentRange) {
ranges.push(currentRange);
currentRange = null;
}
}
if (currentRange) {
ranges.push(currentRange);
}
// Return the first range found
return ranges;
}
const buildDecorations = (mappings: LineMapping[]) => {
return ViewPlugin.fromClass(
class {
decorations: DecorationSet;
mappings: LineMapping[];
constructor(view: EditorView) {
this.mappings = mappings;
this.decorations = this.buildDecorations(view);
}
update(update: ViewUpdate) {
if (!update.docChanged) {
// Ignore non text changes
return;
}
// console.log("Previous selection\n", update.startState.selection);
// console.log("Current selection\n", update.state.selection);
const ranges = findMappingsRanges(this.mappings);
const affectedRanges = ranges.filter((range) => {
return (
update.changes.touchesRange(
update.startState.doc.line(range[0]).from,
update.startState.doc.line(range[1]).to,
) !== false
);
});
console.log(affectedRanges);
// update.changes.touchesRange(from);
update.changes.iterChanges(
(
fromA: number,
toA: number,
fromB: number,
toB: number,
inserted: Text,
) => {
// update.startState.doc.lineAt(fromA);
// update.startState.doc.lineAt(toA);
// console.log("fromA", fromA);
// console.log("toA", toA);
// console.log("fromB", fromB);
// console.log("toB", toB);
// console.log("inserted", inserted);
},
);
this.decorations = this.buildDecorations(update.view);
}
buildDecorations(view: EditorView): DecorationSet {
const decorations = [];
const doc = view.state.doc;
for (let lineNumber = 1; lineNumber <= doc.lines; lineNumber++) {
const mapping = this.mappings.find((m) => m.finalLine === lineNumber);
if (!mapping) {
// Unlikely
continue;
}
const deco = getLineDecoration(mapping);
if (deco) {
const line = doc.line(lineNumber);
decorations.push(deco.range(line.from));
}
}
return Decoration.set(decorations, true);
}
},
{ decorations: (v) => v.decorations },
);
};
interface ConflictRange {
from: number;
to: number;
source: "old" | "new" | "both"; // where the content originated
}
const UnifiedDiffView: React.FC<UnifiedDiffViewProps> = ({
initialOldText,
initialNewText,
onConflictResolved,
}) => {
const diffChunks = diff(initialOldText, initialNewText);
const { doc: unified, mapping } = createUnifiedDocument(
initialOldText,
initialNewText,
diffChunks,
);
const unifiedLines = unified.split("\n");
const initialRanges: ConflictRange[] = mapping.map((m) => {
const createInitialRanges = (
doc: string,
mappings: LineMapping[],
): ConflictRange[] => {
const lines = doc.split("\n");
return mappings.map((m) => {
// Calculate line positions directly from text
const lineStartPos = unifiedLines
.slice(0, m.finalLine - 1)
.join("\n").length;
const lineStartPos = lines.slice(0, m.finalLine - 1).join("\n").length;
// Add 1 for the newline except for first line
const fromPos = m.finalLine > 1 ? lineStartPos + 1 : 0;
const toPos = fromPos + unifiedLines[m.finalLine - 1].length;
const from = m.finalLine > 1 ? lineStartPos + 1 : 0;
const to = from + lines[m.finalLine - 1].length;
return {
from: fromPos,
to: toPos,
from,
to,
source:
m.oldLine !== null && m.newLine !== null
? "both"
@ -324,8 +177,12 @@ const UnifiedDiffView: React.FC<UnifiedDiffViewProps> = ({
: "new",
};
});
};
const conflictRangesField = StateField.define<ConflictRange[]>({
const createRangesStateField = (
initialRanges: ConflictRange[],
): StateField<ConflictRange[]> => {
return StateField.define<ConflictRange[]>({
create: () => initialRanges,
update: (ranges, tr) => {
if (!tr.docChanged) {
@ -348,41 +205,64 @@ const UnifiedDiffView: React.FC<UnifiedDiffViewProps> = ({
return newRanges;
},
});
};
const buildDecorationsAlternate = EditorView.decorations.compute(
["doc", conflictRangesField],
(state) => {
const ranges = state.field(conflictRangesField);
const builder = new RangeSetBuilder<Decoration>();
const createDecorationsExtension = (
rangesStateField: StateField<ConflictRange[]>,
) => {
return EditorView.decorations.compute(["doc", rangesStateField], (state) => {
const ranges = state.field(rangesStateField);
const builder = new RangeSetBuilder<Decoration>();
for (const range of ranges) {
const startLine = state.doc.lineAt(range.from);
const endLine = state.doc.lineAt(range.to);
for (let i = 0; i <= endLine.number - startLine.number; i += 1) {
const line = state.doc.line(startLine.number + i);
if (range.source === "old") {
builder.add(
line.from,
line.from,
Decoration.line({ class: "cm-deletedLine" }),
);
} else if (range.source === "new") {
builder.add(
line.from,
line.from,
Decoration.line({ class: "cm-addedLine" }),
);
}
for (const range of ranges) {
const startLine = state.doc.lineAt(range.from);
const endLine = state.doc.lineAt(range.to);
for (let i = 0; i <= endLine.number - startLine.number; i += 1) {
const line = state.doc.line(startLine.number + i);
if (range.source === "old") {
builder.add(
line.from,
line.from,
Decoration.line({ class: "cm-deletedLine" }),
);
} else if (range.source === "new") {
builder.add(
line.from,
line.from,
Decoration.line({ class: "cm-addedLine" }),
);
}
}
}
return builder.finish();
},
return builder.finish();
});
};
interface ConflictRange {
from: number;
to: number;
source: "old" | "new" | "both"; // where the content originated
}
const UnifiedDiffView: React.FC<UnifiedDiffViewProps> = ({
initialOldText,
initialNewText,
onConflictResolved,
}) => {
const diffChunks = diff(initialOldText, initialNewText);
const { doc, mapping } = createUnifiedDocument(
initialOldText,
initialNewText,
diffChunks,
);
const initialRanges = createInitialRanges(doc, mapping);
const conflictRangesField = createRangesStateField(initialRanges);
const extensions = [
conflictRangesField,
buildDecorationsAlternate,
createDecorationsExtension(conflictRangesField),
EditorView.editable.of(true),
EditorView.theme({
"&": {
@ -410,8 +290,7 @@ const UnifiedDiffView: React.FC<UnifiedDiffViewProps> = ({
return (
<div style={{ width: "100%", height: "100%", overflow: "hidden" }}>
<CodeMirror
// value={newText}
value={unified}
value={doc}
height="100%"
theme="none"
basicSetup={false}